diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d43233ed..82f652a3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -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. diff --git a/writer/HDF5NXmx.cpp b/writer/HDF5NXmx.cpp index 0bcb9e5f..8f0261d8 100644 --- a/writer/HDF5NXmx.cpp +++ b/writer/HDF5NXmx.cpp @@ -336,7 +336,7 @@ std::unique_ptr 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 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 size = {static_cast(start.image_size_y), static_cast(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{1.0, 0.0, 0.0}); - - SaveScalar(transformations, "rot2", rot2)-> - Transformation("rad", - "/entry/instrument/detector/transformations/rot1", - "detector", "detector_arm", - "rotation", - std::vector{0.0, -1.0, 0.0}); - - SaveScalar(transformations, "rot3", rot3)-> Transformation("rad", "/entry/instrument/detector/transformations/rot2", "detector", "detector_arm", "rotation", + std::vector{0.0, -1.0, 0.0}); + + SaveScalar(transformations, "rot2", rot2)-> + Transformation("rad", + "/entry/instrument/detector/transformations/rot3", + "detector", "detector_arm", + "rotation", + std::vector{1.0, 0.0, 0.0}); + + SaveScalar(transformations, "rot3", rot3)-> + Transformation("rad", + ".", + "detector", "detector_arm", + "rotation", std::vector{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); }