Files
2026-08-07 15:56:42 +09:00

526 lines
19 KiB
Matlab
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
% function [varargout] = spec_read(specDatFile, varargin)
% call 'spec_read()' for further help
%
% please reports bugs, problems, suggestions for improvements to:
% CXS group
%
% the case of duplicate scannumbers in the spec file, and how to
% address them remains to be implemented
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  Except where otherwise noted, this work is licensed under a          |
%|  Creative Commons Attribution-NonCommercial-ShareAlike 4.0            |
%|  International (CC BY-NC-SA 4.0) license.                             |
%|                                                                       |
%|  Copyright (c) 2017 by Paul Scherrer Institute (http://www.psi.ch)    |
%|                                                                       |
%|      Author: CXS group, PSI  |
%*-----------------------------------------------------------------------*
% You may use this code with the following provisions:
%
% If the code is fully or partially redistributed, or rewritten in another
% computing language this notice should be included in the redistribution.
%
% If this code, or subfunctions or parts of it, is used for research in a
% publication or if it is fully or partially rewritten for another
% computing language the authors and institution should be acknowledged
% in written form in the publication: “Data processing was carried out
% using the “cSAXS matlab package” developed by the CXS group,
% Paul Scherrer Institut, Switzerland.”
% Variations on the latter text can be incorporated upon discussion with
% the CXS group if needed to more specifically reflect the use of the package
% for the published work.
%
% A publication that focuses on describing features, or parameters, that
% are already existing in the code should be first discussed with the
% authors.
%
% This code and subroutines are part of a continuous development, they
% are provided “as they are” without guarantees or liability on part
% of PSI or the authors. It is the user responsibility to ensure its
% proper use and the correctness of the results.
function [varargout] = spec_read(specDatFile, varargin)
import io.*
import utils.find_files
% check minimum number of input arguments
if(~isunix)
error(['The function ''spec_read'' relies on various system calls,\n' ...
' currently available only on UNIX-like systems\n']);
end
if (nargin<1)
spec_help(mfilename);
error('At least the spec data filename has to be specified as input parameter.')
end
% accept cell array with name/value pairs as well
no_of_in_arg = nargin;
if (nargin == 2)
if (isempty(varargin))
% ignore empty cell array
no_of_in_arg = no_of_in_arg -1;
else
if (iscell(varargin{1}))
% use a filled one given as first and only variable parameter
varargin = varargin{1};
no_of_in_arg = 1 + length(varargin);
end
end
end
% check number of input arguments
if (rem(no_of_in_arg,2) ~= 1)
error('The optional parameters have to be specified as ''name'',''value'' pairs');
end
% here the default parameters
scannr = -1;
output_arg = {'meta','motor','counter'};
unhandled_par_error = 1;
force_cell = 0;
% other parameters
burstn0 = 1;
mexpn0 = 1;
creader = 1;
orchestraPath = [];
specReadOptions = [];
% parse the variable input arguments
vararg = cell(0,0);
for jj = 1:2:length(varargin)
name = varargin{jj};
value = varargin{jj+1};
switch name
case 'ScanNr'
if (~all(value==round(value)) || any(value==0))
error('The value of ''%s'' needs to be a (vector of) integer(s) not equal to zero', name)
end
scannr = value;
case 'Burst'
if (~all(value==round(value)) || any(value<=0))
error('The value of ''%s'' needs to be a positive integer', name)
end
burstn0 = max(1,value);
mexpn0 = 1;
case 'MultExp'
if (~all(value==round(value)) || any(value<=0))
error('The value of ''%s'' needs to be a positive integer', name)
end
burstn0 = 1;
mexpn0 = max(1,value);
case 'OutPut'
output_arg = set_TextFlag(output_arg,value);
case 'PilatusMask'
pilatusMask = value;
output_arg = set_TextFlag(output_arg,'+pilatus');
case 'PilatusDir'
pilatusDir0 = value;
output_arg = set_TextFlag(output_arg,'+pilatus');
case 'Cell'
force_cell = value;
case 'CReader'
creader = value;
case 'Orchestra'
orchestraPath = value;
case 'UnhandledParError'
unhandled_par_error = value;
otherwise
vararg{end+1} = name; %#ok<AGROW>
vararg{end+1} = value; %#ok<AGROW>
end
end
specDatFile = beamline.find_specDatFile(specDatFile);
if creader
try
if ~isempty(orchestraPath)
specReadOptions = [specReadOptions ' --orchestra ' orchestraPath];
end
sysCall = [ fullfile(fileparts(mfilename('fullpath')), './spec_reader/spec_reader') ' -s ' specDatFile ' --scanNr ' strjoin(arrayfun(@(x) num2str(x),scannr,'UniformOutput',false),',') specReadOptions];
[stat, out] = system(sysCall);
if stat
error(['Fast spec reader failed: ' strtrim(out)])
end
res = jsondecode([ out ]);
if length(res)==1
varargout{1} = res;
else
varargout{1} = num2cell(res)';
end
catch ME
if creader
warning('Fast spec reader failed.')
disp(ME)
creader = 0;
end
end
end
if ~creader
% reading the spec file for the scans
if ~exist(specDatFile, 'file')
error('Spec file was not found in %s', specDatFile)
end
[scans,scanline] = spec_readscan(specDatFile, scannr);
% reading the spec file for configuration information
[motorc,motorline] = spec_readconfig(specDatFile);
motorn = cell(size(scannr));
for jj=1:numel(scans)
motorn{jj} = motorc{find(motorline<scanline(1),1,'last')};
end
varargout{1} = cell(size(scans));
% copying vararg in order to keep its content for each iteration
vararg_1 = vararg;
vararg_1{end+1} = 'UnhandledParError';
vararg_1{end+1} = 0;
for jj=1:numel(scans)
scanstr = scans{jj}';
arrout = regexp(scanstr{1},' +','split');
scannr(jj) = str2double(arrout{2});
% get the motors
line = ~cellfun(@isempty,regexp(scanstr,'^#P'));
arrout = reshape(transpose(strvcat((scanstr(line)))),1,[]);
arrout = regexp(arrout,' +|(#P[0-9]+)','split');
motorv = str2double(arrout(~cellfun(@isempty,arrout)));
% get the counters
line = ~cellfun(@isempty,regexp(scanstr,'^#L'));
counters = regexp(scanstr{line},' +','split');
counters = counters(2:end);
% get the data
line = ~cellfun(@isempty,regexp(scanstr,'^[0-9+-]'));
data = cellfun(@str2num,scanstr(line),'UniformOutput',false);
data = cell2mat(data(:));
% get Pilatus information if required
if (exist('pilatusMask','var'))
if (exist('pilatusDir0','var'))
pilatusDir = find_pilatusDir(pilatusDir0,scannr(jj));
else
pilatusDir = '';
end
[pilatusDir, pilatusName, vararg] = ...
find_files(strcat(pilatusDir,pilatusMask), vararg_1);
pilatusInd = zeros(numel(pilatusName),2);
for ii=1:numel(pilatusName)
arrout = regexp(pilatusName(ii).name,'[_\.]','split');
pilatusInd(ii,:) = str2double(arrout(end-2:end-1));
end
end
% make a structure
scan_structure = struct;
if (any(strcmp(output_arg,'motor')) || ...
any(strcmp(output_arg,'motors')))
for ii=1:numel(motorn{jj})
scan_structure.(motorn{jj}{ii}) = motorv(ii);
end
end
if (any(strcmp(output_arg,'meta')))
scan_structure.S = scanstr{1};
% get the time stamp and convert it to a MATLAB-compatible date string
line = ~cellfun(@isempty,regexp(scanstr,'^#D'));
datestr = regexp(scanstr{line},' +','split');
scan_structure.D = sprintf('%s-%s-%s %s', ...
datestr{4}, ...
datestr{3}, ...
datestr{6}, ...
datestr{5});
line = ~cellfun(@isempty,regexp(scanstr,'^#C meta'));
meta = scanstr(line);
for ii=1:length(meta)
metaEntry = strsplit(strtrim(meta{ii}), ' ');
switch metaEntry{4}
case {'int', 'float'}
converterFunc = @str2double;
case 'string'
converterFunc = @(x) x;
end
tmp = [];
for jjj=1:str2double(metaEntry{5})
tmp = [tmp converterFunc(metaEntry{5+jjj})];
end
scan_structure.(metaEntry{3}) = tmp;
end
end
if (any(strcmp(output_arg,'counter')) || ...
any(strcmp(output_arg,'counters')))
if (numel(data)>0)
for ii=1:numel(counters)
scan_structure.(counters{ii}) = data(:,ii);
end
end
end
if (any(strcmp(output_arg,'pilatus')))
if (burstn0>1)
burstn = burstn0;
elseif (isfield(scan_structure,'burstn'))
burstn = max(ones(size(scan_structure.burstn)),scan_structure.burstn);
else
burstn = burstn0;
end
if (mexpn0>1)
mexpn = mexpn0;
elseif (isfield(scan_structure,'mexpn'))
mexpn = max(ones(size(scan_structure.burstn)),scan_structure.mexpn);
else
mexpn = mexpn0;
end
if (numel(burstn)==1 && numel(mexpn)==1)
% these pseudo motors shouldn't be scanned and are
% therefore presumably constant
Pilatus = cell(size(data,1),1);
scanInd = [transpose(floor((0:size(data,1)-1)/mexpn)), ...
transpose(mod(0:size(data,1)-1,mexpn))];
for ii=1:size(data,1)
if (burstn<=1)
indices = all(ones(size(pilatusName))'*scanInd(ii,:)==pilatusInd,2);
else
indices = all(ones(size(pilatusName))'*scanInd(ii,1)==pilatusInd(:,1),2);
end
Pilatus{ii} = {pilatusName(indices).name};
if (numel(indices)<burstn)
% accounting for missing frames
% untested, as of Dec 16
tmp = Pilatus{ii};
Pilatus{ii} = cell(1,burstn);
for kk=1:numel(tmp)
Pilatus{ii}{pilatusInd(2,kk)} = tmp{kk};
end
end
end
end
scan_structure.Pilatus = Pilatus;
scan_structure.PilatusDir = pilatusDir;
end
% append orchestra data
if ~isempty(orchestraPath)
[config, data] = readOrchestraFile(orchestraPath, scannr(jj));
for oidx=1:length(config)
scan_structure.(config{oidx}) = data{oidx};
end
end
varargout{1}{jj} = scan_structure;
end
if ((unhandled_par_error) && (~isempty(vararg)))
vararg %#ok<NOPRT>
error('Not all named parameters have been handled.');
end
if (numel(varargout{1})==1 && ~force_cell)
varargout{1} = varargout{1}{1};
end
varargout{2} = vararg;
return
end
end
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [varargout] = spec_readscan(specDatFile, scannr)
import io.*
import utils.*
% check minimum number of input arguments
if (nargin<2)
spec_read_help(mfilename);
error('usage ''spec_readscan(specDatFile, scannr)''');
end
cmd = sprintf('grep -n ''#S '' %s', specDatFile);
[~,sysout] = system(cmd);
arrout = regexp(sysout,'[:\n]','split');
arrout = arrout(~cellfun(@isempty,arrout));
arrout = transpose(reshape(arrout,2,[]));
[s1,~] = size(arrout);
if (any(scannr>0))
tmp = regexp(arrout(:,2),' +','split');
scanID = zeros(size(tmp));
for ii=1:numel(tmp)
scanID(ii) = str2double(tmp{ii}{2});
end
end
% creating a look up table where the scans begin and end
lineI = zeros(size(scannr));
scans = cell(size(scannr));
for ii=1:numel(scannr)
if (scannr(ii)<0)
if (-scannr(ii)>size(arrout,1))
error('There does not seem to be sufficiently many scans in ''%s''', ...
specDatFile);
end
tmp = regexp(arrout(end+scannr(ii)+1,:),' +','split');
lineI(ii) = str2double(tmp{1});
if (scannr(ii) < -1)
tmp = regexp(arrout(end+scannr(ii)+2,:),' +','split');
lineF = str2double(tmp{1})-1;
else
cmd = sprintf('wc -l %s', specDatFile);
[~,sysout] = system(cmd);
lineF = str2double(regexp(sysout,'[0-9]+ ','match'));
end
scannr(ii) = str2double(tmp{2}(2));
else
tmp = find((scannr(ii)==scanID),1,'last');
try
verbose(2,'reading ''%s''',arrout{tmp,2});
catch
fprintf('reading ''%s''\n',arrout{tmp,2});
end
if (isempty(tmp))
fprintf('Do not find scan #S %d in ''%s''.\n', ...
scannr(ii),specDatFile);
continue
end
lineI(ii) = str2double(arrout(tmp,1));
if (tmp<s1)
lineF = str2double(arrout(tmp+1,1))-1;
else
cmd = sprintf('wc -l %s', specDatFile);
[~,sysout] = system(cmd);
lineF = str2double(regexp(sysout,'[0-9]+ ','match'));
end
end
cmd = sprintf('head -n +%d %s | tail -n -%d', ...
lineF, specDatFile, lineF+1-lineI(ii));
% read the scan
[~,sysout] = system(cmd);
scans{ii} = regexp(sysout,'\n','split');
scans{ii} = scans{ii}(~cellfun(@isempty,scans{ii}));
end
varargout{1} = scans;
varargout{2} = lineI;
end
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [motors,linenr] = spec_readconfig(specDatFile)
import io.*
import utils.find_files
% finding the end of the last config
cmd = sprintf('grep -nE ''^#O[0-9]+ '' %s', ...
specDatFile);
[~,sysout] = system(cmd);
arrout = regexp(sysout,'[:\n]','split');
arrout = arrout(~cellfun(@isempty,arrout));
arrout = transpose(reshape(arrout,2,[]));
[s1,~] = size(arrout);
% devide this collection of configuration information into single chuncks
linenr = '';
motors = cell(0);
for ii=1:s1
if (~isempty(regexp(arrout{ii,2},'^#O0+','match')))
if (isempty(linenr))
linenr = str2double(arrout{ii,1});
else
linenr = vertcat(linenr, ...
str2double(arrout{ii,1}));
end
motors{numel(linenr)} = regexp(arrout{ii,2},'(^#O[0-9]+ *)| *','split');
else
motors{numel(linenr)} = horzcat(motors{numel(linenr)}, ...
regexp(arrout{ii,2},'(^#O[0-9]+ *)| *','split'));
end
end
for ii=1:numel(linenr)
motors{ii} = motors{ii}(~cellfun(@isempty,motors{ii}));
end
end
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function pilatusDir = find_pilatusDir(pilatusDir,scannr)
import io.*
import utils.find_files
while (any(exist(pilatusDir,'dir') == [2,7]))
% if the variable pilatusDir is not a complete path to a file
% try to guess where a spec data file can be found, by
% - look for directories called 'spec' or 'dat-files'
% - look for files called '*.dat'
% - take the newest one
compare_str = pilatusDir;
fname = dir(pilatusDir);
if (exist(pilatusDir,'dir'))
if (pilatusDir(end) ~= '/')
pilatusDir = strcat(pilatusDir,'/');
end
for ii=1:numel(fname)
if (strcmp(fname(ii).name,sprintf('S%02d000-%02d999',floor(scannr/1e3),floor(scannr/1e3))) || ...
strcmp(fname(ii).name,sprintf('S%05d',scannr)))
pilatusDir = strcat(pilatusDir,fname(ii).name);
fname = [];
break;
end
end
for ii=1:numel(fname)
if (strcmp(fname(ii).name,'pilatus'))
pilatusDir = strcat(pilatusDir,fname(ii).name);
break;
end
end
end
if (strcmp(pilatusDir,compare_str))
break
end
end
end
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [flags] = set_TextFlag(flags,value)
import io.*
import utils.find_files
if (~iscell(value))
value = {value};
end
for ii=1:numel(value)
value_i = value{ii};
if (regexp(value_i,'^[a-zA-Z]+$'))
flags = {value_i};
if (ii+1<=numel(value))
value{ii+1} = regexprep(value{ii+1},'^([a-zA-Z]*)','+$1');
end
elseif (regexp(value_i,'^\+[a-zA-Z]+$'))
flags = unique([flags,value_i(2:end)]);
if (ii+1<=numel(value))
value{ii+1} = regexprep(value{ii+1},'^([a-zA-Z]*)','+$1');
end
elseif (regexp(value_i,'^-[a-zA-Z]+$'))
flags = flags(~strcmp(flags,value_i(2:end)));
if (ii+1<=numel(value))
value{ii+1} = regexprep(value{ii+1},'^([a-zA-Z]*)','-$1');
end
end
end
end
function [config, data] = readOrchestraFile(orchestraPath, scanNr)
config = [];
data = [];
fname = fullfile(orchestraPath, sprintf('scan_%05d.dat', scanNr));
if ~exist(fname, 'file')
warning('Could not find any orchestra file for scan %d', scanNr);
return;
end
fid = fopen(fname);
fgetl(fid);
config = strsplit(fgetl(fid), ' ');
data=textscan(fid,repmat('%f ', 1, length(config)));
end