Skip to content

Commit e603df2

Browse files
committed
TST: Add a fixture to create random PET data for tests
Add a fixture to create random PET data for tests. Adopt the fixture across PET tests.
1 parent 4de9785 commit e603df2

File tree

5 files changed

+124
-77
lines changed

5 files changed

+124
-77
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ env = "PYTHONHASHSEED=0"
223223
markers = [
224224
"random_gtab_data: Custom marker for random gtab data tests",
225225
"random_dwi_data: Custom marker for random dwi data tests",
226+
"random_pet_data: Custom marker for random pet data tests",
226227
"random_uniform_ndim_data: Custom marker for random multi-dimensional data tests",
227228
"random_uniform_spatial_data: Custom marker for random spatial data tests",
228229
]

test/conftest.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,31 @@ def setup_random_dwi_data(request, setup_random_gtab_data):
297297
gradients,
298298
b0_thres,
299299
)
300+
301+
302+
@pytest.fixture(autouse=True)
303+
def setup_random_pet_data(request):
304+
"""Automatically generate random PET data for tests."""
305+
marker = request.node.get_closest_marker("random_pet_data")
306+
307+
n_frames = 5
308+
vol_size = (4, 4, 4)
309+
midframe = np.arange(n_frames, dtype=np.float32) + 1
310+
total_duration = float(n_frames + 1)
311+
if marker:
312+
n_frames, vol_size, midframe, total_duration = marker.args
313+
314+
rng = request.node.rng
315+
316+
pet_dataobj, affine = _generate_random_uniform_spatial_data(
317+
request, (*vol_size, n_frames), 0.0, 1.0
318+
)
319+
brainmask_dataobj = rng.choice([True, False], size=vol_size).astype(np.uint8)
320+
321+
return (
322+
pet_dataobj,
323+
affine,
324+
brainmask_dataobj,
325+
midframe,
326+
total_duration,
327+
)

test/test_pet_data.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,27 @@
88
from nifreeze.data.pet import PET, from_nii
99

1010

11+
@pytest.fixture
12+
def random_dataset(setup_random_pet_data) -> PET:
13+
"""Create a PET dataset with random data for testing."""
14+
15+
(
16+
pet_dataobj,
17+
affine,
18+
brainmask_dataobj,
19+
midframe,
20+
total_duration,
21+
) = setup_random_pet_data
22+
23+
return PET(
24+
dataobj=pet_dataobj,
25+
affine=affine,
26+
brainmask=brainmask_dataobj,
27+
midframe=midframe,
28+
total_duration=total_duration,
29+
)
30+
31+
1132
@pytest.mark.random_uniform_spatial_data((2, 2, 2, 2), 0.0, 1.0)
1233
def test_from_nii_requires_frame_time(setup_random_uniform_spatial_data, tmp_path):
1334
data, affine = setup_random_uniform_spatial_data
@@ -19,37 +40,22 @@ def test_from_nii_requires_frame_time(setup_random_uniform_spatial_data, tmp_pat
1940
from_nii(fname)
2041

2142

22-
def _create_dataset():
23-
rng = np.random.default_rng(12345)
24-
data = rng.random((4, 4, 4, 5), dtype=np.float32)
25-
affine = np.eye(4, dtype=np.float32)
26-
mask = np.ones((4, 4, 4), dtype=bool)
27-
midframe = np.array([10, 20, 30, 40, 50], dtype=np.float32)
28-
return PET(
29-
dataobj=data,
30-
affine=affine,
31-
brainmask=mask,
32-
midframe=midframe,
33-
total_duration=60.0,
34-
)
35-
36-
37-
def test_pet_set_transform_updates_motion_affines():
38-
dataset = _create_dataset()
43+
@pytest.mark.random_pet_data(5, (4, 4, 4), (10.0, 20.0, 30.0, 40.0, 50.0), 60.0)
44+
def test_pet_set_transform_updates_motion_affines(random_dataset):
3945
idx = 2
40-
data_before = np.copy(dataset.dataobj[..., idx])
46+
data_before = np.copy(random_dataset.dataobj[..., idx])
4147

4248
affine = np.eye(4)
43-
dataset.set_transform(idx, affine)
49+
random_dataset.set_transform(idx, affine)
4450

45-
np.testing.assert_allclose(dataset.dataobj[..., idx], data_before)
46-
assert dataset.motion_affines is not None
47-
assert len(dataset.motion_affines) == len(dataset)
48-
assert isinstance(dataset.motion_affines[idx], Affine)
49-
np.testing.assert_array_equal(dataset.motion_affines[idx].matrix, affine)
51+
np.testing.assert_allclose(random_dataset.dataobj[..., idx], data_before)
52+
assert random_dataset.motion_affines is not None
53+
assert len(random_dataset.motion_affines) == len(random_dataset)
54+
assert isinstance(random_dataset.motion_affines[idx], Affine)
55+
np.testing.assert_array_equal(random_dataset.motion_affines[idx].matrix, affine)
5056

51-
vol, aff, time = dataset[idx]
52-
assert aff is dataset.motion_affines[idx]
57+
vol, aff, time = random_dataset[idx]
58+
assert aff is random_dataset.motion_affines[idx]
5359

5460

5561
@pytest.mark.random_uniform_spatial_data((2, 2, 2, 2), 0.0, 1.0)

test/test_pet_model.py

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,33 @@
55
from nifreeze.model.pet import PETModel
66

77

8-
def _create_dataset():
9-
rng = np.random.default_rng(12345)
10-
data = rng.random((4, 4, 4, 5), dtype=np.float32)
11-
affine = np.eye(4, dtype=np.float32)
12-
mask = np.ones((4, 4, 4), dtype=bool)
13-
midframe = np.array([10, 20, 30, 40, 50], dtype=np.float32)
8+
@pytest.fixture
9+
def random_dataset(setup_random_pet_data) -> PET:
10+
"""Create a PET dataset with random data for testing."""
11+
12+
(
13+
pet_dataobj,
14+
affine,
15+
brainmask_dataobj,
16+
midframe,
17+
total_duration,
18+
) = setup_random_pet_data
19+
1420
return PET(
15-
dataobj=data,
21+
dataobj=pet_dataobj,
1622
affine=affine,
17-
brainmask=mask,
23+
brainmask=brainmask_dataobj,
1824
midframe=midframe,
19-
total_duration=60.0,
25+
total_duration=total_duration,
2026
)
2127

2228

23-
def test_petmodel_fit_predict():
24-
dataset = _create_dataset()
29+
@pytest.mark.random_pet_data(5, (4, 4, 4), (10.0, 20.0, 30.0, 40.0, 50.0), 60.0)
30+
def test_petmodel_fit_predict(random_dataset):
2531
model = PETModel(
26-
dataset=dataset,
27-
timepoints=dataset.midframe,
28-
xlim=dataset.total_duration,
32+
dataset=random_dataset,
33+
timepoints=random_dataset.midframe,
34+
xlim=random_dataset.total_duration,
2935
smooth_fwhm=0,
3036
thresh_pct=0,
3137
)
@@ -35,19 +41,19 @@ def test_petmodel_fit_predict():
3541
assert model.is_fitted
3642

3743
# Predict at a specific timepoint
38-
vol = model.fit_predict(dataset.midframe[2])
39-
assert vol.shape == dataset.shape3d
40-
assert vol.dtype == dataset.dataobj.dtype
44+
vol = model.fit_predict(random_dataset.midframe[2])
45+
assert vol.shape == random_dataset.shape3d
46+
assert vol.dtype == random_dataset.dataobj.dtype
4147

4248

43-
def test_petmodel_invalid_init():
44-
dataset = _create_dataset()
49+
@pytest.mark.random_pet_data(5, (4, 4, 4), (10.0, 20.0, 30.0, 40.0, 50.0), 60.0)
50+
def test_petmodel_invalid_init(random_dataset):
4551
with pytest.raises(TypeError):
46-
PETModel(dataset=dataset)
52+
PETModel(dataset=random_dataset)
4753

4854

49-
def test_petmodel_time_check():
50-
dataset = _create_dataset()
55+
@pytest.mark.random_pet_data(5, (4, 4, 4), (10.0, 20.0, 30.0, 40.0, 50.0), 60.0)
56+
def test_petmodel_time_check(random_dataset):
5157
bad_times = np.array([0, 10, 20, 30, 50], dtype=np.float32)
5258
with pytest.raises(ValueError):
53-
PETModel(dataset=dataset, timepoints=bad_times, xlim=60.0)
59+
PETModel(dataset=random_dataset, timepoints=bad_times, xlim=60.0)

test/test_pet_workflow.py

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,57 @@
11
import types
22

33
import numpy as np
4+
import pytest
45

56
from nifreeze.data.pet import PET
67
from nifreeze.estimator import PETMotionEstimator
78

89

9-
def _pet_dataset(n_frames=3):
10-
rng = np.random.default_rng(42)
11-
data = rng.random((2, 2, 2, n_frames), dtype=np.float32)
12-
affine = np.eye(4, dtype=np.float32)
13-
mask = np.ones((2, 2, 2), dtype=bool)
14-
midframe = np.arange(n_frames, dtype=np.float32) + 1
10+
@pytest.fixture
11+
def random_dataset(setup_random_pet_data) -> PET:
12+
"""Create a PET dataset with random data for testing."""
13+
14+
(
15+
pet_dataobj,
16+
affine,
17+
brainmask_dataobj,
18+
midframe,
19+
total_duration,
20+
) = setup_random_pet_data
21+
1522
return PET(
16-
dataobj=data,
23+
dataobj=pet_dataobj,
1724
affine=affine,
18-
brainmask=mask,
25+
brainmask=brainmask_dataobj,
1926
midframe=midframe,
20-
total_duration=float(n_frames + 1),
27+
total_duration=total_duration,
2128
)
2229

2330

24-
def test_lofo_split_shapes(tmp_path):
25-
ds = _pet_dataset(4)
31+
@pytest.mark.random_pet_data(4, (2, 2, 2), (1.0, 2.0, 3.0, 4.0), 5.0)
32+
def test_lofo_split_shapes(random_dataset, tmp_path):
2633
idx = 2
27-
(train_data, train_times), (test_data, test_time) = ds.lofo_split(idx)
28-
assert train_data.shape[-1] == ds.dataobj.shape[-1] - 1
29-
np.testing.assert_array_equal(test_data, ds.dataobj[..., idx])
30-
np.testing.assert_array_equal(train_times, np.delete(ds.midframe, idx))
31-
assert test_time == ds.midframe[idx]
34+
(train_data, train_times), (test_data, test_time) = random_dataset.lofo_split(idx)
35+
assert train_data.shape[-1] == random_dataset.dataobj.shape[-1] - 1
36+
np.testing.assert_array_equal(test_data, random_dataset.dataobj[..., idx])
37+
np.testing.assert_array_equal(train_times, np.delete(random_dataset.midframe, idx))
38+
assert test_time == random_dataset.midframe[idx]
3239

3340

34-
def test_to_from_filename_roundtrip(tmp_path):
35-
ds = _pet_dataset(3)
41+
@pytest.mark.random_pet_data(3, (2, 2, 2), (1.0, 2.0, 3.0), 4.0)
42+
def test_to_from_filename_roundtrip(random_dataset, tmp_path):
3643
out_file = tmp_path / "petdata"
37-
ds.to_filename(out_file)
44+
random_dataset.to_filename(out_file)
3845
assert (tmp_path / "petdata.h5").exists()
3946
loaded = PET.from_filename(tmp_path / "petdata.h5")
40-
np.testing.assert_allclose(loaded.dataobj, ds.dataobj)
41-
np.testing.assert_allclose(loaded.affine, ds.affine)
42-
np.testing.assert_allclose(loaded.midframe, ds.midframe)
43-
assert loaded.total_duration == ds.total_duration
44-
47+
np.testing.assert_allclose(loaded.dataobj, random_dataset.dataobj)
48+
np.testing.assert_allclose(loaded.affine, random_dataset.affine)
49+
np.testing.assert_allclose(loaded.midframe, random_dataset.midframe)
50+
assert loaded.total_duration == random_dataset.total_duration
4551

46-
def test_pet_motion_estimator_run(monkeypatch):
47-
ds = _pet_dataset(3)
4852

53+
@pytest.mark.random_pet_data(5, (4, 4, 4), (10.0, 20.0, 30.0, 40.0, 50.0), 60.0)
54+
def test_pet_motion_estimator_run(random_dataset, monkeypatch):
4955
class DummyModel:
5056
def __init__(self, _dataset):
5157
self._dataset = _dataset
@@ -67,7 +73,7 @@ def run(self, cwd=None):
6773
monkeypatch.setattr("nifreeze.estimator.Registration", DummyRegistration)
6874

6975
estimator = PETMotionEstimator(None)
70-
affines = estimator.run(ds)
71-
assert len(affines) == len(ds)
76+
affines = estimator.run(random_dataset)
77+
assert len(affines) == len(random_dataset)
7278
for mat in affines:
7379
np.testing.assert_array_equal(mat, np.eye(4))

0 commit comments

Comments
 (0)