reader: take the image orientation from the module directions the NXmx file states
NXmx says how the stored image sits in the detector plane, in the NXdetector_module fast_pixel_direction and slow_pixel_direction vectors. rugnux read neither, and assumed every detector was mounted the way this system mounts its own. A facility that bolts its detector a quarter turn round therefore produced a geometry that was wrong by 90 degrees, which no amount of refinement recovers: the run indexed nothing and reported that it could find no lattice. The two vectors are read, taken from McStas into the internal frame, and matched against the eight discrete image orientations. An exact match is adopted; anything else is left alone, because an orientation that is not discrete is a continuous rotation of the detector in its own plane and cannot be told apart from the tilt by looking at the module. Every file examined here is exactly discrete. Measured over the 94-dataset battery: 78 masters state no module vectors at all and 12 state the standard ones, so the change can reach exactly 4. Three of those go from "Nothing was integrated" to a complete merged result with no flags - 43/93/92% indexed, CC(1/2) 0.957/0.984/0.998 - and are the sets that until now needed a hand-passed --rot3 pi/2. The fourth states a half turn, which the existing rotation-axis sign rescue already absorbed, and is unchanged to every digit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lc5JG6kJqZoCWaoZ43JGTW
This commit is contained in:
@@ -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<DetectorOrientation> 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<gemmi::CrystalSystem, char> 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"))
|
||||
|
||||
Reference in New Issue
Block a user