// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #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. std::vector ignored; const auto geom = crystfel::Read("../../tests/crystfel/jf4m.geom", ignored); REQUIRE(geom.panels.size() == 1); CHECK(geom.panels[0].res == Catch::Approx(13333.3)); CHECK(geom.panels[0].clen->path.empty()); CHECK(geom.panels[0].clen->value == Catch::Approx(0.075)); CHECK(geom.data == "/entry/data/data"); // Only the module geometry is read: gain, photon energy, value flags and bad regions are ignored. CHECK(!geom.panels[0].adu_per_photon.has_value()); CHECK(!geom.photon_energy.has_value()); CHECK(!geom.flag_lessthan.has_value()); CHECK(geom.bad.empty()); CHECK(std::find(ignored.begin(), ignored.end(), "photon_energy") != ignored.end()); } 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 = 25.5 q1/corner_y = 1040.25 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 )"); std::vector ignored; const auto geom = crystfel::Read(path, ignored); REQUIRE(geom.panels.size() == 2); const auto &q1 = geom.panels[0]; CHECK(q1.name == "q1"); CHECK(q1.clen->path == "/LCLS/detector_1/EncoderValue"); CHECK(q1.clen->scale == Catch::Approx(1e-3)); CHECK(q1.coffset == Catch::Approx(0.000067)); CHECK(!q1.adu_per_eV.has_value()); 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(1040.25)); 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.has_value()); CHECK(q2.res == Catch::Approx(20000)); CHECK(geom.data == "/%/data"); CHECK(geom.masks.empty()); CHECK(geom.bad.empty()); for (const std::string key: {"adu_per_eV", "photon_energy", "mask", "mask_file", "mask_good", "mask_bad", "q2/adu_per_photon", "badq1/min_fs", "badbox/max_y"}) CHECK(std::find(ignored.begin(), ignored.end(), key) != ignored.end()); std::filesystem::remove(path); } TEST_CASE("CrystFELGeometry_UnitsAndRefusals", "[CrystFELGeometry][portable]") { std::vector ignored; const auto path = WriteGeom("jfjoch_crystfel_geometry_units.geom", R"( clen = 70.5 mm photon_energy = 9.3 keV res = 10000 mask0_data = /entry/mask mask0_badbits = 0x6 p0/min_fs = 0 p0/max_fs = 99 p0/min_ss = 0 p0/max_ss = 99 p0/mask_edge_pixels = 2 p0/no_index = false some_new_keyword = 1 )"); const auto geom = crystfel::Read(path, ignored); CHECK(geom.panels[0].clen->value == Catch::Approx(0.0705)); CHECK(!geom.photon_energy.has_value()); CHECK(geom.masks.empty()); CHECK(std::find(ignored.begin(), ignored.end(), "some_new_keyword") != ignored.end()); CHECK(std::find(ignored.begin(), ignored.end(), "p0/mask_edge_pixels") != ignored.end()); // A camera length is optional. const auto no_clen = WriteGeom("jfjoch_crystfel_geometry_noclen.geom", "res = 10000\np0/min_fs = 0\np0/max_fs = 9\np0/min_ss = 0\np0/max_ss = 9\n"); CHECK(!crystfel::Read(no_clen, ignored).panels[0].clen.has_value()); std::filesystem::remove(no_clen); for (const std::string bad: {"p0/fs = +1.0x +0.1z", "dim1 = 0", "p0/data = /other", "detector_shift_x = /x"}) { const auto p = WriteGeom("jfjoch_crystfel_geometry_refused.geom", "clen = 0.1\nres = 10000\nphoton_energy = 9000\np0/min_fs = 0\np0/max_fs = 9\n" "p0/min_ss = 0\np0/max_ss = 9\n" + bad + "\n"); CHECK_THROWS(crystfel::Read(p, ignored)); std::filesystem::remove(p); } std::filesystem::remove(path); }