MAJOR update: Added functionality to handle pre-computed OD images in a separate location and plot images or run analysis on it.
This commit is contained in:
parent
cf577bfcde
commit
e64665af39
@ -34,11 +34,14 @@ function [results, scan_parameter_values] = performAnalysis(options)
|
|||||||
options.skipSaveFigures (1,1) logical
|
options.skipSaveFigures (1,1) logical
|
||||||
options.skipSaveData (1,1) logical
|
options.skipSaveData (1,1) logical
|
||||||
options.skipSaveOD (1,1) logical
|
options.skipSaveOD (1,1) logical
|
||||||
|
options.skipFullODImagesFolderUse (1,1) logical
|
||||||
options.showProgressBar (1,1) logical
|
options.showProgressBar (1,1) logical
|
||||||
options.measurementName (1,:) char
|
options.measurementName (1,:) char
|
||||||
|
options.selectedPath (1,:) char
|
||||||
options.folderPath (1,:) char
|
options.folderPath (1,:) char
|
||||||
options.baseDataFolder (1,:) char
|
options.baseDataFolder (1,:) char
|
||||||
options.saveDirectory (1,:) char
|
options.saveDirectory (1,:) char
|
||||||
|
options.FullODImagesFolder (1,:) char
|
||||||
options.titleString (1,:) char
|
options.titleString (1,:) char
|
||||||
options.font (1,:) char
|
options.font (1,:) char
|
||||||
options.SAVE_TO_WORKSPACE (1,1) logical
|
options.SAVE_TO_WORKSPACE (1,1) logical
|
||||||
|
|||||||
@ -8,84 +8,225 @@ function results_all = batchAnalyze(dataSources, options)
|
|||||||
if ~isfield(options, 'baseDataFolder')
|
if ~isfield(options, 'baseDataFolder')
|
||||||
options.baseDataFolder = '//DyLabNAS/Data';
|
options.baseDataFolder = '//DyLabNAS/Data';
|
||||||
end
|
end
|
||||||
|
|
||||||
|
% Default skip flag
|
||||||
|
if ~isfield(options, 'skipFullODImagesFolderUse')
|
||||||
|
options.skipFullODImagesFolderUse = false;
|
||||||
|
end
|
||||||
|
|
||||||
|
% Determine whether to use FullODImagesFolder or raw baseDataFolder
|
||||||
|
useFullOD = false;
|
||||||
|
if isfield(options, 'FullODImagesFolder') && isfolder(options.FullODImagesFolder)
|
||||||
|
if ~isfolder(options.baseDataFolder)
|
||||||
|
if ~options.skipFullODImagesFolderUse
|
||||||
|
% Case 1a: raw folder missing, fallback to FullODImagesFolder
|
||||||
|
useFullOD = true;
|
||||||
|
fprintf('\n[INFO] Raw data folder not found but found full OD Images folder.\n');
|
||||||
|
fprintf('\n[INFO] Using full OD Images folder: %s\n', options.FullODImagesFolder);
|
||||||
|
else
|
||||||
|
% Case 1b: raw folder missing, fallback to
|
||||||
|
% FullODImagesFolder but user overrides
|
||||||
|
error('Raw data folder not found, found full OD Images folder which cannot be used (set skipFullODImagesFolderUse=false to override). Aborting.\n');
|
||||||
|
end
|
||||||
|
elseif ~options.skipFullODImagesFolderUse
|
||||||
|
% Case 2: both exist, prioritize FullODImagesFolder unless skipped
|
||||||
|
useFullOD = true;
|
||||||
|
fprintf('\n[INFO] Both raw data folder (%s) and full OD Images folder (%s) found.\n', ...
|
||||||
|
options.baseDataFolder, options.FullODImagesFolder);
|
||||||
|
fprintf('\n[INFO] Prioritizing full OD Images folder (set skipFullODImagesFolderUse=true to override).\n');
|
||||||
|
else
|
||||||
|
% Case 3: both exist, prioritize raw data folder because of overrride
|
||||||
|
fprintf('\n[INFO] Both raw data folder (%s) and full OD Images folder (%s) found.\n', ...
|
||||||
|
options.baseDataFolder, options.FullODImagesFolder);
|
||||||
|
fprintf('\n[INFO] Prioritizing raw data folder (set skipFullODImagesFolderUse=false to override).\n');
|
||||||
|
end
|
||||||
|
elseif isfolder(options.baseDataFolder)
|
||||||
|
% 🚨 Raw data folder exists, full OD images does not
|
||||||
|
fprintf('\n[INFO] Full OD Images folder not found but found raw data folder.\n');
|
||||||
|
fprintf('\n[INFO] Using raw data folder: %s\n', options.baseDataFolder);
|
||||||
|
end
|
||||||
|
|
||||||
|
% 🚨 Sanity check if neither folder exists
|
||||||
|
if ~useFullOD && ~isfolder(options.baseDataFolder)
|
||||||
|
warning('\n[ERROR] Neither raw data folder (%s) nor a valid full OD Images folder were found. Exiting.', ...
|
||||||
|
options.baseDataFolder);
|
||||||
|
results_all = {};
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
% ===== Estimate dataset memory and get per-run estimates =====
|
% ===== Estimate dataset memory and get per-run estimates =====
|
||||||
[options.SAVE_TO_WORKSPACE, runMemoryGB] = Helper.estimateDatasetMemory(dataSources, options);
|
[options.SAVE_TO_WORKSPACE, ~] = Helper.estimateDatasetMemory(dataSources, options);
|
||||||
|
|
||||||
results_all = {}; % one element per folder
|
results_all = {}; % one element per folder
|
||||||
|
|
||||||
for i = 1:numel(dataSources)
|
% --- FULL OD MODE ---
|
||||||
ds = dataSources{i};
|
if useFullOD
|
||||||
|
% --- List available FullODImages_* folders ---
|
||||||
% Use per-sequence baseFolder if present, otherwise default from options
|
fullODFolders = dir(fullfile(options.FullODImagesFolder, 'FullODImages_*'));
|
||||||
if isfield(ds, 'baseFolder') && ~isempty(ds.baseFolder)
|
fullODFolders = fullODFolders([fullODFolders.isdir]);
|
||||||
baseFolder = fullfile(ds.baseFolder, ds.sequence, ds.date);
|
|
||||||
else
|
ds = dataSources{1}; % only one dataSources struct
|
||||||
baseFolder = fullfile(options.baseDataFolder, ds.sequence, ds.date);
|
|
||||||
|
% Ensure sequences, dates, runs are cell arrays for uniform processing
|
||||||
|
sequences = ds.sequence; if ischar(sequences), sequences = {sequences}; end
|
||||||
|
dates = ds.date; if ischar(dates), dates = {dates}; end
|
||||||
|
runs = ds.runs; if isnumeric(runs), runs = num2cell(runs); end
|
||||||
|
if isstring(runs), runs = cellstr(runs); end
|
||||||
|
|
||||||
|
% Loop over all combinations of sequence × date × run
|
||||||
|
for seqIdx = 1:numel(sequences)
|
||||||
|
for dateIdx = 1:numel(dates)
|
||||||
|
for runIdx = 1:numel(runs)
|
||||||
|
targetSequence = sequences{seqIdx};
|
||||||
|
targetDate = dates{dateIdx};
|
||||||
|
targetRun = runs{runIdx};
|
||||||
|
|
||||||
|
matched = false;
|
||||||
|
for i = 1:numel(fullODFolders)
|
||||||
|
selectedPath = fullfile(fullODFolders(i).folder, fullODFolders(i).name);
|
||||||
|
|
||||||
|
% Load metadata for run info
|
||||||
|
metaFile = fullfile(selectedPath, 'metadata.mat');
|
||||||
|
if ~isfile(metaFile)
|
||||||
|
warning('[WARNING] No metadata.mat in %s, skipping.', selectedPath);
|
||||||
|
continue;
|
||||||
|
end
|
||||||
|
S = load(metaFile, 'metadata');
|
||||||
|
|
||||||
|
% Reconstruct sequence/date/run from stored folderPath
|
||||||
|
dataSourceMeta = makeDataSourceStruct(S.metadata.options.folderPath);
|
||||||
|
|
||||||
|
% Check for match: measurementName and run info
|
||||||
|
if isfield(S.metadata.options, 'measurementName') && ...
|
||||||
|
isfield(options, 'measurementName') && ...
|
||||||
|
strcmp(S.metadata.options.measurementName, options.measurementName) && ...
|
||||||
|
strcmp(dataSourceMeta{1}.sequence, targetSequence) && ...
|
||||||
|
strcmp(dataSourceMeta{1}.date, targetDate) && ...
|
||||||
|
dataSourceMeta{1}.runs == targetRun
|
||||||
|
|
||||||
|
fprintf('\n[INFO] Found matching full OD images subfolder: %s\n', fullODFolders(i).name);
|
||||||
|
options.selectedPath = selectedPath;
|
||||||
|
options.folderPath = S.metadata.options.folderPath;
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ~matched
|
||||||
|
warning('[WARNING] No matching full OD images subfolder found for sequence %s, date %s, run %s, measurementName %s.', ...
|
||||||
|
targetSequence, targetDate, targetRun, options.measurementName);
|
||||||
|
continue; % skip this run but continue to next combination
|
||||||
|
end
|
||||||
|
|
||||||
|
% ✅ Proceed to analysis for this combination
|
||||||
|
try
|
||||||
|
args = [fieldnames(options), struct2cell(options)]';
|
||||||
|
args = args(:)';
|
||||||
|
[analysisResults, scan_parameter_values] = Analyzer.performAnalysis(args{:});
|
||||||
|
|
||||||
|
result = struct();
|
||||||
|
result.sequence = targetSequence;
|
||||||
|
result.date = targetDate;
|
||||||
|
result.run = targetRun;
|
||||||
|
result.path = options.folderPath;
|
||||||
|
result.options = options;
|
||||||
|
result.results = analysisResults;
|
||||||
|
result.scan_parameter_values = scan_parameter_values;
|
||||||
|
|
||||||
|
% Save dataset as MAT
|
||||||
|
if ~isfield(options, 'skipSaveData') || ~options.skipSaveData
|
||||||
|
saveResultStruct(result, options.saveDirectory);
|
||||||
|
end
|
||||||
|
|
||||||
|
results_all{end+1,1} = result;
|
||||||
|
|
||||||
|
catch ME
|
||||||
|
warning("Error processing %s: %s", options.folderPath, ME.message);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return; % ✅ handled all in FullOD mode
|
||||||
|
end
|
||||||
|
|
||||||
for j = 1:numel(ds.runs)
|
% --- RAW MODE (default) ---
|
||||||
runItem = ds.runs(j);
|
ds = dataSources{1}; % single struct
|
||||||
|
|
||||||
% Convert numeric or char arrays to a string with leading zeros if needed
|
% Ensure sequences, dates, and runs are cell arrays
|
||||||
if isnumeric(runItem)
|
sequences = ds.sequence; if ischar(sequences), sequences = {sequences}; end
|
||||||
runID = sprintf('%04d', runItem); % adjust padding as needed
|
dates = ds.date; if ischar(dates), dates = {dates}; end
|
||||||
elseif isstring(runItem)
|
runs = ds.runs; if isnumeric(runs), runs = num2cell(runs); end
|
||||||
runID = runItem;
|
if isstring(runs), runs = cellstr(runs); end
|
||||||
elseif ischar(runItem)
|
|
||||||
runID = string(runItem);
|
% Loop over all combinations of sequence × date × run
|
||||||
elseif iscell(runItem)
|
for seqIdx = 1:numel(sequences)
|
||||||
runID = string(runItem{1}); % handles cell of char
|
for dateIdx = 1:numel(dates)
|
||||||
else
|
for runIdx = 1:numel(runs)
|
||||||
error('Unsupported type for run entry: %s', class(runItem));
|
targetSequence = sequences{seqIdx};
|
||||||
end
|
targetDate = dates{dateIdx};
|
||||||
|
runItem = runs{runIdx};
|
||||||
% --- Check per-run memory before processing ---
|
|
||||||
if runMemoryGB(j) > 0.5 * getAvailableRAM()
|
% Convert runItem to string with leading zeros if numeric
|
||||||
fprintf('[WARNING] Skipping run %s/%s due to high memory requirement (%.2f GB)\n', ...
|
if isnumeric(runItem)
|
||||||
ds.sequence, runID, runMemoryGB(j));
|
runID = sprintf('%04d', runItem);
|
||||||
continue; % skip this run
|
elseif isstring(runItem)
|
||||||
end
|
runID = runItem;
|
||||||
|
elseif ischar(runItem)
|
||||||
% Build folder path
|
runID = string(runItem);
|
||||||
folderPath = fullfile(baseFolder, runID);
|
elseif iscell(runItem)
|
||||||
options.folderPath = folderPath;
|
runID = string(runItem{1});
|
||||||
|
else
|
||||||
try
|
error('Unsupported type for run entry: %s', class(runItem));
|
||||||
% Convert struct -> name-value args
|
end
|
||||||
args = [fieldnames(options), struct2cell(options)]';
|
|
||||||
args = args(:)';
|
% Determine base folder
|
||||||
|
if isfield(ds, 'baseFolder') && ~isempty(ds.baseFolder)
|
||||||
% Perform analysis (now returns 2 outputs)
|
baseFolder = fullfile(ds.baseFolder, targetSequence, targetDate);
|
||||||
[analysisResults, scan_parameter_values] = Analyzer.performAnalysis(args{:});
|
else
|
||||||
|
baseFolder = fullfile(options.baseDataFolder, targetSequence, targetDate);
|
||||||
% Store flat struct with metadata + results + options
|
|
||||||
result = struct();
|
|
||||||
result.sequence = ds.sequence;
|
|
||||||
result.date = ds.date;
|
|
||||||
result.run = runID;
|
|
||||||
result.path = folderPath;
|
|
||||||
result.options = options;
|
|
||||||
result.results = analysisResults;
|
|
||||||
result.scan_parameter_values = scan_parameter_values;
|
|
||||||
|
|
||||||
% Save each dataset as its own MAT file
|
|
||||||
if ~isfield(options, 'skipSaveData') || ~options.skipSaveData
|
|
||||||
saveResultStruct(result, options.saveDirectory);
|
|
||||||
end
|
end
|
||||||
|
|
||||||
% Append to output
|
% Build folder path
|
||||||
results_all{end+1,1} = result;
|
folderPath = fullfile(baseFolder, runID);
|
||||||
|
options.folderPath = folderPath;
|
||||||
catch ME
|
|
||||||
warning("Error processing %s/%s/%s: %s", ...
|
try
|
||||||
ds.sequence, ds.date, runID, ME.message);
|
% Convert struct -> name-value args
|
||||||
|
args = [fieldnames(options), struct2cell(options)]';
|
||||||
|
args = args(:)';
|
||||||
|
|
||||||
|
% Perform analysis
|
||||||
|
[analysisResults, scan_parameter_values] = Analyzer.performAnalysis(args{:});
|
||||||
|
|
||||||
|
% Store results
|
||||||
|
result = struct();
|
||||||
|
result.sequence = targetSequence;
|
||||||
|
result.date = targetDate;
|
||||||
|
result.run = runID;
|
||||||
|
result.path = folderPath;
|
||||||
|
result.options = options;
|
||||||
|
result.results = analysisResults;
|
||||||
|
result.scan_parameter_values = scan_parameter_values;
|
||||||
|
|
||||||
|
% Save dataset
|
||||||
|
if ~isfield(options, 'skipSaveData') || ~options.skipSaveData
|
||||||
|
saveResultStruct(result, options.saveDirectory);
|
||||||
|
end
|
||||||
|
|
||||||
|
% Append to output
|
||||||
|
results_all{end+1,1} = result;
|
||||||
|
|
||||||
|
catch ME
|
||||||
|
warning("Error processing %s/%s/%s: %s", ...
|
||||||
|
targetSequence, targetDate, runID, ME.message);
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
%% ---- Local function for saving results ----
|
%% --- Local helper functions ---
|
||||||
function saveResultStruct(result, saveDirectory)
|
function saveResultStruct(result, saveDirectory)
|
||||||
% Define results folder
|
% Define results folder
|
||||||
resultsFolder = fullfile(saveDirectory, "Results", "SavedData");
|
resultsFolder = fullfile(saveDirectory, "Results", "SavedData");
|
||||||
@ -117,12 +258,33 @@ function saveResultStruct(result, saveDirectory)
|
|||||||
save(indexFile, "nextIdx");
|
save(indexFile, "nextIdx");
|
||||||
end
|
end
|
||||||
|
|
||||||
%% ---- Local function to get available RAM ----
|
function dataSource = makeDataSourceStruct(folderPath)
|
||||||
function availableRAM = getAvailableRAM()
|
% Split by file separators (handles / or \)
|
||||||
if ispc
|
parts = regexp(folderPath, '[\\/]', 'split');
|
||||||
[~, sys] = memory;
|
|
||||||
availableRAM = sys.PhysicalMemory.Available;
|
% Remove empty parts caused by leading slashes
|
||||||
else
|
parts = parts(~cellfun('isempty', parts));
|
||||||
availableRAM = 16e9; % fallback: 16 GB if not Windows
|
|
||||||
end
|
% 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
|
end
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,16 @@ function [od_imgs, scan_parameter_values, file_list] = collectODImages(options)
|
|||||||
end
|
end
|
||||||
|
|
||||||
% --- General path to full od image folders ---
|
% --- General path to full od image folders ---
|
||||||
fullodimage_folders = dir(fullfile(options.saveDirectory,'FullODImages_*'));
|
if isfield(options, 'FullODImagesFolder') && ...
|
||||||
|
~isempty(options.FullODImagesFolder) && ...
|
||||||
|
isfolder(options.FullODImagesFolder)
|
||||||
|
% User-specified parent folder
|
||||||
|
fullodimage_folders = dir(fullfile(options.FullODImagesFolder, 'FullODImages_*'));
|
||||||
|
else
|
||||||
|
% Default to saveDirectory
|
||||||
|
fullodimage_folders = dir(fullfile(options.saveDirectory, 'FullODImages_*'));
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
% --- Specific sequence, data and run ---
|
% --- Specific sequence, data and run ---
|
||||||
dataSource = makeDataSourceStruct(options.folderPath);
|
dataSource = makeDataSourceStruct(options.folderPath);
|
||||||
@ -74,24 +83,44 @@ function [od_imgs, scan_parameter_values, file_list] = collectODImages(options)
|
|||||||
raw_file_list = evalin('base', 'raw_file_list');
|
raw_file_list = evalin('base', 'raw_file_list');
|
||||||
nFiles = size(full_od_imgs,3);
|
nFiles = size(full_od_imgs,3);
|
||||||
fprintf('\n[INFO] Cropping and subtracting background from images...\n');
|
fprintf('\n[INFO] Cropping and subtracting background from images...\n');
|
||||||
elseif ~options.SAVE_TO_WORKSPACE && ~isempty(fullodimage_folders)
|
elseif ~options.SAVE_TO_WORKSPACE || (~isempty(fullodimage_folders) || (isfield(options,'selectedPath') && isfolder(options.selectedPath)))
|
||||||
matched = false;
|
matched = false;
|
||||||
for r = 1:numel(fullodimage_folders)
|
|
||||||
metaPath = fullfile(fullodimage_folders(r).folder, fullodimage_folders(r).name, 'metadata.mat');
|
% --- Use selectedPath directly if it exists and full OD usage is enabled ---
|
||||||
if ~isfile(metaPath), continue; end
|
if isfield(options, 'selectedPath') && isfolder(options.selectedPath) ...
|
||||||
S = load(metaPath,'metadata');
|
&& (~isfield(options, 'skipFullODImagesFolderUse') || ~options.skipFullODImagesFolderUse)
|
||||||
if isequaln(S.metadata.options, options)
|
fullodimage_folder = options.selectedPath;
|
||||||
fprintf('\n[INFO] Found matching full OD images folder: %s\n', fullodimage_folders(r).name);
|
matched = true;
|
||||||
fullodimage_folder = fullfile(fullodimage_folders(r).folder, fullodimage_folders(r).name);
|
fprintf('\n[INFO] Using selected full OD images subfolder: %s\n', fullodimage_folder);
|
||||||
matched = true;
|
else
|
||||||
break;
|
% --- Otherwise, search among available full OD image folders ---
|
||||||
|
for r = 1:numel(fullodimage_folders)
|
||||||
|
metaPath = fullfile(fullodimage_folders(r).folder, fullodimage_folders(r).name, 'metadata.mat');
|
||||||
|
if ~isfile(metaPath), continue; end
|
||||||
|
S = load(metaPath,'metadata');
|
||||||
|
|
||||||
|
% Compare only measurementName and folder path via dataSource struct
|
||||||
|
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)
|
||||||
|
fullodimage_folder = fullfile(fullodimage_folders(r).folder, fullodimage_folders(r).name);
|
||||||
|
matched = true;
|
||||||
|
fprintf('\n[INFO] Found matching full OD images subfolder: %s\n', fullodimage_folders(r).name);
|
||||||
|
break;
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if ~matched
|
if ~matched
|
||||||
fprintf('\n[INFO] No matching full OD images folder for this run found.\n');
|
fprintf('\n[INFO] No matching full OD images subfolder for this run found.\n');
|
||||||
[~, ~, ~, ~] = Helper.processRawData(options);
|
[~, ~, ~, ~] = Helper.processRawData(options);
|
||||||
fprintf('\n[INFO] Completed computing OD images. Images will be stored on disk for reuse.\n');
|
fprintf('\n[INFO] Completed computing OD images. Images will be stored on disk for reuse.\n');
|
||||||
end
|
end
|
||||||
|
|
||||||
|
% --- Load mat files from determined folder ---
|
||||||
mat_files = dir(fullfile(fullodimage_folder,'*.mat'));
|
mat_files = dir(fullfile(fullodimage_folder,'*.mat'));
|
||||||
mat_files = mat_files(~strcmp({mat_files.name}, 'metadata.mat')); % Exclude metadata.mat
|
mat_files = mat_files(~strcmp({mat_files.name}, 'metadata.mat')); % Exclude metadata.mat
|
||||||
nFiles = numel(mat_files);
|
nFiles = numel(mat_files);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
function [SAVE_TO_WORKSPACE, runMemoryGB] = estimateDatasetMemory(dataSources, options)
|
function [SAVE_TO_WORKSPACE, runMemoryGB] = estimateDatasetMemory(dataSources, options)
|
||||||
% Estimate per-run memory and decide whether to save dataset to workspace
|
% Estimate per-run memory and decide whether to save dataset to workspace
|
||||||
|
%
|
||||||
|
% Supports both raw data folders and a preselected run folder.
|
||||||
|
|
||||||
% --- Measured memory per image (bytes) ---
|
% --- Measured memory per image (bytes) ---
|
||||||
bytesPerFullImage = 37.75 * 1e6; % full OD image
|
bytesPerFullImage = 37.75 * 1e6; % full OD image
|
||||||
@ -16,55 +18,61 @@ function [SAVE_TO_WORKSPACE, runMemoryGB] = estimateDatasetMemory(dataSources, o
|
|||||||
SAVE_TO_WORKSPACE = true; % default, may change per run
|
SAVE_TO_WORKSPACE = true; % default, may change per run
|
||||||
runMemoryGB = []; % store per-run memory
|
runMemoryGB = []; % store per-run memory
|
||||||
|
|
||||||
% --- Loop over all data sources and runs ---
|
% --- Case 1: selectedPath exists ---
|
||||||
for i_ds = 1:numel(dataSources)
|
if isfield(options, 'selectedPath') && isfolder(options.selectedPath)
|
||||||
ds = dataSources{i_ds};
|
runFolder = options.selectedPath;
|
||||||
|
files = dir(fullfile(runFolder, '*.h5'));
|
||||||
|
nFiles = numel(files);
|
||||||
|
|
||||||
if isfield(ds, 'baseFolder') && ~isempty(ds.baseFolder)
|
if nFiles > 0
|
||||||
baseFolder = fullfile(ds.baseFolder, ds.sequence, ds.date);
|
runBytes = nFiles * (bytesPerFullImage + bytesPerCroppedImage);
|
||||||
else
|
runMemoryGB(end+1,1) = runBytes/1e9;
|
||||||
baseFolder = fullfile(options.baseDataFolder, ds.sequence, ds.date);
|
|
||||||
|
if runBytes > 0.75 * availableRAM
|
||||||
|
SAVE_TO_WORKSPACE = false;
|
||||||
|
fprintf('[INFO] Selected run %s estimated size %.2f GB exceeds 75%% of available RAM. Saving to disk.\n', ...
|
||||||
|
runFolder, runBytes/1e9);
|
||||||
|
else
|
||||||
|
fprintf('[INFO] Selected run %s estimated size %.2f GB fits in memory.\n', ...
|
||||||
|
runFolder, runBytes/1e9);
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for j_run = 1:numel(ds.runs)
|
% --- Case 2: fallback to raw data folders ---
|
||||||
runItem = ds.runs(j_run);
|
else
|
||||||
|
for i_ds = 1:numel(dataSources)
|
||||||
% Convert runItem to string runID
|
ds = dataSources{i_ds};
|
||||||
if isnumeric(runItem)
|
if isfield(ds, 'baseFolder') && ~isempty(ds.baseFolder)
|
||||||
runID = sprintf('%04d', runItem);
|
baseFolder = fullfile(ds.baseFolder, ds.sequence, ds.date);
|
||||||
elseif isstring(runItem)
|
|
||||||
runID = runItem;
|
|
||||||
elseif ischar(runItem)
|
|
||||||
runID = string(runItem);
|
|
||||||
elseif iscell(runItem)
|
|
||||||
runID = string(runItem{1});
|
|
||||||
else
|
else
|
||||||
error('Unsupported run type');
|
baseFolder = fullfile(options.baseDataFolder, ds.sequence, ds.date);
|
||||||
end
|
end
|
||||||
|
|
||||||
runFolder = fullfile(baseFolder, runID);
|
for j_run = 1:numel(ds.runs)
|
||||||
|
runItem = ds.runs(j_run);
|
||||||
|
runID = sprintf('%04d', runItem);
|
||||||
|
runFolder = fullfile(baseFolder, runID);
|
||||||
|
|
||||||
if isfolder(runFolder)
|
if isfolder(runFolder)
|
||||||
files = dir(fullfile(runFolder, '*.h5'));
|
files = dir(fullfile(runFolder, '*.h5'));
|
||||||
nFiles = numel(files);
|
nFiles = numel(files);
|
||||||
if nFiles == 0
|
if nFiles == 0
|
||||||
continue;
|
continue;
|
||||||
end
|
end
|
||||||
|
|
||||||
% Memory estimate for this run (full + cropped)
|
runBytes = nFiles * (bytesPerFullImage + bytesPerCroppedImage);
|
||||||
runBytes = nFiles * (bytesPerFullImage + bytesPerCroppedImage);
|
runMemoryGB(end+1,1) = runBytes/1e9;
|
||||||
runMemoryGB(end+1,1) = runBytes/1e9;
|
|
||||||
|
|
||||||
% Decide workspace flag per run by comparing with 50% of available RAM
|
if runBytes > 0.75 * availableRAM
|
||||||
if runBytes > 0.75 * availableRAM
|
SAVE_TO_WORKSPACE = false;
|
||||||
SAVE_TO_WORKSPACE = false;
|
fprintf('[INFO] Run %s/%s estimated size %.2f GB exceeds 75%% of available RAM. Saving to disk.\n', ...
|
||||||
fprintf('\n[INFO] Estimated size on memory of Run %s/%s too large (%.2f GB). Will save full OD images to disk and partially to workspace if not done so already.\n', ...
|
ds.sequence, runID, runBytes/1e9);
|
||||||
ds.sequence, runID, runBytes/1e9);
|
else
|
||||||
else
|
fprintf('[INFO] Run %s/%s estimated size %.2f GB fits in memory.\n', ...
|
||||||
fprintf('\n[INFO] Estimated size on memory of Run %s/%s = %.2f GB. Will save full OD images data completely to workspace if not done so already.\n', ...
|
ds.sequence, runID, runBytes/1e9);
|
||||||
ds.sequence, runID, runBytes/1e9);
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -40,10 +40,20 @@ function [full_od_imgs, full_bkg_imgs, raw_scan_parameter_values, raw_file_list]
|
|||||||
strrep(dataSource{1}.date,'/','-'), ...
|
strrep(dataSource{1}.date,'/','-'), ...
|
||||||
dataSource{1}.runs);
|
dataSource{1}.runs);
|
||||||
|
|
||||||
fullODImageFolder = fullfile(options.saveDirectory, ['FullODImages_' runID]);
|
% --- Determine parent folder for FullODImages ---
|
||||||
|
if isfield(options, 'FullODImagesFoldersPath') && ...
|
||||||
|
~isempty(options.FullODImagesFoldersPath) && ...
|
||||||
|
isfolder(options.FullODImagesFoldersPath)
|
||||||
|
parentFolder = options.FullODImagesFoldersPath;
|
||||||
|
else
|
||||||
|
parentFolder = options.saveDirectory;
|
||||||
|
end
|
||||||
|
|
||||||
|
% --- Create uniquely identified full OD image folder ---
|
||||||
|
fullODImageFolder = fullfile(parentFolder, ['FullODImages_' runID]);
|
||||||
if ~exist(fullODImageFolder,'dir'), mkdir(fullODImageFolder); end
|
if ~exist(fullODImageFolder,'dir'), mkdir(fullODImageFolder); end
|
||||||
fprintf('\n[INFO] Creating folder of full OD images on disk: %s\n', fullODImageFolder);
|
fprintf('\n[INFO] Creating folder of full OD images on disk: %s\n', fullODImageFolder);
|
||||||
|
|
||||||
% --- Save metadata for this run ---
|
% --- Save metadata for this run ---
|
||||||
metadata.options = options;
|
metadata.options = options;
|
||||||
metadata.timestamp = datetime; % still record analysis time
|
metadata.timestamp = datetime; % still record analysis time
|
||||||
|
|||||||
102
Data-Analyzer/+Helper/selectDataSourcePath.m
Normal file
102
Data-Analyzer/+Helper/selectDataSourcePath.m
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
function [selectedPath, folderPath] = selectDataSourcePath(dataSources, options)
|
||||||
|
% Helper function to select a run/folder for analysis
|
||||||
|
%
|
||||||
|
% Inputs:
|
||||||
|
% dataSources - cell array of structs with fields: sequence, date, runs
|
||||||
|
% options - struct, may contain: baseDataFolder, FullODImagesFolder, skipFullODImagesFolderUse
|
||||||
|
%
|
||||||
|
% Outputs:
|
||||||
|
% selectedPath - actual folder to use (raw or FullOD)
|
||||||
|
% folderPath - constructed path from dataSources (always raw folder)
|
||||||
|
|
||||||
|
allPaths = {}; % initialize candidate paths
|
||||||
|
|
||||||
|
% --- Gather candidate raw data paths ---
|
||||||
|
for i = 1:numel(dataSources)
|
||||||
|
ds = dataSources{i};
|
||||||
|
dateParts = strsplit(ds.date,'/');
|
||||||
|
for run = ds.runs
|
||||||
|
runStr = sprintf('%04d', run);
|
||||||
|
rawPath = fullfile(options.baseDataFolder, ds.sequence, dateParts{:}, runStr);
|
||||||
|
if isfolder(rawPath)
|
||||||
|
allPaths{end+1} = rawPath;
|
||||||
|
else
|
||||||
|
fprintf('[INFO] Raw data folder does not exist: %s\n', rawPath);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
% --- Determine whether FullODImagesFolder should be used ---
|
||||||
|
useFullOD = false;
|
||||||
|
if ~isempty(allPaths)
|
||||||
|
if isfield(options,'FullODImagesFolder') && isfolder(options.FullODImagesFolder)
|
||||||
|
if ~isfield(options,'skipFullODImagesFolderUse') || ~options.skipFullODImagesFolderUse
|
||||||
|
fprintf('\n[INFO] Both raw data folder (%s) and full OD Images folder (%s) found.\n', ...
|
||||||
|
options.baseDataFolder, options.FullODImagesFolder);
|
||||||
|
fprintf('[INFO] Prioritizing full OD Images folder (set skipFullODImagesFolderUse=true to override).\n');
|
||||||
|
useFullOD = true;
|
||||||
|
else
|
||||||
|
fprintf('\n[INFO] Both raw data folder (%s) and full OD Images folder (%s) found.\n', ...
|
||||||
|
options.baseDataFolder, options.FullODImagesFolder);
|
||||||
|
fprintf('[INFO] Prioritizing raw data folder (set skipFullODImagesFolderUse=false to override).\n');
|
||||||
|
end
|
||||||
|
else
|
||||||
|
fprintf('\n[INFO] Using raw data folder(s) since full OD images not found or not specified.\n');
|
||||||
|
end
|
||||||
|
elseif isfield(options,'FullODImagesFolder') && isfolder(options.FullODImagesFolder)
|
||||||
|
if ~options.skipFullODImagesFolderUse
|
||||||
|
useFullOD = true;
|
||||||
|
fprintf('\n[INFO] Raw data folder(s) not found but found full OD Images folder which will be used.\n');
|
||||||
|
allPaths{end+1} = options.FullODImagesFolder;
|
||||||
|
else
|
||||||
|
error('Raw data folder(s) not found, found full OD Images folder which cannot be used (set skipFullODImagesFolderUse=false to override). Aborting.\n');
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if isempty(allPaths)
|
||||||
|
error('No valid paths for data found. Aborting.');
|
||||||
|
end
|
||||||
|
|
||||||
|
% --- If using full OD images folder, match the subfolder corresponding to sequence/date/run ---
|
||||||
|
if useFullOD
|
||||||
|
matchedPaths = {};
|
||||||
|
for i = 1:numel(dataSources)
|
||||||
|
ds = dataSources{i};
|
||||||
|
for run = ds.runs
|
||||||
|
expectedName = sprintf('FullODImages_%s_%s_Run%04d', ds.sequence, strrep(ds.date,'/','-'), run);
|
||||||
|
candidateFolder = fullfile(options.FullODImagesFolder, expectedName);
|
||||||
|
if isfolder(candidateFolder)
|
||||||
|
matchedPaths{end+1} = candidateFolder;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if ~isempty(matchedPaths)
|
||||||
|
allPaths = matchedPaths;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
% --- Determine if user selection is needed ---
|
||||||
|
if numel(allPaths) > 1
|
||||||
|
set(0,'DefaultUicontrolFontSize',10);
|
||||||
|
[selectedIndex, tf] = listdlg('PromptString','Select a run to analyze:', ...
|
||||||
|
'SelectionMode','single', ...
|
||||||
|
'ListString', allPaths, ...
|
||||||
|
'ListSize',[500, 300]);
|
||||||
|
if ~tf
|
||||||
|
error('No path selected. Aborting.');
|
||||||
|
end
|
||||||
|
selectedPath = allPaths{selectedIndex};
|
||||||
|
else
|
||||||
|
% Only one candidate; select automatically
|
||||||
|
selectedPath = allPaths{1};
|
||||||
|
end
|
||||||
|
|
||||||
|
fprintf('\n[INFO] Selected path: %s\n', selectedPath);
|
||||||
|
|
||||||
|
% --- Construct folderPath from first entry in dataSources ---
|
||||||
|
ds = dataSources{1};
|
||||||
|
dateParts = strsplit(ds.date,'/');
|
||||||
|
runStr = sprintf('%04d', ds.runs(1));
|
||||||
|
folderPath = fullfile(options.baseDataFolder, ds.sequence, dateParts{:}, runStr);
|
||||||
|
|
||||||
|
end
|
||||||
@ -4,16 +4,17 @@
|
|||||||
dataSources = {
|
dataSources = {
|
||||||
struct('sequence', 'StructuralPhaseTransition', ...
|
struct('sequence', 'StructuralPhaseTransition', ...
|
||||||
'date', '2025/08/16', ...
|
'date', '2025/08/16', ...
|
||||||
'runs', [8]) % specify run numbers as a string in "" or just as a numeric value
|
'runs', [8, 9, 10]) % specify run numbers as a string in "" or just as a numeric value
|
||||||
};
|
};
|
||||||
|
|
||||||
options = struct();
|
options = struct();
|
||||||
|
|
||||||
% File / paths
|
% File / paths
|
||||||
options.baseDataFolder = '//DyLabNAS/Data';
|
options.baseDataFolder = '//DyLabNAS/Data';
|
||||||
options.measurementName = 'DropletsToStripes';
|
options.measurementName = 'DropletsToStripes';
|
||||||
scriptFullPath = mfilename('fullpath');
|
scriptFullPath = mfilename('fullpath');
|
||||||
options.saveDirectory = fileparts(scriptFullPath);
|
options.saveDirectory = fileparts(scriptFullPath);
|
||||||
|
options.FullODImagesFolder = 'E:/Data - Experiment/FullODImages/202508';
|
||||||
|
|
||||||
% Camera / imaging
|
% Camera / imaging
|
||||||
options.cam = 5;
|
options.cam = 5;
|
||||||
@ -61,12 +62,15 @@ switch options.measurementName
|
|||||||
end
|
end
|
||||||
|
|
||||||
% Flags
|
% Flags
|
||||||
options.skipNormalization = false;
|
|
||||||
options.skipUnshuffling = true;
|
options.skipUnshuffling = true;
|
||||||
|
options.skipNormalization = false;
|
||||||
|
|
||||||
options.skipPreprocessing = true;
|
options.skipPreprocessing = true;
|
||||||
options.skipMasking = true;
|
options.skipMasking = true;
|
||||||
options.skipIntensityThresholding = true;
|
options.skipIntensityThresholding = true;
|
||||||
options.skipBinarization = true;
|
options.skipBinarization = true;
|
||||||
|
|
||||||
|
options.skipFullODImagesFolderUse = false;
|
||||||
options.skipSaveFigures = true;
|
options.skipSaveFigures = true;
|
||||||
options.skipSaveData = false;
|
options.skipSaveData = false;
|
||||||
options.skipSaveOD = true;
|
options.skipSaveOD = true;
|
||||||
@ -76,43 +80,10 @@ options.showProgressBar = true;
|
|||||||
% Extras
|
% Extras
|
||||||
options.font = 'Bahnschrift';
|
options.font = 'Bahnschrift';
|
||||||
|
|
||||||
%% ===== Build Paths from Data Sources =====
|
|
||||||
|
|
||||||
allPaths = {}; % initialize
|
|
||||||
|
|
||||||
for i = 1:length(dataSources)
|
|
||||||
ds = dataSources{i};
|
|
||||||
|
|
||||||
% Split the date string into year/month/day
|
|
||||||
dateParts = strsplit(ds.date, '/');
|
|
||||||
|
|
||||||
for run = ds.runs
|
|
||||||
runStr = sprintf('%04d', run); % 4-digit run number
|
|
||||||
fullPath = fullfile(options.baseDataFolder, ds.sequence, dateParts{:}, runStr);
|
|
||||||
|
|
||||||
if isfolder(fullPath) % only include valid directories
|
|
||||||
allPaths{end+1} = fullPath;
|
|
||||||
else
|
|
||||||
warning('Path does not exist: %s', fullPath);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
% Let user select a single path
|
|
||||||
set(0,'DefaultUicontrolFontSize',10); % increase default font size
|
|
||||||
[selectedIndex, tf] = listdlg('PromptString','Select a path to analyze:', ...
|
|
||||||
'SelectionMode','single', ...
|
|
||||||
'ListString', allPaths, ...
|
|
||||||
'ListSize',[400, 300]); % width x height in pixels
|
|
||||||
if tf
|
|
||||||
options.folderPath = fullfile(allPaths{selectedIndex}); % ✅ store in options
|
|
||||||
fprintf('Path set to selection: %s\n', options.folderPath);
|
|
||||||
else
|
|
||||||
error('No path selected. Aborting.');
|
|
||||||
end
|
|
||||||
|
|
||||||
%% ===== Collect Images and Launch Viewer =====
|
%% ===== Collect Images and Launch Viewer =====
|
||||||
|
|
||||||
|
[options.selectedPath, options.folderPath] = Helper.selectDataSourcePath(dataSources, options);
|
||||||
|
|
||||||
[od_imgs, scan_parameter_values, file_list] = Helper.collectODImages(options);
|
[od_imgs, scan_parameter_values, file_list] = Helper.collectODImages(options);
|
||||||
|
|
||||||
Analyzer.runInteractiveODImageViewer(od_imgs, scan_parameter_values, file_list, options);
|
Analyzer.runInteractiveODImageViewer(od_imgs, scan_parameter_values, file_list, options);
|
||||||
@ -10,10 +10,11 @@ dataSources = {
|
|||||||
options = struct();
|
options = struct();
|
||||||
|
|
||||||
% File / paths
|
% File / paths
|
||||||
options.baseDataFolder = '//DyLabNAS/Data';
|
options.baseDataFolder = '//DyLabNAS/Data';
|
||||||
options.measurementName = 'DropletsToStripes';
|
options.measurementName = 'DropletsToStripes';
|
||||||
scriptFullPath = mfilename('fullpath');
|
scriptFullPath = mfilename('fullpath');
|
||||||
options.saveDirectory = fileparts(scriptFullPath);
|
options.saveDirectory = fileparts(scriptFullPath);
|
||||||
|
options.FullODImagesFolder = 'E:/Data - Experiment/FullODImages/202508';
|
||||||
|
|
||||||
% Camera / imaging
|
% Camera / imaging
|
||||||
options.cam = 5;
|
options.cam = 5;
|
||||||
@ -53,24 +54,27 @@ switch options.measurementName
|
|||||||
options.scan_reference_values = [2.45, 2.44, 2.43, 2.42, 2.4, 2.39, 2.38, 2.37, 2.36, 2.35, 2.34, 2.32, 2.3, 2.28, 2.25, 2.2, 2.15, 2.10, 2.0, 1.90, 1.8];
|
options.scan_reference_values = [2.45, 2.44, 2.43, 2.42, 2.4, 2.39, 2.38, 2.37, 2.36, 2.35, 2.34, 2.32, 2.3, 2.28, 2.25, 2.2, 2.15, 2.10, 2.0, 1.90, 1.8];
|
||||||
options.titleString = 'BEC to Stripes';
|
options.titleString = 'BEC to Stripes';
|
||||||
case 'DropletsToStripes'
|
case 'DropletsToStripes'
|
||||||
options.scan_reference_values = [0, 5, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 35, 40];
|
options.scan_reference_values = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45];
|
||||||
options.titleString = 'Droplets to Stripes';
|
options.titleString = 'Droplets to Stripes';
|
||||||
case 'StripesToDroplets'
|
case 'StripesToDroplets'
|
||||||
options.scan_reference_values = fliplr([0, 5, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 35, 40]);
|
options.scan_reference_values = fliplr([0, 5, 10, 15, 20, 25, 30, 35, 40, 45]);
|
||||||
options.titleString = 'Stripes to Droplets';
|
options.titleString = 'Stripes to Droplets';
|
||||||
end
|
end
|
||||||
|
|
||||||
% Flags
|
% Flags
|
||||||
|
options.skipUnshuffling = true;
|
||||||
options.skipNormalization = false;
|
options.skipNormalization = false;
|
||||||
options.skipUnshuffling = false;
|
|
||||||
options.skipPreprocessing = true;
|
options.skipPreprocessing = true;
|
||||||
options.skipMasking = true;
|
options.skipMasking = true;
|
||||||
options.skipIntensityThresholding = true;
|
options.skipIntensityThresholding = true;
|
||||||
options.skipBinarization = true;
|
options.skipBinarization = true;
|
||||||
|
|
||||||
|
options.skipFullODImagesFolderUse = false;
|
||||||
options.skipSaveFigures = true;
|
options.skipSaveFigures = true;
|
||||||
options.skipSaveData = false;
|
options.skipSaveData = false;
|
||||||
options.skipSaveOD = true;
|
options.skipSaveOD = true;
|
||||||
options.skipLivePlot = true;
|
options.skipLivePlot = false;
|
||||||
options.showProgressBar = true;
|
options.showProgressBar = true;
|
||||||
|
|
||||||
% Extras
|
% Extras
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user