Files
Jungfraujoch/tests/DetectorSetupTest.cpp
T
leonarski_fandClaude Opus 5 77c6d0f4ec Report the stored image depth in bit_depth_readout, and write underload_value
NXmx has no field for the depth of the stored image - only bit_depth_readout,
"how many bits the electronics record per pixel". The two diverge exactly when
summation is used: the readout keeps the detector's native width while the
summed image must be wider to hold the sum.

Every NXmx reader nonetheless takes bit_depth_readout as the width of the stored
pixel. dxtbx ignores the non-standard bit_depth_image entirely for a generic
NXmx file, derives its masking markers from bit_depth_readout, and raises
"Unsupported integer dtype uint32" for a 32-bit image when the field is absent.
Reporting the electronic value there would mislead precisely where it differs.

So report the image depth in both fields, and drop the machinery that existed to
carry the electronic one for a DECTRIS detector: the SIMPLON read, the
DetectorSetup setter, and the receiver-side propagation of a key that the DECTRIS
stream2 protocol does not even define. JUNGFRAU and PSI EIGER keep their readout
depth, which the FPGA acquisition genuinely needs.

Also write NXmx underload_value, the lowest valid value. Without it a reader
takes the trusted minimum to be -0x7FFFFFFF, so the error-pixel marker sits
inside the trusted range and is consumed as an intensity. Measured with DIALS
3.27 on a written file: trusted_range goes from (-2147483647, 32766) to
(-32767, 32766), so the INT16_MIN gap pixels are now masked.

Third fix in the same area: JFJochReceiverLite::Configure took the image width
from the incoming stream but not the sign, while the image itself is forwarded
byte-for-byte. A detector sending int32 was re-declared uint32, and the VDS
master was typed unsigned over signed data files. Take pixel_signed from the
stream too - it and the width are both carried by the one image_dtype key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 18:15:44 +02:00

188 lines
8.3 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <catch2/catch_all.hpp>
#include "../common/DetectorSetup.h"
#include "../common/NetworkAddressConvert.h"
TEST_CASE("DetectorSetup_MismatchInSize") {
REQUIRE_THROWS(
DetectorSetup(DetectorGeometryModular(8), DetectorType::JUNGFRAU, "JF", {"mx1", "mx2", "mx3", "mx4"}));
REQUIRE_THROWS(
DetectorSetup(DetectorGeometryModular(2), DetectorType::JUNGFRAU, "JF", {"mx1", "mx2", "mx3", "mx4"}));
REQUIRE_NOTHROW(
DetectorSetup(DetectorGeometryModular(4), DetectorType::JUNGFRAU, "JF", {"mx1", "mx2", "mx3", "mx4"}));
}
TEST_CASE("DetectorSetup_MismatchInSize_EIGER") {
REQUIRE_THROWS(DetectorSetup(DetectorGeometryModular(4), DetectorType::EIGER, "JF", {"mx1", "mx2", "mx3", "mx4"}));
REQUIRE_NOTHROW(DetectorSetup(DetectorGeometryModular(2), DetectorType::EIGER, "JF", {"mx1", "mx2", "mx3", "mx4"}));
}
TEST_CASE("DetectorSetup_MismatchInSize_DECTRIS") {
REQUIRE_NOTHROW(DetectorSetup(DetectorGeometryFixed(123, 112), DetectorType::DECTRIS, "JF", {"mx1"}));
REQUIRE_NOTHROW(DetectorSetup(DetectorGeometryFixed(123, 112), DetectorType::DECTRIS, "JF", {}));
REQUIRE_THROWS(DetectorSetup(DetectorGeometryFixed(123, 112), DetectorType::DECTRIS, "JF", {"mx1", "mx2"}));
}
TEST_CASE("DetectorSetup_MismatchInGeometry") {
REQUIRE_THROWS(DetectorSetup(DetectorGeometryFixed(123, 112), DetectorType::EIGER, "JF"));
REQUIRE_THROWS(DetectorSetup(DetectorGeometryFixed(123, 112), DetectorType::JUNGFRAU, "JF"));
REQUIRE_THROWS(DetectorSetup(DetectorGeometryModular(2), DetectorType::DECTRIS, "JF"));
}
TEST_CASE("DetectorSetup_ReadoutDepth") {
// The electronic readout depth is only carried where the acquisition needs it: JUNGFRAU is
// fixed at 16, PSI EIGER takes it from the detector settings, and a DECTRIS detector does not
// carry one at all - what it reports downstream is the image depth.
auto setup = DetDECTRIS(123,123, "zzz", "a");
REQUIRE(!setup.GetBitDepthReadout());
auto setup2 = DetJF(1);
REQUIRE(setup2.GetBitDepthReadout() == 16);
auto setup3 = DetEIGER(1);
REQUIRE(!setup3.GetBitDepthReadout());
}
TEST_CASE("DetectorSetup_ImageDepth") {
auto setup = DetDECTRIS(123,123, "zzz", "a");
REQUIRE_NOTHROW(setup.BitDepthImage(16));
REQUIRE(setup.GetBitDepthImage() == 16);
REQUIRE_THROWS(setup.BitDepthImage(12));
REQUIRE_NOTHROW(setup.BitDepthImage(32));
REQUIRE(setup.GetBitDepthImage() == 32);
REQUIRE_NOTHROW(setup.BitDepthImage(8));
REQUIRE(setup.GetBitDepthImage() == 8);
REQUIRE_THROWS(setup.BitDepthImage(0));
REQUIRE_THROWS(setup.BitDepthImage(15));
REQUIRE_THROWS(setup.BitDepthImage(-1));
auto setup2 = DetJF(1);
REQUIRE(!setup2.GetBitDepthImage());
REQUIRE_THROWS(setup2.BitDepthImage(32));
auto setup3 = DetEIGER(1);
REQUIRE(!setup3.GetBitDepthImage());
REQUIRE_THROWS(setup3.BitDepthImage(32));
}
TEST_CASE("DetectorSetup_ProtoBuf") {
DetectorSetup setup = DetJF(DetectorGeometryModular(4), "JF", {"mx1","mx2","mx3","mx4"});
REQUIRE(setup.GetDescription() == "JF");
REQUIRE(setup.GetDetectorModuleHostname().size() == 4);
REQUIRE(setup.GetDetectorModuleHostname()[3] == "mx4");
REQUIRE(setup.GetPixelSize_mm() == Catch::Approx(0.075));
REQUIRE(setup.GetModulesNum() == 4);
REQUIRE(setup.GetGeometry().GetModulesNum() == 4);
}
TEST_CASE("DetectorSetup_ProtoBuf_FullSpeed") {
DetectorSetup setup = DetJF(DetectorGeometryModular(4), "JF", {"mx1","mx2","mx3","mx4"});
REQUIRE(setup.GetUDPInterfaceCount() == 2);
REQUIRE_NOTHROW(setup.UDPInterfaceCount(1));
REQUIRE_THROWS(setup.UDPInterfaceCount(0));
REQUIRE_THROWS(setup.UDPInterfaceCount(5));
REQUIRE_THROWS(setup.UDPInterfaceCount(-56));
REQUIRE(setup.GetUDPInterfaceCount() == 1);
}
TEST_CASE("DetectorSetup_IPv4Base_2Interfaces") {
DetectorSetup detector = DetJF(DetectorGeometryModular(2));
detector.UDPInterfaceCount(2);
REQUIRE_NOTHROW(detector.BaseIPv4Addr("64.1.124.1"));
REQUIRE(detector.GetSrcIPv4Addr(0) == IPv4AddressFromStr("64.1.124.1"));
REQUIRE(detector.GetSrcIPv4Addr(1) == IPv4AddressFromStr("64.1.124.2"));
REQUIRE(detector.GetSrcIPv4Addr(3) == IPv4AddressFromStr("64.1.124.4"));
REQUIRE_THROWS(detector.GetSrcIPv4Addr(4));
}
TEST_CASE("DetectorSetup_IPv4Base_1Interface") {
DetectorSetup detector = DetJF(DetectorGeometryModular(4));
detector.UDPInterfaceCount(1);
REQUIRE_NOTHROW(detector.BaseIPv4Addr("64.1.124.1"));
REQUIRE(detector.GetSrcIPv4Addr(0) == IPv4AddressFromStr("64.1.124.1"));
REQUIRE(detector.GetSrcIPv4Addr(1) == IPv4AddressFromStr("64.1.124.2"));
REQUIRE(detector.GetSrcIPv4Addr(3) == IPv4AddressFromStr("64.1.124.4"));
REQUIRE_THROWS(detector.GetSrcIPv4Addr(4));
}
TEST_CASE("DetectorSetup_LoadTrimFile") {
DetectorSetup setup(DetectorGeometryModular(4), DetectorType::EIGER, "E", {"mx1","mx2","mx3","mx4","mx5","mx6","mx7","mx8"});
REQUIRE_NOTHROW(setup.SetTrimFiles({"a", "b", "c", "d", "e", "f", "g", "h"}));
REQUIRE(setup.GetTrimFileDirectory().empty());
REQUIRE(setup.GetTrimFileNames().size() == 8);
REQUIRE_NOTHROW(setup.SetTrimFiles({"../../tests"})); // must be directory
REQUIRE(setup.GetTrimFileDirectory() == "../../tests");
REQUIRE(setup.GetTrimFileNames().empty());
REQUIRE_THROWS(setup.SetTrimFiles({"Ridiculous_file_name"}));
}
TEST_CASE("DetectorSetup_LoadGainFile") {
DetectorSetup setup(DetectorGeometryModular(4), DetectorType::JUNGFRAU, "JF", {"mx1","mx2","mx3","mx4"});
REQUIRE_THROWS(setup.LoadGain({}));
REQUIRE(setup.GetGainCalibration().empty());
REQUIRE_THROWS(setup.LoadGain({
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin"
}));
REQUIRE_THROWS(setup.LoadGain({
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin"
}));
REQUIRE_NOTHROW(setup.LoadGain({
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin"
}));
REQUIRE(setup.GetGainCalibration().size() == 4);
}
TEST_CASE("DetectorSetup_LoadGainFile_CopyConstructor") {
DetectorSetup setup(DetectorGeometryModular(4), DetectorType::JUNGFRAU, "JF", {"mx1","mx2","mx3","mx4"});
REQUIRE_NOTHROW(setup.LoadGain({
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin",
"../../tests/test_data/gainMaps_M049.bin"
}));
REQUIRE(setup.GetGainCalibration().size() == 4);
// Copy constructor works
DetectorSetup setup2(setup);
REQUIRE(setup2.GetGainCalibration().size() == 4);
REQUIRE(setup2.GetGainCalibration()[0].GetG0Mean() == setup.GetGainCalibration()[0].GetG0Mean());
REQUIRE(setup2.GetGainCalibration()[3].GetG1Mean() == setup.GetGainCalibration()[3].GetG1Mean());
}
TEST_CASE("DetectorSetup_MaxFrameTime") {
DetectorSetup s1(DetectorGeometryModular(8), DetectorType::JUNGFRAU);
s1.UDPInterfaceCount(1);
REQUIRE(s1.GetMinFrameTime() == MIN_FRAME_TIME_JUNGFRAU_HALF_SPEED);
s1.UDPInterfaceCount(2);
REQUIRE(s1.GetMinFrameTime() == MIN_FRAME_TIME_JUNGFRAU_FULL_SPEED);
DetectorSetup s2(DetectorGeometryModular(8), DetectorType::EIGER);
REQUIRE(s2.GetMinFrameTime() == MIN_FRAME_TIME_EIGER);
}