Files
Jungfraujoch/tests/DiffractionExperimentTest.cpp
T
leonarski_fandClaude Opus 5 26fc4b02b3 symmetry: carry the space group as the group, not as its number
The adopted space group travelled the pipeline as a bare int and was rebuilt
downstream with find_spacegroup_by_number, which returns the reference setting.
So every setting a number cannot name was destroyed one line after it was
determined: P 1 1 2 came back as P 1 2 1, I 1 1 2 as C 1 2 1, R 3:R as R 3:H.

DatasetSettings now holds the gemmi::SpaceGroup itself, DiffractionExperiment
exposes it as GetGemmiSpaceGroup() / GetSpaceGroupOrP1(), and everything that
used to take an int - HKLKeyGenerator (its int constructor is gone, so the
compiler finds the callers), the merge, the R-free flags, French-Wilson, the
reindexing ambiguity, the completeness enumeration, the MTZ and mmCIF exports,
the model validation - takes the group. -S keeps the setting the symbol names
rather than reducing it to a number.

The end message carries both spellings and a reader prefers the name, since
only the name keeps the setting while the number is what a reader written
before the name understands. It carries them over CBOR too: the determined
group was never serialised at all, so a group rugnux chose reached the master
file only when the same process wrote it, and an online writer fell back to
whatever the user had supplied at the start. Both keys are optional additions,
so an older reader skips them and a newer one reads an older sender.

On disk the master's /entry/sample/space_group carries the extended
Hermann-Mauguin name and is what the reader takes the group from, so a setting
survives a _process.h5 and the --mode scale that re-reads it; the number stays
beside it and is the fallback for files written before. Every one of the 230
reference settings the old writer could produce reads back as itself, so older
files are unaffected.

Stage A and Stage B of the search still enumerate reference settings only, so
this determines no group differently today - it is what the enumeration needs
before it can be widened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-08-31 07:16:43 +02:00

1207 lines
42 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/DiffractionExperiment.h"
#include "../compression/JFJochCompressor.h"
#include "../common/NetworkAddressConvert.h"
using namespace std::literals::chrono_literals;
TEST_CASE("DiffractionExperiment_FilePath","[DiffractionExperiment]") {
DiffractionExperiment x;
REQUIRE_NOTHROW(x.FilePrefix("x"));
REQUIRE(x.GetFilePrefix() == "x");
// trailing / needs to be ignored
REQUIRE_THROWS(x.FilePrefix("/x"));
REQUIRE_THROWS(x.FilePrefix("../x"));
REQUIRE_THROWS(x.FilePrefix("x/../y"));
x.Mode(DetectorMode::PedestalG0);
REQUIRE(x.GetFilePrefix() == "x");
x.Mode(DetectorMode::PedestalG1);
REQUIRE(x.GetFilePrefix() == "x");
x.Mode(DetectorMode::PedestalG2);
REQUIRE(x.GetFilePrefix() == "x");
REQUIRE_NOTHROW(x.FilePrefix("x6_master.h5"));
REQUIRE(x.GetFilePrefix() == "x6");
REQUIRE_NOTHROW(x.FilePrefix("long_name/directory/file_master.h5"));
REQUIRE(x.GetFilePrefix() == "long_name/directory/file");
REQUIRE_NOTHROW(x.FilePrefix("_master.h5"));
REQUIRE(x.GetFilePrefix() == "_master.h5");
}
TEST_CASE("DiffractionExperiment_Compression","[DiffractionExperiment]") {
DiffractionExperiment x;
// Compression
x.Compression(CompressionAlgorithm::BSHUF_LZ4);
REQUIRE(x.GetCompressionAlgorithm() == CompressionAlgorithm::BSHUF_LZ4);
x.Compression(CompressionAlgorithm::BSHUF_ZSTD);
REQUIRE(x.GetCompressionAlgorithm() == CompressionAlgorithm::BSHUF_ZSTD);
x.Compression(CompressionAlgorithm::BSHUF_ZSTD_RLE);
REQUIRE(x.GetCompressionAlgorithm() == CompressionAlgorithm::BSHUF_ZSTD_RLE);
x.Compression(CompressionAlgorithm::NO_COMPRESSION);
REQUIRE(x.GetCompressionAlgorithm() == CompressionAlgorithm::NO_COMPRESSION);
}
TEST_CASE("DiffractionExperiment_UnitCell","[DiffractionExperiment]") {
DiffractionExperiment x;
REQUIRE(!x.GetUnitCell());
UnitCell cell{
.a = 10,
.b = 20,
.c = 30,
.alpha = 90,
.beta = 95,
.gamma = 89
};
REQUIRE_NOTHROW(x.SetUnitCell(cell));
REQUIRE(x.GetUnitCell());
REQUIRE(x.GetUnitCell()->c == 30);
REQUIRE_NOTHROW(x.SetUnitCell({}));
REQUIRE(!x.GetUnitCell());
REQUIRE_NOTHROW(x.SetUnitCell(cell));
REQUIRE(x.GetUnitCell());
UnitCell cell_with_zero_angle{
.a = 10,
.b = 20,
.c = 30,
.alpha = 90,
.beta = 0,
.gamma = 89
};
REQUIRE_NOTHROW(x.SetUnitCell(cell_with_zero_angle));
REQUIRE(!x.GetUnitCell());
UnitCell cell_with_zero_dist{
.a = 10,
.b = 20,
.c = 0,
.alpha = 90,
.beta = 90,
.gamma = 89
};
REQUIRE_NOTHROW(x.SetUnitCell(cell_with_zero_dist));
REQUIRE(!x.GetUnitCell());
UnitCell cell_with_nonsense{
.a = 10,
.b = 20,
.c = 30,
.alpha = 90,
.beta = -90,
.gamma = 89
};
REQUIRE_THROWS(x.SetUnitCell(cell_with_nonsense));
}
TEST_CASE("IPv4AddressToStr","") {
REQUIRE(IPv4AddressToStr(0x0f32010a) == "10.1.50.15");
}
TEST_CASE("IPv4AddressFromStr","") {
REQUIRE(IPv4AddressFromStr("10.1.50.15") == 0x0f32010a);
REQUIRE_THROWS(IPv4AddressFromStr("256.257.0.1"));
REQUIRE_THROWS(IPv4AddressFromStr("ff.ff.ff.1"));
}
TEST_CASE("MacAddressToStr","") {
REQUIRE(MacAddressToStr(0xF1EEDDCCBBAA) == "aa:bb:cc:dd:ee:f1");
REQUIRE(MacAddressToStr(0x0000DDCCBB00) == "00:bb:cc:dd:00:00");
}
TEST_CASE("MacAddressFromStr","") {
REQUIRE(MacAddressFromStr("aa:bb:cc:dd:ee:f1") == 0xF1EEDDCCBBAA);
REQUIRE(MacAddressFromStr("11:22:33:44:55:66") == 0x665544332211);
REQUIRE_THROWS(MacAddressFromStr("11:22:33:44:55:66:77"));
REQUIRE_THROWS(MacAddressFromStr("11:22:33:44:55"));
REQUIRE_THROWS(MacAddressFromStr("456:22:33:44:55"));
REQUIRE_THROWS(MacAddressFromStr("xy:22:33:44:55"));
}
TEST_CASE("DiffractionExperiment_DataStreams","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF(18)); // 9M
x.DataStreams(4);
REQUIRE(x.GetModulesNum() == 18);
REQUIRE(x.GetDataStreamsNum() == 4);
REQUIRE(x.GetModulesNum(0) == 5);
REQUIRE(x.GetModulesNum(1) == 5);
REQUIRE(x.GetModulesNum(2) == 4);
REQUIRE(x.GetModulesNum(3) == 4);
REQUIRE_THROWS(x.GetModulesNum(4));
x.DataStreams(2);
REQUIRE(x.GetModulesNum() == 18);
REQUIRE(x.GetDataStreamsNum() == 2);
REQUIRE(x.GetModulesNum(0) == 9);
REQUIRE(x.GetModulesNum(1) == 9);
REQUIRE_THROWS(x.GetModulesNum(2));
x = DiffractionExperiment(DetJF(2));
x.DataStreams(2);
REQUIRE(x.GetModulesNum() == 2);
REQUIRE(x.GetDataStreamsNum() == 2);
REQUIRE(x.GetModulesNum(0) == 1);
REQUIRE(x.GetModulesNum(1) == 1);
x.DataStreams(5);
REQUIRE(x.GetModulesNum() == 2);
REQUIRE(x.GetDataStreamsNum() == 2);
REQUIRE(x.GetModulesNum(0) == 1);
REQUIRE(x.GetModulesNum(1) == 1);
}
TEST_CASE("DiffractionExperiment_DetectorGeometry","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF(18)); // 9M configuration #1 - via constructor
x.Conversion();
x.DataStreams(4);
REQUIRE(x.GetDataStreamsNum() == 4);
REQUIRE(x.GetXPixelsNum() == 1030);
REQUIRE(x.GetYPixelsNum() == 514 * 18);
REQUIRE(x.GetModulesNum() == 18);
REQUIRE(x.GetModulesNum(0) == 5);
REQUIRE(x.GetModulesNum(2) == 4);
REQUIRE(x.GetFirstModuleOfDataStream(0) == 0);
REQUIRE(x.GetFirstModuleOfDataStream(1) == 5);
REQUIRE(x.GetFirstModuleOfDataStream(2) == 10);
REQUIRE(x.GetFirstModuleOfDataStream(3) == 14);
REQUIRE(x.GetPixelsNum() == 18 * 514 * 1030);
x.Raw();
REQUIRE(x.GetPixelsNum() == 18 * 512 * 1024);
REQUIRE(x.GetXPixelsNum() == 1024);
REQUIRE(x.GetYPixelsNum() == 512 * 18);
x.Conversion();
x = DiffractionExperiment(DetJF(18, 2)); // 9M configuration #2
REQUIRE(x.GetPixelsNum() == 1030 * 514 * 18);
REQUIRE(x.GetXPixelsNum() == 1030 * 2);
REQUIRE(x.GetYPixelsNum() == 514 * 18 / 2);
REQUIRE(x.GetPixel0OfModuleConv(0) == 16 * CONVERTED_MODULE_SIZE + 513 * CONVERTED_MODULE_COLS * 2);
REQUIRE(x.GetPixel0OfModuleConv(1) == 16 * CONVERTED_MODULE_SIZE + 513 * CONVERTED_MODULE_COLS * 2 + CONVERTED_MODULE_COLS);
REQUIRE(x.GetPixel0OfModuleConv(12) == 4 * CONVERTED_MODULE_SIZE + 513 * CONVERTED_MODULE_COLS * 2);
REQUIRE(x.GetPixel0OfModuleConv(13) == 4 * CONVERTED_MODULE_SIZE + 513 * CONVERTED_MODULE_COLS * 2 + CONVERTED_MODULE_COLS);
REQUIRE(x.GetPixel0OfModuleConv(14) == 2 * CONVERTED_MODULE_SIZE + 513 * CONVERTED_MODULE_COLS * 2);
REQUIRE(x.GetPixel0OfModuleConv(15) == 2 * CONVERTED_MODULE_SIZE + 513 * CONVERTED_MODULE_COLS * 2 + CONVERTED_MODULE_COLS);
REQUIRE(x.GetPixel0OfModuleConv(16) == 0 * CONVERTED_MODULE_SIZE + 513 * CONVERTED_MODULE_COLS * 2);
REQUIRE(x.GetPixel0OfModuleConv(17) == 0 * CONVERTED_MODULE_SIZE + 513 * CONVERTED_MODULE_COLS * 2 + CONVERTED_MODULE_COLS);
REQUIRE_THROWS(x.GetPixel0OfModuleConv(18));
x.Raw();
REQUIRE(x.GetPixelsNum() == 1024 * 512 * 18);
REQUIRE(x.GetXPixelsNum() == 1024);
REQUIRE(x.GetYPixelsNum() == 512 * 18);
}
TEST_CASE("DiffractionExperiment_DetectorGeometry_gaps","[DiffractionExperiment]") {
const size_t gap_x = 8;
const size_t gap_y = 36;
DiffractionExperiment x(DetJF(18, 2, gap_x, gap_y, false));
x.Conversion();
x.DataStreams(4);
REQUIRE(x.GetDataStreamsNum() == 4);
REQUIRE(x.GetPixelsNum() == (1030 * 2 + gap_x) * (514 * 9 + (9-1) * gap_y) );
REQUIRE(x.GetXPixelsNum() == 1030 * 2 + gap_x);
REQUIRE(x.GetYPixelsNum() == 514 * 9 + (9-1) * gap_y);
REQUIRE(x.GetModulesNum() == 18);
REQUIRE(x.GetModulesNum(0) == 5);
REQUIRE(x.GetModulesNum(2) == 4);
REQUIRE(x.GetFirstModuleOfDataStream(0) == 0);
REQUIRE(x.GetFirstModuleOfDataStream(1) == 5);
REQUIRE(x.GetFirstModuleOfDataStream(2) == 10);
REQUIRE(x.GetFirstModuleOfDataStream(3) == 14);
REQUIRE(x.GetPixel0OfModuleConv(0) == (2*1030 + gap_x) * (514 + gap_y) * (0/2));
REQUIRE(x.GetPixel0OfModuleConv(4) == (2*1030 + gap_x) * (514 + gap_y) * (4/2));
REQUIRE(x.GetPixel0OfModuleConv(8) == (2*1030 + gap_x) * (514 + gap_y) * (8/2));
REQUIRE(x.GetPixel0OfModuleConv(14) == (2*1030 + gap_x) * (514 + gap_y) * (14/2));
REQUIRE(x.GetPixel0OfModuleConv(0) == (2*1030 + gap_x) * 0 * (514 + gap_y));
REQUIRE(x.GetPixel0OfModuleConv(1) == (2*1030 + gap_x) * 0 * (514 + gap_y) + 1030 + gap_x);
REQUIRE(x.GetPixel0OfModuleConv(16) == (2*1030 + gap_x) * 8 * (514 + gap_y));
REQUIRE(x.GetPixel0OfModuleConv(17) == (2*1030 + gap_x) * 8 * (514 + gap_y) + 1030 + gap_x);
}
TEST_CASE("DiffractionExperiment_DetectorGeometry_gaps_mirror_y","[DiffractionExperiment]") {
const size_t gap_x = 8;
const size_t gap_y = 36;
DiffractionExperiment x(DetJF(20, 2, gap_x, gap_y, true));
x.Conversion();
REQUIRE(x.GetPixel0OfModuleConv(0) == (2*1030+gap_x) * 513 + (2*1030 + gap_x) * 9 * (514 + gap_y));
REQUIRE(x.GetPixel0OfModuleConv(1) == (2*1030+gap_x) * 513 + (2*1030 + gap_x) * 9 * (514 + gap_y) + 1030 + gap_x);
REQUIRE(x.GetPixel0OfModuleConv(14) == (2*1030+gap_x) * 513 + (2*1030 + gap_x) * 2 * (514 + gap_y));
REQUIRE(x.GetPixel0OfModuleConv(15) == (2*1030+gap_x) * 513 + (2*1030 + gap_x) * 2 * (514 + gap_y) + 1030 + gap_x);
REQUIRE(x.GetPixel0OfModuleConv(16) == (2*1030+gap_x) * 513 + (2*1030 + gap_x) * 1 * (514 + gap_y));
REQUIRE(x.GetPixel0OfModuleConv(17) == (2*1030+gap_x) * 513 + (2*1030 + gap_x) * 1 * (514 + gap_y) + 1030 + gap_x);
REQUIRE(x.GetPixel0OfModuleConv(18) == (2*1030+gap_x) * 513 + (2*1030 + gap_x) * 0 * (514 + gap_y));
REQUIRE(x.GetPixel0OfModuleConv(19) == (2*1030+gap_x) * 513 + (2*1030 + gap_x) * 0 * (514 + gap_y) + 1030 + gap_x);
REQUIRE_THROWS(x.GetPixel0OfModuleConv(20));
}
TEST_CASE("DiffractionExperiment_Metadata","[DiffractionExperiment]") {
DiffractionExperiment x;
x.DetectorDistance_mm(30.0).BeamX_pxl(1200).BeamY_pxl(1000).FrameTime(900us);
REQUIRE(x.GetBeamX_pxl() == Catch::Approx(1200));
REQUIRE(x.GetBeamY_pxl() == Catch::Approx(1000));
REQUIRE(x.GetDetectorDistance_mm() == Catch::Approx(30.0));
REQUIRE_THROWS(x.PedestalG0Frames(-1));
REQUIRE_THROWS(x.PedestalG1Frames(-1));
REQUIRE_THROWS(x.PedestalG2Frames(-1));
}
TEST_CASE("DiffractionExperiment_Wavelength", "[DiffractionExperiment]") {
DiffractionExperiment x;
x.IncidentEnergy_keV(6.0);
REQUIRE(x.GetIncidentEnergy_keV() == 6.0);
REQUIRE(x.GetWavelength_A() == Catch::Approx(12.39854 / 6.0));
// From https://advanced-microscopy.utah.edu/education/electron-micro/
// Therefore, the wavelength at 100 keV, 200 keV, and 300 keV in electron microscopes
// is 3.70 pm, 2.51 pm and 1.96 pm, respectively.
x.IncidentEnergy_keV(100.0).ElectronSource(true);
REQUIRE(x.GetIncidentEnergy_keV() == 100.0);
CHECK(fabs(x.GetWavelength_A() - 0.0370) < 0.0005);
x.IncidentEnergy_keV(200.0).ElectronSource(true);
REQUIRE(x.GetIncidentEnergy_keV() == 200.0);
CHECK(fabs(x.GetWavelength_A() - 0.0251) < 0.0005);
x.IncidentEnergy_keV(300.0).ElectronSource(true);
REQUIRE(x.GetIncidentEnergy_keV() == 300.0);
CHECK(fabs(x.GetWavelength_A() - 0.0196) < 0.0005);
}
TEST_CASE("DiffractionExperiment_ZMQPreview", "[DiffractionExperiment]") {
DiffractionExperiment x;
x.ZMQPreviewPeriod(0ms);
REQUIRE(x.GetZMQPreviewPeriod().has_value());
REQUIRE(x.GetZMQPreviewPeriod().value().count() == 0);
x.ZMQPreviewPeriod({});
REQUIRE(!x.GetZMQPreviewPeriod().has_value());
x.ZMQPreviewPeriod(1s);
REQUIRE(x.GetZMQPreviewPeriod() == std::chrono::milliseconds(1000));
REQUIRE_THROWS(x.ZMQPreviewPeriod(-5ms));
x.Mode(DetectorMode::PedestalG0);
REQUIRE(!x.GetZMQPreviewPeriod().has_value());
x.Mode(DetectorMode::PedestalG1);
REQUIRE(!x.GetZMQPreviewPeriod().has_value());
x.Mode(DetectorMode::PedestalG2);
REQUIRE(!x.GetZMQPreviewPeriod().has_value());
}
TEST_CASE("DiffractionExperiment_SpaceGroup", "[DiffractionExperiment]") {
DiffractionExperiment x;
REQUIRE(!x.GetGemmiSpaceGroup());
REQUIRE(x.GetCrystalSystem() == gemmi::CrystalSystem::Monoclinic);
REQUIRE_THROWS(x.SpaceGroupNumber(-1));
REQUIRE_THROWS(x.SpaceGroupNumber(500));
REQUIRE_NOTHROW(x.SpaceGroupNumber(96));
REQUIRE(x.GetSpaceGroupNumber() == 96);
REQUIRE(x.GetCrystalSystem() == gemmi::CrystalSystem::Tetragonal);
REQUIRE(x.GetGemmiSpaceGroup());
REQUIRE(x.GetSpaceGroupName() == "P43212");
REQUIRE(x.GetSpaceGroupOrP1().number == 96);
x.SpaceGroupNumber(std::nullopt);
REQUIRE(!x.GetGemmiSpaceGroup());
REQUIRE(x.GetSpaceGroupOrP1().number == 1);
}
TEST_CASE("DiffractionExperiment_SpaceGroupSetting", "[DiffractionExperiment]") {
// The experiment carries the group itself, so a non-reference setting survives it. Set by number
// the same groups come back as their reference settings, which is what the number can name.
DiffractionExperiment x;
for (const char *setting : {"P 1 1 2", "I 1 1 2", "R 3:R", "P 2 21 2"}) {
const gemmi::SpaceGroup *sg = gemmi::find_spacegroup_by_name(setting);
REQUIRE(sg != nullptr);
x.SetSpaceGroup(*sg);
CHECK(x.GetGemmiSpaceGroup()->xhm() == setting);
CHECK(x.GetSpaceGroupNumber() == sg->number);
CHECK(x.GetCentering() == sg->centring_type());
x.SpaceGroupNumber(sg->number);
CHECK(x.GetGemmiSpaceGroup()->xhm() != setting);
}
x.SetSpaceGroup(std::nullopt);
CHECK(!x.GetGemmiSpaceGroup());
}
TEST_CASE("DiffractionExperiment_SampleName", "[DiffractionExperiment]") {
DiffractionExperiment x;
REQUIRE_NOTHROW(x.SampleName(""));
REQUIRE(x.GetSampleName().empty());
REQUIRE_NOTHROW(x.SampleName("test_sample"));
REQUIRE(x.GetSampleName() == "test_sample");
}
TEST_CASE("DiffractionExperiment_SaveCalibration", "[DiffractionExperiment]") {
DiffractionExperiment x;
x.ImagesPerTrigger(2).NumTriggers(2);
REQUIRE(!x.GetSaveCalibration());
x.ImagesPerTrigger(1).NumTriggers(5);
REQUIRE(x.GetSaveCalibration());
x.ImagesPerTrigger(5).NumTriggers(1);
REQUIRE(x.GetSaveCalibration());
x.Mode(DetectorMode::PedestalG0);
REQUIRE(!x.GetSaveCalibration());
}
TEST_CASE("DiffractionExperiment_FrameCountTime_JUNGFRAU","[DiffractionExperiment]") {
DetectorSetup setup(DetJF(1));
setup.ReadOutTime(50us);
DiffractionExperiment x(setup);
REQUIRE_NOTHROW(x.FrameTime(1000us, 3us));
REQUIRE_NOTHROW(x.FrameTime(1000us));
REQUIRE(x.GetFrameCountTime() == 1000us - 50us);
REQUIRE_NOTHROW(x.FrameTime(2000us));
REQUIRE_THROWS(x.FrameTime(400us));
REQUIRE_THROWS(x.FrameTime(2500us));
REQUIRE_THROWS(x.FrameTime(10000us, 2100us));
REQUIRE_NOTHROW(x.FrameTime(10000us, 10us));
}
TEST_CASE("DiffractionExperiment_FrameCountTime_JUNGFRAU_PedestalG1G2","[DiffractionExperiment]") {
DetectorSetup setup(DetJF(1));
setup.ReadOutTime(50us);
DiffractionExperiment x(setup);
REQUIRE_NOTHROW(x.FrameTime(1000us));
REQUIRE(x.GetFrameTime() == 1000us);
REQUIRE(x.GetFrameCountTime() == 1000us - 50us);
x.Mode(DetectorMode::PedestalG1);
REQUIRE(x.GetFrameTime() == FRAME_TIME_PEDE_G1G2);
REQUIRE(x.GetFrameCountTime() == 1000us - 50us);
}
TEST_CASE("DiffractionExperiment_InternalPacketGenerator", "[DiffractionExperiment]") {
DiffractionExperiment x;
x.NumTriggers(50).ImagesPerTrigger(20);
REQUIRE(x.GetNumTriggers() == 50);
// Default is false
REQUIRE(!x.IsUsingInternalPacketGen());
x.UseInternalPacketGenerator(true);
REQUIRE(x.IsUsingInternalPacketGen());
REQUIRE(x.GetNumTriggers() == 50);
REQUIRE(x.GetFrameNum() == 50*20);
}
TEST_CASE("DiffractionExperiment_CopyConstructor", "[DiffractionExperiment]") {
DiffractionExperiment a = DiffractionExperiment();
a.BeamX_pxl(150);
REQUIRE(a.GetBeamX_pxl() == 150.0);
DiffractionExperiment b(a);
REQUIRE(b.GetBeamX_pxl() == 150.0);
b.BeamX_pxl(100);
REQUIRE(a.GetBeamX_pxl() == 150.0);
REQUIRE(b.GetBeamX_pxl() == 100.0);
DiffractionExperiment c = b;
REQUIRE (c.GetBeamX_pxl() == 100.0);
c.BeamX_pxl(10);
REQUIRE (a.GetBeamX_pxl() == 150.0);
REQUIRE (b.GetBeamX_pxl() == 100.0);
REQUIRE (c.GetBeamX_pxl() == 10.0);
}
TEST_CASE("DiffractionExperiment_AzIntegration_QRange","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF(8, 2, 8, 36));
REQUIRE_THROWS(x.QRangeForAzimInt_recipA(-1,5));
REQUIRE_THROWS(x.QRangeForAzimInt_recipA(2,1));
REQUIRE_THROWS(x.QRangeForAzimInt_recipA(0,5));
REQUIRE_THROWS(x.QRangeForAzimInt_recipA(1,12));
REQUIRE_THROWS(x.QRangeForAzimInt_recipA(1,std::nanf("")));
REQUIRE_THROWS(x.QRangeForAzimInt_recipA(std::nanf(""),1));
REQUIRE_NOTHROW(x.QRangeForAzimInt_recipA(1, 5));
REQUIRE_NOTHROW(x.QRangeForAzimInt_recipA(0.01, 0.1));
}
TEST_CASE("DiffractionExperiment_AzIntegration_QSpacing","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF(8, 2, 8, 36));
x.QSpacingForAzimInt_recipA(0.456);
REQUIRE(x.GetQSpacingForAzimInt_recipA() == Catch::Approx(0.456));
DiffractionExperiment y(x);
REQUIRE(y.GetQSpacingForAzimInt_recipA() == Catch::Approx(0.456));
}
TEST_CASE("DiffractionExperiment_AzIntegration_Bins","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF(8, 2, 8, 36));
x.QRangeForAzimInt_recipA(1,6);
x.QSpacingForAzimInt_recipA(1.0);
REQUIRE(x.GetAzimuthalIntegrationSettings().GetBinCount() == 5);
x.QSpacingForAzimInt_recipA(0.3);
REQUIRE(x.GetAzimuthalIntegrationSettings().GetBinCount() == 17); // 5 / 0.3 = 16.6
x.QSpacingForAzimInt_recipA(0.1);
REQUIRE(x.GetAzimuthalIntegrationSettings().GetBinCount() == 50);
}
TEST_CASE("DiffractionExperiment_StorageCells","[DiffractionExperiment]") {
const int64_t num_triggers = 20;
DiffractionExperiment x;
x.FrameTime(std::chrono::milliseconds(1)).ImagesPerTrigger(5).NumTriggers(num_triggers);
REQUIRE(x.GetNumTriggers() == num_triggers);
REQUIRE_NOTHROW(x.StorageCells(5));
REQUIRE(x.GetStorageCellNumber() == 5);
REQUIRE_THROWS(x.StorageCells(0));
REQUIRE_THROWS(x.StorageCells(-1));
REQUIRE_THROWS(x.StorageCells(32));
REQUIRE_NOTHROW(x.StorageCells(3));
REQUIRE(x.GetStorageCellNumber() == 3);
REQUIRE_NOTHROW(x.StorageCells(15));
REQUIRE(x.GetStorageCellNumber() == 15);
REQUIRE_NOTHROW(x.StorageCells(4));
REQUIRE(x.GetStorageCellNumber() == 4);
REQUIRE_NOTHROW(x.StorageCells(8));
REQUIRE(x.GetStorageCellNumber() == 8);
x.StorageCells(16);
REQUIRE(x.GetStorageCellNumber() == 16);
REQUIRE(x.GetFrameNumPerTrigger() == x.GetStorageCellNumber());
REQUIRE(x.GetImageNum() == x.GetStorageCellNumber() * num_triggers);
REQUIRE(x.GetFrameNum() == x.GetStorageCellNumber() * num_triggers);
x.UseInternalPacketGenerator(true);
REQUIRE(x.GetFrameNumPerTrigger() == x.GetStorageCellNumber());
REQUIRE(x.GetImageNum() == x.GetStorageCellNumber() * num_triggers);
REQUIRE(x.GetFrameNum() == x.GetStorageCellNumber() * num_triggers);
}
TEST_CASE("DiffractionExperiment_StorageCells_Pedestal","[DiffractionExperiment]") {
DiffractionExperiment x;
x.PedestalG0Frames(1456).PedestalG1Frames(323).PedestalG2Frames(456).StorageCells(16);
x.Mode(DetectorMode::PedestalG0);
REQUIRE(x.GetStorageCellNumber() == 16);
REQUIRE(x.GetNumTriggers() == 1);
REQUIRE(x.GetFrameNumPerTrigger() == 1456*16);
REQUIRE(x.GetFrameNum() == 1456 * 16);
x.Mode(DetectorMode::PedestalG1);
REQUIRE(x.GetStorageCellNumber() == 2);
REQUIRE(x.GetNumTriggers() == 1);
REQUIRE(x.GetFrameNumPerTrigger() == 2 * 323);
REQUIRE(x.GetFrameNum() == 323 * 2);
x.StorageCells(8);
x.Mode(DetectorMode::PedestalG2);
REQUIRE(x.GetStorageCellNumber() == 2);
REQUIRE(x.GetNumTriggers() == 1);
REQUIRE(x.GetFrameNumPerTrigger() == 2 * 456);
}
TEST_CASE("DiffractionExperiment_StorageCells_Pedestal_FixedG1","[DiffractionExperiment]") {
DiffractionExperiment x;
x.PedestalG0Frames(1456).PedestalG1Frames(323).PedestalG2Frames(456).StorageCells(15).FixedGainG1(true);
x.Mode(DetectorMode::PedestalG1);
REQUIRE(x.GetStorageCellNumber() == 15);
REQUIRE(x.GetNumTriggers() == 1);
REQUIRE(x.GetFrameNumPerTrigger() == 15 * 323);
REQUIRE(x.GetFrameNum() == 323 * 15);
}
TEST_CASE("DiffractionExperiment_ImageTime","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF(3));
DatasetSettings s;
x.FrameTime(583us);
s.ImageTime(583us * 4);
x.ImportDatasetSettings(s);
REQUIRE(x.GetSummation() == 4);
s.ImageTime({});
x.ImportDatasetSettings(s);
REQUIRE(x.GetSummation() == 1);
s.ImageTime(585us);
REQUIRE_THROWS(x.ImportDatasetSettings(s));
}
TEST_CASE("DiffractionExperiment_GridScan","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF(3));
DatasetSettings s;
s.NumTriggers(1).ImagesPerTrigger(89);
s.GridScan(GridScanSettings(10, 1.0, 1.0, false, false));
REQUIRE(s.GridScan());
CHECK(s.GridScan()->GetNFast() == 10);
CHECK(s.GridScan()->GetNSlow() == 1);
CHECK(s.GridScan()->GetNElem() == 10);
x.ImportDatasetSettings(s);
REQUIRE(x.GetGridScan());
REQUIRE(x.GetGridScan()->GetNFast() == 10);
REQUIRE(x.GetGridScan()->GetNSlow() == 9);
}
TEST_CASE("DiffractionExperiment_ImageTime_EIGER","[DiffractionExperiment]") {
auto readout = 20us;
DiffractionExperiment x(DetEIGER(3));
DatasetSettings s;
x.FrameTime(583us);
REQUIRE(x.GetDetectorSetup().GetReadOutTime() == readout);
s.ImageTime(5ms);
x.ImportDatasetSettings(s);
REQUIRE(x.GetSummation() == 1);
REQUIRE(x.GetFrameTime() == 5ms);
REQUIRE(x.GetFrameCountTime() == 5000us - readout);
REQUIRE(x.GetEigerBitDepth() == 32);
REQUIRE(x.GetBitDepthReadout() == 32);
s.ImageTime({});
REQUIRE_NOTHROW(x.ImportDatasetSettings(s));
REQUIRE(x.GetSummation() == 1);
REQUIRE(x.GetFrameTime() == 583us);
REQUIRE(x.GetFrameCountTime() == 583us - readout);
REQUIRE(x.GetEigerBitDepth() == 16);
REQUIRE(x.GetBitDepthReadout() == 16);
s.ImageTime(200us);
REQUIRE_THROWS(x.ImportDatasetSettings(s));
s.ImageTime(250us);
REQUIRE_NOTHROW(x.ImportDatasetSettings(s));
REQUIRE(x.GetSummation() == 1);
REQUIRE(x.GetBitDepthReadout() == 8);
REQUIRE(x.GetEigerBitDepth() == 8);
}
TEST_CASE("DiffractionExperiment_ImageTime_DECTRIS","[DiffractionExperiment]") {
DiffractionExperiment x(DetDECTRIS(123,456,"D",{}));
x.Detector().MinFrameTime(582us);
DatasetSettings s;
x.FrameTime(583us);
REQUIRE(x.GetDetectorSetup().GetReadOutTime() == 0us);
s.ImageTime(5ms);
x.ImportDatasetSettings(s);
REQUIRE(x.GetSummation() == 1);
REQUIRE(x.GetFrameTime() == 5ms);
REQUIRE(x.GetFrameCountTime() == 5000us);
s.ImageTime({});
REQUIRE_NOTHROW(x.ImportDatasetSettings(s));
REQUIRE(x.GetSummation() == 1);
REQUIRE(x.GetFrameTime() == 583us);
REQUIRE(x.GetFrameCountTime() == 583us);
s.ImageTime(581us);
REQUIRE_THROWS(x.ImportDatasetSettings(s));
}
TEST_CASE("DiffractionExperiment_PulsedSource","[DiffractionExperiment]") {
DiffractionExperiment x;
REQUIRE(!x.IsPulsedSource()); // default must be off
x.ImagesPerTrigger(50).NumTriggers(100).Mode(DetectorMode::Standard).PedestalG0Frames(1000)
.PedestalG1Frames(200).PedestalG2Frames(100);
x.PulsedSource(true);
REQUIRE(x.GetFrameNumPerTrigger() == 1);
REQUIRE(x.GetImageNum() == 100);
x.Summation(10);
REQUIRE(x.GetFrameNumPerTrigger() == 1);
REQUIRE(x.GetNumTriggers() == 100 * 10);
REQUIRE(x.GetFrameNum() == 100 * 10);
REQUIRE(x.GetImageNum() == 100);
x.Raw();
REQUIRE(x.GetSummation() == 1);
REQUIRE(x.GetFrameNumPerTrigger() == 1);
REQUIRE(x.GetNumTriggers() == 100);
REQUIRE(x.GetFrameNum() == 100);
REQUIRE(x.GetImageNum() == 100);
x.Mode(DetectorMode::PedestalG0);
REQUIRE(x.GetImageNum() == 0);
REQUIRE(x.GetFrameNumPerTrigger() == 1000);
REQUIRE(x.GetNumTriggers() == 1);
x.Mode(DetectorMode::PedestalG1);
REQUIRE(x.GetImageNum() == 0);
REQUIRE(x.GetFrameNumPerTrigger() == 200);
REQUIRE(x.GetNumTriggers() == 1);
x.Mode(DetectorMode::PedestalG2);
REQUIRE(x.GetImageNum() == 0);
REQUIRE(x.GetFrameNumPerTrigger() == 100);
REQUIRE(x.GetNumTriggers() == 1);
}
TEST_CASE("DiffractionExperiment_DefaultDataProcessingSettings","[DiffractionExperiment]") {
REQUIRE_NOTHROW(DiffractionExperiment::CheckDataProcessingSettings(
DiffractionExperiment::DefaultDataProcessingSettings()));
}
TEST_CASE("DiffractionExperiment_FPGA_PixelSigned_JUNGFRAU","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF(4));
x.Conversion().PixelSigned({});
REQUIRE(x.IsPixelSigned());
x.Raw().PixelSigned({});
REQUIRE(!x.IsPixelSigned());
x.Conversion().PixelSigned(false);
REQUIRE(!x.IsPixelSigned());
x.Raw().PixelSigned(true);
REQUIRE(x.IsPixelSigned());
}
TEST_CASE("DiffractionExperiment_FPGA_PixelSigned_EIGER","[DiffractionExperiment]") {
DiffractionExperiment x(DetEIGER(4));
x.Conversion().PixelSigned({});
REQUIRE(!x.IsPixelSigned());
x.Raw().PixelSigned({});
REQUIRE(!x.IsPixelSigned());
x.Conversion().PixelSigned(false);
REQUIRE(!x.IsPixelSigned());
x.Raw().PixelSigned(true);
REQUIRE(x.IsPixelSigned());
}
TEST_CASE("DiffractionExperiment_Summation","[DiffractionExperiment]") {
DiffractionExperiment x;
REQUIRE_THROWS(x.Summation(0));
REQUIRE_THROWS(x.Summation(-1));
REQUIRE_NOTHROW(x.Summation(1));
REQUIRE(x.GetByteDepthImage() == 2);
REQUIRE(x.GetSummation() == 1);
REQUIRE(x.GetFPGASummation() == 1);
REQUIRE_NOTHROW(x.Summation(2));
REQUIRE(x.GetSummation() == 2);
REQUIRE(x.GetFPGASummation() == 2);
REQUIRE(x.GetByteDepthImage() == 2);
REQUIRE_NOTHROW(x.Summation(3));
REQUIRE(x.GetByteDepthImage() == 4);
REQUIRE(x.GetSummation() == 3);
REQUIRE(x.GetFPGASummation() == 3);
x.BitDepthImage(16);
REQUIRE(x.GetByteDepthImage() == 2);
x.BitDepthImage(32);
REQUIRE(x.GetByteDepthImage() == 4);
x.BitDepthImage(16);
REQUIRE_NOTHROW(x.Summation(500));
CHECK(x.GetByteDepthImage() == 4);
CHECK(x.GetSummation() == 500);
CHECK(x.GetFPGASummation() == 1);
CHECK(x.IsCPUSummation());
}
TEST_CASE("DiffractionExperiment_DetectorModuleHostname","[DiffractionExperiment]") {
std::vector<std::string> h = {"mx1", "mx2", "mx3"};
DiffractionExperiment x(DetectorSetup(DetectorGeometryModular(3),
DetectorType::JUNGFRAU,
"X", h));
std::vector<AcquisitionDeviceNetConfig> net_cfg;
net_cfg.push_back(AcquisitionDeviceNetConfig{
.mac_addr = "00:00:00:00:00:00",
.ipv4_addr = "10.10.50.1",
.udp_port = 1234});
net_cfg.push_back(AcquisitionDeviceNetConfig{
.mac_addr = "00:00:00:00:00:01",
.ipv4_addr = "10.10.50.2",
.udp_port = 1234});
std::vector<std::string> h_out;
REQUIRE_NOTHROW(h_out = x.GetDetectorModuleHostname());
REQUIRE(h == h_out);
auto det_cfg = x.GetDetectorModuleConfig(net_cfg);
REQUIRE(det_cfg.size() == x.GetModulesNum());
}
TEST_CASE("DiffractionExperiment_GetImageFillValue","") {
DiffractionExperiment x;
x.BitDepthImage(16).PixelSigned(false);
REQUIRE(x.GetImageFillValue() == UINT16_MAX);
x.BitDepthImage(16).PixelSigned(true);
REQUIRE(x.GetImageFillValue() == INT16_MIN);
x.BitDepthImage(32).PixelSigned(true);
REQUIRE(x.GetImageFillValue() == INT32_MIN);
x.BitDepthImage(32).PixelSigned(false);
REQUIRE(x.GetImageFillValue() == UINT32_MAX);
x.BitDepthImage(8).PixelSigned(true);
REQUIRE(x.GetImageFillValue() == INT8_MIN);
x.BitDepthImage(8).PixelSigned(false);
REQUIRE(x.GetImageFillValue() == UINT8_MAX);
}
TEST_CASE("DiffractionExperiment_GetUnderflow","") {
DiffractionExperiment x;
x.BitDepthImage(16).PixelSigned(true);
REQUIRE(x.GetUnderflow() == INT16_MIN);
x.BitDepthImage(16).PixelSigned(false);
REQUIRE(x.GetUnderflow() == -1);
x.BitDepthImage(32).PixelSigned(true);
REQUIRE(x.GetUnderflow() == INT32_MIN);
x.BitDepthImage(32).PixelSigned(false);
REQUIRE(x.GetUnderflow() == -1);
x.BitDepthImage(8).PixelSigned(true);
REQUIRE(x.GetUnderflow() == INT8_MIN);
x.BitDepthImage(8).PixelSigned(false);
REQUIRE(x.GetUnderflow() == -1);
}
TEST_CASE("DiffractionExperiment_GetSaturationLevel_Implicit","") {
DiffractionExperiment x;
x.BitDepthImage(32).PixelSigned(true);
REQUIRE(x.GetSaturationLimit() == INT32_MAX);
x.BitDepthImage(32).PixelSigned(false);
REQUIRE(x.GetSaturationLimit() == UINT32_MAX);
x.BitDepthImage(8).PixelSigned(true);
REQUIRE(x.GetSaturationLimit() == INT8_MAX);
x.BitDepthImage(8).PixelSigned(false);
REQUIRE(x.GetSaturationLimit() == UINT8_MAX);
}
TEST_CASE("DiffractionExperiment_GetSaturationLevel_Explicit","") {
DiffractionExperiment x;
x.BitDepthImage(8).PixelSigned(false);
x.Detector().SaturationLimit(954);
REQUIRE(x.GetSaturationLimit() == UINT8_MAX); // 954 > UINT8_MAX
x.BitDepthImage(8).PixelSigned(true);
REQUIRE(x.GetSaturationLimit() == INT8_MAX); // 954 > UINT8_MAX
x.BitDepthImage(32).PixelSigned(false);
REQUIRE(x.GetSaturationLimit() == 954);
x.BitDepthImage(16).PixelSigned(true);
REQUIRE(x.GetSaturationLimit() == 954);
}
TEST_CASE("DiffractionExperiment_Appendix","") {
DiffractionExperiment x;
StartMessage message;
x.FillMessage(message);
REQUIRE(message.user_data == nlohmann::json());
REQUIRE(x.GetImageAppendix() == nlohmann::json());
REQUIRE(x.GetHeaderAppendix() == nlohmann::json());
x.ImageAppendix("ImageAppendix").HeaderAppendix("HeaderAppendix");
x.FillMessage(message);
REQUIRE(x.GetImageAppendix() == "ImageAppendix");
REQUIRE(x.GetHeaderAppendix() == "HeaderAppendix");
REQUIRE(message.user_data == "HeaderAppendix");
}
TEST_CASE("DiffractionExperiment_DarkMask","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF4M());
REQUIRE_THROWS(x.Mode(DetectorMode::DarkMask));
REQUIRE(x.GetDetectorMode() == DetectorMode::Standard);
x.Detector(DetEIGER(1));
REQUIRE_THROWS(x.Mode(DetectorMode::DarkMask));
REQUIRE(x.GetDetectorMode() == DetectorMode::Standard);
x.Detector(DetDECTRIS(1024,1024, "Det1", ""));
x.FrameTime(std::chrono::milliseconds(100)).ImagesPerTrigger(100).NumTriggers(3);
x.IncidentEnergy_keV(10.0);
DarkMaskSettings dark_mask_settings;
dark_mask_settings.FrameTime(std::chrono::milliseconds(20)).NumberOfFrames(456).Threshold_keV(3.4);
x.ImportDarkMaskSettings(dark_mask_settings);
CHECK(x.GetNumTriggers() == 3);
CHECK(x.GetImageNum() == 3 * 100);
CHECK(x.GetEigerThreshold_keV() == Catch::Approx(5.0));
CHECK(x.GetDarkMaskNumberOfFrames() == 456);
REQUIRE_NOTHROW(x.Mode(DetectorMode::DarkMask));
REQUIRE(x.GetDetectorMode() == DetectorMode::DarkMask);
CHECK(x.GetNumTriggers() == 1);
CHECK(x.GetFrameNumPerTrigger() == 456);
CHECK(x.GetFrameNum() == 456);
CHECK(x.GetEigerThreshold_keV() == Catch::Approx(3.4));
CHECK(x.GetFrameTime() == std::chrono::milliseconds(20));
CHECK(x.GetImageTime() == std::chrono::milliseconds(20));
CHECK(x.GetImageNum() == 0);
}
TEST_CASE("DiffractionExperiment_ConversionOnFPGA","[DiffractionExperiment]") {
DiffractionExperiment x;
x.Conversion();
REQUIRE(x.IsJungfrauConvPhotonCnt()); // conversion on FPGA must be default true!
x.Raw();
REQUIRE(!x.IsJungfrauConvPhotonCnt()); // conversion on FPGA off for raw mode
x.Mode(DetectorMode::PedestalG0);
REQUIRE(!x.IsJungfrauConvPhotonCnt()); // conversion on FPGA off for pedestal modes
x.Mode(DetectorMode::PedestalG1);
REQUIRE(!x.IsJungfrauConvPhotonCnt()); // conversion on FPGA off for pedestal modes
x.Mode(DetectorMode::PedestalG2);
REQUIRE(!x.IsJungfrauConvPhotonCnt()); // conversion on FPGA off for pedestal modes
x.Mode(DetectorMode::Standard);
x.JungfrauConvPhotonCnt(false);
REQUIRE(!x.IsJungfrauConvPhotonCnt()); // conversion on FPGA turned explicitly off
x.JungfrauConvPhotonCnt(true);
REQUIRE(x.IsJungfrauConvPhotonCnt()); // conversion on FPGA back on
}
TEST_CASE("DiffractionExperiment_EIGER","[DiffractionExperiment]") {
DiffractionExperiment x(DetEIGER(4));
x.Conversion();
REQUIRE(!x.IsJungfrauConvPhotonCnt());
REQUIRE(!x.IsPixelSigned());
x.ImagesPerTrigger(245).NumTriggers(100);
REQUIRE(!x.GetSaveCalibration());
}
TEST_CASE("DiffractionExperiment_JungfrauConversionFactor","[DiffractionExperiment]") {
DiffractionExperiment x(DetJF(4));
x.Conversion().IncidentEnergy_keV(16.0);
REQUIRE(!x.GetJungfrauConversionFactor_keV().has_value());
REQUIRE(x.GetIncidentEnergy_keV() == 16.0f);
REQUIRE(x.GetPhotonEnergyForConversion_keV() == 16.0f);
x.JungfrauConversionFactor_keV(0.25f);
REQUIRE(x.GetJungfrauConversionFactor_keV().has_value());
REQUIRE(x.GetJungfrauConversionFactor_keV() == 0.25f);
REQUIRE(x.GetIncidentEnergy_keV() == 16.0f);
REQUIRE(x.GetPhotonEnergyForConversion_keV() == 0.25f);
}
TEST_CASE("DiffractionExperiment_SeriesID","[DiffractionExperiment]") {
DiffractionExperiment x;
REQUIRE(x.GetRunNumber() == 0);
x.IncrementRunNumber();
REQUIRE(x.GetRunNumber() == 1);
DatasetSettings d;
d.RunNumber(25).RunName("run1");
x.ImportDatasetSettings(d);
REQUIRE(x.GetRunNumber() == 25);
REQUIRE(x.GetRunName() == "run1");
d.RunNumber({}).RunName({}).FilePrefix("file1");
x.ImportDatasetSettings(d);
REQUIRE(x.GetRunNumber() == 1);
REQUIRE(x.GetRunName() == "1:file1");
}
TEST_CASE("DiffractionExperiment_GetDefaultPlotBinning", "[DiffractionExperiment]") {
DiffractionExperiment x;
x.FrameTime(std::chrono::milliseconds(1));
x.ImagesPerTrigger(5000);
CHECK(x.GetDefaultPlotBinning() == 500); // 5000 x 1 ms = 5 s, so binning of 500 ms
x.Summation(2).ImagesPerTrigger(2500);
CHECK(x.GetDefaultPlotBinning() == 250); // 2500 x 1 ms = 5 s, so binning of 500 ms
x.ImagesPerTrigger(2499); // less than 5s, no binning
CHECK(x.GetDefaultPlotBinning() == 1);
x.ImagesPerTrigger(1); // 1 image, no binning
CHECK(x.GetDefaultPlotBinning() == 1);
x.Detector(DetEIGER(4));
DatasetSettings ds;
ds.ImageTime(1s);
REQUIRE_NOTHROW(x.ImportDatasetSettings(ds));
x.ImagesPerTrigger(10000); // 10k images, but with 1s image time, there should be no binning in any case
REQUIRE(x.GetDefaultPlotBinning() == 1);
}
TEST_CASE("DiffractionExperiment_ImportDataset_TooManyFrames", "[DiffractionExperiment]") {
DiffractionExperiment x(DetJF4M());
x.ImagesPerTrigger(345).NumTriggers(17);
DatasetSettings dataset;
dataset.ImagesPerTrigger(100000).NumTriggers(100000);
REQUIRE_THROWS(x.ImportDatasetSettings(dataset));
REQUIRE(x.GetImageNum() == 345 * 17);
}
TEST_CASE("DiffractionExperiment_AutoSummation", "[DiffractionExperiment]") {
DiffractionExperiment x(DetJF4M());
x.Summation(4).AutoSummation(true);
x.Conversion().StorageCells(1);
x.AutoSummation(true);
REQUIRE(x.GetSummation() == 4);
x.Raw().StorageCells(1);
x.AutoSummation(true);
REQUIRE(x.GetSummation() == 4);
x.Mode(DetectorMode::PedestalG0).StorageCells(1);
x.AutoSummation(true);
REQUIRE(x.GetSummation() == 1);
x.Conversion().StorageCells(2);
x.AutoSummation(true);
REQUIRE(x.GetSummation() == 1);
x.Conversion().StorageCells(1);
x.AutoSummation(false);
REQUIRE(x.GetSummation() == 1);
x.Raw().StorageCells(1);
x.AutoSummation(false);
REQUIRE(x.GetSummation() == 1);
}
TEST_CASE("DiffractionExperiment_ExportROIMask", "[DiffractionExperiment]") {
DiffractionExperiment x(DetJF4M());
x.Raw();
x.ROI().SetROI(ROIDefinition{.boxes = {
ROIBox("roi0", 800 , 800, 0, 4),
ROIBox("roi1", 0 , 1, 2162, 2163)
}});
int64_t xpixel = 2068;
int64_t ypixel = 2164;
auto map = x.ExportROIMap();
REQUIRE(map.size() == xpixel * ypixel);
CHECK(map[800] == 1);
CHECK(map[801] == 0);
CHECK(map[2162 * xpixel + 1] == 2);
}
TEST_CASE("DiffractionExperiment_PedestalRun","[DiffractionExperiment]") {
DiffractionExperiment x;
REQUIRE(!x.IsPedestalRun()); // default - no pedestal
x.ApplyPixelMask(true);
REQUIRE(x.IsApplyPixelMask());
x.Mode(DetectorMode::PedestalG0);
REQUIRE(x.IsPedestalRun());
REQUIRE(!x.IsApplyPixelMask());
x.Mode(DetectorMode::PedestalG1);
REQUIRE(x.IsPedestalRun());
REQUIRE(!x.IsApplyPixelMask());
x.Mode(DetectorMode::PedestalG2);
REQUIRE(x.IsPedestalRun());
REQUIRE(!x.IsApplyPixelMask());
}
TEST_CASE("DiffractionExperiment_ImagesPerFile_Defaults", "[DiffractionExperiment]") {
// images_per_file is optional in the settings, because at setup time the acquisition it should
// suit is not necessarily known. GetImagesPerFile() is the single place that resolves it, and
// everything downstream (receiver, pusher, puller, writer) requires the answer to be a fixed
// non-zero number - so every branch below must produce one.
const GoniometerAxis rotation("omega", 0.0f, 0.1f, {-1, 0, 0}, {});
const GoniometerAxis stationary("omega", 0.0f, 0.0f, {-1, 0, 0}, {});
SECTION("taken literally when given") {
DiffractionExperiment x(DetJF4M());
x.ImagesPerTrigger(5000).NumTriggers(1).Goniometer(rotation).ImagesPerFile(250);
CHECK(x.GetImagesPerFile() == 250);
}
SECTION("stills default to 1000") {
DiffractionExperiment x(DetJF4M());
x.ImagesPerTrigger(50000).NumTriggers(1);
CHECK(x.GetImagesPerFile() == DEFAULT_IMAGES_PER_FILE);
}
SECTION("a rotation sweep goes into one file") {
DiffractionExperiment x(DetJF4M());
x.ImagesPerTrigger(3600).NumTriggers(1).Goniometer(rotation);
CHECK(x.GetImagesPerFile() == 3600);
}
SECTION("a long rotation sweep is split again") {
DiffractionExperiment x(DetJF4M());
x.ImagesPerTrigger(ROTATION_SINGLE_FILE_IMAGE_LIMIT + 1).NumTriggers(1).Goniometer(rotation);
CHECK(x.GetImagesPerFile() == DEFAULT_IMAGES_PER_FILE);
}
SECTION("a goniometer that does not turn is not a rotation sweep") {
DiffractionExperiment x(DetJF4M());
x.ImagesPerTrigger(3600).NumTriggers(1).Goniometer(stationary);
CHECK(x.GetImagesPerFile() == DEFAULT_IMAGES_PER_FILE);
}
SECTION("a grid scan splits on whole fast-axis rows") {
DiffractionExperiment x(DetJF4M());
x.ImagesPerTrigger(10000).NumTriggers(1)
.GridScan(GridScanSettings(30, 10.0f, 10.0f, false, false));
const int64_t images_per_file = x.GetImagesPerFile();
CHECK(images_per_file % 30 == 0);
CHECK(images_per_file >= DEFAULT_IMAGES_PER_FILE);
CHECK(images_per_file < DEFAULT_IMAGES_PER_FILE + 30);
}
SECTION("a single self-contained file holds the whole run") {
DiffractionExperiment x(DetJF4M());
x.ImagesPerTrigger(5000).NumTriggers(1)
.SetFileWriterFormat(FileWriterFormat::NXmxIntegrated).ImagesPerFile(250);
CHECK(x.GetImagesPerFile() == 5000);
}
}
// The saturation limit is exclusive inside Jungfraujoch and inclusive in the file, so the two sides
// must convert. They stopped agreeing once, and the value then lost a count on every
// write-read-write cycle; these pin the pairing and what each stored type declares.
TEST_CASE("DiffractionExperiment_SaturationAndErrorMarkers", "[DiffractionExperiment]") {
SECTION("the conversions are inverses") {
for (int64_t limit : {256L, 65536L, 32768L, 2147483648L}) {
CHECK(SaturationLimitFromValue(SaturationValueFromLimit(limit)) == limit);
CHECK(SaturationValueFromLimit(limit) == limit - 1);
}
}
struct Case { int64_t bits; bool is_signed; int64_t saturation; int64_t underload; int64_t error; };
// saturation_value is the highest value that is still a count; error is the marker actually
// stored in the pixels. Unsigned images reserve the top code, signed ones the bottom.
const std::vector<Case> cases = {
{16, false, UINT16_MAX - 1, 0, UINT16_MAX},
{32, false, UINT32_MAX - 1, 0, UINT32_MAX},
{16, true, INT16_MAX - 1, INT16_MIN + 1, INT16_MIN},
{32, true, INT32_MAX - 1, INT32_MIN + 1, INT32_MIN},
};
for (const auto &c: cases) {
DiffractionExperiment x(DetJF(1));
x.BitDepthImage(c.bits).PixelSigned(c.is_signed);
StartMessage message;
x.FillMessage(message);
CAPTURE(c.bits, c.is_signed);
CHECK(message.saturation_value == c.saturation);
CHECK(message.saturation_value == SaturationValueFromLimit(x.GetSaturationLimit()));
REQUIRE(message.underload_value.has_value());
CHECK(message.underload_value.value() == c.underload);
// The marker the pixels really carry - GetUnderflow() was written here, and it is -1 for an
// unsigned image, which matches no pixel such an image can hold.
REQUIRE(message.error_value.has_value());
CHECK(message.error_value.value() == c.error);
CHECK(message.error_value.value() == x.GetImageFillValue());
// DIALS remaps the top two codes whenever bit_depth_readout is present, without looking at
// the pixel type. For an unsigned image they fall below underload_value and are masked, as
// intended; for a signed one they land inside the trusted range and a saturated pixel would
// be integrated as a count of -2. Unsigned 32-bit cannot be read at all without the field.
CHECK(message.bit_depth_readout.has_value() == !c.is_signed);
}
}