The detector plane was three PONI angles and nothing else, so the two things it
cannot express - an image mirrored in Y, and one mounted at a multiple of 90
degrees - had no home at all. They are now the DetectorOrientation carried by the
detector setup, composed with the PONI rotation into one orthogonal matrix whose
columns ARE the fast axis, the slow axis and the sample->PONI normal:
lab = R(rot1, rot2, rot3) * Delta * ( (x-bx)*p , (y-by)*p , distance )
GetFastAxis/GetSlowAxis/GetNormalAxis read those columns and DetectorAxes() sets
the plane from them, decomposing back to the angles; PoniRotMatrix and
PoniAnglesFromMatrix are the conversion in both directions, exact on the canonical
branch (rot2 in [-pi/2, pi/2]) and with a stated convention at gimbal lock. The
angles stay stored rather than re-derived, so a geometry given as angles is
written back as the same angles, to the bit.
Delta is never inferred. In particular an arbitrary rot3 is NOT decomposed into a
quarter turn plus a residual: rot3 is a fitted quantity, and a least-squares step
must not be able to turn the stored image. It is set only where something states
it - the detector setup, --detector-mirror-y / --detector-quarter-turns, or the
value a file this system wrote records - and defaults to the identity, which makes
the whole change a no-op for every existing detector and every existing file.
It is a different setting from DetectorSetup::mirror_y, which flips the MODULE
LAYOUT while an image is assembled and so decides what the stored pixels are.
Merging the two would apply the mirror twice for every modular detector, or change
the pixel content of every file written; both are ruled out. The new one earns its
keep exactly where the old one is a no-op: a detector whose image arrives already
assembled has no layout to flip.
Both generators are signed permutations of the in-plane offset, so they preserve
the distance from the PONI. That is why almost nothing downstream changes:
everything needing an azimuth already goes through LabCoord, and everything that
does not needs only a radius. The two hand-written copies of the rotation -
XtalResidual and RingOptimizer - take the discrete part as four constants next to
cos_rot3/sin_rot3, since it acts in the detector frame where rot3 acts in the
laboratory and cannot be folded into it. RingOptimizer needs it despite being a
radial fit: it fits the tilt, and the discrete part changes which way the tilt
tips a ring.
Carried as two optional CBOR keys and two detectorSpecific datasets, both
back-compatible; the NXmx module axis vectors and the translation direction stop
being hardcoded and are computed from it, reproducing today's values exactly at
the identity. GetPoniRotMatrix is renamed GetDetectorMatrix, because it is no
longer only the PONI rotation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lc5JG6kJqZoCWaoZ43JGTW
167 lines
8.1 KiB
C++
167 lines
8.1 KiB
C++
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <memory>
|
|
#include <chrono>
|
|
#include <optional>
|
|
|
|
#include "DetectorGeometry.h"
|
|
#include "DetectorOrientation.h"
|
|
#include "../jungfrau/JFModuleGainCalibration.h"
|
|
#include "DetectorGeometryFixed.h"
|
|
#include "DetectorGeometryModular.h"
|
|
#include "DetectorSettings.h"
|
|
|
|
constexpr uint16_t SimplonStream2Port = 31001;
|
|
|
|
enum class DetectorType {EIGER, JUNGFRAU, DECTRIS};
|
|
|
|
class DetectorSetup {
|
|
std::string description;
|
|
std::string serial_number;
|
|
std::shared_ptr<DetectorGeometry> geometry;
|
|
std::vector<std::string> det_modules_hostname;
|
|
std::vector<std::string> gain_file_names;
|
|
std::vector<std::string> trim_file_names;
|
|
std::string trim_file_directory;
|
|
std::vector<int> trim_energy_eV_values;
|
|
std::shared_ptr<JFGainCalibration> gain_calibration;
|
|
int64_t udp_interface_count = 2;
|
|
float pixel_size_um = 75.0f;
|
|
std::string sensor_material = "Si";
|
|
float sensor_thickness_um = 320.0f;
|
|
std::vector<int64_t> tx_delay;
|
|
DetectorType detector_type;
|
|
int32_t high_voltage = 120.0;
|
|
uint32_t ipv4_base_addr = 0x010a0a0a;
|
|
bool module_sync = true;
|
|
// Whether the assembled image is mirrored in Y relative to the detector's raw readout order.
|
|
// A property of the configuration, not something derivable from the assembled image: it says
|
|
// how the modules were laid out to reach the MX convention of row 0 at the top.
|
|
bool mirror_y = true;
|
|
// How the ASSEMBLED image sits in the detector plane - mirrored in Y, turned by a multiple of 90
|
|
// degrees. A different thing from mirror_y above, which is spent on the module layout before the
|
|
// geometry sees anything; this one changes no pixel, only how a pixel coordinate is taken to the
|
|
// laboratory. Identity by default, which is every detector assembled by this system.
|
|
DetectorOrientation image_orientation;
|
|
std::chrono::nanoseconds read_out_time;
|
|
std::chrono::nanoseconds min_count_time;
|
|
std::chrono::nanoseconds min_frame_time;
|
|
float min_energy_threshold_keV = 2.7f;
|
|
int32_t temperature_thresold_degC = 55;
|
|
|
|
std::string dectris_roi;
|
|
|
|
std::optional<int64_t> bit_depth_image;
|
|
std::optional<int64_t> bit_depth_readout;
|
|
std::optional<int64_t> saturation_limit;
|
|
std::optional<DetectorSettings> settings;
|
|
|
|
DetectorSetup(std::shared_ptr<DetectorGeometry> geom,
|
|
DetectorType detector_type,
|
|
const std::string &description = "Detector",
|
|
const std::vector<std::string> &det_modules_hostname = {});
|
|
public:
|
|
DetectorSetup(const DetectorGeometryFixed& geom,
|
|
DetectorType detector_type,
|
|
const std::string &description = "Detector",
|
|
const std::vector<std::string> &det_modules_hostname = {});
|
|
DetectorSetup(const DetectorGeometryModular& geom,
|
|
DetectorType detector_type,
|
|
const std::string &description = "Detector",
|
|
const std::vector<std::string> &det_modules_hostname = {});
|
|
void LoadGain(const std::vector<std::string> &filenames);
|
|
void SetTrimFiles(const std::vector<std::string> &filenames);
|
|
|
|
DetectorSetup& TxDelay(const std::vector<int64_t> &v);
|
|
DetectorSetup& UDPInterfaceCount(int64_t input);
|
|
DetectorSetup& SensorMaterial(const std::string &input);
|
|
DetectorSetup& SensorThickness_um(float input);
|
|
DetectorSetup& PixelSize_um(float input);
|
|
DetectorSetup& HighVoltage(int32_t input);
|
|
DetectorSetup& SerialNumber(const std::string &input);
|
|
DetectorSetup& BaseIPv4Addr(const std::string &input);
|
|
DetectorSetup& ModuleSync(bool input);
|
|
DetectorSetup& MirrorY(bool input);
|
|
DetectorSetup& ImageOrientation(const DetectorOrientation &input);
|
|
DetectorSetup& ReadOutTime(std::chrono::nanoseconds input);
|
|
DetectorSetup& Geometry(const DetectorGeometryFixed& input);
|
|
DetectorSetup& BitDepthImage(int64_t input);
|
|
DetectorSetup& MinFrameTime(std::chrono::nanoseconds input);
|
|
DetectorSetup& MinCountTime(std::chrono::nanoseconds input);
|
|
DetectorSetup& MinThreshold_keV(float input);
|
|
DetectorSetup& SaturationLimit(std::optional<int64_t> input);
|
|
DetectorSetup& Description(const std::string &input);
|
|
DetectorSetup& DECTRISROI(const std::string &input);
|
|
DetectorSetup& DefaultSettings(const std::optional<DetectorSettings> &input);
|
|
DetectorSetup& TempThreshold_degC(int64_t input);
|
|
DetectorSetup& TrimEnergies_eV(std::vector<int> input);
|
|
|
|
[[nodiscard]] DetectorType GetDetectorType() const;
|
|
[[nodiscard]] const DetectorGeometry& GetGeometry() const;
|
|
[[nodiscard]] const std::vector<std::string>& GetDetectorModuleHostname() const;
|
|
[[nodiscard]] uint64_t GetModulesNum() const;
|
|
[[nodiscard]] std::string GetDescription() const;
|
|
[[nodiscard]] float GetPixelSize_mm() const;
|
|
[[nodiscard]] float GetSensorThickness_um() const;
|
|
[[nodiscard]] std::string GetSensorMaterial() const;
|
|
[[nodiscard]] const std::vector<JFModuleGainCalibration> &GetGainCalibration() const;
|
|
[[nodiscard]] int64_t GetUDPInterfaceCount() const;
|
|
[[nodiscard]] const std::vector<int64_t> &GetTxDelay() const; // can be empty for default
|
|
[[nodiscard]] const std::vector<std::string> &GetGainFileNames() const;
|
|
[[nodiscard]] const std::vector<std::string> &GetTrimFileNames() const;
|
|
[[nodiscard]] std::string GetTrimFileDirectory() const;
|
|
[[nodiscard]] int32_t GetHighVoltage() const;
|
|
[[nodiscard]] std::string GetSerialNumber() const;
|
|
[[nodiscard]] uint32_t GetSrcIPv4Addr(uint32_t half_module) const;
|
|
[[nodiscard]] std::string GetBaseIPv4Addr() const;
|
|
[[nodiscard]] bool IsModuleSync() const;
|
|
[[nodiscard]] bool IsMirrorY() const;
|
|
[[nodiscard]] DetectorOrientation GetImageOrientation() const;
|
|
[[nodiscard]] std::chrono::nanoseconds GetReadOutTime() const;
|
|
[[nodiscard]] std::chrono::nanoseconds GetMinFrameTime() const;
|
|
[[nodiscard]] std::chrono::nanoseconds GetMinCountTime() const;
|
|
[[nodiscard]] std::optional<int64_t> GetBitDepthReadout() const;
|
|
[[nodiscard]] std::optional<int64_t> GetBitDepthImage() const;
|
|
[[nodiscard]] std::string GetDECTRISStream2Addr() const;
|
|
[[nodiscard]] float GetMinThreshold_keV() const;
|
|
[[nodiscard]] std::optional<int64_t> GetSaturationLimit() const;
|
|
[[nodiscard]] std::string GetDECTRISROI() const;
|
|
[[nodiscard]] std::optional<DetectorSettings> GetDefaultSettings() const;
|
|
[[nodiscard]] int32_t GetTempThreshold_degC() const;
|
|
[[nodiscard]] std::vector<int> GetTrimEnergies_eV() const;
|
|
};
|
|
|
|
DetectorSetup DetJF4M(const std::string &description = "Detector",
|
|
const std::vector<std::string> &det_modules_hostname = {});
|
|
DetectorSetup DetJF9M(const std::string &description = "Detector",
|
|
const std::vector<std::string> &det_modules_hostname = {});
|
|
|
|
DetectorSetup DetJF(int32_t nmodules,
|
|
int32_t horizontal_stacking = 1,
|
|
int32_t gap_x = 0,
|
|
int32_t gap_y = 0,
|
|
bool mirror_y = true,
|
|
const std::string &description = "Detector",
|
|
const std::vector<std::string> &det_modules_hostname = {});
|
|
DetectorSetup DetJF(const DetectorGeometryModular &geom,
|
|
const std::string &description = "Detector",
|
|
const std::vector<std::string> &det_modules_hostname = {});
|
|
|
|
DetectorSetup DetEIGER(int32_t nmodules,
|
|
int32_t horizontal_stacking = 1,
|
|
int32_t gap_x = 0,
|
|
int32_t gap_y = 0,
|
|
bool mirror_y = true,
|
|
const std::string &description = "Detector",
|
|
const std::vector<std::string> &det_modules_hostname = {});
|
|
DetectorSetup DetEIGER(const DetectorGeometryModular &geom,
|
|
const std::string &description = "Detector",
|
|
const std::vector<std::string> &det_modules_hostname = {});
|
|
|
|
DetectorSetup DetDECTRIS(int64_t width, int64_t height,
|
|
const std::string &description,
|
|
const std::string &addr);
|