// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #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 &det_modules_hostname) : DetectorSetup(std::make_shared(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 &det_modules_hostname) : DetectorSetup(std::make_shared(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 in_geometry, DetectorType in_detector_type, const std::string &in_description, const std::vector &in_det_modules_hostname) : geometry(std::move(in_geometry)), description(in_description), det_modules_hostname(in_det_modules_hostname), detector_type(in_detector_type), read_out_time(0), min_frame_time(std::chrono::milliseconds(1)), min_count_time(std::chrono::microseconds(MIN_COUNT_TIME_IN_US)) { if (description.empty()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Detector description cannot be empty"); switch (detector_type) { case DetectorType::EIGER: high_voltage = 150; read_out_time = std::chrono::microseconds(3); 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 = std::chrono::microseconds(20); 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; bit_depth_readout = 16; bit_depth_image = 16; 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 &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 &filenames) { if (filenames.size() != GetModulesNum()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch in number of gain calibration files"); gain_file_names = filenames; for (const auto& i: filenames) gain_calibration.emplace_back(i); } 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 &DetectorSetup::GetGainCalibration() const { return gain_calibration; } 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(input); return *this; } DetectorSetup &DetectorSetup::TxDelay(const std::vector &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 &DetectorSetup::GetTxDelay() const { return tx_delay; } const std::vector &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 &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 &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::microseconds 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::microseconds DetectorSetup::GetReadOutTime() const { return read_out_time; } std::chrono::microseconds DetectorSetup::GetMinFrameTime() const { switch (GetDetectorType()) { case DetectorType::EIGER: return std::chrono::microseconds(MIN_FRAME_TIME_EIGER_IN_US); case DetectorType::JUNGFRAU: if (GetUDPInterfaceCount() == 1) return std::chrono::microseconds(MIN_FRAME_TIME_JUNGFRAU_HALF_SPEED_IN_US); return std::chrono::microseconds(MIN_FRAME_TIME_JUNGFRAU_FULL_SPEED_IN_US); case DetectorType::DECTRIS: return min_frame_time; default: throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Detector not supported"); } } std::chrono::microseconds DetectorSetup::GetMinCountTime() const { return min_count_time; } DetectorSetup &DetectorSetup::MinCountTime(std::chrono::microseconds input) { min_count_time = input; return *this; } DetectorSetup &DetectorSetup::MinFrameTime(std::chrono::microseconds 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 DetectorSetup::GetBitDepthReadout() const { return bit_depth_readout; } std::optional 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 input) { if (input && input <= 0) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Saturation limit must be positive number"); saturation_limit = input; return *this; } std::optional 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 DetectorSetup::GetDefaultSettings() const { return settings; } DetectorSetup &DetectorSetup::DefaultSettings(const std::optional &input) { settings = input; return *this; } DetectorSetup DetJF4M(const std::string &description, const std::vector &det_modules_hostname) { return DetJF(8, 2, 8, 36, true, description, det_modules_hostname); } DetectorSetup DetJF9M(const std::string &description, const std::vector &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 &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 &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 &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 &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(input); return *this; }