initial commit

This commit is contained in:
2026-08-07 15:56:42 +09:00
commit 91ad25aca9
1012 changed files with 159314 additions and 0 deletions
+642
View File
@@ -0,0 +1,642 @@
% 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<AGROW> 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<NASGU>
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);