From 34f0d9f679efc9d58bd0d48f1867a0aa358642c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Tue, 13 Oct 2026 12:35:36 +0200 Subject: [PATCH] Drop detector type and quad flag from the gap pixel API insert_gap_pixels and gapped_shape only used the detector type to reject anything but Jungfrau and Eiger, and the quad flag only to shrink the module width to 512. Both Jungfrau and Eiger use 256x256 pixel chips in 1024x512 modules, and an Eiger quad is 512x512 pixels, so its only chip boundaries are at pixel 256 and the module width never matters. The layout is now fixed and both functions take just the image, its ROI and the GapPixels configuration, in C++ and in Python. RawFile keeps rejecting gap pixels for other detector types with std::invalid_argument, now checked in the reader instead of the layout. --- docs/src/GapPixels.rst | 9 +- docs/src/python/file/pyGapPixels.rst | 15 +- include/aare/GapPixels.hpp | 32 ++-- python/src/bind_GapPixels.hpp | 54 +++---- python/tests/test_GapPixels.py | 42 +++-- python/tests/test_MultiThreadedFileReader.py | 2 +- python/tests/test_RawFile.py | 16 +- src/GapPixels.cpp | 15 +- src/GapPixels.test.cpp | 162 +++++++------------ src/RawFile.cpp | 10 +- src/RawFile.test.cpp | 30 ++-- 11 files changed, 157 insertions(+), 230 deletions(-) diff --git a/docs/src/GapPixels.rst b/docs/src/GapPixels.rst index 34a2acce..75e8ed85 100644 --- a/docs/src/GapPixels.rst +++ b/docs/src/GapPixels.rst @@ -15,9 +15,9 @@ Configuration ------------- ``GapPixels`` holds the configuration. The chip size (256 pixels) and the -module size (1024x512 pixels, 512x512 for an Eiger quad) are fixed by the -detector type. Other detector types are rejected with -``std::invalid_argument``. +module size (1024x512 pixels) are fixed. An Eiger quad (512x512 pixels) +needs no special handling, since its only chip boundaries are at pixel 256. +``RawFile`` rejects other detector types with ``std::invalid_argument``. ================ ================ =========================================== Field Default Meaning @@ -96,8 +96,7 @@ detector coordinates of the image. aare::NDArray pedestal = ...; // 512 x 1024 auto gapped = aare::insert_gap_pixels( - pedestal.view(), aare::ROI{0, 1024, 0, 512}, - aare::DetectorType::Jungfrau, false, aare::GapPixels{}); + pedestal.view(), aare::ROI{0, 1024, 0, 512}, aare::GapPixels{}); ``gapped_shape`` returns the shape of the gapped image of a ROI. diff --git a/docs/src/python/file/pyGapPixels.rst b/docs/src/python/file/pyGapPixels.rst index 862c8d72..c2cf040b 100644 --- a/docs/src/python/file/pyGapPixels.rst +++ b/docs/src/python/file/pyGapPixels.rst @@ -10,9 +10,10 @@ modules. Gap positions are determined in detector coordinates before any ROI, so a ROI only receives the gaps that fall strictly inside it. -Only Jungfrau and Eiger are supported; other detector types raise -``ValueError``. The chip size and the module size (1024x512, or 512x512 for -an Eiger quad) are fixed by the detector type. +Only Jungfrau and Eiger raw files are supported; ``RawFile`` raises +``ValueError`` for other detector types. The chip size (256 pixels) and the +module size (1024x512 pixels) are fixed. An Eiger quad (512x512 pixels) +needs no special handling, since its only chip boundaries are at pixel 256. ================ ================ =========================================== Field Default Meaning @@ -40,7 +41,7 @@ stacked modules read as ``(1028, 1030)`` with the defaults and .. code-block:: python - from aare import RawFile, File, GapPixels, ModuleGaps, insert_gap_pixels, DetectorType + from aare import RawFile, File, GapPixels, ModuleGaps, insert_gap_pixels # defaults: chip gaps of 2 filled with 0 with RawFile("run_master_0.json", gap_pixels=True) as f: @@ -52,12 +53,12 @@ stacked modules read as ``(1028, 1030)`` with the defaults and image = f.read_frame() # the same transform for an image in memory, e.g. a pedestal - gapped_pedestal = insert_gap_pixels(pedestal, DetectorType.Jungfrau, gaps) + gapped_pedestal = insert_gap_pixels(pedestal, gaps) ``insert_gap_pixels`` accepts 2D ``uint8``, ``uint16``, ``uint32``, ``int32``, ``float32`` and ``float64`` arrays and returns a new array of the -same dtype. Pass ``roi`` when the image covers part of the detector and -``quad=True`` for an Eiger quad. ``gapped_shape`` returns the shape of the +same dtype. Pass ``roi`` when the image covers part of the detector. +``gapped_shape`` returns the shape of the gapped image of a ROI. .. autoclass:: GapPixels diff --git a/include/aare/GapPixels.hpp b/include/aare/GapPixels.hpp index f187cf78..148a9c28 100644 --- a/include/aare/GapPixels.hpp +++ b/include/aare/GapPixels.hpp @@ -32,10 +32,11 @@ struct ModuleGaps { /** * @brief Configuration for inserting gap pixels into Jungfrau and Eiger - * images. Chip and module sizes are fixed by the detector type: 256x256 - * pixel chips in 1024x512 pixel modules (512x512 for an Eiger quad). Gaps - * are inserted at the chip boundaries inside each module and, only when - * module_gaps is set, between modules. + * images. Chip and module sizes are fixed: 256x256 pixel chips in 1024x512 + * pixel modules. Gaps are inserted at the chip boundaries inside each module + * and, only when module_gaps is set, between modules. An Eiger quad is + * 512x512 pixels, so its only chip boundaries are at pixel 256 and the + * module width never comes into play. * * Gap positions are determined in detector coordinates before any ROI, so * a ROI only receives the gaps that fall strictly inside it. @@ -126,11 +127,10 @@ struct GapLayout { }; /** - * @brief Resolve the gap layout for a detector - * @throws std::invalid_argument for detectors other than Jungfrau and Eiger - * or for an inconsistent configuration + * @brief Resolve the gap layout of a configuration + * @throws std::invalid_argument for an inconsistent configuration */ -GapLayout gap_layout(DetectorType detector, bool quad, const GapPixels &gaps); +GapLayout gap_layout(const GapPixels &gaps); /// @brief Gapped shape {rows, cols} of a detector coordinate ROI std::array gapped_shape(const ROI &roi, const GapLayout &layout); @@ -326,27 +326,25 @@ void split_edge_counts(NDView destination, const ROI &roi, /** * @brief Shape {rows, cols} of the gapped image of a detector coordinate ROI - * @throws std::invalid_argument for unsupported detectors or an invalid ROI + * @param roi detector coordinates of the image + * @param gaps gap configuration + * @throws std::invalid_argument for an invalid ROI or configuration */ -std::array gapped_shape(const ROI &roi, DetectorType detector, - bool quad, const GapPixels &gaps = {}); +std::array gapped_shape(const ROI &roi, const GapPixels &gaps = {}); /** * @brief Insert gap pixels into an image * @param source ungapped image of `roi` * @param roi detector coordinates of `source` - * @param detector detector type, Jungfrau or Eiger - * @param quad true for an Eiger quad * @param gaps gap configuration * @return gapped image - * @throws std::invalid_argument for unsupported detectors, an invalid - * configuration or a source shape that does not match the ROI + * @throws std::invalid_argument for an invalid configuration or a source + * shape that does not match the ROI */ template NDArray insert_gap_pixels(NDView source, const ROI &roi, - DetectorType detector, bool quad, const GapPixels &gaps = {}) { - const auto layout = detail::gap_layout(detector, quad, gaps); + const auto layout = detail::gap_layout(gaps); detail::validate_roi(roi); NDArray result(detail::gapped_shape(roi, layout)); detail::fill_gap_pixels(result.view(), roi, layout, diff --git a/python/src/bind_GapPixels.hpp b/python/src/bind_GapPixels.hpp index 0fc19bcb..727fe123 100644 --- a/python/src/bind_GapPixels.hpp +++ b/python/src/bind_GapPixels.hpp @@ -19,12 +19,10 @@ namespace { template py::array insert_gap_pixels_typed(const py::array &image, const ROI &roi, - DetectorType detector, bool quad, const GapPixels &gaps) { py::array_t typed(image); auto source = make_const_view_2d(typed); - auto *result = new NDArray( - insert_gap_pixels(source, roi, detector, quad, gaps)); + auto *result = new NDArray(insert_gap_pixels(source, roi, gaps)); return return_image_data(result); } @@ -46,7 +44,8 @@ void define_gap_pixels_bindings(py::module &m) { py::class_(m, "GapPixels", R"( Configuration for inserting gap pixels into Jungfrau and Eiger - images. Chip and module sizes are fixed by the detector type. + images. Chip and module sizes are fixed: 256x256 pixel chips in + 1024x512 pixel modules. Parameters ---------- @@ -103,21 +102,25 @@ void define_gap_pixels_bindings(py::module &m) { m.def( "gapped_shape", - [](const ROI &roi, DetectorType detector_type, const GapPixels &gaps, - bool quad) { - const auto shape = gapped_shape(roi, detector_type, quad, gaps); + [](const ROI &roi, const GapPixels &gaps) { + const auto shape = gapped_shape(roi, gaps); return py::make_tuple(shape[0], shape[1]); }, - py::arg("roi"), py::arg("detector_type"), py::arg("gaps") = GapPixels{}, - py::arg("quad") = false, R"( + py::arg("roi"), py::arg("gaps") = GapPixels{}, R"( Shape (rows, cols) of the gapped image of a detector coordinate ROI. + + Parameters + ---------- + roi : ROI + Detector coordinates of the image. + gaps : GapPixels + Gap configuration. )"); m.def( "insert_gap_pixels", - [](const py::array &image, DetectorType detector_type, - const GapPixels &gaps, std::optional roi, - bool quad) -> py::array { + [](const py::array &image, const GapPixels &gaps, + std::optional roi) -> py::array { if (image.ndim() != 2) { throw py::value_error( fmt::format("Expected a 2D image, got {}D", image.ndim())); @@ -125,51 +128,40 @@ void define_gap_pixels_bindings(py::module &m) { const ROI rect = roi.value_or(ROI{0, image.shape(1), 0, image.shape(0)}); if (py::isinstance>(image)) { - return insert_gap_pixels_typed( - image, rect, detector_type, quad, gaps); + return insert_gap_pixels_typed(image, rect, gaps); } if (py::isinstance>(image)) { - return insert_gap_pixels_typed( - image, rect, detector_type, quad, gaps); + return insert_gap_pixels_typed(image, rect, gaps); } if (py::isinstance>(image)) { - return insert_gap_pixels_typed( - image, rect, detector_type, quad, gaps); + return insert_gap_pixels_typed(image, rect, gaps); } if (py::isinstance>(image)) { - return insert_gap_pixels_typed( - image, rect, detector_type, quad, gaps); + return insert_gap_pixels_typed(image, rect, gaps); } if (py::isinstance>(image)) { - return insert_gap_pixels_typed( - image, rect, detector_type, quad, gaps); + return insert_gap_pixels_typed(image, rect, gaps); } if (py::isinstance>(image)) { - return insert_gap_pixels_typed( - image, rect, detector_type, quad, gaps); + return insert_gap_pixels_typed(image, rect, gaps); } throw py::type_error( "Unsupported dtype, expected uint8, uint16, uint32, int32, " "float32 or float64"); }, - py::arg("image"), py::arg("detector_type"), - py::arg("gaps") = GapPixels{}, py::arg("roi") = py::none(), - py::arg("quad") = false, R"( + py::arg("image"), py::arg("gaps") = GapPixels{}, + py::arg("roi") = py::none(), R"( Insert gap pixels into a 2D image. Parameters ---------- image : numpy.ndarray Ungapped image of ``roi``. - detector_type : DetectorType - Jungfrau or Eiger. gaps : GapPixels Gap configuration. roi : ROI, optional Detector coordinates of the image. Defaults to the full image starting at pixel (0, 0). - quad : bool - True for an Eiger quad. Returns ------- diff --git a/python/tests/test_GapPixels.py b/python/tests/test_GapPixels.py index 147b9947..1c954dd6 100644 --- a/python/tests/test_GapPixels.py +++ b/python/tests/test_GapPixels.py @@ -2,7 +2,7 @@ import numpy as np import pytest -from aare import DetectorType, GapPixels, ModuleGaps, ROI, gapped_shape, insert_gap_pixels +from aare import GapPixels, ModuleGaps, ROI, gapped_shape, insert_gap_pixels def reference(image, boundaries_x, boundaries_y, fill=0): @@ -33,21 +33,19 @@ def test_configuration_defaults_and_equality(): def test_gapped_shape(): - assert gapped_shape(ROI(0, 1024, 0, 512), DetectorType.Jungfrau) == (514, 1030) - assert gapped_shape(ROI(0, 1024, 0, 1024), DetectorType.Jungfrau) == (1028, 1030) - assert gapped_shape(ROI(0, 1024, 0, 1024), DetectorType.Jungfrau, GapPixels(module_gaps=ModuleGaps(8, 36))) == (1064, 1030) - assert gapped_shape(ROI(0, 512, 0, 512), DetectorType.Eiger, quad=True) == (514, 514) - assert gapped_shape(ROI(256, 512, 0, 256), DetectorType.Jungfrau) == (256, 256) - assert gapped_shape(ROI(255, 257, 0, 1), DetectorType.Jungfrau) == (1, 4) - with pytest.raises(ValueError): - gapped_shape(ROI(0, 400, 0, 400), DetectorType.Moench) + assert gapped_shape(ROI(0, 1024, 0, 512)) == (514, 1030) + assert gapped_shape(ROI(0, 1024, 0, 1024)) == (1028, 1030) + assert gapped_shape(ROI(0, 1024, 0, 1024), GapPixels(module_gaps=ModuleGaps(8, 36))) == (1064, 1030) + assert gapped_shape(ROI(0, 512, 0, 512)) == (514, 514) # Eiger quad + assert gapped_shape(ROI(256, 512, 0, 256)) == (256, 256) + assert gapped_shape(ROI(255, 257, 0, 1)) == (1, 4) @pytest.mark.parametrize("dtype", [np.uint8, np.uint16, np.uint32, np.int32, np.float32, np.float64]) def test_insert_gap_pixels_matches_numpy(dtype): rng = np.random.default_rng(1) image = rng.integers(0, 200, size=(512, 1024)).astype(dtype) - result = insert_gap_pixels(image, DetectorType.Jungfrau, GapPixels(fill_value=3)) + result = insert_gap_pixels(image, GapPixels(fill_value=3)) expected = reference(image, [(256, 2), (512, 2), (768, 2)], [(256, 2)], fill=3) assert result.dtype == image.dtype assert result.shape == (514, 1030) @@ -58,7 +56,7 @@ def test_insert_gap_pixels_without_module_gaps(): # two stacked modules: chip gaps at rows 256 and 768, none at the module # boundary at row 512 image = np.arange(1024 * 1024, dtype=np.uint32).reshape(1024, 1024) - result = insert_gap_pixels(image, DetectorType.Jungfrau) + result = insert_gap_pixels(image) expected = reference(image, [(256, 2), (512, 2), (768, 2)], [(256, 2), (768, 2)]) assert result.shape == (1028, 1030) assert (result == expected).all() @@ -67,7 +65,7 @@ def test_insert_gap_pixels_without_module_gaps(): def test_insert_gap_pixels_with_module_gaps(): image = np.arange(1024 * 1024, dtype=np.uint32).reshape(1024, 1024) gaps = GapPixels(module_gaps=ModuleGaps(8, 36), fill_value=9) - result = insert_gap_pixels(image, DetectorType.Jungfrau, gaps) + result = insert_gap_pixels(image, gaps) expected = reference(image, [(256, 2), (512, 2), (768, 2)], [(256, 2), (512, 36), (768, 2)], fill=9) assert result.shape == (1064, 1030) assert (result == expected).all() @@ -75,12 +73,12 @@ def test_insert_gap_pixels_with_module_gaps(): def test_insert_gap_pixels_in_roi(): image = np.array([[11, 22]], dtype=np.uint16) - result = insert_gap_pixels(image, DetectorType.Jungfrau, roi=ROI(255, 257, 10, 11)) + result = insert_gap_pixels(image, roi=ROI(255, 257, 10, 11)) assert result.tolist() == [[11, 0, 0, 22]] # a gap on the ROI border is not inserted image = np.arange(8, dtype=np.uint16).reshape(2, 4) - result = insert_gap_pixels(image, DetectorType.Jungfrau, roi=ROI(256, 260, 0, 2)) + result = insert_gap_pixels(image, roi=ROI(256, 260, 0, 2)) assert (result == image).all() @@ -88,8 +86,8 @@ def test_insert_gap_pixels_split_counts(): rng = np.random.default_rng(2) image = rng.integers(0, 1000, size=(512, 1024)).astype(np.uint32) gaps = GapPixels(split_counts=True, seed=3) - first = insert_gap_pixels(image, DetectorType.Jungfrau, gaps) - second = insert_gap_pixels(image, DetectorType.Jungfrau, gaps) + first = insert_gap_pixels(image, gaps) + second = insert_gap_pixels(image, gaps) assert first.sum() == image.sum() assert (first == second).all() # edge pixel and its gap pixel hold floor and ceil of the half; row 255 @@ -98,19 +96,17 @@ def test_insert_gap_pixels_split_counts(): assert (pair.sum(axis=0) == image[:255, 255]).all() assert (pair.max(axis=0) == (image[:255, 255] + 1) // 2).all() - halves = insert_gap_pixels(np.full((2, 2), 3.0), DetectorType.Jungfrau, gaps, roi=ROI(255, 257, 255, 257)) + halves = insert_gap_pixels(np.full((2, 2), 3.0), gaps, roi=ROI(255, 257, 255, 257)) assert (halves == 0.75).all() def test_insert_gap_pixels_rejects_bad_input(): image = np.zeros((400, 400), dtype=np.uint16) with pytest.raises(ValueError): - insert_gap_pixels(image, DetectorType.Moench) + insert_gap_pixels(image, roi=ROI(0, 401, 0, 400)) with pytest.raises(ValueError): - insert_gap_pixels(image, DetectorType.Jungfrau, roi=ROI(0, 401, 0, 400)) - with pytest.raises(ValueError): - insert_gap_pixels(np.zeros((2, 2, 2), dtype=np.uint16), DetectorType.Jungfrau) + insert_gap_pixels(np.zeros((2, 2, 2), dtype=np.uint16)) with pytest.raises(TypeError): - insert_gap_pixels(np.zeros((2, 2), dtype=np.int64), DetectorType.Jungfrau) + insert_gap_pixels(np.zeros((2, 2), dtype=np.int64)) with pytest.raises(ValueError): - insert_gap_pixels(image, DetectorType.Jungfrau, GapPixels(split_counts=True, chip_gap=4)) + insert_gap_pixels(image, GapPixels(split_counts=True, chip_gap=4)) diff --git a/python/tests/test_MultiThreadedFileReader.py b/python/tests/test_MultiThreadedFileReader.py index 0b0215c2..cbf5c642 100644 --- a/python/tests/test_MultiThreadedFileReader.py +++ b/python/tests/test_MultiThreadedFileReader.py @@ -158,7 +158,7 @@ def test_gap_pixels_on_raw_files(tmp_path): assert (reader.rows, reader.cols) == (514, 1030) frames = reader.read_all() assert frames.shape == (4, 514, 1030) - assert (frames == insert_gap_pixels(expected, aare.DetectorType.Jungfrau, gaps)).all() + assert (frames == insert_gap_pixels(expected, gaps)).all() reader = MultiThreadedFileReader(master_path, n_threads=2, chunk_size=1, gap_pixels=True) assert reader.gap_pixels == GapPixels() diff --git a/python/tests/test_RawFile.py b/python/tests/test_RawFile.py index d421ac34..a4c9395e 100644 --- a/python/tests/test_RawFile.py +++ b/python/tests/test_RawFile.py @@ -532,7 +532,7 @@ def test_raw_file_gap_pixels(tmp_path, layout): assert (reader.rows(), reader.cols()) == ((512 + 2) * layout_rows, (1024 + 6) * layout_cols) header, image = reader.read_frame() assert header.size == layout_rows * layout_cols - assert (image == insert_gap_pixels(expected, DetectorType.Jungfrau, gaps)).all() + assert (image == insert_gap_pixels(expected, gaps)).all() reader.seek(0) _, images = reader.read_n(2) assert images.shape == (2, reader.rows(), reader.cols()) @@ -545,15 +545,15 @@ def test_raw_file_gap_pixels(tmp_path, layout): with RawFile(master_path, gap_pixels=True) as reader: assert reader.gap_pixels == GapPixels() _, image = reader.read_frame() - assert (image == insert_gap_pixels(expected, DetectorType.Jungfrau)).all() + assert (image == insert_gap_pixels(expected)).all() with File(master_path, gap_pixels=True) as reader: - assert (reader.rows, reader.cols) == insert_gap_pixels(expected, DetectorType.Jungfrau).shape - assert (reader.read_frame() == insert_gap_pixels(expected, DetectorType.Jungfrau)).all() + assert (reader.rows, reader.cols) == insert_gap_pixels(expected).shape + assert (reader.read_frame() == insert_gap_pixels(expected)).all() module_gaps = GapPixels(module_gaps=ModuleGaps(8, 36)) with File(master_path, gap_pixels=module_gaps) as reader: - assert (reader.read_frame() == insert_gap_pixels(expected, DetectorType.Jungfrau, module_gaps)).all() + assert (reader.read_frame() == insert_gap_pixels(expected, module_gaps)).all() def test_raw_file_gap_pixels_split_counts(tmp_path): @@ -561,7 +561,7 @@ def test_raw_file_gap_pixels_split_counts(tmp_path): gaps = GapPixels(split_counts=True, seed=11) with RawFile(master_path, gap_pixels=gaps) as reader: _, image = reader.read_frame() - assert (image == insert_gap_pixels(expected, DetectorType.Jungfrau, gaps)).all() + assert (image == insert_gap_pixels(expected, gaps)).all() assert image.sum() == expected.sum() @@ -583,7 +583,7 @@ def test_raw_file_gap_pixels_recorded_jungfrau(test_data_path): _, image = gapped.read_frame() _, reference = plain.read_frame() assert image.shape == (514, 1030) - assert (image == insert_gap_pixels(reference, DetectorType.Jungfrau, gaps)).all() + assert (image == insert_gap_pixels(reference, gaps)).all() @pytest.mark.withdata @@ -597,7 +597,7 @@ def test_raw_file_gap_pixels_recorded_rois(test_data_path): assert images[0].shape == (303, 103) assert images[1].shape == (101, 103) for image, reference, roi in zip(images, references, rois): - assert (image == insert_gap_pixels(reference, DetectorType.Jungfrau, roi=roi)).all() + assert (image == insert_gap_pixels(reference, roi=roi)).all() gapped.seek(0) _, frames = gapped.read_n_with_roi(2, roi_index=1) assert frames.shape == (2, 101, 103) diff --git a/src/GapPixels.cpp b/src/GapPixels.cpp index 3fb6c7e0..61680bfc 100644 --- a/src/GapPixels.cpp +++ b/src/GapPixels.cpp @@ -6,12 +6,7 @@ namespace aare { namespace detail { -GapLayout gap_layout(DetectorType detector, bool quad, const GapPixels &gaps) { - if (detector != DetectorType::Jungfrau && detector != DetectorType::Eiger) { - throw std::invalid_argument( - LOCATION + - "Gap pixels are only supported for Jungfrau and Eiger detectors"); - } +GapLayout gap_layout(const GapPixels &gaps) { if (gaps.chip_gap < 0) { throw std::invalid_argument(LOCATION + "Chip gap must not be negative"); } @@ -26,8 +21,7 @@ GapLayout gap_layout(DetectorType detector, bool quad, const GapPixels &gaps) { } constexpr ssize_t chip = 256; - const ssize_t module_x = - (detector == DetectorType::Eiger && quad) ? 512 : 1024; + constexpr ssize_t module_x = 1024; constexpr ssize_t module_y = 512; return {{chip, module_x, gaps.chip_gap, module_gaps.x}, {chip, module_y, gaps.chip_gap, module_gaps.y}}; @@ -70,9 +64,8 @@ std::mt19937_64 make_generator(std::optional seed) { } // namespace detail -std::array gapped_shape(const ROI &roi, DetectorType detector, - bool quad, const GapPixels &gaps) { - const auto layout = detail::gap_layout(detector, quad, gaps); +std::array gapped_shape(const ROI &roi, const GapPixels &gaps) { + const auto layout = detail::gap_layout(gaps); detail::validate_roi(roi); return detail::gapped_shape(roi, layout); } diff --git a/src/GapPixels.test.cpp b/src/GapPixels.test.cpp index b1859924..97775498 100644 --- a/src/GapPixels.test.cpp +++ b/src/GapPixels.test.cpp @@ -69,10 +69,9 @@ TEST_CASE("Axis gap map inserts chip and module gaps", "[GapPixels]") { CHECK(axis.gap_range(4, 3) == std::pair{1, 3}); } -TEST_CASE("Gap layout follows the detector type", "[GapPixels]") { +TEST_CASE("Gap layout follows the configuration", "[GapPixels]") { SECTION("Jungfrau defaults") { - const auto layout = - detail::gap_layout(DetectorType::Jungfrau, false, GapPixels{}); + const auto layout = detail::gap_layout(GapPixels{}); CHECK(layout.x.chip == 256); CHECK(layout.x.module == 1024); CHECK(layout.x.chip_gap == 2); @@ -82,46 +81,27 @@ TEST_CASE("Gap layout follows the detector type", "[GapPixels]") { CHECK(layout.y.chip_gap == 2); CHECK(layout.y.module_gap == 0); } - SECTION("Eiger quad has a 512 pixel wide module") { - CHECK(detail::gap_layout(DetectorType::Eiger, true, GapPixels{}) - .x.module == 512); - CHECK(detail::gap_layout(DetectorType::Eiger, false, GapPixels{}) - .x.module == 1024); - } SECTION("explicit gaps") { GapPixels gaps; gaps.chip_gap = 4; gaps.module_gaps = ModuleGaps{8, 36}; - const auto layout = - detail::gap_layout(DetectorType::Jungfrau, false, gaps); + const auto layout = detail::gap_layout(gaps); CHECK(layout.x.chip_gap == 4); CHECK(layout.y.chip_gap == 4); CHECK(layout.x.module_gap == 8); CHECK(layout.y.module_gap == 36); } - SECTION("unsupported detectors throw") { - for (auto detector : - {DetectorType::Moench, DetectorType::Mythen3, - DetectorType::Gotthard2, DetectorType::ChipTestBoard, - DetectorType::Generic, DetectorType::Unknown}) { - CHECK_THROWS_AS(detail::gap_layout(detector, false, GapPixels{}), - std::invalid_argument); - } - } SECTION("invalid configuration throws") { GapPixels gaps; gaps.chip_gap = -1; - CHECK_THROWS_AS(detail::gap_layout(DetectorType::Jungfrau, false, gaps), - std::invalid_argument); + CHECK_THROWS_AS(detail::gap_layout(gaps), std::invalid_argument); gaps = GapPixels{}; gaps.module_gaps = ModuleGaps{-8, 36}; - CHECK_THROWS_AS(detail::gap_layout(DetectorType::Eiger, false, gaps), - std::invalid_argument); + CHECK_THROWS_AS(detail::gap_layout(gaps), std::invalid_argument); gaps = GapPixels{}; gaps.split_counts = true; gaps.chip_gap = 4; - CHECK_THROWS_AS(detail::gap_layout(DetectorType::Jungfrau, false, gaps), - std::invalid_argument); + CHECK_THROWS_AS(detail::gap_layout(gaps), std::invalid_argument); } } @@ -130,44 +110,29 @@ TEST_CASE("Gapped shape of detector ROIs", "[GapPixels]") { module_8_36.module_gaps = ModuleGaps{8, 36}; using Shape = std::array; - CHECK(gapped_shape(ROI{0, 1024, 0, 512}, DetectorType::Jungfrau, false) == - Shape{514, 1030}); - CHECK(gapped_shape(ROI{0, 1024, 0, 512}, DetectorType::Jungfrau, false, - module_8_36) == Shape{514, 1030}); - CHECK(gapped_shape(ROI{0, 2048, 0, 512}, DetectorType::Jungfrau, false) == - Shape{514, 2060}); - CHECK(gapped_shape(ROI{0, 2048, 0, 512}, DetectorType::Jungfrau, false, - module_8_36) == Shape{514, 2068}); - CHECK(gapped_shape(ROI{0, 1024, 0, 1024}, DetectorType::Jungfrau, false) == - Shape{1028, 1030}); - CHECK(gapped_shape(ROI{0, 1024, 0, 1024}, DetectorType::Jungfrau, false, - module_8_36) == Shape{1064, 1030}); - CHECK(gapped_shape(ROI{0, 512, 0, 512}, DetectorType::Eiger, true) == - Shape{514, 514}); - CHECK(gapped_shape(ROI{0, 1024, 0, 512}, DetectorType::Eiger, false) == - Shape{514, 1030}); + CHECK(gapped_shape(ROI{0, 1024, 0, 512}) == Shape{514, 1030}); + CHECK(gapped_shape(ROI{0, 1024, 0, 512}, module_8_36) == Shape{514, 1030}); + CHECK(gapped_shape(ROI{0, 2048, 0, 512}) == Shape{514, 2060}); + CHECK(gapped_shape(ROI{0, 2048, 0, 512}, module_8_36) == Shape{514, 2068}); + CHECK(gapped_shape(ROI{0, 1024, 0, 1024}) == Shape{1028, 1030}); + CHECK(gapped_shape(ROI{0, 1024, 0, 1024}, module_8_36) == + Shape{1064, 1030}); + // an Eiger quad + CHECK(gapped_shape(ROI{0, 512, 0, 512}) == Shape{514, 514}); SECTION("ROI borders exclude gaps") { - CHECK(gapped_shape(ROI{100, 400, 0, 10}, DetectorType::Jungfrau, - false) == Shape{10, 302}); - CHECK(gapped_shape(ROI{256, 512, 0, 1}, DetectorType::Jungfrau, - false) == Shape{1, 256}); - CHECK(gapped_shape(ROI{255, 257, 0, 1}, DetectorType::Jungfrau, - false) == Shape{1, 4}); - CHECK(gapped_shape(ROI{200, 301, 300, 601}, DetectorType::Jungfrau, - false) == Shape{301, 103}); - CHECK(gapped_shape(ROI{200, 301, 300, 601}, DetectorType::Jungfrau, - false, module_8_36) == Shape{337, 103}); - CHECK(gapped_shape(ROI{512, 768, 0, 256}, DetectorType::Jungfrau, - false) == Shape{256, 256}); + CHECK(gapped_shape(ROI{100, 400, 0, 10}) == Shape{10, 302}); + CHECK(gapped_shape(ROI{256, 512, 0, 1}) == Shape{1, 256}); + CHECK(gapped_shape(ROI{255, 257, 0, 1}) == Shape{1, 4}); + CHECK(gapped_shape(ROI{200, 301, 300, 601}) == Shape{301, 103}); + CHECK(gapped_shape(ROI{200, 301, 300, 601}, module_8_36) == + Shape{337, 103}); + CHECK(gapped_shape(ROI{512, 768, 0, 256}) == Shape{256, 256}); } SECTION("invalid ROI throws") { - CHECK_THROWS_AS( - gapped_shape(ROI{-1, 10, 0, 10}, DetectorType::Jungfrau, false), - std::invalid_argument); - CHECK_THROWS_AS( - gapped_shape(ROI{10, 5, 0, 10}, DetectorType::Jungfrau, false), - std::invalid_argument); + CHECK_THROWS_AS(gapped_shape(ROI{-1, 10, 0, 10}), + std::invalid_argument); + CHECK_THROWS_AS(gapped_shape(ROI{10, 5, 0, 10}), std::invalid_argument); } } @@ -257,11 +222,11 @@ TEST_CASE("insert_gap_pixels on a Jungfrau module", "[GapPixels]") { auto source = pattern(512, 1024); GapPixels gaps; gaps.fill_value = 5; - auto result = insert_gap_pixels(NDView(source.view()), - roi, DetectorType::Jungfrau, false, gaps); + auto result = + insert_gap_pixels(NDView(source.view()), roi, gaps); REQUIRE(result.shape() == std::array{514, 1030}); - const auto layout = detail::gap_layout(DetectorType::Jungfrau, false, gaps); + const auto layout = detail::gap_layout(gaps); for (ssize_t row = 0; row < 512; ++row) { for (ssize_t col = 0; col < 1024; ++col) { if (result(layout.y.gapped(row), layout.x.gapped(col)) != @@ -300,8 +265,7 @@ TEST_CASE("insert_gap_pixels on ROIs", "[GapPixels]") { source(0, 0) = 11; source(0, 1) = 22; auto result = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false); + insert_gap_pixels(NDView(source.view()), roi); REQUIRE(result.shape() == std::array{1, 4}); CHECK(result(0, 0) == 11); CHECK(result(0, 1) == 0); @@ -311,8 +275,8 @@ TEST_CASE("insert_gap_pixels on ROIs", "[GapPixels]") { SECTION("gap on the ROI border is not inserted") { const ROI roi{256, 260, 0, 2}; auto source = pattern(2, 4); - auto result = insert_gap_pixels(NDView(source.view()), - roi, DetectorType::Jungfrau, false); + auto result = + insert_gap_pixels(NDView(source.view()), roi); REQUIRE(result.shape() == std::array{2, 4}); for (ssize_t row = 0; row < 2; ++row) { for (ssize_t col = 0; col < 4; ++col) { @@ -326,9 +290,8 @@ TEST_CASE("insert_gap_pixels on ROIs", "[GapPixels]") { gaps.fill_value = -1; // wraps for unsigned types const ROI roi{200, 301, 300, 601}; auto source = pattern(301, 101); - auto result = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, gaps); + auto result = insert_gap_pixels( + NDView(source.view()), roi, gaps); REQUIRE(result.shape() == std::array{337, 103}); // chip gap at column 256 -> gapped columns 56, 57 CHECK(result(0, 55) == source(0, 55)); @@ -345,11 +308,7 @@ TEST_CASE("insert_gap_pixels on ROIs", "[GapPixels]") { auto source = pattern(2, 4); CHECK_THROWS_AS( insert_gap_pixels(NDView(source.view()), - ROI{0, 5, 0, 2}, DetectorType::Jungfrau, false), - std::invalid_argument); - CHECK_THROWS_AS( - insert_gap_pixels(NDView(source.view()), - ROI{0, 4, 0, 2}, DetectorType::Moench, false), + ROI{0, 5, 0, 2}), std::invalid_argument); } SECTION("floating point images") { @@ -359,9 +318,8 @@ TEST_CASE("insert_gap_pixels on ROIs", "[GapPixels]") { source(0, 1) = 2.5; GapPixels gaps; gaps.fill_value = 0.25; - auto result = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, gaps); + auto result = insert_gap_pixels(NDView(source.view()), + roi, gaps); CHECK(result(0, 0) == 1.5); CHECK(result(0, 1) == 0.25); CHECK(result(0, 2) == 0.25); @@ -381,14 +339,14 @@ TEST_CASE("Module boundaries receive no gap unless module gaps are set", gaps.split_counts = true; gaps.seed = 1; } - auto result = insert_gap_pixels(NDView(source.view()), - roi, DetectorType::Jungfrau, false, gaps); + auto result = + insert_gap_pixels(NDView(source.view()), roi, gaps); REQUIRE(result.shape() == std::array{4, 2}); CHECK(result == source); gaps.module_gaps = ModuleGaps{0, 3}; - auto with_gap = insert_gap_pixels(NDView(source.view()), - roi, DetectorType::Jungfrau, false, gaps); + auto with_gap = + insert_gap_pixels(NDView(source.view()), roi, gaps); REQUIRE(with_gap.shape() == std::array{7, 2}); for (ssize_t row = 2; row < 5; ++row) { CHECK(with_gap(row, 0) == 9); @@ -415,9 +373,8 @@ TEST_CASE("Split counts between edge pixels and their gaps", "[GapPixels]") { source(1, 1) = 8; source(1, 2) = 2; source(1, 3) = 3; - auto result = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, gaps); + auto result = insert_gap_pixels( + NDView(source.view()), roi, gaps); REQUIRE(result.shape() == std::array{2, 6}); for (ssize_t row = 0; row < 2; ++row) { CAPTURE(row); @@ -445,9 +402,8 @@ TEST_CASE("Split counts between edge pixels and their gaps", "[GapPixels]") { source(0, 1) = 8; source(1, 0) = 4; source(1, 1) = 1; - auto result = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, gaps); + auto result = insert_gap_pixels( + NDView(source.view()), roi, gaps); REQUIRE(result.shape() == std::array{4, 4}); auto quad_sum = [&](ssize_t row, ssize_t col) { return result(row, col) + result(row, col + 1) + @@ -469,9 +425,8 @@ TEST_CASE("Split counts between edge pixels and their gaps", "[GapPixels]") { const ROI roi{255, 257, 255, 257}; NDArray source({2, 2}, 1.0); source(0, 0) = 3.0; - auto result = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, gaps); + auto result = insert_gap_pixels(NDView(source.view()), + roi, gaps); for (ssize_t row = 0; row < 2; ++row) { for (ssize_t col = 0; col < 2; ++col) { CHECK(result(row, col) == 0.75); @@ -489,12 +444,10 @@ TEST_CASE("Split counts between edge pixels and their gaps", "[GapPixels]") { static_cast((row * 7 + col * 13) % 1000 + 1); } } - auto first = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, gaps); - auto second = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, gaps); + auto first = insert_gap_pixels(NDView(source.view()), + roi, gaps); + auto second = insert_gap_pixels( + NDView(source.view()), roi, gaps); REQUIRE(first.shape() == std::array{514, 1030}); CHECK(total(NDView(first.view())) == total(NDView(source.view()))); @@ -530,9 +483,8 @@ TEST_CASE("Split counts between edge pixels and their gaps", "[GapPixels]") { GapPixels other = gaps; other.seed = 43; - auto third = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, other); + auto third = insert_gap_pixels(NDView(source.view()), + roi, other); CHECK(third != first); } SECTION("module gaps keep the fill value") { @@ -541,9 +493,8 @@ TEST_CASE("Split counts between edge pixels and their gaps", "[GapPixels]") { module_gaps.fill_value = 3; const ROI roi{0, 1024, 0, 1024}; NDArray source({1024, 1024}, uint16_t{7}); - auto result = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, module_gaps); + auto result = insert_gap_pixels( + NDView(source.view()), roi, module_gaps); REQUIRE(result.shape() == std::array{1064, 1030}); for (ssize_t row = 514; row < 550; ++row) { for (ssize_t col = 0; col < 1030; ++col) { @@ -564,9 +515,8 @@ TEST_CASE("Split counts between edge pixels and their gaps", "[GapPixels]") { SECTION("no split at a ROI border") { const ROI roi{256, 260, 0, 1}; NDArray source({1, 4}, uint16_t{9}); - auto result = - insert_gap_pixels(NDView(source.view()), roi, - DetectorType::Jungfrau, false, gaps); + auto result = insert_gap_pixels( + NDView(source.view()), roi, gaps); REQUIRE(result.shape() == std::array{1, 4}); CHECK(result(0, 0) == 9); } diff --git a/src/RawFile.cpp b/src/RawFile.cpp index f12a2a55..6d1d3975 100644 --- a/src/RawFile.cpp +++ b/src/RawFile.cpp @@ -70,9 +70,15 @@ RawFile::RawFile(const std::filesystem::path &fname, const std::string &mode, } m_roi_shapes.reserve(num_rois); if (gap_pixels) { + const auto detector = m_master.detector_type(); + if (detector != DetectorType::Jungfrau && + detector != DetectorType::Eiger) { + throw std::invalid_argument( + LOCATION + "Gap pixels are only supported for Jungfrau " + "and Eiger detectors"); + } m_gap_pixels = gap_pixels; - m_gap_layout = detail::gap_layout( - m_master.detector_type(), m_master.quad() == 1, *gap_pixels); + m_gap_layout = detail::gap_layout(*gap_pixels); m_generator = detail::make_generator(gap_pixels->seed); for (const auto &rect : m_roi_rects) { m_roi_shapes.push_back( diff --git a/src/RawFile.test.cpp b/src/RawFile.test.cpp index d6ebf744..93ab5bcc 100644 --- a/src/RawFile.test.cpp +++ b/src/RawFile.test.cpp @@ -292,10 +292,9 @@ namespace { /// to the same frame read without gaps. template bool matches_gap_oracle(Frame &gapped, Frame &plain, const ROI &roi, - DetectorType detector, bool quad, const GapPixels &gaps) { - auto expected = insert_gap_pixels(NDView(plain.view()), roi, - detector, quad, gaps); + auto expected = + insert_gap_pixels(NDView(plain.view()), roi, gaps); auto actual = gapped.view(); if (expected.shape() != actual.shape()) { UNSCOPED_INFO("shape " << actual.shape(0) << "x" << actual.shape(1) @@ -317,18 +316,14 @@ bool matches_gap_oracle(Frame &gapped, Frame &plain, const ROI &roi, } bool matches_gap_oracle(Frame &gapped, Frame &plain, const ROI &roi, - DetectorType detector, bool quad, const GapPixels &gaps) { switch (plain.bitdepth()) { case 8: - return matches_gap_oracle(gapped, plain, roi, detector, quad, - gaps); + return matches_gap_oracle(gapped, plain, roi, gaps); case 16: - return matches_gap_oracle(gapped, plain, roi, detector, quad, - gaps); + return matches_gap_oracle(gapped, plain, roi, gaps); case 32: - return matches_gap_oracle(gapped, plain, roi, detector, quad, - gaps); + return matches_gap_oracle(gapped, plain, roi, gaps); default: return false; } @@ -354,7 +349,7 @@ TEST_CASE("RawFile inserts gap pixels into synthetic Jungfrau modules", gaps.module_gaps = ModuleGaps{8, 36}; gaps.fill_value = 7; } - const auto layout = detail::gap_layout(DetectorType::Jungfrau, false, gaps); + const auto layout = detail::gap_layout(gaps); const auto shape = detail::gapped_shape(detector, layout); const auto fill = static_cast(gaps.fill_value); @@ -426,8 +421,7 @@ TEST_CASE("RawFile splits counts like the free function", auto gapped_frame = gapped.read_frame(); auto plain_frame = plain.read_frame(); const ROI detector{0, 2048, 0, 512}; - REQUIRE(matches_gap_oracle(gapped_frame, plain_frame, detector, - DetectorType::Jungfrau, false, gaps)); + REQUIRE(matches_gap_oracle(gapped_frame, plain_frame, detector, gaps)); // the next frame continues the generator, so the oracle for frame 1 is // the free function applied after the same number of draws @@ -435,7 +429,7 @@ TEST_CASE("RawFile splits counts like the free function", auto second_plain = plain.read_frame(); auto expected = insert_gap_pixels( NDView(second_plain.view()), detector, - DetectorType::Jungfrau, false, gaps); + gaps); double gapped_total = 0; double expected_total = 0; auto view = frames[0].view(); @@ -509,8 +503,7 @@ TEST_CASE("Gap pixels on recorded Jungfrau and Eiger files", REQUIRE(gapped_frame.rows() == static_cast(test.shape[0])); REQUIRE(gapped_frame.cols() == static_cast(test.shape[1])); REQUIRE(matches_gap_oracle(gapped_frame, plain_frame, plain.master().roi(), - plain.detector_type(), - plain.master().quad() == 1, gaps)); + gaps)); } TEST_CASE("Split counts on a recorded Jungfrau file", @@ -525,7 +518,7 @@ TEST_CASE("Split counts on a recorded Jungfrau file", auto gapped_frame = gapped.read_frame(); auto plain_frame = plain.read_frame(); REQUIRE(matches_gap_oracle(gapped_frame, plain_frame, plain.master().roi(), - DetectorType::Jungfrau, false, gaps)); + gaps)); } TEST_CASE("Gap pixels on a file with two ROIs", @@ -550,8 +543,7 @@ TEST_CASE("Gap pixels on a file with two ROIs", for (size_t index = 0; index < 2; ++index) { CAPTURE(index); REQUIRE(matches_gap_oracle(gapped_frames[index], plain_frames[index], - rois[index], DetectorType::Jungfrau, false, - gaps)); + rois[index], gaps)); } }