mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-11-03 05:03:46 +01:00
Drop detector type and quad flag from the gap pixel API
insert_gap_pixels and gapped_shape only used the detector type to reject anything but Jungfrau and Eiger, and the quad flag only to shrink the module width to 512. Both Jungfrau and Eiger use 256x256 pixel chips in 1024x512 modules, and an Eiger quad is 512x512 pixels, so its only chip boundaries are at pixel 256 and the module width never matters. The layout is now fixed and both functions take just the image, its ROI and the GapPixels configuration, in C++ and in Python. RawFile keeps rejecting gap pixels for other detector types with std::invalid_argument, now checked in the reader instead of the layout.
This commit is contained in:
1 parent
f9216897fd
commit
34f0d9f679
11 files changed
+157
-230
No files matched your search
@@ -19,12 +19,10 @@ namespace {
|
||||
|
||||
template <typename T>
|
||||
py::array insert_gap_pixels_typed(const py::array &image, const ROI &roi,
|
||||
DetectorType detector, bool quad,
|
||||
const GapPixels &gaps) {
|
||||
py::array_t<T, py::array::c_style | py::array::forcecast> typed(image);
|
||||
auto source = make_const_view_2d(typed);
|
||||
auto *result = new NDArray<T, 2>(
|
||||
insert_gap_pixels<T>(source, roi, detector, quad, gaps));
|
||||
auto *result = new NDArray<T, 2>(insert_gap_pixels<T>(source, roi, gaps));
|
||||
return return_image_data(result);
|
||||
}
|
||||
|
||||
@@ -46,7 +44,8 @@ void define_gap_pixels_bindings(py::module &m) {
|
||||
|
||||
py::class_<GapPixels>(m, "GapPixels", R"(
|
||||
Configuration for inserting gap pixels into Jungfrau and Eiger
|
||||
images. Chip and module sizes are fixed by the detector type.
|
||||
images. Chip and module sizes are fixed: 256x256 pixel chips in
|
||||
1024x512 pixel modules.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -103,21 +102,25 @@ void define_gap_pixels_bindings(py::module &m) {
|
||||
|
||||
m.def(
|
||||
"gapped_shape",
|
||||
[](const ROI &roi, DetectorType detector_type, const GapPixels &gaps,
|
||||
bool quad) {
|
||||
const auto shape = gapped_shape(roi, detector_type, quad, gaps);
|
||||
[](const ROI &roi, const GapPixels &gaps) {
|
||||
const auto shape = gapped_shape(roi, gaps);
|
||||
return py::make_tuple(shape[0], shape[1]);
|
||||
},
|
||||
py::arg("roi"), py::arg("detector_type"), py::arg("gaps") = GapPixels{},
|
||||
py::arg("quad") = false, R"(
|
||||
py::arg("roi"), py::arg("gaps") = GapPixels{}, R"(
|
||||
Shape (rows, cols) of the gapped image of a detector coordinate ROI.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
roi : ROI
|
||||
Detector coordinates of the image.
|
||||
gaps : GapPixels
|
||||
Gap configuration.
|
||||
)");
|
||||
|
||||
m.def(
|
||||
"insert_gap_pixels",
|
||||
[](const py::array &image, DetectorType detector_type,
|
||||
const GapPixels &gaps, std::optional<ROI> roi,
|
||||
bool quad) -> py::array {
|
||||
[](const py::array &image, const GapPixels &gaps,
|
||||
std::optional<ROI> roi) -> py::array {
|
||||
if (image.ndim() != 2) {
|
||||
throw py::value_error(
|
||||
fmt::format("Expected a 2D image, got {}D", image.ndim()));
|
||||
@@ -125,51 +128,40 @@ void define_gap_pixels_bindings(py::module &m) {
|
||||
const ROI rect =
|
||||
roi.value_or(ROI{0, image.shape(1), 0, image.shape(0)});
|
||||
if (py::isinstance<py::array_t<uint8_t>>(image)) {
|
||||
return insert_gap_pixels_typed<uint8_t>(
|
||||
image, rect, detector_type, quad, gaps);
|
||||
return insert_gap_pixels_typed<uint8_t>(image, rect, gaps);
|
||||
}
|
||||
if (py::isinstance<py::array_t<uint16_t>>(image)) {
|
||||
return insert_gap_pixels_typed<uint16_t>(
|
||||
image, rect, detector_type, quad, gaps);
|
||||
return insert_gap_pixels_typed<uint16_t>(image, rect, gaps);
|
||||
}
|
||||
if (py::isinstance<py::array_t<uint32_t>>(image)) {
|
||||
return insert_gap_pixels_typed<uint32_t>(
|
||||
image, rect, detector_type, quad, gaps);
|
||||
return insert_gap_pixels_typed<uint32_t>(image, rect, gaps);
|
||||
}
|
||||
if (py::isinstance<py::array_t<int32_t>>(image)) {
|
||||
return insert_gap_pixels_typed<int32_t>(
|
||||
image, rect, detector_type, quad, gaps);
|
||||
return insert_gap_pixels_typed<int32_t>(image, rect, gaps);
|
||||
}
|
||||
if (py::isinstance<py::array_t<float>>(image)) {
|
||||
return insert_gap_pixels_typed<float>(
|
||||
image, rect, detector_type, quad, gaps);
|
||||
return insert_gap_pixels_typed<float>(image, rect, gaps);
|
||||
}
|
||||
if (py::isinstance<py::array_t<double>>(image)) {
|
||||
return insert_gap_pixels_typed<double>(
|
||||
image, rect, detector_type, quad, gaps);
|
||||
return insert_gap_pixels_typed<double>(image, rect, gaps);
|
||||
}
|
||||
throw py::type_error(
|
||||
"Unsupported dtype, expected uint8, uint16, uint32, int32, "
|
||||
"float32 or float64");
|
||||
},
|
||||
py::arg("image"), py::arg("detector_type"),
|
||||
py::arg("gaps") = GapPixels{}, py::arg("roi") = py::none(),
|
||||
py::arg("quad") = false, R"(
|
||||
py::arg("image"), py::arg("gaps") = GapPixels{},
|
||||
py::arg("roi") = py::none(), R"(
|
||||
Insert gap pixels into a 2D image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : numpy.ndarray
|
||||
Ungapped image of ``roi``.
|
||||
detector_type : DetectorType
|
||||
Jungfrau or Eiger.
|
||||
gaps : GapPixels
|
||||
Gap configuration.
|
||||
roi : ROI, optional
|
||||
Detector coordinates of the image. Defaults to the full image
|
||||
starting at pixel (0, 0).
|
||||
quad : bool
|
||||
True for an Eiger quad.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from aare import DetectorType, GapPixels, ModuleGaps, ROI, gapped_shape, insert_gap_pixels
|
||||
from aare import GapPixels, ModuleGaps, ROI, gapped_shape, insert_gap_pixels
|
||||
|
||||
|
||||
def reference(image, boundaries_x, boundaries_y, fill=0):
|
||||
@@ -33,21 +33,19 @@ def test_configuration_defaults_and_equality():
|
||||
|
||||
|
||||
def test_gapped_shape():
|
||||
assert gapped_shape(ROI(0, 1024, 0, 512), DetectorType.Jungfrau) == (514, 1030)
|
||||
assert gapped_shape(ROI(0, 1024, 0, 1024), DetectorType.Jungfrau) == (1028, 1030)
|
||||
assert gapped_shape(ROI(0, 1024, 0, 1024), DetectorType.Jungfrau, GapPixels(module_gaps=ModuleGaps(8, 36))) == (1064, 1030)
|
||||
assert gapped_shape(ROI(0, 512, 0, 512), DetectorType.Eiger, quad=True) == (514, 514)
|
||||
assert gapped_shape(ROI(256, 512, 0, 256), DetectorType.Jungfrau) == (256, 256)
|
||||
assert gapped_shape(ROI(255, 257, 0, 1), DetectorType.Jungfrau) == (1, 4)
|
||||
with pytest.raises(ValueError):
|
||||
gapped_shape(ROI(0, 400, 0, 400), DetectorType.Moench)
|
||||
assert gapped_shape(ROI(0, 1024, 0, 512)) == (514, 1030)
|
||||
assert gapped_shape(ROI(0, 1024, 0, 1024)) == (1028, 1030)
|
||||
assert gapped_shape(ROI(0, 1024, 0, 1024), GapPixels(module_gaps=ModuleGaps(8, 36))) == (1064, 1030)
|
||||
assert gapped_shape(ROI(0, 512, 0, 512)) == (514, 514) # Eiger quad
|
||||
assert gapped_shape(ROI(256, 512, 0, 256)) == (256, 256)
|
||||
assert gapped_shape(ROI(255, 257, 0, 1)) == (1, 4)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [np.uint8, np.uint16, np.uint32, np.int32, np.float32, np.float64])
|
||||
def test_insert_gap_pixels_matches_numpy(dtype):
|
||||
rng = np.random.default_rng(1)
|
||||
image = rng.integers(0, 200, size=(512, 1024)).astype(dtype)
|
||||
result = insert_gap_pixels(image, DetectorType.Jungfrau, GapPixels(fill_value=3))
|
||||
result = insert_gap_pixels(image, GapPixels(fill_value=3))
|
||||
expected = reference(image, [(256, 2), (512, 2), (768, 2)], [(256, 2)], fill=3)
|
||||
assert result.dtype == image.dtype
|
||||
assert result.shape == (514, 1030)
|
||||
@@ -58,7 +56,7 @@ def test_insert_gap_pixels_without_module_gaps():
|
||||
# two stacked modules: chip gaps at rows 256 and 768, none at the module
|
||||
# boundary at row 512
|
||||
image = np.arange(1024 * 1024, dtype=np.uint32).reshape(1024, 1024)
|
||||
result = insert_gap_pixels(image, DetectorType.Jungfrau)
|
||||
result = insert_gap_pixels(image)
|
||||
expected = reference(image, [(256, 2), (512, 2), (768, 2)], [(256, 2), (768, 2)])
|
||||
assert result.shape == (1028, 1030)
|
||||
assert (result == expected).all()
|
||||
@@ -67,7 +65,7 @@ def test_insert_gap_pixels_without_module_gaps():
|
||||
def test_insert_gap_pixels_with_module_gaps():
|
||||
image = np.arange(1024 * 1024, dtype=np.uint32).reshape(1024, 1024)
|
||||
gaps = GapPixels(module_gaps=ModuleGaps(8, 36), fill_value=9)
|
||||
result = insert_gap_pixels(image, DetectorType.Jungfrau, gaps)
|
||||
result = insert_gap_pixels(image, gaps)
|
||||
expected = reference(image, [(256, 2), (512, 2), (768, 2)], [(256, 2), (512, 36), (768, 2)], fill=9)
|
||||
assert result.shape == (1064, 1030)
|
||||
assert (result == expected).all()
|
||||
@@ -75,12 +73,12 @@ def test_insert_gap_pixels_with_module_gaps():
|
||||
|
||||
def test_insert_gap_pixels_in_roi():
|
||||
image = np.array([[11, 22]], dtype=np.uint16)
|
||||
result = insert_gap_pixels(image, DetectorType.Jungfrau, roi=ROI(255, 257, 10, 11))
|
||||
result = insert_gap_pixels(image, roi=ROI(255, 257, 10, 11))
|
||||
assert result.tolist() == [[11, 0, 0, 22]]
|
||||
|
||||
# a gap on the ROI border is not inserted
|
||||
image = np.arange(8, dtype=np.uint16).reshape(2, 4)
|
||||
result = insert_gap_pixels(image, DetectorType.Jungfrau, roi=ROI(256, 260, 0, 2))
|
||||
result = insert_gap_pixels(image, roi=ROI(256, 260, 0, 2))
|
||||
assert (result == image).all()
|
||||
|
||||
|
||||
@@ -88,8 +86,8 @@ def test_insert_gap_pixels_split_counts():
|
||||
rng = np.random.default_rng(2)
|
||||
image = rng.integers(0, 1000, size=(512, 1024)).astype(np.uint32)
|
||||
gaps = GapPixels(split_counts=True, seed=3)
|
||||
first = insert_gap_pixels(image, DetectorType.Jungfrau, gaps)
|
||||
second = insert_gap_pixels(image, DetectorType.Jungfrau, gaps)
|
||||
first = insert_gap_pixels(image, gaps)
|
||||
second = insert_gap_pixels(image, gaps)
|
||||
assert first.sum() == image.sum()
|
||||
assert (first == second).all()
|
||||
# edge pixel and its gap pixel hold floor and ceil of the half; row 255
|
||||
@@ -98,19 +96,17 @@ def test_insert_gap_pixels_split_counts():
|
||||
assert (pair.sum(axis=0) == image[:255, 255]).all()
|
||||
assert (pair.max(axis=0) == (image[:255, 255] + 1) // 2).all()
|
||||
|
||||
halves = insert_gap_pixels(np.full((2, 2), 3.0), DetectorType.Jungfrau, gaps, roi=ROI(255, 257, 255, 257))
|
||||
halves = insert_gap_pixels(np.full((2, 2), 3.0), gaps, roi=ROI(255, 257, 255, 257))
|
||||
assert (halves == 0.75).all()
|
||||
|
||||
|
||||
def test_insert_gap_pixels_rejects_bad_input():
|
||||
image = np.zeros((400, 400), dtype=np.uint16)
|
||||
with pytest.raises(ValueError):
|
||||
insert_gap_pixels(image, DetectorType.Moench)
|
||||
insert_gap_pixels(image, roi=ROI(0, 401, 0, 400))
|
||||
with pytest.raises(ValueError):
|
||||
insert_gap_pixels(image, DetectorType.Jungfrau, roi=ROI(0, 401, 0, 400))
|
||||
with pytest.raises(ValueError):
|
||||
insert_gap_pixels(np.zeros((2, 2, 2), dtype=np.uint16), DetectorType.Jungfrau)
|
||||
insert_gap_pixels(np.zeros((2, 2, 2), dtype=np.uint16))
|
||||
with pytest.raises(TypeError):
|
||||
insert_gap_pixels(np.zeros((2, 2), dtype=np.int64), DetectorType.Jungfrau)
|
||||
insert_gap_pixels(np.zeros((2, 2), dtype=np.int64))
|
||||
with pytest.raises(ValueError):
|
||||
insert_gap_pixels(image, DetectorType.Jungfrau, GapPixels(split_counts=True, chip_gap=4))
|
||||
insert_gap_pixels(image, GapPixels(split_counts=True, chip_gap=4))
|
||||
@@ -158,7 +158,7 @@ def test_gap_pixels_on_raw_files(tmp_path):
|
||||
assert (reader.rows, reader.cols) == (514, 1030)
|
||||
frames = reader.read_all()
|
||||
assert frames.shape == (4, 514, 1030)
|
||||
assert (frames == insert_gap_pixels(expected, aare.DetectorType.Jungfrau, gaps)).all()
|
||||
assert (frames == insert_gap_pixels(expected, gaps)).all()
|
||||
|
||||
reader = MultiThreadedFileReader(master_path, n_threads=2, chunk_size=1, gap_pixels=True)
|
||||
assert reader.gap_pixels == GapPixels()
|
||||
|
||||
@@ -532,7 +532,7 @@ def test_raw_file_gap_pixels(tmp_path, layout):
|
||||
assert (reader.rows(), reader.cols()) == ((512 + 2) * layout_rows, (1024 + 6) * layout_cols)
|
||||
header, image = reader.read_frame()
|
||||
assert header.size == layout_rows * layout_cols
|
||||
assert (image == insert_gap_pixels(expected, DetectorType.Jungfrau, gaps)).all()
|
||||
assert (image == insert_gap_pixels(expected, gaps)).all()
|
||||
reader.seek(0)
|
||||
_, images = reader.read_n(2)
|
||||
assert images.shape == (2, reader.rows(), reader.cols())
|
||||
@@ -545,15 +545,15 @@ def test_raw_file_gap_pixels(tmp_path, layout):
|
||||
with RawFile(master_path, gap_pixels=True) as reader:
|
||||
assert reader.gap_pixels == GapPixels()
|
||||
_, image = reader.read_frame()
|
||||
assert (image == insert_gap_pixels(expected, DetectorType.Jungfrau)).all()
|
||||
assert (image == insert_gap_pixels(expected)).all()
|
||||
|
||||
with File(master_path, gap_pixels=True) as reader:
|
||||
assert (reader.rows, reader.cols) == insert_gap_pixels(expected, DetectorType.Jungfrau).shape
|
||||
assert (reader.read_frame() == insert_gap_pixels(expected, DetectorType.Jungfrau)).all()
|
||||
assert (reader.rows, reader.cols) == insert_gap_pixels(expected).shape
|
||||
assert (reader.read_frame() == insert_gap_pixels(expected)).all()
|
||||
|
||||
module_gaps = GapPixels(module_gaps=ModuleGaps(8, 36))
|
||||
with File(master_path, gap_pixels=module_gaps) as reader:
|
||||
assert (reader.read_frame() == insert_gap_pixels(expected, DetectorType.Jungfrau, module_gaps)).all()
|
||||
assert (reader.read_frame() == insert_gap_pixels(expected, module_gaps)).all()
|
||||
|
||||
|
||||
def test_raw_file_gap_pixels_split_counts(tmp_path):
|
||||
@@ -561,7 +561,7 @@ def test_raw_file_gap_pixels_split_counts(tmp_path):
|
||||
gaps = GapPixels(split_counts=True, seed=11)
|
||||
with RawFile(master_path, gap_pixels=gaps) as reader:
|
||||
_, image = reader.read_frame()
|
||||
assert (image == insert_gap_pixels(expected, DetectorType.Jungfrau, gaps)).all()
|
||||
assert (image == insert_gap_pixels(expected, gaps)).all()
|
||||
assert image.sum() == expected.sum()
|
||||
|
||||
|
||||
@@ -583,7 +583,7 @@ def test_raw_file_gap_pixels_recorded_jungfrau(test_data_path):
|
||||
_, image = gapped.read_frame()
|
||||
_, reference = plain.read_frame()
|
||||
assert image.shape == (514, 1030)
|
||||
assert (image == insert_gap_pixels(reference, DetectorType.Jungfrau, gaps)).all()
|
||||
assert (image == insert_gap_pixels(reference, gaps)).all()
|
||||
|
||||
|
||||
@pytest.mark.withdata
|
||||
@@ -597,7 +597,7 @@ def test_raw_file_gap_pixels_recorded_rois(test_data_path):
|
||||
assert images[0].shape == (303, 103)
|
||||
assert images[1].shape == (101, 103)
|
||||
for image, reference, roi in zip(images, references, rois):
|
||||
assert (image == insert_gap_pixels(reference, DetectorType.Jungfrau, roi=roi)).all()
|
||||
assert (image == insert_gap_pixels(reference, roi=roi)).all()
|
||||
gapped.seek(0)
|
||||
_, frames = gapped.read_n_with_roi(2, roi_index=1)
|
||||
assert frames.shape == (2, 101, 103)
|
||||
Reference in new issue
Block a user