diff --git a/common/DiffractionExperiment.cpp b/common/DiffractionExperiment.cpp index fa535919b..db0bdfa26 100644 --- a/common/DiffractionExperiment.cpp +++ b/common/DiffractionExperiment.cpp @@ -654,6 +654,9 @@ void DiffractionExperiment::FillMessage(StartMessage &message) const { message.images_per_file = GetImagesPerFile(); message.beam_center_x = GetBeamX_pxl(); message.beam_center_y = GetBeamY_pxl(); + const auto direct_beam = GetDiffractionGeometry().GetDirectBeam_pxl(); + message.direct_beam_x = direct_beam.first; + message.direct_beam_y = direct_beam.second; message.detector_distance = GetDetectorDistance_mm() * 1e-3f; message.incident_wavelength = GetWavelength_A(); // NXmx incident_wavelength_spread is the FWHM of the wavelength distribution, diff --git a/common/JFJochMessages.h b/common/JFJochMessages.h index 5e3360708..dae9b38a2 100644 --- a/common/JFJochMessages.h +++ b/common/JFJochMessages.h @@ -210,6 +210,14 @@ struct StartMessage { float detector_distance; float beam_center_x; float beam_center_y; + // Where the undeflected beam lands on the detector, in pixels. This is NOT beam_center_x/y above: + // that is the PONI, the foot of the perpendicular from the sample, and the two part company as + // soon as the detector is tilted. Derived from the rest of the geometry + // (DiffractionGeometry::GetDirectBeam_pxl), and carried so a consumer that wants the beam + // position - which is what most of them mean, XDS among them - does not have to redo the tilt + // arithmetic. Absent in a stream written before these fields existed. + std::optional direct_beam_x; + std::optional direct_beam_y; uint64_t number_of_images; diff --git a/frame_serialize/CBORStream2Deserializer.cpp b/frame_serialize/CBORStream2Deserializer.cpp index d6e8a753e..22ee26e4f 100644 --- a/frame_serialize/CBORStream2Deserializer.cpp +++ b/frame_serialize/CBORStream2Deserializer.cpp @@ -1254,6 +1254,10 @@ namespace { message.beam_center_x = GetCBORFloat(value); else if (key == "beam_center_y") message.beam_center_y = GetCBORFloat(value); + else if (key == "direct_beam_x") + message.direct_beam_x = GetCBORFloat(value); + else if (key == "direct_beam_y") + message.direct_beam_y = GetCBORFloat(value); else if (key == "detector_distance") message.detector_distance = GetCBORFloat(value); else if (key == "number_of_images") diff --git a/frame_serialize/CBORStream2Serializer.cpp b/frame_serialize/CBORStream2Serializer.cpp index a37c135df..e8c0cc006 100644 --- a/frame_serialize/CBORStream2Serializer.cpp +++ b/frame_serialize/CBORStream2Serializer.cpp @@ -689,6 +689,10 @@ void CBORStream2Serializer::SerializeSequenceStart(const StartMessage& message) CBOR_ENC_AXIS(mapEncoder, "detector_translation", message.detector_translation); CBOR_ENC(mapEncoder, "beam_center_x", message.beam_center_x); CBOR_ENC(mapEncoder, "beam_center_y", message.beam_center_y); + // Not a DECTRIS field, and skipped by a consumer that does not know it. beam_center_x/y above is + // the PONI, which is not where the beam lands once the detector is tilted; these two are. + CBOR_ENC(mapEncoder, "direct_beam_x", message.direct_beam_x); + CBOR_ENC(mapEncoder, "direct_beam_y", message.direct_beam_y); CBOR_ENC(mapEncoder, "countrate_correction_enabled", message.countrate_correction_enabled); CBOR_ENC(mapEncoder, "flatfield_enabled", message.flatfield_enabled); CBOR_ENC(mapEncoder, "number_of_images", message.number_of_images); diff --git a/tests/CBORTest.cpp b/tests/CBORTest.cpp index 33a677382..5e5abca64 100644 --- a/tests/CBORTest.cpp +++ b/tests/CBORTest.cpp @@ -17,6 +17,8 @@ TEST_CASE("CBORSerialize_Start", "[CBOR]") { .detector_distance = 0.0005, .beam_center_x = 456.6, .beam_center_y = 124.3, + .direct_beam_x = 461.2, + .direct_beam_y = 129.9, .number_of_images = 34567, .image_size_x = 456, .image_size_y = 457, @@ -89,6 +91,12 @@ TEST_CASE("CBORSerialize_Start", "[CBOR]") { CHECK(output_message.detector_distance == Catch::Approx(message.detector_distance)); CHECK(output_message.beam_center_x == Catch::Approx(message.beam_center_x)); CHECK(output_message.beam_center_y == Catch::Approx(message.beam_center_y)); + // The PONI (beam_center_*) and the direct beam are different points on a tilted detector, so the + // round trip has to keep them apart, not merely keep two floats. + REQUIRE(output_message.direct_beam_x); + REQUIRE(output_message.direct_beam_y); + CHECK(output_message.direct_beam_x.value() == Catch::Approx(message.direct_beam_x.value())); + CHECK(output_message.direct_beam_y.value() == Catch::Approx(message.direct_beam_y.value())); CHECK(output_message.number_of_images == message.number_of_images); CHECK(output_message.image_size_x == message.image_size_x); CHECK(output_message.image_size_y == message.image_size_y);