// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include "NXmxModules.h" #include #include "../common/JFJochException.h" namespace { // A translation dataset as a vector in pixels of the internal frame (x = column, y = row, z = beam). Coord ReadTranslation_px(HDF5Object &file, const std::string &path, double pixel_size_m, const RotMatrix &delta) { HDF5DataSet ds(file, path); std::vector value; ds.ReadVector(value); const auto vec = ds.ReadAttrVec("vector"); if (value.empty() || vec.size() != 3) throw JFJochException(JFJochExceptionCategory::HDF5, path + ": not a translation"); double scale = 1.0; if (ds.AttrExists("units")) { const std::string units = ds.ReadAttrStr("units"); if (units == "mm") scale = 1e-3; else if (units == "um" || units == "microns") scale = 1e-6; } const double len_px = value[0] * scale / pixel_size_m; // McStas -> internal is a half turn about z; the discrete image orientation is undone after it. const Coord mcstas(static_cast(-vec[0]), static_cast(-vec[1]), static_cast(vec[2])); return delta.transpose() * (static_cast(len_px) * mcstas); } } // namespace std::string NXmxModuleGroup(HDF5Object &file, size_t n) { // The index is written zero-padded to the width of the last one (module_00 ... module_15); the width is // the one module 0 has. for (size_t digits = 1; digits <= 3; digits++) { const std::string first = "/entry/instrument/detector/module_" + std::string(digits - 1, '0') + "0/"; if (file.Exists(first + "data_origin")) { std::string index = std::to_string(n); if (index.size() < digits) index.insert(0, digits - index.size(), '0'); const std::string base = "/entry/instrument/detector/module_" + index + "/"; return file.Exists(base + "data_origin") ? base : ""; } } return ""; } DetectorModules ReadNXmxModules(HDF5Object &file, double pixel_size_m, const DetectorOrientation &orientation) { const RotMatrix delta = orientation.Matrix(); std::vector out; for (size_t n = 0;; n++) { const std::string base = NXmxModuleGroup(file, n); if (base.empty()) break; const auto origin = file.ReadVector(base + "data_origin"); const auto size = file.ReadVector(base + "data_size"); if (origin.size() != 2 || size.size() != 2) throw JFJochException(JFJochExceptionCategory::HDF5, base + ": only two-dimensional modules are read"); const Coord o = ReadTranslation_px(file, base + "module_offset", pixel_size_m, delta); const Coord f = ReadTranslation_px(file, base + "fast_pixel_direction", pixel_size_m, delta); const Coord s = ReadTranslation_px(file, base + "slow_pixel_direction", pixel_size_m, delta); if (std::fabs(o.z) > 1e-3 || std::fabs(f.z) > 1e-6 || std::fabs(s.z) > 1e-6) throw JFJochException(JFJochExceptionCategory::HDF5, base + ": a module out of the detector plane is not read"); DetectorModule m; m.y0 = origin[0]; m.x0 = origin[1]; m.height = size[0]; m.width = size[1]; m.ox = o.x; m.oy = o.y; m.fx = f.x; m.fy = f.y; m.sx = s.x; m.sy = s.y; // The readout axes, where the writer says (writer/NXmxModules.cpp); else the module's own array. if (file.Exists(base + "readout_fast_axis") && file.Exists(base + "readout_slow_axis")) { m.fast = DirectionFromName(file.GetString(base + "readout_fast_axis")); m.slow = DirectionFromName(file.GetString(base + "readout_slow_axis")); } out.push_back(m); } if (out.size() < 2) return {}; return DetectorModules(out); }