Files
bartels-rausch 2ec52b7ee7 Add openBIS sync pipeline; fix nappFileList/nappGenerateSpectra/nappPlot bugs; add importData.m ground script
beamtime/nappFileList.m
- Fix h5 LensMode: translate numeric codes to descriptive text (e.g. 1 ->
  T_HiPPHAXPES) to match what .ibw files already store, applied across all
  three h5read fallback branches, not just the oldest-format one

beamtime/nappGenerateSpectra.m
- Bug fixes

beamtime/nappPlot.m
- Bug fixes

import/importData.m (new)
- Ground script for one beamtime: import (nappFileList,
  nappGenerateSpectra), sample/spot and photon-energy assignment per row
  range, campaign metadata (CustomProperties) and column
  descriptions/units set inline, tidy-up, and export to .csv/.mat/.h5
  (via nappExportH5). Leaves FileList in the workspace for PublishReport.m
  and callOpenbisSync.m to run against afterward.

import/nappExportH5.m (new)
- Export FileList + campaign metadata + raw source files to a
  self-contained HDF5 archive
- Low-level HDF5 API (H5F/H5G/H5D/H5A/H5T/H5S/H5P) to fix
  H5FD_truncate/"too many open files" errors from the top-level
  h5create/h5write functions each reopening the file internally
- Explicit rowIndex attribute per row group, since HDF5 group listing
  order isn't guaranteed to match table row order

openBIS/ (new)
- nappSyncToOpenbis.py, callOpenbisSync.m, testpybis.m, README.md
- openBIS sync pipeline: create-or-update per row matched by $name,
  correct lowercase property codes, timestamp formatting, parent linking,
  raw file upload, plot/photo/video thumbnail embedding, config/engine
  split for maintainability -- see README.md for full details
2026-08-31 14:31:39 +02:00

161 lines
11 KiB
Matlab

%% BeamTime2016PhoenixIceNapp
% add ../Functions to path (works on Windows and macOS)
funcDir = fullfile(fileparts(mfilename('fullpath')), '..', 'Functions');
funcDir = char(java.io.File(funcDir).getCanonicalPath); % resolves .. and symlinks cross-platform
if isfolder(funcDir)
addpath(funcDir);
else
warning('Functions directory not found: %s', funcDir);
end
clearvars
close all
clc
%% Import and Generate Spectra
% @DEP_IN: BeamTime2016PhoenixIceNapp
FileList = nappFileList('BeamTime2016PhoenixIceNapp','mode','local');
FileList = nappGenerateSpectra(FileList);
%% Add photon energy and sample type
idxSpotChange1 = find(matches(FileList.name,"PHOENIX0207207.ibw"));
idxSpotChange2 = find(matches(FileList.name,"PHOENIX0225225.ibw"));
idxSpotChange3 = find(matches(FileList.name,"PHOENIX0229229.ibw"));
idxSpotChange4 = find(matches(FileList.name,"PHOENIX0235235.ibw"));
idxSpotChange5 = find(matches(FileList.name,"PHOENIX0235235.ibw"));
idxSpotChange6 = find(matches(FileList.name,"PHOENIX0238238.ibw"));
isIBW = endsWith(FileList.name,'.ibw');
k = find(isIBW);
for ii = 1:length(k)
FileList(k(ii),:).xRayEkin_eV{:} = 2200;
end
for ii = 1:idxSpotChange1
FileList(ii,:).sample{:} = 'NaCl; H2O gas, Spot#1 (0/0/6)';
end
for ii = idxSpotChange1+1:idxSpotChange2
FileList(ii,:).sample{:} = 'NaCl; H2O gas, Spot#2 (0/0.6/6)';
end
for ii = idxSpotChange2+1:idxSpotChange3
FileList(ii,:).sample{:} = 'NaCl; H2O gas, Spot#3 (-1/0.6/6.6)';
end
for ii = idxSpotChange3+1:idxSpotChange4
FileList(ii,:).sample{:} = 'NaCl; H2O gas, Spot#4 (0/0.6/6.1)';
end
for ii = idxSpotChange4+1:idxSpotChange5
FileList(ii,:).sample{:} = 'NaCl; H2O gas, Spot#5 (0.5/1.7/5.8)';
end
for ii = idxSpotChange5+1:idxSpotChange6
FileList(ii,:).sample{:} = 'NaCl; H2O gas, Spot#6 (0/0.9/6)';
end
for ii = idxSpotChange6+1:308
FileList(ii,:).sample{:} = 'NaCl; H2O gas, Spot#6 (-1/0.4/6.4)';
end
%% Add meta-data (keywords)
propNames = ["campaignName","campaignLocation","campaignInstrument","projectName","contact","sourceDirectory","startDate","endDate","dataLevel","publicationDoi", "funding", "contactORCID", "publicationDataDoi", "rawDataDoi", "team"];
try
FileList = addprop(FileList, propNames, repmat("table",1,numel(propNames)));
catch
% properties already exist from a previous run -- fine, just update values below
end
FileList.Properties.CustomProperties.campaignName = "Beamtime Phoenix Spring 2016";
FileList.Properties.CustomProperties.campaignLocation = "SLS/Phoenix";
FileList.Properties.CustomProperties.campaignInstrument = "NappEndstation";
FileList.Properties.CustomProperties.projectName = "NappIce";
FileList.Properties.CustomProperties.contact = "thorsten.bartels-rausch@psi.ch";
FileList.Properties.CustomProperties.contactORCID = "0000-0002-7548-2572";
FileList.Properties.CustomProperties.team = "Thorsten Bartels-Rausch, Xiangrui Kong, Fabrizio Orlando, Luca Artiglia, Astrid Waldner,Thomas Huthwelker,Markus Ammann";
FileList.Properties.CustomProperties.sourceDirectory = FileList.folder{1};
FileList.Properties.CustomProperties.startDate = FileList(1,:).lastModifiedDatestr{:};
FileList.Properties.CustomProperties.endDate = FileList(end,:).lastModifiedDatestr{:};
FileList.Properties.CustomProperties.dataLevel = 0;
FileList.Properties.CustomProperties.publicationDoi = "10.5194/tc-15-2001-2021";
FileList.Properties.CustomProperties.publicationDataDoi = "10.16904/envidat.164";
FileList.Properties.CustomProperties.rawDataDoi = " ";
FileList.Properties.CustomProperties.funding = "SNF 149629";
% Adds human-readable column descriptions and units to the standard table
% properties Properties.VariableDescriptions / Properties.VariableUnits
meta.name = struct('desc','File name of the imported data or photo file.', 'unit','');
meta.folder = struct('desc','Full path to the folder containing the file.', 'unit','');
meta.creationDate = struct('desc','Acquisition timestamp: from instrument metadata for XPS/NEXAFS files, or OS file creation time for photos. Stored as a datevec [Y M D H MI S].', 'unit','');
meta.lastModifiedDatenum = struct('desc','OS file modification time, as a MATLAB datenum.', 'unit','');
meta.lastModifiedDatestr = struct('desc','OS file modification time, as a formatted date string.', 'unit','');
meta.image = struct('desc','2D spectral map (kinetic energy vs. iteration/scan), normalized to counts per second.', 'unit','counts/s');
meta.imageRoh = struct('desc','Raw copy of the 2D spectral map.', 'unit','counts/s');
meta.xRayEkin_eV = struct('desc','X-ray excitation (photon) energy: the scan axis for NEXAFS, or the single excitation energy used for an XPS region.', 'unit','eV');
meta.xRayEkinRange_eV = struct('desc','[min max] range of the X-ray excitation energy scan.', 'unit','eV');
meta.scientaPassEnergy_eV = struct('desc','Analyzer pass energy setting.', 'unit','eV');
meta.scientaDwellTime_ms = struct('desc','Aquisition (step) time per data point.', 'unit','ms');
meta.regionName = struct('desc','Name of the measured region, e.g. an XPS core level (''C1s''), a survey scan, or a NEXAFS edge.', 'unit','');
meta.scientaAcquisitionMode = struct('desc','Analyzer acquisition mode: ''Swept'' (scanning) or ''Fixed'' (fixed energy window).', 'unit','');
meta.scientaEkinRange_eV = struct('desc','[min max] kinetic energy range of the analyzer scan.', 'unit','eV');
meta.scientaEkinStep_eV = struct('desc','Kinetic energy step size between data points.', 'unit','eV');
meta.scientaEkin_eV = struct('desc','The kinetic energy axis of the spectrum.', 'unit','eV');
meta.scientaEkinRoh_eV = struct('desc','Raw copy of the kinetic energy axis of the spectrum, kept alongside scientaEkin_eV in case later processing modifies the data.', 'unit','eV');
meta.beamlineInt = struct('desc','Beamline (incident photon) intensity, I0, used to normalize NEXAFS signal.', 'unit','a.u.');
meta.scientaLensMode = struct('desc','Analyzer lens transmission mode.', 'unit','');
meta.scientaAnalyzerSlit = struct('desc','Analyzer entrance slit setting.', 'unit','');
meta.scientaWorkfunction_eV = struct('desc','Analyzer work function used in the kinetic-to-binding-energy conversion.', 'unit','eV');
meta.scientaRegionIterations = struct('desc','Number of repeated scans within one region measurement, averaged together during import.', 'unit','');
meta.scientaSequenceIterations = struct('desc','Number of sequence repeats.', 'unit','');
meta.importDate = struct('desc','Timestamp of when this row was generated by nappFileList.', 'unit','');
meta.analysisDir = struct('desc','Working directory (or calling script''s folder) at the time of import.', 'unit','');
meta.sample = struct('desc','Sample name or description, from the SpecsLab comment field (.xy files) or set later from the lab journal.', 'unit','');
meta.sampleTemp_dC = struct('desc','Sample (cryo-holder) temperature, from the LabView log.', 'unit','degC');
meta.cellPressure_mbar = struct('desc','Sample cell pressure, from the LabView Baratron log.', 'unit','mbar');
meta.iceTemp_dC = struct('desc','Ice equilibrium temperature, derived from cellPressure_mbar via the Marti & Mauersberger (1993) vapour-pressure parameterization.', 'unit','degC');
meta.smplX_mm = struct('desc','Sample holder X position.', 'unit','mm');
meta.smplY_mm = struct('desc','Sample holder Y position.', 'unit','mm');
meta.smplZ_mm = struct('desc','Sample holder Z position.', 'unit','mm');
meta.sealingTemp = struct('desc','Sealing temperature, from the LabView log.', 'unit','degC');
meta.originalFileName = struct('desc','Original file name before a multi-region Specs .xy file was split into one row per region.', 'unit','');
% added by generate spectra
meta.spectrum_countsPerSecond = struct('desc','1D spectrum for .ibw/.xy rows in ''mean''/''trapz'' mode: the mean (or trapz) of the image data -- already counts/s, since nappFileList normalizes by dwell time before storing image. Not populated for .h5/NEXAFS rows (their result goes to spectrum_counts instead).', 'unit','counts/s');
meta.spectrum_counts = struct('desc','1D spectrum: for .h5/NEXAFS rows, the sum or trapezoidal integral of the image over the selected kinetic-energy range (not time-normalized), for any mode; for .ibw/.xy rows, only populated when mode=''sum'' (sum over the selected iteration range).', 'unit','counts');
meta.logNappGenerateSpectraRange = struct('desc','[start end] range used to generate the spectrum: kinetic-energy limits for .h5 rows, or iteration indices for .ibw/.xy rows.', 'unit','eV (h5) or index (ibw/xy)');
meta.logNappGenerateSpectraDate = struct('desc','Logs when the spectrum was generated', 'unit','');
meta.logNappGenerateSpectraMode = struct('desc','Reduction mode used to generate the spectrum for this row (''mean'', ''sum'', ''trapz'', or ''AugerMeitnerMean'').', 'unit','');
meta.logNappGenerateSpectraCaller = struct('desc','File path of the script or function that called nappGenerateSpectra, for provenance.', 'unit','');
varNames = FileList.Properties.VariableNames;
descriptions = repmat({''}, 1, numel(varNames));
units = repmat({''}, 1, numel(varNames));
for k = 1:numel(varNames)
if isfield(meta, varNames{k})
descriptions{k} = meta.(varNames{k}).desc;
units{k} = meta.(varNames{k}).unit;
end
end
FileList.Properties.VariableDescriptions = descriptions;
FileList.Properties.VariableUnits = units;
%% Tidy up
FileList = removevars(FileList,{'smplX_mm','smplY_mm','smplZ_mm','scientaAnalyzerSlit','scientaWorkfunction_eV','sealingTemp'});
FileList.comment = repmat({''}, height(FileList), 1);
clearvars -except FileList
%% Export and Save
ExportList = removevars(FileList, {'image', 'scientaEkin_eV', 'beamlineInt', 'imageRoh', 'scientaEkinRoh_eV', 'xRayEkin_eV', 'spectrum_countsPerSecond', 'spectrum_counts' });
% @DEP_OUT: Apr2016_DataTable.csv
writetable(ExportList,fullfile([datestr(ExportList.lastModifiedDatenum(1),'mmmYYYY') '_DataList.csv']),'Delimiter',',');
% write to command line
whos('FileList'); % basic size/type info
disp('--- Table Properties ---')
disp(FileList.Properties) % show table properties (Description, UserData, VariableDescriptions, etc.)
disp('--- Summary of Table ---')
summary(FileList) % concise summary of variables and types
%%
clearvars -except FileList
% @DEP_OUT: Apr2016_DataTable.mat
save("Apr2016_DataTable.mat")
% @DEP_OUT: Apr2016_DataTable.h5
nappExportH5(FileList, "Apr2016_DataTable.h5");