From efff7c29f5aa59770c03ce0ea626d525bf70a4fc Mon Sep 17 00:00:00 2001 From: ThorBarRa Date: Fri, 21 Nov 2025 19:51:44 +0100 Subject: [PATCH] Updated to new PShell file structure and added abiltiy to read several spectra in each h5 file --- import/nappFileList.m | 419 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 359 insertions(+), 60 deletions(-) diff --git a/import/nappFileList.m b/import/nappFileList.m index 357a7de..d4a8408 100755 --- a/import/nappFileList.m +++ b/import/nappFileList.m @@ -78,6 +78,8 @@ function ListFiles = nappFileList(dataPath,varargin) % * April 2021, X-ray kinetic energy and images are now made unique % * March 2022, input now using varargin % * March 2022, strict data structure required for local and remote sources +% * November 2025, PShell h5 files for NEXAFS have a new structure; added +% creation date to mac for NEXAFS. %% Input % Define conditions and defaults of input @@ -243,6 +245,78 @@ else % The second line of code filters all files that start with Numbers or % letters. All system files starting with a . or ~ are discarded. + % ========================================================================= +% SPLIT HDF5 FILES WITH MULTIPLE DATASETS INTO INDIVIDUAL FILES +% ========================================================================= +% +% BACKGROUND: +% HDF5 files from the beamline can contain multiple experimental scans +% (e.g., scan_1, scan_2, scan_3) stored as separate groups within a +% single file. However, the NEXAFS and XPS data processing pipelines +% are designed to work with one scan per file. +% +% PURPOSE: +% This script identifies HDF5 files that contain multiple scans and +% splits each scan into a separate file with a descriptive name. +% Each output file contains only one scan (renamed to scan_1) plus +% the logs and metadata groups. +% +% PROCESS: +% 1. Loop through all NEXAFS HDF5 files in the file list +% 2. Check if each file contains more than 3 groups (indicating multiple scans) +% 3. If it does, split it using the splitH5Scans function +% 4. The original file is deleted after splitting +% +% OUTPUT: +% For each multi-scan file, creates separate output files: +% Example: 0002_..._O_Kedge.h5  0002_..._O_Kedge_scan_1.h5 +% 0002_..._O_Kedge_scan_2.h5 +% ========================================================================= + +% Loop through each file in the NEXAFS file list +% TmpFileList{2} contains the full paths to all NEXAFS HDF5 files +% ii is the loop counter (1, 2, 3, ... up to the total number of files) +for ii = 1:size(TmpFileList{2}, 1) + + % Get the filename of the current file being processed + current_file = [TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name]; + + % Read information about the structure of the HDF5 file + % This tells us what groups (datasets) are stored in the file + fileinfo = h5info(current_file); + + % Extract the names of all groups at the root level + % Example: {'/scan_1', '/scan_2', '/logs', '/__DATA_TYPES__'} + groupnames = {fileinfo.Groups.Name}; + + % Check if this file has more than 3 groups + % Files with more than 3 groups likely contain multiple scans + % (the extra groups beyond scans are typically '/logs' and '/__DATA_TYPES__') + if numel(groupnames) > 3 + + % This file contains multiple scans, so split it + % The splitH5Scans function will: + % - Create separate files for each scan + % - Rename each scan to '/scan_1' within its new file + % - Delete the original multi-scan file when done + splitH5Scans(current_file); + + end + +end + +% ========================================================================= +% NOTE: After this loop completes, all NEXAFS files in data folder +% will contain exactly one scan per file, ready for processing +% ========================================================================= + +% EPICS files in h5 format list all files, then get rid of system files + TmpFileList{2} = dir([dataPath_NEXAFS filesep '**' filesep '*.h5']); + TmpFileList{2} = TmpFileList{2}(find(~cellfun(@isempty,regexp({TmpFileList{2}.name},'^[a-zA-Z0-9]\w*')))); %#ok + +disp('*************************************************') +disp('* Voila: Splitting NEXAFS files done *') +disp('*************************************************') % only process new files if (istable(p.Results.DataTable) & matches(p.Results.mode,'localNew')) % do this only if previous list was input to function call @@ -282,20 +356,21 @@ end try if contains(p.Results.mode,'data') - tmp.fileinfo = dir([dataPath_XPS '/*.xy']); - filedate_first = tmp.fileinfo(1).datenum; - clearvars name tmp + tmp.fileinfo = dir([dataPath_XPS '/*.xy']); + filedate_first = tmp.fileinfo(1).datenum; + clearvars name tmp else - tmp.fileinfo = dir([dataPath_XPS '/*.ibw']); - filedate_first = tmp.fileinfo(1).datenum; - clearvars name tmp + tmp.fileinfo = dir([dataPath_XPS '/*.ibw']); + filedate_first = tmp.fileinfo(1).datenum; + clearvars name tmp end -catch +catch warning('There appears to be something wrong with the SES or Specs file.') disp('There appears to be something wrong with the SES or Specs file.') - + end + %% Import XPS data to get SES settings % % Uses the IgorPro importer for Matlab: @@ -341,14 +416,14 @@ if contains(p.Results.mode,'data') % for Specs xy data %TODO this is based on fi disp('Problem reading the Specs file') fprintf('Number %.0f: %s \n',ii,[TmpFileList{1}(ii).folder filesep TmpFileList{1}(ii).name]) end - + try %% Convert Specs meta data into variables % data - tmpEndFirstSpectrum = find(diff(tmpSpecsData{1})<0,1); % FIXME: imports only first spectrum + tmpEndFirstSpectrum = find(diff(tmpSpecsData{1})<0,1); % FIXME: imports only first spectrum % counts PE.y = tmpSpecsData{2}(1:tmpEndFirstSpectrum); % kinetic energy @@ -563,18 +638,28 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element %if ~isempty(h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/energy')) % creation date of file - TmpFileList{2}(ii).creationDate = datevec(NaN); + if ismac + tmpCommand = sprintf('stat -f "%%SB" "%s"', [TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name]); + [~,creation] = system(tmpCommand); + TmpFileList{2}(ii).creationDate = datevec(creation); + else + TmpFileList{2}(ii).creationDate = datevec(NaN); + end try % read data %if datenum(filedate_first) <= datenum('21-Nov-2020 00:01:00') % Date not exaclty certain. try - TmpImage= h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/spectrum'); - %else + TmpImage= h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/spectrum'); catch try - TmpImage = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/scienta spectrum'); + TmpImage= h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/spectrum'); + %else catch - TmpImage = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/detector/data'); % pre-2020 (?) h5 structure + try + TmpImage = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/scienta spectrum'); + catch + TmpImage = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/detector/data'); % pre-2020 (?) h5 structure + end end end % 21.8.20 changed from spectrum to scienta spectrum, later @@ -587,11 +672,16 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element % import Photon Energy (cols) try - TmpXrayEkin = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/energy'); % X-ray excitation energy; Photon energy in eV + TmpXrayEkin = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/energy'); % X-ray excitation energy; Photon energy in eV TmpXrayEkin(TmpXrayEkin==0)=[]; catch - TmpXrayEkin = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/PhotonEnergy'); % X-ray excitation energy; Photon energy in eV - TmpXrayEkin(TmpXrayEkin==0)=[]; + try + TmpXrayEkin = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/energy'); % X-ray excitation energy; Photon energy in eV + TmpXrayEkin(TmpXrayEkin==0)=[]; + catch + TmpXrayEkin = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/PhotonEnergy'); % X-ray excitation energy; Photon energy in eV + TmpXrayEkin(TmpXrayEkin==0)=[]; + end end @@ -607,35 +697,45 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element TmpFileList{2}(ii).image = TmpImage(:,ind); % NEXAFS pass energy try - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/PassEnergy'); % in eV - TmpFileList{2}(ii).scientaPassEnergy_eV = str2double(tmp(1)); + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/snaps/PassEnergy'); % in eV + TmpFileList{2}(ii).scientaPassEnergy_eV = str2double(tmp(1,:)); catch - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/PassEnergy'); % in eV + try + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/PassEnergy'); % in eV + TmpFileList{2}(ii).scientaPassEnergy_eV = str2double(tmp(1)); + catch + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/PassEnergy'); % in eV - if tmp(1) == 1 - TmpFileList{2}(ii).scientaPassEnergy_eV = 20; - elseif tmp(1) == 2 - TmpFileList{2}(ii).scientaPassEnergy_eV = 50; - elseif tmp(1) == 3 - TmpFileList{2}(ii).scientaPassEnergy_eV = 100; - elseif tmp(1) == 4 - TmpFileList{2}(ii).scientaPassEnergy_eV = 200; + if tmp(1) == 1 + TmpFileList{2}(ii).scientaPassEnergy_eV = 20; + elseif tmp(1) == 2 + TmpFileList{2}(ii).scientaPassEnergy_eV = 50; + elseif tmp(1) == 3 + TmpFileList{2}(ii).scientaPassEnergy_eV = 100; + elseif tmp(1) == 4 + TmpFileList{2}(ii).scientaPassEnergy_eV = 200; + end end end % NEXAFS dwell time - if datenum(filedate_first) >= datenum('1-Feb-2020 23:59:00') - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/ScientaStepTime')*1000; % in ms + % if datenum(filedate_first) >= datenum('1-Feb-2020 23:59:00') + try + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/snaps/ScientaStepTime')*1000; % in ms - else + catch try - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/StepTime')*1000; % in ms + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/ScientaStepTime')*1000; % in ms catch try - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/StepTime')*1000; % in ms + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/StepTime')*1000; % in ms catch - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/ScientaStepTime')*1000; + try + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/StepTime')*1000; % in ms + catch + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/ScientaStepTime')*1000; + end end end end @@ -649,23 +749,31 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element % Acquisition mode try - TmpFileList{2}(ii).scientaAcquisitionMode = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/AcquisitionMode'); + TmpFileList{2}(ii).scientaAcquisitionMode = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/snaps/AcquisitionMode'); catch - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/AcquireMode'); - if tmp(1) == 0 - TmpFileList{2}(ii).scientaAcquisitionMode = "Swept"; - elseif tmp(1) == 1 - TmpFileList{2}(ii).scientaAcquisitionMode = "Fixed"; + try + TmpFileList{2}(ii).scientaAcquisitionMode = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/AcquisitionMode'); + catch + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/AcquireMode'); + if tmp(1) == 0 + TmpFileList{2}(ii).scientaAcquisitionMode = "Swept"; + elseif tmp(1) == 1 + TmpFileList{2}(ii).scientaAcquisitionMode = "Fixed"; + end + clear tmp end - clear tmp end if strcmp(TmpFileList{2}(ii).scientaAcquisitionMode,'Fixed') % NEXAFS kinetic energy window - fixed mode try - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/ScientaCenterEnergy'); % in eV + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/snaps/ScientaCenterEnergy'); % in eV catch - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/CenterEnergy'); % in eV + try + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/ScientaCenterEnergy'); % in eV + catch + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/CenterEnergy'); % in eV + end end TmpFileList{2}(ii).scientaEkinRange_eV = [double(tmp(1))-TmpFileList{2}(ii).scientaPassEnergy_eV/20 double(tmp(1))+TmpFileList{2}(ii).scientaPassEnergy_eV/20]; @@ -677,19 +785,28 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element else % NEXAFS kinetic energy window - scan mode try - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/ScientaLowEnergy'); % in eV - tmp2 = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/ScientaHighEnergy'); + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/snaps/ScientaLowEnergy'); % in eV + tmp2 = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/snaps/ScientaHighEnergy'); catch - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/LowEnergy'); % in eV - tmp2 = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/HighEnergy'); + try + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/ScientaLowEnergy'); % in eV + tmp2 = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/ScientaHighEnergy'); + catch + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/LowEnergy'); % in eV + tmp2 = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/HighEnergy'); + end end TmpFileList{2}(ii).scientaEkinRange_eV = [tmp(1) tmp2(1)]; clearvars tmp try - TmpFileList{2}(ii).scientaEkinStep_eV = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/StepSize'); + TmpFileList{2}(ii).scientaEkinStep_eV = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/snaps/StepSize'); catch - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/StepSize'); - TmpFileList{2}(ii).scientaEkinStep_eV = abs(tmp(1)); + try + TmpFileList{2}(ii).scientaEkinStep_eV = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/StepSize'); + catch + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/StepSize'); + TmpFileList{2}(ii).scientaEkinStep_eV = abs(tmp(1)); + end end TmpFileList{2}(ii).scientaEkin_eV = [TmpFileList{2}(ii).scientaEkinRange_eV(:,1):TmpFileList{2}(ii).scientaEkinStep_eV(1):TmpFileList{2}(ii).scientaEkinRange_eV(:,2)]'; end @@ -697,8 +814,12 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element clearvars tmp % read I_0 try - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/i0'); + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/i0'); catch + try + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/i0'); + catch + end end try tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/RM2I0'); @@ -715,22 +836,31 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element % Lens Mode try - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/LensMode'); - TmpFileList{2}(ii).scientaLensMode = tmp{1}; + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/snaps/LensMode'); + TmpFileList{2}(ii).scientaLensMode = tmp; catch - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/LensMode'); - TmpFileList{2}(ii).scientaLensMode = num2str(tmp(1)); + try + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/LensMode'); + TmpFileList{2}(ii).scientaLensMode = tmp{1}; + catch + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/LensMode'); + TmpFileList{2}(ii).scientaLensMode = num2str(tmp(1)); + end end clearvars tmp try - TmpFileList{2}(ii).scientaRegionIterations = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/NumIterations'); + TmpFileList{2}(ii).scientaRegionIterations = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan_1/snaps/NumIterations'); catch try - tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/NumIterations'); - TmpFileList{2}(ii).scientaRegionIterations = double(tmp(1)); + TmpFileList{2}(ii).scientaRegionIterations = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/NumIterations'); catch - TmpFileList{2}(ii).scientaRegionIterations = 1; + try + tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/NumIterations'); + TmpFileList{2}(ii).scientaRegionIterations = double(tmp(1)); + catch + TmpFileList{2}(ii).scientaRegionIterations = 1; + end end end TmpFileList{2}(ii).scientaSequenceIterations = 1; @@ -894,6 +1024,22 @@ if matches(p.Results.importLabView,'on') warning('Problem with LabView file') disp('Problem reading LabView file') end + + try + % Get list of all files in that folder + TmpFileList = dir([dataPath_LabView filesep '**' filesep 'logfile*.txt']); + + % import LabView data + LabView = LabView_importData(fullfile(TmpFileList(1).folder,TmpFileList(1).name)); % Overwrite LabView data in workspace, if this is the first import + if size(TmpFileList,1) > 1 % for additional data files, add these to the LabView data structure. + for ii = 2:size(TmpFileList,1) + LabView = LabView_importData(fullfile(TmpFileList(ii).folder,TmpFileList(ii).name),LabView); + end + end + catch + warning('Problem with LabView file') + disp('Problem reading LabView file') + end end disp('*************************************************') disp('* Voila: Done reading LabView data! *') @@ -1356,4 +1502,157 @@ end +end + +function splitH5Scans(infile) +% splitH5Scans - Split an HDF5 file containing multiple scans into separate files +% +% This function takes an HDF5 file with multiple scan groups (scan_1, scan_2, etc.) +% and creates separate files for each scan. Each output file contains only one +% scan (renamed to scan_1) plus the logs group. The original file is deleted after +% successful splitting. +% +% SYNTAX: +% splitH5Scans(infile) +% +% INPUT: +% infile - String containing the full path to the input HDF5 file +% Example: '0002_20251119_145823_O_Kedge_Nov25.h5' +% +% OUTPUT: +% Creates new HDF5 files in the same directory as the input file. +% Each file is named: [original_name]_scan_N.h5 +% Example outputs: +% 0002_20251119_145823_O_Kedge_Nov25_scan_1.h5 +% 0002_20251119_145823_O_Kedge_Nov25_scan_2.h5 +% +% REQUIREMENTS: +% - MATLAB with HDF5 support (R2011a or later) +% - Input file must have more than 3 root-level groups to be processed +% +% EXAMPLE: +% splitH5Scans('0002_20251119_145823_O_Kedge_Nov25.h5'); +% +% Author: Perplexity and Thorsten +% Date: November 2025 + +% Read the structure of the HDF5 file to see what groups it contains +% h5info returns information about groups, datasets, and attributes +fileinfo = h5info(infile); + +% Get the original file's modification date before we start + % This will be copied to all new files to preserve the original timestamp + orig_file_info = dir(infile); + orig_mod_datetime = orig_file_info.date; % Get as datetime object + + +% Extract the names of all groups at the root level of the file +% Example: {'/scan_1', '/scan_2', '/logs', '/__DATA_TYPES__'} +groupnames = {fileinfo.Groups.Name}; + +% Only process files that have more than 3 groups at the root level +% This ensures we only split files with multiple scans +if numel(groupnames) > 3 + + % Find which groups are scan groups (contain 'scan_' in their name) + % This returns a logical array: true for scan groups, false otherwise + is_scan = contains(groupnames, 'scan_'); + + % Extract only the scan group names + % Example: {'/scan_1', '/scan_2'} + scan_names = groupnames(is_scan); + + % Split the input filename into its components + % filepath: directory where file is located (e.g., 'C:\Data\') + % base: filename without extension (e.g., '0002_20251119_145823_O_Kedge_Nov25') + % ext: file extension (e.g., '.h5') + [filepath, base, ext] = fileparts(infile); + + % If no directory path, use current directory + if isempty(filepath) + filepath = '.'; + end + + % Extract the file number prefix and rest of the name using pattern matching + % regexp splits '0002_20251119_145823_O_Kedge_Nov25' into: + % prefix = '0002_' + % rest = '20251119_145823_O_Kedge_Nov25' + pieces = regexp(base, '(\d+_)(.*)', 'tokens', 'once'); + prefix = pieces{1}; % The number at the start (e.g., '0002_') + rest = pieces{2}; % Everything after the number + + % Loop through each scan group and create a separate file for it + for k = 1:numel(scan_names) + + % Get the full path of the current scan group + % Example: '/scan_1' or '/scan_2' + scan_group_full = scan_names{k}; + + % Remove the leading '/' from the scan name for use in filename + % Example: '/scan_2' becomes 'scan_2' + scan_name_clean = regexprep(scan_group_full, '^/', ''); + + % Build the output filename in the same directory as input file + % Example: 'C:\Data\0002_20251119_145823_O_Kedge_Nov25_scan_2.h5' + outname = fullfile(filepath, sprintf('%s%s_%s%s', prefix, rest, scan_name_clean, ext)); + + % Copy the entire original file to create the new output file + % We'll modify this copy to keep only one scan + copyfile(infile, outname); + + % Open the new file for reading and writing using low-level HDF5 functions + % fid is a file identifier used to reference this file + % 'H5F_ACC_RDWR' means read and write access + fid = H5F.open(outname, 'H5F_ACC_RDWR', 'H5P_DEFAULT'); + + % Delete all scan groups except the one we want to keep + % Loop through all scan groups in the original file + for j = 1:numel(scan_names) + other_scan = scan_names{j}; % Get the name of another scan group + + % If this is not the scan we want to keep, delete it + if j ~= k + try + % H5L.delete removes the link (reference) to the group + % This makes the group inaccessible but doesn't free space + H5L.delete(fid, other_scan, 'H5P_DEFAULT'); + catch + % If deletion fails (e.g., group doesn't exist), continue + % The catch block prevents the script from stopping on errors + end + end + end + + % Rename the kept scan group to '/scan_1' for consistency + % This way every output file has a group called '/scan_1' + if ~strcmp(scan_group_full, '/scan_1') + % H5L.move renames/moves a group within the file + % Parameters: source_file, old_path, dest_file, new_path, options + H5L.move(fid, scan_group_full, fid, '/scan_1', 'H5P_DEFAULT', 'H5P_DEFAULT'); + end + + % Close the HDF5 file to save all changes + H5F.close(fid); + + % Copy the modification date from the original file to the new file + % Convert MATLAB datenum to timestamp format: YYYYMMDDhhmm.ss + timestamp_str = string(datetime(orig_mod_datetime), 'yyyyMMddHHmm.ss'); + if ismac + touch_cmd = sprintf('touch -t %s "%s"', timestamp_str, outname); + system(touch_cmd); + end + + % Print confirmation message showing which file was created + % fprintf('Created: %s\n', outname); + end + + % After all split files are successfully created, delete the original file + delete(infile); + % fprintf('Deleted original file: %s\n', infile); + +else + % If the file has 3 or fewer groups, it doesn't need splitting + % disp('File has three or fewer groups. No processing required.'); +end + end