Files
Jungfraujoch/tests/DiffractionExperimentTest.cpp
leonarski_fandjungfrau 4dc2534dbf
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 18m57s
Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 16m55s
Build Packages / build:windows:cuda (push) Successful in 18m48s
Build Packages / build:viewer-tgz:cpu (push) Successful in 13m10s
Build Packages / build:viewer-tgz:cuda (push) Successful in 14m45s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 22m23s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 20m12s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 23m7s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 20m43s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 23m9s
Build Packages / XDS test (durin plugin) (push) Successful in 12m26s
Build Packages / build:rpm (rocky9) (push) Successful in 24m58s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 23m20s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (JFJoch plugin) (push) Successful in 12m37s
Build Packages / build:rpm (rocky8) (push) Successful in 27m58s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 25m38s
Build Packages / Build documentation (push) Successful in 59s
Build Packages / DIALS test (push) Successful in 23m16s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m38s
v1.0.0.rc-162 (#72)
**Files written by Jungfraujoch now import correctly in DIALS, XDS and pyFAI.** A tilted detector, a grid scan, a still recorded at a goniometer position, and saturated or unreadable pixels were each described in a way that a third-party program acted on wrongly. If you process Jungfraujoch data outside Jungfraujoch, prefer this release to any earlier one.

* HDF5: the detector tilt (`rot1`/`rot2`/`rot3`) is exported correctly in the NXmx transformation chain; untilted geometries are unaffected.
* HDF5: a still recorded at a goniometer position is no longer read back as a single image, and a grid scan records a stationary spindle so a program that requires a rotation axis can open it.
* HDF5: the sample transformation chain is written in mounting order, with a Smargon head position told apart from the spindle, one entry per image, `module_offset` as a float unit vector, and `offset_units` on every offset.
* HDF5: saturated, underloaded and unreadable pixels are described so a downstream program masks them - `saturation_value`, `underload_value`, `error_value` and `bit_depth_readout` are written correctly, and a data file missing next to a VDS master reads as the error marker rather than as zero counts.
* HDF5: the rotation axis is read back under whatever name it carries, and `mirror_y` records whether the assembled image is mirrored in Y relative to the detector's raw readout.
* A grid scan and a goniometer axis can both be set; they are no longer alternatives.
* `images_per_file` is chosen from the acquisition when it is not given: a rotation sweep of at most 20000 images goes into a single data file, a grid scan splits on whole fast-axis rows, and stills and serial keep 1000.
* The writer refuses a stream whose start message declares a different pixel format than its images carry, and a DECTRIS detector sending signed images is no longer declared unsigned.
* The image stream can carry the sample transformation chain (`transformations`, in the END message); a producer that does not send it gets the same chain built by the writer.
* rugnux: fixing the space group with `-S` no longer prevents the lattice from being found - a lattice indexed in a different setting is reindexed into that group's own setting, and a run whose crystal does not have that group's lattice stops and names the cell it indexed as, rather than reporting statistics that cannot describe it.
* rugnux: the per-image resolution estimate now predicts the resolution the merged data reach rather than the highest-resolution spot found, and is reported as `SPOT_RESOLUTION_ESTIMATE`.
* rugnux: two runs of the same command on the same images produce the same merged intensities; the azimuthal profile written alongside them is not yet reproducible in the same way.
* rugnux: the offline lattice refinement is bounded by iterations rather than by a wall clock, so a loaded machine can no longer refine to a different lattice; a live acquisition keeps its real-time bound.
* rugnux: the detector-frame modulation correction is fitted on a grid spanning the detector, so whether it is applied no longer depends on how far integration reached.
* rugnux: the geometry pre-pass no longer writes `<prefix>_01.mtz`, `_01.cif`, `_01.hkl` and `_01_image.dat`; the refined second pass writes those files under `<prefix>`, and that is the result to use.
* rugnux: `_process.h5` describes the pixel format of the images it links to, and is written on a thread of its own.
* rugnux: the detector geometry is also logged in XDS's convention (`ORGX`/`ORGY`, detector axis vectors, rotation axis), so it can be compared with an XDS refinement.
* rugnux: an image integrated in pyFAI through the `.poni` file written by `--mode calibration` comes out with the correct azimuth, and the file declares pyFAI's `orientation`, which needs pyFAI 2024.01 or newer. Radial integration is unchanged.
* rugnux: a rotation run is substantially faster throughout - beam-stop detection, first-pass indexing, geometry refinement, integration, scaling and merging - and observations outside the scaling resolution range are dropped as they are ingested. The refined geometry, the space group chosen and the merged statistics are unchanged.
* Faster spot finding and indexing, on the broker as well as in rugnux; the spots found and the lattices indexed are unchanged.
* A run reserves substantially less GPU memory: nothing is allocated for buffers that are never read, and a worker builds only the engines it uses.
* rugnux: with `-N` left at its default the per-image loop of `--mode mx` uses at most 16 workers per GPU, rather than one per hardware thread; an explicit `-N` is obeyed as given.
* CUDA 12 builds now contain device code for Volta, so the RHEL 8 packages and the portable Linux `.tgz` run on a V100; the CUDA 13 artefacts (RHEL 9, Ubuntu, Windows) remain Turing and newer.
* The build resolves a single Eigen for the whole project, and refuses to configure if Ceres picks up a different one; a build that mixed two Eigen versions was undefined behaviour and crashed at -O2.
* Documentation: a security page, and the supported GPU generations and minimum NVIDIA driver version of every released artefact.

**Breaking change to OpenAPI** - regenerate the client (`jfjoch-client` 1.0.0-rc.162, `frontend/src/client`):
* `dataset_settings.images_per_file` is no longer `default: 1000` and no longer accepts `0`; it is optional, and its minimum is 1. A client sending `0` (previously "one file for the whole run") is now rejected - omit the field instead, which for a rotation sweep gives the same single file.
* `file_writer_format` now defaults to `NXmxVDS`, matching the server's own default and the layout recommended for DIALS, XDS and CrystFEL. A generated client that fills in schema defaults and does not set the format explicitly will write VDS masters where it previously wrote legacy ones; set `NXmxLegacy` explicitly to keep them.

---------

Co-authored-by: jungfrau <jungfrau@mx-aare-test.psi.ch>
Reviewed-on: #72
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-08-25 08:21:39 +02:00

1182 lines
41 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");
}
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);
}
}