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)); } }