diff --git a/Data-Analyzer/extractAutocorrelation.m b/Data-Analyzer/extractAutocorrelation.m new file mode 100644 index 0000000..7e66f76 --- /dev/null +++ b/Data-Analyzer/extractAutocorrelation.m @@ -0,0 +1,610 @@ +%% Parameters + +groupList = ["/images/MOT_3D_Camera/in_situ_absorption", "/images/ODT_1_Axis_Camera/in_situ_absorption", ... + "/images/ODT_2_Axis_Camera/in_situ_absorption", "/images/Horizontal_Axis_Camera/in_situ_absorption", ... + "/images/Vertical_Axis_Camera/in_situ_absorption"]; + +folderPath = "E:/Data - Experiment/2025/05/22/"; + +run = '0078'; + +folderPath = strcat(folderPath, run); + +cam = 5; + +angle = 0; +center = [1375, 2020]; +span = [200, 200]; +fraction = [0.1, 0.1]; + +pixel_size = 5.86e-6; +removeFringes = false; + +scan_parameter = 'rot_mag_fin_pol_angle'; +% scan_parameter = 'rot_mag_field'; +scan_parameter_text = 'Angle = '; +% scan_parameter_text = 'BField = '; + +font = 'Bahnschrift'; + +skipPreprocessing = true; +skipMasking = true; +skipIntensityThresholding = true; +skipBinarization = true; + +%% Compute OD image, rotate and extract ROI for analysis +% Get a list of all files in the folder with the desired file name pattern. + +filePattern = fullfile(folderPath, '*.h5'); +files = dir(filePattern); +refimages = zeros(span(1) + 1, span(2) + 1, length(files)); +absimages = zeros(span(1) + 1, span(2) + 1, length(files)); + +for k = 1 : length(files) + baseFileName = files(k).name; + fullFileName = fullfile(files(k).folder, baseFileName); + + fprintf(1, 'Now reading %s\n', fullFileName); + + atm_img = double(imrotate(h5read(fullFileName, append(groupList(cam), "/atoms")), angle)); + bkg_img = double(imrotate(h5read(fullFileName, append(groupList(cam), "/background")), angle)); + dark_img = double(imrotate(h5read(fullFileName, append(groupList(cam), "/dark")), angle)); + + refimages(:,:,k) = subtractBackgroundOffset(cropODImage(bkg_img, center, span), fraction)'; + absimages(:,:,k) = subtractBackgroundOffset(cropODImage(calculateODImage(atm_img, bkg_img, dark_img), center, span), fraction)'; + +end + +% Fringe removal + +if removeFringes + optrefimages = removefringesInImage(absimages, refimages); + absimages_fringe_removed = absimages(:, :, :) - optrefimages(:, :, :); + + nimgs = size(absimages_fringe_removed,3); + od_imgs = cell(1, nimgs); + for i = 1:nimgs + od_imgs{i} = absimages_fringe_removed(:, :, i); + end +else + nimgs = size(absimages(:, :, :),3); + od_imgs = cell(1, nimgs); + for i = 1:nimgs + od_imgs{i} = absimages(:, :, i); + end +end + +%% Get rotation angles +scan_parameter_values = zeros(1, length(files)); + +% Get information about the '/globals' group +for k = 1 : length(files) + baseFileName = files(k).name; + fullFileName = fullfile(files(k).folder, baseFileName); + info = h5info(fullFileName, '/globals'); + for i = 1:length(info.Attributes) + if strcmp(info.Attributes(i).Name, scan_parameter) + if strcmp(scan_parameter, 'rot_mag_fin_pol_angle') + scan_parameter_values(k) = 180 - info.Attributes(i).Value; + else + scan_parameter_values(k) = info.Attributes(i).Value; + end + end + end +end + +%% Extract g2 from experiment data + +fft_imgs = cell(1, nimgs); +spectral_distribution = cell(1, nimgs); +theta_values = cell(1, nimgs); + +N_bins = 32; +Threshold = 75; +Sigma = 2; +N_shots = length(od_imgs); + +% Display the cropped image +for k = 1:N_shots + IMG = od_imgs{k}; + [IMGFFT, IMGPR] = computeFourierTransform(IMG, skipPreprocessing, skipMasking, skipIntensityThresholding, skipBinarization); + + % Calculate the x and y limits for the cropped image + y_min = center(1) - span(2) / 2; + y_max = center(1) + span(2) / 2; + x_min = center(2) - span(1) / 2; + x_max = center(2) + span(1) / 2; + + % Generate x and y arrays representing the original coordinates for each pixel + x_range = linspace(x_min, x_max, span(1)); + y_range = linspace(y_min, y_max, span(2)); + + [rows, cols] = size(IMGFFT); + zoom_size = 50; % Zoomed-in region around center + mid_x = floor(cols/2); + mid_y = floor(rows/2); + fft_imgs{k} = IMGFFT(mid_y-zoom_size:mid_y+zoom_size, mid_x-zoom_size:mid_x+zoom_size); + + [theta_vals, S_theta] = computeNormalizedAngularSpectralDistribution(fft_imgs{k}, 10, 20, N_bins, Threshold, Sigma); + spectral_distribution{k} = S_theta; + theta_values{k} = theta_vals; +end + +% Create matrix of shape (N_shots x N_bins) +delta_nkr_all = zeros(N_shots, N_bins); +for k = 1:N_shots + delta_nkr_all(k, :) = spectral_distribution{k}; +end + +% Grouping by scan parameter value (e.g., alpha) +[unique_scan_parameter_values, ~, idx] = unique(scan_parameter_values); + +% Number of unique alpha values +N_alpha = length(unique_scan_parameter_values); + +% Preallocate result arrays +g2_all = zeros(N_alpha, N_bins); +g2_error_all = zeros(N_alpha, N_bins); + +for i = 1:N_alpha + group_idx = find(idx == i); % Indices of 20 shots for this alpha + group_data = delta_nkr_all(group_idx, :); % (20 x N_bins) array + + for dtheta = 0:N_bins-1 + temp = zeros(length(group_idx), 1); + for j = 1:length(group_idx) + profile = group_data(j, :); + profile_shifted = circshift(profile, -dtheta, 2); + + num = mean(profile .* profile_shifted); + denom = mean(profile)^2; + + temp(j) = num / denom - 1; + end + g2_all(i, dtheta+1) = mean(temp); + g2_error_all(i, dtheta+1) = std(temp) / sqrt(length(group_idx)); % Standard error + end +end + +% Reconstruct theta axis from any one of the stored values +theta_vals = theta_values{1}; % assuming it's in radians + +% Number of unique alpha values +nAlpha = size(g2_all, 1); + +% Generate a colormap with enough unique colors +cmap = sky(nAlpha); % You can also try 'jet', 'turbo', 'hot', etc. + +figure(1); +clf; +set(gcf,'Position',[100 100 950 750]) +hold on; +legend_entries = cell(nAlpha, 1); + +for i = 1:nAlpha + errorbar(theta_vals/pi, g2_all(i, :), g2_error_all(i, :), ... + 'o-', 'Color', cmap(i,:), 'LineWidth', 1.2, ... + 'MarkerSize', 5, 'CapSize', 3); + legend_entries{i} = sprintf('$\\alpha = %g^\\circ$', unique_scan_parameter_values(i)); +end +ylim([-1.5 3.0]); % Set y-axis limits here +set(gca, 'FontSize', 14); +hXLabel = xlabel('$\delta\theta / \pi$', 'Interpreter', 'latex'); +hYLabel = ylabel('$g^{(2)}(\delta\theta)$', 'Interpreter', 'latex'); +hTitle = title('B = 2.45 G - Droplets to Stripes', 'Interpreter', 'tex'); +legend(legend_entries, 'Interpreter', 'latex', 'Location', 'bestoutside'); +set([hXLabel, hYLabel], 'FontName', font) +set([hXLabel, hYLabel], 'FontSize', 14) +set(hTitle, 'FontName', font, 'FontSize', 16, 'FontWeight', 'bold'); % Set font and size for title +grid on; + +%% Extract g2 from simulation data + +Data = load('E:/Results - Numerics/Data_Full3D/PhaseDiagram/ImagTimePropagation/Theta0/HighN/aS_9.562000e+01_theta_000_phi_000_N_712500/Run_000/psi_gs.mat','psi','Params','Transf','Observ'); + +% Data = load('E:/Results - Numerics/Data_Full3D/PhaseDiagram/ImagTimePropagation/Theta40/HighN/aS_9.562000e+01_theta_040_phi_000_N_508333/Run_000/psi_gs.mat','psi','Params','Transf','Observ'); + +Params = Data.Params; +Transf = Data.Transf; +Observ = Data.Observ; + +if isgpuarray(Data.psi) + psi = gather(Data.psi); +else + psi = Data.psi; +end +if isgpuarray(Data.Observ.residual) + Observ.residual = gather(Data.Observ.residual); +else + Observ.residual = Data.Observ.residual; +end + +% Axes scaling and coordinates in micrometers +x = Transf.x * Params.l0 * 1e6; +y = Transf.y * Params.l0 * 1e6; +z = Transf.z * Params.l0 * 1e6; + +dx = x(2)-x(1); dy = y(2)-y(1); dz = z(2)-z(1); + +% Calculate frequency increment (frequency axes) +Nx = length(x); % grid size along X +Ny = length(y); % grid size along Y +dx = mean(diff(x)); % real space increment in the X direction (in micrometers) +dy = mean(diff(y)); % real space increment in the Y direction (in micrometers) +dvx = 1 / (Nx * dx); % reciprocal space increment in the X direction (in micrometers^-1) +dvy = 1 / (Ny * dy); % reciprocal space increment in the Y direction (in micrometers^-1) + +% Create the frequency axes +vx = (-Nx/2:Nx/2-1) * dvx; % Frequency axis in X (micrometers^-1) +vy = (-Ny/2:Ny/2-1) * dvy; % Frequency axis in Y (micrometers^-1) + +% Calculate maximum frequencies +% kx_max = pi / dx; +% ky_max = pi / dy; + +% Generate reciprocal axes +% kx = linspace(-kx_max, kx_max * (Nx-2)/Nx, Nx); +% ky = linspace(-ky_max, ky_max * (Ny-2)/Ny, Ny); + +% Create the Wavenumber axes +kx = 2*pi*vx; % Wavenumber axis in X +ky = 2*pi*vy; % Wavenumber axis in Y + +% Compute probability density |psi|^2 +n = abs(psi).^2; + +nxz = squeeze(trapz(n*dy,2)); +nyz = squeeze(trapz(n*dx,1)); +nxy = squeeze(trapz(n*dz,3)); + +skipPreprocessing = true; +skipMasking = true; +skipIntensityThresholding = true; +skipBinarization = true; + +font = 'Bahnschrift'; +% Extract g2 + +N_bins = 90; +Threshold = 75; +Sigma = 2; + +IMG = nxy; + +[IMGFFT, IMGPR] = computeFourierTransform(IMG, skipPreprocessing, skipMasking, skipIntensityThresholding, skipBinarization); + +[theta_vals, S_theta] = computeNormalizedAngularSpectralDistribution(IMGFFT, 10, 35, N_bins, Threshold, Sigma); + +g2_all = zeros(1, N_bins); % Preallocate + +for dtheta = 0:N_bins-1 + profile = S_theta; + profile_shifted = circshift(profile, -dtheta, 2); + + num = mean(profile .* profile_shifted); + denom = mean(profile)^2; + + g2_all(dtheta+1) = num / denom - 1; +end + +figure(2); +clf +set(gcf,'Position',[500 100 1000 800]) +t = tiledlayout(2, 2, 'TileSpacing', 'compact', 'Padding', 'compact'); % 1x4 grid + +% Display the cropped OD image +nexttile +plotxy = pcolor(x,y,IMG'); +set(plotxy, 'EdgeColor', 'none'); +cbar1 = colorbar; +cbar1.Label.Interpreter = 'latex'; +colormap(gca, Helper.Colormaps.plasma()) +xlabel('$x$ ($\mu$m)', 'Interpreter', 'latex', 'FontSize', 14) +ylabel('$y$ ($\mu$m)', 'Interpreter', 'latex', 'FontSize', 14) +title('$|\Psi(x,y)|^2$', 'Interpreter', 'latex', 'FontSize', 14) + +% Plot the power spectrum +nexttile; +imagesc(kx, ky, log(1 + abs(IMGFFT).^2)); +axis square; +hcb = colorbar; +colormap(gca, Helper.Colormaps.plasma()) +set(gca, 'FontSize', 14); % For tick labels only +set(gca,'YDir','normal') +hXLabel = xlabel('k_x', 'Interpreter', 'tex'); +hYLabel = ylabel('k_y', 'Interpreter', 'tex'); +hTitle = title('Power Spectrum - S(k_x,k_y)', 'Interpreter', 'tex'); +set([hXLabel, hYLabel], 'FontName', font) +set([hXLabel, hYLabel], 'FontSize', 14) +set(hTitle, 'FontName', font, 'FontSize', 16, 'FontWeight', 'bold'); % Set font and size for title + +% Plot the angular distribution +nexttile +plot(theta_vals/pi, S_theta,'Linewidth',2); +set(gca, 'FontSize', 14); % For tick labels only +hXLabel = xlabel('\theta/\pi [rad]', 'Interpreter', 'tex'); +hYLabel = ylabel('Normalized magnitude (a.u.)', 'Interpreter', 'tex'); +hTitle = title('Angular Spectral Distribution - S(\theta)', 'Interpreter', 'tex'); +set([hXLabel, hYLabel], 'FontName', font) +set([hXLabel, hYLabel], 'FontSize', 14) +set(hTitle, 'FontName', font, 'FontSize', 16, 'FontWeight', 'bold'); % Set font and size for title +grid on + +nexttile +plot(theta_vals/pi, g2_all, 'o-', 'LineWidth', 1.2, 'MarkerSize', 5); +set(gca, 'FontSize', 14); +ylim([-1.5 3.0]); % Set y-axis limits here +hXLabel = xlabel('$\delta\theta / \pi$', 'Interpreter', 'latex'); +hYLabel = ylabel('$g^{(2)}(\delta\theta)$', 'Interpreter', 'latex'); +hTitle = title('Autocorrelation', 'Interpreter', 'tex'); +set([hXLabel, hYLabel], 'FontName', font) +set([hXLabel, hYLabel], 'FontSize', 14) +set(hTitle, 'FontName', font, 'FontSize', 16, 'FontWeight', 'bold'); % Set font and size for title +grid on; + +%% Helper Functions +function [IMGFFT, IMGPR] = computeFourierTransform(I, skipPreprocessing, skipMasking, skipIntensityThresholding, skipBinarization) + % computeFourierSpectrum - Computes the 2D Fourier power spectrum + % of binarized and enhanced lattice image features, with optional central mask. + % + % Inputs: + % I - Grayscale or RGB image matrix + % + % Output: + % F_mag - 2D Fourier power spectrum (shifted) + + if ~skipPreprocessing + % Preprocessing: Denoise + filtered = imgaussfilt(I, 10); + IMGPR = I - filtered; % adjust sigma as needed + else + IMGPR = I; + end + + if ~skipMasking + [rows, cols] = size(IMGPR); + [X, Y] = meshgrid(1:cols, 1:rows); + % Elliptical mask parameters + cx = cols / 2; + cy = rows / 2; + + % Shifted coordinates + x = X - cx; + y = Y - cy; + + % Ellipse semi-axes + rx = 0.4 * cols; + ry = 0.2 * rows; + + % Rotation angle in degrees -> radians + theta_deg = 30; % Adjust as needed + theta = deg2rad(theta_deg); + + % Rotated ellipse equation + cos_t = cos(theta); + sin_t = sin(theta); + + x_rot = (x * cos_t + y * sin_t); + y_rot = (-x * sin_t + y * cos_t); + + ellipseMask = (x_rot.^2) / rx^2 + (y_rot.^2) / ry^2 <= 1; + + % Apply cutout mask + IMGPR = IMGPR .* ellipseMask; + end + + if ~skipIntensityThresholding + % Apply global intensity threshold mask + intensity_thresh = 0.20; + intensity_mask = IMGPR > intensity_thresh; + IMGPR = IMGPR .* intensity_mask; + end + + if ~skipBinarization + % Adaptive binarization and cleanup + IMGPR = imbinarize(IMGPR, 'adaptive', 'Sensitivity', 0.0); + IMGPR = imdilate(IMGPR, strel('disk', 2)); + IMGPR = imerode(IMGPR, strel('disk', 1)); + IMGPR = imfill(IMGPR, 'holes'); + F = fft2(double(IMGPR)); % Compute 2D Fourier Transform + IMGFFT = abs(fftshift(F))'; % Shift zero frequency to center + else + F = fft2(double(IMGPR)); % Compute 2D Fourier Transform + IMGFFT = abs(fftshift(F))'; % Shift zero frequency to center + end +end + +function [theta_vals, S_theta] = computeNormalizedAngularSpectralDistribution(IMGFFT, r_min, r_max, num_bins, threshold, sigma) + % Apply threshold to isolate strong peaks + IMGFFT(IMGFFT < threshold) = 0; + + % Prepare polar coordinates + [ny, nx] = size(IMGFFT); + [X, Y] = meshgrid(1:nx, 1:ny); + cx = ceil(nx/2); + cy = ceil(ny/2); + R = sqrt((X - cx).^2 + (Y - cy).^2); + Theta = atan2(Y - cy, X - cx); % range [-pi, pi] + + % Choose radial band + radial_mask = (R >= r_min) & (R <= r_max); + + % Initialize the angular structure factor array + S_theta = zeros(1, num_bins); % Pre-allocate for 180 angle bins + % Define the angle values for the x-axis + theta_vals = linspace(0, pi, num_bins); + + % Loop through each angle bin + for i = 1:num_bins + angle_start = (i-1) * pi / num_bins; + angle_end = i * pi / num_bins; + + % Define a mask for the given angle range + angle_mask = (Theta >= angle_start & Theta < angle_end); + + bin_mask = radial_mask & angle_mask; + + % Extract the Fourier components for the given angle + fft_angle = IMGFFT .* bin_mask; + + % Integrate the Fourier components over the radius at the angle + S_theta(i) = sum(sum(abs(fft_angle).^2)); % sum of squared magnitudes + end + + % Create a 1D Gaussian kernel + half_width = ceil(3 * sigma); + x = -half_width:half_width; + gauss_kernel = exp(-x.^2 / (2 * sigma^2)); + gauss_kernel = gauss_kernel / sum(gauss_kernel); % normalize + + % Apply convolution (circular padding to preserve periodicity) + S_theta = conv([S_theta(end-half_width+1:end), S_theta, S_theta(1:half_width)], gauss_kernel, 'same'); + S_theta = S_theta(half_width+1:end-half_width); % crop back to original size + + % Normalize to 1 + S_theta = S_theta / max(S_theta); +end + +function ret = getBkgOffsetFromCorners(img, x_fraction, y_fraction) + % image must be a 2D numerical array + [dim1, dim2] = size(img); + + s1 = img(1:round(dim1 * y_fraction), 1:round(dim2 * x_fraction)); + s2 = img(1:round(dim1 * y_fraction), round(dim2 - dim2 * x_fraction):dim2); + s3 = img(round(dim1 - dim1 * y_fraction):dim1, 1:round(dim2 * x_fraction)); + s4 = img(round(dim1 - dim1 * y_fraction):dim1, round(dim2 - dim2 * x_fraction):dim2); + + ret = mean([mean(s1(:)), mean(s2(:)), mean(s3(:)), mean(s4(:))]); +end + +function ret = subtractBackgroundOffset(img, fraction) + % Remove the background from the image. + % :param dataArray: The image + % :type dataArray: xarray DataArray + % :param x_fraction: The fraction of the pixels used in x axis + % :type x_fraction: float + % :param y_fraction: The fraction of the pixels used in y axis + % :type y_fraction: float + % :return: The image after removing background + % :rtype: xarray DataArray + + x_fraction = fraction(1); + y_fraction = fraction(2); + offset = getBkgOffsetFromCorners(img, x_fraction, y_fraction); + ret = img - offset; +end + +function ret = cropODImage(img, center, span) + % Crop the image according to the region of interest (ROI). + % :param dataSet: The images + % :type dataSet: xarray DataArray or DataSet + % :param center: The center of region of interest (ROI) + % :type center: tuple + % :param span: The span of region of interest (ROI) + % :type span: tuple + % :return: The cropped images + % :rtype: xarray DataArray or DataSet + + x_start = floor(center(1) - span(1) / 2); + x_end = floor(center(1) + span(1) / 2); + y_start = floor(center(2) - span(2) / 2); + y_end = floor(center(2) + span(2) / 2); + + ret = img(y_start:y_end, x_start:x_end); +end + +function ret = calculateODImage(imageAtom, imageBackground, imageDark) + % Calculate the OD image for absorption imaging. + % :param imageAtom: The image with atoms + % :type imageAtom: numpy array + % :param imageBackground: The image without atoms + % :type imageBackground: numpy array + % :param imageDark: The image without light + % :type imageDark: numpy array + % :return: The OD images + % :rtype: numpy array + + numerator = imageBackground - imageDark; + denominator = imageAtom - imageDark; + + numerator(numerator == 0) = 1; + denominator(denominator == 0) = 1; + + ret = -log(double(abs(denominator ./ numerator))); + + if numel(ret) == 1 + ret = ret(1); + end +end + +function [optrefimages] = removefringesInImage(absimages, refimages, bgmask) + % removefringesInImage - Fringe removal and noise reduction from absorption images. + % Creates an optimal reference image for each absorption image in a set as + % a linear combination of reference images, with coefficients chosen to + % minimize the least-squares residuals between each absorption image and + % the optimal reference image. The coefficients are obtained by solving a + % linear set of equations using matrix inverse by LU decomposition. + % + % Application of the algorithm is described in C. F. Ockeloen et al, Improved + % detection of small atom numbers through image processing, arXiv:1007.2136 (2010). + % + % Syntax: + % [optrefimages] = removefringesInImage(absimages,refimages,bgmask); + % + % Required inputs: + % absimages - Absorption image data, + % typically 16 bit grayscale images + % refimages - Raw reference image data + % absimages and refimages are both cell arrays containing + % 2D array data. The number of refimages can differ from the + % number of absimages. + % + % Optional inputs: + % bgmask - Array specifying background region used, + % 1=background, 0=data. Defaults to all ones. + % Outputs: + % optrefimages - Cell array of optimal reference images, + % equal in size to absimages. + % + + % Dependencies: none + % + % Authors: Shannon Whitlock, Caspar Ockeloen + % Reference: C. F. Ockeloen, A. F. Tauschinsky, R. J. C. Spreeuw, and + % S. Whitlock, Improved detection of small atom numbers through + % image processing, arXiv:1007.2136 + % Email: + % May 2009; Last revision: 11 August 2010 + + % Process inputs + + % Set variables, and flatten absorption and reference images + nimgs = size(absimages,3); + nimgsR = size(refimages,3); + xdim = size(absimages(:,:,1),2); + ydim = size(absimages(:,:,1),1); + + R = single(reshape(refimages,xdim*ydim,nimgsR)); + A = single(reshape(absimages,xdim*ydim,nimgs)); + optrefimages=zeros(size(absimages)); % preallocate + + if not(exist('bgmask','var')); bgmask=ones(ydim,xdim); end + k = find(bgmask(:)==1); % Index k specifying background region + + % Ensure there are no duplicate reference images + % R=unique(R','rows')'; % comment this line if you run out of memory + + % Decompose B = R*R' using singular value or LU decomposition + [L,U,p] = lu(R(k,:)'*R(k,:),'vector'); % LU decomposition + + for j=1:nimgs + b=R(k,:)'*A(k,j); + % Obtain coefficients c which minimise least-square residuals + lower.LT = true; upper.UT = true; + c = linsolve(U,linsolve(L,b(p,:),lower),upper); + + % Compute optimised reference image + optrefimages(:,:,j)=reshape(R*c,[ydim xdim]); + end +end + diff --git a/Data-Analyzer/fourierAnalysis.m b/Data-Analyzer/fourierAnalysis.m index b0b06ba..526838a 100644 --- a/Data-Analyzer/fourierAnalysis.m +++ b/Data-Analyzer/fourierAnalysis.m @@ -309,111 +309,6 @@ set(hTitle, 'FontName', font, 'FontSize', 16, 'FontWeight', 'bold'); % Set font grid on; hold off; -%% Extract g2 - -fft_imgs = cell(1, nimgs); -spectral_distribution = cell(1, nimgs); -theta_values = cell(1, nimgs); - -N_bins = 32; -Threshold = 75; -Sigma = 2; -N_shots = length(od_imgs); - -% Display the cropped image -for k = 1:N_shots - IMG = od_imgs{k}; - [IMGFFT, IMGPR] = computeFourierTransform(IMG, skipPreprocessing, skipMasking, skipIntensityThresholding, skipBinarization); - - % Calculate the x and y limits for the cropped image - y_min = center(1) - span(2) / 2; - y_max = center(1) + span(2) / 2; - x_min = center(2) - span(1) / 2; - x_max = center(2) + span(1) / 2; - - % Generate x and y arrays representing the original coordinates for each pixel - x_range = linspace(x_min, x_max, span(1)); - y_range = linspace(y_min, y_max, span(2)); - - [rows, cols] = size(IMGFFT); - zoom_size = 50; % Zoomed-in region around center - mid_x = floor(cols/2); - mid_y = floor(rows/2); - fft_imgs{k} = IMGFFT(mid_y-zoom_size:mid_y+zoom_size, mid_x-zoom_size:mid_x+zoom_size); - - [theta_vals, S_theta] = computeNormalizedAngularSpectralDistribution(fft_imgs{k}, 10, 20, N_bins, Threshold, Sigma); - spectral_distribution{k} = S_theta; - theta_values{k} = theta_vals; -end - -% Create matrix of shape (N_shots x N_bins) -delta_nkr_all = zeros(N_shots, N_bins); -for k = 1:N_shots - delta_nkr_all(k, :) = spectral_distribution{k}; -end - -% Grouping by scan parameter value (e.g., alpha) -[unique_scan_parameter_values, ~, idx] = unique(scan_parameter_values); - -% Number of unique alpha values -N_alpha = length(unique_scan_parameter_values); - -% Preallocate result arrays -g2_all = zeros(N_alpha, N_bins); -g2_error_all = zeros(N_alpha, N_bins); - -for i = 1:N_alpha - group_idx = find(idx == i); % Indices of 20 shots for this alpha - group_data = delta_nkr_all(group_idx, :); % (20 x N_bins) array - - for dtheta = 0:N_bins-1 - temp = zeros(length(group_idx), 1); - for j = 1:length(group_idx) - profile = group_data(j, :); - profile_shifted = circshift(profile, -dtheta, 2); - - num = mean(profile .* profile_shifted); - denom = mean(profile)^2; - - temp(j) = num / denom - 1; - end - g2_all(i, dtheta+1) = mean(temp); - g2_error_all(i, dtheta+1) = std(temp) / sqrt(length(group_idx)); % Standard error - end -end - -% Reconstruct theta axis from any one of the stored values -theta_vals = theta_values{1}; % assuming it's in radians - -% Number of unique alpha values -nAlpha = size(g2_all, 1); - -% Generate a colormap with enough unique colors -cmap = sky(nAlpha); % You can also try 'jet', 'turbo', 'hot', etc. - -figure(4); -clf; -set(gcf,'Position',[100 100 950 750]) -hold on; -legend_entries = cell(nAlpha, 1); - -for i = 1:nAlpha - errorbar(theta_vals/pi, g2_all(i, :), g2_error_all(i, :), ... - 'o-', 'Color', cmap(i,:), 'LineWidth', 1.2, ... - 'MarkerSize', 5, 'CapSize', 3); - legend_entries{i} = sprintf('$\\alpha = %g^\\circ$', unique_scan_parameter_values(i)); -end -ylim([-1.5 3.0]); % Set y-axis limits here -set(gca, 'FontSize', 14); -hXLabel = xlabel('$\delta\theta / \pi$', 'Interpreter', 'latex'); -hYLabel = ylabel('$g^{(2)}(\delta\theta)$', 'Interpreter', 'latex'); -hTitle = title('B = 2.45 G - Droplets to Stripes', 'Interpreter', 'tex'); -legend(legend_entries, 'Interpreter', 'latex', 'Location', 'bestoutside'); -set([hXLabel, hYLabel], 'FontName', font) -set([hXLabel, hYLabel], 'FontSize', 14) -set(hTitle, 'FontName', font, 'FontSize', 16, 'FontWeight', 'bold'); % Set font and size for title -grid on; - %% Helper Functions function [IMGFFT, IMGPR] = computeFourierTransform(I, skipPreprocessing, skipMasking, skipIntensityThresholding, skipBinarization)