diff --git a/common/DiffractionExperiment.cpp b/common/DiffractionExperiment.cpp index 76395722..79d3f29e 100644 --- a/common/DiffractionExperiment.cpp +++ b/common/DiffractionExperiment.cpp @@ -1487,7 +1487,7 @@ std::vector DiffractionExperiment::BuildTransformationCh if (const auto goniometer = GetGoniometer()) { DetectorTransformation axis(goniometer->GetName(), TransformationType::Rotation, goniometer->GetAxis()); - if (goniometer->IsScanning() && (image_num > 0)) + if (image_num > 0) axis.Values(to_float(goniometer->GetAngleContainer(image_num))); else axis.Value(goniometer->GetStart_deg()); @@ -1498,12 +1498,15 @@ std::vector DiffractionExperiment::BuildTransformationCh add(DetectorTransformation("omega", TransformationType::Rotation, {-1, 0, 0}).Value(0.0f)); } - // Smargon chi and phi are ordinary axes that happen not to move. + // Smargon chi and phi are ordinary axes that happen not to move. They still carry one entry per + // image: a reader takes the number of images from the innermost axis of the sample chain when no + // axis varies, and chi/phi are innermost whenever they are present. if (const auto smargon = dataset.GetSmargonPosition()) { + const auto n = static_cast(std::max(image_num, 1)); add(DetectorTransformation("chi", TransformationType::Rotation, smargon->chi_axis) - .Value(smargon->chi_deg)); + .Values(std::vector(n, smargon->chi_deg))); add(DetectorTransformation("phi", TransformationType::Rotation, smargon->phi_axis) - .Value(smargon->phi_deg)); + .Values(std::vector(n, smargon->phi_deg))); } return chain; diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1b73a6d6..3ff34466 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -14,6 +14,7 @@ This is an UNSTABLE release. It includes many experimental features, as well as * Tests: a tilted detector is now cross-checked against pyFAI and DIALS, in the unit tests and against a written file in CI. * rugnux: the detector geometry is also logged in XDS's convention (`ORGX`/`ORGY`, detector axis vectors, rotation axis), so it can be compared directly with an XDS refinement. * HDF5: a data file missing next to a VDS master now reads as the error-pixel marker instead of zero counts, so those frames are masked rather than silently integrated as blank. +* HDF5: a still or grid scan recorded with a Smargon head position is no longer read back as a single image. * The writer refuses a stream whose start message declares a different pixel format than its images carry, instead of writing a master that does not describe its own data. * HDF5: `module_offset` is written as a float with a proper unit vector, and every transformation offset declares `offset_units`, so a reader does not fall back to the axis's own units - degrees on a rotation - when interpreting a length. * The image stream can carry the sample transformation chain (`transformations`, in the END message) in mounting order, so a goniometer axis, the Smargon chi/phi and a grid stage are described together and unambiguously. It is optional - a producer that does not send it gets the same chain built by the writer - so no metadata version change is needed. diff --git a/writer/HDF5NXmx.cpp b/writer/HDF5NXmx.cpp index fef8391c..281e8e52 100644 --- a/writer/HDF5NXmx.cpp +++ b/writer/HDF5NXmx.cpp @@ -758,15 +758,22 @@ void NXmx::Sample(const StartMessage &start, const EndMessage &end) { // Smargon chi/phi are static positioners closest to the sample, so they are appended // at the innermost end of the transformation chain (whatever depends_on currently is). - auto write_smargon = [&start](HDF5Group& transformations, std::string& depends_on) { + auto write_smargon = [&start, &end](HDF5Group& transformations, std::string& depends_on) { if (!start.smargon_position) return; - SaveScalar(transformations, "chi", start.smargon_position->chi_deg)-> + + // One entry per image, even though neither angle moves. A reader takes the number of images + // from the innermost axis of the sample chain when no axis varies - chi and phi are innermost + // whenever they are present - so written as scalars, a still with a head position reads back + // as a single image however many were collected. + const auto n = static_cast(std::max(end.max_image_number, 1)); + + SaveVector(transformations, "chi", std::vector(n, start.smargon_position->chi_deg))-> Transformation("deg", depends_on, "", "", "rotation", {start.smargon_position->chi_axis.x, start.smargon_position->chi_axis.y, start.smargon_position->chi_axis.z}, {0, 0, 0}, ""); depends_on = "/entry/sample/transformations/chi"; - SaveScalar(transformations, "phi", start.smargon_position->phi_deg)-> + SaveVector(transformations, "phi", std::vector(n, start.smargon_position->phi_deg))-> Transformation("deg", depends_on, "", "", "rotation", {start.smargon_position->phi_axis.x, start.smargon_position->phi_axis.y, start.smargon_position->phi_axis.z}, {0, 0, 0}, "");