From 532f7953489f22cdc018abbd2bfb189590c8ce1d Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Tue, 3 Nov 2026 11:39:36 +0100 Subject: [PATCH] module_placement in custom_geometry's words: x0/y0/fast_axis/slow_axis after mirror_y dataset_settings.module_placement (detector_module_placement) now names a module as custom_geometry's detector_module does - x0, y0 (image position of the readout pixel 0) and fast_axis, slow_axis (detector_module_direction) - but in the stored image, after mirror_y; x0_plane, y0_plane and fast_step, slow_step give where pixel 0 lies on the plane and the plane step per readout pixel. Both schema descriptions state the frame and the mirror (y0 = H - 1 - y0, Yp <-> Yn). DetectorModule carries the readout axes in the image (pixel 0 follows from the box): from the detector geometry after mirror_y (ModulesAsImaged), from how the XFEL reader turns a panel, and Xp/Yp for a panel read along its own array. They travel in the CBOR detector_modules (fast_axis/slow_axis) and in the NXmx module_NN groups (readout_fast_axis/readout_slow_axis, beside the NXmx fields), so a stored file names its modules as the broker does. ModulesPlaced matches the four keys exactly; a module not of the detector, placed twice or left out is an error (400 at /start). The rugnux log names modules the same way. New test ModulePlacement_TurnedMirroredModules (Yp/Xn module, mirror_y on) and PanelLayout_Placement. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UqXYdJKPUFLoeAchjzv3x4 --- broker/ModulePlacementConvert.cpp | 87 ++++--- broker/ModulePlacementConvert.h | 9 +- broker/OpenAPIConvert.cpp | 2 +- broker/gen/model/Dataset_settings.h | 2 +- broker/gen/model/Detector_module.h | 4 +- .../gen/model/Detector_module_placement.cpp | 222 +++++++++--------- broker/gen/model/Detector_module_placement.h | 84 +++---- broker/jfjoch_api.yaml | 116 ++++----- broker/redoc-static.html | 13 +- common/DatasetSettings.cpp | 4 +- common/DatasetSettings.h | 6 +- common/DetectorModules.cpp | 97 +++++++- common/DetectorModules.h | 43 +++- docs/CBOR.md | 2 +- docs/HDF5.md | 1 + docs/JFJOCH_BROKER.md | 14 +- docs/RUGNUX_CLI.md | 2 +- docs/RUGNUX_FORMATS.md | 31 ++- docs/python_client/docs/DatasetSettings.md | 2 +- docs/python_client/docs/DetectorModule.md | 2 +- .../docs/DetectorModulePlacement.md | 20 +- frame_serialize/CBORStream2Deserializer.cpp | 4 + frame_serialize/CBORStream2Serializer.cpp | 4 +- frontend/src/client/types.gen.ts | 99 ++++---- frontend/src/client/zod.gen.ts | 51 ++-- reader/JFJochXFELReader.cpp | 3 + reader/NXmxModules.cpp | 5 + rugnux/RugnuxStills.cpp | 15 +- rugnux/rugnux_cli.cpp | 6 +- tests/BrokerHttpAuthTest.cpp | 15 +- tests/CBORTest.cpp | 7 +- tests/JFJochReaderTest.cpp | 83 +++++++ tests/JFJochStateMachineTest.cpp | 92 ++++++-- tests/PanelLayoutTest.cpp | 31 +++ writer/NXmxModules.cpp | 4 + 35 files changed, 765 insertions(+), 417 deletions(-) diff --git a/broker/ModulePlacementConvert.cpp b/broker/ModulePlacementConvert.cpp index 8630fc7db..36da0b712 100644 --- a/broker/ModulePlacementConvert.cpp +++ b/broker/ModulePlacementConvert.cpp @@ -5,40 +5,73 @@ namespace model = org::openapitools::server::model; -DetectorModule Convert(const model::Detector_module_placement &input) { - DetectorModule m; - m.x0 = input.getBoxX(); - m.y0 = input.getBoxY(); - m.width = input.getBoxWidth(); - m.height = input.getBoxHeight(); - m.ox = input.getPlaneX(); - m.oy = input.getPlaneY(); - m.fx = input.getXStepX(); - m.fy = input.getXStepY(); - m.sx = input.getYStepX(); - m.sy = input.getYStepY(); - return m; +namespace { + +DetectorModuleGeometry::Direction Convert(const model::Detector_module_direction &input) { + switch (input.getValue()) { + case model::Detector_module_direction::eDetector_module_direction::XN: + return DetectorModuleGeometry::Direction::Xneg; + case model::Detector_module_direction::eDetector_module_direction::YP: + return DetectorModuleGeometry::Direction::Ypos; + case model::Detector_module_direction::eDetector_module_direction::YN: + return DetectorModuleGeometry::Direction::Yneg; + default: + return DetectorModuleGeometry::Direction::Xpos; + } } -model::Detector_module_placement ConvertPlacement(const DetectorModule &input) { +model::Detector_module_direction Convert(DetectorModuleGeometry::Direction input) { + model::Detector_module_direction d; + switch (input) { + case DetectorModuleGeometry::Direction::Xneg: + d.setValue(model::Detector_module_direction::eDetector_module_direction::XN); + break; + case DetectorModuleGeometry::Direction::Ypos: + d.setValue(model::Detector_module_direction::eDetector_module_direction::YP); + break; + case DetectorModuleGeometry::Direction::Yneg: + d.setValue(model::Detector_module_direction::eDetector_module_direction::YN); + break; + default: + d.setValue(model::Detector_module_direction::eDetector_module_direction::XP); + break; + } + return d; +} + +} // namespace + +ModulePlacement Convert(const model::Detector_module_placement &input) { + ModulePlacement p; + p.x0 = input.getX0(); + p.y0 = input.getY0(); + p.fast = Convert(input.getFastAxis()); + p.slow = Convert(input.getSlowAxis()); + p.x0_plane = input.getX0Plane(); + p.y0_plane = input.getY0Plane(); + const auto f = input.getFastStep(), s = input.getSlowStep(); + p.fast_step = {f.at(0), f.at(1)}; + p.slow_step = {s.at(0), s.at(1)}; + return p; +} + +model::Detector_module_placement ConvertPlacement(const ModulePlacement &input) { model::Detector_module_placement m; - m.setBoxX(input.x0); - m.setBoxY(input.y0); - m.setBoxWidth(input.width); - m.setBoxHeight(input.height); - m.setPlaneX(input.ox); - m.setPlaneY(input.oy); - m.setXStepX(input.fx); - m.setXStepY(input.fy); - m.setYStepX(input.sx); - m.setYStepY(input.sy); + m.setX0(input.x0); + m.setY0(input.y0); + m.setFastAxis(Convert(input.fast)); + m.setSlowAxis(Convert(input.slow)); + m.setX0Plane(input.x0_plane); + m.setY0Plane(input.y0_plane); + m.setFastStep({input.fast_step[0], input.fast_step[1]}); + m.setSlowStep({input.slow_step[0], input.slow_step[1]}); return m; } -std::vector ParseModulePlacement(const std::string &json_text) { +std::vector ParseModulePlacement(const std::string &json_text) { const auto list = nlohmann::json::parse(json_text).at("module_placement") .get>(); - std::vector out; + std::vector out; for (const auto &e: list) { e.validate(); out.push_back(Convert(e)); @@ -49,6 +82,6 @@ std::vector ParseModulePlacement(const std::string &json_text) { std::string ModulePlacementJson(const DetectorModules &modules) { std::vector list; for (const auto &m: modules.Get()) - list.push_back(ConvertPlacement(m)); + list.push_back(ConvertPlacement(PlacementOf(m))); return nlohmann::json{{"module_placement", list}}.dump(2) + "\n"; } diff --git a/broker/ModulePlacementConvert.h b/broker/ModulePlacementConvert.h index dc108cf47..869913189 100644 --- a/broker/ModulePlacementConvert.h +++ b/broker/ModulePlacementConvert.h @@ -4,7 +4,7 @@ #pragma once // The module placement (dataset_settings.module_placement of jfjoch_api.yaml) between the generated model -// and DetectorModule. Only the generated model and common/ are needed, so the portable programs (rugnux, +// and ModulePlacement (common/DetectorModules.h). Only the generated model and common/ are needed, so the portable programs (rugnux, // the viewer) use it as the broker does. #include @@ -13,10 +13,11 @@ #include "gen/model/Detector_module_placement.h" #include "../common/DetectorModules.h" -DetectorModule Convert(const org::openapitools::server::model::Detector_module_placement &input); -org::openapitools::server::model::Detector_module_placement ConvertPlacement(const DetectorModule &input); +ModulePlacement Convert(const org::openapitools::server::model::Detector_module_placement &input); +org::openapitools::server::model::Detector_module_placement ConvertPlacement(const ModulePlacement &input); // A module placement file: the dataset_settings fragment {"module_placement": [...]}, as rugnux writes it // and /start takes it merged into its dataset settings. -std::vector ParseModulePlacement(const std::string &json_text); +std::vector ParseModulePlacement(const std::string &json_text); +// The placement of every module (PlacementOf), in the table's order. std::string ModulePlacementJson(const DetectorModules &modules); diff --git a/broker/OpenAPIConvert.cpp b/broker/OpenAPIConvert.cpp index 727f5f3bd..006be681f 100644 --- a/broker/OpenAPIConvert.cpp +++ b/broker/OpenAPIConvert.cpp @@ -700,7 +700,7 @@ DatasetSettings Convert(const org::openapitools::server::model::Dataset_settings if (input.polarizationFactorIsSet()) ret.PolarizationFactor(input.getPolarizationFactor()); - std::vector modules; + std::vector modules; for (const auto &m: input.getModulePlacement()) modules.push_back(Convert(m)); ret.ModulePlacement(modules); diff --git a/broker/gen/model/Dataset_settings.h b/broker/gen/model/Dataset_settings.h index 9e11d191b..46463f2a8 100644 --- a/broker/gen/model/Dataset_settings.h +++ b/broker/gen/model/Dataset_settings.h @@ -339,7 +339,7 @@ public: bool asyncStartIsSet() const; void unsetAsync_start(); /// - /// Where the detector's modules lie for this dataset, as rugnux measures them: rugnux writes `<prefix>_module_placement.json`, which holds exactly this field, to be merged into the dataset settings. Each entry names its module by the module's box in the assembled image (`box_x`, `box_y`, `box_width`, `box_height`), in any order; every module must be named once, else `/start` fails with 400. The image is assembled as configured (`custom_geometry`); the placement goes into the start message and the NXmx `module_NN` groups, and the analysis takes it. Omitted or empty: modules as assembled. + /// Where the detector's modules lie for this dataset, as rugnux measures them: rugnux writes `<prefix>_module_placement.json`, which holds exactly this field, to be merged into the dataset settings. Each entry names its module by its readout pixel 0 and readout axes in the stored image (`x0`, `y0`, `fast_axis`, `slow_axis`, after `mirror_y`; see `detector_module_placement`), in any order; every module must be named once, and only modules of the detector, else `/start` fails with 400. The image is assembled as configured (`custom_geometry`); the placement goes into the start message and the NXmx `module_NN` groups, and the analysis takes it. Omitted or empty: modules as assembled. /// std::vector getModulePlacement() const; void setModulePlacement(std::vector const& value); diff --git a/broker/gen/model/Detector_module.h b/broker/gen/model/Detector_module.h index 803f54336..c39ba6c84 100644 --- a/broker/gen/model/Detector_module.h +++ b/broker/gen/model/Detector_module.h @@ -12,7 +12,7 @@ /* * Detector_module.h * - * One module of `custom_geometry`: where its readout pixel 0 goes in the assembled image and which way its fast and slow axes run there. `x0`/`y0` are that readout corner, before `mirror_y` - not the module's box in the stored image (`detector_module_placement` names a module by that box). + * One module of `custom_geometry`: where its readout pixel 0 goes in the assembled image and which way its fast and slow readout axes run there. Frame: `custom_geometry` is BEFORE `mirror_y` (the image as assembled from the readout); `module_placement` is AFTER `mirror_y` (the image as stored, the only one a file reader sees). Same words in both: `x0`/`y0` is the image position of the module's readout pixel 0 and `fast_axis`/`slow_axis` say which way its fast and slow readout axes run in the image. With `mirror_y` on, a module's entry differs between the two by the mirror: for an image of height H, y0 (module_placement) = H - 1 - y0 (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With `mirror_y` off they are equal. */ #ifndef Detector_module_H_ @@ -26,7 +26,7 @@ namespace org::openapitools::server::model { /// -/// One module of `custom_geometry`: where its readout pixel 0 goes in the assembled image and which way its fast and slow axes run there. `x0`/`y0` are that readout corner, before `mirror_y` - not the module's box in the stored image (`detector_module_placement` names a module by that box). +/// One module of `custom_geometry`: where its readout pixel 0 goes in the assembled image and which way its fast and slow readout axes run there. Frame: `custom_geometry` is BEFORE `mirror_y` (the image as assembled from the readout); `module_placement` is AFTER `mirror_y` (the image as stored, the only one a file reader sees). Same words in both: `x0`/`y0` is the image position of the module's readout pixel 0 and `fast_axis`/`slow_axis` say which way its fast and slow readout axes run in the image. With `mirror_y` on, a module's entry differs between the two by the mirror: for an image of height H, y0 (module_placement) = H - 1 - y0 (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With `mirror_y` off they are equal. /// class Detector_module { diff --git a/broker/gen/model/Detector_module_placement.cpp b/broker/gen/model/Detector_module_placement.cpp index ef9680a0f..d2ac0b5e2 100644 --- a/broker/gen/model/Detector_module_placement.cpp +++ b/broker/gen/model/Detector_module_placement.cpp @@ -21,16 +21,10 @@ namespace org::openapitools::server::model Detector_module_placement::Detector_module_placement() { - m_Box_x = 0L; - m_Box_y = 0L; - m_Box_width = 0L; - m_Box_height = 0L; - m_Plane_x = 0.0; - m_Plane_y = 0.0; - m_X_step_x = 0.0; - m_X_step_y = 0.0; - m_Y_step_x = 0.0; - m_Y_step_y = 0.0; + m_X0 = 0L; + m_Y0 = 0L; + m_X0_plane = 0.0; + m_Y0_plane = 0.0; } @@ -55,9 +49,9 @@ bool Detector_module_placement::validate(std::stringstream& msg, const std::stri - /* Box_x */ { - const int64_t& value = m_Box_x; - const std::string currentValuePath = _pathPrefix + ".boxX"; + /* X0 */ { + const int64_t& value = m_X0; + const std::string currentValuePath = _pathPrefix + ".x0"; if (value < 0ll) @@ -69,9 +63,9 @@ bool Detector_module_placement::validate(std::stringstream& msg, const std::stri } - /* Box_y */ { - const int64_t& value = m_Box_y; - const std::string currentValuePath = _pathPrefix + ".boxY"; + /* Y0 */ { + const int64_t& value = m_Y0; + const std::string currentValuePath = _pathPrefix + ".y0"; if (value < 0ll) @@ -81,35 +75,69 @@ bool Detector_module_placement::validate(std::stringstream& msg, const std::stri } } - + - /* Box_width */ { - const int64_t& value = m_Box_width; - const std::string currentValuePath = _pathPrefix + ".boxWidth"; + /* Fast_step */ { + const std::vector& value = m_Fast_step; + const std::string currentValuePath = _pathPrefix + ".fastStep"; - if (value < 1ll) + if (value.size() < 2) { success = false; - msg << currentValuePath << ": must be greater than or equal to 1;"; + msg << currentValuePath << ": must have at least 2 elements;"; + } + if (value.size() > 2) + { + success = false; + msg << currentValuePath << ": must have at most 2 elements;"; + } + { // Recursive validation of array elements + const std::string oldValuePath = currentValuePath; + int i = 0; + for (const double& value : value) + { + const std::string currentValuePath = oldValuePath + "[" + std::to_string(i) + "]"; + + + + i++; + } } } - /* Box_height */ { - const int64_t& value = m_Box_height; - const std::string currentValuePath = _pathPrefix + ".boxHeight"; + /* Slow_step */ { + const std::vector& value = m_Slow_step; + const std::string currentValuePath = _pathPrefix + ".slowStep"; - if (value < 1ll) + if (value.size() < 2) { success = false; - msg << currentValuePath << ": must be greater than or equal to 1;"; + msg << currentValuePath << ": must have at least 2 elements;"; + } + if (value.size() > 2) + { + success = false; + msg << currentValuePath << ": must have at most 2 elements;"; + } + { // Recursive validation of array elements + const std::string oldValuePath = currentValuePath; + int i = 0; + for (const double& value : value) + { + const std::string currentValuePath = oldValuePath + "[" + std::to_string(i) + "]"; + + + + i++; + } } } - + return success; } @@ -118,34 +146,28 @@ bool Detector_module_placement::operator==(const Detector_module_placement& rhs) return - (getBoxX() == rhs.getBoxX()) + (getX0() == rhs.getX0()) && - (getBoxY() == rhs.getBoxY()) + (getY0() == rhs.getY0()) && - (getBoxWidth() == rhs.getBoxWidth()) + (getFastAxis() == rhs.getFastAxis()) && - (getBoxHeight() == rhs.getBoxHeight()) + (getSlowAxis() == rhs.getSlowAxis()) && - (getPlaneX() == rhs.getPlaneX()) + (getX0Plane() == rhs.getX0Plane()) && - (getPlaneY() == rhs.getPlaneY()) + (getY0Plane() == rhs.getY0Plane()) && - (getXStepX() == rhs.getXStepX()) + (getFastStep() == rhs.getFastStep()) && - (getXStepY() == rhs.getXStepY()) - && - - (getYStepX() == rhs.getYStepX()) - && - - (getYStepY() == rhs.getYStepY()) + (getSlowStep() == rhs.getSlowStep()) ; @@ -159,113 +181,93 @@ bool Detector_module_placement::operator!=(const Detector_module_placement& rhs) void to_json(nlohmann::json& j, const Detector_module_placement& o) { j = nlohmann::json::object(); - j["box_x"] = o.m_Box_x; - j["box_y"] = o.m_Box_y; - j["box_width"] = o.m_Box_width; - j["box_height"] = o.m_Box_height; - j["plane_x"] = o.m_Plane_x; - j["plane_y"] = o.m_Plane_y; - j["x_step_x"] = o.m_X_step_x; - j["x_step_y"] = o.m_X_step_y; - j["y_step_x"] = o.m_Y_step_x; - j["y_step_y"] = o.m_Y_step_y; + j["x0"] = o.m_X0; + j["y0"] = o.m_Y0; + j["fast_axis"] = o.m_Fast_axis; + j["slow_axis"] = o.m_Slow_axis; + j["x0_plane"] = o.m_X0_plane; + j["y0_plane"] = o.m_Y0_plane; + j["fast_step"] = o.m_Fast_step; + j["slow_step"] = o.m_Slow_step; } void from_json(const nlohmann::json& j, Detector_module_placement& o) { - j.at("box_x").get_to(o.m_Box_x); - j.at("box_y").get_to(o.m_Box_y); - j.at("box_width").get_to(o.m_Box_width); - j.at("box_height").get_to(o.m_Box_height); - j.at("plane_x").get_to(o.m_Plane_x); - j.at("plane_y").get_to(o.m_Plane_y); - j.at("x_step_x").get_to(o.m_X_step_x); - j.at("x_step_y").get_to(o.m_X_step_y); - j.at("y_step_x").get_to(o.m_Y_step_x); - j.at("y_step_y").get_to(o.m_Y_step_y); + j.at("x0").get_to(o.m_X0); + j.at("y0").get_to(o.m_Y0); + j.at("fast_axis").get_to(o.m_Fast_axis); + j.at("slow_axis").get_to(o.m_Slow_axis); + j.at("x0_plane").get_to(o.m_X0_plane); + j.at("y0_plane").get_to(o.m_Y0_plane); + j.at("fast_step").get_to(o.m_Fast_step); + j.at("slow_step").get_to(o.m_Slow_step); } -int64_t Detector_module_placement::getBoxX() const +int64_t Detector_module_placement::getX0() const { - return m_Box_x; + return m_X0; } -void Detector_module_placement::setBoxX(int64_t const value) +void Detector_module_placement::setX0(int64_t const value) { - m_Box_x = value; + m_X0 = value; } -int64_t Detector_module_placement::getBoxY() const +int64_t Detector_module_placement::getY0() const { - return m_Box_y; + return m_Y0; } -void Detector_module_placement::setBoxY(int64_t const value) +void Detector_module_placement::setY0(int64_t const value) { - m_Box_y = value; + m_Y0 = value; } -int64_t Detector_module_placement::getBoxWidth() const +org::openapitools::server::model::Detector_module_direction Detector_module_placement::getFastAxis() const { - return m_Box_width; + return m_Fast_axis; } -void Detector_module_placement::setBoxWidth(int64_t const value) +void Detector_module_placement::setFastAxis(org::openapitools::server::model::Detector_module_direction const& value) { - m_Box_width = value; + m_Fast_axis = value; } -int64_t Detector_module_placement::getBoxHeight() const +org::openapitools::server::model::Detector_module_direction Detector_module_placement::getSlowAxis() const { - return m_Box_height; + return m_Slow_axis; } -void Detector_module_placement::setBoxHeight(int64_t const value) +void Detector_module_placement::setSlowAxis(org::openapitools::server::model::Detector_module_direction const& value) { - m_Box_height = value; + m_Slow_axis = value; } -double Detector_module_placement::getPlaneX() const +double Detector_module_placement::getX0Plane() const { - return m_Plane_x; + return m_X0_plane; } -void Detector_module_placement::setPlaneX(double const value) +void Detector_module_placement::setX0Plane(double const value) { - m_Plane_x = value; + m_X0_plane = value; } -double Detector_module_placement::getPlaneY() const +double Detector_module_placement::getY0Plane() const { - return m_Plane_y; + return m_Y0_plane; } -void Detector_module_placement::setPlaneY(double const value) +void Detector_module_placement::setY0Plane(double const value) { - m_Plane_y = value; + m_Y0_plane = value; } -double Detector_module_placement::getXStepX() const +std::vector Detector_module_placement::getFastStep() const { - return m_X_step_x; + return m_Fast_step; } -void Detector_module_placement::setXStepX(double const value) +void Detector_module_placement::setFastStep(std::vector const value) { - m_X_step_x = value; + m_Fast_step = value; } -double Detector_module_placement::getXStepY() const +std::vector Detector_module_placement::getSlowStep() const { - return m_X_step_y; + return m_Slow_step; } -void Detector_module_placement::setXStepY(double const value) +void Detector_module_placement::setSlowStep(std::vector const value) { - m_X_step_y = value; -} -double Detector_module_placement::getYStepX() const -{ - return m_Y_step_x; -} -void Detector_module_placement::setYStepX(double const value) -{ - m_Y_step_x = value; -} -double Detector_module_placement::getYStepY() const -{ - return m_Y_step_y; -} -void Detector_module_placement::setYStepY(double const value) -{ - m_Y_step_y = value; + m_Slow_step = value; } diff --git a/broker/gen/model/Detector_module_placement.h b/broker/gen/model/Detector_module_placement.h index 3b8792d17..eb8e1bf86 100644 --- a/broker/gen/model/Detector_module_placement.h +++ b/broker/gen/model/Detector_module_placement.h @@ -12,20 +12,22 @@ /* * Detector_module_placement.h * - * One module of the assembled image and where it lies on the detector plane, in pixels of the assembled image as stored (after `mirror_y`; a pixel's centre at its index). The module is its box in the image: image pixel (x, y) with box_x <= x < box_x + box_width and box_y <= y < box_y + box_height. That pixel lies on the plane at (plane_x, plane_y) + (x - box_x) (x_step_x, x_step_y) + (y - box_y) (y_step_x, y_step_y). The box corner (box_x, box_y) is the module's lowest image x and y, whatever its readout direction: it is not the readout corner `x0`/`y0` of `custom_geometry`. As assembled: plane_x = box_x, plane_y = box_y, (x_step_x, x_step_y) = (1, 0), (y_step_x, y_step_y) = (0, 1). + * One module of the image and where it lies on the detector plane, as rugnux measures it. Frame: `custom_geometry` is BEFORE `mirror_y` (the image as assembled from the readout); `module_placement` is AFTER `mirror_y` (the image as stored, the only one a file reader sees). Same words in both: `x0`/`y0` is the image position of the module's readout pixel 0 and `fast_axis`/`slow_axis` say which way its fast and slow readout axes run in the image. With `mirror_y` on, a module's entry differs between the two by the mirror: for an image of height H, y0 (module_placement) = H - 1 - y0 (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With `mirror_y` off they are equal. `x0`, `y0`, `fast_axis` and `slow_axis` identify the module, whatever the order of the entries, and with the module size they give its box in the image. The plane is in image pixels (a pixel's centre at its index): readout pixel (i, j) - i along the fast axis, j along the slow axis from pixel 0 - lies on it at (x0_plane, y0_plane) + i fast_step + j slow_step. As assembled: x0_plane = x0, y0_plane = y0, and each step is the unit vector of its axis (Xp = [1, 0], Xn = [-1, 0], Yp = [0, 1], Yn = [0, -1]). */ #ifndef Detector_module_placement_H_ #define Detector_module_placement_H_ +#include "Detector_module_direction.h" +#include #include namespace org::openapitools::server::model { /// -/// One module of the assembled image and where it lies on the detector plane, in pixels of the assembled image as stored (after `mirror_y`; a pixel's centre at its index). The module is its box in the image: image pixel (x, y) with box_x <= x < box_x + box_width and box_y <= y < box_y + box_height. That pixel lies on the plane at (plane_x, plane_y) + (x - box_x) (x_step_x, x_step_y) + (y - box_y) (y_step_x, y_step_y). The box corner (box_x, box_y) is the module's lowest image x and y, whatever its readout direction: it is not the readout corner `x0`/`y0` of `custom_geometry`. As assembled: plane_x = box_x, plane_y = box_y, (x_step_x, x_step_y) = (1, 0), (y_step_x, y_step_y) = (0, 1). +/// One module of the image and where it lies on the detector plane, as rugnux measures it. Frame: `custom_geometry` is BEFORE `mirror_y` (the image as assembled from the readout); `module_placement` is AFTER `mirror_y` (the image as stored, the only one a file reader sees). Same words in both: `x0`/`y0` is the image position of the module's readout pixel 0 and `fast_axis`/`slow_axis` say which way its fast and slow readout axes run in the image. With `mirror_y` on, a module's entry differs between the two by the mirror: for an image of height H, y0 (module_placement) = H - 1 - y0 (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With `mirror_y` off they are equal. `x0`, `y0`, `fast_axis` and `slow_axis` identify the module, whatever the order of the entries, and with the module size they give its box in the image. The plane is in image pixels (a pixel's centre at its index): readout pixel (i, j) - i along the fast axis, j along the slow axis from pixel 0 - lies on it at (x0_plane, y0_plane) + i fast_step + j slow_step. As assembled: x0_plane = x0, y0_plane = y0, and each step is the unit vector of its axis (Xp = [1, 0], Xn = [-1, 0], Yp = [0, 1], Yn = [0, -1]). /// class Detector_module_placement { @@ -58,78 +60,64 @@ public: /// Detector_module_placement members /// - /// Lowest image x of the module's box (NXmx data_origin) + /// Image x of the module's readout pixel 0, after mirror_y /// - int64_t getBoxX() const; - void setBoxX(int64_t const value); + int64_t getX0() const; + void setX0(int64_t const value); /// - /// Lowest image y of the module's box (NXmx data_origin) + /// Image y of the module's readout pixel 0, after mirror_y /// - int64_t getBoxY() const; - void setBoxY(int64_t const value); + int64_t getY0() const; + void setY0(int64_t const value); /// - /// Width of the box in image pixels (NXmx data_size) + /// /// - int64_t getBoxWidth() const; - void setBoxWidth(int64_t const value); + org::openapitools::server::model::Detector_module_direction getFastAxis() const; + void setFastAxis(org::openapitools::server::model::Detector_module_direction const& value); /// - /// Height of the box in image pixels (NXmx data_size) + /// /// - int64_t getBoxHeight() const; - void setBoxHeight(int64_t const value); + org::openapitools::server::model::Detector_module_direction getSlowAxis() const; + void setSlowAxis(org::openapitools::server::model::Detector_module_direction const& value); /// - /// Plane x of image pixel (box_x, box_y) + /// Plane x of readout pixel 0, in image pixels /// - double getPlaneX() const; - void setPlaneX(double const value); + double getX0Plane() const; + void setX0Plane(double const value); /// - /// Plane y of image pixel (box_x, box_y) + /// Plane y of readout pixel 0, in image pixels /// - double getPlaneY() const; - void setPlaneY(double const value); + double getY0Plane() const; + void setY0Plane(double const value); /// - /// Plane x step for one image pixel along +x + /// Plane displacement (x, y) for one pixel along the fast readout axis /// - double getXStepX() const; - void setXStepX(double const value); + std::vector getFastStep() const; + void setFastStep(std::vector const value); /// - /// Plane y step for one image pixel along +x + /// Plane displacement (x, y) for one pixel along the slow readout axis /// - double getXStepY() const; - void setXStepY(double const value); - /// - /// Plane x step for one image pixel along +y - /// - double getYStepX() const; - void setYStepX(double const value); - /// - /// Plane y step for one image pixel along +y - /// - double getYStepY() const; - void setYStepY(double const value); + std::vector getSlowStep() const; + void setSlowStep(std::vector const value); friend void to_json(nlohmann::json& j, const Detector_module_placement& o); friend void from_json(const nlohmann::json& j, Detector_module_placement& o); protected: - int64_t m_Box_x; + int64_t m_X0; - int64_t m_Box_y; + int64_t m_Y0; - int64_t m_Box_width; + org::openapitools::server::model::Detector_module_direction m_Fast_axis; - int64_t m_Box_height; + org::openapitools::server::model::Detector_module_direction m_Slow_axis; - double m_Plane_x; + double m_X0_plane; - double m_Plane_y; + double m_Y0_plane; - double m_X_step_x; + std::vector m_Fast_step; - double m_X_step_y; - - double m_Y_step_x; - - double m_Y_step_y; + std::vector m_Slow_step; }; diff --git a/broker/jfjoch_api.yaml b/broker/jfjoch_api.yaml index c2b982368..c17e32e67 100644 --- a/broker/jfjoch_api.yaml +++ b/broker/jfjoch_api.yaml @@ -682,10 +682,11 @@ components: description: | Where the detector's modules lie for this dataset, as rugnux measures them: rugnux writes `_module_placement.json`, which holds exactly this field, to be merged into the dataset settings. - Each entry names its module by the module's box in the assembled image (`box_x`, `box_y`, - `box_width`, `box_height`), in any order; every module must be named once, else `/start` fails - with 400. The image is assembled as configured (`custom_geometry`); the placement goes into the - start message and the NXmx `module_NN` groups, and the analysis takes it. Omitted or empty: + Each entry names its module by its readout pixel 0 and readout axes in the stored image (`x0`, `y0`, + `fast_axis`, `slow_axis`, after `mirror_y`; see `detector_module_placement`), in any order; every + module must be named once, and only modules of the detector, else `/start` fails with 400. The image + is assembled as configured (`custom_geometry`); the placement goes into the start message and the + NXmx `module_NN` groups, and the analysis takes it. Omitted or empty: modules as assembled. tokens: type: array @@ -2392,8 +2393,14 @@ components: type: object description: | One module of `custom_geometry`: where its readout pixel 0 goes in the assembled image and which way - its fast and slow axes run there. `x0`/`y0` are that readout corner, before `mirror_y` - not the - module's box in the stored image (`detector_module_placement` names a module by that box). + its fast and slow readout axes run there. + Frame: `custom_geometry` is BEFORE `mirror_y` (the image as assembled from the readout); `module_placement` + is AFTER `mirror_y` (the image as stored, the only one a file reader sees). Same words in both: + `x0`/`y0` is the image position of the module's readout pixel 0 and `fast_axis`/`slow_axis` say which + way its fast and slow readout axes run in the image. With `mirror_y` on, a module's entry differs + between the two by the mirror: for an image of height H, y0 (module_placement) = H - 1 - y0 + (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With `mirror_y` off they + are equal. required: - x0 - y0 @@ -2413,65 +2420,64 @@ components: detector_module_placement: type: object description: | - One module of the assembled image and where it lies on the detector plane, in pixels of the - assembled image as stored (after `mirror_y`; a pixel's centre at its index). The module is its box - in the image: image pixel (x, y) with box_x <= x < box_x + box_width and box_y <= y < box_y + box_height. - That pixel lies on the plane at - (plane_x, plane_y) + (x - box_x) (x_step_x, x_step_y) + (y - box_y) (y_step_x, y_step_y). - The box corner (box_x, box_y) is the module's lowest image x and y, whatever its readout direction: - it is not the readout corner `x0`/`y0` of `custom_geometry`. - As assembled: plane_x = box_x, plane_y = box_y, (x_step_x, x_step_y) = (1, 0), - (y_step_x, y_step_y) = (0, 1). + One module of the image and where it lies on the detector plane, as rugnux measures it. + Frame: `custom_geometry` is BEFORE `mirror_y` (the image as assembled from the readout); `module_placement` + is AFTER `mirror_y` (the image as stored, the only one a file reader sees). Same words in both: + `x0`/`y0` is the image position of the module's readout pixel 0 and `fast_axis`/`slow_axis` say which + way its fast and slow readout axes run in the image. With `mirror_y` on, a module's entry differs + between the two by the mirror: for an image of height H, y0 (module_placement) = H - 1 - y0 + (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With `mirror_y` off they + are equal. + `x0`, `y0`, `fast_axis` and `slow_axis` identify the module, whatever the order of the entries, and with + the module size they give its box in the image. The plane is in image pixels (a pixel's centre at its + index): readout pixel (i, j) - i along the fast axis, j along the slow axis from pixel 0 - lies on it at + (x0_plane, y0_plane) + i fast_step + j slow_step. + As assembled: x0_plane = x0, y0_plane = y0, and each step is the unit vector of its axis + (Xp = [1, 0], Xn = [-1, 0], Yp = [0, 1], Yn = [0, -1]). required: - - box_x - - box_y - - box_width - - box_height - - plane_x - - plane_y - - x_step_x - - x_step_y - - y_step_x - - y_step_y + - x0 + - y0 + - fast_axis + - slow_axis + - x0_plane + - y0_plane + - fast_step + - slow_step properties: - box_x: + x0: type: integer format: int64 minimum: 0 - description: Lowest image x of the module's box (NXmx data_origin) - box_y: + description: Image x of the module's readout pixel 0, after mirror_y + y0: type: integer format: int64 minimum: 0 - description: Lowest image y of the module's box (NXmx data_origin) - box_width: - type: integer - format: int64 - minimum: 1 - description: Width of the box in image pixels (NXmx data_size) - box_height: - type: integer - format: int64 - minimum: 1 - description: Height of the box in image pixels (NXmx data_size) - plane_x: + description: Image y of the module's readout pixel 0, after mirror_y + fast_axis: + $ref: '#/components/schemas/detector_module_direction' + slow_axis: + $ref: '#/components/schemas/detector_module_direction' + x0_plane: type: number - description: Plane x of image pixel (box_x, box_y) - plane_y: + description: Plane x of readout pixel 0, in image pixels + y0_plane: type: number - description: Plane y of image pixel (box_x, box_y) - x_step_x: - type: number - description: Plane x step for one image pixel along +x - x_step_y: - type: number - description: Plane y step for one image pixel along +x - y_step_x: - type: number - description: Plane x step for one image pixel along +y - y_step_y: - type: number - description: Plane y step for one image pixel along +y + description: Plane y of readout pixel 0, in image pixels + fast_step: + type: array + items: + type: number + minItems: 2 + maxItems: 2 + description: Plane displacement (x, y) for one pixel along the fast readout axis + slow_step: + type: array + items: + type: number + minItems: 2 + maxItems: 2 + description: Plane displacement (x, y) for one pixel along the slow readout axis detector: type: object required: diff --git a/broker/redoc-static.html b/broker/redoc-static.html index 9b9371a59..6965f23ed 100644 --- a/broker/redoc-static.html +++ b/broker/redoc-static.html @@ -554,10 +554,11 @@ Assuming that Smargon is used as static positioner and not moving during the sca async_start
boolean
Default: false

When set to true, /start will not wait for detector and Jungfraujoch to be ready for the measurement.

Array of objects (detector_module_placement)

Where the detector's modules lie for this dataset, as rugnux measures them: rugnux writes <prefix>_module_placement.json, which holds exactly this field, to be merged into the dataset settings. -Each entry names its module by the module's box in the assembled image (box_x, box_y, -box_width, box_height), in any order; every module must be named once, else /start fails -with 400. The image is assembled as configured (custom_geometry); the placement goes into the -start message and the NXmx module_NN groups, and the analysis takes it. Omitted or empty: +Each entry names its module by its readout pixel 0 and readout axes in the stored image (x0, y0, +fast_axis, slow_axis, after mirror_y; see detector_module_placement), in any order; every +module must be named once, and only modules of the detector, else /start fails with 400. The image +is assembled as configured (custom_geometry); the placement goes into the start message and the +NXmx module_NN groups, and the analysis takes it. Omitted or empty: modules as assembled.

tokens
Array of strings[ items non-empty ]

Bearer tokens that protect this dataset: any number, all equivalent (e.g. a beamline secret and a dataset-specific user secret). While set, the endpoints marked with the @@ -568,7 +569,7 @@ protection. Never returned by any endpoint.

Request samples

Content type
application/json
{
  • "images_per_trigger": 1,
  • "ntrigger": 1,
  • "image_time_us": 0,
  • "beam_x_pxl": 0.1,
  • "beam_y_pxl": 0.1,
  • "detector_distance_mm": 0.1,
  • "incident_energy_keV": 0.001,
  • "file_prefix": "",
  • "images_per_file": 1,
  • "space_group_number": 1,
  • "sample_name": "",
  • "compression": "bslz4",
  • "total_flux": 0.1,
  • "transmission": 1,
  • "beam_size_x_um": 0.1,
  • "beam_size_y_um": 0.1,
  • "goniometer": {
    },
  • "grid_scan": {
    },
  • "header_appendix": null,
  • "image_appendix": null,
  • "data_reduction_factor_serialmx": 1,
  • "pixel_value_low_threshold": 0,
  • "run_number": 0,
  • "run_name": "string",
  • "experiment_group": "string",
  • "poisson_compression": 16,
  • "write_nxmx_hdf5_master": true,
  • "save_calibration": true,
  • "polarization_factor": -1,
  • "ring_current_mA": 0.1,
  • "sample_temperature_K": 0.1,
  • "poni_rot1_rad": 0,
  • "poni_rot2_rad": 0,
  • "poni_rot3_rad": 0,
  • "unit_cell": {
    },
  • "spot_finding": true,
  • "smargon": {
    },
  • "max_spot_count": 250,
  • "detect_ice_rings": true,
  • "async_start": false,
  • "module_placement": [
    ],
  • "tokens": [
    ],
  • "xray_fluorescence_spectrum": {
    }
}

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Wait for acquisition running

Block execution of external script till detector and Jungfraujoch are ready to collect data. +

http://localhost:5232/start

Request samples

Content type
application/json
{
  • "images_per_trigger": 1,
  • "ntrigger": 1,
  • "image_time_us": 0,
  • "beam_x_pxl": 0.1,
  • "beam_y_pxl": 0.1,
  • "detector_distance_mm": 0.1,
  • "incident_energy_keV": 0.001,
  • "file_prefix": "",
  • "images_per_file": 1,
  • "space_group_number": 1,
  • "sample_name": "",
  • "compression": "bslz4",
  • "total_flux": 0.1,
  • "transmission": 1,
  • "beam_size_x_um": 0.1,
  • "beam_size_y_um": 0.1,
  • "goniometer": {
    },
  • "grid_scan": {
    },
  • "header_appendix": null,
  • "image_appendix": null,
  • "data_reduction_factor_serialmx": 1,
  • "pixel_value_low_threshold": 0,
  • "run_number": 0,
  • "run_name": "string",
  • "experiment_group": "string",
  • "poisson_compression": 16,
  • "write_nxmx_hdf5_master": true,
  • "save_calibration": true,
  • "polarization_factor": -1,
  • "ring_current_mA": 0.1,
  • "sample_temperature_K": 0.1,
  • "poni_rot1_rad": 0,
  • "poni_rot2_rad": 0,
  • "poni_rot3_rad": 0,
  • "unit_cell": {
    },
  • "spot_finding": true,
  • "smargon": {
    },
  • "max_spot_count": 250,
  • "detect_ice_rings": true,
  • "async_start": false,
  • "module_placement": [
    ],
  • "tokens": [
    ],
  • "xray_fluorescence_spectrum": {
    }
}

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Wait for acquisition running

Block execution of external script till detector and Jungfraujoch are ready to collect data. To not block web server for a indefinite period of time, the procedure is provided with a timeout. Extending timeout is possible, but requires to ensure safety that client will not close the connection and retry the connection.

query Parameters
timeout
integer [ 0 .. 3600 ]
Default: 60

Timeout in seconds (0 == immediate response)

@@ -1130,7 +1131,7 @@ then image might be replaced in the buffer between calling /images and /image.cb