A valid NXmx master written outside the DECTRIS toolchain could not be opened. Measured on a Diamond-written master of a 360 deg EIGER 16M sweep, where the images and the meta file are pure DECTRIS and only the master is third-party - which is why the two sides disagree on units at all. Five independent things, of which two were silent: * The image size came from detectorSpecific/x_pixels_in_detector, a DECTRIS extension rather than NXmx, so a third-party writer has no reason to emit it. It now comes from the image array's own shape, as it already did for a VDS master. * Lengths were assumed to be metres and the units attribute was never read. A pixel size, sensor thickness or distance stated in millimetres - correct NXmx - was silently a factor of a thousand out. The unit is now read; an undeclared one still means metres, an unknown one is refused. * The detector distance can sit in NXinstrument rather than in NXdetector; that is now the last fallback after the NXmx and the firmware-1.x spellings. * A pixel mask that is an external link into a file not holding it passed the Exists() check and then threw on the open. Whether the array is there is now decided by opening it. * Each data file was re-opened and searched for /entry/data/data, ignoring the path the master's own link names. A master linking to a plain /data therefore found no images at all - and that was a warning and exit code 0 over a sweep sitting right there, not an error. The link is now taken at its word, and a master that links to data files but yields no images is an error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3yNBXk4wKdMZy1ak2NY7f
79 lines
3.9 KiB
C++
79 lines
3.9 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "../writer/HDF5Objects.h" // HDF5ReadOnlyFile, HDF5VirtualDatasetMapping, HDF5DataSetLayout
|
|
#include "../common/JFJochMessages.h" // FileWriterFormat, HDF5DataSourceMessage
|
|
|
|
// Turns a global image number into the HDF5 file + local index that physically holds its pixels,
|
|
// for all three on-disk layouts (legacy linked data files, VDS, contiguous/integrated). This is
|
|
// the part of the reader whose "links to files stay" constant: it knows where the raw images
|
|
// live, independent of which master file the per-image metadata is read from.
|
|
//
|
|
// Open data-file handles are cached, so scanning many images (e.g. reprocessing) does not reopen
|
|
// the same file on every read. HDF5 is not thread-safe, so every call must be made with the
|
|
// global hdf5_mutex held by the caller; the locator does no locking of its own.
|
|
class HDF5ImageLocator {
|
|
public:
|
|
struct Location {
|
|
std::shared_ptr<HDF5ReadOnlyFile> file;
|
|
uint32_t local_index = 0;
|
|
// Path the file was opened from. Needed to open it a second time as a plain file, for the
|
|
// positional reads HDF5ImageSource does outside the mutex.
|
|
std::string path;
|
|
// Where the images sit INSIDE that file. /entry/data/data everywhere DECTRIS writes, but a
|
|
// VDS names its source dataset and is free to name another one, so take it at its word.
|
|
std::string dataset = "/entry/data/data";
|
|
};
|
|
|
|
// One data file of a legacy multi-file dataset, with the dataset the master's link names
|
|
// inside it - the same "take the link at its word" the VDS branch already does.
|
|
struct LegacyFile {
|
|
std::string path;
|
|
std::string dataset;
|
|
};
|
|
|
|
// Layout description, filled by the reader once the master file has been parsed. All paths
|
|
// are absolute: legacy data files and VDS mapping filenames are resolved relative to the
|
|
// master before being handed over, so the locator never deals with relative paths.
|
|
struct Layout {
|
|
FileWriterFormat format = FileWriterFormat::NoFile;
|
|
HDF5DataSetLayout data_layout = HDF5DataSetLayout::CONTIGUOUS;
|
|
std::shared_ptr<HDF5ReadOnlyFile> master_file;
|
|
std::string master_filename;
|
|
std::vector<LegacyFile> legacy_files;
|
|
size_t images_per_file = 1;
|
|
std::vector<HDF5VirtualDatasetMapping> vds_mappings;
|
|
};
|
|
|
|
void Configure(Layout layout);
|
|
void Clear();
|
|
|
|
// Resolve a global image number to {file, local index}. Throws if the image is not covered
|
|
// by the layout. Does not bounds-check against the total image count - the caller does that.
|
|
Location Resolve(int64_t global_image) const;
|
|
|
|
// Source mapping for re-writing a derived file (e.g. _process.h5) so it links back to the
|
|
// original pixel sources rather than to a master. total_images is supplied by the caller.
|
|
// stride is the step between consecutive images of the derived file in the SOURCE: image i of the
|
|
// output comes from source image first_image + i * stride. It has to match the stride the caller
|
|
// processed with, or the pictures and the per-image analysis in the derived file describe
|
|
// different frames.
|
|
std::vector<HDF5DataSourceMessage> GetSourceMapping(uint64_t first_image,
|
|
std::optional<uint64_t> image_count,
|
|
uint64_t total_images,
|
|
uint64_t stride = 1) const;
|
|
|
|
private:
|
|
Layout layout_;
|
|
mutable std::map<std::string, std::shared_ptr<HDF5ReadOnlyFile> > file_cache_;
|
|
std::shared_ptr<HDF5ReadOnlyFile> OpenCached(const std::string &path) const;
|
|
};
|