Calculations/Data-Analyzer/analyzePhaseTransitionSimulation.m

555 lines
20 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.

%% Extract Images
baseDir = 'D:/Results - Numerics/Data_Full3D/PhaseTransition/DTS/';
JobNumber = 0;
runFolder = sprintf('Run_%03d', JobNumber);
movieFileName = 'DropletsToStripes.mp4'; % Output file name
datafileName = './DropletsToStripes.mat';
reverseOrder = false; % Set this to true to reverse the theta ordering
TitleString = 'Change across transition: Droplets To Stripes';
%%
baseDir = 'D:/Results - Numerics/Data_Full3D/PhaseTransition/STD/';
JobNumber = 0;
runFolder = sprintf('Run_%03d', JobNumber);
movieFileName = 'StripesToDroplets.mp4'; % Output file name
datafileName = './StripesToDroplets.mat';
reverseOrder = true; % Set this to true to reverse the theta ordering
TitleString = 'Change across transition: Stripes To Droplets';
%%
folderList = dir(baseDir);
isValid = [folderList.isdir] & ~ismember({folderList.name}, {'.', '..'});
folderNames = {folderList(isValid).name};
nimgs = numel(folderNames);
% Extract theta values from folder names
PolarAngleVals = zeros(1, nimgs);
for k = 1:nimgs
tokens = regexp(folderNames{k}, 'theta_(\d{3})', 'tokens');
if isempty(tokens)
warning('No theta found in folder name: %s', folderNames{k});
PolarAngleVals(k) = NaN;
else
PolarAngleVals(k) = str2double(tokens{1}{1});
end
end
% Choose sort direction
sortDirection = 'ascend';
if reverseOrder
sortDirection = 'descend';
end
% Sort folderNames based on polar angle
[~, sortIdx] = sort(PolarAngleVals, sortDirection);
folderNames = folderNames(sortIdx);
PolarAngleVals = PolarAngleVals(sortIdx); % Optional: if you still want sorted list
imgs = cell(1, nimgs);
alphas = zeros(1, nimgs);
for k = 1:nimgs
folderName = folderNames{k};
SaveDirectory = fullfile(baseDir, folderName, runFolder);
% Extract alpha (theta) again from folder name
tokens = regexp(folderName, 'theta_(\d{3})', 'tokens');
alpha_val = str2double(tokens{1}{1});
alphas(k) = alpha_val;
matPath = fullfile(SaveDirectory, 'psi_gs.mat');
if ~isfile(matPath)
warning('Missing psi_gs.mat in %s', SaveDirectory);
continue;
end
try
Data = load(matPath, 'psi', 'Params', 'Transf', 'Observ');
catch ME
warning('Failed to load %s: %s', matPath, ME.message);
continue;
end
Params = Data.Params;
Transf = Data.Transf;
Observ = Data.Observ;
psi = Data.psi;
if isgpuarray(psi)
psi = gather(psi);
end
if isgpuarray(Observ.residual)
Observ.residual = gather(Observ.residual);
end
% Axes and projection
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
n = abs(psi).^2;
nxy = squeeze(trapz(n * dz, 3));
imgs{k} = nxy;
end
%% Analyze Images
makeMovie = true; % Set to false to disable movie creation
font = 'Bahnschrift';
skipPreprocessing = true;
skipMasking = true;
skipIntensityThresholding = true;
skipBinarization = true;
% Run Fourier analysis over images
fft_imgs = cell(1, nimgs);
spectral_contrast = zeros(1, nimgs);
spectral_weight = zeros(1, nimgs);
g2_all = cell(1, nimgs);
theta_values_all = cell(1, nimgs);
N_bins = 180;
Threshold = 25;
Sigma = 2;
if makeMovie
% Create VideoWriter object for movie
videoFile = VideoWriter(movieFileName, 'MPEG-4');
videoFile.Quality = 100; % Set quality to maximum (0–100)
videoFile.FrameRate = 2; % Set the frame rate (frames per second)
open(videoFile); % Open the video file to write
end
% Display the cropped image
for k = 1:nimgs
IMG = imgs{k};
[IMGFFT, IMGPR] = computeFourierTransform(IMG, skipPreprocessing, skipMasking, skipIntensityThresholding, skipBinarization);
[theta_vals, S_theta] = computeNormalizedAngularSpectralDistribution(IMGFFT, 10, 35, N_bins, Threshold, Sigma);
g2 = 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(dtheta+1) = num / denom - 1;
end
g2_all{k} = g2;
theta_values_all{k} = theta_vals;
figure(1);
clf
set(gcf,'Position',[500 100 1000 800])
t = tiledlayout(2, 2, 'TileSpacing', 'compact', 'Padding', 'compact'); % 1x4 grid
y_min = min(y);
y_max = max(y);
x_min = min(x);
x_max = max(x);
% Display the cropped OD image
ax1 = nexttile;
imagesc(x, y, 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(alphas(k), '%.1f')], ...
'Color', 'white', 'FontWeight', 'bold', 'Interpreter', 'tex', 'FontSize', 20, 'Units', 'normalized', 'HorizontalAlignment', 'right', 'VerticalAlignment', 'top');
axis square;
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('Density', '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
% Plot the power spectrum
ax2 = nexttile;
[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);
imagesc(log(1 + abs(fft_imgs{k}).^2));
% 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
axis square;
hcb = colorbar;
colormap(ax2, '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)', '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
spectral_contrast(k) = computeSpectralContrast(fft_imgs{k}, 10, 25, Threshold);
[theta_vals, S_theta] = computeNormalizedAngularSpectralDistribution(fft_imgs{k}, 10, 25, N_bins, Threshold, Sigma);
spectral_weight(k) = trapz(theta_vals, S_theta);
plot(theta_vals/pi, S_theta,'Linewidth',2);
axis square;
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, hText], '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, '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;
if makeMovie
frame = getframe(gcf);
writeVideo(videoFile, frame);
else
pause(0.5); % Only pause when not recording
end
end
if makeMovie
close(videoFile);
disp(['Movie saved to ', movieFileName]);
end
%% Track across the transition
figure(2);
set(gcf,'Position',[100 100 950 750])
plot(alphas, spectral_contrast, 'o--', ...
'LineWidth', 1.5, 'MarkerSize', 6);
set(gca, 'FontSize', 14); % For tick labels only
hXLabel = xlabel('\alpha (degrees)', 'Interpreter', 'tex');
% hXLabel = xlabel('B_z (G)', 'Interpreter', 'tex');
hYLabel = ylabel('Spectral Contrast', 'Interpreter', 'tex');
hTitle = title(TitleString, '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
figure(3);
set(gcf,'Position',[100 100 950 750])
plot(alphas, spectral_weight, 'o--', ...
'LineWidth', 1.5, 'MarkerSize', 6);
set(gca, 'FontSize', 14); % For tick labels only
hXLabel = xlabel('\alpha (degrees)', 'Interpreter', 'tex');
% hXLabel = xlabel('B_z (G)', 'Interpreter', 'tex');
hYLabel = ylabel('Spectral Weight', 'Interpreter', 'tex');
hTitle = title(TitleString, '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
save(datafileName, 'alphas', 'spectral_contrast', 'spectral_weight');
figure(4);
clf;
set(gcf,'Position',[100 100 950 750])
hold on;
% Reconstruct theta axis from any one of the stored values
theta_vals = theta_values_all{1}; % assuming it's in radians
legend_entries = cell(nimgs, 1);
% Generate a colormap with enough unique colors
cmap = sky(nimgs); % You can also try 'jet', 'turbo', 'hot', etc.
for i = 1:nimgs
plot(theta_vals/pi, g2_all{i}, ...
'o-', 'Color', cmap(i,:), 'LineWidth', 1.2, ...
'MarkerSize', 5);
legend_entries{i} = sprintf('$\\alpha = %g^\\circ$', alphas(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(TitleString, '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;
%% Track across the transition
set(0,'defaulttextInterpreter','latex')
set(groot, 'defaultAxesTickLabelInterpreter','latex'); set(groot, 'defaultLegendInterpreter','latex');
format long
font = 'Bahnschrift';
% Load data
Data = load('./DropletsToStripes.mat', 'alphas', 'spectral_contrast', 'spectral_weight');
dts_alphas = Data.alphas;
dts_sc = Data.spectral_contrast;
dts_sw = Data.spectral_weight;
Data = load('./StripesToDroplets.mat', 'alphas', 'spectral_contrast', 'spectral_weight');
std_alphas = Data.alphas;
std_sc = Data.spectral_contrast;
std_sw = Data.spectral_weight;
% Normalize dts data
dts_min = min(dts_sw);
dts_max = max(dts_sw);
dts_range = dts_max - dts_min;
dts_sf_norm = (dts_sw - dts_min) / dts_range;
% Normalize std data
std_min = min(std_sw);
std_max = max(std_sw);
std_range = std_max - std_min;
std_sf_norm = (std_sw - std_min) / std_range;
figure(5);
set(gcf,'Position',[100 100 950 750])
plot(dts_alphas, dts_sc, 'o--', 'LineWidth', 1.5, 'MarkerSize', 6, 'DisplayName' , 'Droplets to Stripes');
hold on
plot(std_alphas, std_sc, 'o--', 'LineWidth', 1.5, 'MarkerSize', 6, 'DisplayName' , 'Stripes to Droplets');
set(gca, 'FontSize', 14); % For tick labels only
hXLabel = xlabel('\alpha (degrees)', 'Interpreter', 'tex');
hYLabel = ylabel('Spectral Contrast', 'Interpreter', 'tex');
hTitle = title('Change across transition', 'Interpreter', 'tex');
legend
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
figure(6);
set(gcf,'Position',[100 100 950 750])
plot(dts_alphas, dts_sw, 'o--', 'LineWidth', 1.5, 'MarkerSize', 6, 'DisplayName' , 'Droplets to Stripes');
hold on
plot(std_alphas, std_sw, 'o--', 'LineWidth', 1.5, 'MarkerSize', 6, 'DisplayName' , 'Stripes to Droplets');
set(gca, 'FontSize', 14); % For tick labels only
hXLabel = xlabel('\alpha (degrees)', 'Interpreter', 'tex');
hYLabel = ylabel('Spectral Weight', 'Interpreter', 'tex');
hTitle = title('Change across transition', 'Interpreter', 'tex');
legend
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
%%
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 contrast = computeSpectralContrast(IMGFFT, r_min, r_max, 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);
% Ring region (annulus) mask
ring_mask = (R >= r_min) & (R <= r_max);
% Squared magnitude in the ring
ring_power = abs(IMGFFT).^2 .* ring_mask;
% Maximum power in the ring
ring_max = max(ring_power(:));
% Power at the DC component
dc_power = abs(IMGFFT(cy, cx))^2;
% Avoid division by zero
if dc_power == 0
contrast = Inf; % or NaN or 0, depending on how you want to handle this
else
contrast = ring_max / dc_power;
end
end