mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-10-17 04:05:12 +02:00
Second part of splitting up dev/gap. Only the in-memory transform; no reader changes. - `GapPixels` and `ModuleGaps` configure 2 pixel gaps at the chip boundaries inside each Jungfrau or Eiger module and, only when module gaps are set, gaps between modules. Gap pixels get a fill value or, with `split_counts`, half of the neighbouring double size pixel. - `insert_gap_pixels` and `gapped_shape` apply the transform to an image in memory. Gap positions follow detector coordinates, so a ROI receives exactly the gaps that fall inside it. - Python bindings, tests and docs for the above. Chip and module sizes are fixed at 256 and 1024x512, so there is no detector type or quad argument: an Eiger quad is 512x512 and its only chip boundaries are at pixel 256. Reading raw files with gaps comes in the next PR.
131 lines
5.4 KiB
Python
131 lines
5.4 KiB
Python
# SPDX-License-Identifier: MPL-2.0
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from aare import GapPixels, ModuleGaps, ROI, gapped_shape, insert_gap_pixels
|
|
|
|
|
|
def reference(image, boundaries_x, boundaries_y, fill=0):
|
|
"""Insert gap columns and rows with numpy: boundaries map a source index to
|
|
the number of pixels inserted before it."""
|
|
cols = [index for index, count in boundaries_x for _ in range(count)]
|
|
rows = [index for index, count in boundaries_y for _ in range(count)]
|
|
result = np.insert(image, cols, fill, axis=1)
|
|
return np.insert(result, rows, fill, axis=0)
|
|
|
|
|
|
def test_configuration_defaults_and_equality():
|
|
gaps = GapPixels()
|
|
assert gaps.chip_gap == 2
|
|
assert gaps.module_gaps is None
|
|
assert gaps.fill_value == 0
|
|
assert gaps.split_counts is False
|
|
assert gaps.seed is None
|
|
assert gaps == GapPixels()
|
|
assert gaps != GapPixels(fill_value=1)
|
|
|
|
gaps = GapPixels(chip_gap=4, module_gaps=ModuleGaps(8, 36), fill_value=-1, split_counts=False, seed=5)
|
|
assert gaps.module_gaps == ModuleGaps(x=8, y=36)
|
|
assert gaps.seed == 5
|
|
assert "ModuleGaps(x=8, y=36)" in repr(gaps)
|
|
gaps.module_gaps = None
|
|
assert gaps.module_gaps is None
|
|
|
|
|
|
def test_gapped_shape():
|
|
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, 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)
|
|
assert (result == expected).all()
|
|
|
|
|
|
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)
|
|
expected = reference(image, [(256, 2), (512, 2), (768, 2)], [(256, 2), (768, 2)])
|
|
assert result.shape == (1028, 1030)
|
|
assert (result == expected).all()
|
|
|
|
|
|
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, 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()
|
|
|
|
|
|
def test_insert_gap_pixels_in_roi():
|
|
image = np.array([[11, 22]], dtype=np.uint16)
|
|
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, roi=ROI(256, 260, 0, 2))
|
|
assert (result == image).all()
|
|
|
|
|
|
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, 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
|
|
# is excluded because it is split again vertically
|
|
pair = np.stack([first[:255, 255], first[:255, 256]])
|
|
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), 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, roi=ROI(0, 401, 0, 400))
|
|
with pytest.raises(ValueError):
|
|
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))
|
|
with pytest.raises(ValueError):
|
|
insert_gap_pixels(image, GapPixels(split_counts=True, chip_gap=4))
|
|
with pytest.raises(ValueError):
|
|
insert_gap_pixels(image, GapPixels(fill_value=float("nan")))
|
|
with pytest.raises(ValueError):
|
|
insert_gap_pixels(image, GapPixels(fill_value=1e20))
|
|
with pytest.raises(ValueError):
|
|
insert_gap_pixels(image, GapPixels(chip_gap=6148914691236517205))
|
|
with pytest.raises(ValueError):
|
|
insert_gap_pixels(image, GapPixels(module_gaps=ModuleGaps(8, 2000)))
|
|
with pytest.raises(ValueError):
|
|
insert_gap_pixels(image, roi=ROI(2**40, 2**40 + 400, 0, 400))
|
|
|
|
|
|
def test_insert_gap_pixels_nan_fill_for_float_images():
|
|
image = np.ones((2, 2), dtype=np.float32)
|
|
result = insert_gap_pixels(image, GapPixels(fill_value=float("nan")), roi=ROI(255, 257, 0, 2))
|
|
assert result.shape == (2, 4)
|
|
assert np.isnan(result[:, 1:3]).all()
|
|
assert (result[:, [0, 3]] == 1).all()
|