// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once #include #include #include #include #include "DetectorModules.h" // The detector plane as a picture: raster pixel (i, j) is the plane point (x0 + i, y0 + j). For the // algorithms that need the detector as it really is - a beam-stop shadow and the gaps it crosses, the point // symmetry about the beam, a picture of the detector - and not as the panels are stored. struct PlaneRaster { int64_t x0 = 0, y0 = 0, width = 0, height = 0; }; // The raster holding every panel. It starts at the plane origin, so plane and raster coordinates agree, // unless a panel lies left of or above it. Empty table: the image itself. PlaneRaster PlaneRasterOf(const DetectorModules &modules, int64_t width, int64_t height); // The image (width x height) as the plane raster: each raster pixel takes the image pixel nearest its centre // on the panel it falls on, and `gap` where it falls on none. Empty table, with the image's own raster: a copy. template std::vector AssembleToPlane(const DetectorModules &modules, const std::vector &image, int64_t width, int64_t height, const PlaneRaster &raster, T gap) { std::vector out(static_cast(raster.width * raster.height), gap); const auto put = [&](const DetectorModule &m, int64_t i0, int64_t i1, int64_t j0, int64_t j1) { for (int64_t j = std::max(j0, 0); j < std::min(j1, raster.height); j++) for (int64_t i = std::max(i0, 0); i < std::min(i1, raster.width); i++) { const auto [x, y] = m.PlaneToImage(static_cast(raster.x0 + i), static_cast(raster.y0 + j)); if (m.ContainsImagePixel(x, y)) out[j * raster.width + i] = image[static_cast(std::floor(y + 0.5)) * width + static_cast(std::floor(x + 0.5))]; } }; if (modules.empty()) { put(DetectorModule::AsImaged(0, 0, width, height), -raster.x0, width - raster.x0, -raster.y0, height - raster.y0); return out; } // Panel by panel, over the raster pixels of its footprint's box. Panels do not overlap. for (const auto &m: modules.Get()) { const PlaneBox box = DetectorModules({m}).PlaneBounds(width, height); put(m, static_cast(std::floor(box.x0)) - raster.x0, static_cast(std::ceil(box.x1)) - raster.x0, static_cast(std::floor(box.y0)) - raster.y0, static_cast(std::ceil(box.y1)) - raster.y0); } return out; } // The reverse, for a result computed on the raster: each image pixel on a panel takes the raster pixel // nearest its centre on the plane, `outside` where that is off the raster; a pixel on no panel (a gap) // is `outside` too. Empty table, with the image's own raster: a copy. template std::vector GatherFromPlane(const DetectorModules &modules, const std::vector &plane, const PlaneRaster &raster, int64_t width, int64_t height, T outside) { std::vector out(static_cast(width * height), outside); modules.ForEachPixel(width, height, [&](int64_t, size_t index, double px, double py) { const auto i = static_cast(std::floor(px + 0.5)) - raster.x0; const auto j = static_cast(std::floor(py + 0.5)) - raster.y0; if (i >= 0 && i < raster.width && j >= 0 && j < raster.height) out[index] = plane[j * raster.width + i]; }); return out; }