Bug fixes, exception handling, new measurement analysis scripts.

This commit is contained in:
Karthik 2025-08-19 00:52:59 +02:00
parent 1a99ff2c2a
commit 5ff4cbbc2d
12 changed files with 501 additions and 127 deletions

View File

@ -2,23 +2,20 @@ function results = conductSpectralAnalysis(od_imgs, scan_parameter_values, optio
%% Performs Fourier analysis on a set of optical density (OD) images. %% Performs Fourier analysis on a set of optical density (OD) images.
% Computes radial and angular spectral distributions, optionally plots % Computes radial and angular spectral distributions, optionally plots
% results, saves figures, and can render a video of the analysis. % results, and saves figures.
% %
% Inputs: % Inputs:
% od_imgs - cell array of OD images % od_imgs - cell array of OD images
% scan_parameter_values - array of scan parameter values corresponding to each image % scan_parameter_values - array of scan parameter values corresponding to each image
% OPTIONS - % OPTIONS -
% saveDirectory - directory to save files % saveDirectory - base directory to save results
% savefileName - base filename for saved figures/video
% skipMovieRender - skip creating the video of analysis
% skipSaveFigures - skip saving plots % skipSaveFigures - skip saving plots
% skipSaveOD - skip saving OD images as .mat
% skipPreprocessing - skip preprocessing of images before FFT % skipPreprocessing - skip preprocessing of images before FFT
% skipMasking - skip masking of OD images % skipMasking - skip masking of OD images
% skipIntensityThresholding- skip thresholding of intensity % skipIntensityThresholding- skip thresholding of intensity
% skipBinarization - skip binarization of OD images % skipBinarization - skip binarization of OD images
% skipNormalization - skip normalization when plotting angular spectrum % skipNormalization - skip normalization when plotting angular spectrum
% skipLivePlot = skip live plotting of figures % skipLivePlot - skip live plotting of figures
% pixel_size - physical pixel size of camera sensor (m) % pixel_size - physical pixel size of camera sensor (m)
% magnification - imaging magnification % magnification - imaging magnification
% zoom_size - number of pixels to crop around FFT center % zoom_size - number of pixels to crop around FFT center
@ -31,8 +28,13 @@ function results = conductSpectralAnalysis(od_imgs, scan_parameter_values, optio
% Radial_WindowSize - window size for smoothing radial spectrum % Radial_WindowSize - window size for smoothing radial spectrum
% scan_parameter - string, type of scan parameter (used in plot text) % scan_parameter - string, type of scan parameter (used in plot text)
% font - font name for plots % font - font name for plots
%
% Outputs:
% results - struct containing spectra and analysis results
% Figures (if enabled) are saved into:
% [saveDirectory]/Results/SpectralAnalysisSavedFigures/
%% Unpack struct arguments %% ===== Unpack struct arguments =====
pixel_size = options.pixel_size; pixel_size = options.pixel_size;
magnification = options.magnification; magnification = options.magnification;
zoom_size = options.zoom_size; zoom_size = options.zoom_size;
@ -51,10 +53,7 @@ function results = conductSpectralAnalysis(od_imgs, scan_parameter_values, optio
skipIntensityThresholding = options.skipIntensityThresholding; skipIntensityThresholding = options.skipIntensityThresholding;
skipBinarization = options.skipBinarization; skipBinarization = options.skipBinarization;
skipLivePlot = options.skipLivePlot; skipLivePlot = options.skipLivePlot;
skipMovieRender = options.skipMovieRender;
skipSaveFigures = options.skipSaveFigures; skipSaveFigures = options.skipSaveFigures;
skipSaveOD = options.skipSaveOD;
savefileName = options.savefileName;
saveDirectory = options.saveDirectory; saveDirectory = options.saveDirectory;
scan_parameter = options.scan_parameter; scan_parameter = options.scan_parameter;
font = options.font; font = options.font;
@ -70,33 +69,16 @@ function results = conductSpectralAnalysis(od_imgs, scan_parameter_values, optio
S_k_all = cell(1, N_shots); S_k_all = cell(1, N_shots);
S_k_smoothed_all = cell(1, N_shots); S_k_smoothed_all = cell(1, N_shots);
S_theta_norm_all = cell(1, N_shots); S_theta_norm_all = cell(1, N_shots);
PS_all = cell(1, N_shots); % 2D FFT power spectrum |F(kx,ky)|^2
% Optional save directory override
if ~isempty(saveDirectory)
savefileName = fullfile(saveDirectory, savefileName);
end
% Prepare video if enabled
if ~skipMovieRender
videoFile = VideoWriter([savefileName '.mp4'], 'MPEG-4');
videoFile.Quality = 100;
videoFile.FrameRate = 2;
open(videoFile);
end
% Prepare folder to save figures % Prepare folder to save figures
if ~skipSaveFigures if ~skipSaveFigures
saveFolder = [savefileName '_SavedFigures']; saveFolder = fullfile(saveDirectory, "Results", "SpectralAnalysisSavedFigures");
if ~exist(saveFolder, 'dir') if ~exist(saveFolder, 'dir')
mkdir(saveFolder); mkdir(saveFolder);
end end
end end
% Initialize lists for power spectra and radial spectra
PS_all = cell(1, N_shots); % 2D FFT power spectrum |F(kx,ky)|^2
%% ===== Main loop over images ===== %% ===== Main loop over images =====
for k = 1:N_shots for k = 1:N_shots
IMG = od_imgs{k}; IMG = od_imgs{k};
@ -253,24 +235,11 @@ function results = conductSpectralAnalysis(od_imgs, scan_parameter_values, optio
ax.YMinorGrid = 'on'; ax.YMinorGrid = 'on';
end end
%% ===== Save outputs ===== %% ===== Save figures =====
if ~skipMovieRender
frame = getframe(gcf);
writeVideo(videoFile, frame);
end
if ~skipSaveFigures if ~skipSaveFigures
fileNamePNG = fullfile(saveFolder, sprintf('fft_analysis_img_%03d.png', k)); fileNamePNG = fullfile(saveFolder, sprintf('fft_analysis_img_%03d.png', k));
print(gcf, fileNamePNG, '-dpng', '-r100'); print(gcf, fileNamePNG, '-dpng', '-r100');
end elseif ~skipLivePlot
if ~skipSaveOD
odDataStruct = struct();
odDataStruct.IMG = IMG;
odDataStruct.x = x;
odDataStruct.y = y;
odDataStruct.scan_parameter_value = scan_parameter_values(k);
save(fullfile(saveFolder, sprintf('od_image_%03d.mat', k)), '-struct', 'odDataStruct');
end
if skipMovieRender && skipSaveFigures
pause(0.5); pause(0.5);
end end
end end
@ -290,7 +259,4 @@ function results = conductSpectralAnalysis(od_imgs, scan_parameter_values, optio
results.S_theta_norm_all = S_theta_norm_all; results.S_theta_norm_all = S_theta_norm_all;
results.angular_spectral_weight = angular_spectral_weight; results.angular_spectral_weight = angular_spectral_weight;
if ~skipMovieRender
close(videoFile);
end
end end

View File

@ -31,17 +31,14 @@ function results = performAnalysis(options)
options.skipBinarization (1,1) logical options.skipBinarization (1,1) logical
options.skipNormalization (1,1) logical options.skipNormalization (1,1) logical
options.skipLivePlot (1,1) logical options.skipLivePlot (1,1) logical
options.skipMovieRender (1,1) logical
options.skipSaveFigures (1,1) logical options.skipSaveFigures (1,1) logical
options.skipSaveOD (1,1) logical
options.showProgressBar (1,1) logical options.showProgressBar (1,1) logical
options.savefileName (1,:) char options.measurementName (1,:) char
options.folderPath (1,:) char options.folderPath (1,:) char
options.baseDataFolder (1,:) char options.baseDataFolder (1,:) char
options.saveDirectory (1,:) char options.saveDirectory (1,:) char
options.titleString (1,:) char options.titleString (1,:) char
options.font (1,:) char options.font (1,:) char
end end
% Collect OD images % Collect OD images

View File

@ -68,7 +68,7 @@ function runInteractiveODImageViewer(od_imgs, scan_parameter_values, file_list,
shortName = [fname, ext]; shortName = [fname, ext];
% Update figure title with shot + filename % Update figure title with shot + filename
if strcmp(options.scan_parameter, 'rot_mag_fin_pol_angle') if strcmp(options.scan_parameter, 'ps_rot_mag_fin_pol_angle')
hFig.Name = sprintf('Shot %d | %s', idx, shortName); hFig.Name = sprintf('Shot %d | %s', idx, shortName);
txtHandle.String = sprintf('%.1f^\\circ', scan_parameter_values(idx)); txtHandle.String = sprintf('%.1f^\\circ', scan_parameter_values(idx));
else else

View File

@ -1,4 +1,4 @@
function [ordered_od_imgs, ordered_scan_parameter_values, ordered_file_list] = collectODImages(options) function [od_imgs, scan_parameter_values, file_list] = collectODImages(options)
%% Applies cropping, background subtraction, and optional fringe removal, optional unshuffling on OD image dataset %% Applies cropping, background subtraction, and optional fringe removal, optional unshuffling on OD image dataset
% Automatically reuses in-memory full dataset if available; % Automatically reuses in-memory full dataset if available;
% otherwise, reads and processes raw HDF5 data. % otherwise, reads and processes raw HDF5 data.
@ -17,17 +17,29 @@ function [ordered_od_imgs, ordered_scan_parameter_values, ordered_file_list] = c
% .scan_reference_values: reference values for unshuffling % .scan_reference_values: reference values for unshuffling
% %
% Outputs: % Outputs:
% ordered_od_imgs : cell array of processed OD images (ordered) % od_imgs : cell array of processed OD images
% ordered_scan_parameter_values: vector of scan parameter values (ordered) % scan_parameter_values: vector of scan parameter values
% ordered_file_list : cell array of file names (ordered) % file_list : cell array of file names
% --- Early exit if processed data already exist --- % --- Early exit if processed data already exist AND options match ---
if evalin('base', 'exist(''od_imgs'',''var'') && exist(''scan_parameter_values'',''var'') && exist(''file_list'',''var'')') reuseVarsExist = evalin('base', ...
fprintf('\nReusing processed OD images, scan parameters, and file list from memory.\n'); 'exist(''od_imgs'',''var'') && exist(''scan_parameter_values'',''var'') && exist(''file_list'',''var'') && exist(''prior_options'',''var'')');
ordered_od_imgs = evalin('base','od_imgs');
ordered_scan_parameter_values = evalin('base','scan_parameter_values'); if reuseVarsExist
ordered_file_list = evalin('base','file_list'); prior_options = evalin('base','prior_options');
% Define which fields are critical for reuse
critical_fields = {'folderPath','cam','angle','ImagingMode','PulseDuration','center','span','fraction','removeFringes','skipUnshuffling','scan_reference_values'};
if ~haveOptionsChanged(options, prior_options, critical_fields)
fprintf('\nReusing processed OD images, scan parameters, and file list from memory (options unchanged).\n');
od_imgs = evalin('base','od_imgs');
scan_parameter_values = evalin('base','scan_parameter_values');
file_list = evalin('base','file_list');
return; % skip rest of the function return; % skip rest of the function
else
fprintf('\nProcessed-data-related options changed. Reprocessing full OD image dataset...\n');
end
end end
% --- Check if the full OD dataset and scan parameters exist in workspace --- % --- Check if the full OD dataset and scan parameters exist in workspace ---
@ -37,12 +49,29 @@ function [ordered_od_imgs, ordered_scan_parameter_values, ordered_file_list] = c
evalin('base', 'exist(''raw_file_list'', ''var'')'); evalin('base', 'exist(''raw_file_list'', ''var'')');
if fullDataExists if fullDataExists
% Both required datasets exist, use them directly % Both required datasets exist, check if raw-data options changed
prior_options = evalin('base','prior_options');
% Define critical fields that affect raw-data computation
critical_raw_fields = {'folderPath','cam','angle','ImagingMode','PulseDuration'};
if ~haveOptionsChanged(options, prior_options, critical_raw_fields)
fprintf('\nReusing full OD image dataset and scan parameters from memory.\n'); fprintf('\nReusing full OD image dataset and scan parameters from memory.\n');
full_od_imgs = evalin('base', 'full_od_imgs'); full_od_imgs = evalin('base', 'full_od_imgs');
full_bkg_imgs = evalin('base', 'full_bkg_imgs'); full_bkg_imgs = evalin('base', 'full_bkg_imgs');
raw_scan_parameter_values = evalin('base', 'raw_scan_parameter_values'); raw_scan_parameter_values = evalin('base', 'raw_scan_parameter_values');
raw_file_list = evalin('base', 'raw_file_list'); raw_file_list = evalin('base', 'raw_file_list');
else
fprintf('\nRaw-data-related options changed. Recomputing full OD image dataset...\n');
[full_od_imgs, full_bkg_imgs, raw_scan_parameter_values, raw_file_list] = Helper.processRawData(options);
% Save raw full dataset for reuse
assignin('base', 'full_od_imgs', full_od_imgs);
assignin('base', 'full_bkg_imgs', full_bkg_imgs);
assignin('base', 'raw_scan_parameter_values', raw_scan_parameter_values);
assignin('base', 'raw_file_list', raw_file_list);
fprintf('\nCompleted recomputing OD images. Stored in workspace for reuse.\n');
end
else else
% Either dataset is missing, process raw HDF5 files completely % Either dataset is missing, process raw HDF5 files completely
fprintf('\nFull OD image dataset or scan parameters not found in memory.\n'); fprintf('\nFull OD image dataset or scan parameters not found in memory.\n');
@ -53,7 +82,7 @@ function [ordered_od_imgs, ordered_scan_parameter_values, ordered_file_list] = c
assignin('base', 'full_bkg_imgs', full_bkg_imgs); assignin('base', 'full_bkg_imgs', full_bkg_imgs);
assignin('base', 'raw_scan_parameter_values', raw_scan_parameter_values); assignin('base', 'raw_scan_parameter_values', raw_scan_parameter_values);
assignin('base', 'raw_file_list', raw_file_list); assignin('base', 'raw_file_list', raw_file_list);
fprintf('\nCompleted computing OD images. Stored in workspace for reuse.\n'); fprintf('\nCompleted computing OD images and will be stored in workspace for reuse.\n');
end end
nFiles = size(full_od_imgs, 3); nFiles = size(full_od_imgs, 3);
@ -64,20 +93,20 @@ function [ordered_od_imgs, ordered_scan_parameter_values, ordered_file_list] = c
% --- Process each image: crop and subtract background --- % --- Process each image: crop and subtract background ---
for k = 1:nFiles for k = 1:nFiles
od_img = full_od_imgs(:,:,k); % original full OD image, never modified full_od_img = full_od_imgs(:,:,k); % original full OD image, never modified
bkg_img = full_bkg_imgs(:,:,k); % original full background image, never modified full_bkg_img = full_bkg_imgs(:,:,k); % original full background image, never modified
if any(isnan(od_img(:))) if any(isnan(full_od_img(:)))
absimages(:,:,k) = nan(options.span(1)+1, options.span(2)+1, 'single'); absimages(:,:,k) = nan(options.span(1)+1, options.span(2)+1, 'single');
continue continue
end end
if any(isnan(bkg_img(:))) if any(isnan(full_bkg_img(:)))
refimages(:,:,k) = nan(options.span(1)+1, options.span(2)+1, 'single'); refimages(:,:,k) = nan(options.span(1)+1, options.span(2)+1, 'single');
continue continue
end end
% Crop image around the region of interest % Crop image around the region of interest
cropped_absimage = Helper.cropODImage(od_img, options.center, options.span); cropped_absimage = Helper.cropODImage(full_od_img, options.center, options.span);
cropped_refimage = Helper.cropODImage(bkg_img, options.center, options.span); cropped_refimage = Helper.cropODImage(full_bkg_img, options.center, options.span);
% Subtract background offset based on fraction % Subtract background offset based on fraction
processed_absimage = Helper.subtractBackgroundOffset(cropped_absimage, options.fraction); processed_absimage = Helper.subtractBackgroundOffset(cropped_absimage, options.fraction);
@ -93,10 +122,10 @@ function [ordered_od_imgs, ordered_scan_parameter_values, ordered_file_list] = c
fprintf('\nApplying fringe removal to processed images...\n'); fprintf('\nApplying fringe removal to processed images...\n');
optrefimages = Helper.removeFringesInImage(absimages, refimages); optrefimages = Helper.removeFringesInImage(absimages, refimages);
absimages_fringe_removed = absimages - optrefimages; absimages_fringe_removed = absimages - optrefimages;
processed_od_imgs = arrayfun(@(i) absimages_fringe_removed(:,:,i), 1:nFiles, 'UniformOutput', false); od_imgs = arrayfun(@(i) absimages_fringe_removed(:,:,i), 1:nFiles, 'UniformOutput', false);
fprintf('\nFringe removal completed.\n'); fprintf('\nFringe removal completed.\n');
else else
processed_od_imgs = arrayfun(@(i) absimages(:,:,i), 1:nFiles, 'UniformOutput', false); od_imgs = arrayfun(@(i) absimages(:,:,i), 1:nFiles, 'UniformOutput', false);
end end
% --- Optional unshuffling based on scan reference values --- % --- Optional unshuffling based on scan reference values ---
@ -113,7 +142,7 @@ function [ordered_od_imgs, ordered_scan_parameter_values, ordered_file_list] = c
counter = 1; counter = 1;
temp_scan_values = raw_scan_parameter_values; temp_scan_values = raw_scan_parameter_values;
temp_od_imgs = processed_od_imgs; temp_od_imgs = od_imgs;
temp_file_list = raw_file_list; temp_file_list = raw_file_list;
for rep = 1:n_reps for rep = 1:n_reps
@ -129,18 +158,51 @@ function [ordered_od_imgs, ordered_scan_parameter_values, ordered_file_list] = c
counter = counter + 1; counter = counter + 1;
end end
end end
od_imgs = ordered_od_imgs;
scan_parameter_values = ordered_scan_parameter_values;
file_list = ordered_file_list;
fprintf('\nImage reordering completed.\n'); fprintf('\nImage reordering completed.\n');
else else
% No unshuffling: keep original order % No unshuffling: keep original order
ordered_od_imgs = processed_od_imgs; scan_parameter_values = raw_scan_parameter_values;
ordered_scan_parameter_values = raw_scan_parameter_values; file_list = raw_file_list;
ordered_file_list = raw_file_list;
end end
% --- Save processed dataset for reuse --- % --- Save processed dataset and options for reuse ---
assignin('base', 'od_imgs', ordered_od_imgs); assignin('base', 'od_imgs', od_imgs);
assignin('base', 'scan_parameter_values', ordered_scan_parameter_values); assignin('base', 'scan_parameter_values', scan_parameter_values);
assignin('base', 'file_list', ordered_file_list); assignin('base', 'file_list', file_list);
assignin('base', 'prior_options', options);
% --- Optionally save OD images as figures ---
if ~options.skipSaveFigures
odFolder = fullfile(saveDirectory, "Results", "ODImages");
if ~exist(odFolder, 'dir')
mkdir(odFolder);
end
for k = 1:length(od_imgs)
img = od_imgs{k};
fileName = fullfile(odFolder, sprintf('OD_img_%03d.png', k));
imwrite(mat2gray(img), fileName);
end
end
fprintf('\nOD image dataset ready for further analysis.\n'); fprintf('\nOD image dataset ready for further analysis.\n');
end
% --- Local helper function to compare options ---
function changed = haveOptionsChanged(options, prior_options, critical_fields)
changed = false;
for f = critical_fields
fname = f{1};
if isfield(options, fname) && isfield(prior_options, fname)
if ~isequal(options.(fname), prior_options.(fname))
changed = true; return
end
elseif xor(isfield(options, fname), isfield(prior_options, fname))
changed = true; return
end
end
end end

View File

@ -25,20 +25,12 @@ function plotAverageSpectra(scan_parameter_values, spectral_analysis_results, va
addParameter(p, 'FigNum', 1, @(x) isnumeric(x) && isscalar(x)); addParameter(p, 'FigNum', 1, @(x) isnumeric(x) && isscalar(x));
addParameter(p, 'ColormapPS', Colormaps.coolwarm(), @(x) isnumeric(x) || ismatrix(x)); addParameter(p, 'ColormapPS', Colormaps.coolwarm(), @(x) isnumeric(x) || ismatrix(x));
addParameter(p, 'Font', 'Arial', @ischar); addParameter(p, 'Font', 'Arial', @ischar);
addParameter(p, 'SaveFileName', 'figure.fig', @ischar); addParameter(p, 'SaveFileName', 'avgspectra.fig', @ischar);
addParameter(p, 'SaveDirectory', pwd, @ischar); addParameter(p, 'SaveDirectory', pwd, @ischar);
addParameter(p, 'SkipSaveFigures', false, @islogical); addParameter(p, 'SkipSaveFigures', false, @islogical);
parse(p, varargin{:}); parse(p, varargin{:});
opts = p.Results; opts = p.Results;
scanParam = opts.ScanParameterName;
figNum = opts.FigNum;
colormapPS = opts.ColormapPS;
fontName = opts.Font;
saveFileName = opts.SaveFileName;
saveDirectory = opts.SaveDirectory;
skipSaveFigures = opts.SkipSaveFigures;
% --- Unique scan parameters --- % --- Unique scan parameters ---
[uniqueParams, ~, idx] = unique(scan_parameter_values); [uniqueParams, ~, idx] = unique(scan_parameter_values);
nParams = numel(uniqueParams); nParams = numel(uniqueParams);
@ -67,7 +59,7 @@ function plotAverageSpectra(scan_parameter_values, spectral_analysis_results, va
avgS_theta = avgS_theta / nShots; avgS_theta = avgS_theta / nShots;
% ==== Plot ==== % ==== Plot ====
fig = figure(figNum); clf; fig = figure(opts.FigNum); clf;
set(fig, 'Color', 'w', 'Position', [400 200 1200 400]); set(fig, 'Color', 'w', 'Position', [400 200 1200 400]);
tLayout = tiledlayout(1,3,'TileSpacing','compact','Padding','compact'); tLayout = tiledlayout(1,3,'TileSpacing','compact','Padding','compact');
@ -79,14 +71,14 @@ function plotAverageSpectra(scan_parameter_values, spectral_analysis_results, va
imagesc(kx, ky, log(1 + avgPS)); imagesc(kx, ky, log(1 + avgPS));
axis image; axis image;
set(gca, 'FontSize', axisFontSize, 'YDir', 'normal'); set(gca, 'FontSize', axisFontSize, 'YDir', 'normal');
xlabel('k_x [\mum^{-1}]','Interpreter','tex','FontSize',axisFontSize,'FontName',fontName); xlabel('k_x [\mum^{-1}]','Interpreter','tex','FontSize',axisFontSize,'FontName',opts.Font);
ylabel('k_y [\mum^{-1}]','Interpreter','tex','FontSize',axisFontSize,'FontName',fontName); ylabel('k_y [\mum^{-1}]','Interpreter','tex','FontSize',axisFontSize,'FontName',opts.Font);
title('Average Power Spectrum','FontSize',titleFontSize,'FontWeight','bold'); title('Average Power Spectrum','FontSize',titleFontSize,'FontWeight','bold');
colormap(colormapPS); colormap(opts.ColormapPS);
colorbar; colorbar;
% --- Annotate scan parameter --- % --- Annotate scan parameter ---
if strcmp(scanParam,'ps_rot_mag_fin_pol_angle') if strcmp(opts.ScanParameterName,'ps_rot_mag_fin_pol_angle')
txt = sprintf('%.1f^\\circ', currentParam); txt = sprintf('%.1f^\\circ', currentParam);
else else
txt = sprintf('%.2f G', currentParam); txt = sprintf('%.2f G', currentParam);
@ -119,8 +111,8 @@ function plotAverageSpectra(scan_parameter_values, spectral_analysis_results, va
% --- Save figure --- % --- Save figure ---
saveFigure(fig, ... saveFigure(fig, ...
'SaveFileName', saveFileName, ... 'SaveFileName', opts.SaveFileName, ...
'SaveDirectory', saveDirectory, ... 'SaveDirectory', opts.SaveDirectory, ...
'SkipSaveFigures', skipSaveFigures); 'SkipSaveFigures', opts.SkipSaveFigures);
end end
end end

View File

@ -11,7 +11,7 @@ options = struct();
% File / paths % File / paths
options.baseDataFolder = '//DyLabNAS/Data'; options.baseDataFolder = '//DyLabNAS/Data';
options.savefileName = 'BECToDroplets'; options.measurementName = 'BECToDroplets';
scriptFullPath = mfilename('fullpath'); scriptFullPath = mfilename('fullpath');
options.saveDirectory = fileparts(scriptFullPath); options.saveDirectory = fileparts(scriptFullPath);
@ -45,7 +45,7 @@ options.zoom_size = 50;
% Scan parameter % Scan parameter
options.scan_parameter = 'rot_mag_field'; options.scan_parameter = 'rot_mag_field';
switch options.savefileName switch options.measurementName
case 'BECToDroplets' case 'BECToDroplets'
options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8]; options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8];
options.titleString = 'BEC to Droplets'; options.titleString = 'BEC to Droplets';

View File

@ -11,7 +11,7 @@ options = struct();
% File / paths % File / paths
options.baseDataFolder = '//DyLabNAS/Data'; options.baseDataFolder = '//DyLabNAS/Data';
options.savefileName = 'BECToDroplets'; options.measurementName = 'BECToDroplets';
scriptFullPath = mfilename('fullpath'); scriptFullPath = mfilename('fullpath');
options.saveDirectory = fileparts(scriptFullPath); options.saveDirectory = fileparts(scriptFullPath);
@ -45,7 +45,7 @@ options.zoom_size = 50;
% Scan parameter % Scan parameter
options.scan_parameter = 'rot_mag_field'; options.scan_parameter = 'rot_mag_field';
switch options.savefileName switch options.measurementName
case 'BECToDroplets' case 'BECToDroplets'
options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8]; options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8];
options.titleString = 'BEC to Droplets'; options.titleString = 'BEC to Droplets';

View File

@ -0,0 +1,163 @@
idx = 1;
compiled_results = results_all{idx}.results;
options.skipSaveFigures = false;
%% ------------------ 1. Mean ± Std Plots ------------------
% Plot Radial Spectral Contrast
Plotter.plotMeanWithSE(scan_parameter_values, compiled_results.spectral_analysis_results.radial_spectral_contrast, ...
'Title', options.titleString, ...
'XLabel', 'B (G)', ...
'YLabel', 'Radial Spectral Contrast', ...
'FigNum', 1, ...
'FontName', options.font, ...
'SaveFileName', 'RadialSpectralContrast.fig', ...
'SaveDirectory', [options.saveDirectory '/Results'], ...
'SkipSaveFigures', options.skipSaveFigures);
% Plot Angular Spectral Weight
Plotter.plotMeanWithSE(scan_parameter_values, compiled_results.spectral_analysis_results.angular_spectral_weight, ...
'Title', options.titleString, ...
'XLabel', 'B (G)', ...
'YLabel', 'Angular Spectral Weight', ...
'FigNum', 2, ...
'FontName', options.font, ...
'SaveFileName', 'AngularSpectralWeight.fig', ...
'SaveDirectory', [options.saveDirectory '/Results'], ...
'SkipSaveFigures', options.skipSaveFigures);
% Plot Peak Offset Angular Correlation
Plotter.plotMeanWithSE(options.scan_reference_values, compiled_results.custom_g_results.max_g2_all_per_scan_parameter_value, ...
'Title', options.titleString, ...
'XLabel', 'B (G)', ...
'YLabel', '$\mathrm{max}[g^{(2)}_{[50,70]}(\delta\theta)]$', ...
'FigNum', 3, ...
'YLim', [0 1], ...
'FontName', options.font, ...
'SaveFileName', 'PeakOffsetAngularCorrelation.fig', ...
'SaveDirectory', [options.saveDirectory '/Results'], ...
'SkipSaveFigures', options.skipSaveFigures);
%% ------------------ 2. g²(θ) across transition ------------------
Plotter.plotG2(compiled_results.full_g2_results.g2_all, ...
compiled_results.full_g2_results.g2_error_all, ...
compiled_results.full_g2_results.theta_values, ...
options.scan_reference_values, ...
'rot_mag_field', ...
'Title', options.titleString, ...
'XLabel', '$\delta\theta / \pi$', ...
'YLabel', '$g^{(2)}(\delta\theta)$', ...
'FigNum', 4, ...
'FontName', options.font, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'G2ThetaAcrossTransition.fig', ...
'SaveDirectory', [options.saveDirectory '/Results'], ...
'Colormap', @Colormaps.coolwarm);
%% ------------------ 3. PDF of max g² across transition ------------------
Plotter.plotPDF(compiled_results.custom_g_results.max_g2_all_per_scan_parameter_value, options.scan_reference_values, ...
'Title', options.titleString, ...
'XLabel', 'B (G)', ...
'YLabel', '$\mathrm{max}[g^{(2)}]$', ...
'FigNum', 5, ...
'FontName', options.font, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'PDF_MaxG2AcrossTransition.fig', ...
'SaveDirectory', [options.saveDirectory '/Results'], ...
'NumPoints', 200, ...
'DataRange', [0 1.5], ...
'Colormap', @Colormaps.coolwarm, ...
'XLim', [min(options.scan_reference_values) max(options.scan_reference_values)]);
%% ------------------ 4. Cumulants across transition ------------------
Plotter.plotCumulants(options.scan_reference_values, ...
{compiled_results.custom_g_results.mean_max_g2, compiled_results.custom_g_results.var_max_g2, compiled_results.custom_g_results.skew_max_g2_angle, compiled_results.custom_g_results.fourth_order_cumulant_max_g2}, ...
'Title', 'Cumulants of Peak Offset Angular Correlation', ...
'XLabel', 'B (G)', ...
'FigNum', 6, ...
'FontName', options.font, ...
'MarkerSize', 6, ...
'LineWidth', 1.5, ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveFileName', 'CumulantOfPeakOffsetAngularCorrelation.fig', ...
'SaveDirectory', [options.saveDirectory '/Results']);
%{
%% ------------------ 6. Average of Spectra Plots ------------------
Plotter.plotAverageSpectra(scan_parameter_values, ...
spectral_analysis_results, ...
'ScanParameterName', scan_parameter, ...
'FigNum', 7, ...
'ColormapPS', Colormaps.coolwarm(), ...
'Font', 'Bahnschrift', ...
'SaveFileName', 'avgSpectra.fig', ...
'SaveDirectory', [options.saveDirectory '/Results'], ...
'SkipSaveFigures', options.skipSaveFigures);
%% ------------------ 7. Compare quantities ------------------
% Load Droplets Stripes data
Data = load(dtsFile, ...
'unique_scan_parameter_values', ...
'mean_max_g2_values', ...
'std_error_g2_values');
dts_scan_parameter_values = Data.unique_scan_parameter_values;
dts_mean_mg2 = Data.mean_max_g2_values;
dts_stderr_mg2 = Data.std_error_g2_values;
% Load Stripes Droplets data
Data = load(stdFile, ...
'unique_scan_parameter_values', ...
'mean_max_g2_values', ...
'std_error_g2_values');
std_scan_parameter_values = Data.unique_scan_parameter_values;
std_mean_mg2 = Data.mean_max_g2_values;
std_stderr_mg2 = Data.std_error_g2_values;
% Prepare cell arrays for multiple datasets
scanValsCell = {dts_scan_parameter_values, std_scan_parameter_values};
meanValsCell = {dts_mean_mg2, std_mean_mg2};
stderrValsCell = {dts_stderr_mg2, std_stderr_mg2};
% Compare datasets
compareMultipleDatasets(scanValsCell, meanValsCell, stderrValsCell, ...
'FigNum', 8, ...
'FontName', 'Bahnschrift', ...
'MarkerSize', 6, ...
'LineWidth', 1.5, ...
'CapSize', 5, ...
'YLim', [0 1], ...
'Labels', {'Droplets Stripes', 'Stripes Droplets'}, ...
'Title', 'AngularCorrelation_Comparison', ...
'XLabel', 'B (G)', ...
'YLabel', '$\mathrm{max}[g^{(2)}_{[50,70]}(\delta\theta)]$', ...
'SkipSaveFigures', options.skipSaveFigures, ...
'SaveDirectory', [options.saveDirectory '/Results'], ...
'SaveFileName', 'AngularCorrelation_Comparison.fig');
%% ------------------ 8. Heatmaps ------------------
BFields = [2.35, 2.15, 2.0, 1.85, 1.7, 1.55, 1.4, 1.35];
% Heatmap of mean_max_g2_values
Plotter.plotHeatmap(compiled_results, options.scan_groups, BFields, 'mean_max_g2_values', ...
'Colormap', @sky, ...
'CLim', [0 1], ...
'XLabel', '\alpha (degrees)', ...
'YLabel', 'BField (G)', ...
'Title', '$\mathrm{max}[g^{(2)}_{[50,70]}(\delta\theta)]$', ...
'FigNum', 9, ...
'SaveFileName', 'Heatmap_MaxG2.fig', ...
'SaveDirectory', options.resultsDir);
% Heatmap of radial_spectral_contrast
Plotter.plotHeatmap(compiled_results, options.scan_groups, BFields, 'radial_spectral_contrast', ...
'Colormap', @sky, ...
'CLim', [0 0.008], ...
'XLabel', '\alpha (degrees)', ...
'YLabel', 'BField (G)', ...
'Title', 'Radial Spectral Contrast', ...
'FigNum', 10, ...
'SaveFileName', 'Heatmap_RadialSpectralContrast.fig', ...
'SaveDirectory', options.resultsDir);
%}

View File

@ -0,0 +1,116 @@
%% ===== BEC-Droplets-Stripes Settings =====
% Specify data location to run analysis on
dataSources = {
struct('sequence', 'StructuralPhaseTransition', ...
'date', '2025/08/16', ...
'runs', [8]) % specify run numbers as a string in "" or just as a numeric value
};
options = struct();
% File / paths
options.baseDataFolder = '//DyLabNAS/Data';
options.measurementName = 'DropletsToStripes';
scriptFullPath = mfilename('fullpath');
options.saveDirectory = fileparts(scriptFullPath);
% Camera / imaging
options.cam = 5;
options.angle = 0;
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.removeFringes = false;
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;
% Scan parameter
options.scan_parameter = 'ps_rot_mag_fin_pol_angle';
switch options.measurementName
case 'BECToDroplets'
options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8];
options.titleString = 'BEC to Droplets';
case 'BECToStripes'
options.scan_reference_values = [2.45, 2.44, 2.43, 2.42, 2.4, 2.39, 2.38, 2.37, 2.36, 2.35, 2.34, 2.32, 2.3, 2.28, 2.25, 2.2, 2.15, 2.10, 2.0, 1.90, 1.8];
options.titleString = 'BEC to Stripes';
case 'DropletsToStripes'
options.scan_reference_values = [0, 5, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 35, 40];
options.titleString = 'Droplets to Stripes';
case 'StripesToDroplets'
options.scan_reference_values = fliplr([0, 5, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 35, 40]);
options.titleString = 'Stripes to Droplets';
end
% Flags
options.skipNormalization = false;
options.skipUnshuffling = false;
options.skipPreprocessing = true;
options.skipMasking = true;
options.skipIntensityThresholding = true;
options.skipBinarization = true;
options.skipSaveFigures = false;
options.skipLivePlot = false;
options.showProgressBar = true;
% Extras
options.font = 'Bahnschrift';
%% ===== Build Paths from Data Sources =====
allPaths = {}; % initialize
for i = 1:length(dataSources)
ds = dataSources{i};
% Split the date string into year/month/day
dateParts = strsplit(ds.date, '/');
for run = ds.runs
runStr = sprintf('%04d', run); % 4-digit run number
fullPath = fullfile(options.baseDataFolder, ds.sequence, dateParts{:}, runStr);
if isfolder(fullPath) % only include valid directories
allPaths{end+1} = fullPath;
else
warning('Path does not exist: %s', fullPath);
end
end
end
% Let user select a single path
set(0,'DefaultUicontrolFontSize',10); % increase default font size
[selectedIndex, tf] = listdlg('PromptString','Select a path to analyze:', ...
'SelectionMode','single', ...
'ListString', allPaths, ...
'ListSize',[400, 300]); % width x height in pixels
if tf
options.folderPath = allPaths{selectedIndex}; % store in options
fprintf('Path set to selection: %s\n', options.folderPath);
else
error('No path selected. Aborting.');
end
%% ===== Collect Images and Launch Viewer =====
[od_imgs, scan_parameter_values, file_list] = Helper.collectODImages(options);
Analyzer.runInteractiveODImageViewer(od_imgs, scan_parameter_values, file_list, options);

View File

@ -0,0 +1,78 @@
%% ===== BEC-Droplets-Stripes Settings =====
% Specify data location to run analysis on
dataSources = {
struct('sequence', 'StructuralPhaseTransition', ...
'date', '2025/08/16', ...
'runs', [8]) % specify run numbers as a string in "" or just as a numeric value
};
options = struct();
% File / paths
options.baseDataFolder = '//DyLabNAS/Data';
options.measurementName = 'DropletsToStripes';
scriptFullPath = mfilename('fullpath');
options.saveDirectory = fileparts(scriptFullPath);
% Camera / imaging
options.cam = 5;
options.angle = 0;
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.removeFringes = false;
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;
% Scan parameter
options.scan_parameter = 'ps_rot_mag_fin_pol_angle';
switch options.measurementName
case 'BECToDroplets'
options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8];
options.titleString = 'BEC to Droplets';
case 'BECToStripes'
options.scan_reference_values = [2.45, 2.44, 2.43, 2.42, 2.4, 2.39, 2.38, 2.37, 2.36, 2.35, 2.34, 2.32, 2.3, 2.28, 2.25, 2.2, 2.15, 2.10, 2.0, 1.90, 1.8];
options.titleString = 'BEC to Stripes';
case 'DropletsToStripes'
options.scan_reference_values = [0, 5, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 35, 40];
options.titleString = 'Droplets to Stripes';
case 'StripesToDroplets'
options.scan_reference_values = fliplr([0, 5, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 35, 40]);
options.titleString = 'Stripes to Droplets';
end
% Flags
options.skipNormalization = false;
options.skipUnshuffling = false;
options.skipPreprocessing = true;
options.skipMasking = true;
options.skipIntensityThresholding = true;
options.skipBinarization = true;
options.skipSaveFigures = false;
options.skipLivePlot = false;
options.showProgressBar = true;
% Extras
options.font = 'Bahnschrift';
%% ===== Run Batch Analysis =====
results_all = Helper.batchAnalyze(dataSources, options);

View File

@ -11,8 +11,8 @@ options = struct();
% File / paths % File / paths
options.baseDataFolder = '//DyLabNAS/Data'; options.baseDataFolder = '//DyLabNAS/Data';
options.savefileName = 'BECToDroplets'; options.measurementName = 'BECToDroplets';
scriptFullPath = mfilename('fullpath'); scriptFullPath ´= mfilename('fullpath');
options.saveDirectory = fileparts(scriptFullPath); options.saveDirectory = fileparts(scriptFullPath);
% Camera / imaging % Camera / imaging
@ -45,7 +45,7 @@ options.zoom_size = 50;
% Scan parameter % Scan parameter
options.scan_parameter = 'rot_mag_field'; options.scan_parameter = 'rot_mag_field';
switch options.savefileName switch options.measurementName
case 'BECToDroplets' case 'BECToDroplets'
options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8]; options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8];
options.titleString = 'BEC to Droplets'; options.titleString = 'BEC to Droplets';

View File

@ -11,7 +11,7 @@ options = struct();
% File / paths % File / paths
options.baseDataFolder = '//DyLabNAS/Data'; options.baseDataFolder = '//DyLabNAS/Data';
options.savefileName = 'BECToStripes'; options.measurementName = 'BECToStripes';
scriptFullPath = mfilename('fullpath'); scriptFullPath = mfilename('fullpath');
options.saveDirectory = fileparts(scriptFullPath); options.saveDirectory = fileparts(scriptFullPath);
@ -45,7 +45,7 @@ options.zoom_size = 50;
% Scan parameter % Scan parameter
options.scan_parameter = 'rot_mag_field'; options.scan_parameter = 'rot_mag_field';
switch options.savefileName switch options.measurementName
case 'BECToDroplets' case 'BECToDroplets'
options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8]; options.scan_reference_values = [2.40, 2.39, 2.38, 2.37, 2.35, 2.34, 2.32, 2.30, 2.28, 2.26, 2.24, 2.22, 2.2, 2.15, 2.10, 2.05, 2, 1.95, 1.90, 1.85, 1.8];
options.titleString = 'BEC to Droplets'; options.titleString = 'BEC to Droplets';