% HDF5_LOAD Load an hdf5 file % % DATA = HDF5_LOAD(filename) reads a complete file hierarchy recursively, with % file name/path being specified by the 'filename' argument % % DATA = HDF5_LOAD(filename, '-a') reads a complete file hierarchy % recursively, including attributes % % DATA = HDF5_LOAD(filename, location) reads a particular group, link, or a single dataset % specified by the 'location' argument % % ATT = HDF5_LOAD(filename, location, '-a') reads all datasets and attributes associated % with a particular location in the file (group, link or dataset) % % ATT = HDF5_LOAD(filename, location, '-ca') reads all datasets and attributes associated % with a particular location in the file (group, link or dataset) and % converts datasets to a specific matlab class based on attribute 'MATLAB_class' % % SLICE = HDF5_LOAD(filename, location, {rowRange, colRange, frameRange, ...}) reads a % portion of a dataset along specified dimentions, where slicing ranges can be defined in % the following ways (negative indexes count from the end of the corresponding dimensions): % range = scalar_index - reads a particular row/col/frame/... (indentical to % 'range = [scalar_index, scalar_index]') % range = [start_index, end_index] - reads all data between start and end % indexes % range = [start_index, Inf] - reads all data from start_index to the last % existing element in the file % range = [], or range is omitted at the end - reads the full range of values for that % dimention (indentical to 'range = [1, Inf]') % % Examples: % hdf5_load('scan_003.hdf5') % hdf5_load('scan_003.hdf5', '/entry/sample/description') % hdf5_load('scan_003.hdf5', '/entry/collection/data/spec', '-a') % hdf5_load('scan_003.hdf5', '/entry/instrument/Pilatus_2M/data', {5}) % hdf5_load('scan_003.hdf5', '/entry/instrument/Pilatus_2M/data', {[-100, Inf]}) % hdf5_load('scan_003.hdf5', '/entry/instrument/Pilatus_2M/data', {5, [500, Inf], [1, 100]}) % hdf5_load('scan_003.hdf5', '/entry/instrument/Pilatus_2M/data', {[], [], [1, 100]}) %*-----------------------------------------------------------------------* %|                                                                       | %|  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 the Science IT 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 data = hdf5_load(filename, varargin) import io.HDF.* load_attr = false; convert2matlab = false; narginchk(1, 3); if nargin == 1 % Read the complete file hierarchy recursively try info = h5info(filename); info.Name = ''; % a special case of the root group catch ME if strcmp(ME.identifier, 'MATLAB:imagesci:h5info:fileOpenErr') ME = MException('hdf5_load:h5info', ... strjoin({'File', filename, 'does not exist'})); end throwAsCaller(ME); end [data, links] = hdf5_loadGroup(filename, info); data = assign_links(data, info, links); elseif nargin == 2 if any(strcmp(varargin{1}, {'-a', '-ca', '-c'})) % second argument is an attribute flag try info = h5info(filename); info.Name = ''; % a special case of the root group catch ME if strcmp(ME.identifier, 'MATLAB:imagesci:h5info:fileOpenErr') ME = MException('hdf5_load:h5info', ... strjoin({'File', filename, 'does not exist or is not valid h5 file'})); end throwAsCaller(ME); end if any(strcmp(varargin{1}, {'-a', '-ca'})) load_attr = true; end if any(strcmp(varargin{1}, {'-ca', '-c'})) convert2matlab = true; end [data, links] = hdf5_loadGroup(filename, info, convert2matlab, load_attr); data = assign_links(data, info, links, varargin{1}); else % Read a group or a single dataset location = varargin{1}; try info = h5info(filename, location); if strcmp(info.Name, '/') % a special case of the root group info.Name = ''; end catch ME if strcmp(ME.identifier, 'MATLAB:imagesci:h5info:fileOpenErr') ME = MException('hdf5_load:h5info', ... strjoin({'File', filename, 'does not exist'})); elseif strcmp(ME.identifier, 'MATLAB:imagesci:h5info:libraryError') ME = MException('hdf5_load:h5info', ... strjoin({'H5Location', location, 'was not found in', filename, 'file'})); end throwAsCaller(ME); end if isfield(info, 'Groups') % Read a group with its internal hierarchy [data, links] = hdf5_loadGroup(filename, info); data = assign_links(data, info, links); elseif isfield(info, 'Datatype') % Read a data set type = info.Datatype.Class; data = hdf5_loadDataset(filename, location, type); elseif isfield(info, 'Type') % Read a link data = hdf5_loadLink(info); else error('hdf5_load:parse_argument', ... 'The 2-nd argument must be a name of a group, dataset, or link'); end end elseif nargin == 3 % Read attributes of a group or a data set, or slices of a data set location = varargin{1}; try info = h5info(filename, location); if strcmp(info.Name, '/') % a special case of the root group info.Name = ''; end catch ME if strcmp(ME.identifier, 'MATLAB:imagesci:h5info:fileOpenErr') ME = MException('hdf5_load:h5info', ... strjoin({'File', filename, 'does not exist or is not HDF5 format'})); elseif strcmp(ME.identifier, 'MATLAB:imagesci:h5info:libraryError') ME = MException('hdf5_load:h5info', ... strjoin({'H5Location', location, 'was not found in', filename, 'file'})); end throwAsCaller(ME); end if iscell(varargin{2}) % Read slices of a data set slices = varargin{2}; % Check if the specified location is a data set if ~isfield(info, 'Dataspace') error('hdf5_load:invalid_location', ... 'Slicing ranges are not applicable, the location is not a data set'); end data_size = info.Dataspace.Size; if length(slices) > length(data_size) error('hdf5_load:invalid_slicing', ... 'A number of slicing ranges is larger than a dimention of a data set') end % Parse ranges startIndex = ones(1, length(data_size)); nElements = Inf(1, length(data_size)); for i = 1:length(slices) [startIndex(i), nElements(i)] = parse_range(slices{i}, data_size(i)); end % Read data data = h5read(filename, location, startIndex, nElements); elseif any(strcmp(varargin{2}, {'-a', '-ca', '-c'})) % Read attributes and/or convert to matlab structures if any(strcmp(varargin{2}, {'-a', '-ca'})) load_attr = true; end if any(strcmp(varargin{2}, {'-ca', '-c'})) convert2matlab = true; end if isfield(info, 'Groups') % Read a group with its internal hierarchy [data, links] = hdf5_loadGroup(filename, info, convert2matlab, load_attr); data = assign_links(data, info, links, varargin{2}); elseif isfield(info, 'Datatype') % Read a data set type = info.Datatype.Class; dset_val = hdf5_loadDataset(filename, location, type); if load_attr || convert2matlab [dset_attr, ml_class_dset] = hdf5_loadAttributes(info, convert2matlab, load_attr); else ml_class_dset = []; end if ~isempty(ml_class_dset) switch ml_class_dset case 'complex' dset_val = dset_val.r + 1i*dset_val.i; case 'cell' if ~iscell(dset_val) dset_val = {dset_val}; end case 'char_array' dset_val = char(dset_val); otherwise conv2ml = str2func(ml_class_dset); dset_val = conv2ml(dset_val); end end if load_attr data.Attributes = dset_attr; data.Value = dset_val; else data = dset_val; end elseif isfield(info, 'Type') % Read a link data = hdf5_loadLink(info, convert2matlab, load_attr); end else error('hdf5_load:parse_argument', ... 'Incorrect 3-rd argument'); end end function [data, links] = hdf5_loadGroup(filename, info, varargin) import io.HDF.* if nargin > 2 convert2matlab = varargin{1}; load_attr = varargin{2}; else convert2matlab = false; load_attr = false; end data = []; % Collect links links = info.Links.'; % transform to a row for easier indexing if ~isempty(links) for link_ind = 1:length(links) links(link_ind).Name = [info.Name, '/', links(link_ind).Name]; end end % Load the datasets for dataset_ind = 1:length(info.Datasets) dset_info = info.Datasets(dataset_ind); dset_name = dset_info.Name; location = [info.Name, '/', dset_name]; type = dset_info.Datatype.Class; dset_val = hdf5_loadDataset(filename, location, type); % Load attributes of a dataset if load_attr || convert2matlab [dset_attr, ml_class_dset] = hdf5_loadAttributes(dset_info, convert2matlab, load_attr); else ml_class_dset = []; end if ~isempty(ml_class_dset) switch ml_class_dset case 'complex' dset_val = dset_val.r + 1i*dset_val.i; case 'cell' if ~iscell(dset_val) dset_val = {dset_val}; end case 'char_array' dset_val = char(dset_val); otherwise conv2ml = str2func(ml_class_dset); dset_val = conv2ml(dset_val); end end if load_attr data.(dset_name).Attributes = dset_attr; data.(dset_name).Value = dset_val; else data.(dset_name) = dset_val; end end % Load attributes of a group if load_attr || convert2matlab [group_attr, ml_class_group] = hdf5_loadAttributes(info, convert2matlab, load_attr); if load_attr data.Attributes = group_attr; end else ml_class_group = []; end % Load the internal groups recursively for group_ind = 1:length(info.Groups) [group_data, child_links] = hdf5_loadGroup(filename, info.Groups(group_ind), convert2matlab, load_attr); [~, group_name] = fileparts(info.Groups(group_ind).Name); data.(group_name) = group_data; % Aggregate links links = [links, child_links]; %#ok There shouldn't be too many links present end if ~isempty(ml_class_group) % convert the groups data_temp = data; data = []; if isfield(data_temp, 'Attributes') data.Attributes = data_temp.Attributes; data_temp = rmfield(data_temp, 'Attributes'); fn = fieldnames(data_temp); for group_ind = 1:length(fn) switch ml_class_group case 'cell' data.Value{group_ind} = data_temp.(fn{group_ind}); case 'structure array' data.Value(group_ind) = data_temp.(fn{group_ind}); otherwise keyboard end end else fn = fieldnames(data_temp); for group_ind = 1:length(fn) switch ml_class_group case 'cell' data{group_ind} = data_temp.(fn{group_ind}); case 'structure array' data(group_ind) = data_temp.(fn{group_ind}); otherwise keyboard end end end end function [data, ml_class] = hdf5_loadAttributes(info, convert2matlab, load_attr) data = []; ml_class = []; if isfield(info, 'Attributes') % info structure may not contain Attributes field attr_info = info.Attributes; for attr_ind = 1:length(attr_info) attr = attr_info(attr_ind); attr_name = attr.Name; if ~isvarname(attr_name) if ~any(strcmpi({attr_info.Name}, ['MATLAB' attr_name])) && ~strcmpi(attr_name, '_class') warning('Invalid attribute name! Added "MATLAB" prefix to %s.', attr_name) attr_name = ['MATLAB' attr_name]; else error('Invalid attribute name.') end end if convert2matlab && strcmpi(attr_name, 'MATLAB_class') ml_class = attr.Value{1}; elseif load_attr if iscell(attr.Value) data.(attr_name) = attr.Value{1}; else data.(attr_name) = attr.Value; end end end end function data = hdf5_loadDataset(filename, location, type) if strcmp(type, 'H5T_ENUM') % Workaround for a bug in h5postprocessenums (part of h5read) function data = read_enum(filename, location); else data = h5read(filename, location); if iscell(data) && numel(data) == 1 && ischar(data{1}) data = data{1}; % utility string unwrapping from a single cell end end function data = hdf5_loadLink(link, varargin) if nargin > 2 convert2matlab = varargin{1}; load_attr = varargin{2}; else convert2matlab = false; load_attr = false; end switch link.Type case {'hard link', 'soft link'} filename = link.Filename; location = link.Value{1}; case 'external link' filename = absolute_path(link.Value{1}, link.Filename); location = link.Value{2}; otherwise error('hdf5_load:hdf5_loadLink', ... strjoin({'Unknown link type at', link.Name})); end link_info = h5info(filename, location); if strcmp(link_info.Name, '/') % a special case of the root group link_info.Name = ''; end if isfield(link_info, 'Groups') [data, links] = hdf5_loadGroup(filename, link_info, convert2matlab, load_attr); data = assign_links(data, link_info, links); elseif isfield(link_info, 'Datatype') type = link_info.Datatype.Class; data = hdf5_loadDataset(filename, location, type); elseif isfield(link_info, 'Type') data = hdf5_loadLink(link_info, convert2matlab, load_attr); else error('hdf5_load:hdf5_loadLink', ... strjoin({'A link at', link_info.Name, 'must be a name of a group, dataset, or link'})); end function data = assign_links(data, info, links, varargin) import io.HDF.* if ~isempty(varargin) flag = varargin{1}; else flag = []; end if ~isempty(links) cut_start = length(info.Name) + 1; while true resolved_links = false(size(links)); for ind = 1:length(links) link = links(ind); place = strrep(link.Name(cut_start:end), '/', '.'); target = []; target_struc = []; if ~isempty(flag) && contains(flag, 'a') && contains(flag, 'c') target_struc = ['.Value']; end switch link.Type case {'hard link', 'soft link'} try parent = strsplit(link.Value{1}, '/'); parent = strjoin(parent(1:end-1), '/'); parent_info = h5info(info.Filename, parent); if isfield(parent_info, 'Attributes') && ~isempty(parent_info.Attributes) for ii=1:numel(parent_info.Attributes) if strcmp(parent_info.Attributes(ii).Name, 'MATLAB_class') && ~isempty(flag) && contains(flag, 'c') % get pointer index pnt_indx = strsplit(link.Value{1}, '_'); pnt_indx = str2double(pnt_indx(end)); target_add = []; switch parent_info.Attributes(ii).Value{1} case 'cell' target_add = sprintf('{%d}', pnt_indx+1); case 'structure array' target_add = sprintf('(%d)', pnt_indx+1); otherwise keyboard end target = [strrep(parent, '/', '.') target_struc target_add]; break end end end if isempty(target) target = [strrep(link.Value{1}(cut_start:end), '/', '.') target_struc]; end evalc(['data', place, ' = data', target]); catch continue % postpone this link resolution end case 'external link' ext_link = absolute_path(link.Value{1}, info.Filename); % make sure to reference the same variable in evalc! if ~isempty(flag) target_data = hdf5_load(ext_link, link.Value{2}, flag); %#ok else target_data = hdf5_load(ext_link, link.Value{2}); end evalc(['data', place, ' = target_data']); otherwise error('hdf5_load:assign_links', ... strjoin({'Unknown link type at', place})); end resolved_links(ind) = true; end if all(resolved_links) % all links have been assigned return end if ~any(resolved_links) % none of the links has been assigned in this iteration error('hdf5_load:assign_links', ... strjoin({'Cannot assign link(s) at', ''})); end links = links(~resolved_links); end end function filepath = absolute_path(filepath, current_filepath) if ~startsWith(filepath, '/') path = fileparts(current_filepath); filepath = fullfile(path, filepath); end function [startVal, nVals] = parse_range(valRange, maxVal) if isempty(valRange) % empty startVal = 1; nVals = Inf; elseif isscalar(valRange) % single value if valRange <= -1 valRange = maxVal + valRange + 1; end startVal = valRange; nVals = 1; elseif isvector(valRange) && numel(valRange) == 2 % vector with two values if valRange(1) <= -1 if isinf(valRange(1)) valRange(1) = 1; % = -Inf else valRange(1) = maxVal + valRange(1) + 1; end end startVal = valRange(1); if valRange(2) <= -1 if isinf(valRange(2)) valRange(2) = 1; % = -Inf else valRange(2) = maxVal + valRange(2) + 1; end end nVals = valRange(2) - startVal + 1; else error('hdf5_load:parse_range', ... 'A range should be specified with <= 2 parameters'); end if startVal < 1 || startVal > maxVal || nVals < 1 || (nVals > maxVal && ~isinf(nVals)) error('hdf5_load:parse_range', ... 'The resulting range is out of data borders'); end function data = read_enum(filename, location) file_id = H5F.open(filename); dset_id = H5D.open(file_id, location); type_id = H5D.get_type(dset_id); data = H5D.read(dset_id); % numerical member of enumeration data = H5T.enum_nameof(type_id, data); % associated symbol name H5T.close(type_id); H5D.close(dset_id); H5F.close(file_id);