diff --git a/include/aare/StrixelPixelRemapAlgorithm.hpp b/include/aare/StrixelPixelRemapAlgorithm.hpp index 0db90dbc..eb4384bb 100644 --- a/include/aare/StrixelPixelRemapAlgorithm.hpp +++ b/include/aare/StrixelPixelRemapAlgorithm.hpp @@ -102,7 +102,7 @@ strixel_to_pixel_maps(defs::SensorConfig const &sensor_config, */ template void ApplyRemap(NDView input, NDView order_map, - NDArray &output) { + NDView output) { if (output.shape() != order_map.shape()) { throw std::invalid_argument( @@ -125,7 +125,7 @@ void ApplyRemap(NDView input, NDView order_map, } // Corrupt map, must throw - if (static_cast(flat_index) >= input.size()) { + if (flat_index >= input.size()) { throw std::runtime_error( "ApplyRemap: order map contains an invalid pixel index."); } diff --git a/include/aare/StrixelPixelRemapConfig.hpp b/include/aare/StrixelPixelRemapConfig.hpp index d552e175..61fa0c50 100644 --- a/include/aare/StrixelPixelRemapConfig.hpp +++ b/include/aare/StrixelPixelRemapConfig.hpp @@ -1,3 +1,4 @@ +#pragma once #include "aare/StrixelPixelRemapDefs.hpp" namespace aare::remap::config::jungfrau { diff --git a/include/aare/StrixelPixelRemapGenerate.hpp b/include/aare/StrixelPixelRemapGenerate.hpp index 8c378f5a..08510764 100644 --- a/include/aare/StrixelPixelRemapGenerate.hpp +++ b/include/aare/StrixelPixelRemapGenerate.hpp @@ -99,7 +99,7 @@ combine_group_maps(defs::StrixelGroupToPixelMap const &first, effective_roi.ymax = std::max(effective_roi.ymax, second.effective_roi.ymax); - return {.map = std::move(combined), .effective_roi = effective_roi}; + return {std::move(combined), effective_roi}; } } // namespace detail diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 74f35ce3..1ff2fdbe 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -46,6 +46,8 @@ set(PYTHON_FILES aare/RawFile.py aare/transform.py aare/ScanParameters.py + aare/strixelremap/__init__.py + aare/strixelremap/SensorConfig.py aare/utils.py) # Copy the python files to the build directory diff --git a/python/aare/__init__.py b/python/aare/__init__.py index 72c2573b..b7a6be29 100644 --- a/python/aare/__init__.py +++ b/python/aare/__init__.py @@ -67,3 +67,5 @@ from ._aare import ( PixelHistogram_u32, PixelHistogram_u64, ) + +from . import strixelremap diff --git a/python/aare/strixelremap/SensorConfig.py b/python/aare/strixelremap/SensorConfig.py new file mode 100644 index 00000000..8cd0a07f --- /dev/null +++ b/python/aare/strixelremap/SensorConfig.py @@ -0,0 +1,22 @@ +from .._aare import strixelremap + + +def SensorConfig(sensor_geometry, group_configs): + """ + Helper function to create a sensor configuration from a sensor geometry and a list of group configurations + + Args: + sensor_geometry (SensorPixelGeometry): The sensor geometry + group_configs (list[GroupConfig]): The list of group configurations + """ + + if(len(group_configs) == 0): + raise ValueError("group_configs must contain at least one group configuration") + + N = len(group_configs) + + if N > 4: + raise ValueError("sensor configs with more than 4 groups are not bound in Python") + + sensor_config_cls = getattr(strixelremap, f"SensorConfig_{N}PixelGroups") + return sensor_config_cls(sensor_geometry, group_configs) diff --git a/python/aare/strixelremap/__init__.py b/python/aare/strixelremap/__init__.py new file mode 100644 index 00000000..a7170a98 --- /dev/null +++ b/python/aare/strixelremap/__init__.py @@ -0,0 +1,4 @@ +from .SensorConfig import SensorConfig + +from .._aare.strixelremap import * + diff --git a/python/src/StrixelRemap/bind_InclusiveROI.hpp b/python/src/StrixelRemap/bind_InclusiveROI.hpp new file mode 100644 index 00000000..d5875884 --- /dev/null +++ b/python/src/StrixelRemap/bind_InclusiveROI.hpp @@ -0,0 +1,109 @@ +#include + +#include "aare/InclusiveROI.hpp" + +namespace py = pybind11; + +// TODO: How to import like aare import StrixelPixelRemap + +void define_InclusiveROI(py::module &m) { + py::class_(m, "InclusiveROI") + .def(py::init(), py::arg("xmin"), py::arg("xmax"), + py::arg("ymin"), py::arg("ymax")) + .def_readwrite("xmin", &aare::InclusiveROI::xmin, + "minimum x coordinate (inclusive)") + .def_readwrite("xmax", &aare::InclusiveROI::xmax, + "maximum x coordinate (inclusive)") + .def_readwrite("ymin", &aare::InclusiveROI::ymin, + "minimum y coordinate (inclusive)") + .def_readwrite("ymax", &aare::InclusiveROI::ymax, + "maximum y coordinate (inclusive)") + + .def_property_readonly("width", &aare::InclusiveROI::width, + "width of the ROI") + + .def_property_readonly("height", &aare::InclusiveROI::height, + "height of the ROI") + + .def_property_readonly("size", &aare::InclusiveROI::size, + "number of pixels in the ROI") + + .def("is_empty", &aare::InclusiveROI::is_empty, + "check if the ROI is empty") + + .def( + "contains", + [](const aare::InclusiveROI &self, int x, int y) { + return self.contains(x, y); + }, + R"( + check if a point is contained in the ROI + + Parameters + ---------- + x : int + x coordinate of the point + y : int + y coordinate of the point + + Returns + ------- + bool + True if the point is contained in the ROI, False otherwise + )") + + .def( + "fits_in", + [](const aare::InclusiveROI &self, int ncols, int nrows) { + return self.fits_in(ncols, nrows); + }, + R"( + check if the ROI fits within a given number of columns and rows + Parameters + ---------- + ncols : int + number of columns + nrows : int + number of rows + Returns + ------- + bool + True if the ROI fits within the given dimensions, False otherwise + )") + + .def("__eq__", &aare::InclusiveROI::operator==, py::is_operator(), + "check if two InclusiveROI objects are equal") + + .def_static("emptyROI", &aare::InclusiveROI::emptyROI, + "create an empty InclusiveROI"); + + m.def("toInclusiveROI", &toInclusiveROI, py::arg("roi").noconvert(), + R"( + Convert a half-open ROI to an inclusive ROI + + Parameters + ---------- + roi : ROI + Half-open ROI to be converted + + Returns + ------- + InclusiveROI + Inclusive ROI with the same physical extent + )"); + + m.def("toHalfopenROI", &toHalfopenROI, py::arg("roi").noconvert(), + R"( + Convert an inclusive ROI to a half-open ROI + + Parameters + ---------- + roi : InclusiveROI + Inclusive ROI to be converted + + Returns + ------- + ROI + Half-open ROI with the same physical extent + )"); +} \ No newline at end of file diff --git a/python/src/StrixelRemap/bind_PredefinedVariables.hpp b/python/src/StrixelRemap/bind_PredefinedVariables.hpp new file mode 100644 index 00000000..a109b80f --- /dev/null +++ b/python/src/StrixelRemap/bind_PredefinedVariables.hpp @@ -0,0 +1,330 @@ +#include + +#include "aare/StrixelPixelRemapConfig.hpp" +#include "aare/StrixelPixelRemapGenerate.hpp" + +namespace py = pybind11; + +void define_predefinedConfigs(py::module &m) { + + // Predefined strixel geometries + m.attr("StrxP25") = aare::remap::config::jungfrau::StrxP25; + // Strixel geometry for 25 µm pitch strixels on iLGAD sensors (multiplicity + // = 3) + + m.attr("StrxP15") = aare::remap::config::jungfrau::StrxP15; + //"Strixel geometry for 15 µm pitch strixels on iLGAD sensors " + //"(multiplicity = 5)"; + m.attr("StrxP18") = aare::remap::config::jungfrau::StrxP18; + //"Strixel geometry for 18 µm pitch strixels on iLGAD sensors " + //"(multiplicity = 4)"; + m.attr("StrxP37") = aare::remap::config::jungfrau::StrxP37; + //"Strixel geometry for 37 µm pitch strixels on iLGAD sensors " + //"(multiplicity = 2)"; + + // Predefined sensor placements + m.attr("Chip1") = aare::remap::config::jungfrau::Chip1; + // Placement of the 2x2cm iLGAD sensor on the second chip (Chip1) of the + // Jungfrau module with no rotation applied. + + m.attr("Chip6") = aare::remap::config::jungfrau::Chip6; + //"Placement of the 2x2cm iLGAD sensor on the seventh chip (Chip6) of " + //"the Jungfrau module" + //"with a 180-degree rotation applied."; + m.attr("Quad") = aare::remap::config::jungfrau::Quad; + //"Placement of the 4x4cm iLGAD sensor on the quad " + //"(Chip1+Chip2+Chip5+Chip6) of the Jungfrau module" + //"with no rotation applied."; + + // Predefined sensor geometries + m.attr("SingleChipMP_iLGAD_pix") = + aare::remap::config::jungfrau::SingleChipMP_iLGAD_pix; + // "Pixel geometry of the 2x2 cm iLGAD sensor"; + m.attr("Quad_iLGAD_pix") = aare::remap::config::jungfrau::Quad_iLGAD_pix; + // "Pixel geometry of the 4x4 cm iLGAD sensor"; + m.attr("SingleChipMP_TEW_pix") = + aare::remap::config::jungfrau::SingleChipMP_TEW_pix; + // "Pixel geometry of the 2x2 cm TEW sensor"; + + // Predefined strixel groups + m.attr("SingleChipMP_iLGAD_P25") = + aare::remap::config::jungfrau::SingleChipMP_iLGAD_P25; + + // "Strixel group of 25 µm pitch strixels on the 2x2 cm iLGAD sensor"; + m.attr("SingleChipMP_iLGAD_P15") = + aare::remap::config::jungfrau::SingleChipMP_iLGAD_P15; + + // "Strixel group of 15 µm pitch strixels on the 2x2 cm iLGAD sensor"; + m.attr("SingleChipMP_iLGAD_P18") = + aare::remap::config::jungfrau::SingleChipMP_iLGAD_P18; + + // "Strixel group of 18.75 µm pitch strixels on the 2x2 cm iLGAD sensor"; + m.attr("SingleChipMP_TEW_P25") = + aare::remap::config::jungfrau::SingleChipMP_TEW_P25; + + // "Strixel group of 25 µm pitch strixels on the 2x2 cm TEW sensor"; + m.attr("SingleChipMP_TEW_P15") = + aare::remap::config::jungfrau::SingleChipMP_TEW_P15; + // "Strixel group of 15 µm pitch strixels on the 2x2 cm TEW sensor"; + m.attr("SingleChipMP_TEW_P18") = + aare::remap::config::jungfrau::SingleChipMP_TEW_P18; + // "Strixel group of 18.75 µm pitch strixels on the 2x2 cm TEW sensor"; + m.attr("Quad_iLGAD_bottomhalf") = + aare::remap::config::jungfrau::Quad_iLGAD_bottomhalf; + // "Strixel group of 25 µm pitch strixels located on the bottom half of " + // "4x4 cm iLGAD sensor"; + m.attr("Quad_iLGAD_tophalf") = + aare::remap::config::jungfrau::Quad_iLGAD_tophalf; + // "Strixel group of 25 µm pitch strixels located on the top half of 4x4 " + // "cm iLGAD sensor"; + + // Predefined sensor configurations + m.attr("SingleChipMP_iLGAD") = + aare::remap::config::jungfrau::SingleChipMP_iLGAD; + // "Sensor configuration of the 2x2 cm iLGAD sensor with all strixel + // groups"; + m.attr("SingleChipMP_TEW") = + aare::remap::config::jungfrau::SingleChipMP_TEW; + // "Sensor configuration of the 2x2 cm TEW sensor with all strixel groups"; + m.attr("Quad_iLGAD") = aare::remap::config::jungfrau::Quad_iLGAD; + // "Sensor configuration of the 4x4 cm iLGAD sensor with all strixel " + // "groups"; +} + +void define_predefinedStrixelPixelMaps(py::module &m) { + m.attr("jungfrau_ilgad_singlechip_25um_strixel_map") = py::cpp_function( + &aare::remap::generate::jungfrau_ilgad_singlechip_25um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a strixel-to-pixel remapping map for the Strx25 strixel group on a Jungfrau ILGAD sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + m.attr("jungfrau_ilgad_singlechip_15um_strixel_map") = py::cpp_function( + &aare::remap::generate::jungfrau_ilgad_singlechip_15um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a strixel-to-pixel remapping map for the Strx15 strixel group on a Jungfrau ILGAD sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + m.attr("jungfrau_ilgad_singlechip_18um_strixel_map") = py::cpp_function( + &aare::remap::generate::jungfrau_ilgad_singlechip_18um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a strixel-to-pixel remapping map for the Strx18 strixel group on a Jungfrau ILGAD sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + m.attr("jungfrau_ilgad_strixel_maps") = py::cpp_function( + &aare::remap::generate::jungfrau_ilgad_quad_25um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a list of strixel-to-pixel remapping map for each strixel group on a Jungfrau ILGAD quad sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + list[StrixelGroupToPixelMap] + A list of StrixelGroupToPixelMap, one for each strixel group on the sensor. + Each map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + // TEW sensor strixel maps + m.attr("jungfrau_tew_singlechip_25um_strixel_map") = py::cpp_function( + &aare::remap::generate::jungfrau_tew_singlechip_25um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a strixel-to-pixel remapping map for the Strx25 strixel group on a Jungfrau TEW sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + m.attr("jungfrau_tew_singlechip_15um_strixel_map") = py::cpp_function( + &aare::remap::generate::jungfrau_tew_singlechip_15um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a strixel-to-pixel remapping map for the Strx15 strixel group on a Jungfrau TEW sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + m.attr("jungfrau_tew_singlechip_18um_strixel_map") = py::cpp_function( + &aare::remap::generate::jungfrau_tew_singlechip_18um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a strixel-to-pixel remapping map for the Strx18 strixel group on a Jungfrau TEW sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + m.attr("jungfrau_tew_strixel_maps") = py::cpp_function( + &aare::remap::generate::jungfrau_tew_singlechip_multipitch_strixel_maps, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a list of strixel-to-pixel remapping map for each strixel group on a Jungfrau TEW sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + list[StrixelGroupToPixelMap] + A list of StrixelGroupToPixelMap, one for each strixel group on the sensor. + Each map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + // Jungfrau quad sensor strixel maps + m.attr("jungfrau_ilgad_quadbottom_25um_strixel_map") = py::cpp_function( + &aare::remap::generate::jungfrau_ilgad_quadbottom_25um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a strixel-to-pixel remapping map for the bottom half of the Strx25 strixel group on a Jungfrau ILGAD quad sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + m.attr("jungfrau_ilgad_quadtop_25um_strixel_map") = py::cpp_function( + &aare::remap::generate::jungfrau_ilgad_quadtop_25um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a strixel-to-pixel remapping map for the top half of the Strx25 strixel group on a Jungfrau ILGAD quad sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); + + m.attr("jungfrau_ilgad_quad_25um_strixel_map") = py::cpp_function( + &aare::remap::generate::jungfrau_ilgad_quad_25um_strixel_map, + py::arg("user_roi").noconvert(), py::arg("placement").noconvert(), + py::arg("bond_shift").noconvert() = aare::remap::defs::BondShift{0, 0}, + R"( + Generates a strixel-to-pixel remapping map for the entire Strx25 strixel group on a Jungfrau ILGAD quad sensor + Parameters + ---------- + user_roi : InclusiveROI + ROI in global module coordinate system. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + bond_shift : BondShift, optional + Bonding shift applied before the configured sensor placement rotation. Default is (0, 0). + Returns + ------- + StrixelGroupToPixelMap + combined maps of the bottom and top halves of the Strx25 strixel group on a Jungfrau ILGAD quad sensor. + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + )"); +} \ No newline at end of file diff --git a/python/src/StrixelRemap/bind_StrixelRemap.hpp b/python/src/StrixelRemap/bind_StrixelRemap.hpp new file mode 100644 index 00000000..4f12bce0 --- /dev/null +++ b/python/src/StrixelRemap/bind_StrixelRemap.hpp @@ -0,0 +1,259 @@ + +#include + +#include "aare/StrixelPixelRemapDefs.hpp" + +namespace py = pybind11; + +// TODO: How to import like aare import StrixelPixelRemap + +void define_PixelStrixelMapDefs(py::module &m) { + + py::enum_(m, "Rotation") + .value("Identity", aare::remap::defs::Rotation::Identity) + .value("Rotate180", aare::remap::defs::Rotation::Rotate180) + .export_values(); + + py::enum_(m, "ModuloOrdering") + .value("Forward", aare::remap::defs::ModuloOrdering::Forward) + .value("Reverse", aare::remap::defs::ModuloOrdering::Reverse) + .export_values(); + + py::class_(m, "Guardring") + .def(py::init(), py::arg("x"), py::arg("y")) + .def_readwrite("x", &aare::remap::defs::Guardring::x, + "ring width in pixels") + .def_readwrite("y", &aare::remap::defs::Guardring::y, + "ring height in pixels") + .def( + "__eq__", + [](const aare::remap::defs::Guardring &self, + const aare::remap::defs::Guardring &other) { + return self.x == other.x && self.y == other.y; + }, + py::is_operator()) + + .def("__repr__", [](const aare::remap::defs::Guardring &self) { + return fmt::format("Guardring(x={}, y={})", self.x, self.y); + }); + + py::class_(m, "BondShift") + .def(py::init(), py::arg("x"), py::arg("y")) + .def_readwrite("x", &aare::remap::defs::BondShift::x, + "bond shift in x direction (pixels)") + .def_readwrite("y", &aare::remap::defs::BondShift::y, + "bond shift in y direction (pixels)"); + + py::class_(m, "SensorPixelGeometry") + .def(py::init(), + py::arg("num_pix_x"), py::arg("num_pix_y"), + py::arg("guardring") = aare::remap::defs::Guardring{0, 0}) + .def_readwrite("num_pix_x", + &aare::remap::defs::SensorPixelGeometry::num_pix_x, + "number of pixels in x direction") + .def_readwrite("num_pix_y", + &aare::remap::defs::SensorPixelGeometry::num_pix_y, + "number of pixels in y direction") + .def_readwrite( + "guardring", &aare::remap::defs::SensorPixelGeometry::guardring, + "physical guardring around the sensor (default Guardring(0,0))"); + + py::class_(m, + "GroupStrixelGeometry") + .def(py::init(), py::arg("multiplicity"), + py::arg("pitch_um")) + .def_readwrite("multiplicity", + &aare::remap::defs::GroupStrixelGeometry::multiplicity, + "maximum number of pixels a strixel covers") + .def_readwrite("pitch_um", + &aare::remap::defs::GroupStrixelGeometry::pitch_um, + "effective minimal strixel pitch [µm]"); + + py::class_(m, "GroupRouting") + .def(py::init(), + py::arg("mod_order") = aare::remap::defs::ModuloOrdering::Forward) + .def_readwrite( + "mod_order", &aare::remap::defs::GroupRouting::mod_order, + "modulo ordering of pixels within each strixel multiplicity group " + "default(ModuloOrdering::Forward)"); + + py::class_(m, "GroupConfig") + .def(py::init(), + py::arg("strixel"), py::arg("routing"), + py::arg("placement_on_sensor")) + + .def(py::init([](const aare::remap::defs::GroupStrixelGeometry &strixel, + const aare::remap::defs::ModuloOrdering &mod_order, + const aare::InclusiveROI &placement_on_sensor) { + return aare::remap::defs::GroupConfig{ + strixel, {mod_order}, placement_on_sensor}; + }), + py::arg("strixel"), py::arg("routing"), + py::arg("placement_on_sensor")) + .def_readwrite("strixel", &aare::remap::defs::GroupConfig::strixel, + "strixel geometry of the group") + .def_readwrite("routing", &aare::remap::defs::GroupConfig::routing, + "pixel-to-strixel routing pattern") + .def_readwrite( + "placement_on_sensor", + &aare::remap::defs::GroupConfig::placement_on_sensor, + "placement of the strixel group on the sensor (sensor-local " + "coordinates)"); + + py::class_( + m, "SensorModulePlacement") + .def(py::init(), + py::arg("placement_on_module"), py::arg("rotation")) + .def_readwrite( + "placement_on_module", + &aare::remap::defs::SensorModulePlacement::placement_on_module, + "sensor bounds in module coordinates") + .def_readwrite( + "rotation", &aare::remap::defs::SensorModulePlacement::rotation, + "physical orientation of the mounted sensor-ASIC assembly with " + "respect to the module reference frame"); + + py::class_( + m, "StrixelGroupToPixelMap") + .def(py::init<>()) + .def_readonly("effective_roi", + &aare::remap::defs::StrixelGroupToPixelMap::effective_roi, + "effective pixel ROI covered by this map (InclusiveROI)") + + .def_property_readonly( + "map", + [](const aare::remap::defs::StrixelGroupToPixelMap &self) + -> py::array { + return py::array_t( + self.map.shape(), self.map.data(), + py::cast(&self, py::return_value_policy::reference)); + }); +} + +template void define_SensorConfig(py::module &m) { + const auto class_name = fmt::format("SensorConfig_{}PixelGroups", N); + py::class_>(m, class_name.c_str()) + .def(py::init>(), + py::arg("pixel"), py::arg("group_configs")) + .def_readwrite("pixel", &aare::remap::defs::SensorConfig::pixel, + "sensor pixel geometry (SensorPixelGeometry)") + .def_readwrite("group_configs", + &aare::remap::defs::SensorConfig::group_configs, + "[list] of strixel group configurations"); +} + +void define_RemapAlgorithm(py::module &m) { + + m.def("strixel_to_pixel_map", &aare::remap::algo::strixel_to_pixel_map, + py::arg("group_config").noconvert(), py::arg("pixel").noconvert(), + py::arg("placement").noconvert(), py::arg("user_roi").noconvert(), + py::arg("bond_shift").noconvert() = + aare::remap::defs::BondShift{0, 0}, + R"( + Creates a StrixeltoPixelMap for a specific GroupConfig + + Parameters + ---------- + group_config : GroupConfig + Configuration of the strixel group. + pixel : SensorPixelGeometry + Pixel geometry of the sensor. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + user_roi : InclusiveROI + User-defined region of interest. (in global module coordinates) + bond_shift : BondShift, optional + Shift applied to the bond positions. Default is (0, 0). + + Returns + ------- + StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + + )"); + + // cant use np.take or have to mask -1 indices + m.def( + "ApplyRemap", + [](py::array input, + py::array_t + order_map, + py::array output) { + if (!(input.flags() & py::array::c_style) || + !(output.flags() & py::array::c_style)) { + throw std::runtime_error("Arrays must be C-contiguous"); + } + + if (input.ndim() != 2 || output.ndim() != 2) { + throw std::runtime_error("Input and output arrays must be 2D"); + } + + auto i_dtype = input.dtype(); + auto o_dtype = output.dtype(); + + if (!input.dtype().is(output.dtype())) { + throw std::runtime_error( + "Input and output arrays must have the same dtype"); + } + + auto input_array = + py::array_t::ensure(input); + auto output_array = + py::array_t::ensure(output); + + aare::remap::algo::ApplyRemap( + make_view_2d(input_array), // TODO expecting uint16_t for now + make_view_2d(order_map), make_view_2d(output_array)); + }, + py::arg("input").noconvert(), py::arg("order_map").noconvert(), + py::arg("output").noconvert(), + R"( + Applies a given remapping rule to an input array. + + Parameters + ---------- + input : np.array + Original array + order_map : np.array[ssize_t, 2] + Rule for remapping + output : np.array + Remapped array + + )"); +} + +template +void define_RemapAlgorithmforSensorConfig(py::module &m) { + + m.def("strixel_to_pixel_maps", &aare::remap::algo::strixel_to_pixel_maps, + py::arg("sensor_config").noconvert(), + py::arg("placement").noconvert(), py::arg("user_roi").noconvert(), + py::arg("bond_shift").noconvert() = + aare::remap::defs::BondShift{0, 0}, + R"( + Creates a StrixeltoPixelMap for all GroupConfigs in a SensorConfig + + Parameters + ---------- + sensor_config : SensorConfig + Configuration of the sensor, including all configurations of the strixel groups. + placement : SensorModulePlacement + Placement and orientation of the sensor on the module. + user_roi : InclusiveROI + User-defined region of interest. (in global module coordinates) + bond_shift : BondShift, optional + Shift applied to the bond positions. Default is (0, 0). + + Returns + ------- + list of StrixelGroupToPixelMap + map(row, col) contains the flattened pixel index of the corresponding source pixel in the user-provided input ROI for strixel defined at (row, col). + An entry of -1 indicates that the corresponding strixel position has no valid source pixel. + + )"); +} diff --git a/python/src/module.cpp b/python/src/module.cpp index d53baf4f..e35f4f37 100644 --- a/python/src/module.cpp +++ b/python/src/module.cpp @@ -22,6 +22,10 @@ #include "bind_RawFile.hpp" #include "bind_calibration.hpp" +#include "StrixelRemap/bind_InclusiveROI.hpp" +#include "StrixelRemap/bind_PredefinedVariables.hpp" +#include "StrixelRemap/bind_StrixelRemap.hpp" + // TODO! migrate the other names #include "ctb_raw_file.hpp" #include "file.hpp" @@ -63,6 +67,10 @@ double, 'f' for float) define_ClusterFileSink(m, "Cluster" #N "x" #M #TYPE_CODE); \ define_ClusterCollector(m, "Cluster" #N "x" #M #TYPE_CODE); +#define DEFINE_BINDINGS_SENSORCONFIG(N) \ + define_SensorConfig(strixelremap); \ + define_RemapAlgorithmforSensorConfig(strixelremap); + PYBIND11_MODULE(_aare, m) { auto experimental = m.def_submodule( "experimental", "Experimental APIs that may change without notice"); @@ -182,4 +190,17 @@ PYBIND11_MODULE(_aare, m) { define_eta(m, "d"); define_eta(m, "i"); define_eta(m, "i16"); + + auto strixelremap = + m.def_submodule("strixelremap", "Strixel remapping utilities."); + + define_InclusiveROI(strixelremap); + define_PixelStrixelMapDefs(strixelremap); + define_RemapAlgorithm(strixelremap); + DEFINE_BINDINGS_SENSORCONFIG(1); + DEFINE_BINDINGS_SENSORCONFIG(2); + DEFINE_BINDINGS_SENSORCONFIG(4); + DEFINE_BINDINGS_SENSORCONFIG(3); + define_predefinedConfigs(strixelremap); + define_predefinedStrixelPixelMaps(strixelremap); } diff --git a/python/tests/test_StrixelPixelRemapAPI.py b/python/tests/test_StrixelPixelRemapAPI.py new file mode 100644 index 00000000..488daaba --- /dev/null +++ b/python/tests/test_StrixelPixelRemapAPI.py @@ -0,0 +1,88 @@ +import pytest + +import numpy as np + +from aare import strixelremap +from aare import ROI + +def test_roimappings(): + """ Test exclusive to inclusive roi mapping and vice versa """ + + roi = ROI(0, 10, 0, 5) + + inclusive_roi = strixelremap.toInclusiveROI(roi) + + assert inclusive_roi.xmin == 0 + assert inclusive_roi.xmax == 9 + assert inclusive_roi.ymin == 0 + assert inclusive_roi.ymax == 4 + + exclusive_roi = strixelremap.toHalfopenROI(inclusive_roi) + + assert exclusive_roi.xmin == 0 + assert exclusive_roi.xmax == 10 + assert exclusive_roi.ymin == 0 + assert exclusive_roi.ymax == 5 + +def test_emptyROI(): + """ Test creation of an empty ROI """ + + empty_roi = strixelremap.InclusiveROI.emptyROI() + + assert empty_roi.xmin == 0 + assert empty_roi.xmax == -1 + assert empty_roi.ymin == 0 + assert empty_roi.ymax == -1 + +def test_customSensorConfiguration(): + """ Test that a custom sensor configuration can be created and used to remap strixel pixels """ + + my_strixel_group = strixelremap.GroupStrixelGeometry(multiplicity=2, pitch_um=25.0) + + assert my_strixel_group.multiplicity == 2 + assert my_strixel_group.pitch_um == 25.0 + + my_sensor_geometry = strixelremap.SensorPixelGeometry(num_pix_x = 10, num_pix_y = 5) + + assert my_sensor_geometry.num_pix_x == 10 + assert my_sensor_geometry.num_pix_y == 5 + assert my_sensor_geometry.guardring == strixelremap.Guardring(0,0) + + my_group_config = strixelremap.GroupConfig(strixel = my_strixel_group, routing = strixelremap.ModuloOrdering.Forward,placement_on_sensor = strixelremap.InclusiveROI(0,9,0,4)) + + my_sensor_config = strixelremap.SensorConfig(sensor_geometry = my_sensor_geometry, group_configs = [my_group_config]) + + # rebase + user_roi = strixelremap.InclusiveROI(strixelremap.Chip1.placement_on_module.xmin + 0, strixelremap.Chip1.placement_on_module.xmin + 9, strixelremap.Chip1.placement_on_module.ymin + 0, strixelremap.Chip1.placement_on_module.ymin + 4) + + strixelpixelmap = strixelremap.strixel_to_pixel_maps(sensor_config = my_sensor_config, placement = strixelremap.Chip1, user_roi = user_roi) + + assert len(strixelpixelmap) == 1 + + assert strixelpixelmap[0].effective_roi == my_group_config.placement_on_sensor + + map = strixelpixelmap[0].map + assert map.shape == (10 ,5) + + assert np.array_equal(map, np.array([[0, 2, 4, 6, 8], [1, 3, 5, 7, 9], [10, 12, 14, 16, 18], [11, 13, 15, 17, 19], [20, 22, 24, 26, 28], [21, 23, 25, 27, 29], [30, 32, 34, 36, 38], [31, 33, 35, 37, 39], [40, 42, 44, 46, 48], [41, 43, 45, 47, 49]])) + +def test_predefinedRemap(): + """ Test predefined maps API """ + + user_roi = strixelremap.InclusiveROI(strixelremap.Chip1.placement_on_module.xmin + 5, strixelremap.Chip1.placement_on_module.xmin + 9, strixelremap.Chip1.placement_on_module.ymin + 5, strixelremap.Chip1.placement_on_module.ymin + 7) + strixelpixelmap = strixelremap.jungfrau_tew_singlechip_25um_strixel_map(user_roi = user_roi, placement = strixelremap.Chip1) + + print(strixelpixelmap.map) + + assert strixelpixelmap.map.shape == (9, 2) + + assert np.array_equal(strixelpixelmap.map, np.array([[-1, 2], [0, 3], [1, 4], [-1, 7], [5,8], [6,9], [-1,12], [10,13], [11,14]])) + + + + + +# test apply remap + + +# Documenation, Example \ No newline at end of file