Calculations/Data-Analyzer/analyzeFolder.m

703 lines
28 KiB
Matlab
Raw 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.

function results = analyzeFolder(options)
% Ensure required fields are defined in options
arguments
options.scan_parameter (1,:) char
options.scan_groups (1,:) double
options.cam (1,1) double
options.angle (1,1) double
options.center (1,2) double
options.span (1,2) double
options.fraction (1,2) double
options.ImagingMode (1,:) char
options.PulseDuration (1,1) double
options.removeFringes (1,1) logical
options.skipUnshuffling (1,1) logical
options.pixel_size (1,1) double
options.magnification (1,1) double
options.zoom_size (1,1) double
options.r_min (1,1) double
options.r_max (1,1) double
options.N_angular_bins (1,1) double
options.Angular_Threshold (1,1) double
options.Angular_Sigma (1,1) double
options.Angular_WindowSize (1,1) double
options.theta_min (1,1) double
options.theta_max (1,1) double
options.N_radial_bins (1,1) double
options.Radial_Sigma (1,1) double
options.Radial_WindowSize (1,1) double
options.k_min (1,1) double
options.k_max (1,1) double
options.skipPreprocessing (1,1) logical
options.skipMasking (1,1) logical
options.skipIntensityThresholding (1,1) logical
options.skipBinarization (1,1) logical
options.folderPath (1,:) char
end
% Assign variables from options
scan_parameter = options.scan_parameter;
scan_groups = options.scan_groups;
folderPath = options.folderPath;
center = options.center;
span = options.span;
fraction = options.fraction;
ImagingMode = options.ImagingMode;
PulseDuration = options.PulseDuration;
removeFringes = options.removeFringes;
skipUnshuffling = options.skipUnshuffling;
pixel_size = options.pixel_size;
magnification = options.magnification;
zoom_size = options.zoom_size;
r_min = options.r_min;
r_max = options.r_max;
N_angular_bins = options.N_angular_bins;
Angular_Threshold = options.Angular_Threshold;
Angular_Sigma = options.Angular_Sigma;
Angular_WindowSize = options.Angular_WindowSize;
theta_min = options.theta_min;
theta_max = options.theta_max;
N_radial_bins = options.N_radial_bins;
Radial_Sigma = options.Radial_Sigma;
Radial_WindowSize = options.Radial_WindowSize;
k_min = options.k_min;
k_max = options.k_max;
skipPreprocessing = options.skipPreprocessing;
skipMasking = options.skipMasking;
skipIntensityThresholding = options.skipIntensityThresholding;
skipBinarization = options.skipBinarization;
cam = options.cam;
angle = options.angle;
% Load images and analyze them (keep using the cleaned body of your original function)
% Fix the incorrect usage of 'cam' and 'angle' not defined locally
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"];
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, ImagingMode, PulseDuration), 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
% ===== Unshuffle if necessary to do so =====
if ~skipUnshuffling
n_values = length(scan_groups);
n_total = length(scan_parameter_values);
% Infer number of repetitions
n_reps = n_total / n_values;
% Preallocate ordered arrays
ordered_scan_values = zeros(1, n_total);
ordered_od_imgs = cell(1, n_total);
counter = 1;
for rep = 1:n_reps
for val = scan_groups
% Find the next unused match for this val
idx = find(scan_parameter_values == val, 1, 'first');
% Assign and remove from list to avoid duplicates
ordered_scan_values(counter) = scan_parameter_values(idx);
ordered_od_imgs{counter} = od_imgs{idx};
% Mark as used by removing
scan_parameter_values(idx) = NaN; % NaN is safe since original values are 0:5:45
od_imgs{idx} = []; % empty cell so it won't be matched again
counter = counter + 1;
end
end
% Now assign back
scan_parameter_values = ordered_scan_values;
od_imgs = ordered_od_imgs;
end
% Extract quantities
fft_imgs = cell(1, nimgs);
spectral_distribution = cell(1, nimgs);
theta_values = cell(1, nimgs);
radial_spectral_contrast = zeros(1, nimgs);
angular_spectral_weight = zeros(1, nimgs);
N_shots = length(od_imgs);
for k = 1:N_shots
IMG = od_imgs{k};
if ~(max(IMG(:)) > 1)
IMGFFT = NaN(size(IMG));
else
[IMGFFT, IMGPR] = computeFourierTransform(IMG, skipPreprocessing, skipMasking, skipIntensityThresholding, skipBinarization);
end
% Size of original image (in pixels)
[Ny, Nx] = size(IMG);
% Real-space pixel size in micrometers after magnification
dx = pixel_size / magnification;
dy = dx; % assuming square pixels
% Real-space axes
x = ((1:Nx) - ceil(Nx/2)) * dx * 1E6;
y = ((1:Ny) - ceil(Ny/2)) * dy * 1E6;
% Reciprocal space increments (frequency domain, μm⁻¹)
dvx = 1 / (Nx * dx);
dvy = 1 / (Ny * dy);
% Frequency axes
vx = (-floor(Nx/2):ceil(Nx/2)-1) * dvx;
vy = (-floor(Ny/2):ceil(Ny/2)-1) * dvy;
% Wavenumber axes
kx_full = 2 * pi * vx * 1E-6; % μm⁻¹
ky_full = 2 * pi * vy * 1E-6;
% Crop FFT image around center
mid_x = floor(Nx/2);
mid_y = floor(Ny/2);
fft_imgs{k} = IMGFFT(mid_y-zoom_size:mid_y+zoom_size, mid_x-zoom_size:mid_x+zoom_size);
% Crop wavenumber axes to match fft_imgs{k}
kx = kx_full(mid_x - zoom_size : mid_x + zoom_size);
ky = ky_full(mid_y - zoom_size : mid_y + zoom_size);
[theta_vals, S_theta] = computeAngularSpectralDistribution(fft_imgs{k}, kx, ky, k_min, k_max, N_angular_bins, Angular_Threshold, Angular_Sigma, []);
[k_rho_vals, S_k] = computeRadialSpectralDistribution(fft_imgs{k}, kx, ky, theta_min, theta_max, N_radial_bins);
S_k_smoothed = movmean(S_k, Radial_WindowSize); % Compute moving average (use convolution) or use conv for more control
spectral_distribution{k} = S_theta;
theta_values{k} = theta_vals;
radial_spectral_contrast(k) = computeRadialSpectralContrast(k_rho_vals, S_k_smoothed, k_min, k_max);
S_theta_norm = S_theta / max(S_theta); % Normalize to 1
angular_spectral_weight(k) = trapz(theta_vals, S_theta_norm);
end
% Assuming scan_parameter_values and spectral_weight are column vectors (or row vectors of same length)
[unique_scan_parameter_values, ~, idx] = unique(scan_parameter_values);
% Preallocate arrays
mean_rsc = zeros(size(unique_scan_parameter_values));
stderr_rsc = zeros(size(unique_scan_parameter_values));
% Loop through each unique theta and compute mean and standard error
for i = 1:length(unique_scan_parameter_values)
group_vals = radial_spectral_contrast(idx == i);
mean_rsc(i) = mean(group_vals, 'omitnan');
stderr_rsc(i) = std(group_vals, 'omitnan') / sqrt(length(group_vals)); % standard error = std / sqrt(N)
end
% Preallocate arrays
mean_asw = zeros(size(unique_scan_parameter_values));
stderr_asw = zeros(size(unique_scan_parameter_values));
% Loop through each unique theta and compute mean and standard error
for i = 1:length(unique_scan_parameter_values)
group_vals = angular_spectral_weight(idx == i);
mean_asw(i) = mean(group_vals, 'omitnan');
stderr_asw(i) = std(group_vals, 'omitnan') / sqrt(length(group_vals)); % standard error = std / sqrt(N)
end
% Convert spectral distribution to matrix (N_shots x N_angular_bins)
delta_nkr_all = zeros(N_shots, N_angular_bins);
for k = 1:N_shots
delta_nkr_all(k, :) = spectral_distribution{k};
end
% Group by scan parameter values (e.g., alpha, angle, etc.)
[unique_scan_parameter_values, ~, idx] = unique(scan_parameter_values);
N_params = length(unique_scan_parameter_values);
% Define angular range and conversion
angle_range = 180;
angle_per_bin = angle_range / N_angular_bins;
max_peak_angle = 180;
max_peak_bin = round(max_peak_angle / angle_per_bin);
% Parameters for search
window_size = 10;
angle_threshold = 100;
% Initialize containers for final results
mean_max_g2_values = zeros(1, N_params);
mean_max_g2_angle_values = zeros(1, N_params);
var_max_g2_values = zeros(1, N_params);
var_max_g2_angle_values = zeros(1, N_params);
std_error_g2_values = zeros(1, N_params);
% Also store raw data per group
g2_all_per_group = cell(1, N_params);
angle_all_per_group = cell(1, N_params);
for i = 1:N_params
group_idx = find(idx == i);
group_data = delta_nkr_all(group_idx, :);
N_reps = size(group_data, 1);
g2_values = zeros(1, N_reps);
angle_at_max_g2 = zeros(1, N_reps);
for j = 1:N_reps
profile = group_data(j, :);
% Restrict search to 060° for highest peak
restricted_profile = profile(1:max_peak_bin);
[~, peak_idx_rel] = max(restricted_profile);
peak_idx = peak_idx_rel;
peak_angle = (peak_idx - 1) * angle_per_bin;
if peak_angle < angle_threshold
offsets = round(50 / angle_per_bin) : round(70 / angle_per_bin);
else
offsets = -round(70 / angle_per_bin) : -round(50 / angle_per_bin);
end
ref_window = mod((peak_idx - window_size):(peak_idx + window_size) - 1, N_angular_bins) + 1;
ref = profile(ref_window);
correlations = zeros(size(offsets));
angles = zeros(size(offsets));
for k = 1:length(offsets)
shifted_idx = mod(peak_idx + offsets(k) - 1, N_angular_bins) + 1;
sec_window = mod((shifted_idx - window_size):(shifted_idx + window_size) - 1, N_angular_bins) + 1;
sec = profile(sec_window);
num = mean(ref .* sec, 'omitnan');
denom = mean(ref.^2, 'omitnan');
g2 = num / denom;
correlations(k) = g2;
angles(k) = mod((peak_idx - 1 + offsets(k)) * angle_per_bin, angle_range);
end
[max_corr, max_idx] = max(correlations);
g2_values(j) = max_corr;
angle_at_max_g2(j) = angles(max_idx);
end
% Store raw values
g2_all_per_group{i} = g2_values;
angle_all_per_group{i} = angle_at_max_g2;
% Final stats
mean_max_g2_values(i) = mean(g2_values, 'omitnan');
var_max_g2_values(i) = var(g2_values, 0, 'omitnan');
mean_max_g2_angle_values(i)= mean(angle_at_max_g2, 'omitnan');
var_max_g2_angle_values(i) = var(angle_at_max_g2, 0, 'omitnan');
n_i = numel(g2_all_per_group{i}); % Number of repetitions for this param
std_error_g2_values(i) = sqrt(var_max_g2_values(i) / n_i);
end
results.folderPath = folderPath;
results.scan_parameter = scan_parameter;
results.scan_groups = scan_groups;
results.mean_max_g2_values = mean_max_g2_values;
results.std_error_g2_values = std_error_g2_values;
results.mean_max_g2_angle = mean_max_g2_angle_values;
results.radial_spectral_contrast= mean_rsc;
results.angular_spectral_weight = mean_asw;
end
%% 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 [k_rho_vals, S_radial] = computeRadialSpectralDistribution(IMGFFT, kx, ky, thetamin, thetamax, num_bins)
% IMGFFT : 2D FFT image (fftshifted and cropped)
% kx, ky : 1D physical wavenumber axes [μm⁻¹] matching FFT size
% thetamin : Minimum angle (in radians)
% thetamax : Maximum angle (in radians)
% num_bins : Number of radial bins
[KX, KY] = meshgrid(kx, ky);
K_rho = sqrt(KX.^2 + KY.^2);
Theta = atan2(KY, KX);
if thetamin < thetamax
angle_mask = (Theta >= thetamin) & (Theta <= thetamax);
else
angle_mask = (Theta >= thetamin) | (Theta <= thetamax);
end
power_spectrum = abs(IMGFFT).^2;
r_min = min(K_rho(angle_mask));
r_max = max(K_rho(angle_mask));
r_edges = linspace(r_min, r_max, num_bins + 1);
k_rho_vals = 0.5 * (r_edges(1:end-1) + r_edges(2:end));
S_radial = zeros(1, num_bins);
for i = 1:num_bins
r_low = r_edges(i);
r_high = r_edges(i + 1);
radial_mask = (K_rho >= r_low) & (K_rho < r_high);
full_mask = radial_mask & angle_mask;
S_radial(i) = sum(power_spectrum(full_mask));
end
end
function [theta_vals, S_theta] = computeAngularSpectralDistribution(IMGFFT, kx, ky, k_min, k_max, num_bins, threshold, sigma, windowSize)
% Apply threshold to isolate strong peaks
IMGFFT(IMGFFT < threshold) = 0;
% Create wavenumber meshgrid
[KX, KY] = meshgrid(kx, ky);
Kmag = sqrt(KX.^2 + KY.^2); % radial wavenumber magnitude
Theta = atan2(KY, KX); % range [-pi, pi]
% Restrict to radial band in wavenumber space
radial_mask = (Kmag >= k_min) & (Kmag <= k_max);
% Initialize angular structure factor
S_theta = zeros(1, num_bins);
theta_vals = linspace(0, pi, num_bins); % only 0 to pi due to symmetry
% Loop over angular bins
for i = 1:num_bins
angle_start = (i - 1) * pi / num_bins;
angle_end = i * pi / num_bins;
angle_mask = (Theta >= angle_start) & (Theta < angle_end);
bin_mask = radial_mask & angle_mask;
fft_angle = IMGFFT .* bin_mask;
S_theta(i) = sum(sum(abs(fft_angle).^2));
end
% Optional smoothing
if exist('sigma', 'var') && ~isempty(sigma)
% Gaussian smoothing
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);
% Circular convolution
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);
elseif exist('windowSize', 'var') && ~isempty(windowSize)
% Moving average smoothing
pad = floor(windowSize / 2);
kernel = ones(1, windowSize) / windowSize;
S_theta = conv([S_theta(end - pad + 1:end), S_theta, S_theta(1:pad)], kernel, 'same');
S_theta = S_theta(pad + 1:end - pad);
end
end
function contrast = computeRadialSpectralContrast(k_rho_vals, S_k_smoothed, k_min, k_max)
% Computes the ratio of the peak in S_k_smoothed within [k_min, k_max]
% to the value at (or near) k = 0.
% Ensure inputs are column vectors
k_rho_vals = k_rho_vals(:);
S_k_smoothed = S_k_smoothed(:);
% Step 1: Find index of k ≈ 0
[~, idx_k0] = min(abs(k_rho_vals)); % Closest to zero
S_k0 = S_k_smoothed(idx_k0);
% Step 2: Find indices in specified k-range
in_range = (k_rho_vals >= k_min) & (k_rho_vals <= k_max);
if ~any(in_range)
warning('No values found in the specified k-range. Returning NaN.');
contrast = NaN;
return;
end
% Step 3: Find peak value in the specified k-range
S_k_peak = max(S_k_smoothed(in_range));
% Step 4: Compute contrast
contrast = S_k_peak / S_k0;
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 imageOD = calculateODImage(imageAtom, imageBackground, imageDark, mode, exposureTime)
%CALCULATEODIMAGE Calculates the optical density (OD) image for absorption imaging.
%
% imageOD = calculateODImage(imageAtom, imageBackground, imageDark, mode, exposureTime)
%
% Inputs:
% imageAtom - Image with atoms
% imageBackground - Image without atoms
% imageDark - Image without light
% mode - 'LowIntensity' (default) or 'HighIntensity'
% exposureTime - Required only for 'HighIntensity' [in seconds]
%
% Output:
% imageOD - Computed OD image
%
arguments
imageAtom (:,:) {mustBeNumeric}
imageBackground (:,:) {mustBeNumeric}
imageDark (:,:) {mustBeNumeric}
mode char {mustBeMember(mode, {'LowIntensity', 'HighIntensity'})} = 'LowIntensity'
exposureTime double = NaN
end
% Compute numerator and denominator
numerator = imageBackground - imageDark;
denominator = imageAtom - imageDark;
% Avoid division by zero
numerator(numerator == 0) = 1;
denominator(denominator == 0) = 1;
% Calculate OD based on mode
switch mode
case 'LowIntensity'
imageOD = -log(abs(denominator ./ numerator));
case 'HighIntensity'
if isnan(exposureTime)
error('Exposure time must be provided for HighIntensity mode.');
end
imageOD = abs(denominator ./ numerator);
imageOD = -log(imageOD) + (numerator - denominator) ./ (7000 * (exposureTime / 5e-6));
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