diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ad94b5..eb8bfb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -352,6 +352,9 @@ set(PUBLICHEADERS include/aare/FilePtr.hpp include/aare/Frame.hpp include/aare/GainMap.hpp + include/aare/InclusiveROI.hpp + include/aare/Remap.hpp + include/aare/RemapConfig.hpp include/aare/ROIGeometry.hpp include/aare/DetectorGeometry.hpp include/aare/JungfrauDataFile.hpp @@ -390,6 +393,7 @@ set(SourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/src/RawFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RawMasterFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RawSubFile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Remap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/to_string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/task.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/ifstream_helpers.cpp diff --git a/include/aare/InclusiveROI.hpp b/include/aare/InclusiveROI.hpp new file mode 100644 index 0000000..1f15632 --- /dev/null +++ b/include/aare/InclusiveROI.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include + +namespace aare { + +struct InclusiveROI { + ssize_t xmin; + ssize_t xmax; // inclusive + ssize_t ymin; + ssize_t ymax; // inclusive + + constexpr ssize_t width() const noexcept { return xmax - xmin + 1; } + + constexpr ssize_t height() const noexcept { return ymax - ymin + 1; } + + constexpr ssize_t size() const noexcept { return width() * height(); } + + [[nodiscard]] constexpr bool is_empty() const noexcept { + return xmax < xmin || ymax < ymin; + } + + [[nodiscard]] constexpr bool contains(ssize_t x, ssize_t y) const noexcept { + return x >= xmin && x <= xmax && y >= ymin && y <= ymax; + } + + [[nodiscard]] constexpr bool fits_in(ssize_t ncols, + ssize_t nrows) const noexcept { + return xmin >= 0 && ymin >= 0 && xmax < ncols && ymax < nrows; + } + + static InclusiveROI emptyROI() noexcept { return {0, -1, 0, -1}; } + // TODO (nice to have) + // static InclusiveROI from_shape(ssize_t width, ssize_t height); +}; + +/*********************** + * Printint utility + ***********************/ +inline std::ostream &operator<<(std::ostream &os, InclusiveROI const &roi) { + os << "ROI (inclusive): x=[" << roi.xmin << ", " << roi.xmax << "], y=[" + << roi.ymin << ", " << roi.ymax << "], width=" << roi.width() + << ", height=" << roi.height() << ", pixels=" << roi.size(); + return os; +}; + +} // namespace aare + +namespace aare::inclusiveroi::geom { + +// coordinate space transforms +static inline InclusiveROI translate(InclusiveROI r, ssize_t dx, ssize_t dy) { + return {r.xmin + dx, r.xmax + dx, r.ymin + dy, r.ymax + dy}; +} +static inline InclusiveROI to_local(InclusiveROI const &roi) { + return {0, roi.xmax - roi.xmin, 0, roi.ymax - roi.ymin}; +} + +// mirroring (with respect to external grid given by width and height) +static inline InclusiveROI mirrorX(InclusiveROI r, ssize_t width) { + int x0p = (width - 1) - r.xmax; + int x1p = (width - 1) - r.xmin; + return {x0p, x1p, r.ymin, r.ymax}; +} +static inline InclusiveROI mirrorY(InclusiveROI r, ssize_t height) { + int y0p = (height - 1) - r.ymax; + int y1p = (height - 1) - r.ymin; + return {r.xmin, r.xmax, y0p, y1p}; +} +static inline InclusiveROI mirrorXY(InclusiveROI r, ssize_t width, + ssize_t height) { + return {mirrorX(mirrorY(r, height), width)}; +} + +// intersection / union +static inline InclusiveROI intersect(InclusiveROI const &a, + InclusiveROI const &b) { + InclusiveROI r; + r.xmin = std::max(a.xmin, b.xmin); + r.ymin = std::max(a.ymin, b.ymin); + r.xmax = std::min(a.xmax, b.xmax); + r.ymax = std::min(a.ymax, b.ymax); + + if (r.xmin > r.xmax || r.ymin > r.ymax) { + std::cout << "WARNING: ROIs do not intersect!" << std::endl; + return InclusiveROI::emptyROI(); // empty + } + return r; +} + +static inline InclusiveROI unite(InclusiveROI const &a, InclusiveROI const &b) { + // Horizontal union: same y-range + if (a.ymin == b.ymin && a.ymax == b.ymax) { + if (a.xmax + 1 >= b.xmin && + b.xmax + 1 >= a.xmin) { // overlap or adjacent + return {std::min(a.xmin, b.xmin), std::max(a.xmax, b.xmax), a.ymin, + a.ymax}; + } + } + + // Vertical union: same x-range + if (a.xmin == b.xmin && a.xmax == b.xmax) { + if (a.ymax + 1 >= b.ymin && + b.ymax + 1 >= a.ymin) { // overlap or adjacent + return {a.xmin, a.xmax, std::min(a.ymin, b.ymin), + std::max(a.ymax, b.ymax)}; + } + } + + throw std::runtime_error("ROIs cannot be united contiguously"); +} + +} // namespace aare::inclusiveroi::geom \ No newline at end of file diff --git a/include/aare/Remap.hpp b/include/aare/Remap.hpp new file mode 100644 index 0000000..65f56ba --- /dev/null +++ b/include/aare/Remap.hpp @@ -0,0 +1,239 @@ +#pragma once + +#include "aare/InclusiveROI.hpp" // IMPORTANT: Uses InclusiveROI!!! +#include "aare/NDArray.hpp" +#include "aare/NDView.hpp" +#include "aare/RemapConfig.hpp" + +#include + +namespace aare::remap::model { + +enum class SensorTech : int { iLGAD, TEW }; + +// This is dummy for now because it is not yet decided how best to depict the +// differences in batches R&D, production and year in a scalable way with +// unified naming conventions +enum class SensorRevision : int { RevA, RevB, RevC }; + +enum class SensorLayout : int { + Halfmodule, // not implemented yet! + Quad, + DoubleChip, // not implemented yet (new batch)! + SingleMP37, // not implemented yet (TEW 2024) + SingleMP25, + SingleMP18, + SingleMP15 +}; + +// Orientation of the sensor with respect to the ASICs / the HDI +// Normal means lower part of sensor (as in GDS) aligns with lower part of HDI +enum class Rotation : int { Normal = 0, Inverse = 1 }; + +struct SensorKey { + SensorTech tech; + SensorRevision rev = SensorRevision::RevA; + SensorLayout layout; +}; + +struct BondShift { + int x = 0; + int y = 0; +}; + +struct StrixelSensorConfig { + // --- Sensor identity (determines multiplicator, layout, groups) + SensorKey key; + // std::string label; + + // --- Pixel geometry + int cols; + int rows; + int guardring; // 9, 0 + BondShift bond_shift; + + // --- Strixel geometry + int multiplicator; // 2, 3, 5, or 4 + int shift_x; // number of “extra” square pixels (0, 1, 3, 2) + double pitch_um; // physical pitch (37.5 / 25 / 15 / 18.75) + int cols_remap; + int rows_remap; + + // --- Geometry of this strixel group *in local pixel coordinates* + // e.g. G1 = [10..246, 9..63] + InclusiveROI roi_group; + + // --- Sensor placement in *module-global* coordinates + // Example: chip 1 = [256..511, 0..255], chip 6 = [512..757, 256..511] + InclusiveROI roi_module; + + // --- Orientation + // --- Rotation of the chip (Normal / Inverse) + // Used to mirror group ROIs and determine mod ordering. + Rotation rotation; + std::optional chip_id; // only relevant for multiple sensors on module + + private: + friend StrixelSensorConfig + makeSensorConfig(SensorKey, std::optional user_rot, + std::optional chip_id, BondShift); + + // dumb, private constructor! (To decouple responsibilities) + StrixelSensorConfig(SensorKey key_, int cols_, int rows_, int guardring_, + BondShift bond_shift_, int multiplicator_, int shift_x_, + double pitch_um_, int cols_remap_, int rows_remap_, + InclusiveROI roi_group_, InclusiveROI roi_module_, + Rotation rotation_, std::optional chip_id_) + : key(key_), cols(cols_), rows(rows_), guardring(guardring_), + bond_shift(bond_shift_), multiplicator(multiplicator_), + shift_x(shift_x_), pitch_um(pitch_um_), cols_remap(cols_remap_), + rows_remap(rows_remap_), roi_group(roi_group_), + roi_module(roi_module_), rotation(rotation_), chip_id(chip_id_) {} +}; + +struct MappingResult { + aare::NDArray order_map; // strixel coordinates + int rows; // strixel coordinates + int cols; // strixel coordinates + int multiplicator; + aare::InclusiveROI + scd_roi_pixel; // final ROI in local pixel coordinates (smallest + // common denominator between receiver-ROI and + // strixel group with the same multiplicity) +}; + +} // namespace aare::remap::model + +namespace aare::remap::geom { + +/** + * Align roi_user to coordinate system of roi_base + */ +aare::InclusiveROI alignROIs(InclusiveROI const &roi_user, + InclusiveROI const &roi_base); + +/** + * Auto-rotate based on chip_id + */ +aare::remap::model::Rotation autoRotate(int chip_id); + +} // namespace aare::remap::geom + +namespace aare::remap::resolve { +using namespace aare::remap::model; + +aare::remap::config::GroupDescriptor const &groupDescriptor(SensorKey); +aare::remap::config::ChipGeometry chipGeometry(SensorKey); +aare::InclusiveROI moduleROI(SensorKey, std::optional chip_id); + +} // namespace aare::remap::resolve + +namespace aare::remap::algo { +using namespace aare::remap::model; + +/** + * Core remapping function for a single contiguous unit + * (Multipitch G1, G2, or G3, or halfquad). + * + * \param roi_user user-supplied roi in chip or quad coordinates + * \param roi_group valid, full roi of a strixel pitch-group or contiguous + * strixel region (e.g. halfquad) in local coordinates + * \param multiplicator multiplicity of the strixel design; 3 (MP25, Quad), 5 + * (MP15), 4 (MP18) + * \param rot Rotation Normal or Inverse; mods[] order reversed if rotation == + * Inverse. + * \param shifty optional shift in y (in strixel map space!) + */ +MappingResult generateUnitMap(aare::InclusiveROI const &roi_user, + aare::InclusiveROI const &roi_group, + int multiplicator, Rotation rot, int shifty = 0); + +/** + * Utility to join to separately mapped core units of a Strixel Quad (bottom and + * top half with gap-pixels in-between) + */ +MappingResult joinQuadMaps(MappingResult const &bottom, + MappingResult const &top, int gap_rows); + +/** + * Public API: + * Generates mapping for a given region of single chip multipitch strixel + * sensor (G1,G2,G3) and returns only the active intersection with the ROI of + * the JSON file. + * \param roi_module JSON rx_ROI in module coordinates (as read from master + * file) + * \param key SensorKey encoding SensorTech, SensorLayout, SensorRevision + * \param chip_id 1 or 6 + * \param rot Rotation (optional): Normal or Inverse, only supply if you know + * what you are doing! Otherwise give std::nullopt and the rotation will be + * automatically determined based on chip_id + * \param bond_shift For modules like M408, where we know the bump bonding is + * shifted in y, auto-rotates depending on chip_id or user supplied rotation, + * x-shift is possible for completeness + */ +MappingResult +generateMPStrixelMapping(aare::InclusiveROI const &roi_user_module, + SensorKey key, int chip_id, + std::optional user_rot, BondShift); + +/** + * Public API: + * Generates mapping for a full Quad Sensor with multiplicity 3 (25 um pitch). + * + * \param roi_module JSON rx_ROI in module coordinates + * \param key SensorKey encoding SensorTech, SensorLayout, SensorRevision + * \param rot Rotation (optional): Normal or Inverse, default is + * Rotation::Normal (give std::nullopt) + * \param bond_shift Included for completeness, should always be 0 for Quad + * (give default constructed) + */ +MappingResult +generateQuadStrixelMapping(aare::InclusiveROI const &roi_user_module, + SensorKey key, std::optional user_rot, + BondShift); + +/** + * Public API: + * Applies a given remapping rule to an input array. + * + * \param input Original array + * \param order_map Rule for remapping + * \param output Remapped array + */ +template +void ApplyRemap(aare::NDView const &input, + aare::NDArray const &order_map, + aare::NDArray &output) { + for (size_t row = 0; row < order_map.shape(0); ++row) { + for (size_t col = 0; col < order_map.shape(1); ++col) { + auto flat_index = order_map(row, col); + if (flat_index >= 0 && + static_cast(flat_index) < input.size()) { + // FIXME: input[flat_index] bypasses const-correctness due to + // broken NDView::operator[] Safe here because we only read from + // input. Should be fixed when/if NDView is corrected. + output(row, col) = static_cast(input[flat_index]); + } else { + output(row, col) = static_cast(0); // or nan? + } + } + } +} + +} // namespace aare::remap::algo + +namespace aare::remap::format { +using namespace aare::remap::model; + +/** + * Helpers for printing + */ +static inline std::string toString(SensorKey); +static inline std::string toString(SensorLayout); +static inline std::string toString(SensorTech); +static inline std::string toString(SensorRevision); +static inline std::string toString(Rotation); +static inline std::string toString(StrixelSensorConfig const &c); +inline std::ostream &operator<<(std::ostream &os, StrixelSensorConfig const &c); + +} // namespace aare::remap::format \ No newline at end of file diff --git a/include/aare/RemapConfig.hpp b/include/aare/RemapConfig.hpp new file mode 100644 index 0000000..4190d3d --- /dev/null +++ b/include/aare/RemapConfig.hpp @@ -0,0 +1,141 @@ +#include "aare/InclusiveROI.hpp" // IMPORTANT: Uses InclusiveROI!!! + +namespace aare::remap::config { + +/** + * Helper structs for strong-typing + */ +struct ChipGeometry { + int cols; + int rows; + int guardring; +}; + +struct GroupDescriptor { + int multiplicator; + int x_shift; // to skip additional square pixels that are not remapped + InclusiveROI strixel_roi; // in chip-local coords (no mirroring) + int nrows_remap; // precomputed or computed once + int ncols_remap; // same + double pitch_um; +}; + +// Separate mappings for the multi-pitch single-chip strixel sensor +// (small pitch in vertical) +struct SingleChipMP_iLGAD { + static constexpr bool multi_chip = true; + + static constexpr ChipGeometry geometry{ + .cols = 256, .rows = 256, .guardring = 9}; + + // Sensor placement in module coordinates: + static constexpr InclusiveROI chip1{256, 511, 0, 255}; + static constexpr InclusiveROI chip6{512, 767, 256, 511}; + + // Group descriptors: + // Group 1: 25um pitch, groups of 3, 1 column of square pixels + static inline const GroupDescriptor P25 = { + .multiplicator = 3, + .x_shift = 1, + .strixel_roi = + InclusiveROI{geometry.guardring + 1, + geometry.cols - geometry.guardring - 1, + geometry.guardring, (geometry.rows / 4) - 1}, + .nrows_remap = ((geometry.rows / 4) - geometry.guardring) * 3, + .ncols_remap = (geometry.cols - (2 * geometry.guardring) - 1) / 3, + .pitch_um = 25.0}; + + // Group 2: 15um pitch, groups of 5, 3 columns of square pixels + static inline const GroupDescriptor P15 = { + .multiplicator = 5, + .x_shift = 3, + .strixel_roi = + InclusiveROI{geometry.guardring + 3, + geometry.cols - geometry.guardring - 1, + geometry.rows / 4, (geometry.rows / 4) * 2 - 1}, + .nrows_remap = (geometry.rows / 4) * 5, + .ncols_remap = (geometry.cols - (2 * geometry.guardring) - 3) / 5, + .pitch_um = 15.0}; + + // Group 3: 18.75um pitch, groups of 4, 2 columns of square pixels (double + // the size of the other groups, differe in design of metal layer) + static inline const GroupDescriptor P18 = { + .multiplicator = 4, + .x_shift = 2, + .strixel_roi = InclusiveROI{geometry.guardring + 2, + geometry.cols - geometry.guardring - 1, + (geometry.rows / 4) * 2, + geometry.rows - geometry.guardring - 1}, + .nrows_remap = ((geometry.rows / 4) * 2 - geometry.guardring) * 4, + .ncols_remap = (geometry.cols - (2 * geometry.guardring) - 2) / 4, + .pitch_um = 18.75}; +}; + +struct SingleChipMP_TEW { + static constexpr bool multi_chip = true; + + static constexpr ChipGeometry geometry{ + .cols = 256, .rows = 256, .guardring = 0}; + + // Sensor placement in module coordinates: + static constexpr InclusiveROI chip1{256, 511, 0, 255}; + static constexpr InclusiveROI chip6{512, 767, 256, 511}; + + // Group descriptors: + // Group 1: 25um pitch, groups of 3, 1 column of square pixels + static inline const GroupDescriptor P25 = { + .multiplicator = 3, + .x_shift = 1, + .strixel_roi = + InclusiveROI{1, geometry.cols - 1, 0, (geometry.rows / 4) - 1}, + .nrows_remap = (geometry.rows / 4) * 3, + .ncols_remap = geometry.cols / 3, // 85 + .pitch_um = 25.0}; + + // Group 2: 15um pitch, groups of 5, 3 columns of square pixels + static inline const GroupDescriptor P15 = { + .multiplicator = 5, + .x_shift = 1, + .strixel_roi = InclusiveROI{1, geometry.cols - 1, geometry.rows / 4, + (geometry.rows / 4) * 2 - 1}, + .nrows_remap = (geometry.rows / 4) * 5, + .ncols_remap = geometry.cols / 5, // 51 + .pitch_um = 15.0}; + + // Group 3: 18.75um pitch, groups of 4, 2 columns of square pixels (double + // the size of the other groups, differe in design of metal layer) + static inline const GroupDescriptor P18 = { + .multiplicator = 4, + .x_shift = 0, + .strixel_roi = InclusiveROI{0, geometry.cols - 1, + (geometry.rows / 4) * 2, geometry.rows - 1}, + .nrows_remap = ((geometry.rows / 4) * 2) * 4, + .ncols_remap = geometry.cols / 4, + .pitch_um = 18.75}; +}; + +// Constants describing the mapping of a 25µm strixel quad sensor +// A detailed reference of the layout can be found at +// https://1drv.ms/b/c/2c9cf72ecfd75ec5/EcVe188u95wggCx7HwMAAAABH4SoINkrNCUqPnmmsUjGRg?e=TwaHzY +struct Quad_iLGAD { + static constexpr bool multi_chip = false; + + static constexpr ChipGeometry geometry{ + .cols = 512, .rows = 512, .guardring = 9}; + + // Sensor placement in module coordinates: + static constexpr InclusiveROI coords{256, 767, 0, 511}; + + // Group descriptors: + static inline const GroupDescriptor Half = { + .multiplicator = 3, + .x_shift = 2, + .strixel_roi = InclusiveROI{geometry.guardring + 1, + geometry.cols - geometry.guardring - 1, + geometry.guardring, geometry.rows - 2}, + .nrows_remap = (geometry.rows - 1 - geometry.guardring) * 3, + .ncols_remap = (geometry.cols - 2 - 2 * geometry.guardring) / 3, + .pitch_um = 25.0}; +}; + +} // namespace aare::remap::config \ No newline at end of file diff --git a/src/Remap.cpp b/src/Remap.cpp new file mode 100644 index 0000000..22c1b79 --- /dev/null +++ b/src/Remap.cpp @@ -0,0 +1,531 @@ +#include "aare/Remap.hpp" + +#include + +/****************************** + * **************************** + * aare::remap::model + * + * Basic depiction of remapping + * **************************** + ******************************/ +namespace aare::remap::model { + +// Factory function (public API) +StrixelSensorConfig makeSensorConfig(SensorKey key, + std::optional user_rot, + std::optional chip_id, + BondShift bond_shift) { + + auto const &G = resolve::groupDescriptor(key); + auto const geometry = resolve::chipGeometry(key); + aare::InclusiveROI roi_module = resolve::moduleROI(key, chip_id); + + Rotation rot = Rotation::Normal; + if (user_rot.has_value()) { + rot = user_rot.value(); + } else if (chip_id.has_value()) { + rot = geom::autoRotate(chip_id.value()); + } + + StrixelSensorConfig cfg(key, geometry.cols, geometry.rows, + geometry.guardring, bond_shift, G.multiplicator, + G.x_shift, G.pitch_um, G.ncols_remap, G.nrows_remap, + G.strixel_roi, roi_module, rot, chip_id); + + // Apply physical transforms + if (cfg.bond_shift.x != 0 || cfg.bond_shift.y != 0) + cfg.roi_group = aare::inclusiveroi::geom::translate( + cfg.roi_group, cfg.bond_shift.x, cfg.bond_shift.y); + + if (cfg.rotation == Rotation::Inverse) + cfg.roi_group = aare::inclusiveroi::geom::mirrorXY(cfg.roi_group, + cfg.cols, cfg.rows); + + return cfg; +} + +} // namespace aare::remap::model + +/****************************** + * **************************** + * aare::remap::format + * + * Format helpers + * **************************** + ******************************/ +namespace aare::remap::format { + +static inline std::string toString(SensorTech tech) { + switch (tech) { + case SensorTech::iLGAD: + return "Technology: iLGAD"; + case SensorTech::TEW: + return "Technology: TEW"; + default: + return "SensorTech::Unknown"; + } +} + +static inline std::string toString(SensorRevision rev) { + switch (rev) { + case SensorRevision::RevA: + return "Revision: RevA"; + case SensorRevision::RevB: + return "Revision: RevB"; + case SensorRevision::RevC: + return "Revision: RevC"; + default: + return "SensorRevision::Unknown"; + } +} + +static inline std::string toString(SensorLayout l) { + switch (l) { + case SensorLayout::SingleMP25: + return "Layout: SingleMP25 (G1, 25 um pitch)"; + case SensorLayout::SingleMP15: + return "Layout: SingleMP15 (G2, 15 um pitch)"; + case SensorLayout::SingleMP18: + return "Layout: SingleMP18 (G3, 18.75 um pitch)"; + case SensorLayout::SingleMP37: + return "Layout: SingleMP37 (G4, 37.5 um pitch)"; + case SensorLayout::Quad: + return "Layout: Quad (25 um pitch)"; + case SensorLayout::Halfmodule: + return "Layout: Halfmodule"; + case SensorLayout::DoubleChip: + return "Layout: DoubleChip"; + default: + return "SensorLayout::Unknown"; + } +} + +static inline std::string toString(SensorKey key) { + return toString(key.tech) + " | " + toString(key.layout) + " | " + + toString(key.rev); +} + +static inline std::string toString(Rotation r) { + return (r == Rotation::Normal ? "Normal" : "Inverse"); +} + +static inline std::string toString(StrixelSensorConfig const &c) { + std::ostringstream os; + + os << "StrixelSensorConfig\n" + << " key : " << toString(c.key) << "\n" + << " rotation : " << toString(c.rotation) << "\n"; + + if (c.chip_id) + os << " chip_id : " << *c.chip_id << "\n"; + + os << " pixel geometry :\n" + << " cols x rows : " << c.cols << " x " << c.rows << "\n" + << " guardring : " << c.guardring << "\n" + << " bond_shift_x : " << c.bond_shift.x << "\n" + << " bond_shift_y : " << c.bond_shift.y << "\n"; + + os << " strixel geometry :\n" + << " multiplicator : " << c.multiplicator << "\n" + << " shift_x : " << c.shift_x << "\n" + << " pitch_um : " << c.pitch_um << "\n" + << " remap cols : " << c.cols_remap << "\n" + << " remap rows : " << c.rows_remap << "\n"; + + os << " roi_group : " << c.roi_group << "\n" + << " roi_module : " << c.roi_module << "\n"; + + return os.str(); +} + +inline std::ostream &operator<<(std::ostream &os, + StrixelSensorConfig const &c) { + return os << toString(c); +} + +} // namespace aare::remap::format + +/****************************** + * **************************** + * aare::remap::geom + * + * Geometric helpers + * **************************** + ******************************/ +namespace aare::remap::geom { + +using namespace aare::remap::model; + +aare::InclusiveROI alignROIs(aare::InclusiveROI const &roi_user, + aare::InclusiveROI const &roi_base) { + const int dx = roi_base.xmin; + const int dy = roi_base.ymin; // + bond_shift_y; + + return {roi_user.xmin - dx, roi_user.xmax - dx, roi_user.ymin - dy, + roi_user.ymax - dy}; + // return roi::geom::translate(roi_user, roi_base.xmin, roi_base.ymin); +} + +Rotation autoRotate(int chip_id) { + return (chip_id == 1 ? Rotation::Normal + : chip_id == 6 ? Rotation::Inverse + : throw std::runtime_error("Unknown chip_id")); +} + +} // namespace aare::remap::geom + +/****************************** + * **************************** + * aare::remap::algo + * + * Remapping algorithms + * **************************** + ******************************/ +namespace aare::remap::algo { + +using namespace aare::remap::model; +using namespace aare::remap::format; + +MappingResult generateUnitMap(aare::InclusiveROI const &roi_user, + aare::InclusiveROI const &roi_group, + int multiplicator, Rotation rot, int shifty) { + // Helper to make sure that we work with a correct number of strixel columns + // (i.e. that we do not map pixel columns if the ncols in ASIC pixel + // coordinates is not a multiple of strixel ncols) + if (roi_group.width() % multiplicator != 0) + throw std::logic_error( + "Group ROI width not divisible by multiplicator"); + + const int tot_ncols_strx = roi_group.width() / multiplicator; + + // Define mod ordering (Normal or Inverse) + std::vector mods(multiplicator); + for (int i = 0; i < multiplicator; i++) + mods[i] = i; + if (rot == Rotation::Inverse) + std::reverse(mods.begin(), mods.end()); + + // -- 1) Compute effective ROI = intersection( roi_user, roi_group ) + aare::InclusiveROI eff = + aare::inclusiveroi::geom::intersect(roi_user, roi_group); + if (eff.xmax < eff.xmin || eff.ymax < eff.ymin) { + return {{}, 0, 0, -1, aare::InclusiveROI::emptyROI()}; // empty + } + + // DEBUG + std::cout << "Result of intersecting ROIs " << eff << '\n'; + + //-- 2) Determine min/max row/col of strixel grid before allocating + // (This may vary from the native grid of the group because of ROI + // intersection.) + int min_row_strx = std::numeric_limits::max(); + int max_row_strx = std::numeric_limits::min(); + int min_col_strx = std::numeric_limits::max(); + int max_col_strx = std::numeric_limits::min(); + + for (int y = eff.ymin; y <= eff.ymax; ++y) { + for (int x = eff.xmin; x <= eff.xmax; ++x) { + + const int dx = x - roi_group.xmin; + const int dy = (y - roi_group.ymin); + + const int m = dx % multiplicator; + const int col_strx = dx / multiplicator; + const int row_strx = dy * multiplicator + mods[m] + shifty; + + if (col_strx < 0 || row_strx < 0) + continue; + if (col_strx >= tot_ncols_strx) + continue; + + min_row_strx = std::min(min_row_strx, row_strx); + max_row_strx = std::max(max_row_strx, row_strx); + min_col_strx = std::min(min_col_strx, col_strx); + max_col_strx = std::max(max_col_strx, col_strx); + } + } + + if (min_row_strx > max_row_strx) { + // nothing mapped + return {{}, 0, 0, multiplicator, eff}; + } + + const int nrows_strx = max_row_strx - min_row_strx + 1; + const int ncols_strx = max_col_strx - min_col_strx + 1; + + // Allocate strixel grid order map + aare::NDArray ord({nrows_strx, ncols_strx}, -1); + + // -- 3) For each ASIC pixel in eff ROI, compute remapped (row,col) in group + // local coordinates + for (int y = eff.ymin; y <= eff.ymax; ++y) { + for (int x = eff.xmin; x <= eff.xmax; ++x) { + + const int dx = x - roi_group.xmin; + const int dy = (y - roi_group.ymin); + + const int m = dx % multiplicator; // since eff is intersected with + // roi_group, dx >= 0, so no issue + const int col_strx = dx / multiplicator; + const int row_strx = dy * multiplicator + mods[m] + shifty; + + if (col_strx < min_col_strx || row_strx < min_row_strx) + continue; + + const int cstrx = col_strx - min_col_strx; + const int rstrx = row_strx - min_row_strx; + + if (rstrx >= 0 && rstrx < nrows_strx && cstrx >= 0 && + cstrx < ncols_strx) { + // index into ORIGINAL USER ROI GRID + const int user_pixel = (y - roi_user.ymin) * roi_user.width() + + (x - roi_user.xmin); + + ord(rstrx, cstrx) = user_pixel; + } + } + } + + return {ord, nrows_strx, ncols_strx, multiplicator, eff}; +} + +MappingResult joinQuadMaps(MappingResult const &bottom, + MappingResult const &top, int gap_rows) { + if (bottom.cols == 0 && top.cols == 0) + return {{}, 0, 0, -1, aare::InclusiveROI::emptyROI()}; + + if (bottom.multiplicator != top.multiplicator) { + throw std::runtime_error("Multiplicators not compatible."); + } + + const int global_cols = std::max(bottom.cols, top.cols); + const int global_rows = bottom.rows + gap_rows + top.rows; + + aare::NDArray ord({global_rows, global_cols}, -1); + + // --- copy bottom half --- + for (int r = 0; r < bottom.rows; ++r) { + for (int c = 0; c < bottom.cols; ++c) { + ord(r, c) = bottom.order_map(r, c); + } + } + + // --- copy top half --- + const int top_row_offset = bottom.rows + gap_rows; + for (int r = 0; r < top.rows; ++r) { + for (int c = 0; c < top.cols; ++c) { + ord(top_row_offset + r, c) = top.order_map(r, c); + } + } + + // --- smallest common denominator ROI (pixel space) + aare::InclusiveROI scd = aare::inclusiveroi::geom::intersect( + bottom.scd_roi_pixel, top.scd_roi_pixel); + + return {ord, global_cols, global_rows, bottom.multiplicator, scd}; +} + +MappingResult generateMPStrixelMapping( + aare::InclusiveROI const &roi_user_module, SensorKey key, int chip_id, + std::optional user_rot, BondShift bond_shift) { + // -- 1) initialize config + auto config = makeSensorConfig(key, user_rot, chip_id, bond_shift); + // static_assert(std::is_same_v); + std::cout << "Initialized config: " << config << std::endl; + + if (!(key.layout == SensorLayout::SingleMP25 || + key.layout == SensorLayout::SingleMP15 || + key.layout == SensorLayout::SingleMP18)) { + throw std::runtime_error("Invalid sensor type!"); + } /* else { + std::cout << "Sensor type " << config.label << std::endl; + } */ + + // -- 2) transform user ROI to sensor-local coordinates + const aare::InclusiveROI roi_user_local = + aare::remap::geom::alignROIs(roi_user_module, config.roi_module); + std::cout << "Transformed user ROI: " << roi_user_local << std::endl; + + // -- 3) remap + auto m = generateUnitMap(roi_user_local, config.roi_group, + config.multiplicator, config.rotation); + if (m.cols > 0) + return m; + + // No valid region → return empty + return {{}, 0, 0, config.multiplicator, aare::InclusiveROI::emptyROI()}; +} + +MappingResult +generateQuadStrixelMapping(aare::InclusiveROI const &roi_user_module, + SensorKey key, std::optional user_rot, + BondShift bond_shift) { + // -- 1) initialize configs + auto config = makeSensorConfig(key, user_rot, std::nullopt, bond_shift); + + if (!(key.layout == SensorLayout::Quad)) { + throw std::runtime_error("Invalid sensor type!"); + } + + // -- 2) transform user module coordinates to local coordinates + aare::InclusiveROI roi_user_local = + aare::remap::geom::alignROIs(roi_user_module, config.roi_module); + std::cout << "Transformed user ROI: " << roi_user_local << std::endl; + + // -- 3) get definition of half quad ROI + const aare::InclusiveROI halfquad = config.roi_group; + + // -- 4) remap bottom half (normal mod order) + auto bottom = + generateUnitMap(roi_user_local, halfquad, config.multiplicator, + Rotation::Normal, /*shifty=*/0); + + // -- 5) top half (mirrored ROI, inverse mod order) + aare::InclusiveROI top_halfquad = + aare::inclusiveroi::geom::mirrorXY(halfquad, config.cols, config.rows); + auto top = + generateUnitMap(roi_user_local, top_halfquad, config.multiplicator, + Rotation::Inverse, /*shifty=*/0); + + // -- 6) compose into quad + constexpr int gap_rows = 12; // I don't like that this is hardcoded here + return joinQuadMaps(bottom, top, gap_rows); +} + +} // namespace aare::remap::algo + +/****************************** + * **************************** + * remap::resolve + * + * Resolvers that load from the + * right config. Only here we + * have a config connection! + * **************************** + ******************************/ +namespace aare::remap::resolve { +using namespace aare::remap::config; + +GroupDescriptor const &groupDescriptor(SensorKey key) { + + switch (key.tech) { + + // ================= iLGAD ================= + case SensorTech::iLGAD: + switch (key.layout) { + case SensorLayout::SingleMP25: + return SingleChipMP_iLGAD::P25; + case SensorLayout::SingleMP15: + return SingleChipMP_iLGAD::P15; + case SensorLayout::SingleMP18: + return SingleChipMP_iLGAD::P18; + case SensorLayout::Quad: + return Quad_iLGAD::Half; + default: + throw std::runtime_error("Unsupported SensorLayout for iLGAD"); + } + + // ================= TEW =================== + case SensorTech::TEW: + switch (key.layout) { + case SensorLayout::SingleMP25: + return SingleChipMP_TEW::P25; + case SensorLayout::SingleMP15: + return SingleChipMP_TEW::P15; + case SensorLayout::SingleMP18: + return SingleChipMP_TEW::P18; + default: + throw std::runtime_error("Unsupported SensorLayout for TEW"); + } + + default: + throw std::runtime_error("Unsupported SensorTech"); + } +} + +ChipGeometry chipGeometry(SensorKey key) { + + using SL = SensorLayout; + using ST = SensorTech; + + switch (key.layout) { + case SL::SingleMP25: + case SL::SingleMP15: + case SL::SingleMP18: + switch (key.tech) { + case ST::iLGAD: + return SingleChipMP_iLGAD::geometry; + case ST::TEW: + return SingleChipMP_TEW::geometry; + default: + throw std::logic_error("Unsupported SensorTech"); + } + case SL::Quad: + switch (key.tech) { + case ST::iLGAD: + return Quad_iLGAD::geometry; + default: + throw std::logic_error( + "Unsupported SensorTech for SensorLayout Quad"); + } + + default: + throw std::logic_error("Unsupported SensorLayout"); + } +} + +aare::InclusiveROI moduleROI(SensorKey key, std::optional chip_id) { + + using SL = SensorLayout; + using ST = SensorTech; + + auto requireChip = [&](bool needed) { + if (needed && !chip_id) + throw std::logic_error("chip_id required for this layout"); + if (!needed && chip_id) + throw std::logic_error("chip_id must not be set for this layout"); + }; + + switch (key.layout) { + + // ---------- Single-chip multipitch ---------- + case SL::SingleMP25: + case SL::SingleMP15: + case SL::SingleMP18: { + requireChip(true); + + const int cid = *chip_id; + if (cid != 1 && cid != 6) + throw std::out_of_range("Unsupported chip_id (expected 1 or 6)"); + + switch (key.tech) { + case ST::iLGAD: + return (cid == 1) ? SingleChipMP_iLGAD::chip1 + : SingleChipMP_iLGAD::chip6; + case ST::TEW: + return (cid == 1) ? SingleChipMP_TEW::chip1 + : SingleChipMP_TEW::chip6; + default: + throw std::logic_error("Unsupported SensorTech"); + } + } + + // ---------- Quad layout ---------- + case SL::Quad: { + requireChip(false); + + switch (key.tech) { + case ST::iLGAD: + return Quad_iLGAD::coords; + default: + throw std::logic_error("Quad layout not supported for this tech"); + } + } + + default: + throw std::logic_error("Unsupported SensorLayout"); + } +} + +} // namespace aare::remap::resolve \ No newline at end of file