module_placement in custom_geometry's words: x0/y0/fast_axis/slow_axis after mirror_y
Build Packages / build:portable:windows-x86_64:cuda (push) Failing after 24m10s
Build Packages / build:portable:linux-x86_64:cuda (push) Failing after 12m10s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 26m58s
Build Packages / Create release (push) Successful in 7s
Build Packages / build:portable:macos-arm64:nocuda (push) Failing after 2m53s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 24m49s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 30m24s
Build Packages / Build documentation (push) Successful in 44s
Build Packages / build:portable:linux-x86_64:nocuda (push) Failing after 9m10s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 10m45s
Build Packages / build:portable:windows-x86_64:nocuda (push) Failing after 22m23s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 29m4s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 30m18s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 33m38s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 28m38s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 30m36s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 32m54s
Build Packages / Generate python client (push) Successful in 13s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 32m18s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 35m18s
Build Packages / Unit tests (push) Successful in 2h25m46s
Build Packages / Validation Battery (push) Successful in 1h10m47s

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqXYdJKPUFLoeAchjzv3x4
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-11-03 11:39:36 +01:00
1 parent bcee3fb5b7
commit 532f795348
35 files changed
+765 -417

No files matched your search

+60 -27
View File
@@ -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<DetectorModule> ParseModulePlacement(const std::string &json_text) {
std::vector<ModulePlacement> ParseModulePlacement(const std::string &json_text) {
const auto list = nlohmann::json::parse(json_text).at("module_placement")
.get<std::vector<model::Detector_module_placement>>();
std::vector<DetectorModule> out;
std::vector<ModulePlacement> out;
for (const auto &e: list) {
e.validate();
out.push_back(Convert(e));
@@ -49,6 +82,6 @@ std::vector<DetectorModule> ParseModulePlacement(const std::string &json_text) {
std::string ModulePlacementJson(const DetectorModules &modules) {
std::vector<model::Detector_module_placement> 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";
}
+5 -4
View File
@@ -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 <string>
@@ -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<DetectorModule> ParseModulePlacement(const std::string &json_text);
std::vector<ModulePlacement> ParseModulePlacement(const std::string &json_text);
// The placement of every module (PlacementOf), in the table's order.
std::string ModulePlacementJson(const DetectorModules &modules);
+1 -1
View File
@@ -700,7 +700,7 @@ DatasetSettings Convert(const org::openapitools::server::model::Dataset_settings
if (input.polarizationFactorIsSet())
ret.PolarizationFactor(input.getPolarizationFactor());
std::vector<DetectorModule> modules;
std::vector<ModulePlacement> modules;
for (const auto &m: input.getModulePlacement())
modules.push_back(Convert(m));
ret.ModulePlacement(modules);
+1 -1
View File
@@ -339,7 +339,7 @@ public:
bool asyncStartIsSet() const;
void unsetAsync_start();
/// <summary>
/// Where the detector&#39;s modules lie for this dataset, as rugnux measures them: rugnux writes &#x60;&lt;prefix&gt;_module_placement.json&#x60;, which holds exactly this field, to be merged into the dataset settings. Each entry names its module by the module&#39;s box in the assembled image (&#x60;box_x&#x60;, &#x60;box_y&#x60;, &#x60;box_width&#x60;, &#x60;box_height&#x60;), in any order; every module must be named once, else &#x60;/start&#x60; fails with 400. The image is assembled as configured (&#x60;custom_geometry&#x60;); the placement goes into the start message and the NXmx &#x60;module_NN&#x60; groups, and the analysis takes it. Omitted or empty: modules as assembled.
/// Where the detector&#39;s modules lie for this dataset, as rugnux measures them: rugnux writes &#x60;&lt;prefix&gt;_module_placement.json&#x60;, 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 (&#x60;x0&#x60;, &#x60;y0&#x60;, &#x60;fast_axis&#x60;, &#x60;slow_axis&#x60;, after &#x60;mirror_y&#x60;; see &#x60;detector_module_placement&#x60;), in any order; every module must be named once, and only modules of the detector, else &#x60;/start&#x60; fails with 400. The image is assembled as configured (&#x60;custom_geometry&#x60;); the placement goes into the start message and the NXmx &#x60;module_NN&#x60; groups, and the analysis takes it. Omitted or empty: modules as assembled.
/// </summary>
std::vector<org::openapitools::server::model::Detector_module_placement> getModulePlacement() const;
void setModulePlacement(std::vector<org::openapitools::server::model::Detector_module_placement> const& value);
+2 -2
View File
@@ -12,7 +12,7 @@
/*
* Detector_module.h
*
* One module of &#x60;custom_geometry&#x60;: where its readout pixel 0 goes in the assembled image and which way its fast and slow axes run there. &#x60;x0&#x60;/&#x60;y0&#x60; are that readout corner, before &#x60;mirror_y&#x60; - not the module&#39;s box in the stored image (&#x60;detector_module_placement&#x60; names a module by that box).
* One module of &#x60;custom_geometry&#x60;: where its readout pixel 0 goes in the assembled image and which way its fast and slow readout axes run there. Frame: &#x60;custom_geometry&#x60; is BEFORE &#x60;mirror_y&#x60; (the image as assembled from the readout); &#x60;module_placement&#x60; is AFTER &#x60;mirror_y&#x60; (the image as stored, the only one a file reader sees). Same words in both: &#x60;x0&#x60;/&#x60;y0&#x60; is the image position of the module&#39;s readout pixel 0 and &#x60;fast_axis&#x60;/&#x60;slow_axis&#x60; say which way its fast and slow readout axes run in the image. With &#x60;mirror_y&#x60; on, a module&#39;s entry differs between the two by the mirror: for an image of height H, y0 (module_placement) &#x3D; H - 1 - y0 (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With &#x60;mirror_y&#x60; off they are equal.
*/
#ifndef Detector_module_H_
@@ -26,7 +26,7 @@ namespace org::openapitools::server::model
{
/// <summary>
/// One module of &#x60;custom_geometry&#x60;: where its readout pixel 0 goes in the assembled image and which way its fast and slow axes run there. &#x60;x0&#x60;/&#x60;y0&#x60; are that readout corner, before &#x60;mirror_y&#x60; - not the module&#39;s box in the stored image (&#x60;detector_module_placement&#x60; names a module by that box).
/// One module of &#x60;custom_geometry&#x60;: where its readout pixel 0 goes in the assembled image and which way its fast and slow readout axes run there. Frame: &#x60;custom_geometry&#x60; is BEFORE &#x60;mirror_y&#x60; (the image as assembled from the readout); &#x60;module_placement&#x60; is AFTER &#x60;mirror_y&#x60; (the image as stored, the only one a file reader sees). Same words in both: &#x60;x0&#x60;/&#x60;y0&#x60; is the image position of the module&#39;s readout pixel 0 and &#x60;fast_axis&#x60;/&#x60;slow_axis&#x60; say which way its fast and slow readout axes run in the image. With &#x60;mirror_y&#x60; on, a module&#39;s entry differs between the two by the mirror: for an image of height H, y0 (module_placement) &#x3D; H - 1 - y0 (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With &#x60;mirror_y&#x60; off they are equal.
/// </summary>
class Detector_module
{
+112 -110
View File
@@ -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<double>& 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<double>& 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<double> 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<double> const value)
{
m_X_step_x = value;
m_Fast_step = value;
}
double Detector_module_placement::getXStepY() const
std::vector<double> 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<double> 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;
}
+36 -48
View File
@@ -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 &#x60;mirror_y&#x60;; a pixel&#39;s centre at its index). The module is its box in the image: image pixel (x, y) with box_x &lt;&#x3D; x &lt; box_x + box_width and box_y &lt;&#x3D; y &lt; 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&#39;s lowest image x and y, whatever its readout direction: it is not the readout corner &#x60;x0&#x60;/&#x60;y0&#x60; of &#x60;custom_geometry&#x60;. As assembled: plane_x &#x3D; box_x, plane_y &#x3D; box_y, (x_step_x, x_step_y) &#x3D; (1, 0), (y_step_x, y_step_y) &#x3D; (0, 1).
* One module of the image and where it lies on the detector plane, as rugnux measures it. Frame: &#x60;custom_geometry&#x60; is BEFORE &#x60;mirror_y&#x60; (the image as assembled from the readout); &#x60;module_placement&#x60; is AFTER &#x60;mirror_y&#x60; (the image as stored, the only one a file reader sees). Same words in both: &#x60;x0&#x60;/&#x60;y0&#x60; is the image position of the module&#39;s readout pixel 0 and &#x60;fast_axis&#x60;/&#x60;slow_axis&#x60; say which way its fast and slow readout axes run in the image. With &#x60;mirror_y&#x60; on, a module&#39;s entry differs between the two by the mirror: for an image of height H, y0 (module_placement) &#x3D; H - 1 - y0 (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With &#x60;mirror_y&#x60; off they are equal. &#x60;x0&#x60;, &#x60;y0&#x60;, &#x60;fast_axis&#x60; and &#x60;slow_axis&#x60; 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&#39;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 &#x3D; x0, y0_plane &#x3D; y0, and each step is the unit vector of its axis (Xp &#x3D; [1, 0], Xn &#x3D; [-1, 0], Yp &#x3D; [0, 1], Yn &#x3D; [0, -1]).
*/
#ifndef Detector_module_placement_H_
#define Detector_module_placement_H_
#include "Detector_module_direction.h"
#include <vector>
#include <nlohmann/json.hpp>
namespace org::openapitools::server::model
{
/// <summary>
/// One module of the assembled image and where it lies on the detector plane, in pixels of the assembled image as stored (after &#x60;mirror_y&#x60;; a pixel&#39;s centre at its index). The module is its box in the image: image pixel (x, y) with box_x &lt;&#x3D; x &lt; box_x + box_width and box_y &lt;&#x3D; y &lt; 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&#39;s lowest image x and y, whatever its readout direction: it is not the readout corner &#x60;x0&#x60;/&#x60;y0&#x60; of &#x60;custom_geometry&#x60;. As assembled: plane_x &#x3D; box_x, plane_y &#x3D; box_y, (x_step_x, x_step_y) &#x3D; (1, 0), (y_step_x, y_step_y) &#x3D; (0, 1).
/// One module of the image and where it lies on the detector plane, as rugnux measures it. Frame: &#x60;custom_geometry&#x60; is BEFORE &#x60;mirror_y&#x60; (the image as assembled from the readout); &#x60;module_placement&#x60; is AFTER &#x60;mirror_y&#x60; (the image as stored, the only one a file reader sees). Same words in both: &#x60;x0&#x60;/&#x60;y0&#x60; is the image position of the module&#39;s readout pixel 0 and &#x60;fast_axis&#x60;/&#x60;slow_axis&#x60; say which way its fast and slow readout axes run in the image. With &#x60;mirror_y&#x60; on, a module&#39;s entry differs between the two by the mirror: for an image of height H, y0 (module_placement) &#x3D; H - 1 - y0 (custom_geometry), x0 is the same, and Yp and Yn swap in fast_axis/slow_axis. With &#x60;mirror_y&#x60; off they are equal. &#x60;x0&#x60;, &#x60;y0&#x60;, &#x60;fast_axis&#x60; and &#x60;slow_axis&#x60; 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&#39;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 &#x3D; x0, y0_plane &#x3D; y0, and each step is the unit vector of its axis (Xp &#x3D; [1, 0], Xn &#x3D; [-1, 0], Yp &#x3D; [0, 1], Yn &#x3D; [0, -1]).
/// </summary>
class Detector_module_placement
{
@@ -58,78 +60,64 @@ public:
/// Detector_module_placement members
/// <summary>
/// Lowest image x of the module&#39;s box (NXmx data_origin)
/// Image x of the module&#39;s readout pixel 0, after mirror_y
/// </summary>
int64_t getBoxX() const;
void setBoxX(int64_t const value);
int64_t getX0() const;
void setX0(int64_t const value);
/// <summary>
/// Lowest image y of the module&#39;s box (NXmx data_origin)
/// Image y of the module&#39;s readout pixel 0, after mirror_y
/// </summary>
int64_t getBoxY() const;
void setBoxY(int64_t const value);
int64_t getY0() const;
void setY0(int64_t const value);
/// <summary>
/// Width of the box in image pixels (NXmx data_size)
///
/// </summary>
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);
/// <summary>
/// Height of the box in image pixels (NXmx data_size)
///
/// </summary>
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);
/// <summary>
/// Plane x of image pixel (box_x, box_y)
/// Plane x of readout pixel 0, in image pixels
/// </summary>
double getPlaneX() const;
void setPlaneX(double const value);
double getX0Plane() const;
void setX0Plane(double const value);
/// <summary>
/// Plane y of image pixel (box_x, box_y)
/// Plane y of readout pixel 0, in image pixels
/// </summary>
double getPlaneY() const;
void setPlaneY(double const value);
double getY0Plane() const;
void setY0Plane(double const value);
/// <summary>
/// Plane x step for one image pixel along +x
/// Plane displacement (x, y) for one pixel along the fast readout axis
/// </summary>
double getXStepX() const;
void setXStepX(double const value);
std::vector<double> getFastStep() const;
void setFastStep(std::vector<double> const value);
/// <summary>
/// Plane y step for one image pixel along +x
/// Plane displacement (x, y) for one pixel along the slow readout axis
/// </summary>
double getXStepY() const;
void setXStepY(double const value);
/// <summary>
/// Plane x step for one image pixel along +y
/// </summary>
double getYStepX() const;
void setYStepX(double const value);
/// <summary>
/// Plane y step for one image pixel along +y
/// </summary>
double getYStepY() const;
void setYStepY(double const value);
std::vector<double> getSlowStep() const;
void setSlowStep(std::vector<double> 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<double> m_Fast_step;
double m_X_step_y;
double m_Y_step_x;
double m_Y_step_y;
std::vector<double> m_Slow_step;
};
+61 -55
View File
@@ -682,10 +682,11 @@ components:
description: |
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:
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:
+7 -6
View File
@@ -554,10 +554,11 @@ Assuming that Smargon is used as static positioner and not moving during the sca
</div></div></div></td></tr><tr class=""><td class="sc-dLMFU sc-eldPxv hZbWKe jXkfaY" kind="field" title="async_start"><span class="sc-gFqAkR eRqgPI"></span><span class="property-name">async_start</span></td><td class="sc-fPXMVe bAXEAa"><div><div><span class="sc-Nxspf sc-cfxfcM cXxAXt jrGEaR"></span><span class="sc-Nxspf sc-gFAWRd cXxAXt dYzdDr">boolean</span></div><div><span class="sc-Nxspf cXxAXt"> <!-- -->Default:<!-- --> </span> <span class="sc-Nxspf sc-ddjGPC cXxAXt jZOCAW">false</span></div> <div><div class="sc-eeDRCY sc-eBMEME gTGgei fbXBig"><p>When set to true, <code>/start</code> will not wait for detector and Jungfraujoch to be ready for the measurement.</p>
</div></div></div></td></tr><tr class=""><td class="sc-dLMFU sc-eldPxv sc-hCPjZK hZbWKe jXkfaY GCgeS" kind="field" title="module_placement"><span class="sc-gFqAkR eRqgPI"></span><button aria-label="expand module_placement"><span class="property-name">module_placement</span><svg class="sc-cwHptR ivsSRP" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button></td><td class="sc-fPXMVe bAXEAa"><div><div><span class="sc-Nxspf sc-cfxfcM cXxAXt jrGEaR">Array of </span><span class="sc-Nxspf sc-gFAWRd cXxAXt dYzdDr">objects</span><span class="sc-Nxspf sc-gmPhUn cXxAXt hjnRRA"> (<!-- -->detector_module_placement<!-- -->) </span></div> <div><div class="sc-eeDRCY sc-eBMEME gTGgei fbXBig"><p>Where the detector&#39;s modules lie for this dataset, as rugnux measures them: rugnux writes
<code>&lt;prefix&gt;_module_placement.json</code>, which holds exactly this field, to be merged into the dataset settings.
Each entry names its module by the module&#39;s box in the assembled image (<code>box_x</code>, <code>box_y</code>,
<code>box_width</code>, <code>box_height</code>), in any order; every module must be named once, else <code>/start</code> fails
with 400. The image is assembled as configured (<code>custom_geometry</code>); the placement goes into the
start message and the NXmx <code>module_NN</code> 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 (<code>x0</code>, <code>y0</code>,
<code>fast_axis</code>, <code>slow_axis</code>, after <code>mirror_y</code>; see <code>detector_module_placement</code>), in any order; every
module must be named once, and only modules of the detector, else <code>/start</code> fails with 400. The image
is assembled as configured (<code>custom_geometry</code>); the placement goes into the start message and the
NXmx <code>module_NN</code> groups, and the analysis takes it. Omitted or empty:
modules as assembled.</p>
</div></div></div></td></tr><tr class=""><td class="sc-dLMFU sc-eldPxv hZbWKe jXkfaY" kind="field" title="tokens"><span class="sc-gFqAkR eRqgPI"></span><span class="property-name">tokens</span></td><td class="sc-fPXMVe bAXEAa"><div><div><span class="sc-Nxspf sc-cfxfcM cXxAXt jrGEaR">Array of </span><span class="sc-Nxspf sc-gFAWRd cXxAXt dYzdDr">strings</span><span class="sc-Nxspf sc-cfxfcM sc-kAkpmW cXxAXt jrGEaR gZERHX">[ items<span> <span class="sc-Nxspf sc-fxwrCY cXxAXt eMVOei"> <!-- -->non-empty<!-- --> </span></span> ]</span></div> <div><div class="sc-eeDRCY sc-eBMEME gTGgei fbXBig"><p>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.</p>
</div></button></div><div><button class="sc-kzqdkY bXzzUN"><svg class="sc-cwHptR cSkZlF" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-dSIIpw bpEYcE">400<!-- --> </strong><div class="sc-eeDRCY sc-eBMEME sc-dCFHLb gTGgei kekOtA kCaLQd"><p>Input parsing or validation error</p>
</div></button></div><div><button class="sc-kzqdkY bXzzUN"><svg class="sc-cwHptR cSkZlF" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-dSIIpw bpEYcE">500<!-- --> </strong><div class="sc-eeDRCY sc-eBMEME sc-dCFHLb gTGgei kekOtA kCaLQd"><p>Error within Jungfraujoch code - see output message.</p>
</div></button></div></div></div><div class="sc-iGgWBj sc-gsFSXq ibAZSi eDeKjd"><div class="sc-ejfMa-d bNUPQg"><button class="sc-EgOXT cFEcOl"><span type="post" class="sc-eZYNyq fPdOXD http-verb post">post</span><span class="sc-iEXKAA gUNSDu">/start</span><svg class="sc-cwHptR iVFMLx" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-dlWCHZ kWacYr"><div class="sc-hHOBiw cndTNO"><div class="sc-eeDRCY sc-eBMEME gTGgei fbXBig"><p>Test Jungfraujoch system</p>
</div><div tabindex="0" role="button"><div class="sc-kWtpeL gEncxT"><span>http://localhost:5232</span>/start</div></div></div></div></div><div><h3 class="sc-kpDqfm eBBgSB"> <!-- -->Request samples<!-- --> </h3><div class="sc-bXCLTC dHowgL" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="tab_R_ii2a_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_ii2a_0" tabindex="0" data-rttab="true">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_ii2a_0" aria-labelledby="tab_R_ii2a_0"><div><div class="sc-bbSZdi gCVTxk"><span class="sc-fjvvzt jWReSk">Content type</span><div class="sc-JrDLc iTKzPJ">application/json</div></div><div class="sc-uVWWZ gQROPU"><div class="sc-esYiGF dWQNJI"><div class="sc-koXPp efufqz"><button><div class="sc-fhzFiK kwZDms">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div tabindex="0" class="sc-eeDRCY gTGgei sc-fXSgeo bnrBiz"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"images_per_trigger"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"ntrigger"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"image_time_us"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"beam_x_pxl"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"beam_y_pxl"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"detector_distance_mm"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"incident_energy_keV"</span>: <span class="token number">0.001</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"file_prefix"</span>: <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"images_per_file"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"space_group_number"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"sample_name"</span>: <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"compression"</span>: <span class="token string">&quot;bslz4&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"total_flux"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"transmission"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"beam_size_x_um"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"beam_size_y_um"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"goniometer"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"name"</span>: <span class="token string">&quot;omega&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"step"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"start"</span>: <span class="token number">50</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"vector"</span>: <button class="collapser" aria-label="eLine truncated
</div><div tabindex="0" role="button"><div class="sc-kWtpeL gEncxT"><span>http://localhost:5232</span>/start</div></div></div></div></div><div><h3 class="sc-kpDqfm eBBgSB"> <!-- -->Request samples<!-- --> </h3><div class="sc-bXCLTC dHowgL" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="tab_R_ii2a_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_ii2a_0" tabindex="0" data-rttab="true">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_ii2a_0" aria-labelledby="tab_R_ii2a_0"><div><div class="sc-bbSZdi gCVTxk"><span class="sc-fjvvzt jWReSk">Content type</span><div class="sc-JrDLc iTKzPJ">application/json</div></div><div class="sc-uVWWZ gQROPU"><div class="sc-esYiGF dWQNJI"><div class="sc-koXPp efufqz"><button><div class="sc-fhzFiK kwZDms">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div tabindex="0" class="sc-eeDRCY gTGgei sc-fXSgeo bnrBiz"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"images_per_trigger"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"ntrigger"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"image_time_us"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"beam_x_pxl"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"beam_y_pxl"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"detector_distance_mm"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"incident_energy_keV"</span>: <span class="token number">0.001</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"file_prefix"</span>: <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"images_per_file"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"space_group_number"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"sample_name"</span>: <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"compression"</span>: <span class="token string">&quot;bslz4&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"total_flux"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"transmission"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"beam_size_x_um"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"beam_size_y_um"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"goniometer"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"name"</span>: <span class="token string">&quot;omega&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"step"</span>: <span class="token number">0.1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"start"</span>: <span class="token number">50</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"vector"</span>: <button class="collapser" aria-label="eLine truncated
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.</p>
</div></div><div><h5 class="sc-dAlyuH jbQuod">query<!-- --> Parameters</h5><table class="sc-dAbbOL ccPhmY"><tbody><tr class="last "><td class="sc-dLMFU sc-eldPxv hZbWKe jXkfaY" kind="field" title="timeout"><span class="sc-gFqAkR eRqgPI"></span><span class="property-name">timeout</span></td><td class="sc-fPXMVe bAXEAa"><div><div><span class="sc-Nxspf sc-cfxfcM cXxAXt jrGEaR"></span><span class="sc-Nxspf sc-gFAWRd cXxAXt dYzdDr">integer</span><span> <span class="sc-Nxspf sc-fxwrCY cXxAXt eMVOei"> <!-- -->[ 0 .. 3600 ]<!-- --> </span></span></div><div><span class="sc-Nxspf cXxAXt"> <!-- -->Default:<!-- --> </span> <span class="sc-Nxspf sc-ddjGPC cXxAXt jZOCAW">60</span></div> <div><div class="sc-eeDRCY sc-eBMEME gTGgei fbXBig"><p>Timeout in seconds (0 == immediate response)</p>
@@ -1130,7 +1131,7 @@ then image might be replaced in the buffer between calling /images and /image.cb
</div></button></div></div></div><div class="sc-iGgWBj sc-gsFSXq ibAZSi eDeKjd"><div class="sc-ejfMa-d bNUPQg"><button class="sc-EgOXT cFEcOl"><span type="get" class="sc-eZYNyq qGfV http-verb get">get</span><span class="sc-iEXKAA gUNSDu">/version</span><svg class="sc-cwHptR iVFMLx" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-dlWCHZ kWacYr"><div class="sc-hHOBiw cndTNO"><div class="sc-eeDRCY sc-eBMEME gTGgei fbXBig"><p>Test Jungfraujoch system</p>
</div><div tabindex="0" role="button"><div class="sc-kWtpeL gEncxT"><span>http://localhost:5232</span>/version</div></div></div></div></div></div></div></div></div><div class="sc-dExYaf gZbfaQ"></div></div></div>
<script>
const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.3","info":{"title":"Jungfraujoch","description":"API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland).\nJungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU.\nJungfraujoch uses FPGA boards to acquire data at high data rates.\n\n# License Clarification\n\nWhile this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply**\nwhen this file is used solely to generate OpenAPI clients or when implementing applications that\ninteract with the API. Generated client code and applications using this API definition are not\nsubject to the GPL license requirements and may be distributed under terms of your choosing.\n\nThis exception is similar in spirit to the Linux Kernel's approach to userspace API headers and\nthe GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that\nuser programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not\nderivative works of the kernel and are not subject to the terms of the GPL.\n\nThis exception is intended to allow wider use of this API specification without imposing GPL\nrequirements on applications that merely interact with the API, regardless of whether they\ncommunicate through network calls or other mechanisms.\n","version":"1.0.0-rc.181","contact":{"name":"Filip Leonarski (Paul Scherrer Institute)","email":"filip.leonarski@psi.ch"},"license":{"name":"GPL-3.0","url":"https://www.gnu.org/licenses/gpl-3.0.html"}},"servers":[{"url":"http://localhost:5232","description":"Test Jungfraujoch system"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Per-dataset secret. `/start` may carry one or more equivalent `tokens`; while the current\ndataset has any, the operations marked with this scheme require `Authorization: Bearer <token>`\nand answer 401 otherwise. A dataset started without `tokens` is not protected and the header is ignored.\n"}},"parameters":{"binning":{"in":"query","name":"binning","required":false,"schema":{"default":1,"type":"integer"},"description":"Binning of frames for the plot (0 = default binning)"},"experimental_coord":{"in":"query","name":"experimental_coord","required":false,"schema":{"type":"boolean","default":false},"description":"If measurement has goniometer axis defined, plot X-axis will represent rotation angle\nIf measurement has grid scan defined, plot X-axis and Y-axis will represent grid position, Z will be used as the final value\nFor still measurement the number is ignored\n"},"azint_unit":{"in":"query","name":"azint_unit","required":false,"schema":{"type":"string","enum":["Q_recipA","d_A","two_theta_deg"],"default":"Q_recipA"},"description":"Unit used for azim int.\n"},"fill_value":{"in":"query","name":"fill","required":false,"schema":{"type":"number","format":"float"},"description":"Fill value for elements that were missed during data collection\n"},"plot_type":{"in":"query","name":"type","required":true,"description":"Type of requested plot","schema":{"type":"string","enum":["bkg_estimate","spindle_blind_fraction","azint","azint_1d","spot_count","spot_count_low_res","spot_count_indexed","spot_count_ice","indexing_rate","indexing_lattice_count","indexing_unit_cell_length","indexing_unit_cell_angle","profile_radius","mosaicity","b_factor","error_pixels","saturated_pixels","image_collection_efficiency","receiver_delay","receiver_free_send_buf","strong_pixels","roi_sum","roi_mean","roi_max_count","roi_pixels","roi_weighted_x","roi_weighted_y","packets_received","max_pixel_value","resolution_estimate","pixel_sum","processing_time","beam_center_x","beam_center_y","integrated_reflections","image_scale_factor","image_scale_cc","compression_ratio","ice_ring_score"]}},"roi":{"in":"query","name":"roi","required":false,"schema":{"type":"string","minLength":1},"description":"Name of ROI for which plot is requested"},"image_id":{"in":"query","name":"id","required":false,"schema":{"type":"integer","format":"int64","default":-1,"minimum":-2},"description":"Image ID in the image buffer. Special values: -1 - last image in the buffer, -2: last indexed image in the buffer"},"saturation":{"in":"query","name":"saturation","required":false,"schema":{"type":"number","format":"float","minimum":-32767,"maximum":32767},"description":"Saturation value to set contrast in the preview image; if not provided, then autocontrast procedure is used"},"background":{"in":"query","name":"background","required":false,"schema":{"type":"number","format":"float","default":0,"minimum":-32767,"maximum":32767},"description":"Background value to set contrast in the preview image"},"show_spots":{"in":"query","name":"show_spots","schema":{"type":"boolean","default":true},"description":"Show spot finding results on the image"},"show_predictions":{"in":"query","name":"show_predictions","schema":Line truncated
const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.3","info":{"title":"Jungfraujoch","description":"API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland).\nJungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU.\nJungfraujoch uses FPGA boards to acquire data at high data rates.\n\n# License Clarification\n\nWhile this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply**\nwhen this file is used solely to generate OpenAPI clients or when implementing applications that\ninteract with the API. Generated client code and applications using this API definition are not\nsubject to the GPL license requirements and may be distributed under terms of your choosing.\n\nThis exception is similar in spirit to the Linux Kernel's approach to userspace API headers and\nthe GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that\nuser programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not\nderivative works of the kernel and are not subject to the terms of the GPL.\n\nThis exception is intended to allow wider use of this API specification without imposing GPL\nrequirements on applications that merely interact with the API, regardless of whether they\ncommunicate through network calls or other mechanisms.\n","version":"1.0.0-rc.181","contact":{"name":"Filip Leonarski (Paul Scherrer Institute)","email":"filip.leonarski@psi.ch"},"license":{"name":"GPL-3.0","url":"https://www.gnu.org/licenses/gpl-3.0.html"}},"servers":[{"url":"http://localhost:5232","description":"Test Jungfraujoch system"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Per-dataset secret. `/start` may carry one or more equivalent `tokens`; while the current\ndataset has any, the operations marked with this scheme require `Authorization: Bearer <token>`\nand answer 401 otherwise. A dataset started without `tokens` is not protected and the header is ignored.\n"}},"parameters":{"binning":{"in":"query","name":"binning","required":false,"schema":{"default":1,"type":"integer"},"description":"Binning of frames for the plot (0 = default binning)"},"experimental_coord":{"in":"query","name":"experimental_coord","required":false,"schema":{"type":"boolean","default":false},"description":"If measurement has goniometer axis defined, plot X-axis will represent rotation angle\nIf measurement has grid scan defined, plot X-axis and Y-axis will represent grid position, Z will be used as the final value\nFor still measurement the number is ignored\n"},"azint_unit":{"in":"query","name":"azint_unit","required":false,"schema":{"type":"string","enum":["Q_recipA","d_A","two_theta_deg"],"default":"Q_recipA"},"description":"Unit used for azim int.\n"},"fill_value":{"in":"query","name":"fill","required":false,"schema":{"type":"number","format":"float"},"description":"Fill value for elements that were missed during data collection\n"},"plot_type":{"in":"query","name":"type","required":true,"description":"Type of requested plot","schema":{"type":"string","enum":["bkg_estimate","spindle_blind_fraction","azint","azint_1d","spot_count","spot_count_low_res","spot_count_indexed","spot_count_ice","indexing_rate","indexing_lattice_count","indexing_unit_cell_length","indexing_unit_cell_angle","profile_radius","mosaicity","b_factor","error_pixels","saturated_pixels","image_collection_efficiency","receiver_delay","receiver_free_send_buf","strong_pixels","roi_sum","roi_mean","roi_max_count","roi_pixels","roi_weighted_x","roi_weighted_y","packets_received","max_pixel_value","resolution_estimate","pixel_sum","processing_time","beam_center_x","beam_center_y","integrated_reflections","image_scale_factor","image_scale_cc","compression_ratio","ice_ring_score"]}},"roi":{"in":"query","name":"roi","required":false,"schema":{"type":"string","minLength":1},"description":"Name of ROI for which plot is requested"},"image_id":{"in":"query","name":"id","required":false,"schema":{"type":"integer","format":"int64","default":-1,"minimum":-2},"description":"Image ID in the image buffer. Special values: -1 - last image in the buffer, -2: last indexed image in the buffer"},"saturation":{"in":"query","name":"saturation","required":false,"schema":{"type":"number","format":"float","minimum":-32767,"maximum":32767},"description":"Saturation value to set contrast in the preview image; if not provided, then autocontrast procedure is used"},"background":{"in":"query","name":"background","required":false,"schema":{"type":"number","format":"float","default":0,"minimum":-32767,"maximum":32767},"description":"Background value to set contrast in the preview image"},"show_spots":{"in":"query","name":"show_spots","schema":{"type":"boolean","default":true},"description":"Show spot finding results on the image"},"show_predictions":{"in":"query","name":"show_predictions","schema":Line truncated
var container = document.getElementById('redoc');
Redoc.hydrate(__redoc_state, container);
+2 -2
View File
@@ -446,12 +446,12 @@ std::optional<float> DatasetSettings::GetBandwidthFWHM() const {
return bandwidth_fwhm;
}
DatasetSettings &DatasetSettings::ModulePlacement(const std::vector<DetectorModule> &input) {
DatasetSettings &DatasetSettings::ModulePlacement(const std::vector<::ModulePlacement> &input) {
module_placement = input;
return *this;
}
const std::vector<DetectorModule> &DatasetSettings::GetModulePlacement() const {
const std::vector<::ModulePlacement> &DatasetSettings::GetModulePlacement() const {
return module_placement;
}
+3 -3
View File
@@ -67,7 +67,7 @@ class DatasetSettings {
std::optional<float> bandwidth_fwhm; // relative X-ray bandwidth, FWHM of dlambda/lambda (e.g. 0.01 for 1%)
// Where the modules are for this dataset (rugnux's <prefix>_module_placement.json); empty: as the detector
// assembles them.
std::vector<DetectorModule> module_placement;
std::vector<::ModulePlacement> module_placement;
float poni_rot_1_rad;
float poni_rot_2_rad;
float poni_rot_3_rad;
@@ -118,7 +118,7 @@ public:
DatasetSettings& PixelValueHighThreshold(const std::optional<int64_t> &input);
DatasetSettings& PolarizationFactor(const std::optional<float> &input);
DatasetSettings& BandwidthFWHM(const std::optional<float> &input);
DatasetSettings& ModulePlacement(const std::vector<DetectorModule> &input);
DatasetSettings& ModulePlacement(const std::vector<::ModulePlacement> &input);
DatasetSettings& PoniRot1_rad(float input);
DatasetSettings& PoniRot2_rad(float input);
DatasetSettings& PoniRot3_rad(float input);
@@ -175,7 +175,7 @@ public:
std::optional<float> GetPolarizationFactor() const;
std::optional<float> GetBandwidthFWHM() const;
const std::vector<DetectorModule> &GetModulePlacement() const;
const std::vector<::ModulePlacement> &GetModulePlacement() const;
float GetPoniRot1_rad() const;
float GetPoniRot2_rad() const;
float GetPoniRot3_rad() const;
+84 -13
View File
@@ -22,6 +22,61 @@ DetectorModule DetectorModule::AsImaged(int64_t x0, int64_t y0, int64_t width, i
return m;
}
int64_t DetectorModule::ReadoutX0() const {
return (fast == Direction::Xneg || slow == Direction::Xneg) ? x0 + width - 1 : x0;
}
int64_t DetectorModule::ReadoutY0() const {
return (fast == Direction::Yneg || slow == Direction::Yneg) ? y0 + height - 1 : y0;
}
std::pair<int64_t, int64_t> DirectionStep(DetectorModuleGeometry::Direction d) {
switch (d) {
case DetectorModuleGeometry::Direction::Xneg: return {-1, 0};
case DetectorModuleGeometry::Direction::Ypos: return {0, 1};
case DetectorModuleGeometry::Direction::Yneg: return {0, -1};
default: return {1, 0};
}
}
std::string DirectionName(DetectorModuleGeometry::Direction d) {
switch (d) {
case DetectorModuleGeometry::Direction::Xneg: return "Xn";
case DetectorModuleGeometry::Direction::Ypos: return "Yp";
case DetectorModuleGeometry::Direction::Yneg: return "Yn";
default: return "Xp";
}
}
DetectorModuleGeometry::Direction DirectionOfStep(double dx, double dy) {
if (std::fabs(dx) > std::fabs(dy))
return dx > 0 ? DetectorModuleGeometry::Direction::Xpos : DetectorModuleGeometry::Direction::Xneg;
return dy > 0 ? DetectorModuleGeometry::Direction::Ypos : DetectorModuleGeometry::Direction::Yneg;
}
DetectorModuleGeometry::Direction DirectionFromName(const std::string &name) {
if (name == "Xp") return DetectorModuleGeometry::Direction::Xpos;
if (name == "Xn") return DetectorModuleGeometry::Direction::Xneg;
if (name == "Yp") return DetectorModuleGeometry::Direction::Ypos;
if (name == "Yn") return DetectorModuleGeometry::Direction::Yneg;
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Not a module direction: " + name);
}
ModulePlacement PlacementOf(const DetectorModule &m) {
ModulePlacement p;
p.x0 = m.ReadoutX0();
p.y0 = m.ReadoutY0();
p.fast = m.fast;
p.slow = m.slow;
std::tie(p.x0_plane, p.y0_plane) = m.ImageToPlane(static_cast<double>(p.x0), static_cast<double>(p.y0));
// One readout pixel along an axis is one image pixel along its direction.
const auto [ufx, ufy] = DirectionStep(m.fast);
const auto [usx, usy] = DirectionStep(m.slow);
p.fast_step = {ufx * m.fx + ufy * m.sx, ufx * m.fy + ufy * m.sy};
p.slow_step = {usx * m.fx + usy * m.sx, usx * m.fy + usy * m.sy};
return p;
}
bool DetectorModule::ContainsImagePixel(double x, double y) const {
// A pixel's centre is at its index, so the module covers [x0 - 0.5, x0 + width - 0.5).
return x >= static_cast<double>(x0) - 0.5 && x < static_cast<double>(x0 + width) - 0.5
@@ -195,35 +250,51 @@ DetectorModules ModulesAsImaged(const DetectorGeometry &geom) {
const double xb = xa + cols * f.x + lines * s.x, yb = ya + cols * f.y + lines * s.y;
const auto x0 = static_cast<int64_t>(std::lround(std::min(xa, xb)));
const auto y0 = static_cast<int64_t>(std::lround(std::min(ya, yb)));
out.push_back(DetectorModule::AsImaged(x0, y0, std::lround(std::abs(xb - xa)) + 1,
std::lround(std::abs(yb - ya)) + 1));
DetectorModule mod = DetectorModule::AsImaged(x0, y0, std::lround(std::abs(xb - xa)) + 1,
std::lround(std::abs(yb - ya)) + 1);
mod.fast = DirectionOfStep(f.x, f.y);
mod.slow = DirectionOfStep(s.x, s.y);
out.push_back(mod);
}
return DetectorModules(out);
}
DetectorModules ModulesPlaced(const DetectorModules &target, const std::vector<DetectorModule> &placement) {
const auto box = [](const DetectorModule &m) {
return "(" + std::to_string(m.x0) + ", " + std::to_string(m.y0) + ") " + std::to_string(m.width) + " x "
+ std::to_string(m.height);
};
const auto same_box = [](const DetectorModule &a, const DetectorModule &b) {
return a.x0 == b.x0 && a.y0 == b.y0 && a.width == b.width && a.height == b.height;
DetectorModules ModulesPlaced(const DetectorModules &target, const std::vector<ModulePlacement> &placement) {
const auto name = [](int64_t x0, int64_t y0, DetectorModuleGeometry::Direction fast,
DetectorModuleGeometry::Direction slow) {
return "x0 = " + std::to_string(x0) + ", y0 = " + std::to_string(y0) + ", " + DirectionName(fast) + "/"
+ DirectionName(slow);
};
std::vector<DetectorModule> out = target.Get();
std::vector<bool> named(out.size(), false);
for (const auto &p: placement) {
const auto it = std::find_if(out.begin(), out.end(), [&](const DetectorModule &m) { return same_box(m, p); });
const auto it = std::find_if(out.begin(), out.end(), [&](const DetectorModule &m) {
return m.ReadoutX0() == p.x0 && m.ReadoutY0() == p.y0 && m.fast == p.fast && m.slow == p.slow;
});
if (it == out.end() || named[it - out.begin()])
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Module placement: the module at " + box(p)
"Module placement: the module " + name(p.x0, p.y0, p.fast, p.slow)
+ (it == out.end() ? " is not a module of this detector" : " is placed twice"));
named[it - out.begin()] = true;
*it = p;
// Image +x and +y in readout steps: image (dx, dy) from pixel 0 is readout
// (i, j) = (dx ufx + dy ufy, dx usx + dy usy).
const auto [ufx, ufy] = DirectionStep(p.fast);
const auto [usx, usy] = DirectionStep(p.slow);
DetectorModule &m = *it;
m.fx = ufx * p.fast_step[0] + usx * p.slow_step[0];
m.fy = ufx * p.fast_step[1] + usx * p.slow_step[1];
m.sx = ufy * p.fast_step[0] + usy * p.slow_step[0];
m.sy = ufy * p.fast_step[1] + usy * p.slow_step[1];
const auto dx = static_cast<double>(m.x0 - p.x0), dy = static_cast<double>(m.y0 - p.y0);
m.ox = p.x0_plane + dx * m.fx + dy * m.sx;
m.oy = p.y0_plane + dx * m.fy + dy * m.sy;
}
for (size_t m = 0; m < out.size(); m++)
if (!named[m])
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Module placement: the detector's module at " + box(out[m]) + " is not placed");
"Module placement: the detector's module "
+ name(out[m].ReadoutX0(), out[m].ReadoutY0(), out[m].fast, out[m].slow)
+ " is not placed");
return DetectorModules(out);
}
+39 -4
View File
@@ -5,11 +5,14 @@
#include <cstddef>
#include <cstdint>
#include <array>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "ModuleShift.h"
#include "DetectorModuleGeometry.h"
class DetectorGeometry;
@@ -21,15 +24,25 @@ class DetectorGeometry;
// (ox, oy) + (x - x0) (fx, fy) + (y - y0) (sx, sy)
// - the NXdetector_module description (data_origin, data_size, module_offset, fast_pixel_direction,
// slow_pixel_direction) taken in the plane. A pixel's centre is at its index, as everywhere in the image.
//
// `fast` and `slow` say which way the module's readout axes run in the image; its readout pixel 0 is the
// corner of the box they start from (ReadoutX0/Y0). They name the module in a placement (ModulePlacement)
// and change nothing in the mapping above. A module whose readout is not known is its own pixel array: +x, +y.
struct DetectorModule {
using Direction = DetectorModuleGeometry::Direction;
int64_t x0 = 0, y0 = 0, width = 0, height = 0;
double ox = 0, oy = 0;
double fx = 1, fy = 0;
double sx = 0, sy = 1;
Direction fast = Direction::Xpos, slow = Direction::Ypos;
// The module where the image has it: every pixel maps onto itself.
static DetectorModule AsImaged(int64_t x0, int64_t y0, int64_t width, int64_t height);
// Image position of the readout pixel 0.
[[nodiscard]] int64_t ReadoutX0() const;
[[nodiscard]] int64_t ReadoutY0() const;
[[nodiscard]] bool ContainsImagePixel(double x, double y) const;
[[nodiscard]] std::pair<double, double> ImageToPlane(double x, double y) const;
[[nodiscard]] std::pair<double, double> PlaneToImage(double px, double py) const;
@@ -40,6 +53,28 @@ struct DetectorModule {
void Apply(const ModuleShift &shift);
};
// The image unit step of a readout direction (Xp = (1, 0), Yn = (0, -1), ...), and its name in
// jfjoch_api.yaml (detector_module_direction: Xp, Xn, Yp, Yn).
std::pair<int64_t, int64_t> DirectionStep(DetectorModuleGeometry::Direction d);
std::string DirectionName(DetectorModuleGeometry::Direction d);
// The direction of an image step along x or y (the larger of the two).
DetectorModuleGeometry::Direction DirectionOfStep(double dx, double dy);
DetectorModuleGeometry::Direction DirectionFromName(const std::string &name);
// One entry of a module placement (dataset_settings.module_placement of jfjoch_api.yaml, docs/RUGNUX_FORMATS.md):
// the module named by its readout pixel 0 (x0, y0) and readout axes in the image, and where it lies on the
// plane: readout pixel (i, j) at (x0_plane, y0_plane) + i fast_step + j slow_step.
struct ModulePlacement {
int64_t x0 = 0, y0 = 0;
DetectorModuleGeometry::Direction fast = DetectorModuleGeometry::Direction::Xpos;
DetectorModuleGeometry::Direction slow = DetectorModuleGeometry::Direction::Ypos;
double x0_plane = 0, y0_plane = 0;
std::array<double, 2> fast_step{1, 0}, slow_step{0, 1};
};
// The placement entry of a module: where it is now.
ModulePlacement PlacementOf(const DetectorModule &m);
// A point in the image rugnux reads, on panel `panel` (an index into DetectorModules; -1 when the table is
// empty and the whole image is one flat panel).
struct PanelPoint {
@@ -123,10 +158,10 @@ public:
DetectorModules ModulesAsImaged(const DetectorGeometry &geom);
// `target` with its modules where `placement` puts them (a module placement file: docs/RUGNUX_FORMATS.md). A
// placement entry names its module by the module's box in the image (x0, y0, width, height), never by its
// place in the list: each entry has to match one module of `target` and every module of `target` has to
// be named, else this throws, naming the first that does not match.
DetectorModules ModulesPlaced(const DetectorModules &target, const std::vector<DetectorModule> &placement);
// placement entry names its module by its readout pixel 0 and readout axes in the image (x0, y0, fast, slow),
// never by its place in the list: each entry has to match one module of `target` exactly and every module
// of `target` has to be named once, else this throws, naming the first that does not match.
DetectorModules ModulesPlaced(const DetectorModules &target, const std::vector<ModulePlacement> &placement);
// The modules of a JUNGFRAU image where only its pixel mask says where they are: every region of pixels
// clear of the module-gap bit (PixelMask::ModuleGapPixelBit) has to be one whole module,
+1 -1
View File
@@ -32,7 +32,7 @@ There are minor differences at the moment:
| mirror_y | bool | Whether the assembled image is mirrored in Y relative to the detector's raw readout order. True is the MX convention - row 0 at the top of the detector seen from the sample - and is what absence of the key means | |
| detector_orientation_mirror_y | bool | Whether the assembled image is mirrored in Y relative to the frame the PONI angles are stated in. A different setting from `mirror_y` above, which is about the module layout; this one changes no pixel. Absence means false | |
| detector_orientation_quarter_turns | int | How many multiples of 90 degrees about the beam the assembled image is turned by, relative to the frame the PONI angles are stated in (0-3). Absence means 0 | |
| detector_modules | Array(object) (optional) | Panels: where the image's pixels lie on the detector plane the geometry describes. Each has `x0`, `y0`, `width`, `height` (int, the panel's box in the image) and `ox`, `oy`, `fx`, `fy`, `sx`, `sy` (double, plane pixels): image pixel (x, y) of the panel is at `(ox, oy) + (x - x0) (fx, fy) + (y - y0) (sx, sy)`. Absent for a flat detector, the image being the plane; spot and reflection positions are always plane coordinates | |
| detector_modules | Array(object) (optional) | Panels: where the image's pixels lie on the detector plane the geometry describes. Each has `x0`, `y0`, `width`, `height` (int, the panel's box in the image) and `ox`, `oy`, `fx`, `fy`, `sx`, `sy` (double, plane pixels): image pixel (x, y) of the panel is at `(ox, oy) + (x - x0) (fx, fy) + (y - y0) (sx, sy)`; `fast_axis`, `slow_axis` (string `Xp`/`Xn`/`Yp`/`Yn`, optional, default `Xp`/`Yp`) say which way the panel's readout axes run in the image, naming it in a module placement. Absent for a flat detector, the image being the plane; spot and reflection positions are always plane coordinates | |
| incident_energy | float | X-ray energy \[eV\] | X |
| incident_wavelength | float | X-ray wavelength \[Angstrom\] | X |
| incident_wavelength_spread | float (optional) | FWHM of the X-ray wavelength distribution \[Angstrom\] (NXmx incident_wavelength_spread); omitted when the beam is monochromatic | |
+1
View File
@@ -282,6 +282,7 @@ placed them, into `<prefix>_modules.h5` in the same layout, and reads the same g
| `data_size` | `[rows, columns]` of the module |
| `module_offset` | translation (m) from the image's pixel (0, 0) to the module's first pixel; `depends_on` the detector's `transformations/translation` |
| `fast_pixel_direction`, `slow_pixel_direction` | translation of one pixel (m) along the module's columns and rows; `depends_on` the module's `module_offset` |
| `readout_fast_axis`, `readout_slow_axis` | not NXmx: which way the module's fast and slow readout axes run in the stored image (`Xp`, `Xn`, `Yp`, `Yn`); with the box they give its readout pixel 0, which names the module in a module placement (`dataset_settings.module_placement`) |
Vectors are in the McStas frame. A pixel (row i, column j) of a module is at
`translation + module_offset + j * fast_pixel_direction + i * slow_pixel_direction`, which is how
+9 -5
View File
@@ -28,7 +28,8 @@ and answer `401` otherwise; `/statistics` omits its `measurement` block instead.
`/start` also takes an optional `module_placement`: where the JUNGFRAU modules lie, as `rugnux`
measures them on an earlier run. rugnux writes it as `<prefix>_module_placement.json`, a fragment of the
dataset settings (`{"module_placement": [...]}`) to merge into the `/start` body. Each entry names its
module by the module's box in the assembled image as stored (`box_x`, `box_y`, `box_width`, `box_height`),
module as `custom_geometry` does - `x0`, `y0` (the image position of its readout pixel 0) and
`fast_axis`, `slow_axis` - but in the image as stored, after `mirror_y` (see below),
so the order does not matter, and a list that does not name exactly the detector's modules (one left out,
named twice, or not the detector's) is refused with `400`. The module count and the assembly are the
configuration's (`custom_geometry` below); the placement only moves the modules relative to one another,
@@ -38,10 +39,13 @@ and goes into the start message, the master file's `module_NN` groups and the br
## Broker configuration
`jfjoch_broker` requires JSON configuration files. The file is described by OpenAPI structure `jfjoch_settings` defined in `jfjoch_api.yaml` file.
It is recommended to go through example files in the `etc/`.
In `custom_geometry`, a module's `x0`, `y0` are where its readout pixel 0 goes in the image, before
`mirror_y`, and `fast_axis`/`slow_axis` say which way its readout runs from there. The module placement of
`/start` does not use them: it names a module by its box in the stored image (`box_x`, `box_y`: the
module's lowest x and y after mirroring, whatever its readout direction).
In `custom_geometry`, a module's `x0`, `y0` are where its readout pixel 0 goes in the image, and
`fast_axis`/`slow_axis` say which way its fast and slow readout axes run from there - in the image as
assembled, **before** `mirror_y`. The `module_placement` of `/start` uses the same words, but in the image
as stored, **after** `mirror_y`, because that is the only image a file reader (rugnux) sees. With `mirror_y`
on the two differ 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.
Example configuration (not every section is shown):
+1 -1
View File
@@ -361,7 +361,7 @@ Defaults are taken from the input file; override them to reprocess with a correc
| `--saturation <num>` | Pixel value from which a pixel is overloaded, in the units the frames are read in. Replaces the file's limit and the one stills read off the frames |
| `--crystfel-geometry <file>` | Place the panels of an HDF5/CXI input by a CrystFEL geometry file instead of the file's own metadata: module geometry, camera length, mask arrays and bad regions (optional; [Serial XFEL inputs](RUGNUX_FORMATS.md#serial-xfel-inputs)). Photon energy, camera length and - where the geometry names no mask array - the masks otherwise come from the data files (Cheetah CXI, SACLA) or the command line; gain and saturation are measured. Tilted panels, `dimN` layouts, per-panel data and detector shifts are refused |
| `--module-geometry <file>` | Where the detector's modules are: an NXmx file with one NXdetector_module per module (what a run writes as `<prefix>_modules.h5`) or a CrystFEL geometry file whose panels are laid on the image. Overrides the input's own |
| `--module-placement <file.json>` | Place the modules as a module placement file says (`<prefix>_module_placement.json` of an earlier run, the `module_placement` fragment of the broker's dataset settings; [Module placement file](RUGNUX_FORMATS.md#module-placement-file-prefix_module_placementjson)). Entries name modules by their box in the image (`box_x`, `box_y`, `box_width`, `box_height`); one that matches no module, one named twice, or a module left out, is an error |
| `--module-placement <file.json>` | Place the modules as a module placement file says (`<prefix>_module_placement.json` of an earlier run, the `module_placement` fragment of the broker's dataset settings; [Module placement file](RUGNUX_FORMATS.md#module-placement-file-prefix_module_placementjson)). Entries name modules by their readout pixel 0 and readout axes in the image as stored, after `mirror_y` (`x0`, `y0`, `fast_axis`, `slow_axis`: the words of the broker's `custom_geometry`, which is before `mirror_y`); one that matches no module, one named twice, or a module left out, is an error |
| `--panel-refine[=off\|diagnose]` | Stills on a detector of several modules: by default the geometry bundle adjustment places each module as a rigid block where that predicts held-out spots better - the whole placement must beat the given one, and each module keeps its move only where its own held-out spots are predicted better, the others staying where they were given. `--panel-refine` keeps every move the spots support, without these tests; `=off` keeps the modules as given (a CrystFEL `.geom` included). A placement used is written to `<prefix>_modules.h5`; on stills `<prefix>_module_placement.json` is written unless `=off`, also where every module was held. `=diagnose` also runs a set of tests on the placement and writes them to the log |
| `--mask-good [<file>:]<dataset>[@<bits>]` / `--mask-bad [<file>:]<dataset>[@<bits>]` | HDF5/CXI stills: a mask array in the frames' stored layout, in `<file>` or in each data file; usable only where non-zero (or with all `<bits>`) / unusable where non-zero (or with any `<bits>`). Repeatable ([Serial XFEL inputs](RUGNUX_FORMATS.md#serial-xfel-inputs)) |
| `--rot1 <num>` | PONI detector rotation 1 (rad). On stills, `--rot1` or `--rot2` holds the tilt (not refined); without them stills refine the tilt where held-out frames agree |
+18 -13
View File
@@ -165,9 +165,9 @@ placement too - read back with `--module-placement` and taken by the broker's `/
```json
{
"module_placement": [
{"box_height": 514, "box_width": 1030, "box_x": 0, "box_y": 0,
"plane_x": 0.31, "plane_y": -0.42,
"x_step_x": 1.0, "x_step_y": 0.0002, "y_step_x": -0.0002, "y_step_y": 1.0},
{"x0": 0, "y0": 1023, "fast_axis": "Xp", "slow_axis": "Yn",
"x0_plane": 0.31, "y0_plane": 1022.58,
"fast_step": [1.0, 0.0002], "slow_step": [0.0002, -1.0]},
...
]
}
@@ -179,16 +179,21 @@ read and written through the same OpenAPI model the broker uses; the keys come i
One entry per module, in pixels of the image as it is stored (for a JUNGFRAU: as the broker assembles
it, after `mirror_y`; a pixel's centre at its index):
* `box_x`, `box_y`, `box_width`, `box_height` - the module's box in the image (NXmx `data_origin`,
`data_size`): `box_x`, `box_y` are its lowest image x and y, whatever way its readout runs, so not the
readout corner `x0`, `y0` of a broker's `custom_geometry`. They name the module: entries match modules
by their box, not by their place in the list, so the order of a broker's `custom_geometry`, a master's
`module_NN` groups or a reader's panels does not matter. rugnux's log names modules the same way.
* `plane_x`, `plane_y`, `x_step_x`, `x_step_y`, `y_step_x`, `y_step_y` - where the box lies on the
detector plane: image pixel (x, y) of the module is at
(plane_x, plane_y) + (x - box_x) (x_step_x, x_step_y) + (y - box_y) (y_step_x, y_step_y).
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)`; the refinement's fraction of a pixel and small turn are kept.
* `x0`, `y0`, `fast_axis`, `slow_axis` - the module's readout pixel 0 (the first pixel of its fast/slow
readout) as image x and y, and which way its fast and slow readout axes run in the image
(`Xp`, `Xn`, `Yp`, `Yn`). They are the words of a broker's `custom_geometry`, but in the stored image:
with `mirror_y` on, `y0` here is H - 1 - `y0` there (H the image height) and `Yp`/`Yn` swap. For a
module of a stored file they come from its NXmx `module_NN` group (`data_origin`, `data_size`, and the
readout axes Jungfraujoch writes beside `fast_pixel_direction`/`slow_pixel_direction`; a group without
them, or a panel of a CrystFEL geometry, is read along its own pixel array, `Xp`/`Yp` from its first
pixel), or from how a reader turns a panel into the image. They name the module: entries match modules
exactly by these four, not by their place in the list, and with the module size they give its box.
rugnux's log names modules the same way.
* `x0_plane`, `y0_plane`, `fast_step`, `slow_step` - where the module lies on the detector plane:
readout pixel (i, j) - i along the fast axis, j along the slow axis from pixel 0 - is 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], `Yn` = [0, -1], ...); the refinement's fraction of a pixel and small turn are kept.
A placement is valid only for the image layout it was measured on. `--module-placement <file>` and `/start` refuse
it, naming the module, where an entry matches no module of the image or a module has no entry - another
+1 -1
View File
@@ -45,7 +45,7 @@ Name | Type | Description | Notes
**max_spot_count** | **int** | Maximum number of spots that are saved/used for indexing; spots with highest intensity are selected | [optional] [default to 250]
**detect_ice_rings** | **bool** | Flag spots as ice rings and reduce their effect on indexing | [optional]
**async_start** | **bool** | When set to true, &#x60;/start&#x60; will not wait for detector and Jungfraujoch to be ready for the measurement. | [optional] [default to False]
**module_placement** | [**List[DetectorModulePlacement]**](DetectorModulePlacement.md) | Where the detector&#39;s modules lie for this dataset, as rugnux measures them: rugnux writes &#x60;&lt;prefix&gt;_module_placement.json&#x60;, which holds exactly this field, to be merged into the dataset settings. Each entry names its module by the module&#39;s box in the assembled image (&#x60;box_x&#x60;, &#x60;box_y&#x60;, &#x60;box_width&#x60;, &#x60;box_height&#x60;), in any order; every module must be named once, else &#x60;/start&#x60; fails with 400. The image is assembled as configured (&#x60;custom_geometry&#x60;); the placement goes into the start message and the NXmx &#x60;module_NN&#x60; groups, and the analysis takes it. Omitted or empty: modules as assembled. | [optional]
**module_placement** | [**List[DetectorModulePlacement]**](DetectorModulePlacement.md) | Where the detector&#39;s modules lie for this dataset, as rugnux measures them: rugnux writes &#x60;&lt;prefix&gt;_module_placement.json&#x60;, 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 (&#x60;x0&#x60;, &#x60;y0&#x60;, &#x60;fast_axis&#x60;, &#x60;slow_axis&#x60;, after &#x60;mirror_y&#x60;; see &#x60;detector_module_placement&#x60;), in any order; every module must be named once, and only modules of the detector, else &#x60;/start&#x60; fails with 400. The image is assembled as configured (&#x60;custom_geometry&#x60;); the placement goes into the start message and the NXmx &#x60;module_NN&#x60; groups, and the analysis takes it. Omitted or empty: modules as assembled. | [optional]
**tokens** | **List[str]** | 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 &#x60;bearerAuth&#x60; security scheme require &#x60;Authorization: Bearer &lt;token&gt;&#x60; and answer 401 otherwise. Every accepted &#x60;/start&#x60; replaces the previous dataset&#39;s tokens; omitted or empty means no protection. Never returned by any endpoint. | [optional]
**xray_fluorescence_spectrum** | [**DatasetSettingsXrayFluorescenceSpectrum**](DatasetSettingsXrayFluorescenceSpectrum.md) | | [optional]
+1 -1
View File
@@ -1,6 +1,6 @@
# DetectorModule
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.
## Properties
@@ -1,21 +1,19 @@
# DetectorModulePlacement
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]).
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**box_x** | **int** | Lowest image x of the module&#39;s box (NXmx data_origin) |
**box_y** | **int** | Lowest image y of the module&#39;s box (NXmx data_origin) |
**box_width** | **int** | Width of the box in image pixels (NXmx data_size) |
**box_height** | **int** | Height of the box in image pixels (NXmx data_size) |
**plane_x** | **float** | Plane x of image pixel (box_x, box_y) |
**plane_y** | **float** | Plane y of image pixel (box_x, box_y) |
**x_step_x** | **float** | Plane x step for one image pixel along +x |
**x_step_y** | **float** | Plane y step for one image pixel along +x |
**y_step_x** | **float** | Plane x step for one image pixel along +y |
**y_step_y** | **float** | Plane y step for one image pixel along +y |
**x0** | **int** | Image x of the module&#39;s readout pixel 0, after mirror_y |
**y0** | **int** | Image y of the module&#39;s readout pixel 0, after mirror_y |
**fast_axis** | [**DetectorModuleDirection**](DetectorModuleDirection.md) | |
**slow_axis** | [**DetectorModuleDirection**](DetectorModuleDirection.md) | |
**x0_plane** | **float** | Plane x of readout pixel 0, in image pixels |
**y0_plane** | **float** | Plane y of readout pixel 0, in image pixels |
**fast_step** | **List[float]** | Plane displacement (x, y) for one pixel along the fast readout axis |
**slow_step** | **List[float]** | Plane displacement (x, y) for one pixel along the slow readout axis |
## Example
@@ -1341,6 +1341,10 @@ namespace {
m.sx = GetCBORDouble(map_value);
else if (key == "sy")
m.sy = GetCBORDouble(map_value);
else if (key == "fast_axis")
m.fast = DirectionFromName(GetCBORString(map_value));
else if (key == "slow_axis")
m.slow = DirectionFromName(GetCBORString(map_value));
else
cbor_value_advance(&map_value);
}
+3 -1
View File
@@ -496,7 +496,7 @@ inline void CBOR_ENC_DETECTOR_MODULES(CborEncoder &encoder, const std::vector<De
cborErr(cbor_encoder_create_array(&encoder, &arrayEncoder, modules.size()));
for (const auto &m: modules) {
CborEncoder mapEncoder;
cborErr(cbor_encoder_create_map(&arrayEncoder, &mapEncoder, 10));
cborErr(cbor_encoder_create_map(&arrayEncoder, &mapEncoder, 12));
CBOR_ENC(mapEncoder, "x0", m.x0);
CBOR_ENC(mapEncoder, "y0", m.y0);
CBOR_ENC(mapEncoder, "width", m.width);
@@ -508,6 +508,8 @@ inline void CBOR_ENC_DETECTOR_MODULES(CborEncoder &encoder, const std::vector<De
cborErr(cbor_encode_text_stringz(&mapEncoder, key));
cborErr(cbor_encode_double(&mapEncoder, val));
}
CBOR_ENC(mapEncoder, "fast_axis", DirectionName(m.fast));
CBOR_ENC(mapEncoder, "slow_axis", DirectionName(m.slow));
cborErr(cbor_encoder_close_container(&arrayEncoder, &mapEncoder));
}
cborErr(cbor_encoder_close_container(&encoder, &arrayEncoder));
+52 -47
View File
@@ -396,10 +396,11 @@ export type dataset_settings = {
/**
* 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.
*
*/
@@ -1670,8 +1671,14 @@ export type standard_detector_geometry = {
/**
* 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.
*
*/
export type detector_module = {
@@ -1688,58 +1695,55 @@ export type detector_module = {
};
/**
* 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]).
*
*/
export type detector_module_placement = {
/**
* Lowest image x of the module's box (NXmx data_origin)
* Image x of the module's readout pixel 0, after mirror_y
*/
box_x: number;
x0: number;
/**
* Lowest image y of the module's box (NXmx data_origin)
* Image y of the module's readout pixel 0, after mirror_y
*/
box_y: number;
y0: number;
fast_axis: detector_module_direction;
slow_axis: detector_module_direction;
/**
* Width of the box in image pixels (NXmx data_size)
* Plane x of readout pixel 0, in image pixels
*/
box_width: number;
x0_plane: number;
/**
* Height of the box in image pixels (NXmx data_size)
* Plane y of readout pixel 0, in image pixels
*/
box_height: number;
y0_plane: number;
/**
* Plane x of image pixel (box_x, box_y)
* Plane displacement (x, y) for one pixel along the fast readout axis
*/
plane_x: number;
fast_step: [
number,
number
];
/**
* Plane y of image pixel (box_x, box_y)
* Plane displacement (x, y) for one pixel along the slow readout axis
*/
plane_y: number;
/**
* Plane x step for one image pixel along +x
*/
x_step_x: number;
/**
* Plane y step for one image pixel along +x
*/
x_step_y: number;
/**
* Plane x step for one image pixel along +y
*/
y_step_x: number;
/**
* Plane y step for one image pixel along +y
*/
y_step_y: number;
slow_step: [
number,
number
];
};
export type detector = {
@@ -2566,10 +2570,11 @@ export type dataset_settingsWritable = {
/**
* 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.
*
*/
+30 -21
View File
@@ -632,8 +632,14 @@ export const zStandardDetectorGeometry = z.object({
/**
* 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.
*
*/
export const zDetectorModule = z.object({
@@ -644,28 +650,31 @@ export const zDetectorModule = z.object({
});
/**
* 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]).
*
*/
export const zDetectorModulePlacement = z.object({
box_x: z.coerce.bigint().gte(BigInt(0)).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }),
box_y: z.coerce.bigint().gte(BigInt(0)).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }),
box_width: z.coerce.bigint().gte(BigInt(1)).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }),
box_height: z.coerce.bigint().gte(BigInt(1)).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }),
plane_x: z.number(),
plane_y: z.number(),
x_step_x: z.number(),
x_step_y: z.number(),
y_step_x: z.number(),
y_step_y: z.number()
x0: z.coerce.bigint().gte(BigInt(0)).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }),
y0: z.coerce.bigint().gte(BigInt(0)).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }),
fast_axis: zDetectorModuleDirection,
slow_axis: zDetectorModuleDirection,
x0_plane: z.number(),
y0_plane: z.number(),
fast_step: z.tuple([z.number(), z.number()]),
slow_step: z.tuple([z.number(), z.number()])
});
export const zDatasetSettings = z.object({
+3
View File
@@ -700,6 +700,9 @@ void JFJochXFELReader::Place() {
m.fy = scale * (static_cast<double>(bl.fx) * p.fs_y + static_cast<double>(bl.sx) * p.ss_y);
m.sx = scale * (static_cast<double>(bl.fy) * p.fs_x + static_cast<double>(bl.sy) * p.ss_x);
m.sy = scale * (static_cast<double>(bl.fy) * p.fs_y + static_cast<double>(bl.sy) * p.ss_y);
// The panel's fs and ss run along the block's turn: its readout pixel 0 is (c0, r0).
m.fast = DirectionOfStep(static_cast<double>(bl.fx), static_cast<double>(bl.fy));
m.slow = DirectionOfStep(static_cast<double>(bl.sx), static_cast<double>(bl.sy));
blocks_.push_back({static_cast<size_t>(p.min_ss) * frame_width_ + static_cast<size_t>(p.min_fs), w + 1,
h + 1, r0 * static_cast<int64_t>(width_) + c0,
bl.fy * static_cast<int64_t>(width_) + bl.fx,
+5
View File
@@ -76,6 +76,11 @@ DetectorModules ReadNXmxModules(HDF5Object &file, double pixel_size_m, const Det
m.fy = f.y;
m.sx = s.x;
m.sy = s.y;
// The readout axes, where the writer says (writer/NXmxModules.cpp); else the module's own array.
if (file.Exists(base + "readout_fast_axis") && file.Exists(base + "readout_slow_axis")) {
m.fast = DirectionFromName(file.GetString(base + "readout_fast_axis"));
m.slow = DirectionFromName(file.GetString(base + "readout_slow_axis"));
}
out.push_back(m);
}
if (out.size() < 2)
+9 -6
View File
@@ -515,8 +515,9 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_
start[m].dx = scan[m].dx;
start[m].dy = scan[m].dy;
if (std::hypot(scan[m].dx, scan[m].dy) >= 1.0)
logger.Info("Module {} at box ({}, {}): {} spots on the lattices as placed, {} shifted by "
"({:+.1f}, {:+.1f}) px", m, given.Get()[m].x0, given.Get()[m].y0, scan[m].on_lattice_at_zero,
logger.Info("Module {} (pixel 0 at ({}, {}) {}/{}): {} spots on the lattices as placed, {} shifted by "
"({:+.1f}, {:+.1f}) px", m, given.Get()[m].ReadoutX0(), given.Get()[m].ReadoutY0(),
DirectionName(given.Get()[m].fast), DirectionName(given.Get()[m].slow), scan[m].on_lattice_at_zero,
scan[m].on_lattice_at_best, scan[m].dx, scan[m].dy);
}
scan_mark.Stop();
@@ -596,8 +597,9 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_
if (moved_m && !(moved_t.module[m].Rms() < held_t.module[m].Rms())) {
hold[m] = true;
held_back++;
logger.Info("Module {} at box ({}, {}): held as given - held-out residual {:.3f} px placed, "
"{:.3f} px as given ({} spots)", m, given.Get()[m].x0, given.Get()[m].y0,
logger.Info("Module {} (pixel 0 at ({}, {}) {}/{}): held as given - held-out residual {:.3f} px placed, "
"{:.3f} px as given ({} spots)", m, given.Get()[m].ReadoutX0(), given.Get()[m].ReadoutY0(),
DirectionName(given.Get()[m].fast), DirectionName(given.Get()[m].slow),
moved_t.module[m].Rms(), held_t.module[m].Rms(), moved_t.module[m].n);
}
}
@@ -613,8 +615,9 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_
if (apply) {
for (size_t m = 0; m < rm.modules.size(); m++) {
const auto &ms = rm.modules[m];
logger.Info("Module {} at box ({}, {}): shift ({:+.2f}, {:+.2f}) px, turn {:+.3f} deg, {} spots",
m, given.Get()[m].x0, given.Get()[m].y0, ms.dx, ms.dy, ms.rot_rad * 180.0 / PI, ms.spots);
logger.Info("Module {} (pixel 0 at ({}, {}) {}/{}): shift ({:+.2f}, {:+.2f}) px, turn {:+.3f} deg, {} spots",
m, given.Get()[m].ReadoutX0(), given.Get()[m].ReadoutY0(),
DirectionName(given.Get()[m].fast), DirectionName(given.Get()[m].slow), ms.dx, ms.dy, ms.rot_rad * 180.0 / PI, ms.spots);
}
// The move is a step from where the modules were given; the experiment now has them there,
// so every spot found and every prediction integrated from here on uses it. The image is
+3 -3
View File
@@ -119,7 +119,7 @@ constexpr int CALIBRATION_AZIM_BINS_DEFAULT = 32;
// A module placement file (docs/RUGNUX_FORMATS.md, Module placement file): the dataset_settings fragment
// {"module_placement": [...]} of the broker's /start, read and written through the OpenAPI model.
std::vector<DetectorModule> ReadModulePlacementFile(const std::string &path) {
std::vector<ModulePlacement> ReadModulePlacementFile(const std::string &path) {
std::ifstream in(path);
if (!in)
throw std::runtime_error("cannot open the file");
@@ -2960,8 +2960,8 @@ int RugnuxInputRun::PrepareRugnuxInput() {
throw RugnuxInputFailed{EXIT_FAILURE};
}
}
// Where a module placement file puts them (docs/RUGNUX_FORMATS.md, Module placement file): the modules are named by their boxes
// in the image, so it has to be the same detector, assembled the same way.
// Where a module placement file puts them (docs/RUGNUX_FORMATS.md, Module placement file): the modules are named by their readout
// pixel 0 and axes in the image, so it has to be the same detector, assembled the same way.
if (!module_placement_file.empty()) {
try {
experiment.Modules(ModulesPlaced(experiment.GetModules(), ReadModulePlacementFile(module_placement_file)));
+9 -6
View File
@@ -187,9 +187,12 @@ TEST_CASE("JFJochBrokerHttp_StartModulePlacementMismatch", "[broker]") {
const auto assembled = ModulesAsImaged(DetJF4M().GetGeometry()).Get();
REQUIRE(assembled.size() == 8);
const auto entry = [](const DetectorModule &m) {
return nlohmann::json{{"box_x", m.x0}, {"box_y", m.y0}, {"box_width", m.width}, {"box_height", m.height},
{"plane_x", m.ox}, {"plane_y", m.oy}, {"x_step_x", 1.0}, {"x_step_y", 0.0},
{"y_step_x", 0.0}, {"y_step_y", 1.0}};
const ModulePlacement p = PlacementOf(m);
return nlohmann::json{{"x0", p.x0}, {"y0", p.y0},
{"fast_axis", DirectionName(p.fast)}, {"slow_axis", DirectionName(p.slow)},
{"x0_plane", p.x0_plane}, {"y0_plane", p.y0_plane},
{"fast_step", {p.fast_step[0], p.fast_step[1]}},
{"slow_step", {p.slow_step[0], p.slow_step[1]}}};
};
const auto start = [&](const nlohmann::json &placement) {
nlohmann::json body = {{"beam_x_pxl", 1000}, {"beam_y_pxl", 1000},
@@ -210,10 +213,10 @@ TEST_CASE("JFJochBrokerHttp_StartModulePlacementMismatch", "[broker]") {
twice.push_back(entry(assembled[1]));
CHECK(start(twice) == 400);
DetectorModule elsewhere = assembled[0];
elsewhere.y0 += 1;
nlohmann::json elsewhere = entry(assembled[0]);
elsewhere["y0"] = elsewhere["y0"].get<int64_t>() + 1;
nlohmann::json unknown = missing;
unknown.push_back(entry(elsewhere));
unknown.push_back(elsewhere);
CHECK(start(unknown) == 400);
// Nothing was started.
+6 -1
View File
@@ -1593,7 +1593,8 @@ TEST_CASE("CBORSerialize_StartDetectorModules", "[CBOR][PanelId]") {
}
DetectorModule turned{.x0 = 0, .y0 = 64, .width = 256, .height = 64,
.ox = 300.25, .oy = -12.5, .fx = 0, .fy = 1, .sx = -1, .sy = 0};
.ox = 300.25, .oy = -12.5, .fx = 0, .fy = 1, .sx = -1, .sy = 0,
.fast = DetectorModuleGeometry::Direction::Yneg, .slow = DetectorModuleGeometry::Direction::Xneg};
message.detector_modules = {DetectorModule::AsImaged(0, 0, 256, 64), turned};
{
CBORStream2Serializer serializer(buffer.data(), buffer.size());
@@ -1613,5 +1614,9 @@ TEST_CASE("CBORSerialize_StartDetectorModules", "[CBOR][PanelId]") {
CHECK(m[1].fy == 1);
CHECK(m[1].sx == -1);
CHECK(m[1].sy == 0);
CHECK(m[0].fast == DetectorModuleGeometry::Direction::Xpos);
CHECK(m[0].slow == DetectorModuleGeometry::Direction::Ypos);
CHECK(m[1].fast == DetectorModuleGeometry::Direction::Yneg);
CHECK(m[1].slow == DetectorModuleGeometry::Direction::Xneg);
}
}
+83
View File
@@ -7,6 +7,7 @@
#include "../common/ScanResultGenerator.h"
#include "../writer/FileWriter.h"
#include "../reader/JFJochHDF5Reader.h"
#include "../broker/ModulePlacementConvert.h"
#include "../reader/JFJochCBFReader.h"
#include "../reader/JFJochMarCCDReader.h"
#include "../reader/JFJochSMVReader.h"
@@ -5316,6 +5317,10 @@ TEST_CASE("JFJochReader_TurnedModules_LabCorners", "[HDF5][Full]") {
const auto size = file.ReadVector<int32_t>(module + "/data_size");
REQUIRE(origin.size() == 2);
REQUIRE(size.size() == 2);
// The readout axes beside the NXmx ones, as the geometry turns the module in the stored image.
const Coord fd = geom.GetFastDirection(m), sd = geom.GetSlowDirection(m);
CHECK(file.GetString(module + "/readout_fast_axis") == DirectionName(DirectionOfStep(fd.x, fd.y)));
CHECK(file.GetString(module + "/readout_slow_axis") == DirectionName(DirectionOfStep(sd.x, sd.y)));
for (const int64_t col: {int64_t(0), int64_t(CONVERTED_MODULE_COLS - 1)})
for (const int64_t line: {int64_t(0), int64_t(CONVERTED_MODULE_LINES - 1)}) {
const Coord f = geom.GetFastDirection(m), s = geom.GetSlowDirection(m);
@@ -5407,6 +5412,11 @@ TEST_CASE("JFJochReader_VerticalModules", "[HDF5][Full]") {
CHECK(back[i].fy == Catch::Approx(0.0).margin(1e-6));
CHECK(back[i].sx == Catch::Approx(0.0).margin(1e-6));
CHECK(back[i].sy == Catch::Approx(1.0).margin(1e-6));
// The readout axes read back too: the module's readout pixel 0 is the geometry's.
CHECK(back[i].fast == expected[i].fast);
CHECK(back[i].slow == expected[i].slow);
CHECK(back[i].ReadoutX0() == x.GetDetectorSetup().GetGeometry().GetX0(i));
CHECK(back[i].ReadoutY0() == x.GetDetectorSetup().GetGeometry().GetY0(i));
}
CHECK_FALSE(back.Moved());
}
@@ -5415,3 +5425,76 @@ TEST_CASE("JFJochReader_VerticalModules", "[HDF5][Full]") {
}
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
// Turned and mirrored modules: a stored file names its modules in a module placement by readout pixel 0 and
// axes after mirror_y - for the Yp/Xn module of custom_geometry, y0 mirrored and Yp turned into Yn - and the
// placement rugnux writes from the file lands, entry by entry, on the broker's module of that name.
TEST_CASE("ModulePlacement_TurnedMirroredModules", "[HDF5]") {
RegisterHDF5Filter();
using D = DetectorModuleGeometry::Direction;
const std::vector<DetectorModuleGeometry> layout = {
{0, 0, D::Xpos, D::Ypos}, {0, 550, D::Xpos, D::Ypos}, {1600, 0, D::Ypos, D::Xneg}};
DiffractionExperiment x(DetJF(DetectorGeometryModular(layout, true)));
x.FilePrefix("test_placement_turned").ImagesPerTrigger(1).OverwriteExistingFiles(true);
x.BeamX_pxl(800).BeamY_pxl(500).DetectorDistance_mm(100)
.IncidentEnergy_keV(WVL_1A_IN_KEV).PixelSigned(false).BitDepthImage(16);
x.FrameTime(std::chrono::microseconds(500), std::chrono::microseconds(10));
const auto &geom = x.GetDetectorSetup().GetGeometry();
const int64_t height = x.GetYPixelsNum();
REQUIRE(height == 550 + CONVERTED_MODULE_LINES);
// custom_geometry (1600, 0, Yp, Xn), stored after mirror_y: (1600, H - 1 - 0, Yn, Xn).
CHECK(geom.GetX0(2) == 1600);
CHECK(geom.GetY0(2) == height - 1);
{
StartMessage start_message;
x.FillMessage(start_message);
FileWriter file_set(start_message);
DataMessage message{};
std::vector<uint16_t> image(x.GetPixelsNum(), 0);
message.image = CompressedImage(image, x.GetXPixelsNum(), x.GetYPixelsNum());
message.number = 0;
REQUIRE_NOTHROW(file_set.WriteHDF5(message));
EndMessage end_message;
end_message.max_image_number = 1;
file_set.WriteHDF5(end_message);
file_set.Finalize();
}
DetectorModules read;
{
JFJochHDF5Reader reader;
reader.ReadFile("test_placement_turned_master.h5");
read = reader.GetDataset()->experiment.GetModules();
}
remove("test_placement_turned_master.h5");
remove("test_placement_turned_data_000001.h5");
REQUIRE(read.size() == 3);
const auto placement = ParseModulePlacement(ModulePlacementJson(read));
REQUIRE(placement.size() == 3);
CHECK(placement[2].x0 == 1600);
CHECK(placement[2].y0 == height - 1);
CHECK(placement[2].fast == D::Yneg);
CHECK(placement[2].slow == D::Xneg);
CHECK(placement[2].fast_step[1] == Catch::Approx(-1.0));
CHECK(placement[2].slow_step[0] == Catch::Approx(-1.0));
CHECK(placement[1].y0 == height - 1 - 550);
CHECK(placement[1].slow == D::Yneg);
// The turned module moved by (+2, -3) px on the plane, listed first: it, and only it, moves on the broker's
// modules (ModulesAsImaged, as /start places them).
std::vector<ModulePlacement> moved = {placement[2], placement[0], placement[1]};
moved[0].x0_plane += 2;
moved[0].y0_plane -= 3;
const DetectorModules assembled = ModulesAsImaged(geom);
const DetectorModules placed = ModulesPlaced(assembled, moved);
for (size_t m = 0; m < 3; m++) {
const auto [px, py] = placed[m].ImageToPlane(static_cast<double>(geom.GetX0(m)), static_cast<double>(geom.GetY0(m)));
CHECK(px == Catch::Approx(static_cast<double>(geom.GetX0(m)) + (m == 2 ? 2.0 : 0.0)));
CHECK(py == Catch::Approx(static_cast<double>(geom.GetY0(m)) - (m == 2 ? 3.0 : 0.0)));
}
// Pixel 0 named the wrong way round is no module of this detector.
std::vector<ModulePlacement> wrong = placement;
wrong[2].fast = D::Ypos;
CHECK_THROWS(ModulesPlaced(assembled, wrong));
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
+69 -23
View File
@@ -669,14 +669,25 @@ TEST_CASE("JFJochStateMachine_StartModulePlacement") {
state_machine.AddDetectorSetup(DetJF(2));
state_machine.DebugOnly_SetState(JFJochState::Idle);
const auto assembled = ModulesAsImaged(experiment.GetDetectorSetup().GetGeometry()).Get();
const auto &geom = experiment.GetDetectorSetup().GetGeometry();
const auto assembled = ModulesAsImaged(geom).Get();
REQUIRE(assembled.size() == 2);
// Module 1 moved by (+0.4, -1.3) px and turned slightly, module 0 as assembled - listed 1 first.
// A module is named by its readout pixel 0 and axes in the stored image, after mirror_y.
for (int64_t m = 0; m < 2; m++) {
CHECK(PlacementOf(assembled[m]).x0 == geom.GetX0(m));
CHECK(PlacementOf(assembled[m]).y0 == geom.GetY0(m));
}
// Module 1 moved by (+0.4, -1.3) px and turned slightly about its pixel 0, module 0 as assembled - listed 1 first.
const auto entry = [](const DetectorModule &m, double dx, double dy, double turn) {
return nlohmann::json{{"box_x", m.x0}, {"box_y", m.y0}, {"box_width", m.width}, {"box_height", m.height},
{"plane_x", m.ox + dx}, {"plane_y", m.oy + dy},
{"x_step_x", std::cos(turn)}, {"x_step_y", std::sin(turn)},
{"y_step_x", -std::sin(turn)}, {"y_step_y", std::cos(turn)}};
const ModulePlacement p = PlacementOf(m);
const auto turned = [turn](const std::array<double, 2> &v) {
return std::vector<double>{std::cos(turn) * v[0] - std::sin(turn) * v[1],
std::sin(turn) * v[0] + std::cos(turn) * v[1]};
};
return nlohmann::json{{"x0", p.x0}, {"y0", p.y0},
{"fast_axis", DirectionName(p.fast)}, {"slow_axis", DirectionName(p.slow)},
{"x0_plane", p.x0_plane + dx}, {"y0_plane", p.y0_plane + dy},
{"fast_step", turned(p.fast_step)}, {"slow_step", turned(p.slow_step)}};
};
nlohmann::json body = {{"beam_x_pxl", 500}, {"beam_y_pxl", 500}, {"detector_distance_mm", 100},
{"incident_energy_keV", 12.4}, {"images_per_trigger", 4},
@@ -695,8 +706,11 @@ TEST_CASE("JFJochStateMachine_StartModulePlacement") {
CHECK(modules[0].ox == Catch::Approx(assembled[0].ox));
CHECK(modules[1].x0 == assembled[1].x0);
CHECK(modules[1].y0 == assembled[1].y0);
CHECK(modules[1].ox == Catch::Approx(assembled[1].ox + 0.4));
CHECK(modules[1].oy == Catch::Approx(assembled[1].oy - 1.3));
CHECK(modules[1].fast == assembled[1].fast);
CHECK(modules[1].slow == assembled[1].slow);
const ModulePlacement placed = PlacementOf(modules[1]);
CHECK(placed.x0_plane == Catch::Approx(PlacementOf(assembled[1]).x0_plane + 0.4));
CHECK(placed.y0_plane == Catch::Approx(PlacementOf(assembled[1]).y0_plane - 1.3));
CHECK(modules[1].fy == Catch::Approx(std::sin(0.001)));
// Without it, the modules as assembled.
@@ -707,20 +721,31 @@ TEST_CASE("JFJochStateMachine_StartModulePlacement") {
REQUIRE(pusher.start.detector_modules.size() == 2);
CHECK(pusher.start.detector_modules[1].oy == Catch::Approx(assembled[1].oy));
// A module the detector does not have.
DetectorModule elsewhere = assembled[1];
elsewhere.y0 += 1;
body["module_placement"] = {entry(elsewhere, 0, 0, 0), entry(assembled[0], 0, 0, 0)};
from_json(body, model);
REQUIRE_THROWS_WITH(state_machine.Start(Convert(model)),
Catch::Matchers::ContainsSubstring("is not a module of this detector"));
const auto refused = [&](const nlohmann::json &list, const std::string &why) {
body["module_placement"] = list;
model = {};
from_json(body, model);
REQUIRE_THROWS_WITH(state_machine.Start(Convert(model)), Catch::Matchers::ContainsSubstring(why));
};
// A module the detector does not have: pixel 0 one row off, or the readout axes the other way.
auto off = entry(assembled[1], 0, 0, 0);
off["y0"] = off["y0"].get<int64_t>() + 1;
refused(nlohmann::json::array({off, entry(assembled[0], 0, 0, 0)}), "is not a module of this detector");
auto reversed = entry(assembled[1], 0, 0, 0);
reversed["slow_axis"] = reversed["slow_axis"] == "Yn" ? "Yp" : "Yn";
refused(nlohmann::json::array({reversed, entry(assembled[0], 0, 0, 0)}), "is not a module of this detector");
// One placed twice, one left out.
refused(nlohmann::json::array({entry(assembled[0], 0, 0, 0), entry(assembled[0], 0, 0, 0)}), "is placed twice");
refused(nlohmann::json::array({entry(assembled[0], 0, 0, 0)}), "is not placed");
}
// The module placement file rugnux writes is a dataset_settings fragment: merged into a /start body it
// parses as the broker parses it, and reads back to the same modules.
TEST_CASE("ModulePlacement_FileIsDatasetSettingsFragment") {
DetectorModule a = DetectorModule::AsImaged(0, 0, 1030, 514);
// A mirrored module: its readout pixel 0 is the top row of its box, its slow axis runs -y.
DetectorModule b = DetectorModule::AsImaged(0, 550, 1030, 514);
b.slow = DetectorModuleGeometry::Direction::Yneg;
b.ox += 0.25;
b.oy -= 1.5;
b.fx = std::cos(0.002);
@@ -728,12 +753,30 @@ TEST_CASE("ModulePlacement_FileIsDatasetSettingsFragment") {
b.sx = -std::sin(0.002);
b.sy = std::cos(0.002);
const std::string text = ModulePlacementJson(DetectorModules({a, b}));
CHECK(text.find(R"("slow_axis": "Yn")") != std::string::npos);
const auto back = ParseModulePlacement(text);
REQUIRE(back.size() == 2);
CHECK(back[1].y0 == 550);
CHECK(back[1].ox == b.ox);
CHECK(back[1].fy == b.fy);
CHECK(back[0].x0 == 0);
CHECK(back[0].y0 == 0);
CHECK(back[0].x0_plane == 0.0);
CHECK(back[1].x0 == 0);
CHECK(back[1].y0 == 550 + 513);
CHECK(back[1].fast == DetectorModuleGeometry::Direction::Xpos);
CHECK(back[1].slow == DetectorModuleGeometry::Direction::Yneg);
CHECK(back[1].x0_plane == Catch::Approx(b.ox + 513 * b.sx));
CHECK(back[1].y0_plane == Catch::Approx(b.oy + 513 * b.sy));
CHECK(back[1].fast_step[1] == Catch::Approx(b.fy));
CHECK(back[1].slow_step[0] == Catch::Approx(-b.sx));
CHECK(back[1].slow_step[1] == Catch::Approx(-b.sy));
// Read back onto the same detector, the modules are where they were.
DetectorModule b_assembled = DetectorModule::AsImaged(0, 550, 1030, 514);
b_assembled.slow = DetectorModuleGeometry::Direction::Yneg;
const auto placed = ModulesPlaced(DetectorModules({b_assembled, DetectorModule::AsImaged(0, 0, 1030, 514)}), back);
CHECK(placed[0].ox == Catch::Approx(b.ox));
CHECK(placed[0].oy == Catch::Approx(b.oy));
CHECK(placed[0].sx == Catch::Approx(b.sx));
CHECK(placed[0].sy == Catch::Approx(b.sy));
nlohmann::json body = {{"beam_x_pxl", 500}, {"beam_y_pxl", 500}, {"detector_distance_mm", 100},
{"incident_energy_keV", 12.4}, {"images_per_trigger", 1}};
@@ -744,10 +787,13 @@ TEST_CASE("ModulePlacement_FileIsDatasetSettingsFragment") {
REQUIRE(model.validate(msg));
const auto modules = Convert(model).GetModulePlacement();
REQUIRE(modules.size() == 2);
CHECK(modules[1].x0 == b.x0);
CHECK(modules[1].oy == b.oy);
CHECK(modules[1].sx == b.sx);
CHECK(modules[1].y0 == back[1].y0);
CHECK(modules[1].slow == DetectorModuleGeometry::Direction::Yneg);
CHECK(modules[1].y0_plane == back[1].y0_plane);
// A box key misspelt as custom_geometry's x0 is not a placement.
CHECK_THROWS(ParseModulePlacement(R"({"module_placement": [{"x0": 0, "y0": 0}]})"));
// The keys of the earlier box shape are not a placement, nor is a step of three numbers.
CHECK_THROWS(ParseModulePlacement(R"({"module_placement": [{"box_x": 0, "box_y": 0, "box_width": 1030,
"box_height": 514, "plane_x": 0, "plane_y": 0, "x_step_x": 1, "x_step_y": 0, "y_step_x": 0, "y_step_y": 1}]})"));
CHECK_THROWS(ParseModulePlacement(R"({"module_placement": [{"x0": 0, "y0": 0, "fast_axis": "Xp", "slow_axis": "Yp",
"x0_plane": 0, "y0_plane": 0, "fast_step": [1, 0, 0], "slow_step": [0, 1]}]})"));
}
+31
View File
@@ -635,3 +635,34 @@ TEST_CASE("PanelLayout_DetectorDiagnostic", "[PanelLayout]") {
25.0, 104.0, 95.0)));
CHECK(s == a);
}
// A stored panel's readout is its own pixel array (Xp/Yp from its first pixel), however it is turned on the
// plane: its placement entry carries the turn in the steps, and placing panels as imaged by those entries
// puts every one back where it was.
TEST_CASE("PanelLayout_Placement", "[PanelLayout]") {
const DetectorModules modules = StackedPanels();
const ModulePlacement p = PlacementOf(modules[2]);
CHECK(p.x0 == 0);
CHECK(p.y0 == 2 * PH);
CHECK(p.fast == DetectorModuleGeometry::Direction::Xpos);
CHECK(p.slow == DetectorModuleGeometry::Direction::Ypos);
CHECK(p.x0_plane == 73.0);
CHECK(p.y0_plane == 90.0);
CHECK(p.fast_step == std::array<double, 2>{0, 1});
CHECK(p.slow_step == std::array<double, 2>{-1, 0});
std::vector<DetectorModule> as_imaged;
std::vector<ModulePlacement> placement;
for (int64_t m = NPANELS - 1; m >= 0; m--) {
as_imaged.push_back(DetectorModule::AsImaged(0, m * PH, PW, PH));
placement.push_back(PlacementOf(modules[m]));
}
const DetectorModules back = ModulesPlaced(DetectorModules(as_imaged), placement);
for (size_t i = 0; i < back.size(); i++) {
const DetectorModule &want = modules[NPANELS - 1 - i];
CHECK(back[i].ox == want.ox);
CHECK(back[i].oy == want.oy);
CHECK(back[i].fx == want.fx);
CHECK(back[i].sy == want.sy);
}
}
+4
View File
@@ -45,6 +45,10 @@ void WriteNXmxModules(HDF5Object &file, const DetectorModules &modules, double p
SaveTranslation(group, "module_offset", delta, m.ox, m.oy, pixel_size_m, depends_on);
SaveTranslation(group, "fast_pixel_direction", delta, m.fx, m.fy, pixel_size_m, path + "/module_offset");
SaveTranslation(group, "slow_pixel_direction", delta, m.sx, m.sy, pixel_size_m, path + "/module_offset");
// Not NXmx: which way the module's readout axes run in the stored image (jfjoch_api.yaml
// detector_module_direction), so that its readout pixel 0 and axes name it in a module placement.
group.SaveScalar("readout_fast_axis", DirectionName(m.fast));
group.SaveScalar("readout_slow_axis", DirectionName(m.slow));
}
}