Data import now works with 2016 data, too. Data tracking added to md file.

This commit is contained in:
bartels-rausch committed 2026-08-21 10:18:39 +02:00
1 parent 9c7a6f4705
commit 5b9c7608d7
27 files changed
+4047 -118

No files matched your search

+188
View File
@@ -0,0 +1,188 @@
function D = IBWread(FN);
% IBWread - read Igor wave from IBW file
% D = IBWread('Foo.ibw') reads Igor file Foo.ibw into struct D.
if exist('fullfilename', 'file'), FFN = fullfilename(FN, cd, '.ibw');
else, FFN = FN;
end
% read headers
[D.binHeader, D.waveHeader, FFN] = readIBWheaders(FN);
fid = fopen(FFN,'r');
fid = fopen(FFN,'r');
% For numeric waves, the type field is interpreted bitwise. One of the following bits, as represented by symbols defined in IgorBin.h, will be set:
%
% % #define NT_CMPLX 1 % Complex numbers.
% % #define NT_FP32 2 % 32 bit fp numbers.
% % #define NT_FP64 4 % 64 bit fp numbers.
% % #define NT_I8 8 % 8 bit signed integer. Requires Igor Pro 2.0 or later.
% % #define NT_I16 0x10 % 16 bit integer numbers. Requires Igor Pro 2.0 or later.
% % #define NT_I32 0x20 % 32 bit integer numbers. Requires Igor Pro 2.0 or later.
% % #define NT_UNSIGNED 0x40 % Makes above signed integers unsigned. Requires Igor Pro 3.0 or later.
% NT_FP64 64-bit floating point (double)
% NT_FP32 32-bit floating point (float)
% NT_I32 32 bit integer (long)
% NT_16 16 bit integer (short)
% NT_I8 8 bit integer (char)
datatype = D.waveHeader.type;
CMPLX = rem(datatype,2);
if CMPLX, datatype = datatype-1; end
switch datatype,
case 0, prec = '*char';
case 2, prec = 'single';
case 4, prec = 'double';
case 8, prec = 'int8';
case 16, prec = 'int16';
case 32, prec = 'int32';
case 64+8, prec = 'uint8';
case 64+16, prec = 'uint16';
case 64+32, prec = 'uint32';
otherwise, error('Invalid numerical datatype.');
end
D.bname = D.waveHeader.bname;
% The date/time is store as seconds since midnight, January 1, 1904.
D.creationDate = local_igorTime2vec(D.waveHeader.creationDate);
D.modDate = local_igorTime2vec(D.waveHeader.creationDate);
switch D.binHeader.version,
case 2,
% The version 2 file has the following general layout.
% BinHeader2 structure: 16 bytes
% WaveHeader2 structure excluding wData field; 110 bytes
% Wave data: Variable size
D.Nsam = D.waveHeader.npnts;
D.Ndim = 1;
% X value for point p = hsA*p + hsB
D.dx = D.waveHeader.hsA;
D.x0 = D.waveHeader.hsB;
D.x1 = D.x0+D.Nsam*D.dx;
fseek(fid, 16+110, 'bof');
NN = D.Nsam*(1+double(CMPLX)); % if complex, read twice as many numbers
D.y = fread(fid, NN, prec);
if CMPLX, D.y = D.y(1:2:end) + i*D.y(2:2:end); end; % Re & Im values are interleaved
case 5,
% The version 5 file has the following general layout.
% BinHeader5 structure 64 bytes
% WaveHeader5 structure excluding wData field 320 bytes
% Wave data Variable size
% Optional wave dependency formula Variable size
% Optional wave note data Variable size
D.Nsam = D.waveHeader.npnts;
D.Ndim = sum(D.waveHeader.nDim>0);
% X offset & spacing (documentation is obscure)
D.dx = D.waveHeader.sfA(1:D.Ndim);
D.x0 = D.waveHeader.sfB(1:D.Ndim);
D.x1 = nan; % D.x0+D.Nsam*D.dx;
fseek(fid, 64+320, 'bof');
NN = D.Nsam*(1+double(CMPLX)); % if complex, read twice as many numbers
if isequal(0,datatype), % text data
% Number of bytes of wave data = wfmSize - (sizeof(WaveHeader5) - 4)
NN = D.binHeader.wfmSize-320;
D.y = fread(fid, NN, prec)';
else, % numerical data
D.y = fread(fid, NN, prec);
end
if CMPLX, D.y = D.y(1:2:end) + i*D.y(2:2:end); end; % Re & Im values are interleaved
if ~isempty(D.y) && D.Ndim>1,
D.y = reshape(D.y, D.waveHeader.nDim(1:D.Ndim));
end
end
D.WaveNotes = local_readNotes(fid, D.binHeader);
if isequal(0,datatype), % partition text data into lines using string indices stored @ eof
D = local_partition_text(fid, D);
end
fseek(fid,0,'eof');
D.fileSize = ftell(fid);
fclose(fid);
%===============================
function DV=local_igorTime2vec(it);
% igor time -> date vector a la Matlab
% The date/time is store as seconds since midnight, January 1, 1904.
Nsec_4year = 126230400;
idate_1988 = 21*uint32(Nsec_4year); % # seconds between 1-jan-1904 & 1-jan-1988
it = double(it-idate_1988); % smaller numbers-> use ordinary numbers ("doubles")
M4 = floor(it/Nsec_4year);
YR = double(1988+4*M4);
it = double(it-M4*Nsec_4year);
last_it = it;
while 1, % subtract years as long as they fit
s = etime([YR+1 1 1 0 0 0], [YR 1 1 0 0 0]);
if it<s, break; end % we went too far
YR = YR+1;
it = it-s;
end
MNTH = 1;
while 1, % subtract months as long as they fit
s = etime([YR MNTH+1 1 0 0 0], [YR MNTH 1 0 0 0]);
if it<s, break; end % we went too far
MNTH = MNTH+1;
it = it-s;
end
DAY = 1;
while 1, % subtract days as long as they fit
s = etime([YR MNTH DAY+1 0 0 0], [YR MNTH DAY 0 0 0]);
if it<s, break; end % we went too far
DAY = DAY+1;
it = it-s;
end
HR = floor(it/60^2); it = it-HR*60^2;
MIN = floor(it/60); SEC = it-MIN*60;
DV = [YR, MNTH, DAY, HR, MIN, SEC];
if ~isequal(last_it, etime(DV, [1988+4*M4 1 1 0 0 0])),
error('date error');
end
function Nts = local_readNotes(fid, B);
switch B.version,
case 2,
% BinHeader2 structure 16 bytes
% WaveHeader2 structure excluding wData field 110 bytes
% Wave data Variable size
% 16 bytes of padding 16 bytes
dum = fread(fid,16,'char');
% Optional wave note data
Nts = fread(fid, B.noteSize, '*char').';
case 5,
% BinHeader5 structure 64 bytes
% WaveHeader5 structure excluding wData field 320 bytes
% Wave data Variable size
% Optional wave dependency formula Variable size
dum = fread(fid,B.formulaSize, 'char');
% Optional wave note data Variable size
Nts = fread(fid, B.noteSize, '*char').';
end
function D = local_partition_text(fid, D);
% read sizeindices and chop text accordingly, must be called after reading
% notes
bH = D.binHeader;
% skip other stuff in file
% The version 5 file has the following general layout.
% BinHeader5 structure 64 bytes
% WaveHeader5 structure excluding wData field 320 bytes
% Wave data Variable size
% Optional wave dependency formula Variable size
% Optional wave note data Variable size
% Optional extended data units data Variable size
% Optional extended dimension units data Variable size
% Optional dimension label data Variable size
% String indices used for text waves only Variable size
dum=fseek(fid, -bH.sIndicesSize, 'eof'); % string indices are last in file; it's easier to refer from end-of-file
% bH.sIndicesSize; # bytes of string indices stored for the wave if the wave type is text.
% String indices are used to determine the offset in the file and the number of bytes for each element of the text wave.
sIdx = [0 fread(fid, bH.sIndicesSize/4, 'uint32').'];
for ii=1:numel(sIdx)-1,
STR{ii,1} = D.y(sIdx(ii)+1:sIdx(ii+1));
end
D.y = STR;
% long sIndicesSize; The size of string indicies if this is a text wave.
+24 -9
View File
@@ -12,26 +12,41 @@ function relHum = Napp_RH_p(T_current,partial_pressure_water)
%
%
% *input*
% T_current in K: temperature
% RH in %: relative humidity (RH_{water})
% T_current in K: temperature; (format: scalar, or row/column vector)
% partial_pressure_water in mbar: same shape/length as T_current
%
% *output*
% partial pressure of water in mbar
% relative humidity in % (RH_{water}), same shape as T_current
%% input validation
if ~isvector(T_current) || ~isvector(partial_pressure_water)
error('Napp_RH_p:InvalidInput','T_current and partial_pressure_water must be scalars or vectors (row or column).');
end
if numel(T_current) ~= numel(partial_pressure_water)
error('Napp_RH_p:InvalidInput','T_current and partial_pressure_water must have the same number of elements.');
end
%%
%
% *water*:
% NIST webbook: Bridgeman and Aldrich, 1964 give parametrisation from 273
% to 303 K in bar. log10(p) = A - B/ (T+C).
A = 5.40221;
A = 5.40221;
B = 1838.675;
C = -31.737;
C = -31.737;
p_w = 10.^(A - B./(T_current+C)) .* 1E3; % mbar
RH = (partial_pressure_water*100)/p_w;
% work with linear-indexed vectors so mixed row/column orientations of
% T_current and partial_pressure_water don't trigger implicit expansion
p_w = 10.^(A - B./(T_current(:)+C)) .* 1E3; % mbar
RH = (partial_pressure_water(:).*100)./p_w; % elementwise, matches shape of T_current(:)
% return RH in the same shape as T_current
if isrow(T_current)
RH = RH.';
end
relHum = RH;
for i = 1:length(T_current)
fprintf('RH of %.0f percent at %.1f K (%.1f dC) and %.2f mbar .\n',RH,T_current,T_current-273.15,partial_pressure_water)
for i = 1:numel(T_current)
fprintf('RH of %.0f percent at %.1f K (%.1f dC) and %.2f mbar .\n',relHum(i),T_current(i),T_current(i)-273.15,partial_pressure_water(i))
end
end
+63
View File
@@ -0,0 +1,63 @@
function T = Napp_T_ice(p_current)
%Napp_T_ice marks one or more vapour pressures in a plot of the vapour
% pressure of ice between 200-273 K and returns the corresponding ice
% temperature(s). It also displays the temperature (in K and dC) at each
% pressure in the command window.
%
% * Napp_T_ice(p_current)
% * T = Napp_T_ice([0.5, 1.0, 2.0])
%
% *input*
% p_current in mbar: vapour pressure of ice; (format: scalar, or row/column
% vector)
%
% *output*
% temperature of ice in K (same shape as p_current)
%
% The double + mustBePositive validation in the arguments block gives type
% checking and editor argument hints (autocomplete) for the input. Size is
% checked separately below so both row and column vectors are accepted.
arguments
p_current double {mustBePositive}
end
if ~isvector(p_current)
error('Napp_T_ice:InvalidInput','p_current must be a scalar or a vector (row or column).');
end
%% Water vapour pressure above the ice.
T_range = 200:0.2:273; % K, temperature range for the reference curve
% Marti, J., and K. Mauersberger, Geophys. Res. Lett., 20, 363-366, 1993.
% parameterization from 169 - 273K in Pa: log10(p[Pa]) = A1/T + B
A_1 = -2663.5;
B_1 = 12.537;
p_i = 10.^(A_1./T_range + B_1).*1E-5.*1E3; % mbar, reference curve
%% plot the reference curve (once)
figure( 'Name', 'vapour pressure over ice')
axes('Parent',gcf,'XDir','reverse','FontSize',16);
box on
hold on
% vapour pressure over ice
plot(p_i,T_range,'LineWidth',2)
xlim([0 6.5]);
ylim([220 275]);
xticks(0:0.5:6.5)
grid on
grid minor
title('vapour pressure over ice');
xlabel('p [mbar]');
ylabel('temperature [K]');
%% loop through each pressure: add marker to the same plot and write to command line
T = zeros(size(p_current)); % preallocate output
for i = 1:length(p_current)
% invert log10(p[Pa]) = A1/T + B1 -> T = A1 / (log10(p[Pa]) - B1)
% p_current is in mbar, so p[Pa] = p_current ./ (1E-5.*1E3)
T(i) = A_1 ./ (log10(p_current(i)./(1E-5.*1E3)) - B_1); % K
plot(p_current(i),T(i),'x','MarkerSize',25,'LineWidth',3);
text(p_current(i),T(i),[' <- ' num2str(p_current(i)) ' mbar'],'FontSize',12);
fprintf('The ice temperature at %.2f mbar is %.1f K (%.3f dC) \n',p_current(i),T(i),T(i)-273.15)
end
end
+13 -5
View File
@@ -10,22 +10,30 @@ function Napp_p_RH(T_current,RH)
%
%
% *input*
% T_current in K: temperature
% RH in %: relative humidity (RH_{water})
% T_current in K: temperature; (format: scalar, or row/column vector)
% RH in %: relative humidity (RH_{water}); same shape/length as T_current
%
% *output*
% partial pressure of water in mbar
%% input validation
if ~isvector(T_current) || ~isvector(RH)
error('Napp_p_RH:InvalidInput','T_current and RH must be scalars or vectors (row or column).');
end
if numel(T_current) ~= numel(RH)
error('Napp_p_RH:InvalidInput','T_current and RH must have the same number of elements.');
end
%%
%
% *water*:
% NIST webbook: Bridgeman and Aldrich, 1964 give parametrisation from 273
% to 303 K in bar. log10(p) = A - B/ (T+C).
A = 5.40221;
A = 5.40221;
B = 1838.675;
C = -31.737;
C = -31.737;
p_w = 10.^(A - B./(T_current+C)) .* 1E3; % mbar
p_w = 10.^(A - B./(T_current+C)) .* 1E3; % mbar
for i = 1:length(T_current)
+7 -2
View File
@@ -7,10 +7,15 @@ function p = Napp_p_ice(T_current)
% * p = Napp_p_ice([253.15, 263.15, 273.15])
%
% *input*
% T_current in K: temperature of ice; (format: either single numbers or array)
% T_current in K: temperature of ice; (format: scalar, or row/column vector)
%
% *output*
% vapour pressure of ice in mbar
% vapour pressure of ice in mbar (same shape as T_current)
%% input validation
if ~isvector(T_current)
error('Napp_p_ice:InvalidInput','T_current must be a scalar or a vector (row or column).');
end
%% Water vapour pressure above the ice and in pure liquid water.
+136
View File
@@ -0,0 +1,136 @@
function ListFiles = QuickAndDirtyNexafsImport(DataTable)
%% QuickAndDirtyNexafsImport
%
[filePick,pathPick] = uigetfile({'*.h5'},...
'Select One or More Files',...
'MultiSelect', 'on');
% make lists for Nexafs
if ischar(filePick)
filePick = {filePick};
end
for ii = 1:size(filePick,2)
TmpFileList{2}(ii,1) = dir([pathPick filePick{ii}]);
end
for ii = 1:size(filePick,2)
try
TmpImage= h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/spectrum');
%else
catch
try
TmpImage = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/scienta spectrum');
catch
TmpData = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/detector/data');
%convert 3 dim matrix to 2 dim matrix
for i = 1:size(TmpData,3)%iterations
TmpImage(:,i) = TmpData(:,1,(i)); %#ok<*SAGROW>
end
end
end
% import Photon Energy (cols)
try %general try to ensure that script does not stop upon errors.
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
% Make unique
[TmpFileList{2}(ii).xRayEkin_eV, ind] = unique(TmpXrayEkin);
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));
catch
tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/PassEnergy'); % in eV
TmpFileList{2}(ii).scientaPassEnergy_eV = str2double(tmp(1));
end
% Acquisition mode
try
TmpFileList{2}(ii).scientaAcquisitionMode = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/AcquisitionMode');
catch
TmpFileList{2}(ii).scientaAcquisitionMode = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/AcquireMode');
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
catch
tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/CenterEnergy'); % in eV
end
TmpFileList{2}(ii).scientaEkinRange_eV = [tmp(1)-TmpFileList{2}(ii).scientaPassEnergy_eV/20 tmp(1)+TmpFileList{2}(ii).scientaPassEnergy_eV/20];
TmpFileList{2}(ii).scientaEkinStep_eV = NaN;
TmpFileList{2}(ii).scientaEkin_eV = linspace(TmpFileList{2}(ii).scientaEkinRange_eV(:,1)...
,TmpFileList{2}(ii).scientaEkinRange_eV(:,2),size(TmpFileList{2}(ii).image,1))';
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');
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
TmpFileList{2}(ii).scientaEkinRange_eV = [tmp(1) tmp2(1)];
try
TmpFileList{2}(ii).scientaEkinStep_eV = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/scan 1/attrs/StepSize');
catch
TmpFileList{2}(ii).scientaEkinStep_eV = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/StepSize');
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
catch
end
end
%% Merge lists into one
%
% get index of non-empty structures
tf =~cellfun(@isempty,TmpFileList);
% merge the non-empty list structures into one structure
ListFiles = cat(1,TmpFileList{tf});
% remove lines with empty images
for ii = 1:size(ListFiles,1)
tmpLogic(ii) = isempty(ListFiles(ii).image);
noncells = tmpLogic;
end
ListFiles(noncells) = [];
clearvars -except ListFiles DataTable
%%
%% Sort by date
% rename date fields and delete those fields not needed
[ListFiles.lastModifiedDatestr] = ListFiles.date;
[ListFiles.lastModifiedDatenum] = ListFiles.datenum;
% First, we need to convert into a table, because structures can not be sorted:
ListFiles = struct2table(ListFiles,'AsArray',true);
% Then, we sort the table based on the time (6th column, datenum)
ListFiles = sortrows(ListFiles,6);
% Then, remove un-needed columns
ListFiles = removevars(ListFiles,{'bytes','isdir','date','datenum'});
% merge tables (append imported data to previous table)
if exist("DataTable",'var')
ListFiles = outerjoinRowToTable(ListFiles,DataTable);
end
% Then, we sort the table based on the time (datenum)
ListFiles = sortrows(ListFiles,'lastModifiedDatenum');
end
+134
View File
@@ -0,0 +1,134 @@
%% Develpment of hdf export and import
%
% from table to our instrument hdf structure
%% Todo
%
% * ensure logging of processing in NEXAFS and XPS to select data, make it
% usfull for string operations (no cells)
% * How to include units and definitions
% * Next, make this work for averageded files
%%
clc
% % add some SES data
%tmpFileImport = load('../beamtimes/20220828BeamTimeIceNappIss/FileList.mat');
% FileList = outerjoinRowToTable(FileList, tmpFileImport.FileList);
clearvars tmp*
% select only data that has been used,
[tmpn1, ~] = cellfun(@size,FileList.spectrum_counts); % spectra have been generated
[tmpn2, ~] = cellfun(@size,FileList.spectrum_countsNorm);
[tmpn3, ~] = cellfun(@size,FileList.spectrum_countsPerSecond);
tmpDataSelection = contains(FileList.sample,lettersPattern) ... % we have added sample notes, so this is data
& ~contains(FileList.sample, '(bad data)') & ~contains(FileList.sample, '(no notes)') ...
& (tmpn1 > 1 | tmpn2 > 1 | tmpn3 > 1); % has actually been proceesed.
tmpUserName = 'ThorstenBR';
tmpFileList = FileList(tmpDataSelection,:);
% get table variables that are data
tmpMissingVariable = ~ismember(tmpFileList.Properties.VariableNames,...
[{'image'}; {'spectrum_counts'}; {'spectrum_countsPerSecond'}; {'spectrum_countsNorm'}; {'xRayEkin_eV'}; {'scientaEkin_eV'}; {'imageRoh'}; {'spectrum_countsPerSecondRoh'};{'beamlineInt'}]);
tmpVariablesToBeAdded = tmpFileList.Properties.VariableNames(tmpMissingVariable);
for ij = 1:size(tmpFileList,1)
% get file name and "instruement" name
tmpPathParts = split(tmpFileList(ij,:).folder,"/");
tmpFileName = [tmpPathParts{end-1} tmpUserName '.h5'];
tmpTopGroupName = tmpPathParts{end};
% write data
h5create(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/image'],...
size(tmpFileList(ij,:).image{:}));
h5write(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/image'],...
tmpFileList(ij,:).image{:})
if %XPS
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/image'],'label','electron emission intensity');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/image'],'units','XPS (y,x): counts per second vs. electron volt; NEXAFS (y,x,z): counts vs. X-ray photon kinetic energy vs. electron kinetic energy');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/image'],'definition','X-ray excited electron emission');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/image'],'comment','XPS: intensity vs. electron kinetic energy for each sequence iteration; NEXAFS: intensity vs. x-ray photon kinetic energy (non-linear scaling) vs. electron kinetic energy');
else %NEXAFS
end
try
h5create(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_counts'],...
size(tmpFileList(ij,:).spectrum_counts{:}));
h5write(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_counts'],...
tmpFileList(ij,:).spectrum_counts{:})
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_counts'],'label','electron emission intensity');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_counts'],'units','arb. unit');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_counts'],'definition','X-ray excited electron emission');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_counts'],'comment',' ');
catch
end
try
h5create(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsPerSecond'],...
size(tmpFileList(ij,:).spectrum_countsPerSecond{:}));
h5write(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsPerSecond'],...
tmpFileList(ij,:).spectrum_countsPerSecond{:})
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsPerSecond'],'label',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsPerSecond'],'units',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsPerSecond'],'definition',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsPerSecond'],'comment',' ');
catch
end
try
h5create(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsNorm'],...
size(tmpFileList(ij,:).spectrum_countsNorm{:}));
h5write(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsNorm'],...
tmpFileList(ij,:).spectrum_countsNorm{:})
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsNorm'],'label',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsNorm'],'units',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsNorm'],'definition',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/spectrum_countsNorm'],'comment',' ');
catch
end
try
h5create(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/beamlineInt'],...
size(tmpFileList(ij,:).beamlineInt{:}));
h5write(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/beamlineInt'],...
tmpFileList(ij,:).beamlineInt{:})
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/beamlineInt'],'label',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/beamlineInt'],'units',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/beamlineInt'],'definition',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/beamlineInt'],'comment',' ');
catch
end
h5create(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/xRayEkin_eV'],...
size(tmpFileList(ij,:).xRayEkin_eV{:}));
h5write(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/xRayEkin_eV'],...
tmpFileList(ij,:).xRayEkin_eV{:})
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/xRayEkin_eV'],'label',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/xRayEkin_eV'],'units',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/xRayEkin_eV'],'definition',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/xRayEkin_eV'],'comment',' ');
h5create(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/scientaEkin_eV'],...
size(tmpFileList(ij,:).scientaEkin_eV{:}));
h5write(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/scientaEkin_eV'],...
tmpFileList(ij,:).scientaEkin_eV{:})
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/scientaEkin_eV'],'label',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/scientaEkin_eV'],'units',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/scientaEkin_eV'],'definition',' ');
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:} '/scientaEkin_eV'],'comment',' ');
% add attributes
tmpCells= []; % create empty array
for ii = 1:size(tmpVariablesToBeAdded,2)
if matches(class(tmpFileList.(tmpVariablesToBeAdded{ii})),'cell')
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:}],tmpVariablesToBeAdded{ii},tmpFileList(ij,:).(tmpVariablesToBeAdded{ii}){:});
tmpCells = [tmpCells,', ' tmpVariablesToBeAdded{ii}];
else
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:}],tmpVariablesToBeAdded{ii},tmpFileList(ij,:).(tmpVariablesToBeAdded{ii}));
end
% write variables that were cells to a list; so upon import the
% variable type can be set again.
h5writeatt(tmpFileName,['/' tmpTopGroupName '/' tmpFileList(ij,:).name{:}],'matlabVariablesThatAreCells',tmpCells);
end
clearvars tmpCells
end
disp('*************************************************')
disp('* Voila: HDF file(s) written *')
disp('*************************************************')
+473
View File
@@ -0,0 +1,473 @@
function [map,num,typ,scheme] = brewermap(N,scheme)
% The complete selection of ColorBrewer colorschemes/palettes (RGB colormaps).
%
% Returns an RGB colormap from Cynthia Brewer's ColorBrewer 2.0 palettes,
% intended for mapping and plots with attractive, distinguishable colors.
%
%%% Basic Syntax %%%
%
% brewermap() % print summary
% map = brewermap(N,scheme)
%
%%% Preset Syntax %%%
%
% old = brewermap(scheme)
% map = brewermap()
% map = brewermap(N)
%
% [...,num,typ] = brewermap(...)
%
%% Colorschemes %%
%
% This product includes color specifications and designs developed by Cynthia Brewer.
% See the ColorBrewer website for further information about each colorscheme,
% colour-blind suitability, licensing, and citations: http://colorbrewer.org/
% Each colorscheme is defined by a set of hand-picked RGB values (nodes).
%
% To reverse the colormap sequence prefix the colorscheme name with '-'.
%
% Diverging | Qualitative | Sequential
% ----------|-------------|------------------
% BrBG | Accent | Blues PuBuGn
% PiYG | Dark2 | BuGn PuRd
% PRGn | Paired | BuPu Purples
% PuOr | Pastel1 | GnBu RdPu
% RdBu | Pastel2 | Greens Reds
% RdGy | Set1 | Greys YlGn
% RdYlBu | Set2 | OrRd YlGnBu
% RdYlGn | Set3 | Oranges YlOrBr
% Spectral | | PuBu YlOrRd
%
% If <N> is greater than the requested colorscheme's defining nodes then:
% - Diverging and Sequential colorschemes are interpolated.
% - Qualitative colorschemes repeat the nodes (i.e. just like LINES does).
% Else:
% - Exact values from the ColorBrewer colorschemes are returned.
%
%% Examples %%
%
%%% New colors for the COLORMAP example %%%
%
% >> S = load('spine');
% >> image(S.X)
% >> colormap(brewermap([],"YlGnBu"))
%
%%% New colors for the SURF example %%%
%
% >> [X,Y,Z] = peaks(30);
% >> surfc(X,Y,Z)
% >> colormap(brewermap([],'RdYlGn'))
% >> axis([-3,3,-3,3,-10,5])
%
%%% Plot a colorscheme's RGB values %%%
%
% >> rgbplot(brewermap(NaN, '+Blues')) % standard
% >> rgbplot(brewermap(NaN, '-Blues')) % reversed
%
%%% View information about a colorscheme %%%
%
% >> [~,num,typ] = brewermap(NaN,'Paired')
% num = 12
% typ = 'Qualitative'
%
%%% Multi-line plot using matrices %%%
%
% >> N = 6;
% >> axes('ColorOrder',brewermap(N,'Pastel2'),'NextPlot','replacechildren')
% >> X = linspace(0,pi*3,1000);
% >> Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X(:), 1:N);
% >> plot(X,Y, 'linewidth',4)
%
%%% Multi-line plot in a loop %%%
%
% set(0,'DefaultAxesColorOrder',brewermap(NaN,'Accent'))
% N = 6;
% X = linspace(0,pi*3,1000);
% Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X(:), 1:N);
% for n = 1:N
% plot(X(:),Y(:,n), 'linewidth',4);
% hold all
% end
%
%% Input Arguments (**=default) %%
%
% N = NumericScalar, N>=0, an integer to specify the colormap length.
% = []**, map has the same length as MATLAB's inbuilt colormap functions.
% = NaN, map is exactly the defining RGB nodes (useful for line ColorOrder).
% scheme = CharRowVector or StringScalar, a ColorBrewer colorscheme name.
%
%% Output Arguments %%
%
% map = NumericMatrix, size Nx3, a colormap of RGB values between 0 and 1.
% num = NumericVector, the number of nodes defining the ColorBrewer colorscheme.
% typ = CharRowVector, the colorscheme type: 'Diverging'/'Qualitative'/'Sequential'.
%
%% Dependencies %%
%
% * MATLAB R2009a or later.
%
% See also BREWERMAP_NODES BREWERMAP_VIEW PRESET_COLORMAP CUBEHELIX MAXDISTCOLOR
% LBMAP PARULA LINES RGBPLOT COLORMAP COLORBAR PLOT PLOT3 AXES SET CONTOURF
persistent bmc scm txt
%
if isempty(bmc)
bmc = bmStruct();
end
%
%% Input Wrangling %%
%
if nargin==0
N = [];
end
%
err = 'First input <N> must be a real positive scalar numeric or [] or NaN.';
%
if nargout==0 && nargin==0
hdr = { 'Type'; 'Scheme'; 'Nodes'};
tsn = [{bmc.typ};{bmc.str};{bmc.num}];
fprintf('%-12s %-9s %s\n',hdr{:});
fprintf('%-12s %-9s %u\n',tsn{:});
return
elseif isnumeric(N)
if isequal(N,[])
% Default N is the same as MATLAB colormaps:
N = cmDefaultN();
else
assert(isscalar(N),...
'SC:brewermap:N:NotScalarNumeric',err)
assert(isnan(N) || isreal(N)&&isfinite(N)&&fix(N)==N&&N>=0,...
'SC:brewermap:N:NotWholeRealNotNaN',err)
N = double(N);
end
if nargin<2
assert(~isempty(scm),...
'SC:colorbrewer:scheme:NotPreset',...
'Colorscheme must be preset before this call: BREWERMAP(SCHEME)')
scheme = scm;
else
scheme = bm1s2c(scheme);
assert(ischar(scheme)&&ndims(scheme)==2&&size(scheme,1)==1,...
'SC:brewermap:scheme:NotText',...
'Input <scheme> must be a character vector or a string scalar.') %#ok<ISMAT>
end
else % preset
assert(nargin<2,...
'SC:brewermap:WrongArgumentNumber',...
'If calling with two inputs then the 1st input must be numeric.')
scheme = bm1s2c(N);
assert(ischar(scheme)&&ndims(scheme)==2&&size(scheme,1)==1,...
'SC:brewermap:N:NotText',...
'To preset: the scheme must be a character vector or a string scalar.') %#ok<ISMAT>
if strcmpi(scheme,'list')
map = {bmc.str};
num = [bmc.num];
typ = {bmc.typ};
return
end
end
%
isr = strncmp(scheme,'-',1) || strncmp(scheme,'*',1);
isd = strncmp(scheme,'+',1) || isr; % direction
ids = strcmpi(scheme(1+isd:end),{bmc.str});
assert(any(ids),...
'SC:brewermap:scheme:UnknownColorscheme',...
'Unknown colorscheme name: "%s"',scheme)
%
num = bmc(ids).num;
typ = bmc(ids).typ;
%
if ~isnumeric(N) % preset
map = txt;
txt = N;
scm = scheme;
return
elseif N==0
map = nan(0,3);
return
elseif isnan(N)
N = num;
end
%
% Downsample:
[idx,itp] = bmIndex(N,num,typ);
%
map = bmc(ids).rgb(idx,:)/255;
%
% Interpolate:
if itp
%
% Extrapolate a small amount beyond the end nodes:
%ido = linspace(0,num+1,N+2);
%ido = ido(2:end-1);
% Interpolation completely within the end nodes:
ido = linspace(1,num,N);
%
switch typ
case 'Diverging'
mid = ceil(num/2);
idl = 1:mid;
idr = mid:num;
map = [...
interp1(idl,map(idl,:),ido(ido<=mid),'pchip');...
interp1(idr,map(idr,:),ido(ido>mid),'pchip')];
case 'Sequential'
map = interp1(1:num,map,ido,'pchip');
otherwise
error('SC:brewermap:Interp:UnknownType','Unknown colorscheme type.')
end
%
end
%
% Limit output range:
map = max(0,min(1,map));
%
% Reverse row order:
if isr
map = map(end:-1:1,:);
end
%
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%brewermap
function N = cmDefaultN()
% Get the colormap size from the current figure or default colormap.
try
F = get(groot,'CurrentFigure');
catch %#ok<CTCH> pre HG2
N = size(get(gcf,'colormap'),1);
return
end
if isempty(F)
N = size(get(groot,'DefaultFigureColormap'),1);
else
N = size(F.Colormap,1);
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%cmDefaultN
function arr = bm1s2c(arr)
% If scalar string then extract the character vector, otherwise data is unchanged.
if isa(arr,'string') && isscalar(arr)
arr = arr{1};
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bm1s2c
function [idx,itp] = bmIndex(N,num,typ)
% Ensure exactly the same colors as the online ColorBrewer colorschemes.
%
switch typ
case 'Qualitative'
itp = false;
idx = 1+mod(0:N-1,num);
case 'Diverging'
itp = N>num;
switch N
case 1 % extrapolated
idx = 8;
case 2 % extrapolated
idx = [4,12];
case 3
idx = [5,8,11];
case 4
idx = [3,6,10,13];
case 5
idx = [3,6,8,10,13];
case 6
idx = [2,5,7,9,11,14];
case 7
idx = [2,5,7,8,9,11,14];
case 8
idx = [2,4,6,7,9,10,12,14];
case 9
idx = [2,4,6,7,8,9,10,12,14];
case 10
idx = [1,2,4,6,7,9,10,12,14,15];
otherwise
idx = [1,2,4,6,7,8,9,10,12,14,15];
end
case 'Sequential'
itp = N>num;
switch N
case 1 % extrapolated
idx = 6;
case 2 % extrapolated
idx = [4,8];
case 3
idx = [3,6,9];
case 4
idx = [2,5,7,10];
case 5
idx = [2,5,7,9,11];
case 6
idx = [2,4,6,7,9,11];
case 7
idx = [2,4,6,7,8,10,12];
case 8
idx = [1,3,4,6,7,8,10,12];
otherwise
idx = [1,3,4,6,7,8,10,11,13];
end
otherwise
error('SC:brewermap:Index:UnknownType','Unknown colorscheme type.')
end
%
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmIndex
function bmc = bmStruct()
% Return a structure of all colorschemes: name, scheme type, RGB values, number of nodes.
% Order: sorted first by <typ>, then case-insensitive sorted by <str>.
bmc(35).str = 'YlOrRd';
bmc(35).typ = 'Sequential';
bmc(35).rgb = [255,255,204;255,255,178;255,237,160;254,217,118;254,204,92;254,178,76;253,141,60;252,78,42;240,59,32;227,26,28;189,0,38;177,0,38;128,0,38];
bmc(34).str = 'YlOrBr';
bmc(34).typ = 'Sequential';
bmc(34).rgb = [255,255,229;255,255,212;255,247,188;254,227,145;254,217,142;254,196,79;254,153,41;236,112,20;217,95,14;204,76,2;153,52,4;140,45,4;102,37,6];
bmc(33).str = 'YlGnBu';
bmc(33).typ = 'Sequential';
bmc(33).rgb = [255,255,217;255,255,204;237,248,177;199,233,180;161,218,180;127,205,187;65,182,196;29,145,192;44,127,184;34,94,168;37,52,148;12,44,132;8,29,88];
bmc(32).str = 'YlGn';
bmc(32).typ = 'Sequential';
bmc(32).rgb = [255,255,229;255,255,204;247,252,185;217,240,163;194,230,153;173,221,142;120,198,121;65,171,93;49,163,84;35,132,67;0,104,55;0,90,50;0,69,41];
bmc(31).str = 'Reds';
bmc(31).typ = 'Sequential';
bmc(31).rgb = [255,245,240;254,229,217;254,224,210;252,187,161;252,174,145;252,146,114;251,106,74;239,59,44;222,45,38;203,24,29;165,15,21;153,0,13;103,0,13];
bmc(30).str = 'RdPu';
bmc(30).typ = 'Sequential';
bmc(30).rgb = [255,247,243;254,235,226;253,224,221;252,197,192;251,180,185;250,159,181;247,104,161;221,52,151;197,27,138;174,1,126;122,1,119;122,1,119;73,0,106];
bmc(29).str = 'Purples';
bmc(29).typ = 'Sequential';
bmc(29).rgb = [252,251,253;242,240,247;239,237,245;218,218,235;203,201,226;188,189,220;158,154,200;128,125,186;117,107,177;106,81,163;84,39,143;74,20,134;63,0,125];
bmc(28).str = 'PuRd';
bmc(28).typ = 'Sequential';
bmc(28).rgb = [247,244,249;241,238,246;231,225,239;212,185,218;215,181,216;201,148,199;223,101,176;231,41,138;221,28,119;206,18,86;152,0,67;145,0,63;103,0,31];
bmc(27).str = 'PuBuGn';
bmc(27).typ = 'Sequential';
bmc(27).rgb = [255,247,251;246,239,247;236,226,240;208,209,230;189,201,225;166,189,219;103,169,207;54,144,192;28,144,153;2,129,138;1,108,89;1,100,80;1,70,54];
bmc(26).str = 'PuBu';
bmc(26).typ = 'Sequential';
bmc(26).rgb = [255,247,251;241,238,246;236,231,242;208,209,230;189,201,225;166,189,219;116,169,207;54,144,192;43,140,190;5,112,176;4,90,141;3,78,123;2,56,88];
bmc(25).str = 'Oranges';
bmc(25).typ = 'Sequential';
bmc(25).rgb = [255,245,235;254,237,222;254,230,206;253,208,162;253,190,133;253,174,107;253,141,60;241,105,19;230,85,13;217,72,1;166,54,3;140,45,4;127,39,4];
bmc(24).str = 'OrRd';
bmc(24).typ = 'Sequential';
bmc(24).rgb = [255,247,236;254,240,217;254,232,200;253,212,158;253,204,138;253,187,132;252,141,89;239,101,72;227,74,51;215,48,31;179,0,0;153,0,0;127,0,0];
bmc(23).str = 'Greys';
bmc(23).typ = 'Sequential';
bmc(23).rgb = [255,255,255;247,247,247;240,240,240;217,217,217;204,204,204;189,189,189;150,150,150;115,115,115;99,99,99;82,82,82;37,37,37;37,37,37;0,0,0];
bmc(22).str = 'Greens';
bmc(22).typ = 'Sequential';
bmc(22).rgb = [247,252,245;237,248,233;229,245,224;199,233,192;186,228,179;161,217,155;116,196,118;65,171,93;49,163,84;35,139,69;0,109,44;0,90,50;0,68,27];
bmc(21).str = 'GnBu';
bmc(21).typ = 'Sequential';
bmc(21).rgb = [247,252,240;240,249,232;224,243,219;204,235,197;186,228,188;168,221,181;123,204,196;78,179,211;67,162,202;43,140,190;8,104,172;8,88,158;8,64,129];
bmc(20).str = 'BuPu';
bmc(20).typ = 'Sequential';
bmc(20).rgb = [247,252,253;237,248,251;224,236,244;191,211,230;179,205,227;158,188,218;140,150,198;140,107,177;136,86,167;136,65,157;129,15,124;110,1,107;77,0,75];
bmc(19).str = 'BuGn';
bmc(19).typ = 'Sequential';
bmc(19).rgb = [247,252,253;237,248,251;229,245,249;204,236,230;178,226,226;153,216,201;102,194,164;65,174,118;44,162,95;35,139,69;0,109,44;0,88,36;0,68,27];
bmc(18).str = 'Blues';
bmc(18).typ = 'Sequential';
bmc(18).rgb = [247,251,255;239,243,255;222,235,247;198,219,239;189,215,231;158,202,225;107,174,214;66,146,198;49,130,189;33,113,181;8,81,156;8,69,148;8,48,107];
bmc(17).str = 'Set3';
bmc(17).typ = 'Qualitative';
bmc(17).rgb = [141,211,199;255,255,179;190,186,218;251,128,114;128,177,211;253,180,98;179,222,105;252,205,229;217,217,217;188,128,189;204,235,197;255,237,111];
bmc(16).str = 'Set2';
bmc(16).typ = 'Qualitative';
bmc(16).rgb = [102,194,165;252,141,98;141,160,203;231,138,195;166,216,84;255,217,47;229,196,148;179,179,179];
bmc(15).str = 'Set1';
bmc(15).typ = 'Qualitative';
bmc(15).rgb = [228,26,28;55,126,184;77,175,74;152,78,163;255,127,0;255,255,51;166,86,40;247,129,191;153,153,153];
bmc(14).str = 'Pastel2';
bmc(14).typ = 'Qualitative';
bmc(14).rgb = [179,226,205;253,205,172;203,213,232;244,202,228;230,245,201;255,242,174;241,226,204;204,204,204];
bmc(13).str = 'Pastel1';
bmc(13).typ = 'Qualitative';
bmc(13).rgb = [251,180,174;179,205,227;204,235,197;222,203,228;254,217,166;255,255,204;229,216,189;253,218,236;242,242,242];
bmc(12).str = 'Paired';
bmc(12).typ = 'Qualitative';
bmc(12).rgb = [166,206,227;31,120,180;178,223,138;51,160,44;251,154,153;227,26,28;253,191,111;255,127,0;202,178,214;106,61,154;255,255,153;177,89,40];
bmc(11).str = 'Dark2';
bmc(11).typ = 'Qualitative';
bmc(11).rgb = [27,158,119;217,95,2;117,112,179;231,41,138;102,166,30;230,171,2;166,118,29;102,102,102];
bmc(10).str = 'Accent';
bmc(10).typ = 'Qualitative';
bmc(10).rgb = [127,201,127;190,174,212;253,192,134;255,255,153;56,108,176;240,2,127;191,91,23;102,102,102];
bmc(09).str = 'Spectral';
bmc(09).typ = 'Diverging';
bmc(09).rgb = [158,1,66;213,62,79;215,25,28;244,109,67;252,141,89;253,174,97;254,224,139;255,255,191;230,245,152;171,221,164;153,213,148;102,194,165;43,131,186;50,136,189;94,79,162];
bmc(08).str = 'RdYlGn';
bmc(08).typ = 'Diverging';
bmc(08).rgb = [165,0,38;215,48,39;215,25,28;244,109,67;252,141,89;253,174,97;254,224,139;255,255,191;217,239,139;166,217,106;145,207,96;102,189,99;26,150,65;26,152,80;0,104,55];
bmc(07).str = 'RdYlBu';
bmc(07).typ = 'Diverging';
bmc(07).rgb = [165,0,38;215,48,39;215,25,28;244,109,67;252,141,89;253,174,97;254,224,144;255,255,191;224,243,248;171,217,233;145,191,219;116,173,209;44,123,182;69,117,180;49,54,149];
bmc(06).str = 'RdGy';
bmc(06).typ = 'Diverging';
bmc(06).rgb = [103,0,31;178,24,43;202,0,32;214,96,77;239,138,98;244,165,130;253,219,199;255,255,255;224,224,224;186,186,186;153,153,153;135,135,135;64,64,64;77,77,77;26,26,26];
bmc(05).str = 'RdBu';
bmc(05).typ = 'Diverging';
bmc(05).rgb = [103,0,31;178,24,43;202,0,32;214,96,77;239,138,98;244,165,130;253,219,199;247,247,247;209,229,240;146,197,222;103,169,207;67,147,195;5,113,176;33,102,172;5,48,97];
bmc(04).str = 'PuOr';
bmc(04).typ = 'Diverging';
bmc(04).rgb = [127,59,8;179,88,6;230,97,1;224,130,20;241,163,64;253,184,99;254,224,182;247,247,247;216,218,235;178,171,210;153,142,195;128,115,172;94,60,153;84,39,136;45,0,75];
bmc(03).str = 'PRGn';
bmc(03).typ = 'Diverging';
bmc(03).rgb = [64,0,75;118,42,131;123,50,148;153,112,171;175,141,195;194,165,207;231,212,232;247,247,247;217,240,211;166,219,160;127,191,123;90,174,97;0,136,55;27,120,55;0,68,27];
bmc(02).str = 'PiYG';
bmc(02).typ = 'Diverging';
bmc(02).rgb = [142,1,82;197,27,125;208,28,139;222,119,174;233,163,201;241,182,218;253,224,239;247,247,247;230,245,208;184,225,134;161,215,106;127,188,65;77,172,38;77,146,33;39,100,25];
bmc(01).str = 'BrBG';
bmc(01).typ = 'Diverging';
bmc(01).rgb = [84,48,5;140,81,10;166,97,26;191,129,45;216,179,101;223,194,125;246,232,195;245,245,245;199,234,229;128,205,193;90,180,172;53,151,143;1,133,113;1,102,94;0,60,48];
% Number of nodes:
for k = 1:numel(bmc)
switch bmc(k).typ
case 'Diverging'
bmc(k).num = 11;
case 'Qualitative'
bmc(k).num = size(bmc(k).rgb,1);
case 'Sequential'
bmc(k).num = 9;
otherwise
error('SC:brewermap:Struct:UnknownType','Unknown colorscheme type.')
end
end
%
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmStruct
%
% Code and Implementation:
% Copyright (c) 2014-2025 Stephen Cobeldick
% Color Values Only:
% Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University.
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and limitations under the License.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions as source code must retain the above copyright notice, this
% list of conditions and the following disclaimer.
%
% 2. The end-user documentation included with the redistribution, if any, must
% include the following acknowledgment: "This product includes color
% specifications and designs developed by Cynthia Brewer
% (http://colorbrewer.org/)." Alternately, this acknowledgment may appear in the
% software itself, if and wherever such third-party acknowledgments normally appear.
%
% 4. The name "ColorBrewer" must not be used to endorse or promote products
% derived from this software without prior written permission. For written
% permission, please contact Cynthia Brewer at cbrewer@psu.edu.
%
% 5. Products derived from this software may not be called "ColorBrewer", nor
% may "ColorBrewer" appear in their name, without prior written permission of Cynthia Brewer.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%license
+63
View File
@@ -0,0 +1,63 @@
function brewermap_nodes()
% Create a figure showing all ColorBrewer 2.0 colorschemes' defining nodes.
%
% (c) 2014-2025 Stephen Cobeldick
%
% View Cynthia Brewer's ColorBrewer 2.0 colorschemes/palettes in a figure:
%
% * Each row of the figure shows the defining nodes for one colorscheme.
% * Text with all colorschemes' names.
% * Text with all colorschemes' types (Diverging/Qualitative/Sequential).
%
%%% Syntax %%%
%
% brewermap_nodes()
%
%% Dependencies %%
%
% * MATLAB R2009a or later.
% * brewermap.m <www.mathworks.com/matlabcentral/fileexchange/45208>
%
% See also BREWERMAP BREWERMAP_VIEW CUBEHELIX MAXDISTCOLOR
% LBMAP PARULA LINES RGBPLOT COLORMAP COLORBAR PLOT PLOT3 AXES SET
persistent cbh axh
%
[mcs,nmn,pyt] = brewermap('list');
%
xmx = max(nmn);
ymx = numel(pyt);
%
if ishghandle(cbh)
figure(cbh);
delete(axh);
else
cbh = figure('HandleVisibility','callback', 'IntegerHandle','off',...
'NumberTitle','off', 'Name',mfilename,...
'MenuBar','figure', 'Toolbar','none', 'Tag',mfilename);
set(cbh,'Units','pixels')
pos = get(cbh,'Position');
pos(1:2) = pos(1:2) - 123;
pos(3:4) = max(pos(3:4),[842,532]);
set(cbh,'Position',pos)
end
%
axh = axes('Parent',cbh, 'Color','none',...
'XTick',0:xmx, 'YTick',0.5:ymx, 'YTickLabel',mcs, 'YDir','reverse');
title(axh,'ColorBrewer Color Schemes (brewermap.m)', 'Interpreter','none')
xlabel(axh,'Scheme Nodes')
ylabel(axh,'Scheme Name')
axf = get(axh,'FontName');
%
for y = 1:ymx
N = nmn(y);
M = brewermap(N,mcs{y});
for x = 1:N
patch([x-1,x-1,x,x],[y-1,y,y,y-1],1, 'FaceColor',M(x,:), 'Parent',axh)
end
text(xmx+0.1,y-0.5,pyt{y}, 'Parent',axh, 'FontName',axf)
end
%
drawnow()
%
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%brewermap_nodes
+408
View File
@@ -0,0 +1,408 @@
function [map,num,typ,scheme] = brewermap_view(N,scheme,isco)
% Create an interactive figure for viewing ColorBrewer colormaps. With demo!
%
% (c) 2014-2025 Stephen Cobeldick
%
% View Cynthia Brewer's ColorBrewer 2.0 colorschemes/palettes in a figure:
%
% * Two colorbars give the colorscheme in color and grayscale.
% * A button toggles between 3D-cube and 2D-lineplot of the RGB values.
% * A button toggles an endless demo cycle through the colorschemes.
% * A button reverses the colormap.
% * Thirty-five buttons select any ColorBrewer colorscheme.
% * Text with the colorscheme's type (Diverging/Qualitative/Sequential).
% * Text with the colorscheme's number of nodes (i.e. defining colors).
%
%%% Syntax %%%
%
% brewermap_view
% brewermap_view(N)
% brewermap_view(N,scheme)
% brewermap_view(N,scheme,isco)
% brewermap_view([],...)
% brewermap_view(axes/figure handle array,...) % see "Adjust Colormaps"
% [map,num,typ] = brewermap_view(...)
%
% Calling the function with an output argument blocks MATLAB execution until
% the figure is deleted: the final colormap and parameters are then returned.
%
%% Adjust Colormaps or Colororders of Figures or Axes %%
%
% For R2014b or later: provide axes or figure handles as the first input
% and their COLORMAP will be updated in real-time by BREWERMAP_VIEW.
% For R2019b or later: set the 3rd input to TRUE to update COLORORDER.
%
% >> S = load('spine');
% >> image(S.X)
% >> brewermap_view(gca) % colormap
%
% >> plot(rand(5,7))
% >> brewermap_view(gca,[],true) % colororder
%
%% Input Arguments (**=default) %%
%
% N = NumericScalar, an integer to define the colormap length.
% = []**, colormap length of two hundred and fifty-six (256).
% = NaN, same length as the defining RGB nodes (useful for Line ColorOrder).
% = Array of axes or figure handles. R2014b or later only.
% scheme = CharRowVector or StringScalar, a ColorBrewer colorscheme name.
% = []** randomly selects a colorscheme.
% isco = LogicalScalar, true/false** updates the colororder/colormap of
% the provided axes or figure handles. R2019b or later only.
%
%% Output Arguments (block code execution until figure is closed) %%
%
% map = NumericMatrix, the colormap defined when the figure is closed.
% num = NumericVector, the number of nodes defining the ColorBrewer colorscheme.
% typ = CharRowVector, the colorscheme type: 'Diverging'/'Qualitative'/'Sequential'.
%
%% Dependencies %%
%
% * MATLAB R2009a or later.
% * brewermap.m <www.mathworks.com/matlabcentral/fileexchange/45208>
%
% See also BREWERMAP BREWERMAP_NODES CUBEHELIX PRESET_COLORMAP MAXDISTCOLOR
% RGBPLOT COLORMAP COLORMAPEDITOR COLORBAR UICONTROL ADDLISTENER
persistent ax2D ln2D ax3D pt3D txtH is2D cbAx cbIm pTxt pSld bEig bGrp bRev scm isr
%
new = isempty(ax2D)||~ishghandle(ax2D);
dfn = 256;
upd = false;
upb = false;
hgv = [];
nmr = dfn;
%
%% Input Wrangling %%
%
if nargin>2 && isco
% >R2019a only!
fnh = @colororder;
else % default
fnh = @colormap;
end
%
err = 'First input <N> must be a real positive scalar numeric or [] or NaN.';
if nargin==0 || isnumeric(N)&&isequal(N,[])
N = dfn;
elseif isnumeric(N)
assert(isscalar(N),'SC:brewermap_view:N:NotScalarNumeric',err)
assert(isreal(N)&&isfinite(N)&&fix(N)==N&&N>0||isnan(N),...
'SC:brewermap_view:N:NotRealPositiveIntegerNorNaN',err)
N = double(N);
elseif all(ishghandle(N(:))) % R2014b or later
assert(all(isgraphics(N(:),'axes')|isgraphics(N(:),'figure')),...
'SC:brewermap_view:N:NotAxesNorFigureHandles',...
'First input <N> may be an array of figure or axes handles.')
hgv = N(:);
nmr = arrayfun(@(h)size(fnh(h),1),hgv);
N = nmr(1);
else
error('SC:brewermap_view:N:UnsupportedInput',err)
end
%
[mcs,nmn,pyt] = brewermap('list');
%
% Check BREWERMAP output:
tmp = find([any(diff(double(char(pyt)),1),2);1]);
assert(isequal(tmp,[9;17;35]),'SC:brewermap_view:BREWERMAP:OutputOrder',...
'The BREWERMAP function returned an unexpected colorscheme sequence.')
%
if nargin<2 || isnumeric(scheme)&&isequal(scheme,[])
% Default pseudo-random colorscheme:
isr = rand(1)>0.5;
scm = mcs{1+mod(round(now*1e7),numel(mcs))}; %#ok<TNOW1>
else
% Parse input colorscheme:
scheme = bmv1s2c(scheme);
assert(ischar(scheme) && ndims(scheme)==2 && size(scheme,1)==1,...
'SC:brewermap_view:scheme:NotCharacterVector',...
'Second input <scheme> must be a 1xN char vector.') %#ok<ISMAT>
% Check if a reversed colormap was requested:
isr = strncmp(scheme,'-',1) || strncmp(scheme,'*',1);
isd = strncmp(scheme,'+',1) || isr;
scm = scheme(1+isd:end);
end
%
if isnan(N)
N = nmn(strcmpi(scm,mcs));
end
%
%% Ensure Figure Exists %%
%
% LHS and RHS slider bounds/limits, and slider step sizes:
lbd = 1;
rbd = dfn;
stp = [1,10]; % [minor,major]
%
% Define the 3D cube axis order:
xyz = 'RGB'; % specify the order here.
[~,xyz] = ismember(xyz,'RGB');
%
if new % Create a new figure.
%
% Figure parameters:
M = 9; % buttons per column
gap = 0.01; % gaps
bth = 0.04; % demo button height
btw = 0.10; % demo button width
bgh = 0.40; % button group height
cbw = 0.23; % colorbar width (both together)
cbh = 1-3*gap-bth; % colorbar height
axh = 1-bgh-2*gap; % axes height
axw = 1-cbw-2*gap; % axes width
bgw = axw-btw-gap; % button group width
%
figH = figure('HandleVisibility','callback',...
'IntegerHandle','off', 'NumberTitle','off', 'Units','normalized',...
'Name','ColorBrewer Interactive ColorScheme Selector',...
'MenuBar','figure', 'Toolbar','none', 'Tag',mfilename);
%
% Add 2D lineplot:
ax2D = axes('Parent',figH, 'Position',[gap,bgh+gap,axw,axh], 'Box','on',...
'ColorOrder',[1,0,0; 0,1,0; 0,0,1; 0.6,0.6,0.6], 'HitTest','off',...
'Visible','off', 'XLim',[0,1], 'YLim',[0,1], 'XTick',[], 'YTick',[]);
ln2D = line([0,0,0,0;1,1,1,1],[0,0,0,0;1,1,1,1], 'Parent',ax2D,...
'Visible','off', 'Linestyle','-', 'Marker','.');
%
% Add 3D scatterplot:
ax3D = axes('Parent',figH, 'OuterPosition',[0,bgh,axw+2*gap,1-bgh],...
'Visible','on', 'XLim',[0,1], 'YLim',[0,1], 'ZLim',[0,1], 'HitTest','on');
pt3D = patch('Parent',ax3D, 'XData',[0;1], 'YData',[0;1], 'ZData',[0;1],...
'Visible','on', 'LineStyle','none', 'FaceColor','none', 'MarkerEdgeColor','none',...
'Marker','o', 'MarkerFaceColor','flat', 'MarkerSize',10, 'FaceVertexCData',[1,1,0;1,0,1]);
view(ax3D,3);
grid(ax3D,'on')
lbl = {'Red','Green','Blue'};
xlabel(ax3D,lbl{xyz(1)})
ylabel(ax3D,lbl{xyz(2)})
zlabel(ax3D,lbl{xyz(3)})
%
% Add warning text:
txtH = text('Parent',ax2D, 'Units','normalized', 'Position',[1,1],...
'HorizontalAlignment','right', 'VerticalAlignment','top');
%
% Add demo button:
demo = uicontrol(figH, 'Style','togglebutton', 'Units','normalized',...
'Position',[1-cbw/2,1-bth-gap,cbw/2-gap,bth], 'String','Demo',...
'Max',1, 'Min',0, 'Callback',@bmvDemo); %#ok<NASGU>
% Add 2D/3D button:
is2D = uicontrol(figH, 'Style','togglebutton', 'Units','normalized',...
'Position',[1-cbw/1,1-bth-gap,cbw/2-gap,bth], 'String','2D / 3D',...
'Max',1, 'Min',0, 'Callback',@bmv2D3D);
% Add reverse button:
bRev = uicontrol(figH, 'Style','togglebutton', 'Units','normalized',...
'Position',[bgw+2*gap,bgh-bth,btw,bth], 'String','Reverse',...
'Max',1, 'Min',0, 'Callback',@bmvRevM);
%
% Add colorbars:
C(1,1,:) = [1,1,1];
cbAx(2) = axes('Parent',figH, 'Visible','off', 'Units','normalized',...
'Position',[1-cbw/2,gap,cbw/2-gap,cbh], 'YLim',[0.5,1.5],...
'YDir','normal', 'HitTest','off');
cbAx(1) = axes('Parent',figH, 'Visible','off', 'Units','normalized',...
'Position',[1-cbw/1,gap,cbw/2-gap,cbh], 'YLim',[0.5,1.5],...
'YDir','normal', 'HitTest','off');
cbIm(2) = image('Parent',cbAx(2), 'CData',C);
cbIm(1) = image('Parent',cbAx(1), 'CData',C);
%
% Add parameter sliders, listeners, and corresponding text:
sv = mean([lbd,rbd],2);
pTxt = uicontrol(figH,'Style','text', 'Units','normalized',...
'Position',[bgw+2*gap,bgh-2*bth-gap,btw,bth], 'String','X');
pSld = uicontrol(figH,'Style','slider', 'Units','normalized',...
'Position',[bgw+2*gap,gap,btw,bgh-2*bth-gap], 'Min',lbd(1), 'Max',rbd(1),...
'SliderStep',stp(1,:)/(rbd(1)-lbd(1)), 'Value',sv(1));
addlistener(pSld, 'Value', 'PostSet',@bmvSldr);
%
% Add colorscheme button group:
bGrp = uibuttongroup('Parent',figH, 'BorderType','none', ...
'Units','normalized', 'Position',[gap,gap,bgw,bgh-gap]);
% Determine button locations:
Z = 1:numel(mcs);
Z = Z+(Z>17);
C = (ceil(Z/M)-1)/4;
R = (M-1-mod(Z-1,M))/M;
% Add colorscheme buttons to group:
for jj = numel(mcs):-1:1
bEig(jj) = uicontrol('Parent',bGrp, 'Style','Toggle', 'String',mcs{jj},...
'Unit','normalized', 'Position',[C(jj),R(jj),1/4,1/M]);
end
set(bGrp,'SelectionChangeFcn',@bmvChgS);
%
end
%
%% Nested Functions %%
%
function str = bmvName()
% Generate the colorscheme name.
pfx = '+-';
ids = strcmpi(scm,mcs);
if any(ids)
str = [pfx(1+isr),mcs{ids}];
else % will throw an error in BREWERMAP:
str = scm;
end
end
%
function bmvUpDt()
% Update all graphics objects in the figure.
%
% Get ColorBrewer colormap and grayscale equivalent:
[map,num,typ] = brewermap(N,bmvName());
mag = map*[0.298936;0.587043;0.114021];
%
% Update colorbar values:
set(cbAx, 'YLim',[0,abs(N)+(N==0)]+0.5);
set(cbIm(1), 'CData',reshape(map,[],1,3))
set(cbIm(2), 'CData', repmat(mag,[1,1,3]))
%
% Update 2D line / 3D patch values:
if get(is2D, 'Value')
set(ln2D, 'XData',linspace(0,1,abs(N)));
set(ln2D,{'YData'},num2cell([map,mag],1).');
else % 3D
set(pt3D,...
'XData',map(:,xyz(1)),...
'YData',map(:,xyz(2)),...
'ZData',map(:,xyz(3)),...
'FaceVertexCData',map)
end
%
% Update reverse button:
set(bRev, 'Value',isr)
%
% Update warning text:
str = {[bmvName(),' '];[typ,' '];sprintf('%d Nodes ',num)};
set(txtH,'String',str);
%
% Update parameter value text:
set(pTxt(1), 'String',sprintf('N = %.0f',N));
%
% Update external axes/figure colormaps/colororders:
nmr(1) = N;
for k = 1:numel(hgv)
fnh(hgv(k),brewermap(nmr(k),bmvName()));
end
%
drawnow()
end
%
function bmv2D3D(h,~)
% Switch between 2D-line and 3D-cube representation.
%
if get(h,'Value') % 2D
set(ax3D, 'HitTest','off', 'Visible','off')
set(ax2D, 'HitTest','on', 'Visible','on')
set(pt3D, 'Visible','off')
set(ln2D, 'Visible','on')
else % 3D
set(ax2D, 'HitTest','off', 'Visible','off')
set(ax3D, 'HitTest','on', 'Visible','on')
set(ln2D, 'Visible','off')
set(pt3D, 'Visible','on')
end
%
bmvUpDt();
end
%
function bmvChgS(~,e)
% Change the colorscheme.
%
scm = get(e.NewValue,'String');
%
bmvUpDt()
end
%
function bmvRevM(h,~)
% Reverse the colormap.
%
isr = logical(get(h,'Value'));
%
bmvUpDt()
end
%
function bmvSldr(~,~)
% Update the slider position.
%
if ~upd
return
end
%
N = round(get(pSld,'Value'));
%
bmvUpDt()
end
%
function bmvDemo(h,~)
% Display all ColorBrewer colorschemes sequentially.
%
cnt = 0;
while ishghandle(h)&&get(h,'Value')
cnt = mod(cnt+1,pow2(53));
%
if mod(cnt,23)<1
ids = 1+mod(find(strcmpi(scm,mcs)),numel(mcs));
scm = mcs{ids};
try %#ok<TRYNC>
set(bGrp, 'SelectedObject',bEig(ids));
end
end
%
if mod(cnt,69)<1
isr = ~isr;
end
%
upb = (upb || N<=1) && N<dfn;
N = N - 1 + 2*upb;
%
% Update slider position:
upd = false;
try %#ok<TRYNC>
set(pSld, 'Value',N)
bmvUpDt()
end
upd = true;
%
% Faster/slower:
pause(0.1);
end
%
end
%
%% Initialize the Figure %%
%
set(bGrp,'SelectedObject',bEig(strcmpi(scm,mcs)));
set(pSld,'Value',max(lbd,min(rbd,N)));
upd = true;
bmvUpDt()
%
if nargout
waitfor(ax2D);
scheme = bmvName();
else
clear map
end
%
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%brewermap_view
function arr = bmv1s2c(arr)
% If scalar string then extract the character vector, otherwise data is unchanged.
if isa(arr,'string') && isscalar(arr)
arr = arr{1};
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmv1s2c
%
% Copyright (c) 2014-2025 Stephen Cobeldick
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and limitations under the License.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%license
+831
View File
@@ -0,0 +1,831 @@
function distFig(varargin)
% ===== Syntax ============================================================
% distFig(...,'Screen',Value)
% distFig(...,'Position',Value)
%
% distFig(...,'Rows',Value)
% distFig(...,'Columns',Value)
%
% distFig(...,'Not',Value)
% distFig(...,'Only',Value)
% distFig(...,'Offset',Value)
%
% distFig(...,'Adjust',Value)
% distFig(...,'Extra',Value)
% distFig(...,'Menu',Value)
% distFig(...,'Transpose',Value)
% distFig(...,'Simulink',Value)
% distFig(...,'Tight',Value)
% distFig(...,'Scale',Value)
% distFig(...,'ScaleOrigin',Value)
% distFig(...,'FreezeSize',Value)
% distFig(...,'Order',Value)
%
% ===== Description =======================================================
% distFig(...,'Screen',Value) assigns where the figures will be
% distributed.
% Synonyms for 'Screen' are 'Scr' and 'S'. Value can be:
% 'C' / 'Center' (Default)
% 'W' / 'West' / 'L' / 'Left'
% 'E' / 'East' / 'R' / 'Right'
% 'N' / 'North' / 'T' / 'Top'
% 'S' / 'South' / 'B' / 'Bottom'
% 'Main' / 'Primary'
% 'Ext' / 'External' / 'Secondary'
%
% distFig(...,'Position',Value) assigns in which part of the screen the
% figures will be distributed.
% Synonyms for 'Position' are 'Pos' and 'P'. Value can be:
% 'C' / 'Center' (Default)
% 'W' / 'West' / 'L' / 'Left'
% 'E' / 'East' / 'R' / 'Right'
% 'N' / 'North' / 'T' / 'Top'
% 'S' / 'South' / 'B' / 'Bottom'
% 'NW'
% 'NE'
% 'SW'
% 'SE'
%
% distFig(...,'Rows',Value) assigns how many rows the figures will be
% distributed on.
% Synonyms for 'Rows' are 'R'. Value can be:
% 1...n
% 'Auto' / -1
% 'Auto' indicates that it automatically calculates the number of required
% rows.
%
% distFig(...,'Columns',Value) assigns how many rows the figures will be
% distributed on.
% Synonyms for 'Columns' are 'Cols' and 'C'. Value can be:
% 1...n
% 'Auto' / -1
% 'Auto' indicates that it automatically calculates the number of required
% columns.
%
% distFig(...,'Not',Value) excludes specified figures from the distribution
% list. Value must be an array with the excluded figure numbers. The
% default value is []. The input argument 'gcf' (get current figure) can be
% used as well.
%
% distFig(...,'Only',Value) does only distribute specified figures. Value
% must be an array with the figure which will be distributed. The
% default value is []. The input argument 'gcf' (get current figure) can be
% used as well.
%
% distFig(...,'Offset',Value) can be used to shift all figures on the
% distribution list. Synonyms for 'Offset' is 'O'. Value must be an integer
% larger or equal to zero. The default offset value is 0.
%
% distFig(...,'Adjust',Value) can be used to adjust all figure positions.
% For instance, if all figures should be moved 20 pixels to the right,
% which is not possible with the other features, Value should be [20,0].
% If Value is below 1, the scalars indicate the relative adjustment to the
% screen. If the selected monitor is [1920,1080] pixels, a Values of
% [0.25,-0.5] moves all figures with [480,540] pixels.
% Synonyms for 'Ajust' is 'A'. Value can be:
% [Integer,Integer] (In order to adjust by a specific ammount)
% [Scalar,Scalar]( < 1) (To adjust a relative ammount of the monitor size)
% The default value is [0,0].
%
% distFig(...,'Extra',Value) is used to handle the extra figures, which
% might not fit into 'Rows' * 'Cols'. For instance, 5 figure are to be
% distributed on a 2x2 = 4 area. The fifth figure is then places on top of
% the first figure if 'Extra' is 'restart'. It can be ignored by setting
% 'Extra' to 'ignore', in which the fifth figure will not be distributed.
%
% distFig(...,'Menu',Value) can be used to modify the menu bar.
% Value can be:
% 'figure'
% 'none'
%
% distFig(...,'Transpose',Value) can be used to tranpose the order of
% distributing. The values must be logical and the default value is false.
%
% distFig(...,'Simulink',Value) can be used to handle simulink figures.
% Value can be:
% 'include' (Default)
% 'exclude'
% 'only'
% If value is true, simulink figures will be distributed along with the
% other figures. If value if 'exclude' simulink figures will be
% excluded from the distribution list. If value is 'only', only the
% simulink figures will be distributed and not the ordinary figures.
%
% distFig(...,'Tight',Value) can be used to stretch the figure to the
% border of the figure, so no unnecessary "whitespace" is present.
% Value must be logical and the default value if false.
%
% distFig(...,'Scale',Value) can be used to scale the figure matrix. Both
% horizontal and vertical scaling can be applied, but also similar scaling.
% Value is a single scalar for similar scaling, but is a 2x1 matrix for
% specific scaling in each direction. For instance, distFig('Scale',0.5)
% scales all figures by 50% relative to the upper left corner of the
% distribution area (Default Scale-origin). distFig('Scale',[0.8,0.4])
% scales the figures by 80% and 40% in the horizontal and vertical
% directions respectivly relative to the default scale-origin.
%
% distFig(...,'ScaleOrigin',Value) can be used in combination with the
% 'Scale' option. Value is a 2x1 matrix with the origin. If these values
% are between 0 and 1 the relative monitor size is used, whereas larger
% values should be rounded indicating pixel values. For instance,
% distFig('Scale',0.5,'ScaleOrigin',[0.5,1]) scales the figures relative
% to the midpoint on the right hand side of the selected monitor. For a
% monitor with resolution 1920x1080, this would be equal to
% distFig('Scale',0.5,'ScaleOrigin',[1920,960]).
% The default scale origin is in the upper left corner of the distribution
% area.
% Synonyms for 'ScaleOrigin' are 'Origin', 'Orig' and 'Scaleorig'.
%
% distFig(...,'FreezeSize',Value) can be used to freeze the sizes of the
% figures before distribution. Currently, this function anchors the figures
% to the upper left corner of the original position, yet keeping the
% original size - it does not calculate new positions, so the figures may
% overlap.
% Synonym for 'FreezeSize' is 'Freeze'.
%
% distFig(...,'Order',Value) can be used to control the plotting order. For
% instance, distFig('Only',[5,6,7,8],'Order',[2,1,4,3]) will plot figure
% 5,6,7 and 8 in the order of 6,5,8 and 7. The ordering argument is thus
% related to the input of 'Only'.
%
% ===== Examples ==========================================================
% distFig();
% This will distribute all open figures on the primary screen in two rows.
%
% distFig('Screen','Left','Position','Right','Only',[1,2,4])
% This will only distribute figure 1, 2 and 4 on the right part of the left
% screen.
%
% distFig('Offset',2,'Not',[1,2])
% This will distribute all figure but figure 1 and 2 in the same pattern as
% distFig(), but figure 1 and 2 will not be distributed, but instead there will
% be blank spots where they would have been distributed.
%
% ===== Note ==============================================================
% If you find any errors or bugs with this version please contact me on
% AndersSSimonsen@GMail.com or write a comment on file exchange - then I'll
% take a look at it. :)
%
% This function will only work in MATLAB 2014b and onwards due to the
% massive overhaul in the figure handle system - I will not support
% previous versions.
% =========================================================================
% ===== Version ===========================================================
% =========================================================================
% Anders Schou Simonsen, AndersSSimonsen@GMail.com
% Version 5.5
% 31/03-2015
% =========================================================================
% ===== Default values ====================================================
% =========================================================================
Rows = -1;
Cols = -1;
Scr = 'C';
Pos = 'C';
Tight = false;
Menu = '';
Transpose = false;
Extra = 'restart';
Offset = 0;
Not = [];
Only = [];
Simulink = 'include';
Adjust = [0,0];
Scale = 1;
Scale_Orig = [];
FreezeSize = false;
Order = [];
% =========================================================================
% ===== Get inputs ========================================================
% =========================================================================
if (mod(nargin,2) ~= 0)
error('Uneven number of inputs! Returning...');
end
for i = 1:2:nargin
if (ischar(varargin{i+1}))
varargin{i+1} = lower(varargin{i+1});
end
switch lower(varargin{i})
case {'scr','screen','s'}
% =============================================================
Allow = {
'C' 'c' 'center' 'main' 'primary'
'W' 'w' 'west' 'l' 'left'
'E' 'e' 'east' 'r' 'right'
'N' 'n' 'north' 't' 'top'
'S' 's' 'south' 'b' 'bottom'
'Ext' 'ext' 'external' 'secondary' NaN
};
Trigger = any(strcmp(Allow,varargin{i+1}),2);
if (~any(Trigger))
warning('Input ''%s'' not recognized for ''%s''!',varargin{i+1},varargin{i});
else
Scr = Allow{Trigger,1};
end
case {'pos','position','p'}
% =============================================================
Allow = {
'C' 'c' 'center' NaN NaN
'W' 'w' 'west' 'l' 'left'
'E' 'e' 'east' 'r' 'right'
'N' 'n' 'north' 't' 'top'
'S' 's' 'south' 'b' 'bottom'
'NW' 'nw' NaN NaN NaN
'NE' 'ne' NaN NaN NaN
'SW' 'sw' NaN NaN NaN
'SE' 'se' NaN NaN NaN
};
Trigger = any(strcmp(Allow,varargin{i+1}),2);
if (~any(Trigger))
warning('Input ''%s'' not recognized for ''%s''!',varargin{i+1},varargin{i});
else
Pos = Allow{Trigger,1};
end
case {'rows','r'}
% =============================================================
if (strcmp(varargin{i+1},'auto'))
Rows = -1;
else
if ~((isnumeric(varargin{i+1})) && (mod(varargin{i+1},1) == 0))
warning('Input to ''%s'' must be a round numeric value!',varargin{i});
else
Rows = varargin{i+1};
end
end
case {'cols','columns','c'}
% =============================================================
if (strcmp(varargin{i+1},'auto'))
Cols = -1;
else
if ~((isnumeric(varargin{i+1})) && (mod(varargin{i+1},1) == 0))
warning('Input to ''%s'' must be a round numeric value!',varargin{i});
else
Cols = varargin{i+1};
end
end
case {'ext','extra'}
% =============================================================
Allow = {
'ignore'
'restart'
};
Trigger = any(strcmp(Allow,varargin{i+1}),2);
if (~any(Trigger))
warning('Input ''%s'' not recognized for ''%s''!',varargin{i+1},varargin{i});
else
Extra = Allow{Trigger,1};
end
case {'menu','bar'}
% =============================================================
Allow = {
'figure'
'none'
};
Trigger = any(strcmp(Allow,varargin{i+1}),2);
if (~any(Trigger))
warning('Input ''%s'' not recognized for ''%s''!',varargin{i+1},varargin{i});
else
Menu = Allow{Trigger,1};
end
case {'transpose'}
% =============================================================
if (~islogical(varargin{i+1}))
warning('Input to ''%s'' must be logical!',varargin{i});
else
Transpose = varargin{i+1};
end
case {'offset','o'}
% =============================================================
if ~((isnumeric(varargin{i+1})) && (mod(varargin{i+1},1) == 0))
warning('Input to ''%s'' must be a round numeric value!',varargin{i});
else
Offset = varargin{i+1};
end
case {'adjust','a'}
% =============================================================
if ~(isnumeric(varargin{i+1}))
warning('Input to ''%s'' must be numeric!',varargin{i});
elseif (numel(varargin{i+1}) ~= 2)
warning('Input to ''%s'' must be a (1 x 2) matrix with integers or scalars less than one!',varargin{i});
elseif (all(mod(varargin{i+1},1) == 0))
Adjust = varargin{i+1};
else
if (any(varargin{i+1} > 1))
warning('Input to ''%s'' must be either integers or scalars less than one!',varargin{i});
else
Adjust = varargin{i+1};
end
end
case {'not'}
% =============================================================
if ~((isnumeric(varargin{i+1}) && all(mod(varargin{i+1},1) == 0)) || isobject(varargin{i+1}))
warning('Input to ''%s'' must be round numeric values!',varargin{i});
else
if (isobject(varargin{i+1}))
Not = get(varargin{i+1},'Number');
if (iscell(Not))
Not = cell2mat(Not);
end
else
Not = varargin{i+1};
end
end
case {'only'}
% =============================================================
if ~((isnumeric(varargin{i+1}) && all(mod(varargin{i+1},1) == 0)) || isobject(varargin{i+1}))
warning('Input to ''%s'' must be round numeric values!',varargin{i});
else
if (isobject(varargin{i+1}))
Only = get(varargin{i+1},'Number');
if (iscell(Only))
Only = cell2mat(Only);
end
else
Only = varargin{i+1};
end
end
case {'simu','simulink'}
% =============================================================
Allow = {
'include' NaN
'exclude' 'ignore'
'only' NaN
};
Trigger = any(strcmp(Allow,varargin{i+1}),2);
if (~any(Trigger))
warning('Input ''%s'' not recognized for ''%s''!',varargin{i+1},varargin{i});
else
Simulink = Allow{Trigger,1};
end
case {'tight'}
% =============================================================
if (~islogical(varargin{i+1}))
warning('Input to ''%s'' must be logical!',varargin{i});
else
Tight = varargin{i+1};
end
case {'scale'}
% =============================================================
if (~isnumeric(varargin{i+1}) || (numel(varargin{i+1}) > 2))
warning('Input to ''%s'' must be one or two numeric values (x and y scale)!',varargin{i});
else
Scale = varargin{i+1};
end
case {'origin','orig','scaleorig','scaleorigin'}
% =============================================================
if (~isnumeric(varargin{i+1}) || (numel(varargin{i+1}) ~= 2))
warning('Input to ''%s'' must be two numeric values (x0 and y0)!',varargin{i});
else
Scale_Orig = varargin{i+1};
end
case {'freezesize','freeze'}
% =============================================================
if (~islogical(varargin{i+1}))
warning('Input to ''%s'' must be logical!',varargin{i});
else
FreezeSize = varargin{i+1};
end
case {'order'}
% =============================================================
if ~(isnumeric(varargin{i+1}) && all(mod(varargin{i+1},1) == 0))
warning('Input to ''%s'' must be round numeric values!',varargin{i});
else
Order = varargin{i+1};
end
otherwise
% =============================================================
fprintf('Input ''%s'' not recognized!',varargin{i});
end
end
% =========================================================================
% ===== Constants =========================================================
% =========================================================================
% ===== Limits ============================================================
Limits = {...
'C' [0,1] [0,1]
'W' [0,0.5] [0,1]
'E' [0.5,1] [0,1]
'N' [0,1] [0.5,1]
'NW' [0,0.5] [0.5,1]
'NE' [0.5,1] [0.5,1]
'S' [0,1] [0,0.5]
'SW' [0,0.5] [0,0.5]
'SE' [0.5,1] [0,0.5]
};
% ===== Screen angles =====================================================
Screens = {...
(-pi * 1 / 4) (+pi * 1 / 4) 'E'
(-pi * 3 / 4) (-pi * 1 / 4) 'S'
(+pi * 1 / 4) (+pi * 3 / 4) 'N'
(+pi * 3 / 4) Inf 'W'
-Inf (-pi * 3 / 4) 'W'
};
% ===== Taskbar height ====================================================
Taskbar_Height = 40;
% =========================================================================
% ===== Figure list =======================================================
% =========================================================================
% ===== All figures =======================================================
Fig_Object = findall(0,'type','figure');
if (isempty(Fig_Object))
fprintf('distFig: No figures to distribute!\n');
return;
end
% ===== Remove not visible figures ========================================
Visible = arrayfun(@(n) (strcmp(get(Fig_Object(n),'Visible'),'on')),1:numel(Fig_Object));
Fig_Object = Fig_Object(Visible);
% ===== Include simulink figures ==========================================
Fig_Number = {Fig_Object.Number};
Empty = arrayfun(@(n) (isempty(Fig_Number{n})),1:numel(Fig_Number));
Fig_Number(Empty) = {0};
Fig_Number = cell2mat(Fig_Number);
[~,Index] = sort(get(Fig_Object(Fig_Number == 0),'Name'));
Temp = find(Fig_Number == 0);
Fig_Number(Temp(Index)) = -(numel(Index):(-1):1);
% ===== Logical distribution array ========================================
Fig_Dist = true(1,numel(Fig_Object));
% ===== Simulink figures ==================================================
if strcmp(Simulink,'exclude')
Fig_Dist = (Fig_Number > 0);
end
if strcmp(Simulink,'only')
Fig_Dist = (Fig_Number < 0);
end
% ===== Not ===============================================================
Fig_Dist(ismember(Fig_Number,Not)) = false;
% ===== Only ==============================================================
if (~isempty(Only))
Fig_Dist(~ismember(Fig_Number,Only)) = false;
end
% ===== Number of figures =================================================
nFig_Dist = sum(Fig_Dist);
% ===== Return ============================================================
if (nFig_Dist == 0)
return;
end
% =========================================================================
% ===== Monitor ===========================================================
% =========================================================================
% ===== Get monitor positions =============================================
Monitors = get(0,'MonitorPositions');
Monitors_Center = Monitors(:,1:2) + Monitors(:,3:4) / 2;
Monitors_Label = cell(size(Monitors,1),1);
% ===== Center monitor ====================================================
Temp = sum(abs(Monitors(:,1:2) - 1),2);
Trigger_C = find(Temp == min(Temp));
Monitors_Label{Trigger_C} = 'C';
% ===== External monitors =================================================
Dir = Monitors_Center - repmat(Monitors_Center(Trigger_C,:),size(Monitors_Center,1),1);
Theta = atan2(Dir(:,2),Dir(:,1));
for i = 1:numel(Theta)
if (i ~= Trigger_C)
Monitors_Label{i} = Screens{(Theta(i) > [Screens{:,1}]) & (Theta(i) <= [Screens{:,2}]),3};
end
end
% ===== Select monitor ====================================================
Monitor = Monitors(strcmp(Monitors_Label,Scr),:);
if (isempty(Monitor))
if (strcmp(Scr,'Ext'))
if (size(Monitors,1) == 1)
warning('No external monitor could be found!');
Monitor = Monitors(1,:);
else
Monitor = Monitors(find(~strcmp(Monitors_Label,'C'),1,'first'),:);
end
else
warning(['Screen ''%s'' could not be found! (Using ''C'' instead)\nThe following monitors were identified: %s.\n'...
'If the function cannot find the monitor try and restart MATLAB - this will reset the monitor-matrix, which is being used by the function.'],...
Scr,sprintf('%s\b\b\b\b\b',sprintf('''%s'' and ',Monitors_Label{:})));
Monitor = Monitors(strcmp(Monitors_Label,'C'),:);
if (isempty(Monitor))
warning(['distFig cannot find matching monitors - using the first one!',...
'\nPlease report this problem to AndersSSimonsen@GMail.com, where you include this matrix:'],0);
disp(Monitors);
Monitor = Monitors(1,:);
end
end
end
% =========================================================================
% ===== Rows and columns ==================================================
% =========================================================================
Limit = Limits(strcmp(Limits(:,1),Pos),:);
if ((Rows == (-1)) && (Cols == (-1)))
AR_Ideal = 1.2;
AR_Mean = Inf;
for i = 1:50
% ===== Exception =================================================
if ((nFig_Dist + Offset) == 4)
Rows = 2;
Cols = 2;
break;
end
% ===== Temporary rows and columns ================================
Rows = i;
Cols = ceil((nFig_Dist + Offset) / Rows);
% ===== Sizes =====================================================
x = round(linspace(Limit{2}(1),Limit{2}(2),Cols + 1) * Monitor(3)) + 1;
y = round(linspace(Limit{3}(1),Limit{3}(2),Rows + 1) * (Monitor(4) - Taskbar_Height)) + Taskbar_Height + 1;
clear Size;
[Size(:,:,1),Size(:,:,2)] = meshgrid(diff(x),diff(y));
% ===== Output ====================================================
AR_Mean_Prev = AR_Mean;
AR_Mean = mean(mean(Size(:,:,1) ./ Size(:,:,2)));
if (AR_Mean > AR_Ideal)
AR_Error = abs([AR_Ideal - AR_Mean_Prev,AR_Ideal - AR_Mean]);
if (AR_Error(1) < AR_Error(2))
Rows = i - 1;
end
Cols = ceil((nFig_Dist + Offset) / Rows);
clear Size;
break;
end
end
elseif (Rows == (-1))
Rows = ceil((nFig_Dist + Offset) / Cols);
elseif (Cols == (-1))
Cols = ceil((nFig_Dist + Offset) / Rows);
end
% =========================================================================
% ===== Location matrix ===================================================
% =========================================================================
Sets = ceil(nFig_Dist / (Rows * Cols) + Offset);
Mat = nan(Rows,Cols,Sets);
if (Transpose)
Mat = permute(Mat,[2,1,3]);
end
if (isempty(Order))
Order = (1:sum(Fig_Dist));
end
Temp = sort(Fig_Number(Fig_Dist));
if (max(Order) > numel(Temp))
error('The ordering command is related to the input - not the actual figure numbers! Returning...');
end
Mat((1:nFig_Dist) + Offset) = Temp(Order);
if (Transpose)
Mat = permute(Mat,[2,1,3]);
end
if (strcmp(Extra,'ignore'))
Sets = 1;
end
% =========================================================================
% ===== Position and size =================================================
% =========================================================================
% ===== Grid ==============================================================
x = round(linspace(Limit{2}(1),Limit{2}(2),Cols + 1) * Monitor(3)) + 1;
y = round(linspace(Limit{3}(1),Limit{3}(2),Rows + 1) * (Monitor(4) - Taskbar_Height)) + Taskbar_Height + 1;
% ===== Scale =============================================================
if (isempty(Scale_Orig))
Scale_Orig = [x(1),y(end)];
end
if ((all(mod(Scale_Orig,1) == 0)) && (any(Scale_Orig > 1)))
ScaleOrig_Pixel = Scale_Orig;
else
ScaleOrig_Pixel = Monitor(3:4) .* Scale_Orig;
end
if (numel(Scale) == 1)
Scale(2) = Scale(1);
end
x = (x - ScaleOrig_Pixel(1)) * Scale(1) + ScaleOrig_Pixel(1);
y = (y - ScaleOrig_Pixel(2)) * Scale(2) + ScaleOrig_Pixel(2);
% ===== Positon ===========================================================
[FPos(:,:,1),FPos(:,:,2)] = meshgrid(x(1:end-1),y(1:end-1));
FPos(:,:,2) = flipud(FPos(:,:,2));
FPos(:,:,1) = FPos(:,:,1) + Monitor(1) - 1;
FPos(:,:,2) = FPos(:,:,2) + Monitor(2) - 1;
% ===== Size ==============================================================
[Size(:,:,1),Size(:,:,2)] = meshgrid(diff(x),diff(y));
% ===== Adjust ============================================================
if (any(Adjust ~= 0))
if (all(mod(Adjust,1) == 0))
Adjust_Pixels = Adjust;
else
Adjust_Pixels = round(Adjust .* (Monitor(3:4) + [0,-Taskbar_Height]));
end
else
Adjust_Pixels = [0,0];
end
% =========================================================================
% ===== Distribute figures ================================================
% =========================================================================
for s = 1:Sets
for i = 1:size(Mat,1)
for j = 1:size(Mat,2)
if (~isnan(Mat(i,j,s)))
% ===== Position and figure ===============================
Pos_Temp = [squeeze(FPos(i,j,:))' + Adjust_Pixels,squeeze(Size(i,j,:))'];
gcf_Temp = Fig_Object(Fig_Number == Mat(i,j,s));
gca_Temp = get(gcf_Temp,'CurrentAxes');
% ===== Freeze size =======================================
if (FreezeSize)
Pos_Orig = get(gcf_Temp,'Position');
Pos_Temp(2) = Pos_Temp(2) + Pos_Temp(4) - Pos_Orig(4);
Pos_Temp(3:4) = Pos_Orig(3:4);
end
% ===== Focus figures to bring to front ===================
figure(gcf_Temp);
% ===== Set units =========================================
if (~isempty(Menu))
set(gcf_Temp,'MenuBar',Menu);
end
if (Mat(i,j,s) < 0)
drawnow;
end
gca_Units_Prev = get(gca_Temp,'Units');
gcf_Units_Prev = get(gcf_Temp,'Units');
% ===== Distribute figure =================================
set(gca_Temp,'Units','normalized');
set(gcf_Temp,'Units','pixels','OuterPosition',Pos_Temp);
set(gcf_Temp,'Units',gcf_Units_Prev);
% ===== Apply tight =======================================
if ((Tight) && (Mat(i,j,s) > 0))
drawnow;
set(gca_Temp,'Position',[get(gca_Temp,'TightInset') * eye(4,2),1 - get(gca_Temp,'TightInset') * [1,0,1,0;0,1,0,1]']);
end
set(gca_Temp,'Units',gca_Units_Prev);
end
end
end
end
% =========================================================================
% ===== Check for new updates =============================================
% =========================================================================
% This feature check for a new version of distFig. It is only an attempt,
% so it might not work without bugs. It can be disables by setting the
% variable below called "Update" to "false" or by deleting this whole
% section - this won't affect the rest of the function.
%
% It generates a .txt file called "distFig_Update.txt" located in the same
% folder as distFig.m, which holds the date for the last time the function
% checked for updates. In order to not check for updates all the time, the
% function only does this ONCE every 7 days (variable "Check_Interval"),
% and at max once for every started MATLAB session. Also, it uses appdata,
% so if you're using this as well, it might display messages multiple times
% within a session, if an update is available, if appdata is deleted.
%
% The update feature works by scanning the file exchange page for distFig
% for a string value called "datePublished", and compares it to the date of
% the current version (variable "Date_Current"). If these are not the same,
% a hyperlink will be shown to the function page on file exchange page. It
% thus depends on a very specific format, which might differ(?) from
% different languages - I'm not aware whether this is true, so please
% contact me on AndersSSimonsen@GMail.com if you find this to be true, so I
% can fix the feature. :)
% ===== DISABLE THE UPDATE FEATURE BY SETTING THIS VALUE TO false =========
Update = true;
% ===== DISABLE THE UPDATE FEATURE BY SETTING THIS VALUE TO false =========
if (Update)
try
% ===== Initialize ================================================
Date_Current = [2015,03,31];
Check_Interval = 7;
Check = false;
% ===== Path of update file =======================================
Path = mfilename('fullpath');
Path = sprintf('%s\\distFig_Update.txt',Path(1:end-numel('\distFig')));
if (isempty(getappdata(0,'distFig_Update')))
% ===== Set appdata ===========================================
setappdata(0,'distFig_Update',1);
% ===== Read update file ======================================
File_ID = fopen(Path,'r');
Clock = clock;
Date_Today = Clock(1:3);
if (File_ID == (-1))
% ===== Generate update file ==============================
File_ID = fopen(Path,'w');
fprintf(File_ID,'%d %d %d',Date_Today(1),Date_Today(2),Date_Today(3));
fclose(File_ID);
Check = true;
else
% ===== Read last date ====================================
Data = textscan(File_ID,'%s','delimiter','');
if (strcmp(Data{1},'DISABLE'))
Check = false;
else
Date_Last_Checked = cell2mat(textscan(Data{1}{1},'%f %f %f'));
% ===== Write new date ================================
File_ID = fopen(Path,'w');
fprintf(File_ID,'%d %d %d',Date_Today(1),Date_Today(2),Date_Today(3));
fclose(File_ID);
% ===== Check for new updates? ========================
Days_Elapsed = etime([Date_Today,zeros(1,3)],[Date_Last_Checked,zeros(1,3)]) / (60^2 * 24);
if (Days_Elapsed > Check_Interval)
Check = true;
end
end
end
% ===== Check for updates =====================================
if (Check)
% ===== Read File Exchange date ===========================
Data = urlread('http://se.mathworks.com/matlabcentral/fileexchange/37176-distribute-figures');
Temp = Data((-1:100) + regexp(Data,'datePublished'));
Date_Online = cell2mat(textscan(Temp(26:end),'%f-%f-%f'));
% ===== New update available? =============================
if (~all(Date_Current == Date_Online))
setappdata(0,'distFig_Update',2);
setappdata(0,'distFig_Online_Date',Date_Online);
end
end
end
% ===== Message ===================================================
if (getappdata(0,'distFig_Update') == 2)
try
% ===== New version =======================================
Date_Online = getappdata(0,'distFig_Online_Date');
fprintf('New update available for distFig! ');
disp('<a href = "http://se.mathworks.com/matlabcentral/fileexchange/37176-distribute-figures">Download here</a>')
fprintf('\tCurrent version date:\t%02.0f-%02.0f-%02.0f\n',Date_Current(1),Date_Current(2),Date_Current(3));
fprintf('\tOnline version date:\t%02.0f-%02.0f-%02.0f\n',Date_Online(1),Date_Online(2),Date_Online(3));
% ===== Postpone ==========================================
fprintf('Click here to postpone this message for %d days: ',Check_Interval);
Command = sprintf([...
'setappdata(0,''distFig_Update'',1);',...
'fprintf(''distFig update message postponed for %d days!\\n'');'
],Check_Interval);
disp(sprintf('<a href="matlab:%s">Postpone update message</a>',Command)); %#ok<DSPS>
% ===== Disable ===========================================
fprintf('Click here to disable the update feature forever: ');
Command = sprintf([...
'File_ID = fopen(''%s'',''w'');',...
'fprintf(File_ID,''DISABLE'');',...
'fclose(File_ID);',...
'setappdata(0,''distFig_Update'',1);',...
'fprintf(''distFig update feature disabled!\\n'');'
],Path);
disp(sprintf('<a href="matlab:%s">Disable update feature</a>',Command)); %#ok<DSPS>
catch
setappdata(0,'distFig_Update',1);
end
end
catch Error %#ok<NASGU>
% ===== Error handling ============================================
try
Path_Error = [Path(1:end-numel([filesep 'distFig_Update.txt'])), [filesep 'distFig_Error.mat']];
fprintf([
'There was an error in distFig when checking for updates.',...
'\nPlease report this to AndersSSimonsen@GMail.com by sending the error-file "distFig_Error.mat" located in %s. :)\n\n'],Path_Error);
AppData = getappdata(0); %#ok<NASGU>
save(Path_Error);
catch
end
end
end
+263
View File
@@ -0,0 +1,263 @@
function exportFigureData(figHandle)
%exportFigureData Export line and scatter plot data to ASCII files
%
% This function extracts x and y data from all line and scatter plots in a
% figure (including tiled layouts) and saves each data series to a separate
% ASCII file. File names are constructed from the figure title, tile title
% (if in a tiled layout), axis labels, and legend entries. Data is limited
% to the current x-axis scale range.
%
% SYNTAX:
% exportFigureData() % Uses current active figure
% exportFigureData(figHandle) % Uses specified figure handle
%
% INPUTS:
% figHandle : figure handle (optional)
% If empty or omitted, the current active figure (gcf) is used.
%
% OUTPUT:
% ASCII files are saved in the current working directory, named as:
% <FigureTitle>_<TileTitle>_<YLabel>_vs_<XLabel>_<LineLabel>.txt
% For non-tiled figures:
% <FigureTitle>_<YLabel>_vs_<XLabel>_<LineLabel>.txt
% Each file contains two columns: X-data and Y-data (tab-delimited)
% Handle input: use current figure if none provided
if nargin == 0 || isempty(figHandle)
figHandle = gcf;
end
% Get figure title (use sgtitle if available, else Name)
figTitle = '';
sgtitleObjs = findall(figHandle, 'type', 'text', 'Tag', 'sgtitle');
if ~isempty(sgtitleObjs)
figTitle = get(sgtitleObjs(1), 'String');
if iscell(figTitle)
figTitle = figTitle{1};
end
end
if isempty(figTitle)
figTitle = get(figHandle, 'Name');
end
if isempty(figTitle)
figTitle = 'figure';
end
figTitle = sanitizeFilename(figTitle); % Sanitize figure title
% Get all axes in the figure (works for both regular and tiled layouts)
allAxes = findall(figHandle, 'type', 'axes');
if isempty(allAxes)
warning('exportFigureData:NoAxes', 'No axes found in figure.');
return;
end
% Counters for unnamed line and scatter objects
lineCounter = 0;
scatterCounter = 0;
% Loop over all axes
for ii = 1:length(allAxes)
ax = allAxes(ii);
% Skip legend axes and colorbar axes
if strcmp(ax.Tag, 'legend') || strcmp(ax.Tag, 'colorbar')
continue
end
% Check if this axes uses yyaxis
isYYAxis = isprop(ax, 'YAxis') && numel(ax.YAxis) == 2;
if isYYAxis
sidesToProcess = {'left', 'right'};
else
sidesToProcess = {'single'};
end
% Build a legend object if it exists (for extracting labels)
legendObj = legend(ax);
legendLabels = {};
if ~isempty(legendObj)
legendLabels = legendObj.String;
if ischar(legendLabels)
legendLabels = {legendLabels};
end
end
% Initialize a counter for matching legend entries to graphics objects
graphicsIndex = 0;
% Loop through both sides if yyaxis is used, otherwise process normally once
for s = 1:length(sidesToProcess)
if isYYAxis
yyaxis(ax, sidesToProcess{s}); % Switch active side to expose its lines and label
end
% Get axis titles (now correctly tied to the active side)
xLabel = ax.XLabel.String;
yLabel = ax.YLabel.String;
tileTitle = ax.Title.String;
% Convert to cell if char, then handle empty cases
if ischar(xLabel), xLabel = cellstr(xLabel); end
if ischar(yLabel), yLabel = cellstr(yLabel); end
if ischar(tileTitle), tileTitle = cellstr(tileTitle); end
if isempty(xLabel) || (iscell(xLabel) && isempty(xLabel{1}))
xLabel = 'x';
else
xLabel = xLabel{1};
end
if isempty(yLabel) || (iscell(yLabel) && isempty(yLabel{1}))
yLabel = 'y';
else
yLabel = yLabel{1};
end
if isempty(tileTitle) || (iscell(tileTitle) && isempty(tileTitle{1}))
tileTitle = '';
else
tileTitle = tileTitle{1};
end
% Sanitize axis labels and tile title
xLabel = sanitizeFilename(xLabel);
yLabel = sanitizeFilename(yLabel);
tileTitle = sanitizeFilename(tileTitle);
% Get x-axis limits for filtering
xLims = ax.XLim;
% Get all children of the active side
children = get(ax, 'Children');
% Reverse so we extract in creation order to match the legend
if ~isempty(children)
children = flipud(children(:));
end
% Loop over all child graphics objects for this side
for jj = 1:length(children)
obj = children(jj);
% Check if this is a line object
if isa(obj, 'matlab.graphics.chart.primitive.Line')
graphicsIndex = graphicsIndex + 1;
% Get x and y data
xData = get(obj, 'XData');
yData = get(obj, 'YData');
% Skip if empty
if isempty(xData) || isempty(yData)
continue
end
% Filter data to x-axis range
mask = (xData >= xLims(1)) & (xData <= xLims(2));
xData = xData(mask);
yData = yData(mask);
% Determine label: prefer internal DisplayName to fix yyaxis mismatch
label = '';
if isprop(obj, 'DisplayName') && ~isempty(obj.DisplayName)
label = obj.DisplayName;
elseif graphicsIndex <= length(legendLabels) && ~isempty(legendLabels{graphicsIndex})
label = legendLabels{graphicsIndex};
else
lineCounter = lineCounter + 1;
label = sprintf('line_%d', lineCounter);
end
% Sanitize label for filename
label = sanitizeFilename(label);
% Create filename with tile title if present
if ~isempty(tileTitle)
fileName = sprintf('%s_%s_%s_vs_%s_%s.txt', figTitle, tileTitle, yLabel, xLabel, label);
else
fileName = sprintf('%s_%s_vs_%s_%s.txt', figTitle, yLabel, xLabel, label);
end
% Write data to ASCII file
data = [xData(:), yData(:)];
writematrix(data, fileName, 'Delimiter', '\t');
fprintf('Saved: %s\n', fileName);
% Check if this is a scatter object
elseif isa(obj, 'matlab.graphics.chart.primitive.Scatter')
graphicsIndex = graphicsIndex + 1;
% Get x and y data
xData = get(obj, 'XData');
yData = get(obj, 'YData');
% Skip if empty
if isempty(xData) || isempty(yData)
continue
end
% Ensure row vectors for indexing
xData = xData(:)';
yData = yData(:)';
% Filter data to x-axis range
mask = (xData >= xLims(1)) & (xData <= xLims(2));
xData = xData(mask);
yData = yData(mask);
% Determine label
label = '';
if isprop(obj, 'DisplayName') && ~isempty(obj.DisplayName)
label = obj.DisplayName;
elseif graphicsIndex <= length(legendLabels) && ~isempty(legendLabels{graphicsIndex})
label = legendLabels{graphicsIndex};
else
scatterCounter = scatterCounter + 1;
label = sprintf('scatter_%d', scatterCounter);
end
% Sanitize label for filename
label = sanitizeFilename(label);
% Create filename with tile title if present
if ~isempty(tileTitle)
fileName = sprintf('%s_%s_%s_vs_%s_%s.txt', figTitle, tileTitle, yLabel, xLabel, label);
else
fileName = sprintf('%s_%s_vs_%s_%s.txt', figTitle, yLabel, xLabel, label);
end
% Write data to ASCII file
data = [xData(:), yData(:)];
writematrix(data, fileName, 'Delimiter', '\t');
fprintf('Saved: %s\n', fileName);
end
end
end
end
fprintf('Data export complete.\n');
end
% =========================================================================
% Helper function: sanitize strings for use in filenames
% =========================================================================
function cleanStr = sanitizeFilename(str)
%sanitizeFilename Remove or replace characters invalid in filenames
%
% Replaces spaces, slashes, backslashes, colons, asterisks, parentheses,
% and other problematic characters with underscores.
cleanStr = str;
cleanStr = strrep(cleanStr, ' ', '_');
cleanStr = strrep(cleanStr, '/', '_');
cleanStr = strrep(cleanStr, '\', '_');
cleanStr = strrep(cleanStr, ':', '_');
cleanStr = strrep(cleanStr, '*', '_');
cleanStr = strrep(cleanStr, '?', '_');
cleanStr = strrep(cleanStr, '"', '_');
cleanStr = strrep(cleanStr, '<', '_');
cleanStr = strrep(cleanStr, '>', '_');
cleanStr = strrep(cleanStr, '|', '_');
cleanStr = strrep(cleanStr, '(', '_');
cleanStr = strrep(cleanStr, ')', '_');
cleanStr = strrep(cleanStr, '.', '_'); % Optional: replace dots
end
+24
View File
@@ -0,0 +1,24 @@
function PsiFarbe = getPsiColors()
% Returns a struct with PSI corporate identity colors.
% define PSI cooperate identity colours
PsiFarbe.hellgrau = [226 226 226]/255;
PsiFarbe.mittelgrau = [184 184 184]/255;
PsiFarbe.dunkelgrau = [78 78 77]/255;
PsiFarbe.blaugrau = [236 236 243]/255; % This is actually not a PSI colour
PsiFarbe.gelb = [239 200 37]/255;
PsiFarbe.hellorange = [227 164 31]/255;
PsiFarbe.dunkelorange = [207 92 22]/255;
PsiFarbe.hellrot = [164 36 23]/255;
PsiFarbe.dunkelbraun = [114 85 65]/255;
PsiFarbe.hellbraun = [127 119 47]/255;
PsiFarbe.mittelgruen = [135 150 59]/255;
PsiFarbe.hellgruen = [195 214 47]/255;
PsiFarbe.dunkelgruen = [72 130 33]/255;
PsiFarbe.dunkelrot = [104 47 86]/255;
PsiFarbe.dunkelblau = [37 65 108]/255;
PsiFarbe.mittelgruenblau = [61 164 168]/255;
PsiFarbe.hellblau = [58 162 190]/255;
PsiFarbe.mittelblau = [50 118 158]/255;
end
+107
View File
@@ -0,0 +1,107 @@
function TableLabViewIss = importLabViewIssfile(filenameOrFolder, dataLines)
%IMPORTFILE Import data from a text file or folder
% TableLabViewIss = IMPORTFILE(FILENAME) reads data from text
% file FILENAME and returns the data as a table.
%
% TableLabViewIss = IMPORTFILE(FOLDER) reads all .txt files from
% FOLDER, merges them into a single table (sorted by datetime).
%
% TableLabViewIss = IMPORTFILE(FILE, DATALINES) reads data
% for the specified row interval(s) of text file FILENAME.
%
% Example (single file):
% TableLabViewIss = importLabViewIssfile("/Users/bartels-rausch/Desktop/17-12-2025_09-14datafile.txt", [2, Inf]);
%
% Example (folder):
% TableLabViewIss = importLabViewIssfile("/Users/bartels-rausch/Desktop/DataFolder");
%
% See also READTABLE.
%% Input handling
% If dataLines is not specified, define defaults
if nargin < 2
dataLines = [2, Inf];
end
%% Check if input is a file or folder
if isfolder(filenameOrFolder)
% Process all .txt files in the folder
folderPath = filenameOrFolder;
fileList = dir(fullfile(folderPath, '*.txt'));
if isempty(fileList)
error('No .txt files found in folder: %s', folderPath);
end
fprintf('Found %d .txt files in folder. Processing...\n', length(fileList));
% Initialize empty table
TableLabViewIss = table();
% Loop through each file
for i = 1:length(fileList)
filepath = fullfile(folderPath, fileList(i).name);
fprintf(' [%d/%d] Processing: %s\n', i, length(fileList), fileList(i).name);
% Import this file
tbl_temp = importSingleFile(filepath, dataLines);
% Append to main table
if isempty(TableLabViewIss)
TableLabViewIss = tbl_temp;
else
TableLabViewIss = [TableLabViewIss; tbl_temp];
end
end
% Sort by datetime
[~, sortIdx] = sort(TableLabViewIss.datetime);
TableLabViewIss = TableLabViewIss(sortIdx, :);
fprintf('Merged %d files into single table with %d rows.\n', length(fileList), height(TableLabViewIss));
else
% Process single file
TableLabViewIss = importSingleFile(filenameOrFolder, dataLines);
end
end
%% Helper function to import a single file
function tbl = importSingleFile(filename, dataLines)
% Import options setup
opts = delimitedTextImportOptions("NumVariables", 10);
% Specify range and delimiter
opts.DataLines = dataLines;
opts.Delimiter = "\t";
% Specify column names and types
opts.VariableNames = ["date_DD_MM_JJJJ_", "time_hh_mm_ss_", "pressure_cell_hPa_", "temperature__C_", "x_mm_", "y_mm_", "z_mm_", "Tsealing__C_", "flowCryo_l_min_", "pOut_hPa_"];
opts.VariableTypes = ["datetime", "duration", "double", "double", "double", "double", "double", "double", "double", "double"];
% Specify file level properties
opts.ExtraColumnsRule = "ignore";
opts.EmptyLineRule = "read";
% Specify variable properties
opts = setvaropts(opts, "date_DD_MM_JJJJ_", "InputFormat", "dd.MM.yyyy");
opts = setvaropts(opts, "time_hh_mm_ss_", "InputFormat", "hh:mm:ss.S");
% Import the data as regular table
tbl = readtable(filename, opts);
% Merge date + time into a single datetime column
tbl.datetime = tbl.date_DD_MM_JJJJ_ + tbl.time_hh_mm_ss_;
% Set the datetime display format to show date and time
tbl.datetime.Format = 'dd.MM.yyyy HH:mm:ss.SSS';
% Remove the original date and time columns
tbl(:, ["date_DD_MM_JJJJ_", "time_hh_mm_ss_"]) = [];
% Move datetime to be the first column
tbl = movevars(tbl, "datetime", "Before", 1);
end
+94 -87
View File
@@ -460,7 +460,7 @@ else
% 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__'}
@@ -542,9 +542,9 @@ if ~isempty(TmpFileList{1})
% 1. INITIALIZE MASTER TEMPLATE (Strict Definition)
% =================================================================
% Every variable MUST be declared here so the struct sizes match perfectly
current_file_info.creationDate = [NaN NaN NaN NaN NaN NaN];
current_file_info.lastModifiedDatenum = current_file_info.datenum;
current_file_info.lastModifiedDatenum = current_file_info.datenum;
current_file_info.lastModifiedDatestr = current_file_info.date;
current_file_info.image = {[]};
current_file_info.imageRoh = {[]};
@@ -566,11 +566,11 @@ if ~isempty(TmpFileList{1})
current_file_info.scientaSequenceIterations = NaN;
current_file_info.importDate = datestr(now);
try
current_file_info.analysisDir = file.mFile;
catch
current_file_info.analysisDir = pwd;
current_file_info.analysisDir = file.mFile;
catch
current_file_info.analysisDir = pwd;
end
current_file_info.sample = '';
current_file_info.sample = '';
% =================================================================
% --- Case A: Scienta (.ibw) Files ---
% =================================================================
@@ -619,7 +619,7 @@ if ~isempty(TmpFileList{1})
if isempty(ProcessedXPS), ProcessedXPS = current_file_info; else, ProcessedXPS = [ProcessedXPS; current_file_info]; end %#ok<AGROW>
end
% =================================================================
% =================================================================
% --- Case B: Specs (.xy) Files ---
% =================================================================
elseif endsWith(current_file_info.name, '.xy', 'IgnoreCase', true)
@@ -628,10 +628,10 @@ if ~isempty(TmpFileList{1})
try, osDateVec = datevec(datetime(cDateOS, 'InputFormat', 'dd-MMM-yyyy HH:mm:ss'));
catch, try osDateVec = datevec(mDateOS); catch, osDateVec = [NaN NaN NaN NaN NaN NaN]; end, end
if isempty(osDateVec) || size(osDateVec, 1) == 0, osDateVec = [NaN NaN NaN NaN NaN NaN]; end
fileLines = readlines(current_path);
regionStarts = find(startsWith(fileLines, '# Region:'));
% --- 1. PARSE GLOBAL HEADER (Before First Region) ---
% SpecsLab moved many parameters to the top of the file in newer versions
globalScanMode = '';
@@ -640,42 +640,42 @@ if ~isempty(TmpFileList{1})
globalWorkFunction = NaN;
globalExEnergy = NaN;
globalSample = '';
if ~isempty(regionStarts)
globalHeader = fileLines(1:regionStarts(1)-1);
for L = 1:length(globalHeader)
lineStr = strtrim(globalHeader(L));
lineStr = regexprep(lineStr, '^#\s+', '# '); % Normalize spaces after #
if startsWith(lineStr, '# Scan Mode:')
globalScanMode = strtrim(extractAfter(lineStr, '# Scan Mode:'));
elseif startsWith(lineStr, '# Analyzer Lens Voltage:')
elseif startsWith(lineStr, '# Analyzer Lens Voltage:')
globalLensMode = strtrim(extractAfter(lineStr, '# Analyzer Lens Voltage:'));
elseif startsWith(lineStr, '# Analyzer Lens:')
globalLensMode = strtrim(extractAfter(lineStr, '# Analyzer Lens:'));
elseif startsWith(lineStr, '# Analyzer Slit:')
globalAnalyzerSlit = strtrim(extractAfter(lineStr, '# Analyzer Slit:'));
elseif startsWith(lineStr, '# Work Function:')
elseif startsWith(lineStr, '# Work Function:')
globalWorkFunction = sscanf(strtrim(extractAfter(lineStr, '# Work Function:')), '%f');
elseif startsWith(lineStr, '# Eff. Workfunction:')
globalWorkFunction = sscanf(strtrim(extractAfter(lineStr, '# Eff. Workfunction:')), '%f');
elseif startsWith(lineStr, '# Excitation Energy:')
globalExEnergy = sscanf(strtrim(extractAfter(lineStr, '# Excitation Energy:')), '%f');
elseif startsWith(lineStr, '# Comment:')
elseif startsWith(lineStr, '# Comment:')
globalSample = strtrim(extractAfter(lineStr, '# Comment:'));
% =========================================================================
% OFFLINE XPS BUG FIX (Mg vs Al Source)
% If the file lacks "Monochromator" in the header and the energy is
% incorrectly tagged as ~1453.6 or 1486.6, overwrite it to 1253.6 (Mg Ka).
% =========================================================================
if ~any(contains(string(fileLines), 'Monochromator', 'IgnoreCase', true))
% =========================================================================
% OFFLINE XPS BUG FIX (Mg vs Al Source)
% If the file lacks "Monochromator" in the header and the energy is
% incorrectly tagged as ~1453.6 or 1486.6, overwrite it to 1253.6 (Mg Ka).
% =========================================================================
if ~any(contains(string(fileLines), 'Monochromator', 'IgnoreCase', true))
% Checking both 1453.6 (as requested) and 1486.61 (as seen in your file)
if (abs(globalExEnergy - 1453.61) < 1) || (abs(globalExEnergy - 1486.61) < 1)
globalExEnergy = 1253.6;
globalExEnergy = 1253.6;
end
end
% =========================================================================
% =========================================================================
end
end
end
@@ -685,7 +685,7 @@ if ~isempty(TmpFileList{1})
startIdx = regionStarts(r);
if r < length(regionStarts), endIdx = regionStarts(r+1)-1; else, endIdx = length(fileLines); end
regionBlock = fileLines(startIdx:endIdx);
% =====================================================
% 2. Extract Metadata
% =====================================================
@@ -696,7 +696,7 @@ if ~isempty(TmpFileList{1})
regionEntry.xRayEkin_eV = globalExEnergy;
regionEntry.scientaPassEnergy_eV = NaN;
regionEntry.scientaDwellTime_ms = NaN;
% Inherit defaults from Global Header
regionEntry.scientaAcquisitionMode = globalScanMode;
regionEntry.scientaLensMode = globalLensMode;
@@ -706,14 +706,14 @@ if ~isempty(TmpFileList{1})
if ~isempty(globalSample)
regionEntry.sample = globalSample;
end
% Scan the header lines directly
for L = 1:min(150, length(regionBlock)) % Metadata is always at the top
lineStr = strtrim(regionBlock(L));
if isempty(lineStr) || ~startsWith(lineStr, '#'), continue; end
lineStr = regexprep(lineStr, '^#\s+', '# '); % Normalize spaces after #
if startsWith(lineStr, '# Region:')
rName = strtrim(extractAfter(lineStr, '# Region:'));
elseif startsWith(lineStr, '# Excitation Energy:')
@@ -723,7 +723,7 @@ if ~isempty(TmpFileList{1})
% =========================================================================
if ~any(contains(string(fileLines), 'Monochromator', 'IgnoreCase', true))
if (abs(regionEntry.xRayEkin_eV - 1453.6) < 1) || (abs(regionEntry.xRayEkin_eV - 1486.61) < 1)
regionEntry.xRayEkin_eV = 1253.6;
regionEntry.xRayEkin_eV = 1253.6;
end
end
% =========================================================================
@@ -761,22 +761,22 @@ if ~isempty(TmpFileList{1})
regionEntry.sample = strtrim(extractAfter(lineStr, '# Comment:'));
end
end
% --- Format Name & Region ---
regionEntry.regionName = rName;
cleanRName = strrep(rName, ' ', '_');
cleanRName = strrep(rName, ' ', '_');
% Write the original OS file name
regionEntry.originalFileName = current_file_info.name;
% Prevent identical filenames by appending _Reg1, _Reg2 if multiple regions exist
if length(regionStarts) > 1
regionEntry.name = sprintf('%04d_%s_Reg%d_%s.xy', specID, cleanRName, r, exEnergyStr);
else
regionEntry.name = sprintf('%04d_%s_%s.xy', specID, cleanRName, exEnergyStr);
end
% 2. Extract Data
hIdx = find(startsWith(strtrim(regionBlock), '# Cycle:'));
@@ -882,7 +882,7 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element
tmpCurrentFile = fullfile(TmpFileList{2}(ii).folder, TmpFileList{2}(ii).name);
% ==========================================================
% IN-MEMORY DATE RESTORATION
% Check if this file was created by the splitter. If yes,
% Check if this file was created by the splitter. If yes,
% retrieve its historical dates from the memory map!
% ==========================================================
if exist('splitFileDatesMap', 'var') && isKey(splitFileDatesMap, TmpFileList{2}(ii).name)
@@ -893,7 +893,7 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element
% Otherwise, just read the dates directly from the OS
[cDateOS, mDateOS] = getOSDates(tmpCurrentFile);
end
% START TIME: OS Creation Date (or mapped historical creation)
TmpFileList{2}(ii).creationDate = datevec(cDateOS);
% END TIME: OS Modification Date (or mapped historical modification)
@@ -921,7 +921,7 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element
end
% 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
@@ -1026,14 +1026,21 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element
tmp = h5read([TmpFileList{2}(ii).folder filesep TmpFileList{2}(ii).name],'/entry/instrument/NDAttributes/CenterEnergy'); % in eV
end
end
% fix for very early Phoenix data (08.2026)
if tmp(1)==0
clearvars tmp
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');
TmpFileList{2}(ii).scientaEkinRange_eV = [tmp(1) tmp2(1)];
else
TmpFileList{2}(ii).scientaEkinRange_eV = [double(tmp(1))-TmpFileList{2}(ii).scientaPassEnergy_eV/20 double(tmp(1))+TmpFileList{2}(ii).scientaPassEnergy_eV/20];
end
TmpFileList{2}(ii).scientaEkinRange_eV = [double(tmp(1))-TmpFileList{2}(ii).scientaPassEnergy_eV/20 double(tmp(1))+TmpFileList{2}(ii).scientaPassEnergy_eV/20];
TmpFileList{2}(ii).scientaEkin_eV = { ...
linspace(TmpFileList{2}(ii).scientaEkinRange_eV(1), ...
TmpFileList{2}(ii).scientaEkinRange_eV(2), ...
size(TmpFileList{2}(ii).image,1))' };
TmpFileList{2}(ii).scientaEkin_eV = { ...
linspace(TmpFileList{2}(ii).scientaEkinRange_eV(1), ...
TmpFileList{2}(ii).scientaEkinRange_eV(2), ...
size(TmpFileList{2}(ii).image,1))' };
TmpFileList{2}(ii).scientaEkinStep_eV = NaN;
else
% NEXAFS kinetic energy window - scan mode
try
@@ -1060,11 +1067,11 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element
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 : ...
TmpFileList{2}(ii).scientaEkin_eV = { ...
(TmpFileList{2}(ii).scientaEkinRange_eV(1) : ...
TmpFileList{2}(ii).scientaEkinStep_eV : ...
TmpFileList{2}(ii).scientaEkinRange_eV(2))' };
end
end
clearvars tmp
% read I_0
@@ -1200,7 +1207,7 @@ for ii = 1:size(TmpFileList{3},1) % do this for each picture in the list
% Get OS Dates
tmpCurrentFile = fullfile(TmpFileList{3}(ii).folder, TmpFileList{3}(ii).name);
[cDateOS, ~] = getOSDates(tmpCurrentFile);
% START TIME: OS Creation Date
TmpFileList{3}(ii).creationDate = datevec(cDateOS);
% END TIME: Robust OS Modification Date
@@ -1281,12 +1288,12 @@ if matches(p.Results.importLabView,'on')
try
% Get list of all files in that folder
TmpFileList = [ ...
TmpFileList = [ ...
dir([dataPath_LabView filesep '**' filesep 'logfile*.txt']); ...
dir([dataPath_LabView filesep '**' filesep '*datafile.txt'])];
if ~isempty(TmpFileList)
keep = ~startsWith({TmpFileList.name}, {'.','~'}) & ...
~startsWith({TmpFileList.name}, 'LabView_T_RH', 'IgnoreCase', true);
~startsWith({TmpFileList.name}, 'LabView_T_RH', 'IgnoreCase', true);
TmpFileList = TmpFileList(keep);
[~, iu] = unique(fullfile({TmpFileList.folder}, {TmpFileList.name}), 'stable');
TmpFileList = TmpFileList(iu);
@@ -1338,7 +1345,7 @@ if matches(p.Results.importLabView,'on')
if ~isempty(tmp.Data) % only do this if LabView data are not missing (because data logging was off).
% Cryo-holder set T (Sample holder temperature is in field ts.Data:1)
ListFiles.sampleTemp_dC(ii) = tmp.Data(1,1); % in °C (only get data in first row of tmp)
ListFiles.sampleTemp_dC(ii) = tmp.Data(1,1); % in °C (only get data in first row of tmp)
% Cell pressure
% (TODO: Pick correct Baratron read out: either field: ts.Data 4,8,and 9)
ListFiles.cellPressure_mbar(ii) = tmp.Data(1,4); % in mbar
@@ -1554,11 +1561,11 @@ end
% % col 6: Baratron 2 - cell - 10mbar max - mbar -> ts.Data:8
% % col 7: Baratron 3 - cell - 0.05mbar max - mbar -> ts.Data:9
% % col 8: Baratron 4 - - mbar
% % col 9: Temp.Ice Sample - °C -> ts.Data:1
% % col 9: Temp.Ice Sample - °C -> ts.Data:1
% % col 10: Temp.Heated Sample
% % col 11: none
% % col 12: Temp. Reservoir "Cooler 1" - °C -> ts.Data:5
% % col 13: Temp. Reservoir "Cooler 2" - °C -> ts.Data:6
% % col 12: Temp. Reservoir "Cooler 1" - °C -> ts.Data:5
% % col 13: Temp. Reservoir "Cooler 2" - °C -> ts.Data:6
% % col 14: Flow He(cryoholder) - l/min -> ts.Data:7
% % col 15: none
% % col 16: none
@@ -1572,10 +1579,10 @@ end
% % col 6: Baratron 2 - cell - 10mbar max - mbar -> ts.Data:8
% % col 7: Baratron 3 - cell - 0.05mbar max - mbar -> ts.Data:9
% % col 8: Baratron 4 - - mbar
% % col 9: Temp.Ice Sample - °C -> ts.Data:1
% % col 9: Temp.Ice Sample - °C -> ts.Data:1
% % col 10: Temp.Heated Sample
% % col 11: Temp. Reservoir "Cooler 1" - °C -> ts.Data:5
% % col 12: Temp. Reservoir "Cooler 2" - °C -> ts.Data:6
% % col 11: Temp. Reservoir "Cooler 1" - °C -> ts.Data:5
% % col 12: Temp. Reservoir "Cooler 2" - °C -> ts.Data:6
% % col 13: Flow He(cryoholder) - l/min -> ts.Data:7
% % col 14: Flow gas 1
% % col 15: Pressure chamber - mbar
@@ -1583,11 +1590,11 @@ end
% % col 17: y-position of cryoholder - mm -> ts.Data:11
% % col 18: z-position of cryoholder - mm -> ts.Data:12
% % col 19: nothing
% % col 20: Temp.Sealing - °C -> ts.Data:13
%
%
% % col 20: Temp.Sealing - °C -> ts.Data:13
%
%
% %%
%
%
% % Create an InputParser object
% p = inputParser;
% % Add inputs to the scheme.
@@ -1596,24 +1603,24 @@ end
% addOptional(p,'appendToStructure',defaultAppendToStructure,@isstruct);
% % Parse the inputs.
% parse(p,fullFileName,varargin{:})
%
%
% %%
% % get filedate
% [~,name,~] = fileparts(p.Results.fullFileName);
% tmp = strsplit(name,'_PC');
% filedate = datenum(tmp{1},'yyyy-mm-dd_HH.MM.SS');
%
%
% clearvars name tmp
%
%
% %%
% % parse structure to append to
%
%
% if isstruct(p.Results.appendToStructure)
% LabView = p.Results.appendToStructure;
% end
% %%
% if datenum(filedate) <= datenum('04-Mar-2019 13:30:00')
%
%
% fid = fopen(p.Results.fullFileName);
% data = textscan(fid, ...
% '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f',...
@@ -1628,14 +1635,14 @@ end
% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ...
% = 'Readout Napp Chamber raw signal';
% clear fid ans formatOut nans t x
%
%
% elseif datenum(filedate) >= datenum('15-Sep-2021 01:00:00') && datenum(filedate) <= datenum('01-Apr-2023 01:00:00')
% % during this time, date and time were in american format.
% fid = fopen(p.Results.fullFileName);
% data = textscan(fid, ...
% '%s %s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',...
% 'HeaderLines',1); %
%
%
% fclose(fid);
% % the date formating in the dat file is really messed up. Try to make
% % it consitent with MATLAB. The problem is that MATLAB does not
@@ -1648,7 +1655,7 @@ end
% newTimeSplit = cellfun(@(x) split(char(x),':'),data{2},'UniformOutput', false);
% % combine both with the am/pm string in column 3 to a new datestring
% customDateStr = cellfun(@(x,y,z) [datestr(datenum([str2double(x{3}),str2double(x{1}),str2double(x{2}),str2double(y{1}),str2double(y{2}),str2double(y{3})])), ' ', char(z)], newDateSplit,newTimeSplit,data{3}, 'uniformOutput',false) ;
%
%
% % define time series
% LabView.(['ts_' datestr(customDateStr{1},'YYYYmmdd_HHMM')]) ...
% = timeseries([data{10} data{4} data{5} data{6} data{12} ...
@@ -1657,10 +1664,10 @@ end
% LabView.(['ts_' datestr(customDateStr{1},'YYYYmmdd_HHMM')]).Name ...
% = 'Readout Napp Chamber raw signal';
% clearvars fid ans formatOut nans t x new* custom*
%
%
%
%
% elseif datenum(filedate) >= datenum('01-Apr-2023 02:00:00')
%
%
% fid = fopen(p.Results.fullFileName);
% data = textscan(fid, ...
% '%s %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',...
@@ -1669,17 +1676,17 @@ end
% % PM IS MISSING and some changes to the LabView file. Confirm with Luca....)
% fclose(fid);
% % define time series
%
%
% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ...
% = timeseries([data{9} data{3} data{4} data{5} data{11} ...
% data{12} data{20} data{6} data{7} data{15} data{16} data{17} data{19}] , ...
% datestr(datenum([char(data{1}), char(data{2})],...
% 'dd.mm.yyyyHH:MM:SS'),0));
%
%
% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ...
% = 'Readout Napp Chamber raw signal';
% clear fid ans formatOut nans t x
%
%
% else
% fid = fopen(p.Results.fullFileName);
% data = textscan(fid, ...
@@ -1687,13 +1694,13 @@ end
% 'HeaderLines',1); % data file has 2 text columns, followed by 14 numeric columns
% fclose(fid);
% % define time series
%
%
% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]) ...
% = timeseries([data{9} data{3} data{4} data{5} data{11} ...
% data{12} data{13} data{6} data{7} data{16} data{17} data{18} data{20}] , ...
% datestr(datenum([char(data{1}), char(data{2})],...
% 'dd.mm.yyyyHH:MM:SS'),0));
%
%
% LabView.(['ts_' datestr(datenum([char(data{1}{1}), char(data{2}{1})],'dd.mm.yyyyHH:MM:SS'),'YYYYmmdd_HHMM')]).Name ...
% = 'Readout Napp Chamber raw signal';
% clear fid ans formatOut nans t x
@@ -1885,7 +1892,7 @@ if numel(groupnames) > 3
fileObj.LastWriteTime = System.DateTime.Parse(orig_mDateOS);
catch
end
elseif ismac || isunix
% Mac/Linux: Use 'touch' to restore Modification time
try
@@ -1893,10 +1900,10 @@ if numel(groupnames) > 3
system(sprintf('touch -m -t %s "%s"', timestamp_str, outname));
catch
end
% NOTE: Mac OS makes it incredibly difficult to change the Birth (Creation)
% time via standard commands. However, because we restored the Modification
% time perfectly, our "Paradox Check" in the main loop will automatically
% step in, realize Creation is newer than Modification, and fix the
% NOTE: Mac OS makes it incredibly difficult to change the Birth (Creation)
% time via standard commands. However, because we restored the Modification
% time perfectly, our "Paradox Check" in the main loop will automatically
% step in, realize Creation is newer than Modification, and fix the
% Creation date for us in the data table!
end
% ==========================================================
@@ -1914,7 +1921,7 @@ else
% disp('File has three or fewer groups. No processing required.');
end
end
end
@@ -1946,10 +1953,10 @@ elseif ismac || isunix
[status, cmdOut] = system(['stat -f "%SB" -t "%d-%b-%Y %H:%M:%S" "', fullPath, '"']);
if status == 0
creationDateOS = strtrim(cmdOut);
% ==========================================================
% macOS COPIED FILE WORKAROUND
% If copied/downloaded, macOS sets Birth Time to 'today' but
% If copied/downloaded, macOS sets Birth Time to 'today' but
% keeps the old Modification Time. If Creation > Modification,
% use the older Modification time as the true origin!
% ==========================================================
+54
View File
@@ -0,0 +1,54 @@
function nappImage(DataTable,varargin)
%
% no longer used.
% Define conditions and defaults of input
defaultTableVariable = {'image'};
expectedTableVariable = {'image','imageRoh'};
defaultTableSelection = true(size(DataTable,1));
% Parse input
p = inputParser;
addRequired(p,'DataTable',@istable);
addOptional(p,'tableSelection',defaultTableSelection,@islogical);
addParameter(p,'tableVariable',defaultTableVariable,...
@(x) any(validatestring(x,expectedTableVariable)));
parse(p,DataTable,varargin{:});
% check input (all elements of input variable need to have non-zero size)
if ~all(cell2mat(cellfun(@size,p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable),'UniformOutput',false)),'all')
disp('The table variable appears to be empty. I quit now')
error('The table variable appears to be empty.')
end
for ii = 1:size(p.Results.DataTable(p.Results.tableSelection,:),1)
figure('Name',p.Results.DataTable(p.Results.tableSelection,:).name{ii});
% Create axes
hold(gca,'on');
% Try to plot image with x and y
try
image(p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii},p.Results.DataTable(p.Results.tableSelection,:).scientaEkin_eV{ii},...
p.Results.DataTable.(p.Results.DataFieldname){ii},'Parent',gca,'CDataMapping','scaled');
axis(gca,'ij');
set(gca,'Layer','top');
xlim([p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(1) p.Results.DataTable(p.Results.tableSelection,:).xRayEkin_eV{ii}(end)]);
ylim([p.Results.DataTable(p.Results.tableSelection,:).scientaEkinMin_eV(ii) p.Results.DataTable(p.Results.tableSelection,:).scientaEkinMax_eV(ii)]);
xlabel('Photon energy [eV]')
ylabel('Auger energy [eV]')
% if this does not work, plot without scaling (just with row and column numbers)
catch
image(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii},'Parent',gca,'CDataMapping','scaled');
axis(gca,'ij');
set(gca,'Layer','top');
ylim([1 size(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii},1)]);
xlim([1 size(p.Results.DataTable(p.Results.tableSelection,:).(p.Results.tableVariable){ii},2)]);
xlabel('# Photon energy, column number')
ylabel('# Auger energy, column number')
end
box(gca,'on');
title(p.Results.DataTable(p.Results.tableSelection,:).name{ii},'Interpreter','none')
end
end
+48
View File
@@ -0,0 +1,48 @@
function saveFilename = nappSaveDataTable(DataTable, options)
arguments
DataTable table
options.suffix char = ""
options.varName char = ""
end
currentScriptName = '';
% 1. Determine script name (Your existing logic is solid here)
stackInfo = dbstack;
if length(stackInfo) > 1
[~, currentScriptName, ~] = fileparts(stackInfo(2).file);
if contains(currentScriptName, 'LiveEditorEvaluationHelper')
currentScriptName = '';
end
end
if isempty(currentScriptName)
try
[~, currentScriptName, ~] = fileparts(matlab.desktop.editor.getActiveFilename);
catch
currentScriptName = 'ManualRun';
end
end
% 2. Generate the final filename
saveFilename = sprintf('%s%s.mat', currentScriptName, options.suffix);
% 3. Determine the variable name
% Priority: 1. Manual override, 2. inputname, 3. Default 'DataTable'
if options.varName ~= ""
finalVarName = options.varName;
else
finalVarName = inputname(1);
if isempty(finalVarName)
finalVarName = 'DataTable';
end
end
% 4. SAVE
% We create a struct where the field name is our dynamic string.
% Then we save the struct with the '-struct' flag.
saveData.(finalVarName) = DataTable;
save(saveFilename, '-struct', 'saveData');
fprintf('✅ Successfully saved %s to: %s\n', finalVarName, saveFilename);
end
+85
View File
@@ -0,0 +1,85 @@
function Table = outerjoinRowToTable(Table,TableRowToBeAdded)
%outerjoinRowToTable Adds one row to a table. Variables that are missing in
%either the table or the row are added with empty values {' '} for cells
%and NaN for doubles. Works with tables that contain both cells and
%doubles.
%
% * Strings in the table are not yet supported.
% * The row that is added needs to be formatted as table
% * Update 2024: Adds several rows, that is tables
% * Update 2026: Handles peakfitting columns with different size.
%
% T = outerjoinRowToTable(TableToAddTo,RowToBeAdded)
%
% tmpTableToBeAdded = table({tmpCombined(:,1)},{tmpCombined(:,2)},{'660eV, m50, 092022'},{'#288,#289,#290.ibw'}, ...
% 'VariableNames',["bindingEnergy_eV","spectrum_countsNorm","sample","name"]);
% SpectrumList = outerjoinRowToTable(SpectrumList,tmpTableToBeAdded);
%
% Thorsten, December 2023
% Original Table has more variables
if ~all(ismember(Table.Properties.VariableNames,TableRowToBeAdded.Properties.VariableNames))
tmpMissingVariable = ~ismember(Table.Properties.VariableNames,TableRowToBeAdded.Properties.VariableNames);
tmpVariablesToBeAdded = Table.Properties.VariableNames(tmpMissingVariable);
for ii = 1:size(tmpVariablesToBeAdded,2)
if matches(class(Table.(tmpVariablesToBeAdded{ii})),'double') || matches(class(Table.(tmpVariablesToBeAdded{ii})),'int32')
tmpContentToBeAdded = NaN(size(TableRowToBeAdded,1),size(Table.(tmpVariablesToBeAdded{ii}),2));
TableRowToBeAdded = addvars(TableRowToBeAdded,tmpContentToBeAdded,'NewVariableNames',tmpVariablesToBeAdded{ii});
elseif matches(class(Table.(tmpVariablesToBeAdded{ii})),'cell')
tmpContentToBeAdded = repmat({' '},size(TableRowToBeAdded,1),size(Table.(tmpVariablesToBeAdded{ii}),2));
TableRowToBeAdded = addvars(TableRowToBeAdded,tmpContentToBeAdded,'NewVariableNames',tmpVariablesToBeAdded{ii});
elseif matches(class(Table.(tmpVariablesToBeAdded{ii})),'string')
tmpContentToBeAdded = repmat({" "},size(TableRowToBeAdded,1),size(Table.(tmpVariablesToBeAdded{ii}),2));
TableRowToBeAdded = addvars(TableRowToBeAdded,tmpContentToBeAdded,'NewVariableNames',tmpVariablesToBeAdded{ii});
end
end
end
% Table to be added has more variables
if ~all(ismember(TableRowToBeAdded.Properties.VariableNames,Table.Properties.VariableNames))
tmpMissingVariable = ~ismember(TableRowToBeAdded.Properties.VariableNames,Table.Properties.VariableNames);
tmpVariablesToBeAdded = TableRowToBeAdded.Properties.VariableNames(tmpMissingVariable);
for ii = 1:size(tmpVariablesToBeAdded,2)
if matches(class(TableRowToBeAdded.(tmpVariablesToBeAdded{ii})),'double') || matches(class(TableRowToBeAdded.(tmpVariablesToBeAdded{ii})),'int32')
tmpContentToBeAdded = NaN(size(Table,1),size(TableRowToBeAdded.(tmpVariablesToBeAdded{ii}),2));
Table = addvars(Table,tmpContentToBeAdded,'NewVariableNames',tmpVariablesToBeAdded{ii});
elseif matches(class(TableRowToBeAdded.(tmpVariablesToBeAdded{ii})),'cell')
tmpContentToBeAdded = repmat({' '},size(Table,1),size(TableRowToBeAdded.(tmpVariablesToBeAdded{ii}),2));
Table = addvars(Table,tmpContentToBeAdded,'NewVariableNames',tmpVariablesToBeAdded{ii});
elseif matches(class(TableRowToBeAdded.(tmpVariablesToBeAdded{ii})),'string')
tmpContentToBeAdded = repmat({" "},size(Table,1),size(TableRowToBeAdded.(tmpVariablesToBeAdded{ii}),2));
Table = addvars(Table,tmpContentToBeAdded,'NewVariableNames',tmpVariablesToBeAdded{ii});
end
end
end
% Reconcile column widths for peak-fit columns.
% These numeric columns can have variable width (one entry per peak),
% so a width mismatch is expected when spectra have different numbers
% of peaks. Pad the narrower side with NaN.
variableWidthColumns = {'peakArea', 'peakPosition', 'peakFWHM', ...
'peakBackground', 'peakEta', 'peakShirleyK'};
for ii = 1:numel(variableWidthColumns)
varName = variableWidthColumns{ii};
if ~ismember(varName, Table.Properties.VariableNames) || ...
~ismember(varName, TableRowToBeAdded.Properties.VariableNames)
continue
end
widthT = size(Table.(varName), 2);
widthR = size(TableRowToBeAdded.(varName), 2);
if widthT < widthR
Table.(varName) = [Table.(varName), ...
NaN(height(Table), widthR - widthT)];
elseif widthR < widthT
TableRowToBeAdded.(varName) = [TableRowToBeAdded.(varName), ...
NaN(height(TableRowToBeAdded), widthT - widthR)];
end
end
% Ensure matching column order before concatenation
TableRowToBeAdded = TableRowToBeAdded(:, Table.Properties.VariableNames);
% Add row to table
Table = [Table;TableRowToBeAdded];
end
+151
View File
@@ -0,0 +1,151 @@
function plotExportedData(dataDirectory)
%plotExportedData Plot all exported ASCII data files in one figure
%
% This function reads all .txt files from the exported data (created by
% exportFigureData) and plots them in a single figure for visual comparison.
% File names are parsed to extract axis labels and legend entries.
%
% SYNTAX:
% plotExportedData() % Uses current working directory
% plotExportedData(dataDirectory) % Uses specified directory path
%
% INPUTS:
% dataDirectory : char or string (optional)
% Path to directory containing exported .txt files.
% If omitted, uses current working directory (pwd).
%
% OUTPUT:
% A new figure with all data plotted as lines. Axis labels and legend
% entries are reconstructed from the file names.
%
% NOTES:
% - Reads all .txt files in the specified directory
% - File name format expected: <FigureTitle>_[<TileTitle>_]<YLabel>_vs_<XLabel>_<LineLabel>.txt
% - Plots are created with underscores converted back to spaces for display
% - X and Y axis labels are set based on parsed filename
% - Legend shows line labels from filenames
% - All data is plotted in a single axes for direct visual comparison
%
% EXAMPLE:
% % After running exportFigureData():
% plotExportedData()
% % or
% plotExportedData('../exported_data/')
% Handle input: use current directory if none provided
if nargin == 0 || isempty(dataDirectory)
dataDirectory = pwd;
end
% Get all .txt files in the directory
fileStruct = dir(fullfile(dataDirectory, '*.txt'));
if isempty(fileStruct)
warning('plotExportedData:NoFiles', 'No .txt files found in %s', dataDirectory);
return;
end
% Create a new figure
fig = figure('Name', 'Exported Data Comparison', 'NumberTitle', 'off');
ax = axes(fig);
hold(ax, 'on');
grid(ax, 'on');
% Storage for axis labels (to ensure consistency)
xLabelStr = '';
yLabelStr = '';
legendEntries = {};
% Loop over all .txt files
for ii = 1:length(fileStruct)
fileName = fileStruct(ii).name;
filePath = fullfile(dataDirectory, fileName);
% Read the data
try
data = readmatrix(filePath);
catch ME
warning('plotExportedData:ReadFailed', 'Could not read file: %s\n%s', fileName, ME.message);
continue;
end
% Check if data has at least 2 columns
if size(data, 2) < 2 || size(data, 1) < 1
warning('plotExportedData:InvalidData', 'File %s does not have valid X,Y columns', fileName);
continue;
end
xData = data(:, 1);
yData = data(:, 2);
% Parse filename to extract labels
% Expected format: <FigureTitle>_[<TileTitle>_]<YLabel>_vs_<XLabel>_<LineLabel>.txt
% Remove .txt extension
nameNoExt = fileName(1:end-4);
% Split by '_vs_' to separate y-axis label from the rest
parts = strsplit(nameNoExt, '_vs_');
if length(parts) >= 2
% Extract line label (after _vs_<XLabel>_)
rightPart = parts{2}; % <XLabel>_<LineLabel>
% Find the last underscore to separate XLabel from LineLabel
underscores = find(rightPart == '_');
if ~isempty(underscores)
lastUnderscore = underscores(end);
xLabelExtracted = rightPart(1:lastUnderscore-1);
lineLabel = rightPart(lastUnderscore+1:end);
else
xLabelExtracted = rightPart;
lineLabel = 'data';
end
% Extract y-axis label (before _vs_)
yLabelExtracted = parts{1};
% Remove figure title (first part before first underscore)
% This is tricky, so we'll keep what we have and note it's approximate
% Convert underscores back to spaces for display
xLabelDisplay = strrep(xLabelExtracted, '_', ' ');
yLabelDisplay = strrep(yLabelExtracted, '_', ' ');
lineLabel = strrep(lineLabel, '_', ' ');
% Store axis labels (use the first valid ones found)
if isempty(xLabelStr)
xLabelStr = xLabelDisplay;
end
if isempty(yLabelStr)
yLabelStr = yLabelDisplay;
end
else
% Fallback if filename parsing fails
lineLabel = nameNoExt;
end
% Plot the data
plot(ax, xData, yData, 'DisplayName', lineLabel, 'LineWidth', 1.5, 'Marker', 'o', 'MarkerSize', 4);
% Store legend entry
legendEntries{end+1} = lineLabel;
end
% Set axis labels
xlabel(ax, xLabelStr, 'Interpreter', 'none');
ylabel(ax, yLabelStr, 'Interpreter', 'none');
title(ax, 'Exported Data Comparison', 'Interpreter', 'none');
% Add legend
if ~isempty(legendEntries)
legend(ax, legendEntries, 'Interpreter', 'none', 'Location', 'best');
end
% Optimize figure appearance
grid(ax, 'on');
hold(ax, 'off');
fprintf('Plotted %d data files from: %s\n', length(fileStruct), dataDirectory);
end
+64 -15
View File
@@ -1,30 +1,46 @@
function ax = plotRgaData(tbl, tStart, tEnd)
function ax = plotRgaData(tbl, opts)
%PLOTRGADATA Plot RGA partial pressures vs time (companion to plotCryoData).
%
% ax = plotRgaData(tbl, tStart, tEnd)
% Plots every channel column of an rgaImportData table against its shared
% datetime axis. tStart/tEnd (datetime) optionally subset the range, just
% like plotCryoData. Returns a struct with the axes handle.
% ax = plotRgaData(tbl)
% ax = plotRgaData(tbl, TimeRange=[t1 t2], Masses=["m28" "m18"])
%
% By default the y-axis is linear (RGA scans contain small near-noise
% negatives that a log axis cannot show). To switch to the classic
% semilog view afterwards:
% Plots every channel column of an rgaImportData table against its shared
% datetime axis. If Masses is given, a second figure shows a stackedplot
% of those ions over the same time window. Returns a struct with the
% axes/chart handles.
%
% Name-value arguments (type "plotRgaData(tbl, " and tab for suggestions):
% TimeRange (1x2 datetime) subset window [start end]. Default: full range.
% Masses (string) mass labels for the stackedplot, with or without
% the leading "m": "m28", "28", ["m28" "m18"]. Default: none,
% i.e. the stackedplot is skipped unless masses are requested.
%
% By default the y-axis of the first plot is linear (RGA scans contain
% small near-noise negatives that a log axis cannot show). To switch to
% the classic semilog view afterwards:
% set(ax.partial, 'YScale', 'log');
% (values <= 0 are simply not drawn on a log axis).
%
% EXAMPLE
% tbl = rgaImportData('rga1_Jul_01_2026_08-23-06_AM.txt');
% ax = plotRgaData(tbl);
% ax = plotRgaData(tbl, t1, t2); % zoom to a window
% ax = plotRgaData(tbl); % overlay only
% ax = plotRgaData(tbl, TimeRange=[t1 t2]); % zoom to a window
% ax = plotRgaData(tbl, Masses=["m28" "m18" "m44"]); % overlay + ion stack
%
% See also RGAIMPORTDATA, PLOTCRYODATA
% See also RGAIMPORTDATA, PLOTCRYODATA, STACKEDPLOT
arguments
tbl table
opts.TimeRange (1,2) datetime = [NaT NaT]
opts.Masses (1,:) string = string.empty
end
%--- Extract datetime column ---
dt = tbl.datetime;
%--- If start/stop provided, subset the table ---
if nargin >= 3 && ~isempty(tStart) && ~isempty(tEnd)
mask = dt >= tStart & dt <= tEnd;
%--- If a real time range was given, subset the table (shared by both plots) ---
if ~any(isnat(opts.TimeRange))
mask = dt >= opts.TimeRange(1) & dt <= opts.TimeRange(2);
tbl = tbl(mask, :);
dt = dt(mask);
end
@@ -37,7 +53,7 @@ function ax = plotRgaData(tbl, tStart, tEnd)
units = tbl.Properties.UserData.units;
end
%--- Plot ---
%--- Plot 1: all partial pressures overlaid ---
figure;
tl = tiledlayout(1, 1);
tl.TileSpacing = 'compact';
@@ -57,6 +73,39 @@ function ax = plotRgaData(tbl, tStart, tEnd)
% Match plotCryoData's datetime tick style
ax.partial.XAxis.TickLabelFormat = 'dd-MMM HH:mm';
%--- Plot 2: selected ions as a stackedplot (only if masses requested) ---
ax.stacked = [];
if ~isempty(opts.Masses)
sel = selectIons(chVars, opts.Masses);
if isempty(sel)
warning('plotRgaData:noIonMatch', ...
'No channels matched the requested mass label(s): %s', ...
strjoin(cellstr(opts.Masses), ', '));
else
figure;
ax.stacked = stackedplot(tbl, sel, 'XVariable', 'datetime');
ax.stacked.Title = sprintf('Selected ions [%s]', units);
ax.stacked.XLabel = 'Date & Time';
ax.stacked.DisplayLabels = cellfun(@prettyName, sel, 'UniformOutput', false);
ax.stacked.GridVisible = 'on';
end
end
end
% -------------------------------------------------------------------------
function sel = selectIons(chVars, masses)
% Return the channel names whose mass label matches any entry in masses.
% masses may be given with or without the leading "m" ("m28", "28").
keep = false(1, numel(chVars));
for j = 1:numel(masses)
lbl = masses(j);
if ~startsWith(lbl, "m")
lbl = "m" + lbl;
end
keep = keep | endsWith(string(chVars), "_" + lbl);
end
sel = chVars(keep);
end
% -------------------------------------------------------------------------
+75
View File
@@ -0,0 +1,75 @@
function map = preset_colormap(N,varargin)
% Wrapper for any COLORMAP function, to provide preset parameter values.
%
% (c) 2020-2025 Stephen Cobeldick
%
%%% Syntax %%%
%
% preset_colormap(@fun,p1,p2,...,pN) % store function with any parameters
% map = preset_colormap(N) % generate colormap
% map = preset_colormap % generate colormap
%
%% Examples %%
%
% >> preset_colormap(@cubehelix,0.25,-0.67,1.5,1)
% >> colormap(preset_colormap)
%
% >> preset_colormap(5)
% ans =
% 0 0 0
% 0.1055 0.2788 0.4895
% 0.1660 0.6705 0.4961
% 0.6463 0.8479 0.5076
% 1.0000 1.0000 1.0000
%
% >> preset_colormap(@brewermap,'PuOr')
% >> load topo
% >> load coast
% >> figure
% >> worldmap(topo, topolegend)
% >> contourfm(topo, topolegend);
% >> contourcmap('preset_colormap', 'Colorbar','on', 'Location','horizontal','TitleString','Contour Intervals in Meters');
% >> plotm(lat, long, 'k')
%
%% Dependencies %%
%
% * MATLAB R2009a or later.
%
% See also BREWERMAP CUBEHELIX CMOCEAN LBMAP COLORMAP CONTOURCMAP
persistent fnh arg
%
if nargin==0 % Default N same as MATLAB colormaps.
N = cmDefaultN();
elseif nargin==1 && isnumeric(N)
assert(isscalar(N) && isreal(N) && fix(N)==N,...
'SC:preset_colormap:N:NotRealScalarNumeric',...
'First input <N> must be a real scalar numeric of the colormap size.')
N = double(N);
else % Store function handle and parameter values.
assert(isa(N,'function_handle'),...
'SC:preset_colormap:N:NotFunctionHandle',...
'First input <N> must be a function handle to a colormap function.')
fnh = N;
arg = varargin;
return
end
%
map = fnh(N,arg{:});
%
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%preset_colormap
function N = cmDefaultN()
% Get the colormap size from the current figure or default colormap.
try
F = get(groot,'CurrentFigure');
catch %#ok<CTCH> pre HG2
N = size(get(gcf,'colormap'),1);
return
end
if isempty(F)
N = size(get(groot,'DefaultFigureColormap'),1);
else
N = size(F.Colormap,1);
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%cmDefaultN
+52
View File
@@ -0,0 +1,52 @@
function H = readIBWbinheader(FFN);
% readIBWbinheader - read BinHeader segment of Igor IBW file
% H = readIBWbinheader('foo') reads BinHeader of file Foo.ibw
% IBW version 2,5 only
MAXDIMS = 4;
fid = fopen(FFN,'r');
H.version = fread(fid,1,'int16');
switch H.version,
case 2, % typedef struct BinHeader2 {
H.wfmSize = fread(fid,1,'uint32'); % long wfmSize; // The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.
H.noteSize = fread(fid,1,'uint32'); % long noteSize; // The size of the note text.
H.pictSize = fread(fid,1,'uint32'); % long pictSize; // Reserved. Write zero. Ignore on read.
H.checksum = fread(fid,1,'int16'); % short checksum; // Checksum over this header and the wave header.
case 5,
H.checksum = fread(fid,1,'short');
H.wfmSize = fread(fid,1,'long'); % The size of the WaveHeader5 data structure plus the wave data.
H.formulaSize = fread(fid,1,'long'); % The size of the dependency formula, if any.
H.noteSize = fread(fid,1,'long'); % The size of the note text.
H.dataEUnitsSize = fread(fid,1,'long'); % The size of optional extended data units.
H.dimEUnitsSize = fread(fid,MAXDIMS,'long'); % The size of optional extended dimension units.
H.dimLabelsSize = fread(fid,MAXDIMS,'long'); % The size of optional dimension labels.
H.sIndicesSize = fread(fid,1,'long'); ; % The size of string indicies if this is a text wave.
H.optionsSize1 = fread(fid,1,'long'); ; % Reserved. Write zero. Ignore on read.
H.optionsSize2 = fread(fid,1,'long'); ; % Reserved. Write zero. Ignore on read.
otherwise,
end % switch H.Version
fclose(fid);
if ~ismember(H.version,[2 5]),
error(['Cannot read version ' num2str(H.version) ' IBW files - only versions 2,5 are okay.']);
end
%==FROM IgorBin.h =====================
% #define MAXDIMS 4
%
% typedef struct BinHeader5 {
% short version; // Version number for backwards compatibility.
% short checksum; // Checksum over this header and the wave header.
% long wfmSize; // The size of the WaveHeader5 data structure plus the wave data.
% long formulaSize; // The size of the dependency formula, if any.
% long noteSize; // The size of the note text.
% long dataEUnitsSize; // The size of optional extended data units.
% long dimEUnitsSize[MAXDIMS]; // The size of optional extended dimension units.
% long dimLabelsSize[MAXDIMS]; // The size of optional dimension labels.
% long sIndicesSize; // The size of string indicies if this is a text wave.
% long optionsSize1; // Reserved. Write zero. Ignore on read.
% long optionsSize2; // Reserved. Write zero. Ignore on read.
% } BinHeader5;
+250
View File
@@ -0,0 +1,250 @@
function [BH, H, FFN] = readIBWheaders(FFN);
% readIBWheaders - read BinHeader & WaveHeader segments of Igor IBW file
% [BH, WH, FFN] = readIBWheaders('foo') reads BinHeader & WaveHeader of
% file Foo.ibw - Versions 2 and 5 only. FFN is full filename.
% % 1 byte char
% % 2 bytes short
% % 4 bytes int, long, float, Handle, any kind of pointer
% % 8 bytes double
MAXDIMS = 4;
MAX_WAVE_NAME2 = 18; % Maximum length of wave name in version 1 and 2 files. Does not include the trailing null.
MAX_WAVE_NAME5 = 31; % Maximum length of wave name in version 5 files. Does not include the trailing null.
MAX_UNIT_CHARS = 3;
BH = readIBWbinheader(FFN);
fid = fopen(FFN,'r');
switch BH.version
case 2,
% The version 2 file has the following general layout.
% BinHeader2 structure: 16 bytes
% WaveHeader2 structure excluding wData field; 110 bytes
% Wave data: Variable size
fseek(fid,16,'bof'); % start of wave header of IBW vs 2; % struct WaveHeader2 {
H.type = fread(fid,1, 'uint16'); % short type; % See types (e.g. NT_FP64) above. Zero for text waves.
H.WaveHeader2 = fread(fid,1, 'uint32'); % struct WaveHeader2 **next; // Used in memory only. Write zero. Ignore on read.
H.bname = local_char(fread(fid, MAX_WAVE_NAME2+2, 'char')); % char bname[MAX_WAVE_NAME2+2]; // Name of wave plus trailing null.
H.whVersion = fread(fid,1, 'uint16'); % short whVersion; // Write 0. Ignore on read.
H.srcFldr = fread(fid,1, 'uint16'); % short srcFldr; // Used in memory only. Write zero. Ignore on read.
H.fileName = fread(fid,1, 'uint32'); % Handle fileName; // Used in memory only. Write zero. Ignore on read.
%
H.dataUnits = local_char(fread(fid, MAX_UNIT_CHARS+1, 'char')); % char dataUnits[MAX_UNIT_CHARS+1]; // Natural data units go here - null if none.
H.xUnits = local_char(fread(fid, MAX_UNIT_CHARS+1, 'char')); % char xUnits[MAX_UNIT_CHARS+1]; // Natural x-axis units go here - null if none.
%
H.npnts = fread(fid,1, 'uint32'); % long npnts; // Number of data points in wave.
%
H.aModified = fread(fid,1, 'uint16'); % short aModified; // Used in memory only. Write zero. Ignore on read.
H.hsA = fread(fid,1, 'double'); % double hsA,hsB; // X value for point p = hsA*p + hsB
H.hsB = fread(fid,1, 'double'); % double hsA,hsB; // X value for point p = hsA*p + hsB
%
H.wModified = fread(fid,1, 'uint16'); % short wModified; // Used in memory only. Write zero. Ignore on read.
H.swModified = fread(fid,1, 'uint16'); % short swModified; // Used in memory only. Write zero. Ignore on read.
H.fsValid = fread(fid,1, 'uint16'); % short fsValid; // True if full scale values have meaning.
H.topFullScale = fread(fid,1, 'double'); % double topFullScale,botFullScale; // The min full scale value for wave.
H.botFullScale = fread(fid,1, 'double'); % double topFullScale,botFullScale; // The min full scale value for wave.
%
H.useBits = fread(fid,1, 'char'); % char useBits; // Used in memory only. Write zero. Ignore on read.
H.kindBits = fread(fid,1, 'char'); % char kindBits; // Reserved. Write zero. Ignore on read.
H.formula = fread(fid,1, 'uint32'); % void **formula; // Used in memory only. Write zero. Ignore on read.
H.depID = fread(fid,1, 'uint32'); % long depID; // Used in memory only. Write zero. Ignore on read.
H.creationDate = fread(fid,1, '*uint32'); % unsigned long creationDate; // DateTime of creation. Not used in version 1 files.
H.platform = fread(fid,1, 'uint8'); % unsigned char platform; // 0=unspecified, 1=Macintosh, 2=Windows; Added for Igor Pro 5.5.
% dummy = fread(fid,1, 'uint8'); % align
H.wUnused = fread(fid,1, 'uint8'); % char wUnused[1]; // Reserved. Write zero. Ignore on read.
%
H.modDate = fread(fid,1, '*uint32'); % unsigned long modDate; // DateTime of last modification.
H.waveNoteH = fread(fid,1, 'uint32'); % Handle waveNoteH; // Used in memory only. Write zero. Ignore on read.
%
H.wData = fread(fid,4, 'single').'; % float wData[4]; // The start of the array of waveform data.
case 5,
% The version 5 file has the following general layout.
% BinHeader5 structure: 64 bytes
% WaveHeader5 structure excluding wData field: 320 bytes
% Wave data: Variable size
fseek(fid,64,'bof'); % start of wave header of IBW vs 5
%
H.next = fread(fid,1, 'int32'); % link to next wave in linked list.
%
H.creationDate = fread(fid,1, 'uint32'); % DateTime of creation.
H.modDate = fread(fid,1, 'uint32'); % DateTime of creation.
%
H.npnts = fread(fid,1, 'int32'); % Total number of points (multiply dimensions up to first zero).
H.type = fread(fid,1, 'uint16'); % See types (e.g. NT_FP64) above. Zero for text waves.
H.dLock = fread(fid, 1, 'int16'); % Reserved. Write zero. Ignore on read.
%
H.whpad1 = fread(fid, 6, 'char').'; % Reserved. Write zero. Ignore on read.
H.whVersion = fread(fid, 1, 'int16'); % Write 1. Ignore on read.
H.bname = local_char(fread(fid, MAX_WAVE_NAME5+1, 'char')); % Name of wave plus trailing null.
H.whpad2 = fread(fid, 1, 'int32'); % long Reserved. Write zero. Ignore on read.
H.dFolder = fread(fid, 1, 'int32'); % ptr Used in memory only. Write zero. Ignore on read.
% % Dimensioning info. [0] == rows, [1] == cols etc
H.nDim = fread(fid, MAXDIMS, 'int32').'; % long nDim[MAXDIMS] Number of of items in a dimension -- 0 means no data.
H.sfA = fread(fid, MAXDIMS, 'double').'; % double sfA[MAXDIMS]; Index value for element e of dimension d = sfA[d]*e + sfB[d].
H.sfB = fread(fid, MAXDIMS, 'double').'; % double sfB[MAXDIMS];% double sfB[MAXDIMS];
% %
% % % SI units
H.dataUnits = local_char(fread(fid, MAX_UNIT_CHARS+1, 'char')); % char dataUnits[MAX_UNIT_CHARS+1]; % Natural data units go here - null if none.
H.dimUnits = fread(fid, MAXDIMS*(MAX_UNIT_CHARS+1), '*char').'; % % char dimUnits[MAXDIMS][MAX_UNIT_CHARS+1]; % Natural dimension units go here - null if none.
% %
H.fsValid = fread(fid, 1, 'uint16'); % % short fsValid; % TRUE if full scale values have meaning.
H.whpad3 = fread(fid, 1, 'int16'); % % short whpad3; % Reserved. Write zero. Ignore on read.
H.topFullScale = fread(fid, 1, 'double');
H.botFullScale = fread(fid, 1, 'double'); % % double topFullScale,botFullScale; % The max and max full scale value for wave.
% %
H.dataEUnits = fread(fid, 1, 'int32'); % % Handle dataEUnits; % Used in memory only. Write zero. Ignore on read.
H.dimEUnits = fread(fid, MAXDIMS, 'int32').'; % % Handle dimEUnits[MAXDIMS]; % Used in memory only. Write zero. Ignore on read.
H.dimLabels = fread(fid, MAXDIMS, 'int32').'; % % Handle dimLabels[MAXDIMS]; % Used in memory only. Write zero. Ignore on read.
% %
H.waveNoteH = fread(fid, 1, 'int32'); % % Handle waveNoteH; % Used in memory only. Write zero. Ignore on read.
% %
H.platform = fread(fid, 1, 'uint8'); % % unsigned char platform; % 0=unspecified, 1=Macintosh, 2=Windows; Added for Igor Pro 5.
H.skip_junk________ = fread(fid,80,'char').';
% H. = fread(fid, 1, '');
% % unsigned char spare[3];
% H. = fread(fid, 1, '');
% %
% H. = fread(fid, 1, '');
% % long whUnused[13]; % Reserved. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% %
% H. = fread(fid, 1, '');
% % long vRefNum, dirID; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% %
% H. = fread(fid, 1, '');
% % % The following stuff is considered private to Igor.
% H. = fread(fid, 1, '');
% %
% H. = fread(fid, 1, '');
% % short aModified; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% % short wModified; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% % short swModified; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% %
% H. = fread(fid, 1, '');
% % char useBits; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% % char kindBits; % Reserved. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% % void **formula; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% % long depID; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% %
% H. = fread(fid, 1, '');
% % short whpad4; % Reserved. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% % short srcFldr; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% % Handle fileName; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% %
% H. = fread(fid, 1, '');
% % long **sIndices; % Used in memory only. Write zero. Ignore on read.
% H. = fread(fid, 1, '');
% %
H.wData = fread(fid, 1, 'single');
% % float wData[1]; % The start of the array of data. Must be 64 bit aligned.
% H. = fread(fid, 1, '');
% };
end % switch BH.version
H.EOH_pos = ftell(fid);
fclose(fid);
%==FROM IgorBin.h =====================
% #define MAXDIMS 4
% % #define MAX_WAVE_NAME5 31 // Maximum length of wave name in version 5 files. Does not include the trailing null.
% % #define MAX_UNIT_CHARS 3
% % #define NT_CMPLX 1 % Complex numbers.
% % #define NT_FP32 2 % 32 bit fp numbers.
% % #define NT_FP64 4 % 64 bit fp numbers.
% % #define NT_I8 8 % 8 bit signed integer. Requires Igor Pro 2.0 or later.
% % #define NT_I16 0x10 % 16 bit integer numbers. Requires Igor Pro 2.0 or later.
% % #define NT_I32 0x20 % 32 bit integer numbers. Requires Igor Pro 2.0 or later.
% % #define NT_UNSIGNED 0x40 % Makes above signed integers unsigned. Requires Igor Pro 3.0 or later.
%
% struct WaveHeader5 {
% struct WaveHeader5 **next; % link to next wave in linked list.
%
% unsigned long creationDate; % DateTime of creation.
% unsigned long modDate; % DateTime of last modification.
%
% long npnts; % Total number of points (multiply dimensions up to first zero).
% short type; % See types (e.g. NT_FP64) above. Zero for text waves.
% short dLock; % Reserved. Write zero. Ignore on read.
%
% char whpad1[6]; % Reserved. Write zero. Ignore on read.
% short whVersion; % Write 1. Ignore on read.
% char bname[MAX_WAVE_NAME5+1]; % Name of wave plus trailing null.
% long whpad2; % Reserved. Write zero. Ignore on read.
% struct DataFolder **dFolder; % Used in memory only. Write zero. Ignore on read.
%
% % Dimensioning info. [0] == rows, [1] == cols etc
% long nDim[MAXDIMS]; % Number of of items in a dimension -- 0 means no data.
% double sfA[MAXDIMS]; % Index value for element e of dimension d = sfA[d]*e + sfB[d].
% double sfB[MAXDIMS];
%
% % SI units
% char dataUnits[MAX_UNIT_CHARS+1]; % Natural data units go here - null if none.
% char dimUnits[MAXDIMS][MAX_UNIT_CHARS+1]; % Natural dimension units go here - null if none.
%
% short fsValid; % TRUE if full scale values have meaning.
% short whpad3; % Reserved. Write zero. Ignore on read.
% double topFullScale,botFullScale; % The max and max full scale value for wave.
%
% Handle dataEUnits; % Used in memory only. Write zero. Ignore on read.
% Handle dimEUnits[MAXDIMS]; % Used in memory only. Write zero. Ignore on read.
% Handle dimLabels[MAXDIMS]; % Used in memory only. Write zero. Ignore on read.
%
% Handle waveNoteH; % Used in memory only. Write zero. Ignore on read.
%
% unsigned char platform; % 0=unspecified, 1=Macintosh, 2=Windows; Added for Igor Pro 5.5.
% unsigned char spare[3];
%
% long whUnused[13]; % Reserved. Write zero. Ignore on read.
%
% long vRefNum, dirID; % Used in memory only. Write zero. Ignore on read.
%
% % The following stuff is considered private to Igor.
%
% short aModified; % Used in memory only. Write zero. Ignore on read.
% short wModified; % Used in memory only. Write zero. Ignore on read.
% short swModified; % Used in memory only. Write zero. Ignore on read.
%
% char useBits; % Used in memory only. Write zero. Ignore on read.
% char kindBits; % Reserved. Write zero. Ignore on read.
% void **formula; % Used in memory only. Write zero. Ignore on read.
% long depID; % Used in memory only. Write zero. Ignore on read.
%
% short whpad4; % Reserved. Write zero. Ignore on read.
% short srcFldr; % Used in memory only. Write zero. Ignore on read.
% Handle fileName; % Used in memory only. Write zero. Ignore on read.
%
% long **sIndices; % Used in memory only. Write zero. Ignore on read.
%
% float wData[1]; % The start of the array of data. Must be 64 bit aligned.
% };
function s=local_char(S);
% null-terminated string -> char string
S = S(:).';
S = S(S~=0);
s=char(S);
+130
View File
@@ -0,0 +1,130 @@
function output_table = read_hdf5_as_table(filename,exclude_var_set)
% receives a h5 file written with function table2h5.
% reduced_var_set (cell of character_vectors), not usable yet.
% Last update: 14.08.2023
% By: Juan F. Florez Ospina
% Email: juan.florez-ospina@psi.ch
if ~iscellstr(exclude_var_set)
disp("exclude_var_set needs to be a cell of character vectors, e.g, {'var1','var2'} to be excluded.")
disp("To proceed, exclude_var_set was set to {}. So vars will be excluded.")
exclude_var_set = {};
end
INFO = h5info(filename);
num_rows = length(INFO.Groups);
num_attr1 = length(INFO.Groups(1).Datasets);
num_attr2 = length(INFO.Groups(1).Attributes);
num_cols = num_attr1 + num_attr2;
sz = [num_rows,num_cols];
% Extract group level attributes, they align for all
% groups (here denoting measurements/or rows) of the table in filename .h5
group_columns = {INFO.Groups(1).Attributes.Name};
if num_attr1 == 1 && isempty(INFO.Groups(1).Datasets(num_attr1).Attributes)
dataset_columns = {};
num_attr1 = 0;
num_cols = num_cols - 1;
else
for j = 1 : num_attr1
dataset_columns{j} = INFO.Groups(1).Datasets(j).Attributes.Value;
end
end
%TODO: Implement feature to exclude varibales/columns as well as rows at
%reading time. This requires propagating modifications
% % % Select group and dataset columns to be loaded from hdf5 file, which are
% % % those not in the exclude_var_set.
% % if ~isempty(exclude_var_set)
% % for j = 1 : length(exclude_var_set)
% % % keep all vars except exclude_var_set{j}
% % var_filter = ~strcmp(exclude_var_set{j},group_columns);
% % group_columns = group_columns(var_filter);
% % if sum(var_filter) > 0
% % exclude_var_set(j) = [];
% % end
% % end
% %
% % for j = 1 : length(exclude_var_set)
% % % keep all vars except exclude_var_set{j}
% % var_filter = ~strcmp(exclude_var_set{j},dataset_columns);
% % dataset_columns = dataset_columns(var_filter);
% % if sum(var_filter) > 0
% % exclude_var_set(j) = [];
% % end
% % end
% % end
% Construct cell array with table data, where each row denotes a group, and
% the columns are either datasets or attributes in the given group.
tmp = cell(num_rows,num_cols); % {};
idx = [];
group_columns = {};
for i = 1 : num_rows
disp(['Begin construction of row:',INFO.Groups(i).Name])
for j = 1 : num_cols
if j <= num_attr1
ds_name = [INFO.Groups(i).Name '/' INFO.Groups(i).Datasets(j).Name];
%disp(ds_name)
tmp{i,j} = h5read(filename,ds_name);
if i < 2
column_idx = str2double(INFO.Groups(i).Datasets(j).Name(3:end));
idx = [idx,column_idx];
end
end
if j > num_attr1
Name = INFO.Groups(i).Attributes(j-num_attr1).Name;
Datatype = INFO.Groups(i).Attributes(j-num_attr1).Datatype.Class;
% Detect string attributes and convert them to characters.
% TODO: extend to other necessary data types.
if strcmp(Datatype,'H5T_STRING')
convert_func = @(x) convertStringsToChars(x);
else % no data type conversion is performed
convert_func = @(x) x;
end
tmp{i,j} = convert_func(h5readatt(filename,INFO.Groups(i).Name, Name));
if i < 2
disp(Name)
% Split attribute name into column_tag (containing original
% column idx) and column_name
delimiter_idx = find(INFO.Groups(i).Attributes(j-num_attr1).Name=='_');
% Left most '_' determines the split location
delimiter_idx = delimiter_idx(1);
column_idx = str2double(Name(5:delimiter_idx-1));
group_columns = [group_columns,{Name(delimiter_idx+1:end)}];
%disp([Name(5:delimiter_idx-1),'-',column_name])
idx = [idx,column_idx];
end
end
end
disp(['Ends construction of row:',INFO.Groups(i).Name])
end
% Find original ordering of columns by sorting idx in ascending order
[~, idx_perm] = sort(idx,'ascend');
varNames = [dataset_columns,group_columns];
% Create output_table from cell array using cell2table. This as opposed to
% array2table extract the content of its cells, which facilitates filtering
% operations based on table columns/vars.
%output_table = array2table(tmp(:,idx_perm), 'VariableNames', varNames(idx_perm));
output_table = cell2table(tmp(:,idx_perm), 'VariableNames', varNames(idx_perm));
+58
View File
@@ -19,6 +19,7 @@ Everyone is encouraged to use it, break it (gently), fix it, and extend it. 😄
- [Processing Workflow 🧪](#-processing-workflow)
- [Script Overview ⚙️](#-script-overview)
- [Typical Processing Chain 🔁](#-typical-processing-chain)
- [Track Data 🔦](#data-provenance-and-dependency-tracking)
- [Prerequisites 🧰](#prerequisites)
- [Installation 💻](#installation)
- [Get the code](#1-get-the-code)
@@ -202,6 +203,63 @@ Functions that combine or compare spectra (like `nappNorm` and `nappCopyShift`)
* `'after'`: Finds the closest reference spectrum taken *after* the target.
* `'spectrumNumber'`: Matches the 4-digit sequence number at the start of the filename (e.g., `0045_O1s` finds `0045_C1s`).
* `'internalSelf'` / `'internalMax'`: The spectrum references itself (e.g., normalizing to its own maximum, or calculating its own energy drift).
**Naming Rule:** Whenever practical, a script should save its primary output to a `.mat` file with the same base name as the script itself. This convention simplifies provenance tracking and dependency visualization.
### 🔦 Data Provenance and Dependency Tracking
Every result should be traceable back to the raw data and steps that made it — so future‑you (or your reviewer, or your supervisor at 11pm before a deadline) can actually reconstruct what happened. 🕵️‍♀️
nappMATLAB tracks this via dependency annotations added to your scripts. Tiny comments, big payoff: they record what a script eats, what it spits out, and what else it needs to run.
**What you need to do in your files:** 📝
Slap these near the top of each script:
```matlab
% @DEP_IN: Step1_AnalysisSelectData.mat
% @DEP_OUT: Step2_FitData.mat
% @DEP_RUN: HelperScript.m
```
where:
- `@DEP_IN` 📥 — the data/files this script needs to eat before it can do anything useful.
- `@DEP_OUT` 📤 — the files it produces (its gift to future‑you).
- `@DEP_RUN` 🏃 — other scripts it depends on / calls upon like a sidekick.
Keep these tags honest and up to date — an untagged script is basically a data ghost 👻, haunting your project with no explanation of where it came from. Once your scripts are tagged, three companion functions turn that breadcrumb trail into an actual graph:
#### `buildDependencyGraph.m` 🕸️
The big one. Scans a folder (and one level of subfolders) for `.m` files, hoovers up every `@DEP_IN`/`@DEP_OUT`/`@DEP_RUN` tag it can find, and builds the master dependency graph for your whole project. Run this first and keep its output `G` around — the other two functions feed off it like remoras. 🦈
- Pops open a MATLAB figure of the full graph, color‑coded by file type (scripts, `.mat` data, everything else).
- Saves a crisp `Dependency_MasterGraph.png` plus a `Dependency_MasterGraph.tgf` file for the free [yEd Graph Editor](https://www.yworks.com/products/yed), where you can drag nodes around like a detective's conspiracy board. 🧵📌
```matlab
G = buildDependencyGraph(); % scan current folder
G = buildDependencyGraph(scanPath); % scan a specific folder
```
#### `plotSingleFileDependencies.m` 🔍
Zooms in on one file's immediate social circle: who made it, who's using it — no six‑degrees‑of‑separation nonsense, just the direct neighbors. Perfect for "wait, who's touching this file?" energy. 🤨
```matlab
plotSingleFileDependencies(G, 'FitList.mat');
```
#### `plotNodeHistory.m` 🧬
The full ancestry.com experience for your data. Traces the **entire upstream chain** — every script and file that ever contributed, all the way back to raw data. Use it when someone asks "but where did THIS number actually come from" and you want a receipt. 🧾
```matlab
plotNodeHistory(G, 'Results/FinalPlot.fig');
```
All three save a matching high‑res PNG + `.tgf` (for yEd) next to the MATLAB figure, and highlight your target file in gold 🏆 — because it earned it.
For the fine print, ask MATLAB nicely:
```matlab
help buildDependencyGraph
help plotSingleFileDependencies
help plotNodeHistory
```
***
## Prerequisites
+97
View File
@@ -0,0 +1,97 @@
function tbl = rgaImportData(fullFileName)
%RGAIMPORTDATA Import an SRS RGA "Pressure vs Time" scan (.txt) into a table.
%
% tbl = rgaImportData(fullFileName) reads an RGA ASCII log and returns a
% table ready for plotRgaData, with:
% datetime - absolute time (start time + Time(s))
% time_s - elapsed scan time in seconds
% one column per active channel, named <Name>_m<mass> (e.g. Water_m18),
% holding the partial pressure in the file's units (usually Torr).
%
% The scan units and channel metadata are stored in
% tbl.Properties.VariableUnits and tbl.Properties.UserData.
%
% Two quirks of this file format are handled:
% * multi-word species names in the channel table (e.g. "Carbn dio")
% * adjacent negative values in the data run together with no space
% (e.g. "-2.8663e-010-2.3529e-010"); values are recovered by matching
% scientific-notation tokens rather than by splitting on whitespace.
%
% EXAMPLE
% tbl = rgaImportData('rga1_Jul_01_2026_08-23-06_AM.txt');
% ax = plotRgaData(tbl);
%
% See also PLOTRGADATA, PLOTCRYODATA
lines = readlines(fullFileName); % string array, keeps blanks
%--- Locate the data block header "Time(s) Channel#1 ..." ---
iData = find(startsWith(strip(lines),'Time(s)'), 1);
if isempty(iData)
error('rgaImportData:noData','Could not find the "Time(s)" data header.');
end
header = lines(1:iData-1); % metadata + channel table
%--- Start time (absolute) ---
iStart = find(startsWith(strip(header),'Start time,'), 1);
if isempty(iStart)
startStr = strip(header(1)); % fall back to first line
else
startStr = strip(extractAfter(header(iStart),','));
end
startStr = regexprep(startStr,'\s+',' '); % collapse the double space
startTime = datetime(startStr, ...
'InputFormat','MMM d, yyyy h:mm:ss a', 'Locale','en_US');
%--- Units ---
units = 'a.u.';
iu = find(startsWith(strip(header),'Units,'), 1);
if ~isempty(iu)
p = strip(split(header(iu),','));
units = char(p(end));
end
%--- Channel definitions (e.g. "1 28.00 Nitrogen 1.00 3 OFF") ---
chanPat = '^\s*(\d+)\s+([\d.]+)\s+(.+?)\s+[\d.]+\s+\d+\s+\w+\s*$';
tok = regexp(header, chanPat, 'tokens', 'once');
tok = tok(~cellfun(@isempty, tok));
nCh = numel(tok);
if nCh == 0
error('rgaImportData:noChannels','No channel definitions found.');
end
chMass = zeros(nCh,1); chName = strings(nCh,1);
for k = 1:nCh
chMass(k) = str2double(tok{k}{2});
chName(k) = strip(string(tok{k}{3}));
end
%--- Data: time before the comma + N scientific-notation values ---
dataLines = lines(iData+1:end);
dataLines = dataLines(strlength(strip(dataLines)) > 0);
n = numel(dataLines);
time_s = str2double(extractBefore(dataLines, ','));
matches = regexp(dataLines, '[-+]?\d+\.\d+[eE][-+]?\d+', 'match');
vals = nan(n, nCh);
for r = 1:n
v = str2double(matches{r});
nn = min(numel(v), nCh);
vals(r,1:nn) = v(1:nn);
end
%--- Assemble table ---
dt = startTime + seconds(time_s);
varNames = arrayfun(@(k) matlab.lang.makeValidName( ...
sprintf('%s_m%d', chName(k), round(chMass(k)))), 1:nCh, 'UniformOutput', false);
tbl = table(dt, time_s, 'VariableNames', {'datetime','time_s'});
for k = 1:nCh
tbl.(varNames{k}) = vals(:,k);
end
tbl.Properties.VariableUnits = [{'', 's'}, repmat({char(units)}, 1, nCh)];
tbl.Properties.UserData = struct( ...
'units', char(units), 'startTime', startTime, ...
'channelName', chName, 'channelMass', chMass);
end
+155
View File
@@ -0,0 +1,155 @@
function success = save_table_as_hdf5(input_table, filename)
% This function takes input_table (of table type), structured with
% Thorsten's FileTable format, and stores it as .h5 file with name
% filename. Each row is treated as a group, where columns can be attributes
% of the group or datasets inside the group.
% Last update: 11.08.2023
% By: Juan F. Florez Ospina
% Email: juan.florez-ospina@psi.ch
if istable(input_table)
string_columns = input_table(:,vartype("string")).Properties.VariableNames;
input_table = convertvars(input_table,string_columns,'cellstr');
% split multicolumn variables
input_table = splitvars(input_table);
column_names = input_table.Properties.VariableNames;
else
success = 0;
return
end
% Create array with indices of column_names
column_names_idx = 1 : length(column_names);
% Divide input_table into tables with numeric and cellstr variables/columns
numeric_table = input_table(:,vartype('numeric')); % group attributes of a timestamped measurement
cellstr_table = input_table(:,vartype('cellstr')); % group datasets of a timestamped measurement
% Record columns/vars original index in reference table
numeric_columns = numeric_table.Properties.VariableNames;
numeric_columns_idx = argwhere_var_in_varset(numeric_columns,column_names);
str_columns = cellstr_table.Properties.VariableNames;
str_columns_idx = argwhere_var_in_varset(str_columns,column_names);
% Based on homogeneous columns/cell arrays, we identify the remaining columns
% with possibly heterogeneous entries. Ideally, these should store numeric
% n-dimensional arrays/datasets.
rest_columns = {};
cntr = 1;
for i = 1 : length(input_table.Properties.VariableNames)
variable = input_table.Properties.VariableNames{i};
is_str = sum(strcmp(variable,str_columns))>0;
is_num = sum(strcmp(variable,numeric_columns))>0;
if ~(is_num || is_str)
rest_columns{cntr} = variable;
disp(variable)
cntr = cntr + 1;
end
end
rest_columns_idx = argwhere_var_in_varset(rest_columns,column_names);
% Create an hdf5 file, where rows are seen as measurements/groups with
% attributes given by numeric and str variables and datasets given by the
% remaining variables as long as they are numeric n-dimensional arrays.
cell_table = input_table(:,rest_columns);
[rows, cols] = size(input_table);
for i = 1 : rows
group_name = strcat("/MEAS_",string(i),"/");
if ~isempty(rest_columns)
for j = 1 : length(rest_columns)
dataset_name = strcat("DS",string(rest_columns_idx(j)));
% Some table entries do not provide direct access to the object
% of interest, but may contain either {object} or {{object}}.
% The following loop solves that problem. TODO: is this generic enough?
data = cell_table{i,column_names{rest_columns_idx(j)}}{1}; % <=> data = cell_table{i,rest_columns{j}}{1};
while iscell(data)
data = data{1};
end
% when there is no data, we still need to provide a valid size
if ~isempty(data)
sz = size(data);
else
sz = [1,1];
end
%%%%
% Group's dataset: Create dataset of size sz in a certain group
h5create(filename, strcat(group_name,dataset_name),sz);
% Dataset's attribute: Add attribute with dataset_name equal to column_name
attr = "column_name";
attr_value = column_names{rest_columns_idx(j)}; % <=> rest_columns{j};
h5writeatt(filename,strcat(group_name,dataset_name),attr,attr_value)
%attr = "column_name_idx";
%attr_value = rest_columns_idx(j); % <=> rest_columns{j};
%h5writeatt(filename,strcat(group_name,dataset_name),attr,attr_value)
% Assign 'data' to the dataset. 'data' has to be of admissible type.
try
h5write(filename,strcat(group_name,dataset_name),data)
catch exception
disp(exception)
%h5write(filename,strcat(group_name,dataset_name),cell_table{i,rest_columns{j}})
end
end
else
dataset_name = "DS_EMPTY";
sz = [1,1];
h5create(filename, strcat(group_name,dataset_name),sz);
end
% Group's attribute: Assign group attributes
for attr_idx = 1 : length(numeric_columns)
attr = column_names{numeric_columns_idx(attr_idx)};% <=> attr = numeric_columns{attr_idx};
attr_value = numeric_table{i,attr};
column_idx_tag = ['ATTR',num2str(numeric_columns_idx(attr_idx)),'_'];
h5writeatt(filename,group_name,[column_idx_tag,attr],attr_value)
%attr = "column_name_idx";
%attr_value = numeric_columns_idx(attr_idx); % <=> rest_columns{j};
%h5writeatt(filename,group_name,attr,attr_value)
end
for attr_idx = 1 : length(str_columns)
attr = column_names{str_columns_idx(attr_idx)}; % <=> attr = str_columns{attr_idx}
attr_value = cellstr_table{i,attr};
column_idx_tag = ['ATTR',num2str(str_columns_idx(attr_idx)),'_'];
h5writeatt(filename,group_name,[column_idx_tag,attr],attr_value)
%attr = "column_name_idx";
%attr_value = str_columns_idx(attr_idx); % <=> rest_columns{j};
%h5writeatt(filename,group_name,attr,attr_value)
end
%h5create(filename,"/g2/DS2",[20 Inf],"Chunksize",[5 5]);
end
success = 1;
function idx = argwhere_var_in_varset(var,varset)
ref_idx = 1 : length(varset);
if iscellstr(var)
idx = [];
for i = 1 : length(var)
logical_idx = strcmp(var{i},varset);
idx = [idx,ref_idx(logical_idx)];
end
else
logical_idx = strcmp(var,varset);
idx = ref_idx(logical_idx);
end