writer: write refined detector geometry once at the end, incl. translation

The refined beam centre / detector rotations were written from the
StartMessage at file open and then partially overwritten in Finalize -
but the overwrite updated beam_center_x/y and rot1/2/3 and NOT the
transformations/translation vector, which is what actually encodes the
beam centre in the NXmx geometry chain read by XDS/DIALS/dxtbx. The
reprocessed _process.h5 therefore had an internally inconsistent geometry
(refined rotations combined with the stale, unrefined beam centre).

The master file is never read before Finalize, so write the whole
refinable geometry (beam_center_x/y + translation + rot1/2/3) once there,
from the refined values when present and the StartMessage otherwise. This
drops the open-time write plus in-place overwrite and, unlike the old
path, keeps translation consistent with the refined beam centre.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 16:59:57 +02:00
co-authored by Claude Opus 4.8
parent 4aab8078d6
commit 6c80dbdc32
2 changed files with 27 additions and 29 deletions
+26 -28
View File
@@ -46,7 +46,6 @@ NXmx::NXmx(const StartMessage &start)
Facility(start);
Detector(start);
Metrology(start);
Beam(start);
Attenuator(start);
UserData(start);
@@ -332,8 +331,8 @@ void NXmx::Detector(const StartMessage &start) {
group.NXClass("NXdetector");
SaveScalar(group, "depends_on", "/entry/instrument/detector/transformations/rot3");
SaveScalar(group, "beam_center_x", start.beam_center_x)->Units("pixel");
SaveScalar(group, "beam_center_y", start.beam_center_y)->Units("pixel");
// 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.
SaveScalar(group, "distance", start.detector_distance)->Units("m");
SaveScalar(group, "detector_distance", start.detector_distance)->Units("m");
@@ -574,12 +573,27 @@ void NXmx::Fluorescence(const StartMessage &start) {
group.SaveVector("data", start.fluorescence_spectrum.GetData());
}
void NXmx::Metrology(const StartMessage &start) {
void NXmx::Metrology(const StartMessage &start, const EndMessage &end) {
// The beam centre and detector rotations may have been refined by the offline analysis (rugnux).
// The master file is streamed but never read before Finalize, so the whole geometry is written
// once here, at the end, from the refined values when present and the StartMessage values
// otherwise - simpler than an open-time write followed by an in-place overwrite. The broker
// leaves the refined fields empty, so the user-provided StartMessage geometry is used unchanged.
const float beam_center_x = end.refined_beam_center_x.value_or(start.beam_center_x);
const float beam_center_y = end.refined_beam_center_y.value_or(start.beam_center_y);
const double rot1 = end.refined_poni_rot1.value_or(start.poni_rot1.value_or(0.0f));
const double rot2 = end.refined_poni_rot2.value_or(start.poni_rot2.value_or(0.0f));
const double rot3 = end.refined_poni_rot3.value_or(start.poni_rot3.value_or(0.0f));
HDF5Group detector(*hdf5_file, "/entry/instrument/detector");
SaveScalar(detector, "beam_center_x", beam_center_x)->Units("pixel");
SaveScalar(detector, "beam_center_y", beam_center_y)->Units("pixel");
HDF5Group transformations(*hdf5_file, "/entry/instrument/detector/transformations");
transformations.NXClass("NXtransformations");
std::vector<double> vector{start.beam_center_x * start.pixel_size_x,
start.beam_center_y * start.pixel_size_y,
std::vector<double> vector{beam_center_x * start.pixel_size_x,
beam_center_y * start.pixel_size_y,
start.detector_distance};
double vector_length = sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
@@ -596,10 +610,6 @@ void NXmx::Metrology(const StartMessage &start) {
std::vector<int32_t> size = {static_cast<int32_t>(start.image_size_y),
static_cast<int32_t>(start.image_size_x)};
const double rot1 = start.poni_rot1.value_or(0.0);
const double rot2 = start.poni_rot2.value_or(0.0);
const double rot3 = start.poni_rot3.value_or(0.0);
SaveScalar(transformations, "rot1", rot1)->
Transformation("rad",
"/entry/instrument/detector/transformations/translation",
@@ -888,24 +898,12 @@ void NXmx::Finalize(const EndMessage &end) {
Detector(start_message, end);
Sample(start_message, end);
// Overwrite the master-file geometry with values refined by the offline analysis (rugnux). The
// datasets were written from the StartMessage at file open (the file is streamed, so the geometry
// could not wait for refinement); re-write them in place now that the refined values are known.
// The broker leaves these empty, keeping the user-provided geometry.
const auto overwrite = [&](const std::string &path, auto val) {
HDF5DataSet ds(*hdf5_file, path); // open the existing dataset
ds.Write(HDF5DataType(val), &val);
};
if (end.refined_beam_center_x)
overwrite("/entry/instrument/detector/beam_center_x", *end.refined_beam_center_x);
if (end.refined_beam_center_y)
overwrite("/entry/instrument/detector/beam_center_y", *end.refined_beam_center_y);
if (end.refined_poni_rot1)
overwrite("/entry/instrument/detector/transformations/rot1", static_cast<double>(*end.refined_poni_rot1));
if (end.refined_poni_rot2)
overwrite("/entry/instrument/detector/transformations/rot2", static_cast<double>(*end.refined_poni_rot2));
if (end.refined_poni_rot3)
overwrite("/entry/instrument/detector/transformations/rot3", static_cast<double>(*end.refined_poni_rot3));
// Write the refinable detector geometry (beam centre + transformations) now, from the values
// refined by the offline analysis when present and the StartMessage otherwise. The master file
// is never read before this point, so writing once here is simpler than an open-time write plus
// an in-place overwrite - and, unlike the old overwrite, it also updates the translation vector
// (which encodes the beam centre in the NXmx geometry chain).
Metrology(start_message, end);
AzimuthalIntegration(start_message, end);
ADUHistogram(end);
+1 -1
View File
@@ -44,7 +44,7 @@ class NXmx {
void Facility(const StartMessage &start);
void Beam(const StartMessage &start);
void Metrology(const StartMessage &start);
void Metrology(const StartMessage &start, const EndMessage &end);
void Sample(const StartMessage &start, const EndMessage &end);
void Attenuator(const StartMessage &start);