Calculations/Data-Analyzer/fourierAnalysis.m

718 lines
26 KiB
Matlab
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

%% 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 = "C:/Users/Karthik/Documents/GitRepositories/Calculations/Data-Analyzer/";
run = '0013';
folderPath = strcat(folderPath, run);
cam = 5;
angle = 0;
center = [1285, 2105];
span = [200, 200];
fraction = [0.1, 0.1];
pixel_size = 5.86e-6;
removeFringes = false;
%% 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 = im2double(imrotate(h5read(fullFileName, append(groupList(cam), "/atoms")), angle));
bkg_img = im2double(imrotate(h5read(fullFileName, append(groupList(cam), "/background")), angle));
dark_img = im2double(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
theta_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, 'rot_mag_fin_pol_angle')
theta_values(k) = 180 - info.Attributes(i).Value;
end
end
end
%% Run Fourier analysis over images
fft_imgs = cell(1, nimgs);
spectral_weight = zeros(1, nimgs);
% Create VideoWriter object for movie
videoFile = VideoWriter('Single_Shot_FFT.mp4', 'MPEG-4');
videoFile.Quality = 100; % Set quality to maximum (0100)
videoFile.FrameRate = 2; % Set the frame rate (frames per second)
open(videoFile); % Open the video file to write
% Display the cropped image
for k = 1 : length(od_imgs)
IMG = od_imgs{k};
[IMGFFT, IMGBIN] = computeFourierTransform(IMG);
figure(1);
clf
set(gcf,'Position',[500 100 1000 800])
t = tiledlayout(2, 2, 'TileSpacing', 'compact', 'Padding', 'compact'); % 1x4 grid
font = 'Bahnschrift';
% 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));
% Display the cropped image
ax1 = nexttile;
imagesc(x_range, y_range, IMG)
% Define normalized positions (relative to axis limits)
x_offset = 0.025; % 5% offset from the edges
y_offset = 0.025; % 5% offset from the edges
% Top-right corner (normalized axis coordinates)
hText = text(1 - x_offset, 1 - y_offset, ['Angle: ', num2str(theta_values(k), '%.1f')], ...
'Color', 'white', 'FontWeight', 'bold', 'Interpreter', 'tex', 'FontSize', 20, 'Units', 'normalized', 'HorizontalAlignment', 'right', 'VerticalAlignment', 'top');
axis equal tight;
hcb = colorbar;
colormap(ax1, 'jet');
set(gca, 'FontSize', 14); % For tick labels only
hL = ylabel(hcb, 'Optical Density');
set(hL,'Rotation',-90);
set(gca,'YDir','normal')
set(gca, 'YTick', linspace(y_min, y_max, 5)); % Define y ticks
set(gca, 'YTickLabel', flip(linspace(y_min, y_max, 5))); % Flip only the labels
hXLabel = xlabel('x (pixels)', 'Interpreter', 'tex');
hYLabel = ylabel('y (pixels)', 'Interpreter', 'tex');
hTitle = title('OD Image', 'Interpreter', 'tex');
set([hXLabel, hYLabel, hL, hText], 'FontName', font)
set([hXLabel, hYLabel, hL], 'FontSize', 14)
set(hTitle, 'FontName', font, 'FontSize', 16, 'FontWeight', 'bold'); % Set font and size for title
ax2 = nexttile;
imagesc(x_range, y_range, IMGBIN)
axis equal tight;
hcb = colorbar;
colormap(ax2, 'parula');
set(gca, 'FontSize', 14); % For tick labels only
set(gca,'YDir','normal')
set(gca, 'YTick', linspace(y_min, y_max, 5)); % Define y ticks
set(gca, 'YTickLabel', flip(linspace(y_min, y_max, 5))); % Flip only the labels
hXLabel = xlabel('x (pixels)', 'Interpreter', 'tex');
hYLabel = ylabel('y (pixels)', 'Interpreter', 'tex');
hTitle = title('Denoised - Masked - Binarized', '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
ax3 = nexttile;
[rows, cols] = size(IMGFFT);
zoom_size = 50; % Zoomed-in region around center
mid_x = floor(cols/2);
mid_y = floor(rows/2);
zoomedIMGFFT = IMGFFT(mid_y-zoom_size:mid_y+zoom_size, mid_x-zoom_size:mid_x+zoom_size);
fft_imgs{k} = zoomedIMGFFT;
imagesc(log(1 + zoomedIMGFFT));
% Define normalized positions (relative to axis limits)
x_offset = 0.025; % 5% offset from the edges
y_offset = 0.025; % 5% offset from the edges
% Top-right corner (normalized axis coordinates)
% hText = text(1 - x_offset, 1 - y_offset, ['Angle: ', num2str(theta_values(k), '%.1f')], ...
% 'Color', 'white', 'FontWeight', 'bold', 'Interpreter', 'tex', 'FontSize', 20, 'Units', 'normalized', 'HorizontalAlignment', 'right', 'VerticalAlignment', 'top');
axis equal tight;
hcb = colorbar;
colormap(ax3, 'jet');
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)|^2', 'Interpreter', 'tex');
set([hXLabel, hYLabel, hText], '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
[theta_vals, angular_intensity] = computeAngularDistribution(zoomedIMGFFT, 10, 20, 100, 75);
polarhistogram('BinEdges', theta_vals, 'BinCounts', angular_intensity, ...
'FaceColor', [0.2 0.6 0.9], 'EdgeColor', 'k');
set(gca, 'FontSize', 14); % For tick labels only
hTitle = title('Angular Distribution', 'Interpreter', 'tex');
set(hTitle, 'FontName', font)
set(hTitle, 'FontName', font, 'FontSize', 16, 'FontWeight', 'bold'); % Set font and size for title
%}
% Plot the angular structure factor
nexttile
[theta_vals, S_theta] = computeNormalizedAngularSpectralDistribution(zoomedIMGFFT, 10, 20, 180, 75, 2);
spectral_weight(k) = trapz(theta_vals, sqrt(S_theta));
plot(theta_vals/pi, S_theta,'Linewidth',2);
set(gca, 'FontSize', 14); % For tick labels only
hXLabel = xlabel('\theta (\pi)', 'Interpreter', 'tex');
hYLabel = ylabel('Normalized magnitude (a.u.)', 'Interpreter', 'tex');
hTitle = title('Angular Spectral Distribution - |S(\theta)|^2', 'Interpreter', 'tex');
set([hXLabel, hYLabel, hText], 'FontName', font)
set([hXLabel, hYLabel], 'FontSize', 14)
set(hTitle, 'FontName', font, 'FontSize', 16, 'FontWeight', 'bold'); % Set font and size for title
grid on
drawnow
pause(0.5)
% Capture the current frame and write it to the video
frame = getframe(gcf); % Capture the current figure as a frame
writeVideo(videoFile, frame); % Write the frame to the video
end
% Close the video file
close(videoFile);
%% Track spectral weight across the transition
% Assuming theta_values and spectral_weight are column vectors (or row vectors of same length)
[unique_theta, ~, idx] = unique(theta_values);
% Preallocate arrays
mean_sf = zeros(size(unique_theta));
stderr_sf = zeros(size(unique_theta));
% Loop through each unique theta and compute mean and standard error
for i = 1:length(unique_theta)
group_vals = spectral_weight(idx == i);
mean_sf(i) = mean(group_vals);
stderr_sf(i) = std(group_vals) / sqrt(length(group_vals)); % standard error = std / sqrt(N)
end
figure(2);
set(gcf,'Position',[100 100 950 750])
errorbar(unique_theta, mean_sf, stderr_sf, 'o--', ...
'LineWidth', 1.5, 'MarkerSize', 6, 'CapSize', 5);
set(gca, 'FontSize', 14); % For tick labels only
hXLabel = xlabel('\alpha (degrees)', 'Interpreter', 'tex');
hYLabel = ylabel('Spectral Weight', 'Interpreter', 'tex');
hTitle = title('Change during rotation', '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
%% k-means Clustering
% Reshape to column vector
X = mean_sf(:);
% Determine the number of clusters to try (you can experiment with different values here)
optimalClusters = 3; % Based on prior knowledge or experimentation
% Set the random seed for reproducibility
rng(42);
% Specify initialization method ('plus' is the default)
startMethod = 'plus'; % Options: 'uniform', 'plus', 'sample'
% Apply K-means clustering with controlled initialization
[idx, C] = kmeans(X, optimalClusters, 'Start', startMethod);
% Plot the results
figure(3);
set(gcf,'Position',[100 100 950 750])
hold on;
% Plot error bars with mean_sf and stderr_sf
errorbar(unique_theta, mean_sf, stderr_sf, 'o--', ...
'LineWidth', 1.5, 'MarkerSize', 6, 'CapSize', 5);
% Scatter plot for data points (showing clusters)
scatter(unique_theta, X, 50, idx, 'filled');
% Get the current y-axis limits
current_ylim = ylim;
% Generate colors for each cluster
colors = lines(optimalClusters); % Create distinct colors for each cluster
% Loop through each cluster and fill the regions
for i = 1:optimalClusters
% Find indices of data points that belong to the current cluster
clusterIdx = find(idx == i);
% Find the range of x-values for this cluster
x_min = unique_theta(clusterIdx(1)); % Starting x-value for the cluster
x_max = unique_theta(clusterIdx(end)); % Ending x-value for the cluster
% Fill the region corresponding to the cluster
fill([x_min, x_max, x_max, x_min], ...
[current_ylim(1), current_ylim(1), current_ylim(2), current_ylim(2)], ...
colors(i, :), 'EdgeColor', 'none', 'FaceAlpha', 0.3); % Add transparency
end
hXLabel = xlabel('\alpha (degrees)', 'Interpreter', 'tex');
hYLabel = ylabel('Spectral Weight', 'Interpreter', 'tex');
hTitle = title('Regimes identified by k-means clustering', '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;
hold off;
%% Helper Functions
function [IMGFFT, IMGBIN] = computeFourierTransform(I)
% 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)
% Preprocessing: Denoise
filtered = imgaussfilt(I, 10);
I_filt = I - filtered; % adjust sigma as needed
% Elliptical mask parameters
[rows, cols] = size(I_filt);
[X, Y] = meshgrid(1:cols, 1:rows);
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
I_masked = I_filt .* ellipseMask;
% Apply global intensity threshold mask
intensity_thresh = 0.20;
intensity_mask = I_masked > intensity_thresh;
I_masked = I_masked .* intensity_mask;
% Adaptive binarization and cleanup
IMGBIN = imbinarize(I_masked, 'adaptive', 'Sensitivity', 0.0);
IMGBIN = imdilate(IMGBIN, strel('disk', 2));
IMGBIN = imerode(IMGBIN, strel('disk', 1));
IMGBIN = imfill(IMGBIN, 'holes');
% Compute 2D Fourier Transform
F = fft2(double(I));
IMGFFT = abs(fftshift(F))'; % Shift zero frequency to center
% Define the radius for the circular region to exclude
region_radius = 4; % Adjust the radius as needed
% Create a circular mask
[~, center_idx] = max(IMGFFT(:));
[cx, cy] = ind2sub(size(IMGFFT), center_idx);
% Equation for a circle (centered at cx, cy)
center_region = (X - cx).^2 + (Y - cy).^2 <= region_radius^2;
% Define a scaling factor for the central region (e.g., reduce amplitude by 90%)
scaling_factor = 0.1; % Scale center region by 10%
% Apply the scaling factor to the center region
IMGFFT(center_region) = IMGFFT(center_region) * scaling_factor;
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:180
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
% Deprecated
%% Display Images
%{
figure(1)
clf
set(gcf,'Position',[50 50 950 750])
% 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));
% Display the cropped image
for k = 1 : length(od_imgs)
imagesc(x_range, y_range, od_imgs{k})
axis equal tight;
hcb = colorbar;
hL = ylabel(hcb, 'Optical Density');
set(hL,'Rotation',-90);
colormap jet;
set(gca,'CLim',[0 3.0]);
set(gca,'YDir','normal')
set(gca, 'YTick', linspace(y_min, y_max, 5)); % Define y ticks
set(gca, 'YTickLabel', flip(linspace(y_min, y_max, 5))); % Flip only the labels
xlabel('X', 'Interpreter', 'tex');
ylabel('Y', 'Interpreter', 'tex');
drawnow
pause(0.5)
end
%}
%% Averaged FFT
%{
% Assuming od_imgs is a cell array of size 4*n
n = length(fft_imgs) / 4; % Calculate n
fft_imgs_avg = cell(1, n); % Initialize the new cell array to hold the averaged images
for i = 1:n
% Take the 4 corresponding images from od_imgs
img1 = fft_imgs{4*i-3}; % 1st image in the group
img2 = fft_imgs{4*i-2}; % 2nd image in the group
img3 = fft_imgs{4*i-1}; % 3rd image in the group
img4 = fft_imgs{4*i}; % 4th image in the group
% Compute the average of these 4 images
avg_img = (img1 + img2 + img3 + img4) / 4;
% Store the averaged image in the new cell array
fft_imgs_avg{i} = avg_img;
end
% Create VideoWriter object for movie
videoFile = VideoWriter('Averaged_FFT.mp4', 'MPEG-4');
videoFile.Quality = 100; % Set quality to maximum (0100)
videoFile.FrameRate = 2; % Set the frame rate (frames per second)
open(videoFile); % Open the video file to write
% Display the cropped image
for k = 1 : length(fft_imgs_avg)
figure(3)
clf
set(gcf,'Position',[50 50 1500 550])
set(gca,'FontSize',16,'Box','On','Linewidth',2);
t = tiledlayout(1, 2, 'TileSpacing', 'compact', 'Padding', 'compact'); % 1x2 grid
nexttile
imagesc(log(1 + fft_imgs_avg{k}));
% Define normalized positions (relative to axis limits)
x_offset = 0.025; % 5% offset from the edges
y_offset = 0.025; % 5% offset from the edges
% Top-right corner (normalized axis coordinates)
text(1 - x_offset, 1 - y_offset, ['Angle: ', num2str(theta_values(k), '%.1f')], ...
'Color', 'white', 'FontWeight', 'bold', 'Interpreter', 'tex', 'FontSize', 20, 'Units', 'normalized', 'HorizontalAlignment', 'right', 'VerticalAlignment', 'top');
axis equal tight;
hcb = colorbar;
set(gca,'YDir','normal')
xlabel('X', 'Interpreter', 'tex','FontSize',16);
ylabel('Y', 'Interpreter', 'tex','FontSize',16);
title('Averaged Fourier Power Spectrum','FontSize',16);
% Plot the angular structure factor
nexttile
[theta_vals, angular_intensity] = computeAngularDistribution(fft_imgs_avg{k}, 10, 20, 100, 75);
polarhistogram('BinEdges', theta_vals, 'BinCounts', angular_intensity, ...
'FaceColor', [0.2 0.6 0.9], 'EdgeColor', 'k');
title('Angular Distribution');
drawnow
pause(0.5)
% Capture the current frame and write it to the video
frame = getframe(gcf); % Capture the current figure as a frame
writeVideo(videoFile, frame); % Write the frame to the video
end
% Close the video file
close(videoFile);
%}
%% Angular Distribution
%{
function [theta_vals, angular_intensity] = computeAngularDistribution(IMGFFT, r_min, r_max, num_bins, threshold)
% 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
mask = (R >= r_min) & (R <= r_max);
% Bin intensities by angle
theta_vals = linspace(-pi, pi, num_bins+1);
angular_intensity = zeros(1, num_bins);
for i = 1:num_bins
t0 = theta_vals(i);
t1 = theta_vals(i+1);
bin_mask = mask & (Theta >= t0) & (Theta < t1);
tmp = mean(IMGFFT(bin_mask), 'all');
if tmp > 50
angular_intensity(i) = tmp;
else
angular_intensity(i) = 0;
end
end
end
%}