Files
Jungfraujoch/writer/HDF5Objects.cpp
T
leonarski_f a39fd29f77
Build Packages / XDS test (JFJoch plugin) (push) Successful in 11m4s
Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 17m46s
Build Packages / build:windows:cuda (push) Successful in 20m20s
Build Packages / build:viewer-tgz:cpu (push) Successful in 15m56s
Build Packages / build:viewer-tgz:cuda (push) Successful in 17m57s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 14m10s
Build Packages / build:rugnux:windows (push) Successful in 11m12s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m14s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 22m13s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 19m17s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 21m21s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 17m26s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 23m56s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 20m48s
Build Packages / build:rpm (rocky8) (push) Successful in 23m43s
Build Packages / build:rpm (rocky9) (push) Successful in 20m38s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 24m57s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 20m58s
Build Packages / XDS test (durin plugin) (push) Successful in 10m43s
Build Packages / Generate python client (push) Successful in 47s
Build Packages / Build documentation (push) Successful in 1m5s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (neggia plugin) (push) Successful in 8m57s
Build Packages / DIALS test (push) Successful in 18m40s
v1.0.0-rc.167 (#77)
* `rugnux --model` reports CC(model, data) - the correlation of the merged intensities with the placed, scaled model - by resolution shell, on the same shells as CC1/2, with the reflection count and a significance for each.
* `rugnux --model` fits the model's scale, anisotropic B and bulk-solvent parameters on the working reflections only, so the R-free it reports is measured against a model no free reflection helped scale.
* The bulk-solvent parameters of `rugnux --model` are searched over their physically meaningful range instead of being fitted without bounds, so a model is never scaled with a solvent term that has silently switched itself off.
* The rigid-body placement of `rugnux --model` uses the same bounded bulk solvent as the reported fit, so a model is no longer placed against a target carrying a solvent term with no physical meaning.
* `rugnux --model` puts the model into the data's own description of the lattice before placing it, so a model whose cell is written on other axes - I-centred where the run indexed C-centred, a different unique axis, a permuted orthorhombic cell - is placed rather than scored where it was read; `MODEL_CHANGE_OF_BASIS=` and `MODEL_SETTING_AS_READ=` report it when it happens.
* The rugnux results report opens with a summary - `VERDICT=` (`OK`, `WARNINGS`, `UNUSABLE`, `FAILED`), `VERDICT_TEXT=`, `PATHOLOGY_FLAGS=` with one closed-vocabulary code per condition that warned, and the `WARNING:` lines, which used to close the file - and the sections after it are renumbered 1-5 with no gaps.
* `rugnux --developer` writes the full results report - the pipeline-internal keys and the long explanations the default report now leaves out - and `--finalist-ledger` adds the evidence for every space group the search considered, not only the one it adopted.
* The results report warns when the merged data carry no usable signal and when too little of reciprocal space was measured inside the fitted resolution, and omits `FITTED_RESOLUTION` where the CC1/2 curve it is fitted on never falls off.
* rugnux detects translational pseudo-symmetry and reports it under the `PSEUDO_TRANSLATION` flag as `TNCS_DETECTED=` and the `TNCS_*` keys - a translation the merged data are exactly invariant under is reported as `UNDECLARED_LATTICE_TRANSLATION=` under `LATTICE_TRANSLATION` instead - and a detected pseudo-translation can no longer buy a false screw axis in the space-group search or hide a twin from the L-test (`L_TEST_VS_TNCS=`).
* The space-group search determines glide planes from zonal systematic absences, so a non-Sohncke space group such as P 2_1/c or Pbca is named where the run previously stopped at its Sohncke subgroup; `SOHNCKE_SPACE_GROUP=` carries the best Sohncke group beside it on every run that searched, and a centre of symmetry is never claimed.
* Where the cell metric carries more rotational symmetry than the Bravais class the indexer named, the extra rotations are put to the intensities and the space-group search is asked again on the metric's own cell - adopted only where the intensities confirm the higher symmetry - so a lattice that is nearly but not exactly hexagonal, or whose reduction landed in a sub-cell, still reaches its true point group.
* Systematic-absence calls rest on the evidence rather than on counts: a screw axis whose absent class the data show extinct is no longer refused because a handful of reflections in it read as present, and `SPACE_GROUP_ALTERNATIVES=` no longer drops a candidate that differs only on a zone the sweep never measured.
* A reference correlation measured on too few reflections is refused instead of scored zero, so a run given a reference MTZ is no longer reindexed on an operator that mapped almost everything outside the reference's coverage.
* A frame counts as indexed from 6 spots on its lattice rather than 9, so a weakly diffracting crystal whose frames cannot carry 9 is no longer refused the lattice it fits; `--min-indexed-spots` overrides it.
* `-C` accepts a known cell in any equivalent description - conventional or primitive, centred or not - instead of only the reduced primitive form, so a centred cell given the way it is published no longer makes the run report that it found no lattice.
* Each reflection is corrected for the sensor's quantum efficiency at the angle it meets the detector (attenuation lengths from the NIST tables, which also fixes the spot-width parallax term on CdTe) and for the attenuation of the flight path between the sample and its pixel; `--flight-path air|helium|vacuum` declares the medium - default air, since no file states it - and the report says what was assumed and what it was worth. The unmerged MTZ records the factors in new `QE` and `FLIGHT` columns beside `LP`, so raw counts are `I / LP * QE * FLIGHT`, and `_process.h5` in new optional `qe` and `flight` datasets.
* Rotation geometry post-refinement fits the crystal and the detector at once, against the observed spot positions and the observed rocking angles together, so the refined distance depends far less on how wrong the file's distance was.
* A coarsely sliced sweep integrates correctly: partials are joined into one rocking event by angle rather than by frame count, so two crossings of the Ewald sphere are no longer summed into one full, and at 0.5 degrees per image or coarser the per-frame geometry refinement accepts a spot whose miss the exposure's own rotation accounts for.
* `rugnux --mode scale` reports the detector tilt and direct beam of the geometry it re-scaled at, instead of zeros that read as a flat detector, and no longer warns that no image was indexed on a run whose lattice came from its input file.
* Every rotation run that determined a space group and merged reports what the mounting cost: `SPINDLE_LOST_UNIQUE_FRACTION=` is the fraction (0-1) of unique reflections the mounting made unmeasurable under the measured point group, also written to the master as `/entry/MX/spindleLostUniqueFraction` and what the mounting warning fires on; `SPINDLE_SYMMETRY_AXIS_ANGLE_DEG=` / `SPINDLE_SYMMETRY_AXIS_ORDER=` describe the mounting in the `--developer` report.
* Stills and grid scans carry a per-image `spindle_blind_fraction` - how much of a rotation sweep's blind cone this orientation would make unrecoverable, 0.5 and above calling for a second orientation - through the CBOR stream, HDF5 (`/entry/MX/spindleBlindFraction`), the plot and scan-result APIs, and the viewer and frontend plots; an absent value means the frame could not be assessed and is not a 0.
* The results report's `REPORT_VERSION` is 7.

Reviewed-on: #77
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-09-09 07:25:13 +02:00

1382 lines
49 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <cstring>
#include <filesystem>
#include <iostream>
#include <bitshuffle/bshuf_h5filter.h>
#include "HDF5Objects.h"
// HDF Group contributed LZ4 filter, the one DECTRIS Eiger firmware 1.x wrote its images with.
// Read-only here: bshuf_h5filter.h defines 32008 for bitshuffle but nothing defines this one.
#define LZ4_H5FILTER 32004
#include "H5FDpoison_sec2.h"
std::mutex hdf5_mutex;
static void HDF5PoisonCallback(
const char *filename,
const char *operation,
int error_number,
void *user_data
) {
std::cerr << "HDF5 Poison callback triggered: filename=" << filename << ", operation=" << operation << ", error_number=" << error_number << std::endl;
}
hid_t HDF5Id::GetID() const {
return id;
}
HDF5Id::HDF5Id(HDF5Id &&other) noexcept {
id = other.id;
other.id = -1;
}
HDF5Id::HDF5Id(const HDF5Id &other) {
id = other.id;
if (H5Iis_valid(id)) {
if (H5Iinc_ref(id) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot increment HDF5 Counter");
}
}
HDF5DataSpace::HDF5DataSpace(ScalarTag) : HDF5Id() {
id = H5Screate(H5S_SCALAR);
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot create scalar dataspace");
ndims = 0;
}
HDF5DataSpace::HDF5DataSpace(const std::vector<hsize_t> &dims, const std::vector<hsize_t> &max_dims) : HDF5Id() {
if (dims.empty())
throw JFJochException(JFJochExceptionCategory::HDF5, "Dimension vector empty");
if (max_dims.empty()) {
if (dims[0] == 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Value dimension cannot be 0");
id = H5Screate_simple(dims.size(), dims.data(), nullptr);
} else {
if (max_dims.size() != dims.size())
throw JFJochException(JFJochExceptionCategory::HDF5, "Discrepancy in size of dims/max_dims");
id = H5Screate_simple(dims.size(), dims.data(), max_dims.data());
}
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot create dataspace");
ndims = dims.size();
}
HDF5DataSpace::HDF5DataSpace(const HDF5DataSet &data_set) : HDF5Id() {
id = H5Dget_space(data_set.GetID());
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open dataspace");
int tmp = H5Sget_simple_extent_ndims(id);
if (tmp < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot check dimensions of dataspace");
ndims = tmp;
}
std::vector<hsize_t> HDF5DataSpace::GetDimensions() const {
std::vector<hsize_t> dims(ndims);
int tmp = H5Sget_simple_extent_dims(id, dims.data(), nullptr);
if ((tmp < 0) || (tmp != ndims))
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot check dimensions of dataspace");
return dims;
}
void HDF5DataSpace::SelectHyperslab(const std::vector<hsize_t> &start, const std::vector<hsize_t> &size) {
if ((start.size() != ndims) || (size.size() != ndims))
throw JFJochException(JFJochExceptionCategory::HDF5, "Arrays are inconsistent with dataspace dimension number " + std::to_string(ndims));
if (H5Sselect_hyperslab(id, H5S_SELECT_SET, start.data(), NULL, size.data(), NULL) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot select hyperslab");
}
void HDF5DataSpace::SelectHyperslabWithStride(const std::vector<hsize_t> &start, const std::vector<hsize_t> &size,
const std::vector<hsize_t> &stride) {
if ((start.size() != ndims) || (stride.size() != ndims) || (size.size() != ndims))
throw JFJochException(JFJochExceptionCategory::HDF5, "Arrays are inconsistent with dataspace dimension number");
if (H5Sselect_hyperslab(id, H5S_SELECT_SET, start.data(), stride.data(), size.data(), NULL) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot select hyperslab");
}
HDF5DataSpace::~HDF5DataSpace() {
if (id >= 0) {
H5E_BEGIN_TRY {
H5Sclose(id);
} H5E_END_TRY;
id = -1;
}
}
uint8_t HDF5DataSpace::GetNumOfDimensions() const {
return ndims;
}
HDF5DataType::HDF5DataType(CompressedImageMode mode) : HDF5Id() {
switch (mode) {
case CompressedImageMode::Int8:
id = H5Tcopy(H5T_NATIVE_INT8);
break;
case CompressedImageMode::Int16:
id = H5Tcopy(H5T_NATIVE_INT16);
break;
case CompressedImageMode::Int32:
id = H5Tcopy(H5T_NATIVE_INT32);
break;
case CompressedImageMode::RGB:
case CompressedImageMode::Uint8:
id = H5Tcopy(H5T_NATIVE_UINT8);
break;
case CompressedImageMode::Uint16:
id = H5Tcopy(H5T_NATIVE_UINT16);
break;
case CompressedImageMode::Uint32:
id = H5Tcopy(H5T_NATIVE_UINT32);
break;
case CompressedImageMode::Float16:
id = H5Tcopy(H5T_IEEE_F16LE);
break;
case CompressedImageMode::Float32:
id = H5Tcopy(H5T_IEEE_F32LE);
break;
case CompressedImageMode::Float64:
id = H5Tcopy(H5T_IEEE_F64LE);
break;
}
}
HDF5DataType::HDF5DataType(double val) : HDF5Id() {
id = H5Tcopy(H5T_IEEE_F64LE);
}
HDF5DataType::HDF5DataType(float val) : HDF5Id() {
id = H5Tcopy(H5T_IEEE_F32LE);
}
HDF5DataType::HDF5DataType(bool val) : HDF5Id() {
id = H5Tcopy(H5T_NATIVE_HBOOL);
}
HDF5DataType::HDF5DataType(uint8_t val) : HDF5DataType(1, false) {}
HDF5DataType::HDF5DataType(int8_t val) : HDF5DataType(1, true) {}
HDF5DataType::HDF5DataType(uint16_t val) : HDF5DataType(2, false) {}
HDF5DataType::HDF5DataType(int16_t val) : HDF5DataType(2, true) {}
HDF5DataType::HDF5DataType(uint32_t val) : HDF5DataType(4, false) {}
HDF5DataType::HDF5DataType(int32_t val) : HDF5DataType(4, true) {}
HDF5DataType::HDF5DataType(uint64_t val) : HDF5DataType(8, false) {}
HDF5DataType::HDF5DataType(int64_t val) : HDF5DataType(8, true) {}
HDF5DataType::HDF5DataType(uint64_t size_in_bytes, bool is_signed) : HDF5Id() {
switch (size_in_bytes) {
case 1:
if (is_signed)
id = H5Tcopy(H5T_NATIVE_INT8);
else
id = H5Tcopy(H5T_NATIVE_UINT8);
break;
case 2:
if (is_signed)
id = H5Tcopy(H5T_NATIVE_INT16);
else
id = H5Tcopy(H5T_NATIVE_UINT16);
break;
case 4:
if (is_signed)
id = H5Tcopy(H5T_NATIVE_INT32);
else
id = H5Tcopy(H5T_NATIVE_UINT32);
break;
case 8:
if (is_signed)
id = H5Tcopy(H5T_NATIVE_INT64);
else
id = H5Tcopy(H5T_NATIVE_UINT64);
break;
default:
throw JFJochException(JFJochExceptionCategory::HDF5, "Type outside of standard types");
}
}
HDF5DataType::HDF5DataType(const std::string& str) : HDF5Id() {
id = H5Tcopy(H5T_C_S1);
H5Tset_size(id, str.length() + 1);
}
HDF5DataType::HDF5DataType(const char * str) : HDF5Id() {
id = H5Tcopy(H5T_C_S1);
size_t length = strnlen(str,512);
H5Tset_size(id, length + 1);
}
HDF5DataType::HDF5DataType(const HDF5DataSet &data_set) :HDF5Id() {
id = H5Dget_type(data_set.GetID());
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open HDF5 data type");
}
HDF5DataType::~HDF5DataType() {
if (id >= 0) {
H5E_BEGIN_TRY {
H5Tclose(id);
} H5E_END_TRY;
id = -1;
}
}
size_t HDF5DataType::GetElemSize() const {
return H5Tget_size(id);
}
bool HDF5DataType::IsSigned() const {
if (IsFloat())
return true;
else if (IsInteger())
return H5Tget_sign(id) == H5T_SGN_2;
else
return false;
}
bool HDF5DataType::IsInteger() const {
return H5Tget_class(id) == H5T_INTEGER;
}
bool HDF5DataType::IsFloat() const {
return H5Tget_class(id) == H5T_FLOAT;
}
HDF5Dcpl::HDF5Dcpl() : HDF5Id() {
id = H5Pcreate(H5P_DATASET_CREATE);
ndim = 0;
layout = HDF5DataSetLayout::CONTIGUOUS;
}
HDF5Dcpl::HDF5Dcpl(const HDF5DataSet &data_set) : HDF5Id() {
id = H5Dget_create_plist(data_set.GetID());
// Check if chunking is enabled
H5D_layout_t h5_layout = H5Pget_layout(id);
if (h5_layout == H5D_VIRTUAL)
layout = HDF5DataSetLayout::VIRTUAL;
else if (h5_layout == H5D_CHUNKED) {
layout = HDF5DataSetLayout::CHUNKED;
ndim = H5Pget_chunk(id, 0, nullptr);
if (ndim <= 0) {
H5Pclose(id);
throw JFJochException(JFJochExceptionCategory::HDF5,
"Error getting number of chunk dimensions");
}
} else
layout = HDF5DataSetLayout::CONTIGUOUS;
}
HDF5Dcpl::~HDF5Dcpl() {
if (id >= 0) {
H5E_BEGIN_TRY {
H5Pclose(id);
} H5E_END_TRY;
id = -1;
}
}
void HDF5Dcpl::SetChunking(const std::vector<hsize_t> &dims) {
layout = HDF5DataSetLayout::CHUNKED;
if ((dims.empty()) || dims[0] == 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Value dimension cannot be 0");
ndim = dims.size();
if (H5Pset_chunk(id, dims.size(), dims.data()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot set chunking");
}
std::vector<hsize_t> HDF5Dcpl::GetChunking() {
if (ndim <= 0)
return {};
std::vector<hsize_t> ret(ndim);
if (H5Pget_chunk(id, ndim, ret.data()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot get chunk dimensions");
return ret;
}
uint8_t HDF5Dcpl::GetNumOfDimensions() const {
return ndim;
}
void HDF5Dcpl::SetFillValue32(int32_t val) {
H5Pset_fill_value(id, H5T_NATIVE_INT32, &val);
}
void HDF5Dcpl::SetFillValue16(int16_t val) {
H5Pset_fill_value(id, H5T_NATIVE_INT16, &val);
}
void HDF5Dcpl::SetFillValue8(int8_t val) {
H5Pset_fill_value(id, H5T_NATIVE_INT8, &val);
}
void HDF5Dcpl::SetFillValueU32(uint32_t val) {
H5Pset_fill_value(id, H5T_NATIVE_UINT32, &val);
}
void HDF5Dcpl::SetFillValueU16(uint16_t val) {
H5Pset_fill_value(id, H5T_NATIVE_UINT16, &val);
}
void HDF5Dcpl::SetFillValueU8(uint8_t val) {
H5Pset_fill_value(id, H5T_NATIVE_UINT8, &val);
}
void HDF5Dcpl::SetVirtual(const std::string &path, const std::string &dataset, const HDF5DataSpace &src_dataspace, const HDF5DataSpace &dest_dataspace) {
layout = HDF5DataSetLayout::VIRTUAL;
std::string filename = ExtractFilename(path);
if (H5Pset_virtual(id, dest_dataspace.GetID(), filename.c_str(), dataset.c_str(), src_dataspace.GetID()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot set virtual mapping");
}
HDF5DataSetLayout HDF5Dcpl::GetLayout() const {
return layout;
}
HDF5Fapl::HDF5Fapl() : HDF5Id() {
id = H5Pcreate(H5P_FILE_ACCESS);
}
HDF5Fapl::~HDF5Fapl() {
if (id >= 0) {
H5E_BEGIN_TRY {
H5Pclose(id);
} H5E_END_TRY;
id = -1;
}
}
void HDF5Fapl::SetVersionTo1p10orNewer() {
H5Pset_libver_bounds(id, H5F_LIBVER_V110, H5F_LIBVER_LATEST);
}
void HDF5Fapl::SetCloseStrong() {
H5Pset_fclose_degree(id, H5F_CLOSE_STRONG);
}
void HDF5Fapl::SetPoisonSec2() {
if (H5Pset_fapl_poison_sec2(id) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot enable poisoned sec2 HDF5 VFD");
}
template <typename T>
static HDF5Object& WriteOrCreateScalarAttr(HDF5Object& object, const std::string& name, const T& val) {
HDF5DataSpace dataspace(HDF5DataSpace::Scalar);
HDF5DataType datatype(val);
hid_t attr_id = -1;
if (H5Aexists(object.GetID(), name.c_str()) > 0) {
attr_id = H5Aopen(object.GetID(), name.c_str(), H5P_DEFAULT);
if (attr_id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open attribute " + name);
hid_t existing_type = H5Aget_type(attr_id);
if (existing_type < 0) {
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot get attribute type " + name);
}
const bool recreate =
(H5Tget_class(existing_type) != H5Tget_class(datatype.GetID())) ||
(H5Tget_size(existing_type) != H5Tget_size(datatype.GetID()));
H5Tclose(existing_type);
H5Aclose(attr_id);
if (recreate) {
if (H5Adelete(object.GetID(), name.c_str()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot delete attribute " + name);
attr_id = H5Acreate2(object.GetID(), name.c_str(), datatype.GetID(), dataspace.GetID(), H5P_DEFAULT, H5P_DEFAULT);
} else {
attr_id = H5Aopen(object.GetID(), name.c_str(), H5P_DEFAULT);
}
} else {
attr_id = H5Acreate2(object.GetID(), name.c_str(), datatype.GetID(), dataspace.GetID(), H5P_DEFAULT, H5P_DEFAULT);
}
if (attr_id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot create/open attribute " + name);
herr_t ret = H5Awrite(attr_id, datatype.GetID(), &val);
H5Aclose(attr_id);
if (ret < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Attribute write unsuccessful");
return object;
}
HDF5Object & HDF5Object::Attr(const std::string &name, const std::string &val) {
HDF5DataSpace dataspace(HDF5DataSpace::Scalar);
HDF5DataType datatype(val);
hid_t attr_id = -1;
if (H5Aexists(id, name.c_str()) > 0) {
attr_id = H5Aopen(id, name.c_str(), H5P_DEFAULT);
if (attr_id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open attribute " + name);
hid_t existing_type = H5Aget_type(attr_id);
if (existing_type < 0) {
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot get attribute type " + name);
}
const bool recreate =
(H5Tget_class(existing_type) != H5T_STRING) ||
(H5Tget_size(existing_type) < val.length() + 1);
H5Tclose(existing_type);
H5Aclose(attr_id);
if (recreate) {
if (H5Adelete(id, name.c_str()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot delete attribute " + name);
attr_id = H5Acreate2(id, name.c_str(), datatype.GetID(), dataspace.GetID(), H5P_DEFAULT, H5P_DEFAULT);
} else {
attr_id = H5Aopen(id, name.c_str(), H5P_DEFAULT);
}
} else {
attr_id = H5Acreate2(id, name.c_str(), datatype.GetID(), dataspace.GetID(), H5P_DEFAULT, H5P_DEFAULT);
}
if (attr_id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot create/open attribute " + name);
herr_t ret = H5Awrite(attr_id, datatype.GetID(), val.c_str());
H5Aclose(attr_id);
if (ret < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Attribute write unsuccessful");
return *this;
}
HDF5Object & HDF5Object::Attr(const std::string &name, int32_t val) {
return WriteOrCreateScalarAttr(*this, name, val);
}
HDF5Object & HDF5Object::Attr(const std::string &name, uint32_t val) {
return WriteOrCreateScalarAttr(*this, name, val);
}
HDF5Object & HDF5Object::Attr(const std::string &name, int64_t val) {
return WriteOrCreateScalarAttr(*this, name, val);
}
HDF5Object & HDF5Object::Attr(const std::string &name, uint64_t val) {
return WriteOrCreateScalarAttr(*this, name, val);
}
HDF5Object & HDF5Object::Attr(const std::string &name, double val) {
return WriteOrCreateScalarAttr(*this, name, val);
}
HDF5Object & HDF5Object::Attr(const std::string &name, const std::vector<double> &val) {
HDF5DataSpace dataspace({val.size()});
if (val.empty()) throw JFJochException(JFJochExceptionCategory::HDF5, "Empty array cannot be written");
HDF5DataType datatype(val[0]);
hid_t attr_id = H5Acreate2(id, name.c_str(), datatype.GetID(), dataspace.GetID(), H5P_DEFAULT, H5P_DEFAULT);
herr_t ret = H5Awrite(attr_id, datatype.GetID(), val.data());
H5Aclose(attr_id);
if (ret < 0) throw JFJochException(JFJochExceptionCategory::HDF5, "Atrribute write unsucessful");
return *this;
}
std::vector<double> HDF5Object::ReadAttrVec(const std::string &name) {
hid_t attr_id = H5Aopen(id, name.c_str(), H5P_DEFAULT);
if (attr_id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open attribute " + name);
hid_t space_id = H5Aget_space(attr_id);
if (space_id < 0) {
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot get dataspace for attribute " + name);
}
int ndims = H5Sget_simple_extent_ndims(space_id);
if (ndims != 1) {
H5Sclose(space_id);
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Attribute " + name + " is not a vector");
}
hsize_t dims[1];
H5Sget_simple_extent_dims(space_id, dims, NULL);
std::vector<double> ret(dims[0]);
if (H5Aread(attr_id, H5T_NATIVE_DOUBLE, ret.data()) < 0) {
H5Sclose(space_id);
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot read attribute " + name);
}
H5Sclose(space_id);
H5Aclose(attr_id);
return ret;
}
bool HDF5Object::AttrExists(const std::string &name) {
return H5Aexists(id, name.c_str()) > 0;
}
std::string HDF5Object::ReadAttrStr(const std::string &name) {
hid_t attr_id = H5Aopen(id, name.c_str(), H5P_DEFAULT);
if (attr_id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open attribute " + name);
// Get attribute dataspace
hid_t space_id = H5Aget_space(attr_id);
if (space_id < 0) {
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot get dataspace for attribute " + name);
}
// Check if attribute is scalar
H5S_class_t space_type = H5Sget_simple_extent_type(space_id);
if (space_type != H5S_SCALAR) {
H5Sclose(space_id);
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Attribute " + name + " is not a scalar");
}
H5Sclose(space_id);
// Get attribute type
hid_t type_id = H5Aget_type(attr_id);
if (type_id < 0) {
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot get type for attribute " + name);
}
// Check if attribute is a string type
H5T_class_t type_class = H5Tget_class(type_id);
if (type_class != H5T_STRING) {
H5Tclose(type_id);
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Attribute " + name + " is not a string");
}
// Get string size and read it
size_t size = H5Tget_size(type_id);
std::vector<char> buffer(size + 1, '\0'); // +1 for null terminator to be safe
if (H5Aread(attr_id, type_id, buffer.data()) < 0) {
H5Tclose(type_id);
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot read attribute " + name);
}
H5Tclose(type_id);
H5Aclose(attr_id);
return {buffer.data()};
}
double HDF5Object::ReadAttrDouble(const std::string &name) {
hid_t attr_id = H5Aopen(id, name.c_str(), H5P_DEFAULT);
if (attr_id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open attribute " + name);
hid_t space_id = H5Aget_space(attr_id);
if (space_id < 0) {
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot get dataspace for attribute " + name);
}
H5S_class_t space_type = H5Sget_simple_extent_type(space_id);
if (space_type != H5S_SCALAR) {
H5Sclose(space_id);
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Attribute " + name + " is not a scalar");
}
H5Sclose(space_id);
double value;
if (H5Aread(attr_id, H5T_NATIVE_DOUBLE, &value) < 0) {
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot read attribute " + name);
}
H5Aclose(attr_id);
return value;
}
int64_t HDF5Object::ReadAttrInt(const std::string &name) {
hid_t attr_id = H5Aopen(id, name.c_str(), H5P_DEFAULT);
if (attr_id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open attribute " + name);
hid_t space_id = H5Aget_space(attr_id);
if (space_id < 0) {
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot get dataspace for attribute " + name);
}
H5S_class_t space_type = H5Sget_simple_extent_type(space_id);
if (space_type != H5S_SCALAR) {
H5Sclose(space_id);
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Attribute " + name + " is not a scalar");
}
H5Sclose(space_id);
int64_t value;
if (H5Aread(attr_id, H5T_NATIVE_INT64, &value) < 0) {
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot read attribute " + name);
}
H5Aclose(attr_id);
return value;
}
HDF5Object & HDF5Object::Units(const std::string &val) {
if (!val.empty()) Attr("units", val);
return *this;
}
HDF5Object & HDF5Object::NXClass(const std::string &val) {
if (!val.empty()) Attr("NX_class", val);
return *this;
}
HDF5Object& HDF5Object::Transformation(const std::string& units, const std::string& depends_on,
const std::string& equipment, const std::string& equipment_component,
const std::string& transformation_type, const std::vector<double>& vector) {
if (!units.empty()) Attr("units", units);
if (!depends_on.empty()) Attr("depends_on", depends_on);
if (!equipment.empty()) Attr("equipment", equipment);
if (!equipment_component.empty()) Attr("equipment_component", equipment_component);
if (!transformation_type.empty()) Attr("transformation_type", transformation_type);
Attr("vector", vector);
return *this;
}
HDF5Object& HDF5Object::Transformation(const std::string& units, const std::string& depends_on,
const std::string& equipment, const std::string& equipment_component,
const std::string& transformation_type, const std::vector<double>& vector,
const std::vector<double> &offset, const std::string& offset_units) {
Transformation(units, depends_on, equipment, equipment_component, transformation_type, vector);
Attr("offset", offset);
// An offset is a length, whatever the axis is. Without this attribute a reader falls back to
// `units`, which on a rotation axis is degrees - NXmx readers then try to convert degrees to
// millimetres and raise. Harmless while every offset we write is zero, and wrong the moment one
// is not, so state it always rather than only when a caller remembers to.
Attr("offset_units", offset_units.empty() ? std::string("m") : offset_units);
return *this;
}
HDF5Object& HDF5Object::ExternalLink(std::string filename, const std::string &external_dataset, const std::string &local_name) {
// Linked file is assumed to be in the same directory as current file, so directory information is stripped
int pos = filename.rfind('/');
if (pos != std::string::npos) filename = filename.substr(pos+1);
if (H5Lcreate_external(filename.c_str(),external_dataset.c_str(), GetID(),
local_name.c_str(), H5P_DEFAULT, H5P_DEFAULT) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot create external link in HDF5 file");
return *this;
}
HDF5Object & HDF5Object::HardLink(const std::string &source, const std::string &dest) {
if (H5Lcreate_hard(GetID(), source.c_str(), GetID(), dest.c_str(), H5P_DEFAULT, H5P_DEFAULT) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot create link in HDF5 file");
return *this;
}
std::vector<hsize_t> HDF5Object::GetDimension(const std::string &name) {
HDF5DataSet dataset(*this, name);
HDF5DataSpace dscp(dataset);
return dscp.GetDimensions();
}
std::unique_ptr<HDF5DataSet> HDF5Object::SaveScalar(const std::string &name, const std::string &val) {
return SaveScalar(name, val.c_str());
}
std::unique_ptr<HDF5DataSet> HDF5Object::SaveScalar(const std::string &name, const char *val) {
HDF5DataType data_type(val);
HDF5DataSpace data_space(HDF5DataSpace::Scalar);
auto dataset = std::make_unique<HDF5DataSet>(*this, name, data_type, data_space);
dataset->Write(data_type, val);
return dataset;
}
std::unique_ptr<HDF5DataSet> HDF5Object::SaveVector(const std::string &name, const std::vector<std::string> &val) {
if (val.empty())
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot write empty vector");
int pos = 0;
size_t len = val[0].length();
for (int i = 1; i < val.size(); i++) { if (val[i].length() > len) {pos = i; len = val[i].length();}}
HDF5DataType data_type(val[pos]);
HDF5Dcpl dcpl;
HDF5DataSpace data_space({val.size()});
auto dataset = std::make_unique<HDF5DataSet>(*this, name, data_type, data_space, dcpl);
std::vector<char> buffer((len + 1) * val.size(), '\0');
for (int i = 0; i < val.size(); i++) strncpy(buffer.data() + i * (len+1), val[i].c_str(), len+1);
dataset->Write(data_type, buffer.data());
return dataset;
}
HDF5Group::HDF5Group(const HDF5Object& parent, const std::string &name) : HDF5Group(parent, name.c_str()) {
}
HDF5Group::HDF5Group(const HDF5Object& parent, const char *name) : HDF5Object() {
if (H5Lexists(parent.GetID(), name, H5P_DEFAULT) > 0)
id = H5Gopen(parent.GetID(), name, H5P_DEFAULT);
else
id = H5Gcreate(parent.GetID(), name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open/create HDF5 group " + std::string(name));
}
HDF5Group::~HDF5Group() {
if (id >= 0) {
H5E_BEGIN_TRY {
H5Gclose(id);
} H5E_END_TRY;
id = -1;
}
}
HDF5File::HDF5File(const std::string& filename, bool v1_10) : HDF5Object() {
HDF5Fapl fapl;
H5FD_poison_sec2_init();
H5FD_poison_sec2_set_callback(HDF5PoisonCallback, nullptr);
if (v1_10)
fapl.SetVersionTo1p10orNewer();
fapl.SetCloseStrong();
fapl.SetPoisonSec2();
id = H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, fapl.GetID());
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open/create data HDF5 file " + filename);
}
void HDF5File::Close() {
if (id < 0)
return;
// Invalidate first; if anything below fails we must NOT leave a live id
// behind for the destructor or later code to touch.
const hid_t local_id = id;
id = -1;
H5FD_poison_sec2_begin_fail_pass();
herr_t close_err = 0;
H5E_BEGIN_TRY {
close_err = H5Fclose(local_id);
} H5E_END_TRY;
H5FD_poison_sec2_end_fail_pass();
const char *filename = nullptr;
const char *operation = nullptr;
int error_number = 0;
const int poison_error = H5FD_poison_sec2_get_last_error(
&filename,
&operation,
&error_number
);
if (close_err < 0 || poison_error > 0)
throw JFJochException(
JFJochExceptionCategory::HDF5,
"Failed to close HDF5 file, operation=" +
std::string(operation != nullptr ? operation : "") +
", filename=" +
std::string(filename != nullptr ? filename : "") +
", errno=" +
std::to_string(error_number)
);
}
HDF5File::~HDF5File() {
if (id >= 0) {
const hid_t tmp = id;
id = -1;
H5FD_poison_sec2_begin_fail_pass();
H5E_BEGIN_TRY {
H5Fclose(tmp);
} H5E_END_TRY;
H5FD_poison_sec2_end_fail_pass();
}
}
void HDF5File::Delete(const std::string& path) {
H5Ldelete(id, path.c_str(), H5P_DEFAULT);
}
HDF5ReadOnlyFile::HDF5ReadOnlyFile(const std::string &filename) {
id = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open/create data HDF5 file" + filename);
}
HDF5ReadOnlyFile::~HDF5ReadOnlyFile() {
if (id >= 0) {
H5E_BEGIN_TRY {H5Fclose(id); } H5E_END_TRY;
id = -1;
}
}
HDF5DataSet::HDF5DataSet(const HDF5Object &parent, const std::string &name, const HDF5DataType &data_type,
const HDF5DataSpace &data_space, const HDF5Dcpl &dcpl) : dataset_name(name) {
ndim = data_space.GetNumOfDimensions();
if ((dcpl.GetNumOfDimensions() != 0) && (dcpl.GetNumOfDimensions() != data_space.GetNumOfDimensions()))
throw JFJochException(JFJochExceptionCategory::HDF5, "Inconsistent dimension settings");
id = H5Dcreate2(parent.GetID(), name.c_str(), data_type.GetID(), data_space.GetID(), H5P_DEFAULT, dcpl.GetID(), H5P_DEFAULT);
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot create HDF5 dataset: " + name);
}
HDF5DataSet::HDF5DataSet(const HDF5Object &parent, const std::string &name, const HDF5DataType &data_type,
const HDF5DataSpace &data_space) : dataset_name(name) {
ndim = data_space.GetNumOfDimensions();
id = H5Dcreate2(parent.GetID(), name.c_str(), data_type.GetID(), data_space.GetID(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot create HDF5 dataset " + name);
}
HDF5DataSet::HDF5DataSet(const HDF5Object &parent, const std::string &name) : ndim(0), dataset_name(name) {
id = H5Dopen2(parent.GetID(), name.c_str(), H5P_DEFAULT);
if (id < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open HDF5 dataset " + name);
ndim = HDF5DataSpace(*this).GetNumOfDimensions();
}
void HDF5DataSet::SetExtent(const std::vector<hsize_t> &dims) {
if (dims.size() != ndim)
throw JFJochException(JFJochExceptionCategory::HDF5, "Mismatch in dataset dimensions " + std::to_string(ndim));
herr_t ret = H5Dset_extent(id, dims.data());
if (ret < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Error in H5Dset_extent");
}
HDF5DataSet& HDF5DataSet::Write(const HDF5DataType &data_type, const void *val) {
if (H5Dwrite(id, data_type.GetID(), H5S_ALL, H5S_ALL, H5P_DEFAULT, val) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot write to HDF5 file");
return *this;
}
HDF5DataSet& HDF5DataSet::Flush() {
if (H5Dflush(id) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Error flushing HDF5 dataset");
return *this;
}
HDF5DataSet& HDF5DataSet::WriteDirectChunk(const void *data, hsize_t data_size, const std::vector<hsize_t> &offset) {
if (offset.size() != ndim)
throw JFJochException(JFJochExceptionCategory::HDF5, "Inconsistent dimension settings");
if (H5Dwrite_chunk(id, H5P_DEFAULT, 0, offset.data(), data_size, data) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Error writing dataset to HDF5 file");
return *this;
}
std::string HDF5DataSet::ReadString() const {
HDF5DataSpace file_space(*this);
// A rank-1 dataset holding exactly one string is the same thing as a scalar, and facilities
// write it both ways - JUNGFRAU masters from an early beamline deployment store
// sensor_material and description as shape (1,). Refusing those loses the whole file over a
// difference that carries no information.
if (file_space.GetNumOfDimensions() != 0) {
const auto dims = file_space.GetDimensions();
hsize_t elements = 1;
for (const auto d : dims)
elements *= d;
if (elements != 1)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Dataset tries to read string (scalar) from vector dataset");
}
HDF5DataType file_data_type(*this);
const size_t size = file_data_type.GetElemSize();
if (size == 0)
return "";
std::string buffer(size, '\0');
if (H5Dread(id, file_data_type.GetID(), H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer.data()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
dataset_name + ": read unsuccessful");
const size_t end = buffer.find('\0');
if (end != std::string::npos)
buffer.resize(end);
return buffer;
}
void HDF5DataSet::Close() {
if (id < 0)
return;
const hid_t local_id = id;
id = -1;
herr_t err = 0;
H5E_BEGIN_TRY {
err = H5Dclose(local_id);
} H5E_END_TRY;
if (err < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot close HDF5 dataset");
}
HDF5DataSet::~HDF5DataSet() {
if (id >= 0) {
H5E_BEGIN_TRY {
H5Dclose(id);
} H5E_END_TRY;
id = -1;
}
}
void RegisterHDF5Filter() {
if (H5Zregister(bshuf_H5Filter) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot register Bitshuffle filter");
}
void HDF5Dcpl::SetCompression(CompressionAlgorithm c, size_t block_size) {
constexpr uint32_t cd_nelmts = 2;
unsigned int params[cd_nelmts];
switch (c) {
case CompressionAlgorithm::BSHUF_LZ4:
params[0] = block_size;
params[1] = BSHUF_H5_COMPRESS_LZ4;
if (H5Pset_filter(id, (H5Z_filter_t)BSHUF_H5FILTER, H5Z_FLAG_MANDATORY, cd_nelmts, params) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot set bshuf/lz4 filter");
break;
case CompressionAlgorithm::BSHUF_ZSTD:
case CompressionAlgorithm::BSHUF_ZSTD_RLE:
case CompressionAlgorithm::BSHUF_ZSTD_RLE_HUFF:
#ifdef USE_ZSTD
params[0] = block_size;
params[1] = BSHUF_H5_COMPRESS_ZSTD;
if (H5Pset_filter(id, (H5Z_filter_t)BSHUF_H5FILTER, H5Z_FLAG_MANDATORY, cd_nelmts, params) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot set bshuf/zstd filter");
break;
#else
throw SLSException(SLSExceptionCategory::Compression, "ZSTD support not available");
#endif
case CompressionAlgorithm::NO_COMPRESSION:
break;
default:
throw JFJochException(JFJochExceptionCategory::HDF5, "SetCompression: algorithm not supported");
}
}
CompressionAlgorithm HDF5Dcpl::GetCompression() {
int num_filters = H5Pget_nfilters(id);
if (num_filters > 1)
throw JFJochException(JFJochExceptionCategory::HDF5, "Only single filter supported");
if (num_filters == 0)
return CompressionAlgorithm::NO_COMPRESSION;
uint32_t cd_values[8];
size_t cd_nelemts = sizeof(cd_values);
memset(cd_values, 0, sizeof(cd_values));
const auto filter = H5Pget_filter2(id, 0, nullptr, &cd_nelemts, cd_values, 0, nullptr, nullptr);
// Plain LZ4, no bit shuffle. DECTRIS Eiger firmware 1.x wrote images this way, and those files
// are still deposited; read-only, we never write it. Same block framing as bitshuffle - a 64-bit
// total size, a 32-bit block size, then 32-bit-prefixed blocks - which is why only the unshuffle
// step differs downstream.
if (filter == LZ4_H5FILTER)
return CompressionAlgorithm::LZ4_NO_SHUFFLE;
if (filter != BSHUF_H5FILTER)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Unsupported HDF5 compression filter " + std::to_string(filter)
+ " (supported: bitshuffle " + std::to_string(BSHUF_H5FILTER)
+ ", LZ4 " + std::to_string(LZ4_H5FILTER) + ")");
if (cd_values[4] == BSHUF_H5_COMPRESS_LZ4)
return CompressionAlgorithm::BSHUF_LZ4;
if (cd_values[4] == BSHUF_H5_COMPRESS_ZSTD)
return CompressionAlgorithm::BSHUF_ZSTD;
throw JFJochException(JFJochExceptionCategory::HDF5,"Weird value of parameter " + std::to_string(cd_values[1]));
}
std::string ExtractFilename(const std::string& str) {
std::filesystem::path path(str);
// if (std::filesystem::is_regular_file(path))
return path.filename().string();
}
bool HDF5Object::GetBool(const std::string &name) const {
HDF5DataSet dataset(*this, name);
return dataset.ReadScalar<bool>();
}
std::optional<bool> HDF5Object::GetOptBool(const std::string &name) const {
if (Exists(name))
return GetBool(name);
return std::nullopt;
}
float HDF5Object::GetFloat(const std::string &name) const {
HDF5DataSet dataset(*this, name);
return dataset.ReadScalar<float>();
}
int64_t HDF5Object::GetInt(const std::string &name) const {
HDF5DataSet dataset(*this, name);
return dataset.ReadScalar<int64_t>();
}
std::optional<float> HDF5Object::GetOptFloat(const std::string &name) const {
if (Exists(name))
return GetFloat(name);
return std::nullopt;
}
std::optional<int64_t> HDF5Object::GetOptInt(const std::string &name) const {
if (Exists(name))
return GetInt(name);
return std::nullopt;
}
std::string HDF5Object::GetString(const std::string &name, const std::string &def) const {
if (Exists(name)) {
HDF5DataSet dataset(*this, name);
return dataset.ReadString();
}
return def;
}
bool HDF5Object::Exists(const std::string &name) const {
H5E_BEGIN_TRY {
// The LINK has to be there and the object it names has to be reachable. Those are two
// different questions for an external link, and every DECTRIS Eiger master asks the second
// one: it links saturation_value, pixel_mask, bit_depth_readout and serial_number into a
// companion <prefix>_meta.h5 that is routinely not kept when a dataset is archived or
// deposited. H5Lexists then says yes - the link is written in the master - and the read that
// follows throws, which turns every optional-field guard in the reader into a hard failure
// and refuses the whole file over a value it was prepared to do without.
if (H5Lexists(GetID(), name.c_str(), H5P_DEFAULT) <= 0)
return false;
return H5Oexists_by_name(GetID(), name.c_str(), H5P_DEFAULT) > 0;
} H5E_END_TRY;
return false;
}
bool HDF5Object::IsDataSet(const std::string &name) const {
hid_t dataset_id;
H5E_BEGIN_TRY {
dataset_id = H5Dopen(GetID(), name.c_str(), H5P_DEFAULT);
} H5E_END_TRY;
if (dataset_id < 0)
return false;
H5Dclose(dataset_id);
return true;
}
herr_t func(hid_t group, const char *name, const H5L_info2_t *info, void *op_data) {
auto ret = reinterpret_cast<std::vector<std::string> *>(op_data);
ret->emplace_back(name);
return 0;
}
std::vector<std::string> HDF5Object::FindLeafs(const std::string &name) const {
hid_t gid = H5Gopen(id, name.c_str(), H5P_DEFAULT);
if (gid < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot open HDF5 group " + name);
std::vector<std::string> ret;
hsize_t idx = 0;
H5Literate(gid, H5_INDEX_NAME, H5_ITER_INC, &idx, func, &ret);
H5Gclose(gid);
return ret;
}
bool HDF5Object::IsExternalLink(const std::string& name) const {
H5L_info2_t link_info;
// Get information about the link
if (H5Lget_info(id, name.c_str(), &link_info, H5P_DEFAULT) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Failed to retrieve information about the link");
return (link_info.type == H5L_TYPE_EXTERNAL);
}
std::pair<std::string, std::string> HDF5Object::GetLinkedTarget(const std::string& name) const {
H5L_info2_t link_info;
// Get information about the link
if (H5Lget_info(id, name.c_str(), &link_info, H5P_DEFAULT) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Failed to retrieve information about the link");
if (link_info.type != H5L_TYPE_EXTERNAL)
throw JFJochException(JFJochExceptionCategory::HDF5,
"This is not an external link");
std::vector<char> link_target(link_info.u.val_size + 2, '\0');
if (H5Lget_val(id,
name.c_str(),
link_target.data(),
link_info.u.val_size + 1, H5P_DEFAULT) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Failed to get link location");
const char *target_file_name = nullptr; // To store unpacked file name
const char *target_object_path = nullptr; // To store unpacked object path
// Unpack the external link's value
if (H5Lunpack_elink_val(link_target.data(),
link_info.u.val_size,
nullptr,
&target_file_name,
&target_object_path) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Failed to get link location");
return {target_file_name, target_object_path};
}
std::string HDF5Object::GetLinkedFileName(const std::string& name) const {
return GetLinkedTarget(name).first;
}
namespace {
std::vector<hsize_t> GetDataspaceDimensions(hid_t dataspace_id) {
const int ndims = H5Sget_simple_extent_ndims(dataspace_id);
if (ndims < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot read dataspace dimensions");
std::vector<hsize_t> dims(ndims);
if (ndims > 0 && H5Sget_simple_extent_dims(dataspace_id, dims.data(), nullptr) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot read dataspace dimensions");
return dims;
}
void GetRegularSelection(hid_t dataspace_id,
std::vector<hsize_t> &start,
std::vector<hsize_t> &stride,
std::vector<hsize_t> &count,
std::vector<hsize_t> &block) {
const auto dims = GetDataspaceDimensions(dataspace_id);
const H5S_sel_type selection_type = H5Sget_select_type(dataspace_id);
if (selection_type == H5S_SEL_ALL) {
start.clear();
stride.clear();
count.clear();
block.clear();
return;
}
start.assign(dims.size(), 0);
stride.assign(dims.size(), 1);
count.assign(dims.size(), 1);
block = dims;
if (selection_type != H5S_SEL_HYPERSLABS)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Only regular hyperslab VDS selections are supported");
if (H5Sis_regular_hyperslab(dataspace_id) <= 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Only regular hyperslab VDS selections are supported");
if (H5Sget_regular_hyperslab(dataspace_id,
start.data(),
stride.data(),
count.data(),
block.data()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot decode VDS hyperslab selection");
}
bool ContainsInRegularHyperslab(hsize_t value,
hsize_t start,
hsize_t stride,
hsize_t count,
hsize_t block) {
for (hsize_t i = 0; i < count; i++) {
const hsize_t block_start = start + i * stride;
if ((value >= block_start) && (value < block_start + block))
return true;
}
return false;
}
hsize_t IndexInRegularHyperslab(hsize_t value,
hsize_t start,
hsize_t stride,
hsize_t count,
hsize_t block) {
for (hsize_t i = 0; i < count; i++) {
const hsize_t block_start = start + i * stride;
if ((value >= block_start) && (value < block_start + block))
return i * block + (value - block_start);
}
throw JFJochException(JFJochExceptionCategory::HDF5,
"Image is not part of VDS hyperslab");
}
hsize_t ValueFromRegularHyperslabIndex(hsize_t index,
hsize_t start,
hsize_t stride,
hsize_t count,
hsize_t block) {
if (index >= count * block)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Source image is outside of VDS source hyperslab");
const hsize_t block_number = index / block;
const hsize_t in_block = index % block;
return start + block_number * stride + in_block;
}
}
bool HDF5VirtualDatasetMapping::ContainsVirtualImage(hsize_t image_number) const {
if (virtual_start.empty() || virtual_stride.empty() || virtual_count.empty() || virtual_block.empty())
return false;
return ContainsInRegularHyperslab(image_number,
virtual_start[0],
virtual_stride[0],
virtual_count[0],
virtual_block[0]);
}
hsize_t HDF5VirtualDatasetMapping::SourceImage(hsize_t image_number) const {
if (!ContainsVirtualImage(image_number))
throw JFJochException(JFJochExceptionCategory::HDF5,
"Image is outside of VDS mapping");
const hsize_t source_index = IndexInRegularHyperslab(image_number,
virtual_start[0],
virtual_stride[0],
virtual_count[0],
virtual_block[0]);
if (source_start.empty() || source_stride.empty() || source_count.empty() || source_block.empty())
return source_index;
return ValueFromRegularHyperslabIndex(source_index,
source_start[0],
source_stride[0],
source_count[0],
source_block[0]);
}
std::vector<HDF5VirtualDatasetMapping> HDF5Dcpl::GetVirtualMappings() const {
size_t mapping_count = 0;
if (H5Pget_virtual_count(id, &mapping_count) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot get number of VDS mappings");
if (mapping_count < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot get number of VDS mappings");
std::vector<HDF5VirtualDatasetMapping> ret;
ret.reserve(static_cast<size_t>(mapping_count));
for (size_t i = 0; i < static_cast<size_t>(mapping_count); i++) {
HDF5VirtualDatasetMapping mapping;
const ssize_t filename_size = H5Pget_virtual_filename(id, i, nullptr, 0);
if (filename_size < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot get VDS source filename size");
std::vector<char> filename(filename_size + 1, '\0');
if (H5Pget_virtual_filename(id, i, filename.data(), filename.size()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot get VDS source filename");
mapping.filename = filename.data();
const ssize_t dataset_size = H5Pget_virtual_dsetname(id, i, nullptr, 0);
if (dataset_size < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot get VDS source dataset size");
std::vector<char> dataset(dataset_size + 1, '\0');
if (H5Pget_virtual_dsetname(id, i, dataset.data(), dataset.size()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot get VDS source dataset");
mapping.dataset = dataset.data();
const hid_t virtual_space = H5Pget_virtual_vspace(id, i);
if (virtual_space < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot get VDS virtual dataspace");
const hid_t source_space = H5Pget_virtual_srcspace(id, i);
if (source_space < 0) {
H5Sclose(virtual_space);
throw JFJochException(JFJochExceptionCategory::HDF5,
"Cannot get VDS source dataspace");
}
try {
GetRegularSelection(virtual_space,
mapping.virtual_start,
mapping.virtual_stride,
mapping.virtual_count,
mapping.virtual_block);
GetRegularSelection(source_space,
mapping.source_start,
mapping.source_stride,
mapping.source_count,
mapping.source_block);
} catch (...) {
H5Sclose(source_space);
H5Sclose(virtual_space);
throw;
}
H5Sclose(source_space);
H5Sclose(virtual_space);
ret.emplace_back(std::move(mapping));
}
return ret;
}