Skip to content

Commit ffb449f

Browse files
authored
Merge pull request #942 from EmmaRenauld/volumes_test
Volumes test
2 parents 2dab11c + 8d3f34d commit ffb449f

14 files changed

+622
-389
lines changed

scilpy/image/tests/test_volume_math.py

Lines changed: 121 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
# -*- coding: utf-8 -*-
2-
3-
42
import nibabel as nib
53
import numpy as np
64
from numpy.testing import (assert_array_equal,
@@ -11,12 +9,11 @@
119
gaussian_filter)
1210

1311

14-
from scilpy.image.volume_math import (_validate_imgs,
15-
_validate_imgs_concat,
12+
from scilpy.image.volume_math import (_validate_imgs_type,
1613
_validate_length,
1714
_validate_type,
1815
_validate_float,
19-
cut_up_cube,
16+
_get_neighbors,
2017
lower_threshold_eq, upper_threshold_eq,
2118
lower_threshold, upper_threshold,
2219
lower_threshold_otsu,
@@ -34,194 +31,72 @@
3431
difference, invert,
3532
concatenate, gaussian_blur,
3633
dilation, erosion,
37-
closing, opening)
34+
closing, opening,
35+
neighborhood_correlation)
3836

3937

4038
EPSILON = np.finfo(float).eps
4139

4240

43-
def test_validate_imgs_matching():
44-
data1 = np.zeros((5, 5, 5)).astype(float)
45-
affine1 = np.eye(4)
46-
img1 = nib.Nifti1Image(data1, affine1)
47-
48-
data2 = np.zeros((5, 5, 5)).astype(float)
49-
affine2 = np.eye(4)
50-
img2 = nib.Nifti1Image(data2, affine2)
51-
52-
try:
53-
_validate_imgs(img1, img2)
54-
print("Test with matching shapes passed.")
55-
except ValueError as e:
56-
print("Test with matching shapes failed:", str(e))
57-
58-
59-
def test_validate_imgs_different():
60-
data1 = np.zeros((5, 5, 5)).astype(float)
61-
affine1 = np.eye(4)
62-
img1 = nib.Nifti1Image(data1, affine1)
63-
64-
data2 = np.zeros((6, 6, 6)).astype(float)
65-
affine2 = np.eye(4)
66-
img2 = nib.Nifti1Image(data2, affine2)
67-
41+
def _assert_failed(function, *args):
42+
"""
43+
Assert that a call fails
44+
"""
45+
failed = False
6846
try:
69-
_validate_imgs(img1, img2)
70-
print("Test with different shapes passed.")
71-
except ValueError as e:
72-
print("Test with different shapes failed:", str(e))
47+
function(*args)
48+
except ValueError: # All our tests raise ValueErrors.
49+
failed = True
50+
assert failed
7351

7452

75-
def test_validate_imgs_concat_all():
53+
def test_validate_imgs_type():
54+
fake_affine = np.eye(4)
7655
data1 = np.zeros((5, 5, 5)).astype(float)
77-
affine1 = np.eye(4)
78-
img1 = nib.Nifti1Image(data1, affine1)
56+
img1 = nib.Nifti1Image(data1, fake_affine)
7957

58+
# 1) If they are all images
8059
data2 = np.zeros((6, 6, 6)).astype(float)
81-
affine2 = np.eye(4)
82-
img2 = nib.Nifti1Image(data2, affine2)
83-
84-
try:
85-
_validate_imgs_concat(img1, img2)
86-
print("Test with all valid NIFTI images passed.")
87-
except ValueError as e:
88-
print("Test with all valid NIFTI images failed:", str(e))
60+
img2 = nib.Nifti1Image(data2, fake_affine)
61+
_validate_imgs_type(img1, img2) # Should pass
8962

63+
# 2) If one input is not an image
64+
_assert_failed(_validate_imgs_type, img1, data2)
9065

91-
def test_validate_imgs_concat_not_all():
92-
data1 = np.zeros((5, 5, 5)).astype(float)
93-
affine1 = np.eye(4)
94-
img1 = nib.Nifti1Image(data1, affine1)
95-
96-
not_an_image = "I am not an image"
9766

98-
try:
99-
_validate_imgs_concat(img1, not_an_image)
100-
print("Test with one invalid object passed.")
101-
except ValueError as e:
102-
print("Test with one invalid object failed:", str(e))
67+
def test_validate_length():
10368

104-
105-
def test_validate_length_exact():
69+
# 1) Exact length
10670
exact_length_list = [1, 2, 3]
107-
shorter_list = [1, 2]
108-
109-
try:
110-
_validate_length(exact_length_list, 3)
111-
print("Test with exact length passed.")
112-
except ValueError as e:
113-
print("Test with exact length failed:", str(e))
114-
115-
try:
116-
_validate_length(shorter_list, 3)
117-
print("Test with shorter length passed.")
118-
except ValueError as e:
119-
print("Test with shorter length failed:", str(e))
71+
_validate_length(exact_length_list, 3) # Should pass
12072

73+
shorter_list = [1, 2]
74+
_assert_failed(_validate_length, shorter_list, 3)
12175

122-
def test_validate_length_at_least():
76+
# 2) With option 'at_least'
12377
at_least_list = [1, 2, 3, 4]
124-
shorter_list = [1, 2]
78+
_validate_length(at_least_list, 3, at_least=True) # Should pass
12579

126-
try:
127-
_validate_length(at_least_list, 3, at_least=True)
128-
print("Test with 'at least' length passed.")
129-
except ValueError as e:
130-
print("Test with 'at least' length failed:", str(e))
80+
shorter_list = [1, 2]
81+
_assert_failed(_validate_length, shorter_list, 3)
13182

132-
try:
133-
_validate_length(shorter_list, 3, at_least=True)
134-
print("Test with shorter 'at least' length passed.")
135-
except ValueError as e:
136-
print("Test with shorter 'at least' length failed:", str(e))
13783

84+
def test_validate_type():
13885

139-
def test_validate_type_correct():
14086
correct_type_input = 42 # Integer
87+
_validate_type(correct_type_input, int) # Should pass
14188

142-
try:
143-
_validate_type(correct_type_input, int)
144-
print("Test with correct type passed.")
145-
except ValueError:
146-
print("Test with correct type failed.")
147-
148-
149-
def test_validate_type_incorrect():
15089
incorrect_type_input = "42" # String
90+
_assert_failed(_validate_type, incorrect_type_input, int)
15191

152-
try:
153-
_validate_type(incorrect_type_input, int)
154-
print("Test with incorrect type passed.")
155-
except ValueError:
156-
print("Test with incorrect type failed.")
15792

93+
def test_validate_float():
15894

159-
def test_validate_float_correct():
16095
correct_input = 42 # Integer can be cast to float
96+
_validate_float(correct_input) # Should pass
16197

162-
try:
163-
_validate_float(correct_input)
164-
print("Test with correct type passed.")
165-
except ValueError:
166-
print("Test with correct type failed.")
167-
168-
169-
def test_validate_float_incorrect():
17098
incorrect_input = "not_a_float" # String that can't be cast to float
171-
172-
try:
173-
_validate_float(incorrect_input)
174-
print("Test with incorrect type passed.")
175-
except ValueError:
176-
print("Test with incorrect type failed.")
177-
178-
179-
def test_cut_up_cube_with_known_output():
180-
# Input data: smaller 3x3x3 cube
181-
data = np.arange(3 * 3 * 3).reshape((3, 3, 3))
182-
blck = (3, 3, 3)
183-
184-
# Running the function
185-
result = cut_up_cube(data, blck)
186-
187-
# Expected output shape
188-
expected_shape = (3, 3, 3, 3, 3, 3)
189-
190-
# Expected first block
191-
expected_first_block = np.array([[[0, 0, 0],
192-
[0, 0, 0],
193-
[0, 0, 0]],
194-
195-
[[0, 0, 0],
196-
[0, 0, 1],
197-
[0, 3, 4]],
198-
199-
[[0, 0, 0],
200-
[0, 9, 10],
201-
[0, 12, 13]]])
202-
203-
# Expected last block
204-
expected_last_block = np.array([[[13, 14, 0],
205-
[16, 17, 0],
206-
[0, 0, 0]],
207-
208-
[[22, 23, 0],
209-
[25, 26, 0],
210-
[0, 0, 0]],
211-
212-
[[0, 0, 0],
213-
[0, 0, 0],
214-
[0, 0, 0]]])
215-
216-
# Asserting that the output shape matches the expected shape
217-
assert result.shape == expected_shape, \
218-
f"Expected shape {expected_shape}, got {result.shape}"
219-
220-
# Asserting that the first block matches the expected first block
221-
assert_array_equal(result[0, 0, 0, :, :, :], expected_first_block)
222-
223-
# Asserting that the last block matches the expected last block
224-
assert_array_equal(result[-1, -1, -1, :, :, :], expected_last_block)
99+
_assert_failed(_validate_float, incorrect_input)
225100

226101

227102
def test_lower_threshold_eq():
@@ -243,7 +118,6 @@ def test_lower_threshold_eq():
243118
assert_array_equal(output_data, expected_output)
244119

245120

246-
# Test function for upper_threshold_eq
247121
def test_upper_threshold_eq():
248122
# Create a sample nib.Nifti1Image object
249123
img_data = np.array([0, 1, 2, 3, 4, 5]).astype(float)
@@ -572,6 +446,91 @@ def test_concatenate():
572446
assert_array_almost_equal(img_data_1.shape+(2,), output_data.shape)
573447

574448

449+
def test_get_neighbors():
450+
# Input data: small data with NOT the same dimension in each direction.
451+
data = np.arange(3 * 4 * 5).reshape((3, 4, 5)) + 1
452+
453+
# Cutting into patches of radius 1, i.e. 3x3x3.
454+
result = _get_neighbors(data, radius=1)
455+
456+
# Expected output shape: Should fit 3 x 4 x 5 patches of shape 3x3x3
457+
expected_shape = (3, 4, 5, 3, 3, 3)
458+
assert result.shape == expected_shape, \
459+
f"Expected shape {expected_shape}, got {result.shape}"
460+
461+
# First block: expecting 8 non-zero values (always true with patch_size 3!)
462+
# (draw a cube and check the number of neighbors of the voxel at the
463+
# corner)
464+
assert np.count_nonzero(result[0, 0, 0, :, :, :]) == 8
465+
466+
# Middle blocks: expecting 27 non-zero values
467+
assert np.count_nonzero(result[1, 1, 1, :, :, :]) == 27
468+
469+
# Comparing with values obtained the day we created this test.
470+
# Expected first block:
471+
expected_first_block = np.array([[[0, 0, 0],
472+
[0, 0, 0],
473+
[0, 0, 0]],
474+
[[0, 0, 0],
475+
[0, 1, 2],
476+
[0, 6, 7]],
477+
[[0, 0, 0],
478+
[0, 21, 22],
479+
[0, 26, 27]]])
480+
assert_array_equal(result[0, 0, 0, :, :, :], expected_first_block)
481+
482+
# Expected last block
483+
expected_last_block = np.array([[[34, 35, 0],
484+
[39, 40, 0],
485+
[0, 0, 0]],
486+
[[54, 55, 0],
487+
[59, 60, 0],
488+
[0, 0, 0]],
489+
[[0, 0, 0],
490+
[0, 0, 0],
491+
[0, 0, 0]]])
492+
assert_array_equal(result[-1, -1, -1, :, :, :], expected_last_block)
493+
494+
495+
def test_neighborhood_correlation():
496+
# Note. Not working on 2D data.
497+
affine = np.eye(4)
498+
499+
# Test 1: Perfect correlation
500+
# Compares uniform patch of ones with a uniform patch of twos.
501+
# No background.
502+
img_data_1 = np.ones((3, 3, 3), dtype=float)
503+
img1 = nib.Nifti1Image(img_data_1, affine)
504+
505+
img_data_2 = np.ones((3, 3, 3), dtype=float) * 2
506+
img2 = nib.Nifti1Image(img_data_2, affine)
507+
output = neighborhood_correlation([img1, img2], img1)
508+
assert np.allclose(output, np.ones((3, 3, 3))), \
509+
"Expected a perfect correlation, got: {}".format(output)
510+
511+
# Test 2: Bad correlation.
512+
# Compares uniform patch of ones with a noisy patch of twos.
513+
# There should be a poor correlation (cloud of points is horizontal).
514+
# But we notice that around the edges of the image, high correlation (~1),
515+
# as explained in the correlation method's docstring
516+
img_data_2 = np.ones((3, 3, 3), dtype=float) * 2 + \
517+
np.random.rand(3, 3, 3) * 0.001
518+
img2 = nib.Nifti1Image(img_data_2, affine)
519+
output = neighborhood_correlation([img1, img2], img1)
520+
expected = np.ones((3, 3, 3))
521+
expected[1, 1, 1] = 0
522+
assert np.allclose(output, expected), \
523+
("Expected a bad correlation at central point, good around the border,"
524+
" got: {}").format(output)
525+
526+
# Test 2: Comparing with only background: should be 0 everywhere.
527+
img_data_2 = np.zeros((3, 3, 3)).astype(float)
528+
img2 = nib.Nifti1Image(img_data_2, affine)
529+
output = neighborhood_correlation([img1, img2], img1)
530+
assert np.allclose(output, np.zeros((3, 3, 3))), \
531+
"Expected a 0 correlation everywhere, got {}".format(output)
532+
533+
575534
def test_dilation():
576535
img_data = np.array([0, 1]).astype(float)
577536
affine = np.eye(4)

0 commit comments

Comments
 (0)