diff --git a/reader/HDF5MetadataSource.cpp b/reader/HDF5MetadataSource.cpp index a99e92f5c..f6c35c52d 100644 --- a/reader/HDF5MetadataSource.cpp +++ b/reader/HDF5MetadataSource.cpp @@ -14,6 +14,43 @@ #include "../common/Logger.h" #include "../common/ROIDefinition.h" +// The image orientation the file itself states, in its NXdetector_module pixel directions. NXmx gives +// those in the McStas frame, which is the internal frame turned 180 degrees about z. +// +// Only an exact match against one of the eight discrete orientations is taken. Anything else is a +// continuous rotation of the detector in its own plane, which belongs in rot1/rot2/rot3 and cannot be +// separated from the tilt by looking at the module alone - so it is left as it is rather than +// approximated. Every real file examined here is exactly discrete. +static std::optional ReadModuleOrientation(HDF5Object *file) { + const std::string base = "/entry/instrument/detector/module/"; + if (!file->IsDataSet(base + "fast_pixel_direction") || !file->IsDataSet(base + "slow_pixel_direction")) + return {}; + + HDF5DataSet fast_dataset(*file, base + "fast_pixel_direction"); + HDF5DataSet slow_dataset(*file, base + "slow_pixel_direction"); + if (!fast_dataset.AttrExists("vector") || !slow_dataset.AttrExists("vector")) + return {}; + + const auto f = fast_dataset.ReadAttrVec("vector"); + const auto s = slow_dataset.ReadAttrVec("vector"); + if ((f.size() != 3) || (s.size() != 3)) + return {}; + + const Coord fast(-f[0], -f[1], f[2]); + const Coord slow(-s[0], -s[1], s[2]); + + for (int64_t quarter_turns = 0; quarter_turns < 4; quarter_turns++) { + for (bool mirror_y: {false, true}) { + const DetectorOrientation orientation(mirror_y, quarter_turns); + const RotMatrix m = orientation.Matrix(); + if (((m.Column(0) - fast).Length() < 1e-3f) && ((m.Column(1) - slow).Length() < 1e-3f)) + return orientation; + } + } + return {}; +} + + inline std::pair parse_bravais_lattice(const std::string &val) { if (val.empty()) return {gemmi::CrystalSystem::Triclinic, 'P'}; @@ -821,13 +858,17 @@ HDF5MetadataSource::OpenResult HDF5MetadataSource::Open(const std::string &filen // How the stored image sits in the detector plane. A different setting from mirror_y above, // recorded separately by the writer; absence means the identity, which is what a file written // before it existed - or by anything else - describes. - detector.ImageOrientation(DetectorOrientation( - master_file->GetOptBool( - "/entry/instrument/detector/detectorSpecific/detector_orientation_mirror_y") - .value_or(false), - master_file->GetOptInt( - "/entry/instrument/detector/detectorSpecific/detector_orientation_quarter_turns") - .value_or(0))); + // NXmx states this in the module's pixel directions, which is where it is read from first; + // detectorSpecific carries the same setting for a file this system wrote, and is the fallback + // for one whose module group says nothing usable. Absence of both means the identity. + detector.ImageOrientation(ReadModuleOrientation(master_file.get()).value_or( + DetectorOrientation( + master_file->GetOptBool( + "/entry/instrument/detector/detectorSpecific/detector_orientation_mirror_y") + .value_or(false), + master_file->GetOptInt( + "/entry/instrument/detector/detectorSpecific/detector_orientation_quarter_turns") + .value_or(0)))); // Sensor thickness/material drive the parallax/absorption model, so take them from the file // rather than the DetectorSetup default (NXmx stores thickness in metres). if (master_file->Exists("/entry/instrument/detector/sensor_thickness"))