Build Packages / Unit tests (push) Successful in 1h22m15s
Build Packages / build:windows:nocuda (push) Successful in 18m0s
Build Packages / build:windows:cuda (push) Successful in 20m30s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m39s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m55s
Build Packages / build:rugnux:windows (push) Successful in 11m25s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 20m6s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m27s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 15m34s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m25s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8) (push) Successful in 17m43s
Build Packages / build:rpm (rocky9) (push) Successful in 13m34s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 21m28s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m19s
Build Packages / DIALS test (push) Successful in 12m36s
Build Packages / XDS test (durin plugin) (push) Successful in 6m56s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m48s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m7s
Build Packages / Generate python client (push) Successful in 11s
Build Packages / Build documentation (push) Successful in 36s
Build Packages / Create release (push) Skipped
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m11s
* `rugnux --mode calibration` writes `<prefix>.json` beside the `.poni`, whose `dataset_settings` member is a `jfjoch_broker` `dataset_settings` body as it stands. * `rugnux` and `jfjoch_viewer` read PILATUS miniCBF sweeps natively, without conversion. * Masters written by other facilities open, including Eiger 1.x and third-party NXmx variants. * `rugnux` measures the beam centre on every run, and indexes with it when the file's value indexes nothing. * A detector swung out on a 2theta arm is placed where the file says it stands, and the calibration can hold the tilt fixed. * `rugnux` writes the unmerged MTZ by default, and a P1 merge beside it, so a wrong space group can be re-merged without reprocessing. * Significant improvements to symmetry handling in `rugnux`: the lattice, the point group, the setting and the systematic absences. * The `rugnux` report gives the resolution the CC1/2 fit reached, beside the range the reflections were written to. * The `rugnux` report gives the twinning statistics measured before the space group was decided, beside the ones measured after. * The `rugnux` report gives the strong-direction diffraction limit, and warns when CC1/2 is not monotone with resolution. * `rugnux` ranks screw axes on the evidence their absences carry, rather than on how many control reflections a candidate happens to have. * Twinning is no longer reported when the L-test contradicts it. * The `rugnux` report gives the detector tilt, the measured tilt and the direct beam beside the beam centre, and a post-refined beam centre is judged against the run's own measurement rather than the file's. * `--no-refine-tilt` holds the detector tilt at the value in the file, instead of zeroing it, when the calibration starts from the spots. * The `jfjoch_viewer` grid scan view draws the cells in the proportion of the scan steps, so the map has the shape of the scanned area. Reviewed-on: #76 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
175 lines
7.8 KiB
C++
175 lines
7.8 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "HDF5ImageLocator.h"
|
|
#include "../common/JFJochException.h"
|
|
|
|
namespace {
|
|
// Coalesce consecutive single-image mappings into one contiguous range when the source and
|
|
// virtual images stay contiguous in the same file/dataset.
|
|
void AppendOrExtendSourceMapping(std::vector<HDF5DataSourceMessage> &ret,
|
|
const std::string &filename,
|
|
const std::string &dataset,
|
|
uint64_t source_first_image,
|
|
uint64_t virtual_first_image,
|
|
uint64_t image_count) {
|
|
if (image_count == 0)
|
|
return;
|
|
|
|
if (!ret.empty()) {
|
|
auto &last = ret.back();
|
|
if (last.filename == filename
|
|
&& last.dataset == dataset
|
|
&& last.source_first_image + last.image_count == source_first_image
|
|
&& last.virtual_first_image + last.image_count == virtual_first_image) {
|
|
last.image_count += image_count;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ret.push_back(HDF5DataSourceMessage{
|
|
.filename = filename,
|
|
.dataset = dataset,
|
|
.source_first_image = source_first_image,
|
|
.virtual_first_image = virtual_first_image,
|
|
.image_count = image_count
|
|
});
|
|
}
|
|
}
|
|
|
|
void HDF5ImageLocator::Configure(Layout layout) {
|
|
file_cache_.clear();
|
|
layout_ = std::move(layout);
|
|
}
|
|
|
|
void HDF5ImageLocator::Clear() {
|
|
file_cache_.clear();
|
|
layout_ = Layout{};
|
|
}
|
|
|
|
std::shared_ptr<HDF5ReadOnlyFile> HDF5ImageLocator::OpenCached(const std::string &path) const {
|
|
auto it = file_cache_.find(path);
|
|
if (it != file_cache_.end())
|
|
return it->second;
|
|
auto file = std::make_shared<HDF5ReadOnlyFile>(path);
|
|
file_cache_[path] = file;
|
|
return file;
|
|
}
|
|
|
|
HDF5ImageLocator::Location HDF5ImageLocator::Resolve(int64_t global_image) const {
|
|
if (global_image < 0)
|
|
throw JFJochException(JFJochExceptionCategory::HDF5, "Image out of bounds");
|
|
|
|
if (layout_.format == FileWriterFormat::NXmxLegacy) {
|
|
const uint32_t file_id = global_image / layout_.images_per_file;
|
|
const uint32_t local_index = global_image % layout_.images_per_file;
|
|
const auto &data_file = layout_.legacy_files.at(file_id);
|
|
return {OpenCached(data_file.path), local_index, data_file.path, data_file.dataset};
|
|
}
|
|
|
|
if (layout_.format == FileWriterFormat::NXmxVDS
|
|
&& layout_.data_layout == HDF5DataSetLayout::VIRTUAL) {
|
|
const auto image = static_cast<hsize_t>(global_image);
|
|
for (const auto &mapping: layout_.vds_mappings) {
|
|
if (!mapping.ContainsVirtualImage(image))
|
|
continue;
|
|
return {OpenCached(mapping.filename), static_cast<uint32_t>(mapping.SourceImage(image)),
|
|
mapping.filename, mapping.dataset};
|
|
}
|
|
throw JFJochException(JFJochExceptionCategory::HDF5,
|
|
"Image not covered by /entry/data/data VDS mappings");
|
|
}
|
|
|
|
// Contiguous / integrated: pixels live in the master file at the global index.
|
|
if (!layout_.master_file)
|
|
throw JFJochException(JFJochExceptionCategory::HDF5, "Master file not loaded");
|
|
return {layout_.master_file, static_cast<uint32_t>(global_image), layout_.master_filename};
|
|
}
|
|
|
|
std::vector<HDF5DataSourceMessage> HDF5ImageLocator::GetSourceMapping(uint64_t first_image,
|
|
std::optional<uint64_t> image_count,
|
|
uint64_t total_images,
|
|
uint64_t stride) const {
|
|
if (stride == 0)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Image stride cannot be zero");
|
|
|
|
if (first_image > total_images)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"First image outside dataset range");
|
|
|
|
// With a stride the caller's count is the number of OUTPUT images, which spans
|
|
// (count - 1) * stride + 1 source images - not count of them.
|
|
const uint64_t requested_count =
|
|
image_count.value_or((total_images - first_image + stride - 1) / stride);
|
|
if (requested_count > 0 && first_image + (requested_count - 1) * stride >= total_images)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Requested image range outside dataset range");
|
|
|
|
std::vector<HDF5DataSourceMessage> ret;
|
|
if (requested_count == 0)
|
|
return ret;
|
|
|
|
// Integrated / contiguous source: link directly to the original master file.
|
|
if (layout_.format == FileWriterFormat::NXmxVDS && layout_.data_layout != HDF5DataSetLayout::VIRTUAL) {
|
|
// One extendable run when the images are consecutive; strided output needs one mapping per image,
|
|
// which AppendOrExtendSourceMapping leaves separate because the source indices are not adjacent.
|
|
for (uint64_t local_image = 0; local_image < requested_count; ++local_image)
|
|
AppendOrExtendSourceMapping(ret, layout_.master_filename, "/entry/data/data",
|
|
first_image + local_image * stride, local_image, 1);
|
|
return ret;
|
|
}
|
|
|
|
// VDS source: expand VDS mappings to original source files, not to the VDS master.
|
|
if (layout_.format == FileWriterFormat::NXmxVDS && layout_.data_layout == HDF5DataSetLayout::VIRTUAL) {
|
|
for (uint64_t local_image = 0; local_image < requested_count; ++local_image) {
|
|
const hsize_t virtual_image = first_image + local_image * stride;
|
|
|
|
bool found = false;
|
|
for (const auto &mapping: layout_.vds_mappings) {
|
|
if (!mapping.ContainsVirtualImage(virtual_image))
|
|
continue;
|
|
|
|
const uint64_t source_image = mapping.SourceImage(virtual_image);
|
|
const std::string dataset = mapping.dataset.empty() ? "/entry/data/data" : mapping.dataset;
|
|
|
|
AppendOrExtendSourceMapping(ret, mapping.filename, dataset, source_image, local_image, 1);
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
if (!found)
|
|
throw JFJochException(JFJochExceptionCategory::HDF5,
|
|
"Image not covered by /entry/data/data VDS mappings");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Legacy source: link directly to the linked data files.
|
|
if (layout_.format == FileWriterFormat::NXmxLegacy) {
|
|
if (layout_.images_per_file == 0)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Cannot generate HDF5 source mapping: images_per_file is zero");
|
|
|
|
for (uint64_t local_image = 0; local_image < requested_count; ++local_image) {
|
|
const uint64_t source_global_image = first_image + local_image * stride;
|
|
const uint64_t file_id = source_global_image / layout_.images_per_file;
|
|
const uint64_t source_image = source_global_image % layout_.images_per_file;
|
|
|
|
if (file_id >= layout_.legacy_files.size())
|
|
throw JFJochException(JFJochExceptionCategory::HDF5,
|
|
"Legacy image source file missing");
|
|
|
|
const auto &data_file = layout_.legacy_files.at(file_id);
|
|
AppendOrExtendSourceMapping(ret, data_file.path, data_file.dataset,
|
|
source_image, local_image, 1);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Unsupported HDF5 file layout for source mapping");
|
|
}
|