% Call function without arguments for instructions on how to use it % Filename: $RCSfile: image_read.m,v $ % % $Revision: 1.17 $ $Date: 2013/01/25 10:23:23 $ % $Author: $ % $Tag: $ % % Description: % Macro for reading image data formats used at the SLS / cSAXS beamline. % The data are returned in double precision floating point format. % % Note: % Call without arguments for a brief help text. % % Dependencies: % - edfread % - cbfread % - hdf5read % - fliread % - speread % - char_to_cellstr % % history: % % September 30th 2010: % add a call to hdf5read_main % % June 5th 2009: % disable UhandledParError before calling sub-macros % % January 16th 2009: % adapt to image_orient returning the complete structure rather than just % the data array % % November 19th 2008: % add reading of Matlab files % % September 5th 2008: % skip further processing for a frame if it was not possible to read it % % September 4th 2008: % add the rowcol-from field to the frames structure as origin information % % June 19th 2008: adapt call to image_orient % % May 16th 2008: send variable arguments through find files % % May 9th 2008: 1st version %*-----------------------------------------------------------------------* %|                                                                       | %|  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 % and Computing Department, 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 [frames,vararg_remain] = image_read(filenames,varargin) import io.* import io.HDF.* import io.CBF.* import plotting.* import utils.char_to_cellstr import utils.default_parameter_value import utils.find_files import utils.fopen_until_exists % initialize return arguments frames = struct('data',[], ... 'img_full_size',[], 'rowcol_from',[], ... 'no_of_el_read', [], ... 'header',[], 'filename',[], 'extension', []); % check minimum number of input arguments if (nargin < 1) image_read_help('ext',mfilename); error('At least the 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 % convert the filename to a cell array to use the same loop for single and % multiple file names if (~iscell(filenames)) filenames = { filenames }; end % hdf5 files are read via a separate sub-routine % if length(filenames) == 1 % accept only one file name filename = filenames{1}; [~, ~, ext] = fileparts(filename); if any(strcmp(ext, {'.h5', '.hdf5', '.nxs', '.cxs'})) if length(filenames) == 1 frames = hdf5read(filename, varargin); else frames = hdf5read(filenames, varargin); end vararg_remain = []; return % image_read ends here for hdf5 image files end % end % set default values for the variable input arguments: % default data type for the returned frames data_type = default_parameter_value(mfilename,'DataType'); % recognize file type by file name extension force_file_type = default_parameter_value(mfilename,'ForceFileType'); % from/to row 0 means all rows row_from = default_parameter_value(mfilename,'RowFrom'); row_to = default_parameter_value(mfilename,'RowTo'); % from/to column 0 means all lines column_from = default_parameter_value(mfilename,'ColumnFrom'); column_to = default_parameter_value(mfilename,'ColumnTo'); % determine default orientation based on the file name extension orient_by_extension = default_parameter_value(mfilename,'OrientByExtension'); % filename is actually a mask that may include wildcards filename_is_fmask = default_parameter_value(mfilename,'IsFmask'); % display file name of the file to be loaded display_filename = default_parameter_value(mfilename,'DisplayFilename'); % variable to load from Matlab files matlab_var = default_parameter_value(mfilename,'MatlabVar'); % exit with an error message if unhandled named parameters are left at the % end of this macro unhandled_par_error = 1; % parse the variable input arguments vararg = cell(0,0); for ind = 1:2:length(varargin) name = varargin{ind}; value = varargin{ind+1}; switch name case 'DataType' if (~ischar(value)) error('The DataType must be string defining a valid Matlab data type.'); end data_type = value; case 'ForceFileType' force_file_type = lower(value); case 'MatlabVar' matlab_var = value; case 'RowFrom' row_from = value; case 'ROI' if (length(value) ~= 4) error('The ROI parameter needs a vector of length four as argument.'); end column_from = value(1); row_from = value(2); column_to = value(3); row_to = value(4); case 'RowTo' row_to = value; case 'ColumnFrom' column_from = value; case 'ColumnTo' column_to = value; case 'OrientByExtension' orient_by_extension = value; case 'UnhandledParError' unhandled_par_error = value; case 'IsFmask' filename_is_fmask = value; case 'DisplayFilename' display_filename = value; otherwise vararg{end+1} = name; %#ok vararg{end+1} = value; %#ok end end % initialize the list of unhandled parameters vararg_remain = cell(0,0); % loop over all specified file names file_ind_max = length(filenames); store_ind = 1; for (file_ind=1:file_ind_max) filename = filenames{file_ind}; vararg_remain = vararg; % in case of file name mask get a list of all matching file names data_dir = ''; if (filename_is_fmask) % sub macros must not complain about unknown arguments vararg_remain{end+1} = 'UnhandledParError'; vararg_remain{end+1} = 0; [data_dir, fnames, vararg_remain] = ... find_files( filename, vararg_remain ); else fnames = struct('name',filename); end for (sub_file_ind = 1:length(fnames)) % pick out the current filename filename = [ data_dir fnames(sub_file_ind).name ]; % check for minimum filename length if (length(filename) < 5) error([ mfilename ': invalid filename ' filename ]); end if (isempty(force_file_type)) % get the extension from the last three to four characters extension = lower(filename((end-4):end)); pos = strfind(extension,'.'); if (length(pos) < 1) error([ mfilename ': invalid extension in ' filename ]); end extension = extension(pos(end)+1:end); else % the file name extension is ignored since the file type is % forced to a specific one extension = force_file_type; end if (display_filename) fprintf('loading %s\n',filename); end if ((strcmp(extension,'dat')) || ... (strcmp(extension,'tif')) || (strcmp(extension,'tiff')) || ... (strcmp(extension,'mat'))) % open the file to support functionality like % wait-until-exists [fid,vararg_remain] = fopen_until_exists(filename,vararg); if (fid >= 0) fclose(fid); end end % interprete file in the format indicated by the filename extension switch extension case 'cbf' [frame,vararg_remain] = cbfread(filename,vararg_remain); case {'hdf5', 'h5', 'nxs', 'cxs'} [frame,vararg_remain] = hdf5read_main(filename,vararg_remain); case 'dat' [frame,vararg_remain] = datread(filename,vararg_remain); case 'edf' [frame,vararg_remain] = edfread(filename,vararg_remain); case {'mar', 'mccd'} [frame,vararg_remain] = marread(filename,vararg_remain); case 'mat' tmp_data = load(filename); frame.data = tmp_data.(matlab_var); frame.header = {}; % No header information are available. % Fake exposure time information to avoid problems in other % macros. frame.header{end+1} = 'Exposure_time 1.0'; % add the file modification date to the header dir_entry = dir(filename); frame.header{end+1} = [ 'DateTime ' dir_entry.date ]; case 'raw' [frame,vararg_remain] = fliread(filename,vararg_remain); case 'spe' [frame,vararg_remain] = speread(filename,vararg_remain); case {'tif', 'tiff'} % reading higher bit depths than 16bit needs a sufficiently % up-to-date Matlab version frame.data = imread(filename,'tif'); hdr = imfinfo(filename); frame.header = {}; if (isfield(hdr,'ImageDescription')) frame.header = char_to_cellstr(hdr.ImageDescription); end if ((isfield(hdr,'Model')) && ... (strcmp(hdr.Model(1:7),'PILATUS'))) frame.header{end+1} = [ 'DateTime ' hdr.DateTime ]; frame.header{end+1} = [ 'Software ' hdr.Software ]; frame.header{end+1} = [ 'Model ' hdr.Model ]; else % the exposure time is not available if (isfield(hdr,'exptimesec')) frame.header{end+1} = [ 'Exposure_time' hdr.exptimesec ]; else frame.header{end+1} = 'Exposure_time 1.0'; end if (isfield(hdr,'DateTime')) frame.header{end+1} = [ 'DateTime ' hdr.DateTime ]; else if (isfield(hdr,'FileModDate')) frame.header{end+1} = [ 'DateTime ' hdr.FileModDate ]; else % add the file modification date to the header dir_entry = dir(filename); frame.header{end+1} = ... [ 'DateTime ' dir_entry.date ]; end end end otherwise error([ 'unknown extension of ' filename ]); end % the remaining code is not needed if no file was read if (isempty(frame.data)) continue; end % determine the orientation from the filename extension vararg_remain_prev = vararg_remain; vararg_remain = cell(1,length(vararg_remain)+2); vararg_remain(3:end) = vararg_remain_prev; vararg_remain{1} = 'OrientByExtension'; vararg_remain{2} = orient_by_extension; % set the extension since image_orient is called prior % to defining the return variables frames frame.extension = cell(1,1); frame.extension{1} = extension; % orient image [frame,vararg_remain] = image_orient(frame,vararg_remain); % cut out region of interest full_size = size(frame.data); if ((row_from > 0) || (row_to > 0) ||... (column_from > 0) || (column_to > 0)) if (row_from <= 0) row_from = 1; end if (row_from > size(frame.data,1)) error('The RowFrom specification is beyond the maximum value of %d',... size(frame.data,1)); end if (row_to <= row_from) row_to = full_size(1); end if (row_to > full_size(1)) error('The RowTo specification is beyond the maximum value of %d',... full_size(1)); end if (column_from <= 0) column_from = 1; end if (column_from > full_size(2)) error('The ColumnFrom specification is beyond the maximum value of %d',... full_size(2)); end if (column_to <= column_from) column_to = full_size(2); end if (column_to > size(frame.data,2)) error('The ColumnTo specification is beyond the maximum value of %d',... full_size(2)); end frame.data = frame.data(row_from:row_to,column_from:column_to,:); end % initialize the return array with the now known dimensions if (store_ind == 1) % in case of file name masks or multiple images in one data % file the final array dimensions can only be estimated init_guess = file_ind_max -1 + length(fnames); frames.data = zeros( [ size(frame.data) init_guess ], data_type ); frames.rowcol_from = cell(1,init_guess); frames.no_of_el_read = cell(1,init_guess); frames.img_full_size = cell(1,init_guess); frames.filename = cell(1,init_guess); frames.extension = cell(1,init_guess); frames.header = cell(1,init_guess); end % store the frame(s) if ((size(frame.data,1) ~= size(frames.data,1)) || ... (size(frame.data,2) ~= size(frames.data,2))) error('Expected frame dimension is %d x %d, frame read has %d x %d',... size(frames.data,2),size(frames.data,1),... size(frame.data,2),size(frame.data,1)); end % in some cases multiple frames are stored in a single file if (ndims(frame.data) == 4) store_ind_to = (store_ind+size(frame.data,4)-1); frames.data(:,:,:,store_ind:store_ind_to) = cast(frame.data,data_type); else store_ind_to = (store_ind+size(frame.data,3)-1); frames.data(:,:,store_ind:store_ind_to) = cast(frame.data,data_type); end % store the filename, extension and the header in the return argument for (ind=store_ind:store_ind_to) frames.img_full_size{ind} = full_size; frames.rowcol_from{ind} = [ row_from column_from ]; if (isfield(frame,'no_of_el_read')) frames.no_of_el_read{ind} = frame.no_of_el_read; else frames.no_of_el_read{ind} = size(frame.data,3); end % zero means no ROI, i.e., starting at point (1,1) if (frames.rowcol_from{ind}(1) < 1) frames.rowcol_from{ind}(1) = 1; end if (frames.rowcol_from{ind}(2) < 1) frames.rowcol_from{ind}(2) = 1; end frames.filename{ind} = filename; frames.extension{ind} = extension; frames.header{ind} = frame.header; end % update index to free space in the output arrays store_ind = store_ind_to + 1; % exit in case of unhandled named parameters, if this has not been switched % off if ((unhandled_par_error) && (~isempty(vararg_remain))) vararg_remain error('Not all named parameters have been handled.'); end % restore parameters for next iteration vararg_remain = vararg_remain_prev; end end % resize the output arrays in case the initial size is too large store_ind = store_ind -1; if (size(frames.data,3) > store_ind) frames.data = frames.data(:,:,1:store_ind); frames.img_full_size = frames.img_full_size(1:store_ind); frames.rowcol_from = frames.rowcol_from(1:store_ind); frames.filename = frames.filename(1:store_ind); frames.extension = frames.extension(1:store_ind); frames.header = frames.header(1:store_ind); end function frames = hdf5read(filename, params) import utils.fopen_until_exists import io.image_default_orientation import io.HDF.hdf5_load import io.image_orient import utils.find_files frames = struct('data', [], 'img_full_size', [], 'rowcol_from', [], ... 'no_of_el_read', [], 'header',[], 'filename',[], 'extension', []); p = inputParser; p.KeepUnmatched = true; p.FunctionName = 'image_read'; addParameter(p, 'H5Location', '/'); addParameter(p, 'ReadAttr', false); addParameter(p, 'FrameRange', [1, Inf], @(x) isvector(x) && numel(x) <= 2 && isnumeric(x)); addParameter(p, 'RowFrom', 1, @(x) isscalar(x) && isnumeric(x)); addParameter(p, 'RowTo', Inf, @(x) isscalar(x) && isnumeric(x)); addParameter(p, 'ColumnFrom', 1, @(x) isscalar(x) && isnumeric(x)); addParameter(p, 'ColumnTo', Inf, @(x) isscalar(x) && isnumeric(x)); addParameter(p, 'OrientByExtension', 1, @(x) isscalar(x) && (isnumeric(x) || islogical(x))); addParameter(p, 'Orientation', [0, 0, 0], @(x) isvector(x) && numel(x) == 3 && isnumeric(x)); addParameter(p, 'InvertOrientation', 0, @(x) isscalar(x) && isnumeric(x)); addParameter(p, 'Transpose', 0, @(x) isscalar(x) && isnumeric(x)); addParameter(p, 'FlipLR', 0, @(x) isscalar(x) && isnumeric(x)); addParameter(p, 'FlipUD', 0, @(x) isscalar(x) && isnumeric(x)); addParameter(p, 'CatDim', -1, @(x) isscalar(x) && isnumeric(x)); addParameter(p, 'DisplayFilename', 1, @(x) isscalar(x) && isnumeric(x)); % No support for filename wildcards, i.e. ('IsMask', 1) addParameter(p, 'IsFmask', true, ... @islogical);%@(x) assert(~x, 'Filename wildcards, i.e. (''IsFmask'', 1), are not supported for hdf5 files')); parse(p, params{:}); r = p.Results; vararg_remain = [fieldnames(p.Unmatched)'; struct2cell(p.Unmatched)']; if (r.IsFmask) [data_dir, fnames, vararg_remain] = ... find_files( filename, vararg_remain ); filename = []; for ii=1:length(fnames) filename{ii} = fullfile(data_dir, fnames(ii).name); end end if iscell(filename) dir_entry = dir(filename{1}); frames.header{1} = {['DateTime ' dir_entry.date], 'Exposure_time 0.0'}; [~, ~, frames.extension{1}] = fileparts(filename{1}); frames.extension{1} = frames.extension{1}(2:end); % truncate a leading dot frames.filename{1} = '*multiple_frames*'; else dir_entry = dir(filename); frames.header{1} = {['DateTime ' dir_entry.date], 'Exposure_time 0.0'}; [~, frames.filename{1}, frames.extension{1}] = fileparts(filename); frames.extension{1} = frames.extension{1}(2:end); % truncate a leading dot end inputs_orient = {'OrientByExtension', r.OrientByExtension, 'InvertOrientation', r.InvertOrientation}; if r.OrientByExtension orient_vec = image_default_orientation(frames.header{1}, frames.extension{1}); do_transpose = orient_vec(1); do_fliplr = orient_vec(2); do_flipud = orient_vec(3); % Warn user if they also provided either of 'Orientation', 'Transpose', 'FlipLR' or 'FlipUD' if ~all(ismember({'Orientation', 'Transpose', 'FlipLR', 'FlipUD'}, p.UsingDefaults)) warning(['Default hdf5 image orientation is potentially modified by either ' ... '''Orientation'', or any of ''Transpose'', ''FlipLR'', or ''FlipUD'' parameters. ' ... 'To supress this warning set ''OrientByExtension'' to 0.']); end else do_transpose = 0; do_fliplr = 0; do_flipud = 0; end if ~ismember({'Orientation'}, p.UsingDefaults) do_transpose = r.Orientation(1); do_fliplr = r.Orientation(2); do_flipud = r.Orientation(3); inputs_orient = [inputs_orient, {'Orientation', r.Orientation}]; % Warn user if they also provided either of 'Transpose', 'FlipLR' or 'FlipUD' if ~all(ismember({'Transpose', 'FlipLR', 'FlipUD'}, p.UsingDefaults)) warning(['Image orientation specified via ''Orientation'' parameter is potentially ' ... 'modified by either ''Transpose'', ''FlipLR'', and/or ''FlipUD''. ' ... 'To supress this warning use either ''Orientation'' or a combination of ' ... '''Transpose'', ''FlipLR'', and/or ''FlipUD'' parameters.']); end end if r.Transpose || r.FlipLR || r.FlipUD do_transpose = r.Transpose; do_fliplr = r.FlipLR; do_flipud = r.FlipUD; inputs_orient = [inputs_orient, {'Transpose', r.Transpose}]; inputs_orient = [inputs_orient, {'FlipLR', r.FlipLR}]; inputs_orient = [inputs_orient, {'FlipUD', r.FlipUD}]; end inputs = {}; if ~isempty(r.H5Location) && ischar(r.H5Location) inputs{end+1} = r.H5Location; end if r.ReadAttr inputs{end+1} = '-sa'; end % Add slicing indexes if a user specified any of them if ~all(ismember({'FrameRange', 'RowFrom', 'RowTo', 'ColumnFrom', 'ColumnTo'}, ... p.UsingDefaults)) % Support 0's as start/end index -> full left/right range if numel(r.FrameRange) == 1 r.FrameRange(2) = r.FrameRange(1); end if r.FrameRange(1) == 0; r.FrameRange(1) = 1; end if r.FrameRange(2) == 0; r.FrameRange(2) = Inf; end if r.RowFrom == 0; r.RowFrom = 1; end if r.RowTo == 0; r.RowTo = Inf; end if r.ColumnFrom == 0; r.ColumnFrom = 1; end if r.ColumnTo == 0; r.ColumnTo = Inf; end % Adjust range values according to the consequent image orientation procedure if r.InvertOrientation % Transpose -> FlipLR/FlipUD if do_transpose; [r.ColumnTo, r.ColumnFrom, r.RowTo, r.RowFrom] = ... deal(r.RowTo, r.RowFrom, r.ColumnTo, r.ColumnFrom); end if do_fliplr; [r.ColumnTo, r.ColumnFrom] = deal(-r.ColumnFrom, -r.ColumnTo); end if do_flipud; [r.RowTo, r.RowFrom] = deal(-r.RowFrom, -r.RowTo); end else % FlipLR/FlipUD -> Transpose if do_fliplr; [r.ColumnTo, r.ColumnFrom] = deal(-r.ColumnFrom, -r.ColumnTo); end if do_flipud; [r.RowTo, r.RowFrom] = deal(-r.RowFrom, -r.RowTo); end if do_transpose; [r.ColumnTo, r.ColumnFrom, r.RowTo, r.RowFrom] = ... deal(r.RowTo, r.RowFrom, r.ColumnTo, r.ColumnFrom); end end % Form the input inputs{end+1} = {[r.RowFrom, r.RowTo],[r.ColumnFrom, r.ColumnTo], r.FrameRange}; end if iscell(filename) if (r.DisplayFilename) fprintf('loading %s\n', filename{1}); end tmp = frames; tmp.data = hdf5_load(filename{1}, inputs{:}); % Orient image frame(s) [tmp, vararg_remain] = image_orient(tmp, [inputs_orient, vararg_remain]); % let's handle the 2D (or 3d with singleton) case first if isnumeric(tmp.data) && ndims(tmp.data==3) && size(tmp.data,3)==1 frames.data = zeros([size(tmp.data(:,:,1)) length(filename)*size(tmp.data,3)]); frames.data(:,:,1:size(tmp.data,3)) = tmp.data; if length(filename)>1 for frame=2:length(filename) if (r.DisplayFilename) fprintf('loading %s\n', filename{frame}); end tmp.data = hdf5_load(filename{frame}, inputs{:}); % Orient image frame(s) [tmp, vararg_remain] = image_orient(tmp, [inputs_orient, vararg_remain]); frames.data(:,:,frame) = tmp.data; end end else % in case of more than 2 dimensions, concatenate along the % specified dimension, or return a cell array if length(filename)>1 frames.data{1} = tmp.data; framedim = ndims(frames.data{1}); for frameID=2:length(filename) if (r.DisplayFilename) fprintf('loading %s\n', filename{frameID}); end frames.data{frameID} = hdf5_load(filename{frameID}, inputs{:}); if ndims(frames.data{frameID})~=framedim framedim = -1; end % Orient image frame(s) [frames, vararg_remain] = image_orient(frames, [inputs_orient, vararg_remain]); end if framedim > 0 try if r.CatDim == -1 frames.data = cat(ndims(frames.data{1}),frames.data{:}); else frames.data = cat(r.CatDim, frames.data{:}); end catch warning('Failed to concatenate frames.') end end else frames.data = tmp.data; end end else % Support wait-until-exist functionality [fid, vararg_remain] = fopen_until_exists(filename, vararg_remain(:)); if fid >= 0 fclose(fid); else % Silently exit if a file was not found and ('ErrorIfNotFound', 0) return; end if (r.DisplayFilename) fprintf('loading %s\n', filename); end frames.data = hdf5_load(filename, inputs{:}); % Orient image frame(s) [frames, vararg_remain] = image_orient(frames, [inputs_orient, vararg_remain]); end % If its a dataset then fill these fields for further showing with % image_show.m or image_spec.m if isnumeric(frames.data) frames.img_full_size = {[size(frames.data, 1), size(frames.data, 2)]}; frames.rowcol_from = {[r.RowFrom, r.ColumnFrom]}; frames.no_of_el_read = {size(frames.data, 3)}; elseif (do_fliplr||do_flipud||do_transpose) warning(['H5Location points to a group, not a dataset. Orientation/OrientByExtension/Transpose/FlipUD/FlipLR will be ignored. \n '... 'To remove this warning, set ''OrientByExtension'' to 0 and ''Orientation'' to [0 0 0]']) end % Show a warning message for unsupported input parameters unmatched = vararg_remain(1:2:end); if ~isempty(unmatched) warning('These input parameters are not supported for hdf5 files and will be ignored: %s', ... strjoin(unmatched, ', ')); end