Build Packages / build:viewer-tgz:cpu (push) Successful in 20m56s
Build Packages / build:viewer-tgz:cuda (push) Successful in 24m22s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 25m52s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 26m12s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 31m13s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 31m27s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 32m3s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 20m9s
Build Packages / XDS test (durin plugin) (push) Successful in 11m44s
Build Packages / build:rpm (rocky9) (push) Successful in 22m43s
Build Packages / Generate python client (push) Successful in 35s
Build Packages / Build documentation (push) Successful in 1m25s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (rocky8) (push) Successful in 27m26s
Build Packages / DIALS test (push) Successful in 21m34s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 26m50s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 21m57s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 12m11s
Build Packages / XDS test (neggia plugin) (push) Successful in 10m6s
Build Packages / Unit tests (push) Successful in 1h56m8s
Build Packages / build:windows:nocuda (push) Canceled after 0s
Build Packages / build:windows:cuda (push) Canceled after 0s
DetectorSetup gave a DECTRIS detector bit_depth_image = 16 and bit_depth_readout = 16
the moment it was constructed. Both mean "the detector told us", and at construction
nothing has asked it: the SIMPLON client learns them at configure time, and the lite
receiver reads them off the start message. Until then they were an assumption wearing
the clothes of a measurement.
The assumption was load-bearing in the wrong direction. GetByteDepthImage() gives the
detector's image depth absolute priority - correctly, since the DECTRIS path forwards
images verbatim and the depth has to be the one the pixels actually have - so a value
that was always set meant everything below it was unreachable on DECTRIS:
* image_format_settings.bit_depth_image, which the API documents as "bit depth of
resulting image ... if not provided value is adjusted automatically", was silently
discarded. It works on JUNGFRAU, where the detector's value is not set, and did
nothing at all on an EIGER. No error, no warning.
* so was the promotion to four bytes that summation needs.
Both are now unset for DECTRIS. JUNGFRAU keeps its readout depth of 16, because there
that is a property of the hardware rather than something configured.
Where nothing supplies a depth the answer does not change: GetByteDepthImage() falls
through to the same two bytes it produced before, and the fallback no longer throws
merely because the readout depth is not known yet - GetBitDepthReadoutIfKnown() reports
it as unknown instead of inventing one. The start message carries it as an optional
already, and the receiver already guards on that, so "not known" travels end to end
rather than being papered over. GetBitDepthReadout() still throws for the callers that
genuinely require a value; all of them are on the FPGA path, where it is always set.
Also fixes the jfjoch_test build, which the previous commit broke: SpotExtractorGPU's
Extract() takes a PixelView now, and the parity test still passed a raw pointer. That
should have been caught before it was pushed.
Tests: DetectorSetup (with a new case pinning the contract this restores), plus
DiffractionExperiment, CBOR, writer, HDF5, preprocessing, azimuthal integration and the
GPU spot-finding / integration / decoder suites. rugnux unchanged end to end.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
472 lines
18 KiB
C++
472 lines
18 KiB
C++
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include <filesystem>
|
|
#include <utility>
|
|
|
|
#include "DetectorSetup.h"
|
|
#include "JFJochException.h"
|
|
#include "NetworkAddressConvert.h"
|
|
#define check_max(param, val, max) if ((val) > (max)) throw JFJochException(JFJochExceptionCategory::InputParameterAboveMax, param)
|
|
#define check_min(param, val, min) if ((val) < (min)) throw JFJochException(JFJochExceptionCategory::InputParameterBelowMin, param)
|
|
|
|
DetectorSetup::DetectorSetup(const DetectorGeometryFixed &geom, DetectorType detector_type,
|
|
const std::string &description, const std::vector<std::string> &det_modules_hostname)
|
|
: DetectorSetup(std::make_shared<DetectorGeometryFixed>(geom),
|
|
detector_type, description, det_modules_hostname) {
|
|
switch (detector_type) {
|
|
case DetectorType::DECTRIS:
|
|
break;
|
|
default:
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Detector not compatible with fixed geometry");
|
|
}
|
|
}
|
|
|
|
|
|
DetectorSetup::DetectorSetup(const DetectorGeometryModular &geom, DetectorType detector_type,
|
|
const std::string &description, const std::vector<std::string> &det_modules_hostname)
|
|
: DetectorSetup(std::make_shared<DetectorGeometryModular>(geom),
|
|
detector_type, description, det_modules_hostname) {
|
|
switch (detector_type) {
|
|
case DetectorType::EIGER:
|
|
case DetectorType::JUNGFRAU:
|
|
break;
|
|
default:
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Detector not compatible with modular geometry");
|
|
}
|
|
}
|
|
|
|
DetectorSetup::DetectorSetup(std::shared_ptr<DetectorGeometry> in_geometry,
|
|
DetectorType in_detector_type,
|
|
const std::string &in_description,
|
|
const std::vector<std::string> &
|
|
in_det_modules_hostname)
|
|
: description(in_description),
|
|
geometry(std::move(in_geometry)),
|
|
det_modules_hostname(in_det_modules_hostname),
|
|
gain_calibration(std::make_shared<JFGainCalibration>()),
|
|
detector_type(in_detector_type),
|
|
read_out_time(0),
|
|
min_count_time(MIN_COUNT_TIME),
|
|
min_frame_time(std::chrono::milliseconds(1)) {
|
|
|
|
if (description.empty())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Detector description cannot be empty");
|
|
|
|
switch (detector_type) {
|
|
case DetectorType::EIGER:
|
|
high_voltage = 150;
|
|
read_out_time = PSI_EIGER_READOUT_TIME;
|
|
if (!det_modules_hostname.empty() && (2 * geometry->GetModulesNum() != det_modules_hostname.size()))
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Mismatch between number of modules in detector geometry and hostname (For EIGER - one module = 2 hostnames)");
|
|
break;
|
|
case DetectorType::JUNGFRAU:
|
|
high_voltage = 120;
|
|
bit_depth_readout = 16;
|
|
read_out_time = PSI_JUNGFRAU_READOUT_TIME;
|
|
if (!det_modules_hostname.empty() && (geometry->GetModulesNum() != det_modules_hostname.size()))
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Mismatch between number of modules in detector geometry and hostname");
|
|
break;
|
|
case DetectorType::DECTRIS:
|
|
high_voltage = 0;
|
|
// Neither depth is set here. Both mean "the detector told us", and until something has
|
|
// asked it - the SIMPLON client at configure time, or the start message off the stream -
|
|
// nobody has. JUNGFRAU above keeps its 16, because there a 16-bit readout is a property
|
|
// of the hardware rather than something that gets configured.
|
|
//
|
|
// bit_depth_image in particular is deliberately left unset. It means "the detector told us what depth
|
|
// its images are", and until something has asked the detector - or read it off the
|
|
// stream - nobody has. Defaulting it to 16 made it look answered, and since
|
|
// GetByteDepthImage() gives this value absolute priority, that silently shadowed both
|
|
// the image-format setting the API offers and the promotion to 32 bits that summation
|
|
// needs. Unset, the fallback still lands on 16 bits for a DECTRIS detector, via the
|
|
// readout depth just above.
|
|
read_out_time = std::chrono::microseconds(0);
|
|
if (!det_modules_hostname.empty() && ( det_modules_hostname.size() != 1))
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Just one address need to be provided for DECTRIS detector");
|
|
break;
|
|
default:
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Detector not supported");
|
|
}
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::Description(const std::string &input) {
|
|
if (input.empty())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Detector description cannot be empty");
|
|
description = input;
|
|
return *this;
|
|
}
|
|
|
|
const DetectorGeometry &DetectorSetup::GetGeometry() const {
|
|
return *geometry;
|
|
}
|
|
|
|
const std::vector<std::string> &DetectorSetup::GetDetectorModuleHostname() const {
|
|
return det_modules_hostname;
|
|
}
|
|
|
|
uint64_t DetectorSetup::GetModulesNum() const {
|
|
return geometry->GetModulesNum();
|
|
}
|
|
|
|
std::string DetectorSetup::GetDescription() const {
|
|
return description;
|
|
}
|
|
|
|
float DetectorSetup::GetPixelSize_mm() const {
|
|
return pixel_size_um / 1000.0f;
|
|
}
|
|
|
|
std::string DetectorSetup::GetSensorMaterial() const {
|
|
return sensor_material;
|
|
}
|
|
|
|
float DetectorSetup::GetSensorThickness_um() const {
|
|
return sensor_thickness_um;
|
|
}
|
|
|
|
void DetectorSetup::LoadGain(const std::vector<std::string> &filenames) {
|
|
if (filenames.size() != GetModulesNum())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Mismatch in number of gain calibration files");
|
|
gain_file_names = filenames;
|
|
gain_calibration->LoadGain(filenames);
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::UDPInterfaceCount(int64_t input) {
|
|
if ((input != 1) && (input != 2))
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Only 1 and 2 are supported as UDP interface count");
|
|
udp_interface_count = input;
|
|
return *this;
|
|
}
|
|
|
|
const std::vector<JFModuleGainCalibration> &DetectorSetup::GetGainCalibration() const {
|
|
return gain_calibration->GetCalibration();
|
|
}
|
|
|
|
int64_t DetectorSetup::GetUDPInterfaceCount() const {
|
|
if (detector_type == DetectorType::EIGER)
|
|
return 2;
|
|
return udp_interface_count;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::SensorMaterial(const std::string &input) {
|
|
sensor_material = input;
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::SensorThickness_um(float input) {
|
|
sensor_thickness_um = input;
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::PixelSize_um(float input) {
|
|
pixel_size_um = input;
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::Geometry(const DetectorGeometryFixed &input) {
|
|
if (detector_type != DetectorType::DECTRIS)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"PSI detector geometry cannot be updated during operation");
|
|
geometry = std::make_shared<DetectorGeometryFixed>(input);
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::TxDelay(const std::vector<int64_t> &v) {
|
|
if (!v.empty() && (v.size() != GetModulesNum()))
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Mismatch between size of TX delay vector and modules number");
|
|
for (const auto &i: v) {
|
|
if ((i < 0) || (i > 31))
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "TX delay must be in range 0-31");
|
|
}
|
|
tx_delay = v;
|
|
return *this;
|
|
}
|
|
|
|
const std::vector<int64_t> &DetectorSetup::GetTxDelay() const {
|
|
return tx_delay;
|
|
}
|
|
|
|
const std::vector<std::string> &DetectorSetup::GetGainFileNames() const {
|
|
return gain_file_names;
|
|
}
|
|
|
|
DetectorType DetectorSetup::GetDetectorType() const {
|
|
return detector_type;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::HighVoltage(int32_t input) {
|
|
high_voltage = input;
|
|
return *this;
|
|
}
|
|
|
|
int32_t DetectorSetup::GetHighVoltage() const {
|
|
return high_voltage;
|
|
}
|
|
|
|
void DetectorSetup::SetTrimFiles(const std::vector<std::string> &filenames) {
|
|
if (detector_type != DetectorType::EIGER)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Trim bits make sense only for EIGER");
|
|
|
|
if ((filenames.size() == 1)
|
|
&& std::filesystem::is_directory(filenames[0])) {
|
|
trim_file_directory = filenames[0];
|
|
trim_file_names.clear();
|
|
} else {
|
|
if (filenames.size() != 2 * GetModulesNum())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Mismatch in number of trim bit calibration files");
|
|
trim_file_directory = "";
|
|
trim_file_names = filenames;
|
|
}
|
|
}
|
|
|
|
const std::vector<std::string> &DetectorSetup::GetTrimFileNames() const {
|
|
return trim_file_names;
|
|
}
|
|
|
|
std::string DetectorSetup::GetTrimFileDirectory() const {
|
|
return trim_file_directory;
|
|
}
|
|
|
|
std::string DetectorSetup::GetSerialNumber() const {
|
|
return serial_number;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::SerialNumber(const std::string &input) {
|
|
serial_number = input;
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::BaseIPv4Addr(const std::string &input) {
|
|
ipv4_base_addr = IPv4AddressFromStr(input);
|
|
return *this;
|
|
}
|
|
|
|
uint32_t DetectorSetup::GetSrcIPv4Addr(uint32_t half_module) const {
|
|
if (half_module >= GetUDPInterfaceCount() * GetModulesNum())
|
|
throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds, "Non existing module");
|
|
return ipv4_base_addr + (half_module << 24);
|
|
}
|
|
|
|
std::string DetectorSetup::GetBaseIPv4Addr() const {
|
|
return IPv4AddressToStr(ipv4_base_addr);
|
|
}
|
|
|
|
bool DetectorSetup::IsModuleSync() const {
|
|
if (GetModulesNum() == 1)
|
|
return false;
|
|
else
|
|
return module_sync;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::ModuleSync(bool input) {
|
|
module_sync = input;
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup & DetectorSetup::ReadOutTime(std::chrono::nanoseconds input) {
|
|
if (input.count() < 0)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Read out time has to be non-negative");
|
|
read_out_time = input;
|
|
return *this;
|
|
}
|
|
|
|
std::chrono::nanoseconds DetectorSetup::GetReadOutTime() const {
|
|
return read_out_time;
|
|
}
|
|
|
|
std::chrono::nanoseconds DetectorSetup::GetMinFrameTime() const {
|
|
switch (GetDetectorType()) {
|
|
case DetectorType::EIGER:
|
|
return MIN_FRAME_TIME_EIGER;
|
|
case DetectorType::JUNGFRAU:
|
|
if (GetUDPInterfaceCount() == 1)
|
|
return MIN_FRAME_TIME_JUNGFRAU_HALF_SPEED;
|
|
return MIN_FRAME_TIME_JUNGFRAU_FULL_SPEED;
|
|
case DetectorType::DECTRIS:
|
|
return min_frame_time;
|
|
default:
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Detector not supported");
|
|
}
|
|
}
|
|
|
|
std::chrono::nanoseconds DetectorSetup::GetMinCountTime() const {
|
|
return min_count_time;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::MinCountTime(std::chrono::nanoseconds input) {
|
|
min_count_time = input;
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::MinFrameTime(std::chrono::nanoseconds input) {
|
|
min_frame_time = input;
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::BitDepthImage(int64_t input) {
|
|
if (GetDetectorType() != DetectorType::DECTRIS)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Bit depth image can be only changed for DECTRIS detector");
|
|
switch (input) {
|
|
case 8:
|
|
case 16:
|
|
case 32:
|
|
bit_depth_image = input;
|
|
return *this;
|
|
default:
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Bit depth image can be only 8, 16 or 32");
|
|
}
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::BitDepthReadout(int64_t input) {
|
|
if (GetDetectorType() != DetectorType::DECTRIS)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Bit depth readout can be only changed for DECTRIS detector");
|
|
switch (input) {
|
|
case 8:
|
|
case 12:
|
|
case 16:
|
|
case 32:
|
|
bit_depth_readout = input;
|
|
return *this;
|
|
default:
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Bit depth readout can be only 8, 12, 16 or 32");
|
|
}
|
|
}
|
|
|
|
std::optional<int64_t> DetectorSetup::GetBitDepthReadout() const {
|
|
return bit_depth_readout;
|
|
}
|
|
|
|
std::optional<int64_t> DetectorSetup::GetBitDepthImage() const {
|
|
return bit_depth_image;
|
|
}
|
|
|
|
std::string DetectorSetup::GetDECTRISStream2Addr() const {
|
|
if (GetDetectorType() != DetectorType::DECTRIS)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Stream2 only possible for DECTRIS systems");
|
|
if (det_modules_hostname.size() != 1)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Stream2 address not defined");
|
|
return "tcp://" + det_modules_hostname[0] + ":" + std::to_string(SimplonStream2Port);
|
|
}
|
|
|
|
float DetectorSetup::GetMinThreshold_keV() const {
|
|
return min_energy_threshold_keV;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::MinThreshold_keV(float input) {
|
|
if (input <= 0.0f)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Min threshold must be positive number");
|
|
min_energy_threshold_keV = input;
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::SaturationLimit(std::optional<int64_t> input) {
|
|
if (input && input <= 0)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Saturation limit must be positive number");
|
|
saturation_limit = input;
|
|
return *this;
|
|
}
|
|
|
|
std::optional<int64_t> DetectorSetup::GetSaturationLimit() const {
|
|
return saturation_limit;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::DECTRISROI(const std::string &input) {
|
|
dectris_roi = input;
|
|
return *this;
|
|
}
|
|
|
|
std::string DetectorSetup::GetDECTRISROI() const {
|
|
if (dectris_roi.empty())
|
|
return "disabled";
|
|
return dectris_roi;
|
|
}
|
|
|
|
std::optional<DetectorSettings> DetectorSetup::GetDefaultSettings() const {
|
|
return settings;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::DefaultSettings(const std::optional<DetectorSettings> &input) {
|
|
settings = input;
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup DetJF4M(const std::string &description, const std::vector<std::string> &det_modules_hostname) {
|
|
return DetJF(8, 2, 8, 36, true, description, det_modules_hostname);
|
|
}
|
|
|
|
DetectorSetup DetJF9M(const std::string &description, const std::vector<std::string> &det_modules_hostname) {
|
|
return DetJF(18, 3, 8, 36, true, description, det_modules_hostname);
|
|
}
|
|
|
|
DetectorSetup DetJF(int32_t nmodules, int32_t horizontal_stacking, int32_t gap_x, int32_t gap_y, bool mirror_y,
|
|
const std::string &description, const std::vector<std::string> &det_modules_hostname) {
|
|
return DetJF(DetectorGeometryModular(nmodules, horizontal_stacking, gap_x, gap_y, mirror_y),
|
|
description, det_modules_hostname);
|
|
}
|
|
|
|
DetectorSetup DetJF(const DetectorGeometryModular &geom, const std::string &description,
|
|
const std::vector<std::string> &det_modules_hostname) {
|
|
return {geom, DetectorType::JUNGFRAU, description, det_modules_hostname};
|
|
}
|
|
|
|
DetectorSetup DetEIGER(int32_t nmodules, int32_t horizontal_stacking, int32_t gap_x, int32_t gap_y, bool mirror_y,
|
|
const std::string &description, const std::vector<std::string> &det_modules_hostname) {
|
|
return DetEIGER(DetectorGeometryModular(nmodules, horizontal_stacking, gap_x, gap_y, mirror_y),
|
|
description, det_modules_hostname);
|
|
}
|
|
|
|
DetectorSetup DetEIGER(const DetectorGeometryModular &geom, const std::string &description,
|
|
const std::vector<std::string> &det_modules_hostname) {
|
|
return {geom, DetectorType::EIGER, description, det_modules_hostname};
|
|
}
|
|
|
|
DetectorSetup DetDECTRIS(int64_t width, int64_t height, const std::string &description, const std::string &addr) {
|
|
if (addr.empty())
|
|
return {DetectorGeometryFixed(width, height), DetectorType::DECTRIS, description, {}};
|
|
else
|
|
return {DetectorGeometryFixed(width, height), DetectorType::DECTRIS, description, {addr}};
|
|
}
|
|
|
|
int32_t DetectorSetup::GetTempThreshold_degC() const {
|
|
return temperature_thresold_degC;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::TempThreshold_degC(int64_t input) {
|
|
check_min("Temperature threshold (degC)", input, 40);
|
|
check_max("Temperature threshold (degC)", input, 70);
|
|
temperature_thresold_degC = static_cast<int32_t>(input);
|
|
return *this;
|
|
}
|
|
|
|
DetectorSetup &DetectorSetup::TrimEnergies_eV(std::vector<int> input) {
|
|
trim_energy_eV_values = std::move(input);
|
|
return *this;
|
|
}
|
|
|
|
std::vector<int> DetectorSetup::GetTrimEnergies_eV() const {
|
|
return trim_energy_eV_values;
|
|
}
|