mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-06-04 09:48:40 +02:00
Add first remapping draft
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
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
|
||||
@@ -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 <optional>
|
||||
|
||||
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<int> chip_id; // only relevant for multiple sensors on module
|
||||
|
||||
private:
|
||||
friend StrixelSensorConfig
|
||||
makeSensorConfig(SensorKey, std::optional<Rotation> user_rot,
|
||||
std::optional<int> 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<int> 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<ssize_t, 2> 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<int> 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<Rotation> 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<Rotation> 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 <typename T>
|
||||
void ApplyRemap(aare::NDView<T, 2> const &input,
|
||||
aare::NDArray<ssize_t, 2> const &order_map,
|
||||
aare::NDArray<T, 2> &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<size_t>(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<T>(input[flat_index]);
|
||||
} else {
|
||||
output(row, col) = static_cast<T>(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
|
||||
@@ -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
|
||||
+531
@@ -0,0 +1,531 @@
|
||||
#include "aare/Remap.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
/******************************
|
||||
* ****************************
|
||||
* aare::remap::model
|
||||
*
|
||||
* Basic depiction of remapping
|
||||
* ****************************
|
||||
******************************/
|
||||
namespace aare::remap::model {
|
||||
|
||||
// Factory function (public API)
|
||||
StrixelSensorConfig makeSensorConfig(SensorKey key,
|
||||
std::optional<Rotation> user_rot,
|
||||
std::optional<int> 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<int> 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<int>::max();
|
||||
int max_row_strx = std::numeric_limits<int>::min();
|
||||
int min_col_strx = std::numeric_limits<int>::max();
|
||||
int max_col_strx = std::numeric_limits<int>::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<ssize_t, 2> 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<ssize_t, 2> 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<Rotation> user_rot, BondShift bond_shift) {
|
||||
// -- 1) initialize config
|
||||
auto config = makeSensorConfig(key, user_rot, chip_id, bond_shift);
|
||||
// static_assert(std::is_same_v<decltype(config.pitch_um), double>);
|
||||
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<Rotation> 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<int> 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
|
||||
Reference in New Issue
Block a user