Carry the sample transformation chain as DetectorTransformation
Replaces the start-message TransformationAxis of the previous commit, which was the wrong shape in two ways. DetectorTransformation (common/) mirrors a NeXus NXtransformations axis and holds nothing else: name, type, units, vector, offset, depends_on and the positions themselves. Deliberately without cleverness - the values are either a single number for an axis that does not move or one per image, and nothing derives a position from a start and an increment. That is the point: a producer will later want to report where a stage actually WENT rather than where it was told to go, and a structure that stores start+increment cannot express that. A million images cost 4 MB per axis, which is not a reason to be clever. Hence also the move to the END message: measured positions are only known once the run is over. And hence no metadata version bump, which the previous commit did make. The chain is optional; when it is absent the writer builds the identical chain from the start message, exactly as before. Nothing on the wire changes for a producer that does not send it, so a broker and a writer of different releases still interwork - the constraint the previous version stated is withdrawn. The writer transcribes a chain it is given, without recomputing an angle, which is what makes measured positions possible end to end. JFJochReader_TransformationChain_SentAndBuilt writes the same run both ways and checks the two files read back the same, chi/phi included. CBORSerialize_End_Transformations covers the wire 1:1, asserting the order survives and that a moving axis keeps one value per image while a stationary one keeps a single value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -359,23 +359,28 @@ inline void CBOR_ENC_GRID_SCAN(CborEncoder &encoder, const char* key, const Grid
|
||||
}
|
||||
|
||||
inline void CBOR_ENC_TRANSFORMATIONS(CborEncoder &encoder, const char* key,
|
||||
const std::vector<TransformationAxis> &chain) {
|
||||
const std::vector<DetectorTransformation> &chain) {
|
||||
if (chain.empty())
|
||||
return;
|
||||
|
||||
CborEncoder arrayEncoder, mapEncoder;
|
||||
cborErr(cbor_encode_text_stringz(&encoder, key));
|
||||
// An ARRAY, not a map: the order is the mounting order and must survive. A CBOR map cannot carry
|
||||
// that - RFC 8949 requires deterministic encoders to sort map keys - which is why this exists
|
||||
// beside the (unordered, DECTRIS-defined) goniometer map rather than replacing it.
|
||||
// An ARRAY, not a map: the chain is ordered, base first, and a CBOR map carries no order a
|
||||
// consumer may rely on - RFC 8949 has deterministic encoders sort map keys.
|
||||
cborErr(cbor_encoder_create_array(&encoder, &arrayEncoder, chain.size()));
|
||||
for (const auto &axis: chain) {
|
||||
cborErr(cbor_encoder_create_map(&arrayEncoder, &mapEncoder, 5));
|
||||
CBOR_ENC(mapEncoder, "name", axis.name);
|
||||
CBOR_ENC(mapEncoder, "type", axis.rotation ? "rotation" : "translation");
|
||||
CBOR_ENC(mapEncoder, "vector", axis.vector);
|
||||
CBOR_ENC(mapEncoder, "start", static_cast<float>(axis.start));
|
||||
CBOR_ENC(mapEncoder, "increment", static_cast<float>(axis.increment));
|
||||
cborErr(cbor_encoder_create_map(&arrayEncoder, &mapEncoder, CborIndefiniteLength));
|
||||
CBOR_ENC(mapEncoder, "name", axis.GetName());
|
||||
CBOR_ENC(mapEncoder, "transformation_type", axis.IsRotation() ? "rotation" : "translation");
|
||||
CBOR_ENC(mapEncoder, "units", axis.GetUnits());
|
||||
CBOR_ENC(mapEncoder, "vector", axis.GetVector());
|
||||
CBOR_ENC(mapEncoder, "offset", axis.GetOffset());
|
||||
CBOR_ENC(mapEncoder, "depends_on", axis.GetDependsOn());
|
||||
if (!axis.GetEquipment().empty())
|
||||
CBOR_ENC(mapEncoder, "equipment", axis.GetEquipment());
|
||||
if (!axis.GetEquipmentComponent().empty())
|
||||
CBOR_ENC(mapEncoder, "equipment_component", axis.GetEquipmentComponent());
|
||||
CBOR_ENC(mapEncoder, "values", axis.GetValues());
|
||||
cborErr(cbor_encoder_close_container(&arrayEncoder, &mapEncoder));
|
||||
}
|
||||
cborErr(cbor_encoder_close_container(&encoder, &arrayEncoder));
|
||||
@@ -711,9 +716,6 @@ void CBORStream2Serializer::SerializeSequenceStart(const StartMessage& message)
|
||||
CBOR_ENC(mapEncoder, "series_id", message.run_number);
|
||||
CBOR_ENC(mapEncoder, "fluorescence", message.fluorescence_spectrum);
|
||||
|
||||
// The ordered chain, and beside it the DECTRIS-shaped goniometer map and the grid scan, which a
|
||||
// consumer that predates the chain still understands. Both describe the same setup.
|
||||
CBOR_ENC_TRANSFORMATIONS(mapEncoder, "transformations", message.transformations);
|
||||
if (message.goniometer)
|
||||
CBOR_ENC_GONIOMETER_MAP(mapEncoder, "goniometer", message);
|
||||
if (message.grid_scan)
|
||||
@@ -777,6 +779,8 @@ void CBORStream2Serializer::SerializeSequenceEnd(const EndMessage& message) {
|
||||
CBOR_ENC(mapEncoder, "end_date", message.end_date);
|
||||
|
||||
CBOR_ENC(mapEncoder, "max_image_number", message.max_image_number);
|
||||
// Optional: absent means the writer builds the same chain from the start message itself.
|
||||
CBOR_ENC_TRANSFORMATIONS(mapEncoder, "transformations", message.transformations);
|
||||
CBOR_ENC(mapEncoder, "images_collected", message.images_collected_count);
|
||||
CBOR_ENC(mapEncoder, "images_sent_to_write", message.images_sent_to_write_count);
|
||||
CBOR_ENC(mapEncoder, "data_collection_efficiency", message.efficiency);
|
||||
|
||||
Reference in New Issue
Block a user