Export the detector tilt correctly in the NXmx transformation chain
Build Packages / build:windows:nocuda (push) Successful in 11m32s
Build Packages / build:windows:cuda (push) Successful in 14m21s
Build Packages / build:viewer-tgz:cpu (push) Successful in 12m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 14m42s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 17m0s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 19m48s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 19m48s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m25s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 19m56s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 18m14s
Build Packages / build:rpm (rocky8) (push) Successful in 21m13s
Build Packages / build:rpm (rocky9) (push) Successful in 18m51s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 20m20s
Build Packages / XDS test (durin plugin) (push) Successful in 8m53s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 16m22s
Build Packages / Generate python client (push) Successful in 19s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 1m1s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 8m46s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m6s
Build Packages / DIALS test (push) Successful in 16m54s
Build Packages / Unit tests (push) Successful in 2h37m13s
Build Packages / build:windows:nocuda (push) Successful in 11m32s
Build Packages / build:windows:cuda (push) Successful in 14m21s
Build Packages / build:viewer-tgz:cpu (push) Successful in 12m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 14m42s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 17m0s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 19m48s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 19m48s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m25s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 19m56s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 18m14s
Build Packages / build:rpm (rocky8) (push) Successful in 21m13s
Build Packages / build:rpm (rocky9) (push) Successful in 18m51s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 20m20s
Build Packages / XDS test (durin plugin) (push) Successful in 8m53s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 16m22s
Build Packages / Generate python client (push) Successful in 19s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 1m1s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 8m46s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m6s
Build Packages / DIALS test (push) Successful in 16m54s
Build Packages / Unit tests (push) Successful in 2h37m13s
Three independent errors, all invisible while the tilt is zero - which it is in every test and every CI file, and which is why this survived. 1. rot1 and rot2 carried each other's axis. Jungfraujoch holds the tilt in the PyFAI PONI convention, poni_rot = Rz(-rot3)*Rx(-rot2)*Ry(+rot1), written in the internal frame (x along increasing column, y along increasing row, z along the beam). NXmx uses McStas, which is that frame turned 180 degrees about x - a proper rotation, NOT a mirror - so rotations about y and z reverse sense and those about x do not. Correct vectors are rot1 (0,-1,0), rot2 (1,0,0), rot3 (0,0,-1); only rot3 was already right. 2. The depends_on chain composed the rotations in the reverse order. A chain applies the deepest dependency first, so rot3 has to sit at the root for the product to be R_rot3*R_rot2*R_rot1. Second-order: it only shows up when two angles are non-zero at once. 3. The tilt pivoted about the wrong point. With translation at the root, a reader takes the panel origin as the unrotated vector and merely reorients the panel, while Jungfraujoch rotates the whole sample->pixel vector including the distance. Moving translation inside the rotations fixes the pivot; this was the largest of the three and is invisible to any test that only checks axes. Verified against DIALS 3.27 by comparing the lab position of nine pixels spread over the detector against poni_rot applied to the sample->pixel vector, over nine tilt settings including combined and mixed-sign angles: max error 1.6e-6 mm, which is float32 rounding of the stored metadata. The shipped encoding gives 40.7 mm - 542 pixels - at a (0.2, 0.3, 0.15) rad tilt. Note when comparing by hand that dxtbx applies a parallax correction in its pixel->mm conversion, which Jungfraujoch does not model; get_pixel_lab_coord is therefore not the right comparison point and shows a ~0.1 mm radius-dependent offset that is not a geometry error. Jungfraujoch's own reader is unaffected: it reads rot1/rot2/rot3 by dataset path and never consults the vectors or the chain, which is why the round trip stayed self-consistent throughout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ This is an UNSTABLE release. It includes many experimental features, as well as
|
||||
* HDF5: NXmx `underload_value`, the lowest valid pixel value, is now written.
|
||||
* A DECTRIS detector sending signed images is no longer declared unsigned in the image stream and in HDF5.
|
||||
* rugnux: `_process.h5` now describes the pixel format of the images it links to, instead of the container rugnux processes in.
|
||||
* HDF5: the detector tilt (`rot1`/`rot2`/`rot3`) is now exported correctly in the NXmx transformation chain; a tilted geometry was previously written so that other programs placed the detector wrongly (tens of mm at a few degrees of tilt). Untilted geometries are unaffected.
|
||||
|
||||
### 1.0.0-rc.161
|
||||
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.
|
||||
|
||||
+29
-17
@@ -336,7 +336,7 @@ std::unique_ptr<HDF5DataSet> NXmx::VDS(const StartMessage &start,
|
||||
void NXmx::Detector(const StartMessage &start) {
|
||||
HDF5Group group(*hdf5_file, "/entry/instrument/detector");
|
||||
group.NXClass("NXdetector");
|
||||
SaveScalar(group, "depends_on", "/entry/instrument/detector/transformations/rot3");
|
||||
SaveScalar(group, "depends_on", "/entry/instrument/detector/transformations/translation");
|
||||
|
||||
// beam_center_x/y and the transformations chain (translation + rot1/2/3) are the refinable geometry;
|
||||
// they are written once at Finalize (see Metrology) from the values refined by the offline analysis.
|
||||
@@ -611,8 +611,14 @@ void NXmx::Metrology(const StartMessage &start, const EndMessage &end) {
|
||||
double vector_length = sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
|
||||
std::vector<double> vector_norm{vector[0] / vector_length, vector[1]/vector_length, vector[2]/vector_length};
|
||||
|
||||
// The translation hangs off rot1, not off ".", so the tilt pivots about the SAMPLE: the rotations
|
||||
// then act on the whole sample->pixel vector including the distance, which is what
|
||||
// DiffractionGeometry does (poni_rot * {dx, dy, distance}). With the translation at the root the
|
||||
// panel origin stays put and only its orientation turns, displacing the origin by tens of mm at a
|
||||
// degree of tilt.
|
||||
SaveScalar(transformations, "translation", vector_length)->
|
||||
Transformation("m", ".", "detector", "detector_arm", "translation", vector_norm);
|
||||
Transformation("m", "/entry/instrument/detector/transformations/rot1",
|
||||
"detector", "detector_arm", "translation", vector_norm);
|
||||
|
||||
// https://manual.nexusformat.org/classes/base_classes/NXdetector_module.html?highlight=nxdetector_module
|
||||
// The order of indices (i, j or i, j, k) is slow to fast.
|
||||
@@ -622,28 +628,34 @@ void NXmx::Metrology(const StartMessage &start, const EndMessage &end) {
|
||||
std::vector<int32_t> size = {static_cast<int32_t>(start.image_size_y),
|
||||
static_cast<int32_t>(start.image_size_x)};
|
||||
|
||||
// Jungfraujoch holds the tilt in the PyFAI PONI convention, poni_rot = Rz(-rot3)*Rx(-rot2)*Ry(+rot1)
|
||||
// (DiffractionGeometry::UpdatePoniRotMatrix), written in the internal frame: x along increasing
|
||||
// column, y along increasing row, z along the beam. NXmx uses McStas, which is that frame turned
|
||||
// 180 degrees about x - a proper rotation, not a mirror - so rotations about y and z reverse sense
|
||||
// while those about x keep it. Hence the axis vectors below. A depends_on chain applies the
|
||||
// DEEPEST dependency first, so rot3 sits at the root to make the product R_rot3*R_rot2*R_rot1.
|
||||
SaveScalar(transformations, "rot1", rot1)->
|
||||
Transformation("rad",
|
||||
"/entry/instrument/detector/transformations/translation",
|
||||
"detector", "detector_arm",
|
||||
"rotation",
|
||||
std::vector<double>{1.0, 0.0, 0.0});
|
||||
|
||||
SaveScalar(transformations, "rot2", rot2)->
|
||||
Transformation("rad",
|
||||
"/entry/instrument/detector/transformations/rot1",
|
||||
"detector", "detector_arm",
|
||||
"rotation",
|
||||
std::vector<double>{0.0, -1.0, 0.0});
|
||||
|
||||
SaveScalar(transformations, "rot3", rot3)->
|
||||
Transformation("rad",
|
||||
"/entry/instrument/detector/transformations/rot2",
|
||||
"detector", "detector_arm",
|
||||
"rotation",
|
||||
std::vector<double>{0.0, -1.0, 0.0});
|
||||
|
||||
SaveScalar(transformations, "rot2", rot2)->
|
||||
Transformation("rad",
|
||||
"/entry/instrument/detector/transformations/rot3",
|
||||
"detector", "detector_arm",
|
||||
"rotation",
|
||||
std::vector<double>{1.0, 0.0, 0.0});
|
||||
|
||||
SaveScalar(transformations, "rot3", rot3)->
|
||||
Transformation("rad",
|
||||
".",
|
||||
"detector", "detector_arm",
|
||||
"rotation",
|
||||
std::vector<double>{0.0, 0.0, -1.0});
|
||||
|
||||
DetectorModule("module", origin, size, {-1,0,0}, {0,-1,0}, "rot3",
|
||||
DetectorModule("module", origin, size, {-1,0,0}, {0,-1,0}, "translation",
|
||||
start.pixel_size_x);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user