661 lines
31 KiB
Matlab
661 lines
31 KiB
Matlab
function [od_imgs, scan_parameter_values, scan_reference_values, file_list] = collectODImages(options)
|
|
%% collectODImages
|
|
% Author: Karthik
|
|
% Date: 2025-09-12
|
|
% Version: 1.0
|
|
%
|
|
% Description:
|
|
% Applies cropping, background subtraction, and optional fringe removal, optional unshuffling on OD image dataset.
|
|
% Automatically reuses in-memory full dataset if available otherwise, reads and processes raw HDF5 data.
|
|
%
|
|
% Inputs:
|
|
% options - structure containing processing options:
|
|
% .folderPath : path to raw HDF5 files
|
|
% .saveDirectory : path to save cache (if needed)
|
|
% .cam, .angle : camera selection and rotation angle
|
|
% .ImagingMode, .PulseDuration : imaging parameters
|
|
% .scan_parameter : name of scan parameter
|
|
% .center, .span : cropping settings
|
|
% .fraction : background subtraction fraction
|
|
% .removeFringes : logical flag for fringe removal
|
|
% .skipUnshuffling : logical flag to skip unshuffling
|
|
% .scan_reference_values: reference values for unshuffling
|
|
%
|
|
% Outputs:
|
|
% od_imgs : cell array of processed OD images
|
|
% scan_parameter_values: vector of scan parameter values
|
|
% file_list : cell array of file names
|
|
%
|
|
% Notes:
|
|
% Optional notes, references.
|
|
|
|
% --- Early exit if processed OD images already exist AND options match ---
|
|
reuseExistingODImages = evalin('base', ...
|
|
'exist(''od_imgs'',''var'') && exist(''scan_parameter_values'',''var'') && exist(''file_list'',''var'') && exist(''prior_options'',''var'')');
|
|
|
|
if reuseExistingODImages
|
|
prior_options = evalin('base','prior_options');
|
|
critical_fields = {'folderPath','cam','angle','ImagingMode','PulseDuration',...
|
|
'center','span','fraction','removeFringes','skipUnshuffling','scan_reference_values'};
|
|
|
|
if ~haveOptionsChanged(options, prior_options, critical_fields)
|
|
fprintf('\n[INFO] Reusing processed OD images, scan parameters, and file list from memory.\n');
|
|
od_imgs = evalin('base','od_imgs');
|
|
scan_parameter_values = evalin('base','scan_parameter_values');
|
|
scan_reference_values = evalin('base','scan_reference_values');
|
|
file_list = evalin('base','file_list');
|
|
|
|
% --- Save OD images to disk if requested ---
|
|
if ~options.skipSaveProcessedOD
|
|
saveProcessedOD(od_imgs, options);
|
|
end
|
|
|
|
return; % ✅ bypass cropping/background subtraction
|
|
else
|
|
fprintf('\n[INFO] Processed-data-related options changed. Reprocessing full OD image dataset...\n');
|
|
end
|
|
end
|
|
|
|
% --- General path to full od image folders ---
|
|
if isfield(options, 'FullODImagesFolder')
|
|
full_od_image_parent_folder = options.FullODImagesFolder;
|
|
elseif isfield(options, 'saveDirectory')
|
|
full_od_image_parent_folder = options.saveDirectory;
|
|
else
|
|
full_od_image_parent_folder = '';
|
|
end
|
|
|
|
% --- Specific sequence, data and run ---
|
|
dataSource = makeDataSourceStruct(options.folderPath);
|
|
|
|
% --- Check if workspace full dataset exists ---
|
|
fullDataExists = evalin('base', 'exist(''full_od_imgs'', ''var'')') && ...
|
|
evalin('base', 'exist(''full_bkg_imgs'', ''var'')') && ...
|
|
evalin('base', 'exist(''raw_scan_parameter_values'', ''var'')') && ...
|
|
evalin('base', 'exist(''raw_file_list'', ''var'')') && ...
|
|
evalin('base', 'exist(''prior_options'',''var'')');
|
|
|
|
if ~isfield(options,'SAVE_TO_WORKSPACE')
|
|
[options.SAVE_TO_WORKSPACE, ~] = Helper.estimateDatasetMemory(dataSource, options);
|
|
end
|
|
|
|
full_od_image_subfolder = [];
|
|
|
|
% --- Prepare full_od_imgs, full_bkg_imgs, scan values, file list ---
|
|
if fullDataExists
|
|
% --- Case 1: Already in workspace ---
|
|
fprintf('\n[INFO] Reusing full OD image dataset and scan parameters from memory.\n');
|
|
full_od_imgs = evalin('base','full_od_imgs');
|
|
full_bkg_imgs = evalin('base','full_bkg_imgs');
|
|
raw_scan_parameter_values = evalin('base','raw_scan_parameter_values');
|
|
raw_file_list = evalin('base','raw_file_list');
|
|
nFiles = size(full_od_imgs,3);
|
|
fprintf('\n[INFO] Cropping and subtracting background from images...\n');
|
|
|
|
else
|
|
matched = false;
|
|
useFullODFolders = ~isfield(options,'skipFullODImagesFolderUse') || ~options.skipFullODImagesFolderUse;
|
|
|
|
% --- If user provided a selected path and it is a folder, disambiguate its meaning ---
|
|
if isfield(options,'selectedPath') && isfolder(options.selectedPath)
|
|
selPath = options.selectedPath;
|
|
|
|
% --- Determine if selectedPath looks like a FullODImages folder ---
|
|
selIsFullOD = false;
|
|
if isfile(fullfile(selPath,'metadata.mat'))
|
|
selIsFullOD = true;
|
|
else
|
|
% exact match to any discovered fullodimage_folders entry
|
|
if isfolder(full_od_image_parent_folder) && ~isempty(full_od_image_parent_folder)
|
|
for r = 1:numel(full_od_image_parent_folder)
|
|
if strcmp(full_od_image_parent_folder, selPath)
|
|
selIsFullOD = true;
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if selIsFullOD && useFullODFolders
|
|
% --- selectedPath is explicitly the full OD images folder ---
|
|
full_od_image_subfolder = selPath;
|
|
matched = true;
|
|
fprintf('\n[INFO] Using selected full OD images subfolder: %s\n', full_od_image_subfolder);
|
|
|
|
else
|
|
% --- selectedPath appears to be a raw-data folder (not a full-OD folder) ---
|
|
fprintf('\n[INFO] Selected path appears to be raw data: %s\n', selPath);
|
|
|
|
% If user forces recompute -> recompute from selected raw path
|
|
if isfield(options,'skipFullODImagesFolderUse') && options.skipFullODImagesFolderUse
|
|
[full_od_imgs, full_bkg_imgs, raw_scan_parameter_values, raw_file_list, raw_scan_parameter_names, scan_reference_values] = recomputeODImages(options, selPath);
|
|
if ~options.SAVE_TO_WORKSPACE
|
|
% --- Determine parent folder for FullODImages ---
|
|
if isfield(options, 'FullODImagesFolder') && ...
|
|
isfolder(options.FullODImagesFolder) && ...
|
|
~isempty(options.FullODImagesFolder)
|
|
parentFolder = options.FullODImagesFolder;
|
|
else
|
|
parentFolder = options.saveDirectory;
|
|
end
|
|
dataSource = makeDataSourceStruct(options.folderPath);
|
|
full_od_image_subfolder = createFullODImagesFolderPath(parentFolder, dataSource);
|
|
useFullODFolders = true;
|
|
else
|
|
nFiles = numel(raw_file_list);
|
|
end
|
|
matched = true;
|
|
else
|
|
% Try to find an existing full-OD folder whose metadata references this raw path
|
|
found = false;
|
|
if isfolder(full_od_image_parent_folder) && ~isempty(full_od_image_parent_folder) && useFullODFolders
|
|
for r = 1:numel(full_od_image_parent_folder)
|
|
metaPath = fullfile(full_od_image_parent_folder(r).folder, full_od_image_parent_folder(r).name, 'metadata.mat');
|
|
if ~isfile(metaPath), continue; end
|
|
S = load(metaPath,'metadata');
|
|
% Compare data source from metadata to the selected raw path
|
|
mdDataSource = makeDataSourceStruct(S.metadata.options.folderPath);
|
|
selDataSource = makeDataSourceStruct(selPath);
|
|
if isfield(S.metadata.options,'measurementName') && isfield(options,'measurementName') && ...
|
|
strcmp(S.metadata.options.measurementName, options.measurementName) && ...
|
|
isequal(mdDataSource, selDataSource)
|
|
full_od_image_subfolder = fullfile(full_od_image_parent_folder(r).folder, full_od_image_parent_folder(r).name);
|
|
matched = true;
|
|
found = true;
|
|
fprintf('\n[INFO] Found matching full OD images subfolder for selected raw path: %s\n', full_od_image_parent_folder(r).name);
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
|
|
% If no matching full-OD folder found, recompute from the selected raw path
|
|
if ~found
|
|
fprintf('\n[INFO] Forcing recompute from raw data as no matching full OD images subfolder found (Use skipFullODImagesFolderUse=true to skip directly to computing from raw data).\n');
|
|
[full_od_imgs, full_bkg_imgs, raw_scan_parameter_values, raw_file_list, raw_scan_parameter_names, scan_reference_values] = recomputeODImages(options, selPath);
|
|
if ~options.SAVE_TO_WORKSPACE
|
|
% --- Determine parent folder for FullODImages ---
|
|
if isfield(options, 'FullODImagesFolder') && ...
|
|
isfolder(options.FullODImagesFolder) && ...
|
|
~isempty(options.FullODImagesFolder)
|
|
parentFolder = options.FullODImagesFolder;
|
|
elseif isfield(options, 'saveDirectory') && isfolder(options.saveDirectory)
|
|
parentFolder = options.saveDirectory;
|
|
end
|
|
dataSource = makeDataSourceStruct(options.folderPath);
|
|
full_od_image_subfolder = createFullODImagesFolderPath(parentFolder, dataSource);
|
|
useFullODFolders = true;
|
|
else
|
|
nFiles = numel(raw_file_list);
|
|
end
|
|
matched = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
else
|
|
% --- No selected path: either force recompute or search among fullodimage_folders ---
|
|
if isfield(options,'skipFullODImagesFolderUse') && options.skipFullODImagesFolderUse
|
|
[full_od_imgs, full_bkg_imgs, raw_scan_parameter_values, raw_file_list, raw_scan_parameter_names, scan_reference_values] = recomputeODImages(options, options.baseDataFolder);
|
|
if ~options.SAVE_TO_WORKSPACE
|
|
% --- Determine parent folder for FullODImages ---
|
|
if isfield(options, 'FullODImagesFolder') && ...
|
|
isfolder(options.FullODImagesFolder) && ...
|
|
~isempty(options.FullODImagesFolder)
|
|
parentFolder = options.FullODImagesFolder;
|
|
elseif isfield(options, 'saveDirectory') && isfolder(options.saveDirectory)
|
|
parentFolder = options.saveDirectory;
|
|
end
|
|
dataSource = makeDataSourceStruct(options.folderPath);
|
|
full_od_image_subfolder = createFullODImagesFolderPath(parentFolder, dataSource);
|
|
useFullODFolders = true;
|
|
else
|
|
nFiles = numel(raw_file_list);
|
|
end
|
|
matched = true;
|
|
else
|
|
% Search for existing matching full-OD folder based on options.folderPath
|
|
if isfolder(full_od_image_parent_folder) && ~isempty(full_od_image_parent_folder) && useFullODFolders
|
|
for r = 1:numel(full_od_image_parent_folder)
|
|
metaPath = fullfile(full_od_image_parent_folder(r).folder, full_od_image_parent_folder(r).name,'metadata.mat');
|
|
if ~isfile(metaPath), continue; end
|
|
S = load(metaPath,'metadata');
|
|
|
|
mdDataSource = makeDataSourceStruct(S.metadata.options.folderPath);
|
|
currentDataSource = makeDataSourceStruct(options.folderPath);
|
|
|
|
if isfield(S.metadata.options,'measurementName') && isfield(options,'measurementName') && ...
|
|
strcmp(S.metadata.options.measurementName, options.measurementName) && ...
|
|
isequal(mdDataSource, currentDataSource)
|
|
full_od_image_subfolder = fullfile(full_od_image_parent_folder(r).folder, full_od_image_parent_folder(r).name);
|
|
matched = true;
|
|
fprintf('\n[INFO] Found matching full OD images subfolder: %s\n', full_od_image_parent_folder(r).name);
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
% --- If still not matched, recompute from raw (fallback) ---
|
|
if ~matched
|
|
if ~isfolder(full_od_image_parent_folder) && useFullODFolders
|
|
fprintf('\n[INFO] No full OD images found in workspace or on disk. Will recompute from raw data.\n');
|
|
elseif isfolder(full_od_image_parent_folder) && useFullODFolders
|
|
fprintf('\n[INFO] No matching full OD images subfolder found. Will recompute from raw data.\n');
|
|
end
|
|
[full_od_imgs, full_bkg_imgs, raw_scan_parameter_values, raw_file_list, raw_scan_parameter_names, scan_reference_values] = recomputeODImages(options, options.baseDataFolder);
|
|
if ~options.SAVE_TO_WORKSPACE
|
|
% --- Determine parent folder for FullODImages ---
|
|
if isfield(options, 'FullODImagesFolder') && ...
|
|
isfolder(options.FullODImagesFolder) && ...
|
|
~isempty(options.FullODImagesFolder)
|
|
parentFolder = options.FullODImagesFolder;
|
|
elseif isfield(options, 'saveDirectory') && isfolder(options.saveDirectory)
|
|
parentFolder = options.saveDirectory;
|
|
end
|
|
dataSource = makeDataSourceStruct(options.folderPath);
|
|
full_od_image_subfolder = createFullODImagesFolderPath(parentFolder, dataSource);
|
|
useFullODFolders = true;
|
|
else
|
|
nFiles = numel(raw_file_list);
|
|
end
|
|
end
|
|
|
|
% --- If a folder was determined, load its contents (listing) ---
|
|
if ~isempty(full_od_image_subfolder) && useFullODFolders
|
|
if isfolder(full_od_image_subfolder)
|
|
[mat_files, raw_scan_parameter_names, raw_scan_parameter_values, raw_file_list, nFiles] = prepareFromOnDiskData(full_od_image_subfolder);
|
|
fprintf('\n[INFO] Cropping and subtracting background from images in full OD images folder on disk...\n');
|
|
end
|
|
end
|
|
end
|
|
|
|
% --- Unified cropping & background subtraction ---
|
|
absimages = zeros(options.span(1)+1, options.span(2)+1, nFiles, 'single');
|
|
refimages = zeros(options.span(1)+1, options.span(2)+1, nFiles, 'single');
|
|
|
|
showPB = isfield(options,'showProgressBar') && options.showProgressBar;
|
|
if showPB
|
|
pb = Helper.ProgressBar();
|
|
pb.run('Progress: ');
|
|
end
|
|
|
|
for k = 1:nFiles
|
|
if fullDataExists || ~isempty(full_od_image_subfolder)
|
|
if fullDataExists
|
|
od_mat = full_od_imgs(:,:,k);
|
|
bkg_mat = full_bkg_imgs(:,:,k);
|
|
else
|
|
data = load(fullfile(full_od_image_subfolder, mat_files(k).name));
|
|
od_mat = data.OD;
|
|
bkg_mat = data.BKG;
|
|
|
|
% --- Handle parameter names ---
|
|
raw_scan_parameter_names{k} = data.Scan_Param;
|
|
|
|
% --- Handle parameter values ---
|
|
if numel(data.Scan_Val) == 1
|
|
% First time through: initialize numeric storage if empty
|
|
if isempty(raw_scan_parameter_values)
|
|
raw_scan_parameter_values = zeros(1,nFiles);
|
|
elseif iscell(raw_scan_parameter_values)
|
|
error('Mixed single-parameter and multi-parameter scans detected.');
|
|
end
|
|
raw_scan_parameter_values(k) = data.Scan_Val;
|
|
|
|
else
|
|
% First time through: initialize cell storage if empty
|
|
if isempty(raw_scan_parameter_values)
|
|
raw_scan_parameter_values = cell(nFiles,1);
|
|
elseif isnumeric(raw_scan_parameter_values)
|
|
error('Mixed single-parameter and multi-parameter scans detected.');
|
|
end
|
|
raw_scan_parameter_values{k} = data.Scan_Val(:).';
|
|
end
|
|
|
|
raw_file_list(k) = data.File;
|
|
end
|
|
else
|
|
od_mat = full_od_imgs(:,:,k);
|
|
bkg_mat = full_bkg_imgs(:,:,k);
|
|
end
|
|
|
|
% --- Crop and subtract background ---
|
|
if any(isnan(od_mat(:)))
|
|
absimages(:,:,k) = nan(options.span(1)+1, options.span(2)+1, 'single');
|
|
else
|
|
cropped_od = Helper.cropODImage(od_mat, options.center, options.span);
|
|
absimages(:,:,k) = Helper.subtractBackgroundOffset(cropped_od, options.fraction)';
|
|
end
|
|
|
|
if any(isnan(bkg_mat(:)))
|
|
refimages(:,:,k) = nan(options.span(1)+1, options.span(2)+1, 'single');
|
|
else
|
|
cropped_bkg = Helper.cropODImage(bkg_mat, options.center, options.span);
|
|
refimages(:,:,k) = Helper.subtractBackgroundOffset(cropped_bkg, options.fraction)';
|
|
end
|
|
|
|
if showPB
|
|
progressPercent = round(k/nFiles*100);
|
|
pb.run(progressPercent);
|
|
end
|
|
end
|
|
|
|
if showPB
|
|
pb.run(' Done!');
|
|
end
|
|
|
|
% --- Optional fringe removal ---
|
|
if isfield(options, 'skipFringeRemoval') && ~options.skipFringeRemoval
|
|
fprintf('\n[INFO] Applying fringe removal to processed images...\n');
|
|
optrefimages = Helper.removeFringesInImage(absimages, refimages);
|
|
absimages_fringe_removed = absimages - optrefimages;
|
|
od_imgs = arrayfun(@(i) absimages_fringe_removed(:,:,i), 1:size(absimages,3), 'UniformOutput', false);
|
|
fprintf('\n[INFO] Fringe removal completed.\n');
|
|
else
|
|
od_imgs = arrayfun(@(i) absimages(:,:,i), 1:size(absimages,3), 'UniformOutput', false);
|
|
end
|
|
|
|
% --- Optional unshuffling based on scan reference values ---
|
|
if isfield(options, 'skipUnshuffling') && ~options.skipUnshuffling
|
|
fprintf('\n[INFO] Reordering images...\n');
|
|
|
|
% --- Determine scan reference values ---
|
|
if ~isfield(options, 'scan_reference_values') || isempty(options.scan_reference_values)
|
|
if isnumeric(raw_scan_parameter_values) && isvector(raw_scan_parameter_values)
|
|
% --- Single parameter case (numeric vector) ---
|
|
scan_reference_values = unique(raw_scan_parameter_values(:), 'stable');
|
|
n_total = numel(raw_scan_parameter_values);
|
|
n_values = numel(scan_reference_values);
|
|
elseif iscell(raw_scan_parameter_values)
|
|
% --- Multi-parameter case (cell array of row vectors) ---
|
|
params = cell2mat(raw_scan_parameter_values); % convert to numeric matrix
|
|
scan_reference_values = unique(params, 'rows', 'stable');
|
|
n_total = numel(raw_scan_parameter_values);
|
|
n_values = size(scan_reference_values, 1);
|
|
else
|
|
error('Unsupported format for raw scan parameter values.');
|
|
end
|
|
else
|
|
% --- Use reference values from options ---
|
|
scan_reference_values = options.scan_reference_values;
|
|
|
|
if isnumeric(raw_scan_parameter_values) && isvector(raw_scan_parameter_values)
|
|
n_total = numel(raw_scan_parameter_values);
|
|
n_values = numel(scan_reference_values);
|
|
else
|
|
n_total = size(raw_scan_parameter_values, 1);
|
|
n_values = size(scan_reference_values, 1);
|
|
end
|
|
end
|
|
|
|
% --- Determine sort order ---
|
|
if isfield(options, 'flipSortOrder') && options.flipSortOrder
|
|
sort_order = 'descend';
|
|
else
|
|
sort_order = 'ascend'; % default
|
|
end
|
|
|
|
% --- Sort reference values ---
|
|
if isnumeric(scan_reference_values) && isvector(scan_reference_values)
|
|
scan_reference_values = sort(scan_reference_values, sort_order);
|
|
else
|
|
scan_reference_values = sortrows(scan_reference_values, sort_order);
|
|
end
|
|
|
|
% --- Convert multi-parameter scan_reference_values to cell array ---
|
|
if ~isnumeric(scan_reference_values) || size(scan_reference_values,2) > 1
|
|
scan_reference_values = mat2cell(scan_reference_values, ...
|
|
ones(size(scan_reference_values,1),1), ...
|
|
size(scan_reference_values,2));
|
|
end
|
|
|
|
% --- Reorder images according to scan reference values ---
|
|
n_reps = n_total / n_values;
|
|
tol = 1e-6; % tolerance for floating-point comparisons
|
|
idx_mat = nan(n_reps, n_values);
|
|
|
|
for j = 1:n_values
|
|
if isnumeric(raw_scan_parameter_values)
|
|
% Single parameter
|
|
ref_val = scan_reference_values(j); % scalar
|
|
idx_all = find(abs(raw_scan_parameter_values - ref_val) < tol);
|
|
elseif iscell(raw_scan_parameter_values)
|
|
% Multi-parameter
|
|
ref_val = scan_reference_values{j}; % row vector
|
|
diffs = cellfun(@(x) all(abs(x - ref_val) < tol), raw_scan_parameter_values);
|
|
idx_all = find(diffs);
|
|
end
|
|
|
|
if numel(idx_all) ~= n_reps
|
|
error('Reference value(s) %s occurs %d times; expected %d', ...
|
|
mat2str(ref_val,6), numel(idx_all), n_reps);
|
|
end
|
|
|
|
idx_mat(:, j) = idx_all(:);
|
|
end
|
|
|
|
% --- Reordered indices ---
|
|
ordered_idx = reshape(idx_mat.', 1, []);
|
|
|
|
% --- Apply reorder ---
|
|
od_imgs = od_imgs(ordered_idx);
|
|
|
|
if isnumeric(raw_scan_parameter_values)
|
|
scan_parameter_values = raw_scan_parameter_values(ordered_idx).';
|
|
elseif iscell(raw_scan_parameter_values)
|
|
scan_parameter_values = raw_scan_parameter_values(ordered_idx);
|
|
end
|
|
|
|
file_list = raw_file_list(ordered_idx);
|
|
|
|
fprintf('\n[INFO] Image reordering completed.\n');
|
|
|
|
else
|
|
scan_parameter_values = raw_scan_parameter_values;
|
|
file_list = raw_file_list;
|
|
end
|
|
|
|
% --- Determine scan parameter(s) ---
|
|
if ~isfield(options,'scan_parameter') || isempty(options.scan_parameter)
|
|
|
|
% Flatten all names into a single cell array
|
|
all_names = {};
|
|
for k = 1:numel(raw_scan_parameter_names)
|
|
x = raw_scan_parameter_names{k};
|
|
if iscell(x)
|
|
all_names = [all_names, x(:).']; % flatten row
|
|
else
|
|
all_names{end+1} = x; % single char array
|
|
end
|
|
end
|
|
|
|
% Find unique names (stable order)
|
|
unique_names = unique(all_names, 'stable');
|
|
|
|
% Decide single vs multiple parameter output
|
|
if numel(unique_names) == 1
|
|
scan_parameter_names = unique_names{1}; % single char array
|
|
else
|
|
scan_parameter_names = unique_names; % cell array of char arrays
|
|
end
|
|
else
|
|
scan_parameter_names = options.scan_parameter;
|
|
end
|
|
|
|
% --- Save processed dataset and options to workspace ---
|
|
assignin('base', 'od_imgs', od_imgs);
|
|
assignin('base', 'scan_parameter_names', scan_parameter_names);
|
|
assignin('base', 'scan_parameter_values', scan_parameter_values);
|
|
assignin('base', 'scan_reference_values', scan_reference_values);
|
|
assignin('base', 'file_list', file_list);
|
|
assignin('base', 'prior_options', options);
|
|
|
|
% --- Save OD images to disk if requested ---
|
|
if ~options.skipSaveProcessedOD
|
|
saveProcessedOD(od_imgs, options);
|
|
end
|
|
|
|
fprintf('\n[INFO] OD image dataset ready for further analysis.\n');
|
|
end
|
|
|
|
%% --- Local helper functions ---
|
|
|
|
function changed = haveOptionsChanged(options, prior_options, critical_fields)
|
|
changed = false;
|
|
for f = critical_fields
|
|
fname = f{1};
|
|
if isfield(options, fname) && isfield(prior_options, fname)
|
|
if ~isequal(options.(fname), prior_options.(fname))
|
|
changed = true; return
|
|
end
|
|
elseif xor(isfield(options, fname), isfield(prior_options, fname))
|
|
changed = true; return
|
|
end
|
|
end
|
|
end
|
|
|
|
function saveProcessedOD(od_imgs, options)
|
|
% Saves a cell array of processed OD images (with metadata) to .mat files
|
|
% od_imgs is expected to be a cell array of structs with fields: OD, Scan, File
|
|
%
|
|
% Inputs:
|
|
% od_imgs - cell array of structs: OD (2D array), Scan (scalar), File (string)
|
|
% options - struct containing folderPath and either FullODImagesFolder or saveDirectory
|
|
|
|
nImgs = numel(od_imgs);
|
|
if nImgs == 0
|
|
error('[ERROR] No images found.');
|
|
end
|
|
|
|
% --- Determine image size ---
|
|
[ny, nx] = size(od_imgs{1}.OD);
|
|
|
|
% --- Create unique folder name ---
|
|
dataSource = makeDataSourceStruct(options.folderPath);
|
|
runID = sprintf('%s_%s_Run%04d', ...
|
|
dataSource{1}.sequence, ...
|
|
strrep(dataSource{1}.date,'/','-'), ...
|
|
dataSource{1}.runs);
|
|
|
|
% --- Determine parent folder ---
|
|
if isfield(options, 'FullODImagesFolder') && isfolder(options.FullODImagesFolder) && ~isempty(options.FullODImagesFolder)
|
|
parentFolder = options.FullODImagesFolder;
|
|
elseif isfield(options, 'saveDirectory') && isfolder(options.saveDirectory)
|
|
parentFolder = options.saveDirectory;
|
|
else
|
|
parentFolder = pwd; % fallback to current folder
|
|
end
|
|
|
|
% --- Create ProcessedODImages folder ---
|
|
processedFolder = fullfile(parentFolder, ['ProcessedODImages_' runID]);
|
|
if ~exist(processedFolder, 'dir')
|
|
mkdir(processedFolder);
|
|
end
|
|
fprintf('\n[INFO] Saving processed OD images in folder: %s\n', processedFolder);
|
|
|
|
% --- Check if files already exist ---
|
|
filesExist = all(arrayfun(@(k) isfile(fullfile(processedFolder, sprintf('Image_%04d.mat', k))), 1:nImgs)) ...
|
|
&& isfile(fullfile(processedFolder,'metadata.mat'));
|
|
|
|
if filesExist
|
|
fprintf('\n[INFO] Processed OD .mat files already exist in %s. Skipping save.\n', processedFolder);
|
|
return;
|
|
end
|
|
|
|
% --- Save metadata ---
|
|
metadata.options = options;
|
|
metadata.timestamp = datetime;
|
|
metadata.runID = runID;
|
|
metadata.imageSize = [ny, nx];
|
|
metadata.fileList = string(cellfun(@(c) c.File, od_imgs, 'UniformOutput', false));
|
|
save(fullfile(processedFolder,'metadata.mat'), 'metadata', '-v7.3');
|
|
|
|
% --- Save each image as a struct with OD, Scan, File ---
|
|
for k = 1:nImgs
|
|
imgStruct = od_imgs{k};
|
|
if ~isstruct(imgStruct) || ~all(isfield(imgStruct, {'OD','Scan','File'}))
|
|
error('od_imgs{%d} must be a struct with fields OD, Scan, File.', k);
|
|
end
|
|
|
|
OD = single(imgStruct.OD);
|
|
Scan = single(imgStruct.Scan);
|
|
File = string(imgStruct.File);
|
|
|
|
matFilePath = fullfile(processedFolder, sprintf('Image_%04d.mat', k));
|
|
save(matFilePath, 'OD','Scan','File','-v7.3');
|
|
end
|
|
|
|
fprintf('\n[INFO] Processed OD .mat files and metadata saved successfully.\n');
|
|
end
|
|
|
|
function dataSource = makeDataSourceStruct(folderPath)
|
|
% Split by file separators (handles / or \)
|
|
parts = regexp(folderPath, '[\\/]', 'split');
|
|
|
|
% Remove empty parts caused by leading slashes
|
|
parts = parts(~cellfun('isempty', parts));
|
|
|
|
% Extract sequence, date, and run number
|
|
% Now the indices are correct:
|
|
% parts = {'DyLabNAS', 'Data', 'StructuralPhaseTransition', '2025', '08', '13', '0062'}
|
|
sequence = parts{3}; % "StructuralPhaseTransition"
|
|
year = parts{4}; % "2025"
|
|
month = parts{5}; % "08"
|
|
day = parts{6}; % "13"
|
|
runStr = parts{7}; % "0062"
|
|
|
|
% Build date string
|
|
dateStr = sprintf('%s/%s/%s', year, month, day);
|
|
|
|
% Convert run string to number
|
|
runNum = str2double(runStr);
|
|
|
|
% Construct struct inside a cell array
|
|
dataSource = {
|
|
struct('sequence', sequence, ...
|
|
'date', dateStr, ...
|
|
'runs', runNum)
|
|
};
|
|
end
|
|
|
|
function [full_od_imgs, full_bkg_imgs, raw_scan_parameter_values, raw_file_list, scan_parameter_names, scan_reference_values] = recomputeODImages(options, selectedPath)
|
|
% recomputeODImages: central recompute routine
|
|
% optional argument selectedPath (if provided) will be used as options.folderPath for processing
|
|
|
|
if nargin < 3
|
|
selectedPath = '';
|
|
end
|
|
|
|
% make a local copy of options so we can override folderPath if selectedPath supplied
|
|
opts = options;
|
|
if ~isempty(selectedPath)
|
|
% assume the Helper.processRawData uses opts.folderPath (or opts.selectedPath) — override folderPath to selectedPath
|
|
opts.folderPath = selectedPath;
|
|
end
|
|
|
|
[full_od_imgs, full_bkg_imgs, raw_scan_parameter_values, raw_file_list, scan_parameter_names, scan_reference_values] = Helper.processRawData(opts);
|
|
|
|
if opts.SAVE_TO_WORKSPACE
|
|
fprintf('\n[INFO] Completed computing OD images. Stored in workspace for reuse.\n');
|
|
else
|
|
fprintf('\n[INFO] Completed computing OD images. Stored on disk for reuse.\n');
|
|
end
|
|
end
|
|
|
|
function fullodimagesFolder = createFullODImagesFolderPath(parentFolder, dataSourcesStruct)
|
|
runID = sprintf('%s_%s_Run%04d', ...
|
|
dataSourcesStruct{1}.sequence, ...
|
|
strrep(dataSourcesStruct{1}.date,'/','-'), ...
|
|
dataSourcesStruct{1}.runs);
|
|
fullodimagesFolder = fullfile(parentFolder, ['FullODImages_' runID]);
|
|
end
|
|
|
|
function [mat_files, raw_scan_parameter_names, raw_scan_parameter_values, raw_file_list, nFiles] = prepareFromOnDiskData(folder)
|
|
mat_files = dir(fullfile(folder,'*.mat'));
|
|
mat_files = mat_files(~strcmp({mat_files.name},'metadata.mat')); % exclude metadata
|
|
nFiles = numel(mat_files);
|
|
raw_scan_parameter_names = cell(1,nFiles);
|
|
raw_scan_parameter_values = [];
|
|
raw_file_list = strings(1,nFiles);
|
|
end |