mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-09-21 12:52:12 +02:00
- renamed pedestal.hpp to bind_Pedestal.hpp for python bindings - variance is now always calculated in double and made private - cost correctness in a few places - removed push_fast and explicit updates of mean from Pedestal. If performance is needed use FastPedestal - Pedestal no longer caches std
205 lines
7.1 KiB
Python
205 lines
7.1 KiB
Python
import numpy as np
|
|
import pytest
|
|
|
|
from aare import Pedestal, Pedestal_d, Pedestal_f, Pedestal_i16
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("dtype", "pedestal_type"),
|
|
[(np.float64, Pedestal_d), (np.float32, Pedestal_f), (np.int16, Pedestal_i16)],
|
|
)
|
|
def test_pedestal_factory(dtype, pedestal_type):
|
|
pedestal = Pedestal(rows=2, cols=3, n_samples=4, dtype=dtype)
|
|
|
|
assert isinstance(pedestal, pedestal_type)
|
|
assert pedestal.rows == 2
|
|
assert pedestal.cols == 3
|
|
assert pedestal.n_samples == 4
|
|
|
|
|
|
def test_pedestal_factory_defaults_to_double():
|
|
pedestal = Pedestal(2, 3)
|
|
|
|
assert isinstance(pedestal, Pedestal_d)
|
|
assert pedestal.n_samples == 1000
|
|
|
|
|
|
def test_pedestal_factory_rejects_unbound_dtype():
|
|
with pytest.raises(ValueError, match="Unsupported dtype for Pedestal"):
|
|
Pedestal(2, 3, dtype=np.int32)
|
|
|
|
|
|
@pytest.mark.parametrize("dtype", [np.float64, np.float32, np.int16])
|
|
@pytest.mark.parametrize("name", ["rows", "cols", "n_samples"])
|
|
def test_pedestal_factory_rejects_negative_parameters(dtype, name):
|
|
parameters = dict(rows=1, cols=1, n_samples=10)
|
|
parameters[name] = -1
|
|
with pytest.raises(TypeError):
|
|
Pedestal(**parameters, dtype=dtype)
|
|
|
|
|
|
@pytest.mark.parametrize("pedestal_type", [Pedestal_d, Pedestal_f, Pedestal_i16])
|
|
@pytest.mark.parametrize(
|
|
"args", [(-1, 1), (1, -1), (-1, 1, 10), (1, -1, 10), (1, 1, -1)]
|
|
)
|
|
def test_pedestal_constructor_rejects_negative_parameters(pedestal_type, args):
|
|
with pytest.raises(TypeError):
|
|
pedestal_type(*args)
|
|
|
|
|
|
@pytest.mark.parametrize("dtype", [np.float64, np.float32, np.int16])
|
|
def test_pedestal_std_stays_finite_after_settling(dtype):
|
|
pedestal = Pedestal(1, 1, n_samples=10, dtype=dtype)
|
|
pedestal.push(np.array([[16382]], dtype=np.uint16))
|
|
frame = np.array([[16383]], dtype=np.uint16)
|
|
for _ in range(201):
|
|
pedestal.push(frame)
|
|
|
|
noise = pedestal.std().item()
|
|
assert np.isfinite(noise)
|
|
assert 0 <= noise < 1e-3
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("pedestal_type", "expected_dtype"),
|
|
[(Pedestal_d, np.float64), (Pedestal_f, np.float32), (Pedestal_i16, np.int16)],
|
|
)
|
|
def test_double_precision_moments(pedestal_type, expected_dtype):
|
|
pedestal = pedestal_type(1, 1, 2)
|
|
for value in [30000, 30003, 30002]:
|
|
pedestal.push(np.array([[value]], dtype=np.uint16))
|
|
|
|
assert pedestal.mean().dtype == expected_dtype
|
|
assert pedestal.std().dtype == expected_dtype
|
|
np.testing.assert_array_equal(
|
|
pedestal.mean(), np.array([[30001.75]], dtype=expected_dtype)
|
|
)
|
|
np.testing.assert_allclose(
|
|
pedestal.std(),
|
|
np.array([[np.sqrt(1.1875)]], dtype=expected_dtype),
|
|
rtol=1e-6,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("dtype", [np.float64, np.float32, np.int16])
|
|
@pytest.mark.parametrize("method", ["push", "push_with_threshold"])
|
|
@pytest.mark.parametrize("shape", [(2, 2), (3, 2)])
|
|
def test_pedestal_rejects_mismatched_frame_shapes(dtype, method, shape):
|
|
pedestal = Pedestal(2, 3, dtype=dtype)
|
|
initial = np.full((2, 3), 7, dtype=np.uint16)
|
|
pedestal.push(initial)
|
|
frame = np.zeros(shape, dtype=np.uint16)
|
|
args = (
|
|
(np.full((2, 3), 10, dtype=dtype),)
|
|
if method == "push_with_threshold"
|
|
else ()
|
|
)
|
|
|
|
with pytest.raises(
|
|
RuntimeError, match="Frame shape does not match pedestal shape"
|
|
):
|
|
getattr(pedestal, method)(frame, *args)
|
|
|
|
np.testing.assert_array_equal(pedestal.mean(), initial)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("pedestal_type", "expected_dtype"),
|
|
[(Pedestal_d, np.float64), (Pedestal_f, np.float32)],
|
|
)
|
|
def test_numpy_array_minus_pedestal(pedestal_type, expected_dtype):
|
|
pedestal = pedestal_type(2, 3)
|
|
pedestal.push(np.array([[2, 4, 6], [8, 10, 12]], dtype=np.uint16))
|
|
array = np.array([[12, 14, 16], [18, 20, 22]], dtype=np.uint16)
|
|
|
|
result = array - pedestal
|
|
|
|
np.testing.assert_array_equal(
|
|
result, np.array([[10, 10, 10], [10, 10, 10]], dtype=expected_dtype)
|
|
)
|
|
assert result.dtype == expected_dtype
|
|
|
|
|
|
def test_numpy_array_minus_pedestal_rejects_incompatible_shape():
|
|
pedestal = Pedestal_d(2, 3)
|
|
array = np.zeros((2, 2), dtype=np.float64)
|
|
|
|
with pytest.raises(ValueError):
|
|
array - pedestal
|
|
|
|
|
|
def test_pedestal_exposes_mean_as_read_only_buffer():
|
|
pedestal = Pedestal_d(2, 3)
|
|
pedestal.push(np.array([[2, 4, 6], [8, 10, 12]], dtype=np.uint16))
|
|
|
|
mean = np.asarray(pedestal)
|
|
|
|
np.testing.assert_array_equal(mean, pedestal.view())
|
|
assert np.shares_memory(mean, pedestal.view())
|
|
assert not mean.flags.writeable
|
|
|
|
|
|
@pytest.mark.parametrize("dtype", [np.float64, np.float32, np.int16])
|
|
@pytest.mark.parametrize("method", ["push", "push_with_threshold"])
|
|
def test_pedestal_push_accepts_contiguous_frames(dtype, method):
|
|
pedestal = Pedestal(2, 3, dtype=dtype)
|
|
frame = np.arange(6, dtype=np.uint16).reshape(2, 3)
|
|
args = (
|
|
(np.full((2, 3), 10, dtype=dtype),)
|
|
if method == "push_with_threshold"
|
|
else ()
|
|
)
|
|
|
|
getattr(pedestal, method)(frame, *args)
|
|
|
|
np.testing.assert_array_equal(pedestal.mean(), frame)
|
|
|
|
|
|
@pytest.mark.parametrize("dtype", [np.float64, np.float32, np.int16])
|
|
@pytest.mark.parametrize("input_name", ["push_frame", "threshold_frame", "threshold"])
|
|
@pytest.mark.parametrize("layout", ["transpose", "slice", "reverse"])
|
|
def test_pedestal_rejects_noncontiguous_inputs(dtype, input_name, layout):
|
|
pedestal = Pedestal(2, 3, dtype=dtype)
|
|
frame = np.zeros((2, 3), dtype=np.uint16)
|
|
threshold = np.full((2, 3), 10, dtype=dtype)
|
|
input_dtype = dtype if input_name == "threshold" else np.uint16
|
|
if layout == "transpose":
|
|
invalid = np.ones((3, 2), dtype=input_dtype).T
|
|
elif layout == "slice":
|
|
invalid = np.ones((2, 6), dtype=input_dtype)[:, ::2]
|
|
else:
|
|
invalid = np.ones((4, 6), dtype=input_dtype)[:2, :3][:, ::-1]
|
|
assert not invalid.flags.c_contiguous
|
|
|
|
with pytest.raises(TypeError):
|
|
if input_name == "push_frame":
|
|
pedestal.push(invalid)
|
|
elif input_name == "threshold_frame":
|
|
pedestal.push_with_threshold(invalid, threshold)
|
|
else:
|
|
pedestal.push_with_threshold(frame, invalid)
|
|
|
|
np.testing.assert_array_equal(pedestal.mean(), np.zeros((2, 3)))
|
|
|
|
|
|
@pytest.mark.parametrize("dtype", [np.float64, np.float32, np.int16])
|
|
@pytest.mark.parametrize("input_name", ["push_frame", "threshold_frame", "threshold"])
|
|
@pytest.mark.parametrize("shape", [(), (6,), (2, 3, 2)])
|
|
def test_pedestal_rejects_inputs_with_wrong_ndim(dtype, input_name, shape):
|
|
pedestal = Pedestal(2, 3, dtype=dtype)
|
|
frame = np.ones((2, 3), dtype=np.uint16)
|
|
threshold = np.full((2, 3), 10, dtype=dtype)
|
|
input_dtype = dtype if input_name == "threshold" else np.uint16
|
|
invalid = np.ones(shape, dtype=input_dtype)
|
|
name = "Threshold" if input_name == "threshold" else "Frame"
|
|
|
|
with pytest.raises(ValueError, match=f"{name} must be 2-dimensional"):
|
|
if input_name == "push_frame":
|
|
pedestal.push(invalid)
|
|
elif input_name == "threshold_frame":
|
|
pedestal.push_with_threshold(invalid, threshold)
|
|
else:
|
|
pedestal.push_with_threshold(frame, invalid)
|
|
|
|
np.testing.assert_array_equal(pedestal.mean(), np.zeros((2, 3)))
|