%% ===== BEC-Droplets-Stripes Settings ===== % Specify data location to run analysis on dataSources = { struct('sequence', 'TwoDGas', ... 'date', '2025/06/23', ... 'runs', [300]) % specify run numbers as a string in "" or just as a numeric value }; options = struct(); % File paths options.baseDataFolder = '//DyLabNAS/Data'; options.FullODImagesFolder = 'E:/Data - Experiment/FullODImages/202506'; options.measurementName = 'DropletsToStripes'; scriptFullPath = mfilename('fullpath'); options.saveDirectory = fileparts(scriptFullPath); % Camera / imaging settings options.cam = 4; % 1 - ODT_1_Axis_Camera; 2 - ODT_2_Axis_Camera; 3 - Horizontal_Axis_Camera;, 4 - Vertical_Axis_Camera; options.angle = 0; % angle by which image will be rotated options.center = [1410, 2030]; options.span = [200, 200]; options.fraction = [0.1, 0.1]; options.pixel_size = 5.86e-6; % in meters options.magnification = 23.94; options.ImagingMode = 'HighIntensity'; options.PulseDuration = 5e-6; % in s % Fourier analysis settings options.theta_min = deg2rad(0); options.theta_max = deg2rad(180); options.N_radial_bins = 500; options.Radial_Sigma = 2; options.Radial_WindowSize = 5; % odd number options.k_min = 1.2771; % μm⁻¹ options.k_max = 2.5541; % μm⁻¹ options.N_angular_bins = 360; options.Angular_Threshold = 75; options.Angular_Sigma = 2; options.Angular_WindowSize = 5; options.zoom_size = 50; % options.maximumShift = 8; options.Radial_Theta = deg2rad(45); options.Radial_Minimum = 2; options.Radial_Maximum = 6; options.skipLivePlot = false; % Flags options.skipUnshuffling = false; options.skipNormalization = false; options.skipFringeRemoval = true; options.skipPreprocessing = true; options.skipMasking = true; options.skipIntensityThresholding = true; options.skipBinarization = true; options.skipFullODImagesFolderUse = false; options.skipSaveData = false; options.skipSaveFigures = true; options.skipSaveProcessedOD = true; options.skipLivePlot = false; options.showProgressBar = true; % Extras options.font = 'Bahnschrift'; switch options.measurementName case 'BECToDroplets' options.scan_parameter = 'rot_mag_field'; options.flipSortOrder = true; options.scanParameterUnits = 'gauss'; options.titleString = 'BEC to Droplets'; case 'BECToStripes' options.scan_parameter = 'rot_mag_field'; options.flipSortOrder = true; options.scanParameterUnits = 'gauss'; options.titleString = 'BEC to Stripes'; case 'DropletsToStripes' options.scan_parameter = 'ps_rot_mag_fin_pol_angle'; options.flipSortOrder = false; options.scanParameterUnits = 'degrees'; options.titleString = 'Droplets to Stripes'; case 'StripesToDroplets' options.scan_parameter = 'ps_rot_mag_fin_pol_angle'; options.flipSortOrder = false; options.scanParameterUnits = 'degrees'; options.titleString = 'Stripes to Droplets'; end %% ===== Collect Images and Launch Viewer ===== [options.selectedPath, options.folderPath] = Helper.selectDataSourcePath(dataSources, options); [od_imgs, scan_parameter_values, scan_reference_values, file_list] = Helper.collectODImages(options); %% Conduct spectral analysis spectral_analysis_results = Analyzer.extractFullAngularSpectralDistribution(od_imgs, options); %% ------------------ 1. Plot of all Angular Spectral Distribution Curves ------------------ Plotter.plotSpectralCurves( ... spectral_analysis_results.S_theta_norm_all, ... spectral_analysis_results.theta_vals/pi, ... % correct θ values scan_reference_values, ... % correct scan params 'Title', options.titleString, ... 'XLabel', '\theta / \pi', ... 'YLabel', 'S(\theta)', ... 'HighlightEvery', 10, ... % highlight every 10th repetition 'FontName', options.font, ... 'FigNum', 1, ... 'TileTitlePrefix', '\alpha', ... % user-defined tile prefix 'TileTitleSuffix', '^\circ', ... % user-defined suffix (e.g., degrees symbol) 'SkipSaveFigures', options.skipSaveFigures, ... 'SaveFileName', 'SpectralCurves.fig', ... 'SaveDirectory', options.saveDirectory); %% ------------------ 2. Plot of all Angular Spectral Distribution Curves shifted ------------------ % --- Recenter curves first --- results = Analyzer.recenterSpectralCurves(spectral_analysis_results.S_theta_norm_all, ... spectral_analysis_results.theta_vals/pi, ... scan_reference_values, ... 'SearchRange', [0 90]); % degrees % --- Restrict to desired theta range (e.g., 0 to 0.5*pi) --- thetaMin = 0; % in units of pi (since you divided by pi) thetaMax = 1; % corresponds to pi/2 mask = results.x_values >= thetaMin & results.x_values <= thetaMax; results.x_values = results.x_values(mask); % Apply the same mask to each curve set for i = 1:numel(results.curves) results.curves{i} = results.curves{i}(:, mask); results.curves_mean{i} = results.curves_mean{i}(mask); results.curves_error{i}= results.curves_error{i}(mask); end Plotter.plotSpectralCurvesRecentered( ... results, ... scan_reference_values, ... % correct scan params 'Title', options.titleString, ... 'XLabel', '\theta / \pi', ... 'YLabel', 'S(\theta)', ... 'HighlightEvery', 10, ... % highlight every 10th repetition 'FontName', options.font, ... 'FigNum', 2, ... 'TileTitlePrefix', '\alpha', ... % user-defined tile prefix 'TileTitleSuffix', '^\circ', ... % user-defined suffix (e.g., degrees symbol) 'SkipSaveFigures', options.skipSaveFigures, ... 'SaveFileName', 'SpectralCurves.fig', ... 'SaveDirectory', options.saveDirectory); %% ------------------ 3. Plot cumulants from shifted Angular Spectral Distribution Curves ------------------ Plotter.plotSpectralDistributionCumulants(results, ... 'Title', options.titleString, ... 'XLabel', '\alpha (degrees)', ... 'FontName', options.font, ... 'FontSize', 14, ... 'FigNum', 3, ... 'SkipSaveFigures', false, ... 'SaveFileName', 'SpectralCumulants.fig'); %% ------------------ 4. Fit shifted Angular Spectral Distribution Curves ------------------ [fitResults, rawCurves] = Analyzer.fitTwoGaussianCurves(... spectral_analysis_results.S_theta_norm_all, ... spectral_analysis_results.theta_vals, ... 'MaxTheta', pi/2, ... 'ResidualThreshold', 0.15, ... 'PositionThreshold', pi/15, ... 'AmplitudeThreshold', 0.15); %{ % --- Function call --- plotTwoGaussianFitsOnRaw(fitResults, rawCurves, 8, 12); % 8×12 subplots per page % --- Function definition --- function plotTwoGaussianFitsOnRaw(fitResults, rawCurves, nRows, nCols) % plotTwoGaussianFitsOnRaw - Plots raw curves and overlays valid two-Gaussian fits. % % Inputs: % fitResults - struct array from fitTwoGaussianCurves (may contain fits) % rawCurves - struct array with fields: % .x - raw curve % .theta - corresponding theta values % nRows - number of subplot rows per page (default: 4) % nCols - number of subplot columns per page (default: 6) if nargin < 3, nRows = 4; end if nargin < 4, nCols = 6; end Ncurves = numel(rawCurves); plotsPerPage = nRows * nCols; pageNum = 1; for k = 1:Ncurves % --- New figure/page if needed --- if mod(k-1, plotsPerPage) == 0 figure('Name', sprintf('Curves Page %d', pageNum), ... 'NumberTitle', 'off', 'Color', 'w'); pageNum = pageNum + 1; end % --- Select subplot --- subplot(nRows, nCols, mod(k-1, plotsPerPage)+1); hold on; grid on; box on; % --- Plot raw curve --- xRaw = rawCurves(k).x; thetaRaw = rawCurves(k).theta; if ~isempty(xRaw) && ~isempty(thetaRaw) && all(isfinite(xRaw)) plot(thetaRaw, xRaw, 'k.-', 'LineWidth', 1, 'DisplayName', 'Raw data'); end % --- Overlay fit if valid --- if k <= numel(fitResults) fit = fitResults(k); if isfield(fit, 'isValid') && fit.isValid ... && ~isempty(fit.yFit) && ~isempty(fit.thetaFine) plot(fit.thetaFine, fit.yFit, 'r-', 'LineWidth', 1.2, 'DisplayName', 'Two-Gaussian fit'); end end % --- Formatting --- xlabel('\theta (rad)'); ylabel('Normalized amplitude'); title(sprintf('Curve %d', k), 'FontSize', 10); % --- Legend once per page --- if mod(k-1, plotsPerPage) == 0 legend('Location', 'best', 'FontSize', 8); end hold off; end end %} %% ------------------ 5. Plot fit parameters - amplitude ------------------ Plotter.plotFitParameterPDF(fitResults, scan_reference_values, 'A2', ... 'Title', options.titleString, ... 'XLabel', '\alpha (degrees)', ... 'YLabel', 'Secondary peak amplitude', ... 'FontName', options.font, ... 'FontSize', 16, ... 'FigNum', 4, ... 'SkipSaveFigures', options.skipSaveFigures, ... 'SaveFileName', 'SecondaryPeakAmplitudePDF.fig', ... 'PlotType', 'histogram', ... 'NumberOfBins', 20, ... 'NormalizeHistogram', true, ... 'Colormap', @Colormaps.coolwarm, ... 'XLim', [min(scan_reference_values), max(scan_reference_values)], ... 'YLim', [0, 1.6]); %% ------------------ 6. Plot fit parameters - position ------------------ Plotter.plotFitParameterPDF(fitResults, scan_reference_values, 'mu2', ... 'Title', options.titleString, ... 'XLabel', '\alpha (degrees)', ... 'YLabel', 'Secondary peak position (\theta, rad)', ... 'FontName', options.font, ... 'FontSize', 16, ... 'FigNum', 5, ... 'SkipSaveFigures', options.skipSaveFigures, ... 'SaveFileName', 'SecondaryPeakPositionPDF.fig', ... 'PlotType', 'histogram', ... 'NumberOfBins', 20, ... 'NormalizeHistogram', true, ... 'Colormap', @Colormaps.coolwarm, ... 'XLim', [min(scan_reference_values), max(scan_reference_values)], ... 'YLim', [0, 1.6]); %% ------------------ 7. Plot fit parameters - width ------------------ Plotter.plotFitParameterPDF(fitResults, scan_reference_values, 'sigma2', ... 'Title', options.titleString, ... 'XLabel', '\alpha (degrees)', ... 'YLabel', 'Secondary peak width (\sigma, rad)', ... 'FontName', options.font, ... 'FontSize', 16, ... 'FigNum', 6, ... 'SkipSaveFigures', options.skipSaveFigures, ... 'SaveFileName', 'SecondaryPeakWidthPDF.fig', ... 'PlotType', 'histogram', ... 'NumberOfBins', 20, ... 'NormalizeHistogram', true, ... 'Colormap', @Colormaps.coolwarm, ... 'XLim', [min(scan_reference_values), max(scan_reference_values)], ... 'YLim', [0, 1.6]); %% ------------------ 8. Plot fit parameters of mean shifted Angular Spectral Distribution Curves ------------------ % --- Recenter curves first --- results = Analyzer.recenterSpectralCurves(spectral_analysis_results.S_theta_norm_all, ... spectral_analysis_results.theta_vals/pi, ... scan_reference_values, ... 'SearchRange', [0 90]); % degrees % --- Restrict to desired theta range (e.g., 0 to 0.5*pi) --- thetaMin = 0; % in units of pi (since you divided by pi) thetaMax = 1; % corresponds to pi/2 mask = results.x_values >= thetaMin & results.x_values <= thetaMax; results.x_values = results.x_values(mask); % Apply the same mask to each curve set for i = 1:numel(results.curves) results.curves_mean{i} = results.curves_mean{i}(mask); end % --- Fit two-Gaussian model to mean curves --- [fitResultsMean, ~] = Analyzer.fitTwoGaussianCurves(... results.curves_mean, ... results.x_values*pi, ... 'MaxTheta', pi/2, ... 'ResidualThreshold', 0.15, ... 'PositionThreshold', pi/15, ... 'AmplitudeThreshold', 0.15, ... 'RecenterCurves', false); % --- Prepare parameter values --- N_params = numel(fitResultsMean); amp2_vals = nan(1, N_params); mu2_vals = nan(1, N_params); sigma2_vals = nan(1, N_params); for i = 1:N_params pFit = fitResultsMean(i).pFit; if all(~isnan(pFit)) % Successful fit → use fitted values amp2_vals(i) = pFit(4); mu2_vals(i) = pFit(5); sigma2_vals(i) = pFit(6); else % Fit failed → leave as NaN (skipped automatically) continue; end end % --- Plot using plotMeanWithSE --- Plotter.plotMeanWithSE(scan_reference_values, amp2_vals, ... 'Title', options.titleString, ... 'XLabel', '\alpha (degrees)', ... 'YLabel', 'Secondary peak amplitude', ... 'FigNum', 7, ... 'FontName', options.font, ... 'FontSize', 16); % --- Plot using plotMeanWithSE --- Plotter.plotMeanWithSE(scan_reference_values, mu2_vals, ... 'Title', options.titleString, ... 'XLabel', '\alpha (degrees)', ... 'YLabel', 'Secondary peak position (\theta, rad)', ... 'FigNum', 8, ... 'FontName', options.font, ... 'FontSize', 16); Plotter.plotMeanWithSE(scan_reference_values, sigma2_vals, ... 'Title', options.titleString, ... 'XLabel', '\alpha (degrees)', ... 'YLabel', 'Secondary peak width (\sigma, rad)', ... 'FigNum', 9, ... 'FontName', options.font, ... 'FontSize', 16); %% Inspect individual realizations of a single parameter % --- Recenter curves first --- results = Analyzer.recenterSpectralCurves( ... spectral_analysis_results.S_theta_norm_all, ... spectral_analysis_results.theta_vals/pi, ... scan_reference_values, ... 'SearchRange', [0 90]); % degrees % --- Restrict to desired theta range (e.g., 0 to 0.5*pi) --- thetaMin = 0; % in units of pi (since you divided by pi) thetaMax = 1; % corresponds to pi/2 mask = results.x_values >= thetaMin & results.x_values <= thetaMax; results.x_values = results.x_values(mask); % --- Apply the same mask to each curve set (1x10 cell, each 60x180) --- for i = 1:numel(results.curves) results.curves{i} = results.curves{i}(:, mask); end % --- Convert selected curve set (e.g., 5th) into 1x60 cell array of 1xN row vectors --- paramIdx = 1; % <-- choose which scan point or curve set to analyze curves_matrix = results.curves{paramIdx}; % 60xN numeric curves_cell = num2cell(curves_matrix, 2); % 1x60 cell array curves_cell = cellfun(@(x) x(:).', curves_cell, 'UniformOutput', false); % ensure 1xN row vectors % --- Fit two-Gaussian model to these curves --- [fitResults, rawCurves] = Analyzer.fitTwoGaussianCurves(... curves_cell, ... results.x_values*pi, ... 'MaxTheta', pi/2, ... 'ResidualThreshold', 0.15, ... 'PositionThreshold', pi/15, ... 'AmplitudeThreshold', 0.15, ... 'RecenterCurves', false);