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>
64 lines
3.0 KiB
C++
64 lines
3.0 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "Coord.h"
|
|
|
|
enum class TransformationType { Rotation, Translation };
|
|
|
|
// One axis of a NeXus NXtransformations chain, held the way NXmx needs it written and nothing more.
|
|
//
|
|
// Deliberately without any cleverness: the values are either a single number, meaning an axis that
|
|
// does not move, or one number per image. Nothing here derives a position from a start and an
|
|
// increment, because the point of the structure is to be able to carry positions that were MEASURED
|
|
// rather than inferred - a custom scan, a stage that did not go exactly where it was asked. A
|
|
// million images cost 4 MB per axis at this precision, which is not a reason to be clever.
|
|
//
|
|
// A chain is a std::vector<DetectorTransformation> ordered base first, sample last - the order
|
|
// things are mounted in, which is also the order NXmx composes them in. depends_on names the axis
|
|
// this one is mounted on, and is empty for the base.
|
|
class DetectorTransformation {
|
|
std::string name;
|
|
TransformationType type = TransformationType::Rotation;
|
|
std::string units = "deg";
|
|
Coord vector = {0, 0, 1};
|
|
Coord offset = {0, 0, 0};
|
|
std::string depends_on; // empty = mounted on the base ("." in NXmx)
|
|
std::string equipment;
|
|
std::string equipment_component;
|
|
std::vector<float> values; // one entry = constant, otherwise one per image
|
|
|
|
public:
|
|
DetectorTransformation() = default;
|
|
DetectorTransformation(std::string name, TransformationType type, const Coord &vector);
|
|
|
|
DetectorTransformation& Name(const std::string &input);
|
|
DetectorTransformation& Type(TransformationType input);
|
|
DetectorTransformation& Units(const std::string &input);
|
|
DetectorTransformation& Vector(const Coord &input);
|
|
DetectorTransformation& Offset(const Coord &input);
|
|
DetectorTransformation& DependsOn(const std::string &input);
|
|
DetectorTransformation& Equipment(const std::string &input);
|
|
DetectorTransformation& EquipmentComponent(const std::string &input);
|
|
DetectorTransformation& Values(const std::vector<float> &input);
|
|
DetectorTransformation& Value(float input); // an axis that does not move
|
|
|
|
[[nodiscard]] const std::string& GetName() const;
|
|
[[nodiscard]] TransformationType GetType() const;
|
|
[[nodiscard]] const std::string& GetUnits() const;
|
|
[[nodiscard]] Coord GetVector() const;
|
|
[[nodiscard]] Coord GetOffset() const;
|
|
[[nodiscard]] const std::string& GetDependsOn() const;
|
|
[[nodiscard]] const std::string& GetEquipment() const;
|
|
[[nodiscard]] const std::string& GetEquipmentComponent() const;
|
|
[[nodiscard]] const std::vector<float>& GetValues() const;
|
|
|
|
[[nodiscard]] bool IsRotation() const;
|
|
// True when the axis holds one value for the whole run rather than one per image.
|
|
[[nodiscard]] bool IsConstant() const;
|
|
};
|