From acf6b3604fe52119d65e474c88899068e2592de8 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Tue, 20 Oct 2026 13:42:19 +0200 Subject: [PATCH] tests: CrystFEL geometry parser - old-syntax fixture and a multi-panel file with path-valued clen, directions, masks and bad regions Co-Authored-By: Claude Opus 5.5 --- tests/CMakeLists.txt | 2 + tests/CrystFELGeometryTest.cpp | 105 +++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 tests/CrystFELGeometryTest.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 48b37353b..b7eb6dae8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,6 +10,7 @@ ADD_EXECUTABLE(jfjoch_portable_test EXCLUDE_FROM_ALL TestData.h ZSTDCompressorTest.cpp JFJochReaderTest.cpp + CrystFELGeometryTest.cpp SweepLayoutTest.cpp CBORTest.cpp ImageSpotFinderCPUTest.cpp @@ -99,6 +100,7 @@ ADD_EXECUTABLE(jfjoch_test ModuleSummationTest.cpp ZMQMetadataSocketTest.cpp JFJochReaderTest.cpp + CrystFELGeometryTest.cpp LiveCollectionTest.cpp RugnuxLiveStatusTest.cpp SweepLayoutTest.cpp diff --git a/tests/CrystFELGeometryTest.cpp b/tests/CrystFELGeometryTest.cpp new file mode 100644 index 000000000..9b5ef3f81 --- /dev/null +++ b/tests/CrystFELGeometryTest.cpp @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include +#include +#include + +#include "../reader/CrystFELGeometry.h" + +namespace { + +std::string WriteGeom(const std::string &name, const std::string &text) { + const auto path = (std::filesystem::temp_directory_path() / name).string(); + std::ofstream(path) << text; + return path; +} + +} // namespace + +TEST_CASE("CrystFELGeometry_OldSyntax", "[CrystFELGeometry][portable]") { + // One panel and whole-frame bad strips, global keys only: the old syntax. + const auto geom = crystfel::Read("../../tests/crystfel/jf4m.geom"); + REQUIRE(geom.panels.size() == 1); + CHECK(geom.panels[0].res == Catch::Approx(13333.3)); + CHECK(geom.panels[0].clen == "0.075"); + CHECK(geom.panels[0].adu_per_photon.value() == Catch::Approx(1.0)); + CHECK(geom.photon_energy == "12400"); + CHECK(geom.data == "/entry/data/data"); + REQUIRE(!geom.bad.empty()); + CHECK(geom.bad[0].panel.empty()); + CHECK(!geom.bad[0].in_lab); + CHECK(geom.bad[0].min_fs == 1030); + CHECK(geom.bad[0].max_fs == 1037); + CHECK(geom.bad[0].max_ss == 2163); +} + +TEST_CASE("CrystFELGeometry_Panels", "[CrystFELGeometry][portable]") { + const auto path = WriteGeom("jfjoch_crystfel_geometry_test.geom", R"( +clen = /LCLS/detector_1/EncoderValue ; read from the file +res = 20000 +adu_per_eV = 0.001 ; per eV +data = /%/data +photon_energy = /%/photon_energy_ev +mask = /data/data +mask_file = mask.h5 +mask_good = 0x0 +mask_bad = 0x1 +q1/min_fs = 0 +q1/max_fs = 511 +q1/min_ss = 0 +q1/max_ss = 1023 +q1/fs = +0.000390x -1.000000y +q1/ss = +1.000000x +0.000390y +q1/corner_x = 32.9283 +q1/corner_y = 1033.68 +q1/coffset = 0.000067 +q2/min_ss = 1024 +q2/max_ss = 2047 +q2/max_fs = 511 +q2/fs = -x +q2/ss = y +q2/adu_per_photon = 9.5 +badq1/min_fs = 0 +badq1/max_fs = 4 +badq1/min_ss = 0 +badq1/max_ss = 1023 +badq1/panel = q1 +badbox/min_x = -10 +badbox/max_x = 10 +badbox/min_y = -5 +badbox/max_y = 5 +)"); + const auto geom = crystfel::Read(path); + REQUIRE(geom.panels.size() == 2); + const auto &q1 = geom.panels[0]; + CHECK(q1.name == "q1"); + CHECK(q1.clen == "/LCLS/detector_1/EncoderValue"); + CHECK(q1.coffset == Catch::Approx(0.000067)); + CHECK(q1.adu_per_eV.value() == Catch::Approx(0.001)); + CHECK(q1.fs_x == Catch::Approx(0.00039)); + CHECK(q1.fs_y == Catch::Approx(-1.0)); + CHECK(q1.ss_x == Catch::Approx(1.0)); + CHECK(q1.ss_y == Catch::Approx(0.00039)); + CHECK(q1.corner_y == Catch::Approx(1033.68)); + CHECK(q1.max_ss == 1023); + + const auto &q2 = geom.panels[1]; + CHECK(q2.fs_x == Catch::Approx(-1.0)); + CHECK(q2.fs_y == Catch::Approx(0.0)); + CHECK(q2.ss_y == Catch::Approx(1.0)); + CHECK(q2.min_ss == 1024); + CHECK(q2.adu_per_photon.value() == Catch::Approx(9.5)); + CHECK(q2.res == Catch::Approx(20000)); + + CHECK(geom.data == "/%/data"); + CHECK(geom.mask_file == "mask.h5"); + CHECK(geom.mask_bad == 1); + REQUIRE(geom.bad.size() == 2); + CHECK(geom.bad[0].panel == "q1"); + CHECK(geom.bad[0].max_fs == 4); + CHECK(geom.bad[1].in_lab); + CHECK(geom.bad[1].min_x == Catch::Approx(-10)); + CHECK(geom.bad[1].max_y == Catch::Approx(5)); + std::filesystem::remove(path); +}