Major update - new analysis routines: real space g2, recentered angular spectral distribution curves.
This commit is contained in:
parent
c012497eef
commit
ebebce9ef9
@ -1,8 +1,8 @@
|
||||
function analysis_results = analyzeG2Structures(g2_results, opts)
|
||||
%% analyzeG2Structures
|
||||
% Computes peak anisotropy of g² correlation matrices using ROI and boundary
|
||||
% analysis. Anisotropy is zero if the region cannot be well approximated
|
||||
% by an ellipse or does not stretch enough within the ROI.
|
||||
% analysis, and fits an oriented two-mode Gaussian aligned with the
|
||||
% structure's principal axis within the ROI.
|
||||
%
|
||||
% Inputs:
|
||||
% g2_results : struct with fields
|
||||
@ -14,57 +14,56 @@ function analysis_results = analyzeG2Structures(g2_results, opts)
|
||||
% - roi.size : [w, h] width and height (µm)
|
||||
% - roi.angle : rotation angle (radians, CCW)
|
||||
% - threshold : fraction of peak maximum to define core (default 0.5)
|
||||
% - deviationThreshold : RMS deviation threshold to accept ellipse (default 0.23)
|
||||
% - minEllipseFraction : fraction of ROI diagonal that major axis must span (default 0.8)
|
||||
% - angleLimit : required ellipse angle (radians)
|
||||
% - angleTolerance : tolerance around angleLimit (radians)
|
||||
% - skipLivePlot : true/false (default false)
|
||||
% - fitDeviationThreshold : max normalized RMS deviation for accepting fit
|
||||
% - boundaryPad : extra pixels around boundary to allow Gaussian peaks
|
||||
%
|
||||
% Outputs:
|
||||
% analysis_results : struct with fields
|
||||
% - anisotropy_vals : anisotropy per image (0 = irregular or too small, 0–1 = elliptical)
|
||||
% - peak_centroid : [x, y] per image
|
||||
% - boundary_coords : [x, y] coordinates of peak boundary
|
||||
% - ellipse_params : [x0, y0, a, b, theta] per image
|
||||
% - anisotropy_vals : numeric array
|
||||
% - peak_centroid : {N×1} fitted centroids [x, y]
|
||||
% - ellipse_params : {N×1} fitted parameters + angle
|
||||
% - boundary_coords : {N×1} [x, y] boundary points
|
||||
|
||||
if ~isfield(opts, "skipLivePlot"), opts.skipLivePlot = false; end
|
||||
if ~isfield(opts, "threshold"), opts.threshold = 0.5; end
|
||||
if ~isfield(opts, "deviationThreshold"), opts.deviationThreshold = 0.23; end
|
||||
if ~isfield(opts, "minEllipseFraction"), opts.minEllipseFraction = 0.8; end
|
||||
if ~isfield(opts, "font"), opts.font = 'Arial'; end
|
||||
if ~isfield(opts, "fitDeviationThreshold"), opts.fitDeviationThreshold = 0.2; end
|
||||
if ~isfield(opts, "boundaryPad"), opts.boundaryPad = 2; end
|
||||
|
||||
N_images = numel(g2_results.g2_matrices);
|
||||
|
||||
analysis_results = struct();
|
||||
analysis_results.anisotropy_vals = zeros(1, N_images);
|
||||
analysis_results.peak_centroid = cell(1, N_images);
|
||||
analysis_results = struct();
|
||||
analysis_results.anisotropy_vals = zeros(1, N_images);
|
||||
analysis_results.peak_centroid = cell(1, N_images);
|
||||
analysis_results.boundary_coords = cell(1, N_images);
|
||||
analysis_results.ellipse_params = cell(1, N_images);
|
||||
analysis_results.ellipse_params = cell(1, N_images);
|
||||
|
||||
% ROI definition
|
||||
x0 = opts.roi.center(1);
|
||||
y0 = opts.roi.center(2);
|
||||
w = opts.roi.size(1);
|
||||
h = opts.roi.size(2);
|
||||
x0 = opts.roi.center(1);
|
||||
y0 = opts.roi.center(2);
|
||||
w = opts.roi.size(1);
|
||||
h = opts.roi.size(2);
|
||||
roi_theta = opts.roi.angle;
|
||||
|
||||
% Precompute ROI diagonal for minimum major axis
|
||||
roi_diag = sqrt(w^2 + h^2);
|
||||
minMajorAxis_auto = opts.minEllipseFraction * roi_diag;
|
||||
|
||||
for k = 1:N_images
|
||||
g2_matrix = g2_results.g2_matrices{k};
|
||||
dx_phys = g2_results.dx_phys{k};
|
||||
dy_phys = g2_results.dy_phys{k};
|
||||
|
||||
[X, Y] = meshgrid(dx_phys, dy_phys);
|
||||
|
||||
% ---- Rotated ROI mask ----
|
||||
Xc = X - x0; Yc = Y - y0;
|
||||
%% ---- Core ROI extraction (rotated rectangle) ----
|
||||
Xc = X - x0;
|
||||
Yc = Y - y0;
|
||||
Xr = cos(roi_theta)*Xc + sin(roi_theta)*Yc;
|
||||
Yr = -sin(roi_theta)*Xc + cos(roi_theta)*Yc;
|
||||
roi_mask = (abs(Xr) <= w/2) & (abs(Yr) <= h/2);
|
||||
|
||||
% ---- Threshold ROI to define core region ----
|
||||
x_roi = Xr(roi_mask);
|
||||
y_roi = Yr(roi_mask);
|
||||
z_roi = g2_matrix(roi_mask);
|
||||
|
||||
%% ---- Threshold ROI to define core region ----
|
||||
g2_roi = zeros(size(g2_matrix));
|
||||
g2_roi(roi_mask) = g2_matrix(roi_mask);
|
||||
thresh_val = opts.threshold * max(g2_roi(:));
|
||||
@ -74,88 +73,225 @@ for k = 1:N_images
|
||||
warning('No peak found in ROI for image %d', k);
|
||||
analysis_results.peak_centroid{k} = [NaN, NaN];
|
||||
analysis_results.anisotropy_vals(k) = NaN;
|
||||
analysis_results.ellipse_params{k} = [NaN, NaN, NaN, NaN, NaN];
|
||||
analysis_results.ellipse_params{k} = nan(1,7);
|
||||
analysis_results.boundary_coords{k} = [NaN, NaN];
|
||||
continue;
|
||||
end
|
||||
|
||||
%% ---- Boundary coordinates ----
|
||||
%% ---- Boundary coordinates (edge detection inside ROI) ----
|
||||
B = bwboundaries(BW);
|
||||
boundary = B{1};
|
||||
x_bound = dx_phys(boundary(:,2));
|
||||
y_bound = dy_phys(boundary(:,1));
|
||||
boundary = B{1};
|
||||
x_bound = dx_phys(boundary(:,2));
|
||||
y_bound = dy_phys(boundary(:,1));
|
||||
analysis_results.boundary_coords{k} = [x_bound, y_bound];
|
||||
|
||||
%% ---- Ellipse fitting and anisotropy using boundary region ----
|
||||
stats = regionprops(BW, 'Centroid','MajorAxisLength','MinorAxisLength','Orientation');
|
||||
if ~isempty(stats)
|
||||
a = stats(1).MajorAxisLength / 2 * mean(diff(dx_phys));
|
||||
b = stats(1).MinorAxisLength / 2 * mean(diff(dy_phys));
|
||||
theta = -stats(1).Orientation * pi/180;
|
||||
%% ---- Compute centroid and orientation (use PCA on boundary in ROI-local frame) ----
|
||||
% Transform boundary points to ROI-local coordinates (same local coords as Xr,Yr)
|
||||
Xc_b = x_bound - x0;
|
||||
Yc_b = y_bound - y0;
|
||||
Xr_b = cos(roi_theta)*Xc_b + sin(roi_theta)*Yc_b;
|
||||
Yr_b = -sin(roi_theta)*Xc_b + cos(roi_theta)*Yc_b;
|
||||
|
||||
x_c = interp1(1:numel(dx_phys), dx_phys, stats(1).Centroid(1), 'linear', 'extrap');
|
||||
y_c = interp1(1:numel(dy_phys), dy_phys, stats(1).Centroid(2), 'linear', 'extrap');
|
||||
% PCA on boundary points in ROI-local frame (robust and simple)
|
||||
boundary_mean = [mean(Xr_b), mean(Yr_b)];
|
||||
XYb = [Xr_b - boundary_mean(1), Yr_b - boundary_mean(2)];
|
||||
try
|
||||
coeff = pca(XYb);
|
||||
major = coeff(:,1);
|
||||
angle_region = atan2(major(2), major(1)); % angle in ROI-local coords (radians)
|
||||
catch
|
||||
% fallback: use roi_theta as orientation if PCA fails
|
||||
angle_region = 0;
|
||||
end
|
||||
|
||||
Xb = x_bound - x_c;
|
||||
Yb = y_bound - y_c;
|
||||
Xrot = cos(theta)*Xb + sin(theta)*Yb;
|
||||
Yrot = -sin(theta)*Xb + cos(theta)*Yb;
|
||||
% We'll define longitudinal axis in ROI-local coordinates as angle_region,
|
||||
% transverse axis is angle_region + pi/2.
|
||||
|
||||
r_norm = (Xrot/a).^2 + (Yrot/b).^2;
|
||||
deviation = sqrt(mean((r_norm - 1).^2));
|
||||
%% ---- Use points inside boundary for 1D profile (use ROI-local coords) ----
|
||||
% We already have axis-aligned ROI samples x_roi, y_roi, z_roi in ROI-local coords.
|
||||
xData_local = x_roi;
|
||||
yData_local = y_roi;
|
||||
zData = z_roi;
|
||||
|
||||
%% ---- Interpolate onto regular axis-aligned grid in ROI-local coords ----
|
||||
Nx = ceil(w*10); Ny = ceil(h*10);
|
||||
xq = linspace(-w/2, w/2, Nx);
|
||||
yq = linspace(-h/2, h/2, Ny);
|
||||
[Xq, Yq] = meshgrid(xq, yq);
|
||||
Zq = griddata(xData_local, yData_local, zData, Xq, Yq, 'cubic');
|
||||
|
||||
% ---- Angle criterion (± allowed) ----
|
||||
allowed = opts.angleLimit;
|
||||
tol = opts.angleTolerance;
|
||||
angle_ok = (abs(theta - allowed) <= tol) || (abs(theta + allowed) <= tol);
|
||||
% If griddata produced NaNs in large patches, fill with nearest to avoid empty bins
|
||||
if any(isnan(Zq(:)))
|
||||
Zq = fillmissing(Zq,'nearest');
|
||||
end
|
||||
|
||||
% Only accept ellipse if deviation small, major axis long enough, and angle ok
|
||||
if (deviation > opts.deviationThreshold) || (a < minMajorAxis_auto) || (~angle_ok)
|
||||
anisotropy = 0;
|
||||
style = '--'; % dashed if rejected
|
||||
% --- Build rotated coordinates so major axis aligns with new X'
|
||||
theta = angle_region; % ROI-local angle of major axis
|
||||
Rrot = [cos(theta), sin(theta); -sin(theta), cos(theta)]; % rotation that maps [x;y] -> [x';y'] where x' along major
|
||||
XY = [Xq(:)'; Yq(:)'];
|
||||
XY_rot = Rrot * XY;
|
||||
Xrot = reshape(XY_rot(1,:), size(Xq));
|
||||
Yrot = reshape(XY_rot(2,:), size(Yq));
|
||||
|
||||
% --- Compute 1D longitudinal profile by binning along X' (major axis)
|
||||
nbins_long = size(Xq,1); % choose number of bins ~ Ny (vertical resolution)
|
||||
x_edges_long = linspace(min(Xrot(:)), max(Xrot(:)), nbins_long+1);
|
||||
x_centers_long = 0.5*(x_edges_long(1:end-1)+x_edges_long(2:end));
|
||||
prof_long = nan(1, nbins_long);
|
||||
for ii = 1:nbins_long
|
||||
mask_bin = Xrot >= x_edges_long(ii) & Xrot < x_edges_long(ii+1);
|
||||
vals = Zq(mask_bin);
|
||||
if isempty(vals)
|
||||
prof_long(ii) = NaN;
|
||||
else
|
||||
anisotropy = 1 - b/a;
|
||||
style = '-'; % solid if accepted
|
||||
prof_long(ii) = mean(vals,'omitnan');
|
||||
end
|
||||
end
|
||||
|
||||
analysis_results.peak_centroid{k} = [x_c, y_c];
|
||||
analysis_results.anisotropy_vals(k) = anisotropy;
|
||||
analysis_results.ellipse_params{k} = [x_c, y_c, a, b, theta];
|
||||
else
|
||||
x_c = NaN; y_c = NaN; a=NaN; b=NaN; theta=NaN; anisotropy=NaN; style='--';
|
||||
% --- Compute 1D transverse profile by binning along Y' (minor axis)
|
||||
nbins_trans = size(Xq,2);
|
||||
y_edges_trans = linspace(min(Yrot(:)), max(Yrot(:)), nbins_trans+1);
|
||||
y_centers_trans = 0.5*(y_edges_trans(1:end-1)+y_edges_trans(2:end));
|
||||
prof_trans = nan(1, nbins_trans);
|
||||
for ii = 1:nbins_trans
|
||||
mask_bin = Yrot >= y_edges_trans(ii) & Yrot < y_edges_trans(ii+1);
|
||||
vals = Zq(mask_bin);
|
||||
if isempty(vals)
|
||||
prof_trans(ii) = NaN;
|
||||
else
|
||||
prof_trans(ii) = mean(vals,'omitnan');
|
||||
end
|
||||
end
|
||||
|
||||
% --- Prepare x-axis positions for profile centers (in ROI-local units)
|
||||
xcent_long = x_centers_long; % positions along major axis (µm)
|
||||
xcent_trans = y_centers_trans; % positions along minor axis (µm)
|
||||
|
||||
% --- Remove NaNs from profiles
|
||||
valid_long = ~isnan(prof_long);
|
||||
x_long = xcent_long(valid_long);
|
||||
y_long = prof_long(valid_long);
|
||||
|
||||
valid_trans = ~isnan(prof_trans);
|
||||
x_trans = xcent_trans(valid_trans);
|
||||
y_trans = prof_trans(valid_trans);
|
||||
|
||||
% --- If not enough points, skip
|
||||
if numel(x_long) < 8 || numel(x_trans) < 8
|
||||
warning('Too few points for longitudinal/transverse fits for image %d', k);
|
||||
analysis_results.peak_centroid{k} = [NaN, NaN];
|
||||
analysis_results.anisotropy_vals(k) = NaN;
|
||||
analysis_results.ellipse_params{k} = [NaN, NaN, NaN, NaN, NaN];
|
||||
analysis_results.ellipse_params{k} = nan(1,7);
|
||||
continue;
|
||||
end
|
||||
|
||||
%% ---- Fit two-Gaussian to longitudinal profile (use your working 1D fit) ----
|
||||
twoGauss1D = @(p,x) ...
|
||||
p(1) * exp(-0.5*((x - p(2))/max(p(3),1e-6)).^2) + ... % Gaussian 1
|
||||
p(4) * exp(-0.5*((x - p(5))/max(p(6),1e-6)).^2) + ... % Gaussian 2
|
||||
p(7); % offset
|
||||
% initial guesses (longitudinal)
|
||||
[~, mainIdxL] = max(y_long);
|
||||
A1_guessL = y_long(mainIdxL);
|
||||
mu1_guessL = x_long(mainIdxL);
|
||||
sigma1_guessL = range(x_long)/10;
|
||||
A2_guessL = 0.8 * A1_guessL;
|
||||
mu2_guessL = mu1_guessL + range(x_long)/5;
|
||||
sigma2_guessL = sigma1_guessL;
|
||||
offset_guessL = min(y_long);
|
||||
p0L = [A1_guessL, mu1_guessL, sigma1_guessL, A2_guessL, mu2_guessL, sigma2_guessL, offset_guessL];
|
||||
lbL = [0, min(x_long), 1e-6, 0, min(x_long), 1e-6, -Inf];
|
||||
ubL = [2, max(x_long), range(x_long), 2, max(x_long), range(x_long), Inf];
|
||||
opts_lsq = optimoptions('lsqcurvefit','Display','off','MaxFunctionEvaluations',1e4,'MaxIterations',1e4);
|
||||
try
|
||||
pFitL = lsqcurvefit(twoGauss1D, p0L, x_long, y_long, lbL, ubL, opts_lsq);
|
||||
catch
|
||||
warning('Longitudinal 1D fit failed for image %d, using initial guess.', k);
|
||||
pFitL = p0L;
|
||||
end
|
||||
|
||||
%% ---- Fit two-Gaussian to transverse profile (use same logic) ----
|
||||
[~, mainIdxT] = max(y_trans);
|
||||
A1_guessT = y_trans(mainIdxT);
|
||||
mu1_guessT = x_trans(mainIdxT);
|
||||
sigma1_guessT = range(x_trans)/10;
|
||||
A2_guessT = 0.8 * A1_guessT;
|
||||
mu2_guessT = mu1_guessT + range(x_trans)/5;
|
||||
sigma2_guessT = sigma1_guessT;
|
||||
offset_guessT = min(y_trans);
|
||||
p0T = [A1_guessT, mu1_guessT, sigma1_guessT, A2_guessT, mu2_guessT, sigma2_guessT, offset_guessT];
|
||||
lbT = [0, min(x_trans), 1e-6, 0, min(x_trans), 1e-6, -Inf];
|
||||
ubT = [2, max(x_trans), range(x_trans), 2, max(x_trans), range(x_trans), Inf];
|
||||
try
|
||||
pFitT = lsqcurvefit(twoGauss1D, p0T, x_trans, y_trans, lbT, ubT, opts_lsq);
|
||||
catch
|
||||
warning('Transverse 1D fit failed for image %d, using initial guess.', k);
|
||||
pFitT = p0T;
|
||||
end
|
||||
|
||||
%% ---- Determine anisotropy from the two fits (ratio of widths) ----
|
||||
sigma_long = mean([pFitL(3), pFitL(6)]);
|
||||
sigma_trans = mean([pFitT(3), pFitT(6)]);
|
||||
anisotropy = sigma_trans / sigma_long; % transverse / longitudinal (ratio)
|
||||
% store centroid roughly as ROI-local centroid rotated back to lab coords
|
||||
centroid_local_roi = boundary_mean'; % mean of boundary in ROI-local coords
|
||||
% rotate back to lab frame
|
||||
rotBack = [cos(roi_theta), -sin(roi_theta); sin(roi_theta), cos(roi_theta)];
|
||||
centroid_lab = rotBack * centroid_local_roi + [x0; y0];
|
||||
centroid_lab = centroid_lab(:)';
|
||||
|
||||
analysis_results.peak_centroid{k} = centroid_lab;
|
||||
analysis_results.anisotropy_vals(k) = anisotropy;
|
||||
analysis_results.ellipse_params{k} = [pFitL, pFitT, angle_region];
|
||||
|
||||
%% ---- Visualization ----
|
||||
if ~opts.skipLivePlot
|
||||
fig=figure(100); clf;
|
||||
set(fig, 'Color', 'w', 'Position',[100 100 950 750]);
|
||||
fig = figure(100); clf;
|
||||
set(fig,'Color','w','Position',[100 100 1600 450]);
|
||||
tiledlayout(1,3,'Padding','compact','TileSpacing','compact');
|
||||
|
||||
% --- Panel 1: 2D g² map + ROI + boundary
|
||||
nexttile;
|
||||
imagesc(dx_phys, dy_phys, g2_matrix);
|
||||
axis image;
|
||||
set(gca, 'YDir', 'normal', 'FontName', opts.font, 'FontSize', 14);
|
||||
colormap(Colormaps.coolwarm()); colorbar;
|
||||
hold on;
|
||||
axis image; set(gca,'YDir','normal'); colormap(Colormaps.coolwarm()); colorbar; hold on;
|
||||
|
||||
corners = [ -w/2, -h/2; w/2, -h/2; w/2, h/2; -w/2, h/2];
|
||||
% ROI bounding box (red dashed)
|
||||
rect = [-w/2, -h/2; w/2, -h/2; w/2, h/2; -w/2, h/2; -w/2, -h/2];
|
||||
R = [cos(roi_theta), -sin(roi_theta); sin(roi_theta), cos(roi_theta)];
|
||||
corners_rot = (R*corners')' + [x0, y0];
|
||||
corners_rot = [corners_rot; corners_rot(1,:)];
|
||||
plot(corners_rot(:,1), corners_rot(:,2), 'r--', 'LineWidth',1.5);
|
||||
rect_rot = (R * rect')';
|
||||
plot(rect_rot(:,1)+x0, rect_rot(:,2)+y0, 'r--', 'LineWidth',2);
|
||||
|
||||
plot(x_bound, y_bound, 'g-', 'LineWidth',2);
|
||||
plot(x_c, y_c, 'mo', 'MarkerSize',8, 'LineWidth',2);
|
||||
% Detected peak boundary (green solid)
|
||||
if ~isempty(x_bound)
|
||||
plot(x_bound, y_bound, 'g-', 'LineWidth',2);
|
||||
end
|
||||
|
||||
t = linspace(0, 2*pi, 200);
|
||||
ellipse_x = x_c + a*cos(t)*cos(theta) - b*sin(t)*sin(theta);
|
||||
ellipse_y = y_c + a*cos(t)*sin(theta) + b*sin(t)*cos(theta);
|
||||
plot(ellipse_x, ellipse_y, ['y' style], 'LineWidth',2);
|
||||
xlabel('\Deltax (\mum)'); ylabel('\Deltay (\mum)');
|
||||
title(sprintf('Image %d | 2D g^2', k));
|
||||
|
||||
title(sprintf('Image %d | Anisotropy = %.2f | Deviation = %.2f | θ = %.2f°', ...
|
||||
k, anisotropy, deviation, rad2deg(theta)));
|
||||
xlabel('x (\mum)'); ylabel('y (\mum)');
|
||||
drawnow; pause(1.0);
|
||||
% --- Panel 2: Longitudinal 1D profile and fit
|
||||
nexttile;
|
||||
plot(x_long, y_long, 'k.-', 'DisplayName','Data'); hold on;
|
||||
xFine = linspace(min(x_long), max(x_long), 500);
|
||||
yFitL = twoGauss1D(pFitL, xFine);
|
||||
plot(xFine, yFitL, 'r-', 'LineWidth',1.5,'DisplayName','Fit');
|
||||
title('Longitudinal profile','FontName',opts.font);
|
||||
xlabel('x_{longitudinal} (\mum)');
|
||||
ylabel('g^2');
|
||||
legend('Location','northeast'); grid on;
|
||||
|
||||
% --- Panel 3: Transverse 1D profile and fit
|
||||
nexttile;
|
||||
plot(x_trans, y_trans, 'b.-', 'DisplayName','Data'); hold on;
|
||||
xFineT = linspace(min(x_trans), max(x_trans), 500);
|
||||
yFitT = twoGauss1D(pFitT, xFineT);
|
||||
plot(xFineT, yFitT, 'r-', 'LineWidth',1.5,'DisplayName','Fit');
|
||||
title('Transverse profile','FontName',opts.font);
|
||||
xlabel('x_{transverse} (\mum)');
|
||||
ylabel('g^2');
|
||||
legend('Location','northeast'); grid on;
|
||||
|
||||
drawnow;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -67,7 +67,6 @@ function results = conductSpectralAnalysis(od_imgs, scan_parameter_values, optio
|
||||
%% ===== Initialization =====
|
||||
N_shots = length(od_imgs); % total number of images
|
||||
fft_imgs = cell(1, N_shots); % FFT of each image
|
||||
angular_spectral_distribution = cell(1, N_shots); % S(θ) angular spectrum
|
||||
radial_spectral_contrast = zeros(1, N_shots); % radial contrast metric
|
||||
angular_spectral_weight = zeros(1, N_shots); % integrated angular weight
|
||||
|
||||
@ -147,8 +146,7 @@ function results = conductSpectralAnalysis(od_imgs, scan_parameter_values, optio
|
||||
S_k_smoothed = movmean(S_k, radial_window_size);
|
||||
|
||||
% Store results
|
||||
angular_spectral_distribution{k} = S_theta;
|
||||
radial_spectral_contrast(k) = Calculator.computeRadialSpectralContrast(k_rho_vals, S_k_smoothed, k_min, k_max);
|
||||
radial_spectral_contrast(k) = Calculator.computeRadialSpectralContrast(k_rho_vals, S_k_smoothed, k_min, k_max);
|
||||
|
||||
% Normalize angular spectrum and compute weight
|
||||
S_theta_norm = S_theta / max(S_theta);
|
||||
@ -281,7 +279,6 @@ function results = conductSpectralAnalysis(od_imgs, scan_parameter_values, optio
|
||||
results.S_theta_all = S_theta_all;
|
||||
results.k_rho_vals = k_rho_vals;
|
||||
results.S_k_all = S_k_all;
|
||||
results.angular_spectral_distribution = angular_spectral_distribution;
|
||||
results.S_k_smoothed_all = S_k_smoothed_all;
|
||||
results.radial_spectral_contrast = radial_spectral_contrast;
|
||||
results.S_theta_norm_all = S_theta_norm_all;
|
||||
|
||||
175
Data-Analyzer/+Analyzer/fitTwoGaussianCurves.m
Normal file
175
Data-Analyzer/+Analyzer/fitTwoGaussianCurves.m
Normal file
@ -0,0 +1,175 @@
|
||||
function fitResults = fitTwoGaussianCurves(S_theta_all, theta_vals, varargin)
|
||||
%% fitTwoGaussianCurves
|
||||
% Fits a two-Gaussian model to multiple spectral curves from θ=0 up to
|
||||
% the first secondary peak ≥50% of the primary peak, within 0–π/2 radians.
|
||||
%
|
||||
% Author: Karthik
|
||||
% Date: 2025-10-06
|
||||
% Version: 1.0
|
||||
%
|
||||
% Inputs:
|
||||
% S_theta_all - 1xN cell array of spectral curves
|
||||
% theta_vals - vector of corresponding theta values (radians)
|
||||
%
|
||||
% Optional name-value pairs:
|
||||
% 'MaxTheta' - Maximum theta for search (default: pi/2)
|
||||
% 'DeviationLimit' - Max relative deviation allowed (default: 0.3)
|
||||
%
|
||||
% Output:
|
||||
% fitResults - struct array with fields:
|
||||
% .pFit - fitted parameters
|
||||
% .thetaFit - theta values used for fit
|
||||
% .xFit - curve values used for fit
|
||||
% .yFit - fitted curve evaluated on thetaFine
|
||||
% .thetaFine - fine theta for plotting
|
||||
% .isValid - whether fit passed deviation threshold
|
||||
% .fitMaxTheta - theta used as fit limit
|
||||
|
||||
% --- Parse optional inputs ---
|
||||
p = inputParser;
|
||||
addParameter(p, 'MaxTheta', pi/2, @(x) isnumeric(x) && isscalar(x));
|
||||
addParameter(p, 'DeviationLimit', 0.3, @(x) isnumeric(x) && isscalar(x));
|
||||
parse(p, varargin{:});
|
||||
opts = p.Results;
|
||||
|
||||
Ncurves = numel(S_theta_all);
|
||||
fitResults = struct('pFit',[],'thetaFit',[],'xFit',[],'yFit',[],...
|
||||
'thetaFine',[],'isValid',[],'fitMaxTheta',[]);
|
||||
|
||||
% --- Preprocess curves: shift first peak to zero without wrapping ---
|
||||
S_theta_all_shifted = cell(size(S_theta_all));
|
||||
for k = 1:Ncurves
|
||||
curve = S_theta_all{k};
|
||||
|
||||
% --- Find first peak in 0–MaxTheta range ---
|
||||
idx_range = find(theta_vals >= 0 & theta_vals <= opts.MaxTheta);
|
||||
if isempty(idx_range)
|
||||
[~, peak_idx] = max(curve);
|
||||
else
|
||||
[~, local_max_idx] = max(curve(idx_range));
|
||||
peak_idx = idx_range(local_max_idx);
|
||||
end
|
||||
|
||||
% Take only the part from the peak to the end
|
||||
S_theta_all_shifted{k} = curve(peak_idx:end);
|
||||
end
|
||||
|
||||
% --- Pad curves to the same length ---
|
||||
Npoints_shifted = max(cellfun(@numel, S_theta_all_shifted));
|
||||
for k = 1:Ncurves
|
||||
len = numel(S_theta_all_shifted{k});
|
||||
if len < Npoints_shifted
|
||||
S_theta_all_shifted{k} = [S_theta_all_shifted{k}, nan(1, Npoints_shifted-len)];
|
||||
end
|
||||
end
|
||||
|
||||
% --- Define recentered theta values ---
|
||||
theta_recentered = theta_vals(1:Npoints_shifted) - theta_vals(1);
|
||||
|
||||
for k = 1:Ncurves
|
||||
x = S_theta_all_shifted{k}; % use recentered curve
|
||||
theta = theta_recentered; % recentered x-values
|
||||
|
||||
validIdx = ~isnan(x);
|
||||
x = x(validIdx);
|
||||
theta = theta(validIdx);
|
||||
|
||||
% --- Restrict to 0–MaxTheta ---
|
||||
mask = theta>=0 & theta<=opts.MaxTheta;
|
||||
x = x(mask);
|
||||
theta = theta(mask);
|
||||
if numel(theta) < 8
|
||||
warning('Curve %d too short (<8 points), skipping.', k);
|
||||
continue;
|
||||
end
|
||||
|
||||
% --- Normalize so S(0)=1 ---
|
||||
x = x / x(1);
|
||||
|
||||
% --- Smooth for stable peak detection ---
|
||||
xSmooth = smooth(x, 5, 'moving');
|
||||
|
||||
% --- Find local maxima ---
|
||||
[pk, locIdx] = findpeaks(xSmooth);
|
||||
thetaPeaks = theta(locIdx);
|
||||
|
||||
if isempty(pk)
|
||||
warning('Curve %d has no significant peaks, restricting to within pi/4.', k);
|
||||
fitMaxTheta = pi/4;
|
||||
else
|
||||
% --- Primary peak ---
|
||||
[mainAmp, mainIdx] = max(pk);
|
||||
mainTheta = thetaPeaks(mainIdx);
|
||||
|
||||
thetaPeaks = thetaPeaks(:); % column vector
|
||||
pk = pk(:);
|
||||
|
||||
% --- Secondary peak ≥50% of primary after main ---
|
||||
secondaryIdx = find((thetaPeaks>mainTheta) & (pk>=0.50*mainAmp), 1, 'first');
|
||||
if isempty(secondaryIdx)
|
||||
fitMaxTheta = opts.MaxTheta;
|
||||
else
|
||||
fitMaxTheta = thetaPeaks(secondaryIdx);
|
||||
end
|
||||
end
|
||||
|
||||
% --- Extract data up to secondary peak ---
|
||||
fitMask = theta>=0 & theta<=fitMaxTheta;
|
||||
thetaFit = theta(fitMask);
|
||||
xFit = x(fitMask);
|
||||
if numel(thetaFit) < 8
|
||||
warning('Curve %d has too few points for fitting, skipping.', k);
|
||||
continue;
|
||||
end
|
||||
|
||||
% --- Two-Gaussian model ---
|
||||
twoGauss = @(p,theta) ...
|
||||
1.0*exp(-0.5*((theta-p(1))/max(p(2),1e-6)).^2) + ...
|
||||
p(3)*exp(-0.5*((theta-p(4))/max(p(5),1e-6)).^2);
|
||||
% p = [mu1, sigma1, A2, mu2, sigma2]
|
||||
|
||||
% --- Initial guesses ---
|
||||
mu1_guess = 0;
|
||||
sigma1_guess = 0.1*fitMaxTheta;
|
||||
A2_guess = max(xFit)*0.8;
|
||||
mu2_guess = fitMaxTheta/2;
|
||||
sigma2_guess = fitMaxTheta/5;
|
||||
p0 = [mu1_guess, sigma1_guess, A2_guess, mu2_guess, sigma2_guess];
|
||||
|
||||
% --- Bounds ---
|
||||
lb = [0, 1e-6, 0, 0, 1e-6];
|
||||
ub = [fitMaxTheta/4, fitMaxTheta, 2, fitMaxTheta, fitMaxTheta];
|
||||
|
||||
optsLSQ = optimoptions('lsqcurvefit','Display','off', ...
|
||||
'MaxFunctionEvaluations',1e4,'MaxIterations',1e4);
|
||||
|
||||
% --- Fit ---
|
||||
try
|
||||
pFit = lsqcurvefit(twoGauss, p0, thetaFit, xFit, lb, ub, optsLSQ);
|
||||
catch
|
||||
warning('Curve %d fit failed, using initial guess.', k);
|
||||
pFit = p0;
|
||||
end
|
||||
|
||||
% --- Evaluate fitted curve ---
|
||||
thetaFine = linspace(0, opts.MaxTheta, 500);
|
||||
yFit = twoGauss(pFit, thetaFine);
|
||||
|
||||
% --- Compute relative deviation ---
|
||||
yFitInterp = twoGauss(pFit, thetaFit);
|
||||
relDeviation = abs(yFitInterp - xFit) ./ max(xFit, 1e-6);
|
||||
maxRelDev = max(relDeviation);
|
||||
|
||||
% --- Goodness-of-fit ---
|
||||
isValid = maxRelDev <= opts.DeviationLimit;
|
||||
|
||||
% --- Store results ---
|
||||
fitResults(k).pFit = pFit;
|
||||
fitResults(k).thetaFit = thetaFit;
|
||||
fitResults(k).xFit = xFit;
|
||||
fitResults(k).thetaFine = thetaFine;
|
||||
fitResults(k).yFit = yFit;
|
||||
fitResults(k).isValid = isValid;
|
||||
fitResults(k).fitMaxTheta = fitMaxTheta;
|
||||
end
|
||||
end
|
||||
@ -74,11 +74,11 @@ function [results, scan_parameter_values, scan_reference_values] = performAnalys
|
||||
% Extract angular correlations
|
||||
full_g2_results = Analyzer.extractAutocorrelation(...
|
||||
spectral_analysis_results.theta_vals, ...
|
||||
spectral_analysis_results.angular_spectral_distribution, ...
|
||||
spectral_analysis_results.S_theta_all, ...
|
||||
scan_parameter_values, N_shots, options.N_angular_bins);
|
||||
|
||||
custom_g_results = Analyzer.extractCustomCorrelation(...
|
||||
spectral_analysis_results.angular_spectral_distribution, ...
|
||||
spectral_analysis_results.S_theta_all, ...
|
||||
scan_parameter_values, N_shots, options.N_angular_bins);
|
||||
|
||||
fprintf('\n[INFO] Spectral analysis complete!\n');
|
||||
|
||||
150
Data-Analyzer/+Plotter/plotFitParameterPDF.m
Normal file
150
Data-Analyzer/+Plotter/plotFitParameterPDF.m
Normal file
@ -0,0 +1,150 @@
|
||||
function plotFitParameterPDF(fitResults, scanValues, paramName, varargin)
|
||||
%% plotFitParameterPDF
|
||||
% Author: Karthik
|
||||
% Date: 2025-10-06
|
||||
% Version: 1.0
|
||||
%
|
||||
% Description:
|
||||
% Plots 2D PDF (heatmap) of any parameter from two-Gaussian fit results
|
||||
% for multiple scan parameters, with optional overlay of mean ± SEM.
|
||||
%
|
||||
% Inputs:
|
||||
% fitResults - struct array from fitTwoGaussianCurves
|
||||
% scanValues - vector of scan parameter values
|
||||
% paramName - string specifying the parameter to plot:
|
||||
% 'mu1', 'sigma1', 'A2', 'mu2', 'sigma2'
|
||||
%
|
||||
% Optional name-value pairs (same as before, plus OverlayMeanSEM):
|
||||
% 'OverlayMeanSEM' - logical, overlay mean ± SEM (default: true)
|
||||
|
||||
% --- Parse optional inputs ---
|
||||
p = inputParser;
|
||||
addParameter(p, 'Title', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'XLabel', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'YLabel', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'FigNum', 1, @(x) isscalar(x));
|
||||
addParameter(p, 'FontName', 'Arial', @ischar);
|
||||
addParameter(p, 'FontSize', 14, @isnumeric);
|
||||
addParameter(p, 'SkipSaveFigures', false, @islogical);
|
||||
addParameter(p, 'SaveFileName', 'FitParameterPDF.fig', @ischar);
|
||||
addParameter(p, 'SaveDirectory', pwd, @ischar);
|
||||
addParameter(p, 'NumPoints', 200, @(x) isscalar(x));
|
||||
addParameter(p, 'DataRange', [], @(x) isempty(x) || numel(x)==2);
|
||||
addParameter(p, 'XLim', [], @(x) isempty(x) || numel(x)==2);
|
||||
addParameter(p, 'Colormap', @jet);
|
||||
addParameter(p, 'PlotType', 'histogram', @(x) any(validatestring(x,{'kde','histogram'})));
|
||||
addParameter(p, 'NumberOfBins', 50, @isscalar);
|
||||
addParameter(p, 'NormalizeHistogram', true, @islogical);
|
||||
addParameter(p, 'OverlayMeanSEM', true, @islogical);
|
||||
parse(p, varargin{:});
|
||||
opts = p.Results;
|
||||
|
||||
% --- Map paramName to index ---
|
||||
paramMap = struct('mu1',1,'sigma1',2,'A2',3,'mu2',4,'sigma2',5);
|
||||
if ~isfield(paramMap,paramName)
|
||||
error('Invalid paramName. Must be one of: mu1, sigma1, A2, mu2, sigma2');
|
||||
end
|
||||
paramIdx = paramMap.(paramName);
|
||||
|
||||
% --- Determine repetitions and scan parameters ---
|
||||
N_params = numel(scanValues);
|
||||
N_total = numel(fitResults);
|
||||
N_reps = N_total / N_params;
|
||||
|
||||
% --- Extract chosen parameter values ---
|
||||
paramValues = nan(N_reps, N_params);
|
||||
for k = 1:N_total
|
||||
paramIdxScan = mod(k-1, N_params) + 1;
|
||||
repIdx = floor((k-1)/N_params) + 1;
|
||||
if fitResults(k).isValid
|
||||
paramValues(repIdx, paramIdxScan) = fitResults(k).pFit(paramIdx);
|
||||
end
|
||||
end
|
||||
|
||||
% --- Prepare data per scan parameter ---
|
||||
dataCell = cell(N_params,1);
|
||||
for i = 1:N_params
|
||||
dataCell{i} = paramValues(:,i);
|
||||
end
|
||||
|
||||
% --- Determine y-range ---
|
||||
if isempty(opts.DataRange)
|
||||
allData = cell2mat(dataCell(:));
|
||||
y_min = min(allData);
|
||||
y_max = max(allData);
|
||||
else
|
||||
y_min = opts.DataRange(1);
|
||||
y_max = opts.DataRange(2);
|
||||
end
|
||||
|
||||
% --- Prepare PDF grid/matrix ---
|
||||
if strcmpi(opts.PlotType,'kde')
|
||||
y_grid = linspace(y_min, y_max, opts.NumPoints);
|
||||
pdf_matrix = zeros(numel(y_grid), N_params);
|
||||
else
|
||||
edges = linspace(y_min, y_max, opts.NumberOfBins+1);
|
||||
binCenters = (edges(1:end-1) + edges(2:end))/2;
|
||||
pdf_matrix = zeros(numel(binCenters), N_params);
|
||||
end
|
||||
|
||||
% --- Compute PDFs ---
|
||||
for i = 1:N_params
|
||||
data = dataCell{i};
|
||||
data = data(~isnan(data));
|
||||
if isempty(data), continue; end
|
||||
if strcmpi(opts.PlotType,'kde')
|
||||
f = ksdensity(data, y_grid);
|
||||
pdf_matrix(:,i) = f;
|
||||
else
|
||||
counts = histcounts(data, edges);
|
||||
if opts.NormalizeHistogram
|
||||
binWidth = edges(2) - edges(1);
|
||||
counts = counts / (sum(counts) * binWidth);
|
||||
end
|
||||
pdf_matrix(:,i) = counts(:);
|
||||
end
|
||||
end
|
||||
|
||||
% --- Plot heatmap ---
|
||||
fig = figure(opts.FigNum); clf(fig);
|
||||
set(fig, 'Color', 'w', 'Position', [100 100 950 750]);
|
||||
if strcmpi(opts.PlotType,'kde')
|
||||
imagesc(scanValues, y_grid, pdf_matrix);
|
||||
else
|
||||
imagesc(scanValues, binCenters, pdf_matrix);
|
||||
end
|
||||
|
||||
set(gca, 'YDir', 'normal', 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
xlabel(opts.XLabel, 'FontSize', opts.FontSize, 'FontName', opts.FontName);
|
||||
ylabel(opts.YLabel, 'FontSize', opts.FontSize, 'FontName', opts.FontName);
|
||||
title(opts.Title, 'FontSize', opts.FontSize+2, 'FontWeight', 'bold');
|
||||
|
||||
colormap(feval(opts.Colormap));
|
||||
c = colorbar;
|
||||
if strcmpi(opts.PlotType,'kde') || opts.NormalizeHistogram
|
||||
ylabel(c, 'Probability Density', 'Rotation', -90, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
else
|
||||
ylabel(c, 'Counts', 'Rotation', -90, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
end
|
||||
|
||||
if ~isempty(opts.XLim)
|
||||
xlim(opts.XLim);
|
||||
end
|
||||
|
||||
% --- Overlay mean ± SEM if requested ---
|
||||
if opts.OverlayMeanSEM
|
||||
meanParam = nanmean(paramValues,1);
|
||||
semParam = nanstd(paramValues,0,1) ./ sqrt(sum(~isnan(paramValues),1));
|
||||
hold on;
|
||||
xVec = reshape(scanValues, 1, []); % ensures 1 × N_params
|
||||
fill([xVec, fliplr(xVec)], [meanParam - semParam, fliplr(meanParam + semParam)], ...
|
||||
[0.2 0.4 0.8], 'FaceAlpha',0.2, 'EdgeColor','none');
|
||||
plot(scanValues, meanParam, 'k-', 'LineWidth', 2);
|
||||
end
|
||||
|
||||
% --- Save figure ---
|
||||
if ~opts.SkipSaveFigures
|
||||
if ~exist(opts.SaveDirectory,'dir'), mkdir(opts.SaveDirectory); end
|
||||
savefig(fig, fullfile(opts.SaveDirectory, opts.SaveFileName));
|
||||
end
|
||||
end
|
||||
@ -41,7 +41,7 @@ function plotMeanWithSE(scan_values, data_values, varargin)
|
||||
else
|
||||
groupVals = group;
|
||||
end
|
||||
mean_vals(i) = mean(groupVals);
|
||||
mean_vals(i) = mean(groupVals, 'omitnan');
|
||||
stderr_vals(i) = std(groupVals) / sqrt(length(groupVals));
|
||||
end
|
||||
|
||||
|
||||
182
Data-Analyzer/+Plotter/plotODG2withAnalysis.m
Normal file
182
Data-Analyzer/+Plotter/plotODG2withAnalysis.m
Normal file
@ -0,0 +1,182 @@
|
||||
function plotODG2withAnalysis(od_imgs, scan_parameter_values, g2_results, analysis_results, options, varargin)
|
||||
%% plotODG2withAnalysis
|
||||
% Author: Karthik
|
||||
% Date: 2025-09-30
|
||||
% Version: 1.0
|
||||
%
|
||||
% Description:
|
||||
% For each scan parameter value, produces one or more compact figures.
|
||||
% Each row contains:
|
||||
% - Col 1: OD image
|
||||
% - Col 2: g² correlation matrix
|
||||
% Overlays from analysis (boundary, ellipse, centroid, anisotropy) can be
|
||||
% optionally toggled on/off.
|
||||
%
|
||||
% Inputs:
|
||||
% od_imgs : cell array of OD images
|
||||
% scan_parameter_values: vector/array of scan parameters (one per image)
|
||||
% g2_results : struct with fields
|
||||
% - g2_matrices{k}
|
||||
% - dx_phys{k}, dy_phys{k}
|
||||
% analysis_results : struct with fields
|
||||
% - boundary_coords{k}
|
||||
% - ellipse_params{k}
|
||||
% - peak_centroid{k}
|
||||
% - anisotropy_vals(k)
|
||||
% - roi_params{k} (optional)
|
||||
% options : struct with imaging calibration
|
||||
% - pixel_size (in meters)
|
||||
% - magnification (unitless)
|
||||
% varargin : name-value pairs
|
||||
% - 'FontName', 'FontSize', 'SkipLivePlot', 'SkipSaveFigures',
|
||||
% 'SaveDirectory', 'ShowOverlays', 'RepsPerPage'
|
||||
%
|
||||
% Notes:
|
||||
% Requires conductCorrelationAnalysis and analyzeG2Structures outputs.
|
||||
|
||||
% --- Parse optional name-value pairs ---
|
||||
p = inputParser;
|
||||
addParameter(p, 'FontName', 'Arial', @ischar);
|
||||
addParameter(p, 'FontSize', 12, @isnumeric);
|
||||
addParameter(p, 'SkipLivePlot', false, @islogical);
|
||||
addParameter(p, 'SkipSaveFigures', true, @islogical);
|
||||
addParameter(p, 'SaveDirectory', pwd, @ischar);
|
||||
addParameter(p, 'ShowOverlays', true, @islogical);
|
||||
addParameter(p, 'RepsPerPage', 10, @isnumeric); % pagination
|
||||
parse(p, varargin{:});
|
||||
opts = p.Results;
|
||||
|
||||
% --- Setup save directory if needed ---
|
||||
if ~opts.SkipSaveFigures
|
||||
saveFolder = fullfile(opts.SaveDirectory, 'Results', 'SavedFigures', 'OD_G2_withAnalysis');
|
||||
if ~exist(saveFolder, 'dir')
|
||||
mkdir(saveFolder);
|
||||
end
|
||||
end
|
||||
|
||||
% --- Group by parameter value ---
|
||||
param_vals = scan_parameter_values(:);
|
||||
unique_params = unique(param_vals, 'rows');
|
||||
N_params = size(unique_params, 1);
|
||||
|
||||
for pIdx = 1:N_params
|
||||
matches = ismember(param_vals, unique_params(pIdx, :), 'rows');
|
||||
idx_list = find(matches);
|
||||
N_reps = numel(idx_list);
|
||||
|
||||
% --- Pagination over repetitions ---
|
||||
numPages = ceil(N_reps / opts.RepsPerPage);
|
||||
for pageIdx = 1:numPages
|
||||
repStart = (pageIdx-1)*opts.RepsPerPage + 1;
|
||||
repEnd = min(pageIdx*opts.RepsPerPage, N_reps);
|
||||
repSubset = idx_list(repStart:repEnd);
|
||||
N_rows = numel(repSubset);
|
||||
|
||||
% --- Create compact figure ---
|
||||
if ~opts.SkipLivePlot
|
||||
fig = figure('Color', 'w', 'Position', [100 100 400 800]);
|
||||
figure(fig); % ensure visible
|
||||
else
|
||||
fig = figure('Color', 'w', 'Position', [100 100 400 800], 'Visible','off'); % invisible for direct save
|
||||
end
|
||||
|
||||
t = tiledlayout(fig, N_rows, 2, 'TileSpacing', 'compact', 'Padding', 'compact');
|
||||
title(t, sprintf('Scan parameter: %s | Page %d/%d', ...
|
||||
mat2str(unique_params(pIdx, :)), pageIdx, numPages), ...
|
||||
'FontSize', opts.FontSize + 2, 'FontWeight', 'bold', 'FontName', opts.FontName);
|
||||
|
||||
for r = 1:N_rows
|
||||
k = repSubset(r);
|
||||
|
||||
% --- OD image ---
|
||||
nexttile;
|
||||
[M, N] = size(od_imgs{k});
|
||||
x_phys = ((1:N) - ceil(N/2)) * (options.pixel_size / options.magnification * 1e6); % µm
|
||||
y_phys = ((1:M) - ceil(M/2)) * (options.pixel_size / options.magnification * 1e6); % µm
|
||||
imagesc(x_phys, y_phys, od_imgs{k});
|
||||
axis image;
|
||||
set(gca, 'YDir', 'normal', 'FontName', opts.FontName, 'FontSize', 14);
|
||||
colormap(gca, Colormaps.inferno());
|
||||
xlabel('x (\mum)');
|
||||
ylabel('y (\mum)');
|
||||
|
||||
% --- g² correlation matrix ---
|
||||
nexttile;
|
||||
dx = g2_results.dx_phys{k};
|
||||
dy = g2_results.dy_phys{k};
|
||||
g2_matrix = g2_results.g2_matrices{k};
|
||||
imagesc(dx, dy, g2_matrix);
|
||||
axis image;
|
||||
set(gca, 'YDir', 'normal', 'FontName', opts.FontName, 'FontSize', 14);
|
||||
colormap(gca, Colormaps.coolwarm()); colorbar;
|
||||
xlabel('\Deltax (\mum)'); ylabel('\Deltay (\mum)');
|
||||
hold on;
|
||||
|
||||
% --- Optional overlays ---
|
||||
if opts.ShowOverlays
|
||||
boundary = analysis_results.boundary_coords{k};
|
||||
ellipse = analysis_results.ellipse_params{k};
|
||||
centroid = analysis_results.peak_centroid{k};
|
||||
anisotropy = analysis_results.anisotropy_vals(k);
|
||||
theta = analysis_results.ellipse_params{k}(5);
|
||||
|
||||
% ROI rectangle (rotated)
|
||||
if isfield(analysis_results, 'roi_params')
|
||||
roi = analysis_results.roi_params{k};
|
||||
if ~isempty(roi) && all(~isnan(roi))
|
||||
x0 = roi(1); y0 = roi(2);
|
||||
w = roi(3); h = roi(4);
|
||||
roi_theta = roi(5);
|
||||
corners = [ -w/2, -h/2; w/2, -h/2; w/2, h/2; -w/2, h/2];
|
||||
R = [cos(roi_theta), -sin(roi_theta); sin(roi_theta), cos(roi_theta)];
|
||||
corners_rot = (R*corners')' + [x0, y0];
|
||||
corners_rot = [corners_rot; corners_rot(1,:)];
|
||||
plot(corners_rot(:,1), corners_rot(:,2), 'r--', 'LineWidth',1.5);
|
||||
end
|
||||
end
|
||||
|
||||
if all(~isnan(boundary(:)))
|
||||
plot(boundary(:, 1), boundary(:, 2), 'g-', 'LineWidth', 2);
|
||||
end
|
||||
if all(~isnan(centroid))
|
||||
plot(centroid(1), centroid(2), 'mo', 'MarkerSize', 8, 'LineWidth', 2);
|
||||
x_c = centroid(1); y_c = centroid(2);
|
||||
else
|
||||
x_c = NaN; y_c = NaN;
|
||||
end
|
||||
if all(~isnan(ellipse)) && ~isnan(anisotropy) && anisotropy ~= 0
|
||||
a = ellipse(3);
|
||||
b = ellipse(4);
|
||||
tEllipse = linspace(0, 2*pi, 200);
|
||||
ellipse_x = x_c + a*cos(tEllipse)*cos(theta) - b*sin(tEllipse)*sin(theta);
|
||||
ellipse_y = y_c + a*cos(tEllipse)*sin(theta) + b*sin(tEllipse)*cos(theta);
|
||||
|
||||
if anisotropy > 1.0
|
||||
plot(ellipse_x, ellipse_y, 'y--', 'LineWidth', 2);
|
||||
else
|
||||
plot(ellipse_x, ellipse_y, 'y-', 'LineWidth', 2);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ~opts.SkipLivePlot
|
||||
drawnow; % render figure
|
||||
end
|
||||
|
||||
% --- Save figure ---
|
||||
if ~opts.SkipSaveFigures
|
||||
saveFileName = sprintf('OD_G2_analysis_param_%03d_page_%02d.png', pIdx, pageIdx);
|
||||
Plotter.saveFigure(fig, ...
|
||||
'SaveFileName', saveFileName, ...
|
||||
'SaveDirectory', saveFolder, ...
|
||||
'SkipSaveFigures', opts.SkipSaveFigures);
|
||||
end
|
||||
|
||||
% --- Close invisible figure to free memory ---
|
||||
if opts.SkipLivePlot
|
||||
close(fig);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
122
Data-Analyzer/+Plotter/plotSpectralCurves.m
Normal file
122
Data-Analyzer/+Plotter/plotSpectralCurves.m
Normal file
@ -0,0 +1,122 @@
|
||||
function results = plotSpectralCurves(S_theta_all, theta_vals, scan_reference_values, varargin)
|
||||
%% plotSpectralCurves
|
||||
% Author: Karthik
|
||||
% Date: 2025-10-01
|
||||
% Version: 1.0
|
||||
%
|
||||
% Description:
|
||||
% Plot raw spectral curves with mean, SEM, and highlights
|
||||
%
|
||||
% Inputs:
|
||||
% S_theta_all - 1x(N_reps*N_params) cell array of curves
|
||||
% scan_reference_values - vector of unique scan parameters
|
||||
%
|
||||
% Optional name-value pairs:
|
||||
% 'Title' - Figure title (default: 'Spectral Curves')
|
||||
% 'XLabel' - Label for x-axis (default: '\theta / \pi')
|
||||
% 'YLabel' - Label for y-axis (default: 'S(\theta)')
|
||||
% 'FontName' - Font name (default: 'Arial')
|
||||
% 'FontSize' - Font size (default: 14)
|
||||
% 'FigNum' - Figure number (default: [])
|
||||
% 'SkipSaveFigures' - Logical flag to skip saving (default: false)
|
||||
% 'SaveFileName' - Name of figure file (default: 'SpectralCurves.fig')
|
||||
% 'SaveDirectory' - Directory to save figure (default: pwd)
|
||||
% 'TileTitlePrefix' - Prefix for tile titles (default: 'Scan Parameter')
|
||||
% 'TileTitleSuffix' - Suffix for tile titles (default: '')
|
||||
% 'HighlightEvery' - Highlight every Nth repetition (default: 10)
|
||||
%
|
||||
% Notes:
|
||||
% Automatically computes number of repetitions from S_theta_all length and scan_reference_values.
|
||||
|
||||
% --- Parse name-value pairs ---
|
||||
p = inputParser;
|
||||
addParameter(p, 'Title', 'Spectral Curves', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'XLabel', '\theta / \pi', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'YLabel', 'S(\theta)', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'FontName', 'Arial', @ischar);
|
||||
addParameter(p, 'FontSize', 14, @isnumeric);
|
||||
addParameter(p, 'FigNum', [], @(x) isempty(x) || (isnumeric(x) && isscalar(x)));
|
||||
addParameter(p, 'SkipSaveFigures', false, @islogical);
|
||||
addParameter(p, 'SaveFileName', 'SpectralCurves.fig', @ischar);
|
||||
addParameter(p, 'SaveDirectory', pwd, @ischar);
|
||||
addParameter(p, 'TileTitlePrefix', 'Scan Parameter', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'TileTitleSuffix', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'HighlightEvery', 10, @(x) isnumeric(x) && isscalar(x) && x>=1);
|
||||
parse(p, varargin{:});
|
||||
opts = p.Results;
|
||||
|
||||
% --- Determine number of scan parameters and repetitions ---
|
||||
N_params = numel(scan_reference_values);
|
||||
Ntotal = numel(S_theta_all);
|
||||
Nreps = Ntotal / N_params;
|
||||
|
||||
% --- Prepare curves, mean, SEM ---
|
||||
curves_all = cell(1, N_params);
|
||||
curves_mean = cell(1, N_params);
|
||||
curves_err = cell(1, N_params);
|
||||
Npoints = numel(S_theta_all{1});
|
||||
|
||||
for i = 1:N_params
|
||||
curves = zeros(Nreps, Npoints);
|
||||
for r = 1:Nreps
|
||||
idx = (r-1)*N_params + i; % correct interleaved indexing
|
||||
curves(r,:) = S_theta_all{idx};
|
||||
end
|
||||
curves_all{i} = curves;
|
||||
curves_mean{i} = mean(curves,1);
|
||||
curves_err{i} = std(curves,0,1)/sqrt(Nreps);
|
||||
end
|
||||
|
||||
% --- Create results struct compatible with plotting ---
|
||||
results.curves = curves_all;
|
||||
results.x_values = theta_vals; % generic x-axis
|
||||
results.curves_mean = curves_mean;
|
||||
results.curves_error = curves_err;
|
||||
results.scan_parameter_values = scan_reference_values;
|
||||
|
||||
% --- Create figure ---
|
||||
if isempty(opts.FigNum), fig = figure; else, fig = figure(opts.FigNum); end
|
||||
clf(fig);
|
||||
set(fig,'Color','w','Position',[100 100 950 750]);
|
||||
t = tiledlayout('TileSpacing','compact','Padding','compact');
|
||||
title(t, opts.Title, 'FontName', opts.FontName, 'FontSize', opts.FontSize+2);
|
||||
|
||||
% --- Loop over scan parameters ---
|
||||
for i = 1:N_params
|
||||
ax = nexttile; hold(ax,'on');
|
||||
|
||||
G = results.curves{i}; % [N_reps × Npoints]
|
||||
|
||||
% --- Plot all repetitions in light grey ---
|
||||
plot(ax, results.x_values, G', 'Color', [0.7 0.7 0.7, 0.5]);
|
||||
|
||||
% --- Highlight every Nth repetition ---
|
||||
idx = opts.HighlightEvery:opts.HighlightEvery:size(G,1);
|
||||
for j = idx
|
||||
plot(ax, results.x_values, G(j,:), 'Color', [0.3 0.3 0.3, 1], 'LineWidth', 1.5);
|
||||
end
|
||||
|
||||
% --- Mean + SEM shading ---
|
||||
mu = results.curves_mean{i};
|
||||
se = results.curves_error{i};
|
||||
fill(ax, [results.x_values fliplr(results.x_values)], [mu-se fliplr(mu+se)], ...
|
||||
[0.2 0.4 0.8], 'FaceAlpha',0.2, 'EdgeColor','none');
|
||||
|
||||
% --- Mean curve ---
|
||||
plot(ax, results.x_values, mu, 'b-', 'LineWidth', 2);
|
||||
|
||||
% --- Axes formatting ---
|
||||
grid(ax,'on');
|
||||
xlabel(ax, opts.XLabel, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
ylabel(ax, opts.YLabel, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
title(ax, sprintf('%s=%.3g%s', opts.TileTitlePrefix, results.scan_parameter_values(i), opts.TileTitleSuffix), ...
|
||||
'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
set(ax, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
end
|
||||
|
||||
% --- Save figure ---
|
||||
if ~opts.SkipSaveFigures
|
||||
if ~exist(opts.SaveDirectory,'dir'), mkdir(opts.SaveDirectory); end
|
||||
savefig(fig, fullfile(opts.SaveDirectory, opts.SaveFileName));
|
||||
end
|
||||
end
|
||||
162
Data-Analyzer/+Plotter/plotSpectralCurvesRecentered.m
Normal file
162
Data-Analyzer/+Plotter/plotSpectralCurvesRecentered.m
Normal file
@ -0,0 +1,162 @@
|
||||
function results = plotSpectralCurvesRecentered(S_theta_all, theta_vals, scan_reference_values, varargin)
|
||||
%% plotSpectralCurves
|
||||
% Author: Karthik
|
||||
% Date: 2025-10-01
|
||||
% Version: 1.0
|
||||
%
|
||||
% Description:
|
||||
% Plot raw spectral curves with mean, SEM, and highlights
|
||||
% Preprocess: shifts each curve so its first peak is at zero (no wrap-around)
|
||||
%
|
||||
% Inputs:
|
||||
% S_theta_all - 1x(N_reps*N_params) cell array of curves
|
||||
% theta_vals - vector of theta values (radians)
|
||||
% scan_reference_values - vector of unique scan parameters
|
||||
%
|
||||
% Optional name-value pairs:
|
||||
% 'Title' - Figure title (default: 'Spectral Curves')
|
||||
% 'XLabel' - Label for x-axis (default: '\theta / \pi')
|
||||
% 'YLabel' - Label for y-axis (default: 'S(\theta)')
|
||||
% 'FontName' - Font name (default: 'Arial')
|
||||
% 'FontSize' - Font size (default: 14)
|
||||
% 'FigNum' - Figure number (default: [])
|
||||
% 'SkipSaveFigures' - Logical flag to skip saving (default: false)
|
||||
% 'SaveFileName' - Name of figure file (default: 'SpectralCurves.fig')
|
||||
% 'SaveDirectory' - Directory to save figure (default: pwd)
|
||||
% 'TileTitlePrefix' - Prefix for tile titles (default: 'Scan Parameter')
|
||||
% 'TileTitleSuffix' - Suffix for tile titles (default: '')
|
||||
% 'HighlightEvery' - Highlight every Nth repetition (default: 10)
|
||||
%
|
||||
% Notes:
|
||||
% Automatically computes number of repetitions from S_theta_all length and scan_reference_values.
|
||||
|
||||
% --- Parse name-value pairs ---
|
||||
p = inputParser;
|
||||
addParameter(p, 'Title', 'Spectral Curves', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'XLabel', '\theta / \pi', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'YLabel', 'S(\theta)', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'FontName', 'Arial', @ischar);
|
||||
addParameter(p, 'FontSize', 14, @isnumeric);
|
||||
addParameter(p, 'FigNum', [], @(x) isempty(x) || (isnumeric(x) && isscalar(x)));
|
||||
addParameter(p, 'SkipSaveFigures', false, @islogical);
|
||||
addParameter(p, 'SaveFileName', 'SpectralCurves.fig', @ischar);
|
||||
addParameter(p, 'SaveDirectory', pwd, @ischar);
|
||||
addParameter(p, 'TileTitlePrefix', 'Scan Parameter', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'TileTitleSuffix', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'HighlightEvery', 10, @(x) isnumeric(x) && isscalar(x) && x>=1);
|
||||
parse(p, varargin{:});
|
||||
opts = p.Results;
|
||||
|
||||
% --- Determine number of scan parameters and repetitions ---
|
||||
N_params = numel(scan_reference_values);
|
||||
Ntotal = numel(S_theta_all);
|
||||
Nreps = Ntotal / N_params;
|
||||
|
||||
theta_min = deg2rad(0);
|
||||
theta_max = deg2rad(90);
|
||||
|
||||
% --- Preprocess curves: shift first peak to zero without wrapping ---
|
||||
S_theta_all_shifted = cell(size(S_theta_all));
|
||||
for k = 1:Ntotal
|
||||
curve = S_theta_all{k};
|
||||
|
||||
% --- Find first peak only in specified theta range ---
|
||||
|
||||
idx_range = find(theta_vals >= theta_min & theta_vals <= theta_max);
|
||||
if isempty(idx_range)
|
||||
% fallback: search entire curve if range is empty
|
||||
[~, peak_idx] = max(curve);
|
||||
else
|
||||
[~, local_max_idx] = max(curve(idx_range));
|
||||
peak_idx = idx_range(local_max_idx);
|
||||
end
|
||||
|
||||
% Take only the part from peak to end
|
||||
S_theta_all_shifted{k} = curve(peak_idx:end);
|
||||
end
|
||||
|
||||
% --- Adjust Npoints for plotting ---
|
||||
Npoints_shifted = max(cellfun(@numel, S_theta_all_shifted));
|
||||
for k = 1:Ntotal
|
||||
% Pad shorter curves with NaN to keep sizes consistent
|
||||
len = numel(S_theta_all_shifted{k});
|
||||
if len < Npoints_shifted
|
||||
S_theta_all_shifted{k} = [S_theta_all_shifted{k}, nan(1, Npoints_shifted-len)];
|
||||
end
|
||||
end
|
||||
|
||||
% --- Prepare curves, mean, SEM ---
|
||||
curves_all = cell(1, N_params);
|
||||
curves_mean = cell(1, N_params);
|
||||
curves_err = cell(1, N_params);
|
||||
|
||||
for i = 1:N_params
|
||||
curves = zeros(Nreps, Npoints_shifted);
|
||||
for r = 1:Nreps
|
||||
idx = (r-1)*N_params + i; % correct interleaved indexing
|
||||
curves(r,:) = S_theta_all_shifted{idx};
|
||||
end
|
||||
curves_all{i} = curves;
|
||||
curves_mean{i} = nanmean(curves,1);
|
||||
curves_err{i} = nanstd(curves,0,1)/sqrt(Nreps);
|
||||
end
|
||||
|
||||
% --- Create results struct compatible with plotting ---
|
||||
results.curves = curves_all;
|
||||
results.x_values = theta_vals(1:Npoints_shifted) - theta_vals(1); % center first peak at zero
|
||||
results.curves_mean = curves_mean;
|
||||
results.curves_error = curves_err;
|
||||
results.scan_parameter_values = scan_reference_values;
|
||||
|
||||
% --- Create figure ---
|
||||
if isempty(opts.FigNum), fig = figure; else, fig = figure(opts.FigNum); end
|
||||
clf(fig);
|
||||
set(fig,'Color','w','Position',[100 100 950 750]);
|
||||
t = tiledlayout('TileSpacing','compact','Padding','compact');
|
||||
title(t, opts.Title, 'FontName', opts.FontName, 'FontSize', opts.FontSize+2);
|
||||
|
||||
% --- Loop over scan parameters ---
|
||||
for i = 1:N_params
|
||||
ax = nexttile; hold(ax,'on');
|
||||
|
||||
G = results.curves{i}; % [N_reps × Npoints]
|
||||
|
||||
% --- Plot all repetitions in light grey ---
|
||||
plot(ax, results.x_values, G', 'Color', [0.7 0.7 0.7, 0.5]);
|
||||
|
||||
% --- Highlight every Nth repetition ---
|
||||
idx = opts.HighlightEvery:opts.HighlightEvery:size(G,1);
|
||||
for j = idx
|
||||
plot(ax, results.x_values, G(j,:), 'Color', [0.3 0.3 0.3, 1], 'LineWidth', 1.5);
|
||||
end
|
||||
|
||||
% --- Mean + SEM shading ---
|
||||
mu = results.curves_mean{i};
|
||||
se = results.curves_error{i};
|
||||
fill(ax, [results.x_values fliplr(results.x_values)], [mu-se fliplr(mu+se)], ...
|
||||
[0.2 0.4 0.8], 'FaceAlpha',0.2, 'EdgeColor','none');
|
||||
|
||||
% --- Mean curve ---
|
||||
plot(ax, results.x_values, mu, 'b-', 'LineWidth', 2);
|
||||
|
||||
% --- Vertical reference lines at pi/3 and 2pi/3 ---
|
||||
xlines = [1/3 2/3];
|
||||
for xl = xlines
|
||||
xline(ax, xl, 'k--', 'LineWidth', 1.5, 'Alpha', 0.5);
|
||||
end
|
||||
|
||||
% --- Axes formatting ---
|
||||
grid(ax,'on');
|
||||
xlabel(ax, opts.XLabel, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
ylabel(ax, opts.YLabel, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
title(ax, sprintf('%s=%.3g%s', opts.TileTitlePrefix, results.scan_parameter_values(i), opts.TileTitleSuffix), ...
|
||||
'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
set(ax, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
end
|
||||
|
||||
% --- Save figure ---
|
||||
if ~opts.SkipSaveFigures
|
||||
if ~exist(opts.SaveDirectory,'dir'), mkdir(opts.SaveDirectory); end
|
||||
savefig(fig, fullfile(opts.SaveDirectory, opts.SaveFileName));
|
||||
end
|
||||
end
|
||||
90
Data-Analyzer/+Plotter/plotSpectralDistributionCumulants.m
Normal file
90
Data-Analyzer/+Plotter/plotSpectralDistributionCumulants.m
Normal file
@ -0,0 +1,90 @@
|
||||
function plotSpectralDistributionCumulants(results, varargin)
|
||||
%% plotSpectralCumulants
|
||||
% Author: Karthik
|
||||
% Date: 2025-10-02
|
||||
% Version: 1.0
|
||||
%
|
||||
% Description:
|
||||
% Plot first four cumulants of recentered spectral curves vs scan parameter.
|
||||
%
|
||||
% Inputs:
|
||||
% results_struct - struct returned by plotSpectralCurvesRecentered
|
||||
% (fields: curves, x_values, scan_parameter_values)
|
||||
%
|
||||
% Notes:
|
||||
% Computes cumulants across repetitions at selected theta values
|
||||
% and plots them in 4 tiled subplots.
|
||||
|
||||
% --- Parse name-value pairs ---
|
||||
p = inputParser;
|
||||
addParameter(p, 'Title', 'Spectral Cumulants', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'XLabel', 'Scan Parameter', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'FontName', 'Arial', @ischar);
|
||||
addParameter(p, 'FontSize', 14, @isnumeric);
|
||||
addParameter(p, 'FigNum', [], @(x) isempty(x) || (isnumeric(x) && isscalar(x)));
|
||||
addParameter(p, 'SkipSaveFigures', false, @islogical);
|
||||
addParameter(p, 'SaveFileName', 'SpectralCumulants.fig', @ischar);
|
||||
addParameter(p, 'SaveDirectory', pwd, @ischar);
|
||||
addParameter(p, 'MaxOrder', 4, @isnumeric);
|
||||
parse(p, varargin{:});
|
||||
opts = p.Results;
|
||||
|
||||
% --- Setup ---
|
||||
N_params = numel(results.curves);
|
||||
xvals = results.scan_parameter_values;
|
||||
thetaVals = results.x_values * pi;
|
||||
|
||||
% --- Select specific theta indices (like in g2 case) ---
|
||||
desiredTheta = [pi/12 pi/6 pi/3];
|
||||
[~, thIdx] = arrayfun(@(t) min(abs(thetaVals - t)), desiredTheta);
|
||||
thetaLabels = {'\pi/12','\pi/6','\pi/3'};
|
||||
|
||||
% --- Compute cumulants: [theta × scan × cumulantOrder] ---
|
||||
kappa = zeros(numel(thIdx), N_params, opts.MaxOrder);
|
||||
for i = 1:N_params
|
||||
for t = 1:numel(thIdx)
|
||||
reps_values = results.curves{i}(:, thIdx(t));
|
||||
kappa(t,i,:) = Calculator.computeCumulants(reps_values, opts.MaxOrder);
|
||||
end
|
||||
end
|
||||
|
||||
% --- Colormap ---
|
||||
fullCmap = Colormaps.coolwarm(256);
|
||||
Ntheta = numel(thIdx);
|
||||
blueSide = fullCmap(1:100,:); % avoid white
|
||||
redSide = fullCmap(157:end,:);
|
||||
trimmedCmap = [blueSide; redSide];
|
||||
indices = round(linspace(1,size(trimmedCmap,1),Ntheta));
|
||||
cmap = trimmedCmap(indices,:);
|
||||
|
||||
% --- Create figure ---
|
||||
if isempty(opts.FigNum), fig = figure; else, fig = figure(opts.FigNum); end
|
||||
clf(fig);
|
||||
set(fig,'Color','w','Position',[100 100 950 750]);
|
||||
t = tiledlayout(2,2,'TileSpacing','Compact','Padding','Compact');
|
||||
title(t, opts.Title, 'FontName', opts.FontName, 'FontSize', opts.FontSize+4);
|
||||
|
||||
cumulLabels = {'\kappa_1','\kappa_2','\kappa_3','\kappa_4'};
|
||||
cumulTitles = {'Mean','Variance','Skewness','Binder Cumulant'};
|
||||
|
||||
for k = 1:opts.MaxOrder
|
||||
ax = nexttile; hold(ax,'on');
|
||||
for idx = 1:numel(thIdx)
|
||||
plot(ax, xvals, squeeze(kappa(idx,:,k)), '-o', ...
|
||||
'Color', cmap(idx,:), 'LineWidth', 2, 'MarkerSize', 8, 'MarkerFaceColor', cmap(idx,:));
|
||||
end
|
||||
ylabel(ax, cumulLabels{k}, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
xlabel(ax, opts.XLabel, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
title(ax, cumulTitles{k}, 'FontName', opts.FontName, 'FontSize', opts.FontSize+2);
|
||||
grid(ax,'on'); set(ax,'FontName',opts.FontName,'FontSize',opts.FontSize);
|
||||
|
||||
% --- Legend ---
|
||||
legend(ax, thetaLabels, 'Location', 'best', 'FontSize', opts.FontSize-2);
|
||||
end
|
||||
|
||||
% --- Save figure ---
|
||||
if ~opts.SkipSaveFigures
|
||||
if ~exist(opts.SaveDirectory,'dir'), mkdir(opts.SaveDirectory); end
|
||||
savefig(fig, fullfile(opts.SaveDirectory, opts.SaveFileName));
|
||||
end
|
||||
end
|
||||
@ -46,12 +46,19 @@ function saveFigure(fig, varargin)
|
||||
[~, name, ext] = fileparts(opts.SaveFileName);
|
||||
if isempty(ext)
|
||||
ext = '.fig';
|
||||
elseif ~strcmpi(ext, '.fig')
|
||||
warning('Overriding extension to .fig (was %s).', ext);
|
||||
ext = '.fig';
|
||||
end
|
||||
|
||||
|
||||
saveFullPath = fullfile(opts.SaveDirectory, [name ext]);
|
||||
savefig(fig, saveFullPath);
|
||||
fprintf('Figure saved as MATLAB .fig: %s\n', saveFullPath);
|
||||
|
||||
switch lower(ext)
|
||||
case '.fig'
|
||||
savefig(fig, saveFullPath);
|
||||
fprintf('Figure saved as MATLAB .fig: %s\n', saveFullPath);
|
||||
case '.png'
|
||||
saveas(fig, saveFullPath);
|
||||
fprintf('Figure saved as PNG: %s\n', saveFullPath);
|
||||
otherwise
|
||||
warning('Unsupported extension, saving as .fig');
|
||||
savefig(fig, fullfile(opts.SaveDirectory, [name '.fig']));
|
||||
end
|
||||
end
|
||||
|
||||
@ -187,13 +187,251 @@ Plotter.plotMultiplePCAResults(compiled_results.pca_results, scan_parameter_valu
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveDirectory', figSaveDir);
|
||||
|
||||
%% ------------------ 7. Plot of all Angular Spectral Distribution Curves ------------------
|
||||
Plotter.plotSpectralCurves( ...
|
||||
results_all{1}.results.spectral_analysis_results.S_theta_norm_all, ...
|
||||
results_all{1}.results.spectral_analysis_results.theta_vals/pi, ... % correct θ values
|
||||
results_all{1}.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', 20, ...
|
||||
'TileTitlePrefix', '\alpha', ... % user-defined tile prefix
|
||||
'TileTitleSuffix', '^\circ', ... % user-defined suffix (e.g., degrees symbol)
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SpectralCurves.fig', ...
|
||||
'SaveDirectory', figSaveDir);
|
||||
|
||||
%% ------------------ 8. Plot of all Angular Spectral Distribution Curves shifted ------------------
|
||||
results = Plotter.plotSpectralCurvesRecentered( ...
|
||||
results_all{1}.results.spectral_analysis_results.S_theta_norm_all, ...
|
||||
results_all{1}.results.spectral_analysis_results.theta_vals/pi, ... % correct θ values
|
||||
results_all{1}.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', 21, ...
|
||||
'TileTitlePrefix', '\alpha', ... % user-defined tile prefix
|
||||
'TileTitleSuffix', '^\circ', ... % user-defined suffix (e.g., degrees symbol)
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SpectralCurves.fig', ...
|
||||
'SaveDirectory', figSaveDir);
|
||||
%% ------------------ 9. Plot cumulants from shifted Angular Spectral Distribution Curves ------------------
|
||||
Plotter.plotSpectralDistributionCumulants(results, ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 14, ...
|
||||
'FigNum', 22, ...
|
||||
'SkipSaveFigures', false, ...
|
||||
'SaveFileName', 'SpectralCumulants.fig');
|
||||
|
||||
%% ------------------ 10. Fit shifted Angular Spectral Distribution Curves ------------------
|
||||
fitResults = Analyzer.fitTwoGaussianCurves(...
|
||||
results_all{1}.results.spectral_analysis_results.S_theta_norm_all, ...
|
||||
results_all{1}.results.spectral_analysis_results.theta_vals, ...
|
||||
'MaxTheta', pi/2, ...
|
||||
'DeviationLimit', 1.00);
|
||||
|
||||
%% ------------------ 11. Plot fit parameters - position ------------------
|
||||
Plotter.plotFitParameterPDF(fitResults, results_all{1}.scan_reference_values, 'mu2', ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Secondary peak position (\theta, rad)', ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 16, ...
|
||||
'FigNum', 23, ...
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SecondaryPeakPositionPDF.fig', ...
|
||||
'PlotType', 'histogram', ...
|
||||
'NumberOfBins', 20, ...
|
||||
'NormalizeHistogram', true, ...
|
||||
'Colormap', @Colormaps.coolwarm, ...
|
||||
'XLim', [min(results_all{1}.scan_reference_values), max(results_all{1}.scan_reference_values)]);
|
||||
|
||||
%% ------------------ 12. Plot fit parameters - width ------------------
|
||||
Plotter.plotFitParameterPDF(fitResults, results_all{1}.scan_reference_values, 'sigma2', ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Secondary peak width (\sigma, rad)', ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 16, ...
|
||||
'FigNum', 24, ...
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SecondaryPeakWidthPDF.fig', ...
|
||||
'PlotType', 'histogram', ...
|
||||
'NumberOfBins', 20, ...
|
||||
'NormalizeHistogram', true, ...
|
||||
'Colormap', @Colormaps.coolwarm, ...
|
||||
'XLim', [min(results_all{1}.scan_reference_values), max(results_all{1}.scan_reference_values)]);
|
||||
|
||||
%% ------------------ 13. Plot fit parameters of mean shifted Angular Spectral Distribution Curves ------------------
|
||||
S_theta_all = results_all{1}.results.spectral_analysis_results.S_theta_norm_all;
|
||||
theta_vals = results_all{1}.results.spectral_analysis_results.theta_vals;
|
||||
scanValues = results_all{1}.scan_reference_values;
|
||||
|
||||
N_params = numel(scanValues);
|
||||
N_total = numel(S_theta_all);
|
||||
N_reps = N_total / N_params;
|
||||
|
||||
theta_min = deg2rad(0);
|
||||
theta_max = deg2rad(90);
|
||||
|
||||
% --- Shift curves so first peak is at start ---
|
||||
S_theta_all_shifted = cell(size(S_theta_all));
|
||||
for k = 1:N_total
|
||||
curve = S_theta_all{k};
|
||||
idx_range = find(theta_vals >= theta_min & theta_vals <= theta_max);
|
||||
if isempty(idx_range)
|
||||
[~, peak_idx] = max(curve);
|
||||
else
|
||||
[~, local_max_idx] = max(curve(idx_range));
|
||||
peak_idx = idx_range(local_max_idx);
|
||||
end
|
||||
S_theta_all_shifted{k} = curve(peak_idx:end);
|
||||
end
|
||||
|
||||
% --- Pad shorter curves with NaN to match lengths ---
|
||||
Npoints_shifted = max(cellfun(@numel, S_theta_all_shifted));
|
||||
for k = 1:N_total
|
||||
len = numel(S_theta_all_shifted{k});
|
||||
if len < Npoints_shifted
|
||||
S_theta_all_shifted{k} = [S_theta_all_shifted{k}, nan(1, Npoints_shifted-len)];
|
||||
end
|
||||
end
|
||||
|
||||
% --- Compute mean curves per scan parameter ---
|
||||
meanCurves = cell(1, N_params);
|
||||
for i = 1:N_params
|
||||
curves = zeros(N_reps, Npoints_shifted);
|
||||
for r = 1:N_reps
|
||||
idx = (r-1)*N_params + i; % interleaved indexing
|
||||
curves(r,:) = S_theta_all_shifted{idx};
|
||||
end
|
||||
meanCurves{i} = nanmean(curves,1); % mean over repetitions
|
||||
end
|
||||
|
||||
% --- Fit two-Gaussian model to mean curves ---
|
||||
fitResultsMean = Analyzer.fitTwoGaussianCurves(meanCurves, theta_vals(1:Npoints_shifted)-theta_vals(1), ...
|
||||
'MaxTheta', pi/2, ...
|
||||
'DeviationLimit', 1.0);
|
||||
|
||||
% --- Scatter plot of secondary peak position (mu2) vs scan parameter ---
|
||||
mu2_vals = nan(1, N_params);
|
||||
sigma2_vals = nan(1, N_params);
|
||||
|
||||
for i = 1:N_params
|
||||
if fitResultsMean(i).isValid
|
||||
mu2_vals(i) = fitResultsMean(i).pFit(4); % secondary peak position
|
||||
sigma2_vals(i) = fitResultsMean(i).pFit(5); % secondary peak width
|
||||
end
|
||||
end
|
||||
|
||||
% Secondary peak position
|
||||
plotSecondaryPeakScatter(fitResultsMean, results_all{1}.scan_reference_values, 'mu2', ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Secondary peak position (\theta, rad)', ...
|
||||
'FigNum', 23, ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 16, ...
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SecondaryPeakPositionScatter.fig');
|
||||
|
||||
% Secondary peak width
|
||||
plotSecondaryPeakScatter(fitResultsMean, results_all{1}.scan_reference_values, 'sigma2', ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Secondary peak width (\sigma, rad)', ...
|
||||
'FigNum', 24, ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 16, ...
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SecondaryPeakWidthScatter.fig');
|
||||
|
||||
|
||||
function plotSecondaryPeakScatter(fitResultsMean, scanValues, parameterName, varargin)
|
||||
%% plotSecondaryPeakScatter
|
||||
% Author: Karthik
|
||||
% Date: 2025-10-06
|
||||
% Version: 1.0
|
||||
%
|
||||
% Description:
|
||||
% Scatter plot of secondary peak fit parameters (mu2 or sigma2) vs scan parameter
|
||||
% in the same style as plotMeanWithSE.
|
||||
|
||||
% --- Parse optional inputs ---
|
||||
p = inputParser;
|
||||
addParameter(p, 'Title', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'XLabel', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'YLabel', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'FigNum', [], @(x) isempty(x) || isscalar(x));
|
||||
addParameter(p, 'FontName', 'Arial', @ischar);
|
||||
addParameter(p, 'FontSize', 14, @isnumeric);
|
||||
addParameter(p, 'YLim', [], @(x) isempty(x) || isnumeric(x));
|
||||
addParameter(p, 'SkipSaveFigures', false, @islogical);
|
||||
addParameter(p, 'SaveFileName', 'secondary_peak_scatter.fig', @ischar);
|
||||
addParameter(p, 'SaveDirectory', pwd, @ischar);
|
||||
parse(p, varargin{:});
|
||||
opts = p.Results;
|
||||
|
||||
% --- Extract parameter values ---
|
||||
N_params = numel(fitResultsMean);
|
||||
paramVals = nan(1, N_params);
|
||||
for i = 1:N_params
|
||||
if fitResultsMean(i).isValid
|
||||
switch parameterName
|
||||
case 'mu2'
|
||||
paramVals(i) = fitResultsMean(i).pFit(4);
|
||||
case 'sigma2'
|
||||
paramVals(i) = fitResultsMean(i).pFit(5);
|
||||
otherwise
|
||||
error('Unknown parameter name: %s', parameterName);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
% --- Prepare figure ---
|
||||
if isempty(opts.FigNum)
|
||||
fig = figure;
|
||||
else
|
||||
fig = figure(opts.FigNum);
|
||||
clf(fig);
|
||||
end
|
||||
set(fig, 'Color', 'w', 'Position', [100 100 950 750]);
|
||||
|
||||
% --- Plot as mean ± SE style (SE=0 here) ---
|
||||
errorbar(scanValues, paramVals, zeros(size(paramVals)), 'o--', ...
|
||||
'LineWidth', 1.8, 'MarkerSize', 6, 'CapSize', 5);
|
||||
|
||||
% --- Axis formatting ---
|
||||
set(gca, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
if ~isempty(opts.YLim)
|
||||
ylim(opts.YLim);
|
||||
end
|
||||
xlabel(opts.XLabel, 'Interpreter', 'tex', 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
ylabel(opts.YLabel, 'Interpreter', 'tex', 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
title(opts.Title, 'FontName', opts.FontName, 'FontSize', opts.FontSize + 2, 'FontWeight', 'bold');
|
||||
grid on;
|
||||
|
||||
% --- Save figure ---
|
||||
if ~opts.SkipSaveFigures
|
||||
if ~exist(opts.SaveDirectory, 'dir'), mkdir(opts.SaveDirectory); end
|
||||
savefig(fig, fullfile(opts.SaveDirectory, opts.SaveFileName));
|
||||
end
|
||||
end
|
||||
|
||||
%{
|
||||
%% ------------------ 7. Average of Spectra Plots ------------------
|
||||
%% ------------------ 14. Average of Spectra Plots ------------------
|
||||
|
||||
Plotter.plotAverageSpectra(scan_parameter_values, ...
|
||||
spectral_analysis_results, ...
|
||||
'ScanParameterName', scan_parameter, ...
|
||||
'FigNum', 20, ...
|
||||
'FigNum', 25, ...
|
||||
'ColormapPS', Colormaps.coolwarm(), ...
|
||||
'Font', 'Bahnschrift', ...
|
||||
'SaveFileName', 'avgSpectra.fig', ...
|
||||
|
||||
@ -98,7 +98,7 @@ end
|
||||
[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
|
||||
@ -108,23 +108,32 @@ 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.deviationThreshold = 0.30;
|
||||
options.minEllipseFraction = 0.30;
|
||||
options.angleLimit = deg2rad(45);
|
||||
options.angleTolerance = deg2rad(5);
|
||||
|
||||
options.fitDeviationThreshold = 0.9;
|
||||
|
||||
% Plot control
|
||||
options.skipLivePlot = false; % set true if you don't want per-image figures
|
||||
|
||||
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 = false;
|
||||
saveDirectory = 'C:\Users\Karthik-OfficePC\Documents\GitRepositories\Calculations\Data-Analyzer\+Scripts';
|
||||
|
||||
Plotter.plotODG2withAnalysis(od_imgs, scan_parameter_values, g2_analysis_results, analysis_results, options, ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 14, ...
|
||||
'ShowOverlays', true, ...
|
||||
'RepsPerPage', 5, ... % paginate 10 repetitions per figure
|
||||
'SaveDirectory', 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,1], ...
|
||||
'YLim', [2,5], ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Anisotropy of correlation peaks', ...
|
||||
'FigNum', 1, ...
|
||||
|
||||
@ -188,13 +188,251 @@ Plotter.plotMultiplePCAResults(compiled_results.pca_results, scan_parameter_valu
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveDirectory', figSaveDir);
|
||||
|
||||
%% ------------------ 7. Plot of all Angular Spectral Distribution Curves ------------------
|
||||
Plotter.plotSpectralCurves( ...
|
||||
results_all{1}.results.spectral_analysis_results.S_theta_norm_all, ...
|
||||
results_all{1}.results.spectral_analysis_results.theta_vals/pi, ... % correct θ values
|
||||
results_all{1}.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', 20, ...
|
||||
'TileTitlePrefix', '\alpha', ... % user-defined tile prefix
|
||||
'TileTitleSuffix', '^\circ', ... % user-defined suffix (e.g., degrees symbol)
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SpectralCurves.fig', ...
|
||||
'SaveDirectory', figSaveDir);
|
||||
|
||||
%% ------------------ 8. Plot of all Angular Spectral Distribution Curves shifted ------------------
|
||||
results = Plotter.plotSpectralCurvesRecentered( ...
|
||||
results_all{1}.results.spectral_analysis_results.S_theta_norm_all, ...
|
||||
results_all{1}.results.spectral_analysis_results.theta_vals/pi, ... % correct θ values
|
||||
results_all{1}.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', 21, ...
|
||||
'TileTitlePrefix', '\alpha', ... % user-defined tile prefix
|
||||
'TileTitleSuffix', '^\circ', ... % user-defined suffix (e.g., degrees symbol)
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SpectralCurves.fig', ...
|
||||
'SaveDirectory', figSaveDir);
|
||||
%% ------------------ 9. Plot cumulants from shifted Angular Spectral Distribution Curves ------------------
|
||||
Plotter.plotSpectralDistributionCumulants(results, ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 14, ...
|
||||
'FigNum', 22, ...
|
||||
'SkipSaveFigures', false, ...
|
||||
'SaveFileName', 'SpectralCumulants.fig');
|
||||
|
||||
%% ------------------ 10. Fit shifted Angular Spectral Distribution Curves ------------------
|
||||
fitResults = Analyzer.fitTwoGaussianCurves(...
|
||||
results_all{1}.results.spectral_analysis_results.S_theta_norm_all, ...
|
||||
results_all{1}.results.spectral_analysis_results.theta_vals, ...
|
||||
'MaxTheta', pi/2, ...
|
||||
'DeviationLimit', 1.00);
|
||||
|
||||
%% ------------------ 11. Plot fit parameters - position ------------------
|
||||
Plotter.plotFitParameterPDF(fitResults, results_all{1}.scan_reference_values, 'mu2', ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Secondary peak position (\theta, rad)', ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 16, ...
|
||||
'FigNum', 23, ...
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SecondaryPeakPositionPDF.fig', ...
|
||||
'PlotType', 'histogram', ...
|
||||
'NumberOfBins', 20, ...
|
||||
'NormalizeHistogram', true, ...
|
||||
'Colormap', @Colormaps.coolwarm, ...
|
||||
'XLim', [min(results_all{1}.scan_reference_values), max(results_all{1}.scan_reference_values)]);
|
||||
|
||||
%% ------------------ 12. Plot fit parameters - width ------------------
|
||||
Plotter.plotFitParameterPDF(fitResults, results_all{1}.scan_reference_values, 'sigma2', ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Secondary peak width (\sigma, rad)', ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 16, ...
|
||||
'FigNum', 24, ...
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SecondaryPeakWidthPDF.fig', ...
|
||||
'PlotType', 'histogram', ...
|
||||
'NumberOfBins', 20, ...
|
||||
'NormalizeHistogram', true, ...
|
||||
'Colormap', @Colormaps.coolwarm, ...
|
||||
'XLim', [min(results_all{1}.scan_reference_values), max(results_all{1}.scan_reference_values)]);
|
||||
|
||||
%% ------------------ 13. Plot fit parameters of mean shifted Angular Spectral Distribution Curves ------------------
|
||||
S_theta_all = results_all{1}.results.spectral_analysis_results.S_theta_norm_all;
|
||||
theta_vals = results_all{1}.results.spectral_analysis_results.theta_vals;
|
||||
scanValues = results_all{1}.scan_reference_values;
|
||||
|
||||
N_params = numel(scanValues);
|
||||
N_total = numel(S_theta_all);
|
||||
N_reps = N_total / N_params;
|
||||
|
||||
theta_min = deg2rad(0);
|
||||
theta_max = deg2rad(90);
|
||||
|
||||
% --- Shift curves so first peak is at start ---
|
||||
S_theta_all_shifted = cell(size(S_theta_all));
|
||||
for k = 1:N_total
|
||||
curve = S_theta_all{k};
|
||||
idx_range = find(theta_vals >= theta_min & theta_vals <= theta_max);
|
||||
if isempty(idx_range)
|
||||
[~, peak_idx] = max(curve);
|
||||
else
|
||||
[~, local_max_idx] = max(curve(idx_range));
|
||||
peak_idx = idx_range(local_max_idx);
|
||||
end
|
||||
S_theta_all_shifted{k} = curve(peak_idx:end);
|
||||
end
|
||||
|
||||
% --- Pad shorter curves with NaN to match lengths ---
|
||||
Npoints_shifted = max(cellfun(@numel, S_theta_all_shifted));
|
||||
for k = 1:N_total
|
||||
len = numel(S_theta_all_shifted{k});
|
||||
if len < Npoints_shifted
|
||||
S_theta_all_shifted{k} = [S_theta_all_shifted{k}, nan(1, Npoints_shifted-len)];
|
||||
end
|
||||
end
|
||||
|
||||
% --- Compute mean curves per scan parameter ---
|
||||
meanCurves = cell(1, N_params);
|
||||
for i = 1:N_params
|
||||
curves = zeros(N_reps, Npoints_shifted);
|
||||
for r = 1:N_reps
|
||||
idx = (r-1)*N_params + i; % interleaved indexing
|
||||
curves(r,:) = S_theta_all_shifted{idx};
|
||||
end
|
||||
meanCurves{i} = nanmean(curves,1); % mean over repetitions
|
||||
end
|
||||
|
||||
% --- Fit two-Gaussian model to mean curves ---
|
||||
fitResultsMean = Analyzer.fitTwoGaussianCurves(meanCurves, theta_vals(1:Npoints_shifted)-theta_vals(1), ...
|
||||
'MaxTheta', pi/2, ...
|
||||
'DeviationLimit', 1.0);
|
||||
|
||||
% --- Scatter plot of secondary peak position (mu2) vs scan parameter ---
|
||||
mu2_vals = nan(1, N_params);
|
||||
sigma2_vals = nan(1, N_params);
|
||||
|
||||
for i = 1:N_params
|
||||
if fitResultsMean(i).isValid
|
||||
mu2_vals(i) = fitResultsMean(i).pFit(4); % secondary peak position
|
||||
sigma2_vals(i) = fitResultsMean(i).pFit(5); % secondary peak width
|
||||
end
|
||||
end
|
||||
|
||||
% Secondary peak position
|
||||
plotSecondaryPeakScatter(fitResultsMean, results_all{1}.scan_reference_values, 'mu2', ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Secondary peak position (\theta, rad)', ...
|
||||
'FigNum', 23, ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 16, ...
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SecondaryPeakPositionScatter.fig');
|
||||
|
||||
% Secondary peak width
|
||||
plotSecondaryPeakScatter(fitResultsMean, results_all{1}.scan_reference_values, 'sigma2', ...
|
||||
'Title', options.titleString, ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Secondary peak width (\sigma, rad)', ...
|
||||
'FigNum', 24, ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 16, ...
|
||||
'SkipSaveFigures', options.skipSaveFigures, ...
|
||||
'SaveFileName', 'SecondaryPeakWidthScatter.fig');
|
||||
|
||||
|
||||
function plotSecondaryPeakScatter(fitResultsMean, scanValues, parameterName, varargin)
|
||||
%% plotSecondaryPeakScatter
|
||||
% Author: Karthik
|
||||
% Date: 2025-10-06
|
||||
% Version: 1.0
|
||||
%
|
||||
% Description:
|
||||
% Scatter plot of secondary peak fit parameters (mu2 or sigma2) vs scan parameter
|
||||
% in the same style as plotMeanWithSE.
|
||||
|
||||
% --- Parse optional inputs ---
|
||||
p = inputParser;
|
||||
addParameter(p, 'Title', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'XLabel', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'YLabel', '', @(x) ischar(x) || isstring(x));
|
||||
addParameter(p, 'FigNum', [], @(x) isempty(x) || isscalar(x));
|
||||
addParameter(p, 'FontName', 'Arial', @ischar);
|
||||
addParameter(p, 'FontSize', 14, @isnumeric);
|
||||
addParameter(p, 'YLim', [], @(x) isempty(x) || isnumeric(x));
|
||||
addParameter(p, 'SkipSaveFigures', false, @islogical);
|
||||
addParameter(p, 'SaveFileName', 'secondary_peak_scatter.fig', @ischar);
|
||||
addParameter(p, 'SaveDirectory', pwd, @ischar);
|
||||
parse(p, varargin{:});
|
||||
opts = p.Results;
|
||||
|
||||
% --- Extract parameter values ---
|
||||
N_params = numel(fitResultsMean);
|
||||
paramVals = nan(1, N_params);
|
||||
for i = 1:N_params
|
||||
if fitResultsMean(i).isValid
|
||||
switch parameterName
|
||||
case 'mu2'
|
||||
paramVals(i) = fitResultsMean(i).pFit(4);
|
||||
case 'sigma2'
|
||||
paramVals(i) = fitResultsMean(i).pFit(5);
|
||||
otherwise
|
||||
error('Unknown parameter name: %s', parameterName);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
% --- Prepare figure ---
|
||||
if isempty(opts.FigNum)
|
||||
fig = figure;
|
||||
else
|
||||
fig = figure(opts.FigNum);
|
||||
clf(fig);
|
||||
end
|
||||
set(fig, 'Color', 'w', 'Position', [100 100 950 750]);
|
||||
|
||||
% --- Plot as mean ± SE style (SE=0 here) ---
|
||||
errorbar(scanValues, paramVals, zeros(size(paramVals)), 'o--', ...
|
||||
'LineWidth', 1.8, 'MarkerSize', 6, 'CapSize', 5);
|
||||
|
||||
% --- Axis formatting ---
|
||||
set(gca, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
if ~isempty(opts.YLim)
|
||||
ylim(opts.YLim);
|
||||
end
|
||||
xlabel(opts.XLabel, 'Interpreter', 'tex', 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
ylabel(opts.YLabel, 'Interpreter', 'tex', 'FontName', opts.FontName, 'FontSize', opts.FontSize);
|
||||
title(opts.Title, 'FontName', opts.FontName, 'FontSize', opts.FontSize + 2, 'FontWeight', 'bold');
|
||||
grid on;
|
||||
|
||||
% --- Save figure ---
|
||||
if ~opts.SkipSaveFigures
|
||||
if ~exist(opts.SaveDirectory, 'dir'), mkdir(opts.SaveDirectory); end
|
||||
savefig(fig, fullfile(opts.SaveDirectory, opts.SaveFileName));
|
||||
end
|
||||
end
|
||||
|
||||
%{
|
||||
%% ------------------ 7. Average of Spectra Plots ------------------
|
||||
%% ------------------ 14. Average of Spectra Plots ------------------
|
||||
|
||||
Plotter.plotAverageSpectra(scan_parameter_values, ...
|
||||
spectral_analysis_results, ...
|
||||
'ScanParameterName', scan_parameter, ...
|
||||
'FigNum', 20, ...
|
||||
'FigNum', 25, ...
|
||||
'ColormapPS', Colormaps.coolwarm(), ...
|
||||
'Font', 'Bahnschrift', ...
|
||||
'SaveFileName', 'avgSpectra.fig', ...
|
||||
|
||||
@ -0,0 +1,192 @@
|
||||
%% ===== BEC-Stripes-Droplets Settings =====
|
||||
|
||||
% Specify data location to run analysis on
|
||||
dataSources = {
|
||||
struct('sequence', 'TwoDGas', ...
|
||||
'date', '2025/06/24', ...
|
||||
'runs', [1]) % specify run numbers as a string in "" or just as a numeric value
|
||||
};
|
||||
|
||||
options = struct();
|
||||
|
||||
% File paths
|
||||
options.baseDataFolder = '//DyLabNAS/Data';
|
||||
options.FullODImagesFolder = 'E:/Data - Experiment/FullODImages/202506';
|
||||
options.measurementName = 'StripesToDroplets';
|
||||
scriptFullPath = mfilename('fullpath');
|
||||
options.saveDirectory = fileparts(scriptFullPath);
|
||||
|
||||
% Camera / imaging settings
|
||||
options.cam = 4; % 1 - ODT_1_Axis_Camera; 2 - ODT_2_Axis_Camera; 3 - Horizontal_Axis_Camera;, 4 - Vertical_Axis_Camera;
|
||||
options.angle = 0; % angle by which image will be rotated
|
||||
options.center = [1410, 2030];
|
||||
options.span = [200, 200];
|
||||
options.fraction = [0.1, 0.1];
|
||||
options.pixel_size = 5.86e-6; % in meters
|
||||
options.magnification = 23.94;
|
||||
options.ImagingMode = 'HighIntensity';
|
||||
options.PulseDuration = 5e-6; % in s
|
||||
|
||||
% Fourier analysis settings
|
||||
options.theta_min = deg2rad(0);
|
||||
options.theta_max = deg2rad(180);
|
||||
options.N_radial_bins = 500;
|
||||
options.Radial_Sigma = 2;
|
||||
options.Radial_WindowSize = 5; % odd number
|
||||
|
||||
options.k_min = 1.2771; % μm⁻¹
|
||||
options.k_max = 2.5541; % μm⁻¹
|
||||
options.N_angular_bins = 180;
|
||||
options.Angular_Threshold = 75;
|
||||
options.Angular_Sigma = 2;
|
||||
options.Angular_WindowSize = 5;
|
||||
options.zoom_size = 50;
|
||||
|
||||
%
|
||||
options.maximumShift = 8;
|
||||
options.Radial_Theta = deg2rad(45);
|
||||
options.Radial_Minimum = 2;
|
||||
options.Radial_Maximum = 6;
|
||||
options.skipLivePlot = false;
|
||||
|
||||
% Flags
|
||||
options.skipUnshuffling = false;
|
||||
options.skipNormalization = false;
|
||||
|
||||
options.skipFringeRemoval = true;
|
||||
options.skipPreprocessing = true;
|
||||
options.skipMasking = true;
|
||||
options.skipIntensityThresholding = true;
|
||||
options.skipBinarization = true;
|
||||
|
||||
options.skipFullODImagesFolderUse = false;
|
||||
options.skipSaveData = false;
|
||||
options.skipSaveFigures = true;
|
||||
options.skipSaveProcessedOD = true;
|
||||
options.skipLivePlot = false;
|
||||
options.showProgressBar = true;
|
||||
|
||||
|
||||
% Extras
|
||||
options.font = 'Bahnschrift';
|
||||
switch options.measurementName
|
||||
case 'BECToDroplets'
|
||||
options.scan_parameter = 'rot_mag_field';
|
||||
options.flipSortOrder = false;
|
||||
options.scanParameterUnits = 'gauss';
|
||||
options.titleString = 'BEC to Droplets';
|
||||
case 'BECToStripes'
|
||||
options.scan_parameter = 'rot_mag_field';
|
||||
options.flipSortOrder = false;
|
||||
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
|
||||
|
||||
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.deviationThreshold = 0.30;
|
||||
options.minEllipseFraction = 0.30;
|
||||
options.angleLimit = deg2rad(45);
|
||||
options.angleTolerance = deg2rad(5);
|
||||
|
||||
% 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 = false;
|
||||
saveDirectory = 'C:\Users\Karthik-OfficePC\Documents\GitRepositories\Calculations\Data-Analyzer\+Scripts';
|
||||
Plotter.plotODG2withAnalysis(od_imgs, scan_parameter_values, g2_analysis_results, analysis_results, options, ...
|
||||
'FontName', options.font, ...
|
||||
'FontSize', 14, ...
|
||||
'ShowOverlays', true, ...
|
||||
'RepsPerPage', 5, ... % paginate 10 repetitions per figure
|
||||
'SaveDirectory', 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,1], ...
|
||||
'XLabel', '\alpha (degrees)', ...
|
||||
'YLabel', 'Anisotropy of correlation peaks', ...
|
||||
'FigNum', 1, ...
|
||||
'FontName', options.font, ...
|
||||
'SaveFileName', 'RadialSpectralContrast.fig', ...
|
||||
'SaveDirectory', pwd, ... % save figures inside dataset-specific folder
|
||||
'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', pwd, ...
|
||||
'NumberOfBins', 10, ...
|
||||
'NormalizeHistogram', true, ...
|
||||
'DataRange', [0 1.0], ...
|
||||
'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
|
||||
Loading…
x
Reference in New Issue
Block a user