From 7904c3aa205985a96681047017183b4b65c0899a Mon Sep 17 00:00:00 2001 From: Karthik Chandrashekara Date: Wed, 15 Oct 2025 18:22:46 +0200 Subject: [PATCH] Added routines to analyze BEC to Droplets, BEC to Stripes. --- .../runAngularSpectralDistributionAnalysis.m | 411 ++++++++++++++++++ .../BECToDroplets/runCorrelationAnalysis.m | 181 ++++++++ .../runRadialSpectralDistributionAnalysis.m | 263 +++++++++++ .../runAngularSpectralDistributionAnalysis.m | 411 ++++++++++++++++++ .../BECToStripes/runCorrelationAnalysis.m | 181 ++++++++ .../runRadialSpectralDistributionAnalysis.m | 263 +++++++++++ 6 files changed, 1710 insertions(+) create mode 100644 Data-Analyzer/+Scripts/BECToDroplets/runAngularSpectralDistributionAnalysis.m create mode 100644 Data-Analyzer/+Scripts/BECToDroplets/runCorrelationAnalysis.m create mode 100644 Data-Analyzer/+Scripts/BECToDroplets/runRadialSpectralDistributionAnalysis.m create mode 100644 Data-Analyzer/+Scripts/BECToStripes/runAngularSpectralDistributionAnalysis.m create mode 100644 Data-Analyzer/+Scripts/BECToStripes/runCorrelationAnalysis.m create mode 100644 Data-Analyzer/+Scripts/BECToStripes/runRadialSpectralDistributionAnalysis.m diff --git a/Data-Analyzer/+Scripts/BECToDroplets/runAngularSpectralDistributionAnalysis.m b/Data-Analyzer/+Scripts/BECToDroplets/runAngularSpectralDistributionAnalysis.m new file mode 100644 index 0000000..70174a7 --- /dev/null +++ b/Data-Analyzer/+Scripts/BECToDroplets/runAngularSpectralDistributionAnalysis.m @@ -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); diff --git a/Data-Analyzer/+Scripts/BECToDroplets/runCorrelationAnalysis.m b/Data-Analyzer/+Scripts/BECToDroplets/runCorrelationAnalysis.m new file mode 100644 index 0000000..47d2f24 --- /dev/null +++ b/Data-Analyzer/+Scripts/BECToDroplets/runCorrelationAnalysis.m @@ -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 diff --git a/Data-Analyzer/+Scripts/BECToDroplets/runRadialSpectralDistributionAnalysis.m b/Data-Analyzer/+Scripts/BECToDroplets/runRadialSpectralDistributionAnalysis.m new file mode 100644 index 0000000..29032d1 --- /dev/null +++ b/Data-Analyzer/+Scripts/BECToDroplets/runRadialSpectralDistributionAnalysis.m @@ -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); + + diff --git a/Data-Analyzer/+Scripts/BECToStripes/runAngularSpectralDistributionAnalysis.m b/Data-Analyzer/+Scripts/BECToStripes/runAngularSpectralDistributionAnalysis.m new file mode 100644 index 0000000..c7d899a --- /dev/null +++ b/Data-Analyzer/+Scripts/BECToStripes/runAngularSpectralDistributionAnalysis.m @@ -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); diff --git a/Data-Analyzer/+Scripts/BECToStripes/runCorrelationAnalysis.m b/Data-Analyzer/+Scripts/BECToStripes/runCorrelationAnalysis.m new file mode 100644 index 0000000..0729d96 --- /dev/null +++ b/Data-Analyzer/+Scripts/BECToStripes/runCorrelationAnalysis.m @@ -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 diff --git a/Data-Analyzer/+Scripts/BECToStripes/runRadialSpectralDistributionAnalysis.m b/Data-Analyzer/+Scripts/BECToStripes/runRadialSpectralDistributionAnalysis.m new file mode 100644 index 0000000..5ffd312 --- /dev/null +++ b/Data-Analyzer/+Scripts/BECToStripes/runRadialSpectralDistributionAnalysis.m @@ -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); + +