Added routines to analyze BEC to Droplets, BEC to Stripes.

This commit is contained in:
Karthik 2025-10-15 18:22:46 +02:00
parent c24ab3e994
commit 7904c3aa20
6 changed files with 1710 additions and 0 deletions

View File

@ -0,0 +1,411 @@
%% ===== BEC-Droplets Settings =====
% Specify data location to run analysis on
dataSources = {
struct('sequence', 'StructuralPhaseTransition', ...
'date', '2025/08/13', ...
'runs', [62]) % 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/202508';
options.measurementName = 'BECToDroplets';
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 = [1420, 2050];
options.span = [200, 200];
options.fraction = [0.1, 0.1];
options.pixel_size = 5.86e-6; % in meters
options.magnification = 24.6;
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 = 180;
options.Angular_Threshold = 75;
options.Angular_Sigma = 2;
options.Angular_WindowSize = 5;
options.zoom_size = 50;
% 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', ...
'SaveDirectory', options.saveDirectory);
%% ------------------ 4. Fit shifted Angular Spectral Distribution Curves ------------------
[fitResults, rawCurves] = Analyzer.fitTwoGaussianCurvesToAngularSpectralDistribution(...
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 fitTwoGaussianCurvesToAngularSpectralDistribution (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', ...
'SaveDirectory', options.saveDirectory, ...
'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', ...
'SaveDirectory', options.saveDirectory, ...
'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', ...
'SaveDirectory', options.saveDirectory, ...
'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.fitTwoGaussianCurvesToAngularSpectralDistribution(...
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 peak amplitude mean with SEM ---
Plotter.plotMeanWithSE(scan_reference_values, amp2_vals, ...
'Title', options.titleString, ...
'XLabel', '\alpha (degrees)', ...
'YLabel', 'Secondary peak amplitude', ...
'FigNum', 7, ...
'FontName', options.font, ...
'FontSize', 16, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'SecondaryPeakAmplitudeMeanWithSEM.fig', ...
'SaveDirectory', options.saveDirectory);
% --- Plot peak position mean with SEM ---
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, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'SecondaryPeakPositionMeanWithSEM.fig', ...
'SaveDirectory', options.saveDirectory);
% --- Plot peak width mean with SEM ---
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, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'SecondaryPeakWidthMeanWithSEM.fig', ...
'SaveDirectory', options.saveDirectory);
%% 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.fitTwoGaussianCurvesToAngularSpectralDistribution(...
curves_cell, ...
results.x_values*pi, ...
'MaxTheta', pi/2, ...
'ResidualThreshold', 0.15, ...
'PositionThreshold', pi/15, ...
'AmplitudeThreshold', 0.15, ...
'RecenterCurves', false);

View File

@ -0,0 +1,181 @@
%% ===== BEC-Droplets Settings =====
% Specify data location to run analysis on
dataSources = {
struct('sequence', 'StructuralPhaseTransition', ...
'date', '2025/08/13', ...
'runs', [62]) % 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/202508';
options.measurementName = 'BECToDroplets';
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 = [1420, 2050];
options.span = [200, 200];
options.fraction = [0.1, 0.1];
options.pixel_size = 5.86e-6; % in meters
options.magnification = 24.6;
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 = 180;
options.Angular_Threshold = 75;
options.Angular_Sigma = 2;
options.Angular_WindowSize = 5;
options.zoom_size = 50;
% 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 correlation analysis
options.skipLivePlot = true;
g2_analysis_results = Analyzer.conductCorrelationAnalysis(od_imgs, scan_parameter_values, options);
%% Analyze G2 matrices
% ROI definition
options.roi.center = [3, 3]; % center of ROI in µm (x0, y0)
options.roi.size = [3, 11]; % width and height in µm
options.roi.angle = pi/4; % rotation angle (radians, CCW)
options.threshold = 0.85;
options.fitDeviationThreshold = 0.9;
% Plot control
options.skipLivePlot = true;
analysis_results = Analyzer.analyzeG2Structures(g2_analysis_results, options);
%% Plot raw OD images and the corresponding real space g2 matrix
options.skipLivePlot = true;
options.skipSaveFigures = true;
Plotter.plotODG2withAnalysis(od_imgs, scan_parameter_values, g2_analysis_results, analysis_results, options, ...
'FontName', options.font, ...
'FontSize', 14, ...
'ShowOverlays', true, ...
'RepsPerPage', 5, ...
'SaveDirectory', options.saveDirectory , ...
'SkipLivePlot', options.skipLivePlot, ...
'SkipSaveFigures', options.skipSaveFigures);
%% Plot mean and standard error of anisotropy
Plotter.plotMeanWithSE(scan_parameter_values, analysis_results.anisotropy_vals, ...
'Title', options.titleString, ...
'YLim', [0,6], ...
'XLabel', '\alpha (degrees)', ...
'YLabel', 'Anisotropy of correlation peaks', ...
'FigNum', 1, ...
'FontName', options.font, ...
'SaveFileName', 'RadialSpectralContrast.fig', ...
'SaveDirectory', options.saveDirectory , ...
'SkipSaveFigures', options.skipSaveFigures);
%% Plot distribution of anisotropy
grouped_data = groupDataByScan(scan_parameter_values, analysis_results.anisotropy_vals);
% call plotPDF
Plotter.plotPDF(grouped_data, ...
scan_reference_values, ...
'Title', options.titleString, ...
'XLabel', '\alpha (degrees)', ...
'YLabel', 'Anisotropy of correlation peaks', ...
'FigNum', 2, ...
'FontName', options.font, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'PDF_MaxG2AcrossTransition.fig', ...
'SaveDirectory', options.saveDirectory , ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'DataRange', [0 10], ...
'Colormap', @Colormaps.coolwarm, ...
'XLim', [min(scan_reference_values) max(scan_reference_values)]);
function groupedData = groupDataByScan(scan_values, data_values)
%% groupByScanValues
% Groups data according to unique scan parameter values.
%
% Inputs:
% scan_values : array of scan parameters (length = N_reps * N_scan)
% data_values : numeric array or cell array of measured values
% (same length as scan_values)
%
% Output:
% groupedData : 1 x N_unique cell array, each containing all repetitions
% corresponding to a unique scan value
[unique_vals, ~, idx] = unique(scan_values, 'stable'); % preserve order
groupedData = cell(1, numel(unique_vals));
for i = 1:numel(unique_vals)
if iscell(data_values)
group = data_values(idx == i);
groupedData{i} = [group{:}]; % concatenate if nested cells
else
groupedData{i} = data_values(idx == i);
end
end
end

View File

@ -0,0 +1,263 @@
%% ===== BEC-Droplets Settings =====
% Specify data location to run analysis on
dataSources = {
struct('sequence', 'StructuralPhaseTransition', ...
'date', '2025/08/13', ...
'runs', [62]) % 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/202508';
options.measurementName = 'BECToDroplets';
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 = [1420, 2050];
options.span = [200, 200];
options.fraction = [0.1, 0.1];
options.pixel_size = 5.86e-6; % in meters
options.magnification = 24.6;
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 = 180;
options.Angular_Threshold = 75;
options.Angular_Sigma = 2;
options.Angular_WindowSize = 5;
options.zoom_size = 50;
% 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.extractFullRadialSpectralDistribution(od_imgs, options);
%% ------------------ 1. Plot of all Radial Spectral Distribution Curves ------------------
Plotter.plotSpectralCurves( ...
spectral_analysis_results.S_k_all, ...
spectral_analysis_results.k_rho_vals, ...
scan_reference_values, ... % correct scan params
'Title', options.titleString, ...
'XLabel', 'k_\rho', ...
'YLabel', 'S(k_\rho)', ...
'YScale', 'log', ...
'XLim', [min(spectral_analysis_results.k_rho_vals), max(spectral_analysis_results.k_rho_vals)], ...
'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. Fit truncated Radial Spectral Distribution Curves ------------------
[fitResults, rawCurves] = Analyzer.fitTwoGaussianCurvesToRadialSpectralDistribution(...
spectral_analysis_results.S_k_all, ... % radial spectral curves
spectral_analysis_results.k_rho_vals, ... % corresponding k_rho values
'KRhoRange', [0, 3], ... % truncate curves to 0 <= k_rho <= 3
'AmplitudeRange', [0, 0.5], ... % truncate curves to y >= 0 (all amplitudes)
'ResidualThreshold', 0.15, ... % maximum allowed residual
'PositionThreshold', 0.5, ... % minimum separation between peaks
'AmplitudeThreshold', 0.015); % minimum secondary peak fraction
%%
%{
% --- Function call ---
plotTwoGaussianFitsOnRaw(fitResults, rawCurves, 8, 12); % 8×12 subplots per page
% --- Function definition ---
function plotTwoGaussianFitsOnRaw(fitResults, rawCurves, nRows, nCols)
%% plotTwoGaussianFitsOnRaw
% Plots raw radial spectral curves with their two-Gaussian fits.
%
% Inputs:
% fitResults - struct array from fitTwoGaussianCurvesToRadialSpectralDistribution
% rawCurves - struct array with fields:
% .x - raw normalized amplitudes
% .k - corresponding k_rho values
% nRows - number of subplot rows per page (default: 8)
% nCols - number of subplot columns per page (default: 12)
if nargin < 3, nRows = 8; end
if nargin < 4, nCols = 12; end
Ncurves = numel(rawCurves);
plotsPerPage = nRows * nCols;
pageNum = 1;
for k = 1:Ncurves
% --- Create new figure page if needed ---
if mod(k-1, plotsPerPage) == 0
figure('Name', sprintf('Radial Spectra Page %d', pageNum), ...
'NumberTitle', 'off', 'Color', 'w', ...
'Position', [50 50 1600 900]);
pageNum = pageNum + 1;
end
% --- Subplot selection ---
subplot(nRows, nCols, mod(k-1, plotsPerPage) + 1);
hold on; grid on; box on;
% --- Plot raw curve ---
if isfield(rawCurves, 'x') && isfield(rawCurves, 'k') && ...
~isempty(rawCurves(k).x) && all(isfinite(rawCurves(k).x))
plot(rawCurves(k).k, rawCurves(k).x, 'k.-', ...
'LineWidth', 1, 'DisplayName', 'Raw data');
end
% --- Overlay fit if valid ---
if k <= numel(fitResults)
fit = fitResults(k);
if isfield(fit, 'isValid') && fit.isValid && ...
isfield(fit, 'kFine') && isfield(fit, 'yFit') && ...
~isempty(fit.kFine) && all(isfinite(fit.yFit))
plot(fit.kFine, fit.yFit, 'r-', ...
'LineWidth', 1.2, 'DisplayName', 'Two-Gaussian fit');
end
end
% --- Labels and title ---
xlabel('k_\rho', 'FontSize', 10);
ylabel('Normalized amplitude', 'FontSize', 10);
title(sprintf('Curve %d', k), 'FontSize', 9, 'Interpreter', 'none');
% --- Legend on first subplot of each page ---
if mod(k-1, plotsPerPage) == 0
legend('Location', 'best', 'FontSize', 8);
end
hold off;
end
end
%}
%% ------------------ 3. 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', ...
'SaveDirectory', options.saveDirectory, ...
'PlotType', 'histogram', ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'Colormap', @Colormaps.coolwarm, ...
'XLim', [min(scan_reference_values), max(scan_reference_values)], ...
'YLim', [0, 0.06]);
%% ------------------ 4. 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', ...
'SaveDirectory', options.saveDirectory, ...
'PlotType', 'histogram', ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'Colormap', @Colormaps.coolwarm, ...
'XLim', [min(scan_reference_values), max(scan_reference_values)], ...
'YLim', [1, 2]);
%% ------------------ 5. 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', ...
'SaveDirectory', options.saveDirectory, ...
'PlotType', 'histogram', ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'Colormap', @Colormaps.coolwarm, ...
'XLim', [min(scan_reference_values), max(scan_reference_values)], ...
'YLim', [0, 1.5]);
%% ------------------ 6. Plot secondary Gaussian range ------------------
Plotter.plotSecondaryGaussianRange(fitResults, scan_reference_values, ...
'Title', options.titleString, ...
'XLabel', '\alpha (degrees)', ...
'YLabel', '\mu_2 \pm \sigma_2 (rad)', ...
'FontName', options.font, ...
'FontSize', 16, ...
'FigNum', 8, ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'Colormap', @Colormaps.coolwarm, ...
'OverlayMeanSEM', true, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'SecondaryGaussianRange.fig', ...
'SaveDirectory', options.saveDirectory);

View File

@ -0,0 +1,411 @@
%% ===== BEC-Stripes Settings =====
% Specify data location to run analysis on
dataSources = {
struct('sequence', 'StructuralPhaseTransition', ...
'date', '2025/08/15', ...
'runs', [3]) % 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/202508';
options.measurementName = 'BECToStripes';
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 = [1420, 2050];
options.span = [200, 200];
options.fraction = [0.1, 0.1];
options.pixel_size = 5.86e-6; % in meters
options.magnification = 24.6;
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 = 180;
options.Angular_Threshold = 75;
options.Angular_Sigma = 2;
options.Angular_WindowSize = 5;
options.zoom_size = 50;
% 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', ...
'SaveDirectory', options.saveDirectory);
%% ------------------ 4. Fit shifted Angular Spectral Distribution Curves ------------------
[fitResults, rawCurves] = Analyzer.fitTwoGaussianCurvesToAngularSpectralDistribution(...
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 fitTwoGaussianCurvesToAngularSpectralDistribution (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', ...
'SaveDirectory', options.saveDirectory, ...
'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', ...
'SaveDirectory', options.saveDirectory, ...
'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', ...
'SaveDirectory', options.saveDirectory, ...
'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.fitTwoGaussianCurvesToAngularSpectralDistribution(...
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 peak amplitude mean with SEM ---
Plotter.plotMeanWithSE(scan_reference_values, amp2_vals, ...
'Title', options.titleString, ...
'XLabel', '\alpha (degrees)', ...
'YLabel', 'Secondary peak amplitude', ...
'FigNum', 7, ...
'FontName', options.font, ...
'FontSize', 16, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'SecondaryPeakAmplitudeMeanWithSEM.fig', ...
'SaveDirectory', options.saveDirectory);
% --- Plot peak position mean with SEM ---
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, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'SecondaryPeakPositionMeanWithSEM.fig', ...
'SaveDirectory', options.saveDirectory);
% --- Plot peak width mean with SEM ---
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, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'SecondaryPeakWidthMeanWithSEM.fig', ...
'SaveDirectory', options.saveDirectory);
%% 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.fitTwoGaussianCurvesToAngularSpectralDistribution(...
curves_cell, ...
results.x_values*pi, ...
'MaxTheta', pi/2, ...
'ResidualThreshold', 0.15, ...
'PositionThreshold', pi/15, ...
'AmplitudeThreshold', 0.15, ...
'RecenterCurves', false);

View File

@ -0,0 +1,181 @@
%% ===== BEC-Stripes Settings =====
% Specify data location to run analysis on
dataSources = {
struct('sequence', 'StructuralPhaseTransition', ...
'date', '2025/08/15', ...
'runs', [3]) % 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/202508';
options.measurementName = 'BECToStripes';
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 = [1420, 2050];
options.span = [200, 200];
options.fraction = [0.1, 0.1];
options.pixel_size = 5.86e-6; % in meters
options.magnification = 24.6;
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 = 180;
options.Angular_Threshold = 75;
options.Angular_Sigma = 2;
options.Angular_WindowSize = 5;
options.zoom_size = 50;
% 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 correlation analysis
options.skipLivePlot = true;
g2_analysis_results = Analyzer.conductCorrelationAnalysis(od_imgs, scan_parameter_values, options);
%% Analyze G2 matrices
% ROI definition
options.roi.center = [3, 3]; % center of ROI in µm (x0, y0)
options.roi.size = [3, 11]; % width and height in µm
options.roi.angle = pi/4; % rotation angle (radians, CCW)
options.threshold = 0.85;
options.fitDeviationThreshold = 0.9;
% Plot control
options.skipLivePlot = true;
analysis_results = Analyzer.analyzeG2Structures(g2_analysis_results, options);
%% Plot raw OD images and the corresponding real space g2 matrix
options.skipLivePlot = true;
options.skipSaveFigures = true;
Plotter.plotODG2withAnalysis(od_imgs, scan_parameter_values, g2_analysis_results, analysis_results, options, ...
'FontName', options.font, ...
'FontSize', 14, ...
'ShowOverlays', true, ...
'RepsPerPage', 5, ...
'SaveDirectory', options.saveDirectory , ...
'SkipLivePlot', options.skipLivePlot, ...
'SkipSaveFigures', options.skipSaveFigures);
%% Plot mean and standard error of anisotropy
Plotter.plotMeanWithSE(scan_parameter_values, analysis_results.anisotropy_vals, ...
'Title', options.titleString, ...
'YLim', [0,6], ...
'XLabel', '\alpha (degrees)', ...
'YLabel', 'Anisotropy of correlation peaks', ...
'FigNum', 1, ...
'FontName', options.font, ...
'SaveFileName', 'RadialSpectralContrast.fig', ...
'SaveDirectory', options.saveDirectory , ...
'SkipSaveFigures', options.skipSaveFigures);
%% Plot distribution of anisotropy
grouped_data = groupDataByScan(scan_parameter_values, analysis_results.anisotropy_vals);
% call plotPDF
Plotter.plotPDF(grouped_data, ...
scan_reference_values, ...
'Title', options.titleString, ...
'XLabel', '\alpha (degrees)', ...
'YLabel', 'Anisotropy of correlation peaks', ...
'FigNum', 2, ...
'FontName', options.font, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'PDF_MaxG2AcrossTransition.fig', ...
'SaveDirectory', options.saveDirectory , ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'DataRange', [0 10], ...
'Colormap', @Colormaps.coolwarm, ...
'XLim', [min(scan_reference_values) max(scan_reference_values)]);
function groupedData = groupDataByScan(scan_values, data_values)
%% groupByScanValues
% Groups data according to unique scan parameter values.
%
% Inputs:
% scan_values : array of scan parameters (length = N_reps * N_scan)
% data_values : numeric array or cell array of measured values
% (same length as scan_values)
%
% Output:
% groupedData : 1 x N_unique cell array, each containing all repetitions
% corresponding to a unique scan value
[unique_vals, ~, idx] = unique(scan_values, 'stable'); % preserve order
groupedData = cell(1, numel(unique_vals));
for i = 1:numel(unique_vals)
if iscell(data_values)
group = data_values(idx == i);
groupedData{i} = [group{:}]; % concatenate if nested cells
else
groupedData{i} = data_values(idx == i);
end
end
end

View File

@ -0,0 +1,263 @@
%% ===== BEC-Stripes Settings =====
% Specify data location to run analysis on
dataSources = {
struct('sequence', 'StructuralPhaseTransition', ...
'date', '2025/08/15', ...
'runs', [3]) % 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/202508';
options.measurementName = 'BECToStripes';
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 = [1420, 2050];
options.span = [200, 200];
options.fraction = [0.1, 0.1];
options.pixel_size = 5.86e-6; % in meters
options.magnification = 24.6;
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 = 180;
options.Angular_Threshold = 75;
options.Angular_Sigma = 2;
options.Angular_WindowSize = 5;
options.zoom_size = 50;
% 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.extractFullRadialSpectralDistribution(od_imgs, options);
%% ------------------ 1. Plot of all Radial Spectral Distribution Curves ------------------
Plotter.plotSpectralCurves( ...
spectral_analysis_results.S_k_all, ...
spectral_analysis_results.k_rho_vals, ...
scan_reference_values, ... % correct scan params
'Title', options.titleString, ...
'XLabel', 'k_\rho', ...
'YLabel', 'S(k_\rho)', ...
'YScale', 'log', ...
'XLim', [min(spectral_analysis_results.k_rho_vals), max(spectral_analysis_results.k_rho_vals)], ...
'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. Fit truncated Radial Spectral Distribution Curves ------------------
[fitResults, rawCurves] = Analyzer.fitTwoGaussianCurvesToRadialSpectralDistribution(...
spectral_analysis_results.S_k_all, ... % radial spectral curves
spectral_analysis_results.k_rho_vals, ... % corresponding k_rho values
'KRhoRange', [0, 3], ... % truncate curves to 0 <= k_rho <= 3
'AmplitudeRange', [0, 0.5], ... % truncate curves to y >= 0 (all amplitudes)
'ResidualThreshold', 0.15, ... % maximum allowed residual
'PositionThreshold', 0.5, ... % minimum separation between peaks
'AmplitudeThreshold', 0.015); % minimum secondary peak fraction
%%
%{
% --- Function call ---
plotTwoGaussianFitsOnRaw(fitResults, rawCurves, 8, 12); % 8×12 subplots per page
% --- Function definition ---
function plotTwoGaussianFitsOnRaw(fitResults, rawCurves, nRows, nCols)
%% plotTwoGaussianFitsOnRaw
% Plots raw radial spectral curves with their two-Gaussian fits.
%
% Inputs:
% fitResults - struct array from fitTwoGaussianCurvesToRadialSpectralDistribution
% rawCurves - struct array with fields:
% .x - raw normalized amplitudes
% .k - corresponding k_rho values
% nRows - number of subplot rows per page (default: 8)
% nCols - number of subplot columns per page (default: 12)
if nargin < 3, nRows = 8; end
if nargin < 4, nCols = 12; end
Ncurves = numel(rawCurves);
plotsPerPage = nRows * nCols;
pageNum = 1;
for k = 1:Ncurves
% --- Create new figure page if needed ---
if mod(k-1, plotsPerPage) == 0
figure('Name', sprintf('Radial Spectra Page %d', pageNum), ...
'NumberTitle', 'off', 'Color', 'w', ...
'Position', [50 50 1600 900]);
pageNum = pageNum + 1;
end
% --- Subplot selection ---
subplot(nRows, nCols, mod(k-1, plotsPerPage) + 1);
hold on; grid on; box on;
% --- Plot raw curve ---
if isfield(rawCurves, 'x') && isfield(rawCurves, 'k') && ...
~isempty(rawCurves(k).x) && all(isfinite(rawCurves(k).x))
plot(rawCurves(k).k, rawCurves(k).x, 'k.-', ...
'LineWidth', 1, 'DisplayName', 'Raw data');
end
% --- Overlay fit if valid ---
if k <= numel(fitResults)
fit = fitResults(k);
if isfield(fit, 'isValid') && fit.isValid && ...
isfield(fit, 'kFine') && isfield(fit, 'yFit') && ...
~isempty(fit.kFine) && all(isfinite(fit.yFit))
plot(fit.kFine, fit.yFit, 'r-', ...
'LineWidth', 1.2, 'DisplayName', 'Two-Gaussian fit');
end
end
% --- Labels and title ---
xlabel('k_\rho', 'FontSize', 10);
ylabel('Normalized amplitude', 'FontSize', 10);
title(sprintf('Curve %d', k), 'FontSize', 9, 'Interpreter', 'none');
% --- Legend on first subplot of each page ---
if mod(k-1, plotsPerPage) == 0
legend('Location', 'best', 'FontSize', 8);
end
hold off;
end
end
%}
%% ------------------ 3. 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', ...
'SaveDirectory', options.saveDirectory, ...
'PlotType', 'histogram', ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'Colormap', @Colormaps.coolwarm, ...
'XLim', [min(scan_reference_values), max(scan_reference_values)], ...
'YLim', [0, 0.06]);
%% ------------------ 4. 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', ...
'SaveDirectory', options.saveDirectory, ...
'PlotType', 'histogram', ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'Colormap', @Colormaps.coolwarm, ...
'XLim', [min(scan_reference_values), max(scan_reference_values)], ...
'YLim', [1, 2]);
%% ------------------ 5. 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', ...
'SaveDirectory', options.saveDirectory, ...
'PlotType', 'histogram', ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'Colormap', @Colormaps.coolwarm, ...
'XLim', [min(scan_reference_values), max(scan_reference_values)], ...
'YLim', [0, 1.5]);
%% ------------------ 6. Plot secondary Gaussian range ------------------
Plotter.plotSecondaryGaussianRange(fitResults, scan_reference_values, ...
'Title', options.titleString, ...
'XLabel', '\alpha (degrees)', ...
'YLabel', '\mu_2 \pm \sigma_2 (rad)', ...
'FontName', options.font, ...
'FontSize', 16, ...
'FigNum', 8, ...
'NumberOfBins', 20, ...
'NormalizeHistogram', true, ...
'Colormap', @Colormaps.coolwarm, ...
'OverlayMeanSEM', true, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'SecondaryGaussianRange.fig', ...
'SaveDirectory', options.saveDirectory);