tests.data.test_image   F
last analyzed

Complexity

Total Complexity 75

Size/Duplication

Total Lines 338
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 263
dl 0
loc 338
rs 2.4
c 0
b 0
f 0
wmc 75

45 Methods

Rating   Name   Duplication   Size   Complexity  
A TestImage.test_repr() 0 7 1
A TestImage.test_bad_key() 0 3 2
A TestImage.test_wrong_label_map_type() 0 4 2
A TestImage.test_image_not_found() 0 3 2
A TestImage.test_with_sequences_of_paths() 0 10 2
A TestImage.test_wrong_path_type() 0 3 2
A TestImage.test_wrong_affine() 0 3 2
A TestImage.test_with_list_of_missing_files() 0 3 2
A TestImage.test_get_center() 0 7 1
A TestImage.test_tensor_flip() 0 3 1
A TestImage.test_wrong_scalar_image_type() 0 4 2
A TestImage.test_axis_name_2d() 0 7 1
A TestImage.test_bad_affine() 0 3 2
A TestImage.test_data_tensor() 0 4 1
A TestImage.test_no_input() 0 3 2
A TestImage.test_with_a_list_of_2d_paths() 0 8 1
A TestImage.test_nans_tensor() 0 6 2
A TestImage.test_with_a_list_of_images_with_different_affines() 0 6 2
A TestImage.test_tensor_affine() 0 3 1
A TestImage.test_wrong_path_value() 0 4 2
A TestImage.test_with_a_list_of_images_with_different_shapes() 0 6 2
A TestImage.test_save_image_with_data_type_boolean() 0 4 1
A TestImage.test_copy_no_data() 0 13 1
A TestImage.test_set_data() 0 4 2
A TestImage.test_hist() 0 4 1
A TestImage.test_unload_no_path() 0 5 2
A TestImage.test_pil_3() 0 2 1
A TestImage.test_plot() 0 5 1
A TestImage.test_data_type_uint16_array() 0 4 1
A TestImage.test_pil_1() 0 2 1
A TestImage.test_pil_3d() 0 3 2
A TestImage.test_load_uint() 0 8 3
A TestImage.test_no_type() 0 3 2
A TestImage.test_affine_multipath() 0 7 1
A TestImage.test_gif_rgb() 0 3 2
A TestImage.test_custom_reader() 0 17 1
A TestImage.test_bad_numpy_type_reader() 0 10 1
A TestImage.test_load_unload() 0 12 2
A TestImage.test_slicing() 0 23 4
A TestImage.test_verify_path() 0 16 2
A TestImage.test_different_shape() 0 7 2
A TestImage.test_count() 0 9 1
A TestImage.test_data_type_uint32_array() 0 4 1
A TestImage.test_pil_2() 0 3 2
A TestImage.test_fast_gif() 0 4 3

How to fix   Complexity   

Complexity

Complex classes like tests.data.test_image often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
"""Tests for Image."""
3
4
import copy
5
import sys
6
import tempfile
7
from pathlib import Path
8
9
import nibabel as nib
10
import numpy as np
11
import pytest
12
import torch
13
14
import torchio as tio
15
16
from ..utils import TorchioTestCase
17
18
19
class TestImage(TorchioTestCase):
20
    """Tests for `Image`."""
21
22
    def test_image_not_found(self):
23
        with pytest.raises(FileNotFoundError):
24
            tio.ScalarImage('nopath')
25
26
    @pytest.mark.skipif(sys.platform == 'win32', reason='Path not valid')
27
    def test_wrong_path_value(self):
28
        with pytest.raises(RuntimeError):
29
            tio.ScalarImage('~&./@#"!?X7=+')
30
31
    def test_wrong_path_type(self):
32
        with pytest.raises(TypeError):
33
            tio.ScalarImage(5)
34
35
    def test_wrong_affine(self):
36
        with pytest.raises(TypeError):
37
            tio.ScalarImage(5, affine=1)
38
39
    def test_tensor_flip(self):
40
        sample_input = torch.ones((4, 30, 30, 30))
41
        tio.RandomFlip()(sample_input)
42
43
    def test_tensor_affine(self):
44
        sample_input = torch.ones((4, 10, 10, 10))
45
        tio.RandomAffine()(sample_input)
46
47
    def test_wrong_scalar_image_type(self):
48
        data = torch.ones((1, 10, 10, 10))
49
        with pytest.raises(ValueError):
50
            tio.ScalarImage(tensor=data, type=tio.LABEL)
51
52
    def test_wrong_label_map_type(self):
53
        data = torch.ones((1, 10, 10, 10))
54
        with pytest.raises(ValueError):
55
            tio.LabelMap(tensor=data, type=tio.INTENSITY)
56
57
    def test_no_input(self):
58
        with pytest.raises(ValueError):
59
            tio.ScalarImage()
60
61
    def test_bad_key(self):
62
        with pytest.raises(ValueError):
63
            tio.ScalarImage(path='', data=5)
64
65
    def test_repr(self):
66
        subject = tio.Subject(
67
            t1=tio.ScalarImage(self.get_image_path('repr_test')),
68
        )
69
        assert 'memory' not in repr(subject['t1'])
70
        subject.load()
71
        assert 'memory' in repr(subject['t1'])
72
73
    def test_data_tensor(self):
74
        subject = copy.deepcopy(self.sample_subject)
75
        subject.load()
76
        assert subject.t1.data is subject.t1.tensor
77
78
    def test_bad_affine(self):
79
        with pytest.raises(ValueError):
80
            tio.ScalarImage(tensor=torch.rand(1, 2, 3, 4), affine=np.eye(3))
81
82
    def test_nans_tensor(self):
83
        tensor = np.random.rand(1, 2, 3, 4)
84
        tensor[0, 0, 0, 0] = np.nan
85
        with pytest.warns(RuntimeWarning):
86
            image = tio.ScalarImage(tensor=tensor, check_nans=True)
87
        image.set_check_nans(False)
88
89
    def test_get_center(self):
90
        tensor = torch.rand(1, 3, 3, 3)
91
        image = tio.ScalarImage(tensor=tensor)
92
        ras = image.get_center()
93
        lps = image.get_center(lps=True)
94
        assert ras == (1, 1, 1)
95
        assert lps == (-1, -1, 1)
96
97
    def test_with_list_of_missing_files(self):
98
        with pytest.raises(FileNotFoundError):
99
            tio.ScalarImage(path=['nopath', 'error'])
100
101
    def test_with_sequences_of_paths(self):
102
        shape = (5, 5, 5)
103
        path1 = self.get_image_path('path1', shape=shape)
104
        path2 = self.get_image_path('path2', shape=shape)
105
        paths_tuple = path1, path2
106
        paths_list = list(paths_tuple)
107
        for sequence in (paths_tuple, paths_list):
108
            image = tio.ScalarImage(path=sequence)
109
            assert image.shape == (2, 5, 5, 5)
110
            assert image[tio.STEM] == ['path1', 'path2']
111
112
    def test_with_a_list_of_images_with_different_shapes(self):
113
        path1 = self.get_image_path('path1', shape=(5, 5, 5))
114
        path2 = self.get_image_path('path2', shape=(7, 5, 5))
115
        image = tio.ScalarImage(path=[path1, path2])
116
        with pytest.raises(RuntimeError):
117
            image.load()
118
119
    def test_with_a_list_of_images_with_different_affines(self):
120
        path1 = self.get_image_path('path1', spacing=(1, 1, 1))
121
        path2 = self.get_image_path('path2', spacing=(1, 2, 1))
122
        image = tio.ScalarImage(path=[path1, path2])
123
        with pytest.warns(RuntimeWarning):
124
            image.load()
125
126
    def test_with_a_list_of_2d_paths(self):
127
        shape = (5, 6)
128
        path1 = self.get_image_path('path1', shape=shape, suffix='.nii')
129
        path2 = self.get_image_path('path2', shape=shape, suffix='.img')
130
        path3 = self.get_image_path('path3', shape=shape, suffix='.hdr')
131
        image = tio.ScalarImage(path=[path1, path2, path3])
132
        assert image.shape == (3, 5, 6, 1)
133
        assert image[tio.STEM] == ['path1', 'path2', 'path3']
134
135
    def test_axis_name_2d(self):
136
        path = self.get_image_path('im2d', shape=(5, 6))
137
        image = tio.ScalarImage(path)
138
        height_idx = image.axis_name_to_index('t')
139
        width_idx = image.axis_name_to_index('l')
140
        assert image.height == image.shape[height_idx]
141
        assert image.width == image.shape[width_idx]
142
143
    def test_different_shape(self):
144
        path_1 = self.get_image_path('im_shape1', shape=(5, 5, 5))
145
        path_2 = self.get_image_path('im_shape2', shape=(7, 5, 5))
146
147
        image = tio.ScalarImage([path_1, path_2])
148
        with pytest.raises(RuntimeError):
149
            image.load()
150
151
    @pytest.mark.slow
152
    @pytest.mark.skipif(sys.platform == 'win32', reason='Unstable on Windows')
153
    def test_plot(self):
154
        image = self.sample_subject.t1
155
        image.plot(show=False, output_path=self.dir / 'image.png')
156
157
    def test_data_type_uint16_array(self):
158
        tensor = np.random.rand(1, 3, 3, 3).astype(np.uint16)
159
        image = tio.ScalarImage(tensor=tensor)
160
        assert image.data.dtype == torch.int32
161
162
    def test_data_type_uint32_array(self):
163
        tensor = np.random.rand(1, 3, 3, 3).astype(np.uint32)
164
        image = tio.ScalarImage(tensor=tensor)
165
        assert image.data.dtype == torch.int64
166
167
    def test_save_image_with_data_type_boolean(self):
168
        tensor = np.random.rand(1, 3, 3, 3).astype(bool)
169
        image = tio.ScalarImage(tensor=tensor)
170
        image.save(self.dir / 'image.nii')
171
172
    def test_load_uint(self):
173
        affine = np.eye(4)
174
        for dtype in np.uint16, np.uint32:
175
            data = np.ones((3, 3, 3), dtype=dtype)
176
            img = nib.Nifti1Image(data, affine)
177
            with tempfile.NamedTemporaryFile(suffix='.nii', delete=False) as f:
178
                nib.save(img, f.name)
179
                tio.ScalarImage(f.name).load()
180
181
    def test_pil_3d(self):
182
        with pytest.raises(RuntimeError):
183
            tio.ScalarImage(tensor=torch.rand(1, 2, 3, 4)).as_pil()
184
185
    def test_pil_1(self):
186
        tio.ScalarImage(tensor=torch.rand(1, 2, 3, 1)).as_pil()
187
188
    def test_pil_2(self):
189
        with pytest.raises(RuntimeError):
190
            tio.ScalarImage(tensor=torch.rand(2, 2, 3, 1)).as_pil()
191
192
    def test_pil_3(self):
193
        tio.ScalarImage(tensor=torch.rand(3, 2, 3, 1)).as_pil()
194
195
    def test_set_data(self):
196
        im = self.sample_subject.t1
197
        with pytest.deprecated_call():
198
            im.data = im.data
199
200
    def test_no_type(self):
201
        with pytest.warns(FutureWarning):
202
            tio.Image(tensor=torch.rand(1, 2, 3, 4))
203
204
    def test_custom_reader(self):
205
        path = self.dir / 'im.npy'
206
207
        def numpy_reader(path):
208
            return np.load(path), np.eye(4)
209
210
        def assert_shape(shape_in, shape_out):
211
            np.save(path, np.random.rand(*shape_in))
212
            image = tio.ScalarImage(path, reader=numpy_reader)
213
            assert image.shape == shape_out
214
215
        assert_shape((5, 5), (1, 5, 5, 1))
216
        assert_shape((5, 5, 3), (3, 5, 5, 1))
217
        assert_shape((3, 5, 5), (3, 5, 5, 1))
218
        assert_shape((5, 5, 5), (1, 5, 5, 5))
219
        assert_shape((1, 5, 5, 5), (1, 5, 5, 5))
220
        assert_shape((4, 5, 5, 5), (4, 5, 5, 5))
221
222
    def test_fast_gif(self):
223
        with pytest.warns(RuntimeWarning):
224
            with tempfile.NamedTemporaryFile(suffix='.gif', delete=False) as f:
225
                self.sample_subject.t1.to_gif(0, 0.0001, f.name)
226
227
    def test_gif_rgb(self):
228
        with tempfile.NamedTemporaryFile(suffix='.gif', delete=False) as f:
229
            tio.ScalarImage(tensor=torch.rand(3, 4, 5, 6)).to_gif(0, 1, f.name)
230
231
    @pytest.mark.slow
232
    def test_hist(self):
233
        self.sample_subject.t1.hist(density=False, show=False)
234
        self.sample_subject.t1.hist(density=True, show=False)
235
236
    def test_count(self):
237
        image = self.sample_subject.label
238
        max_n = image.data.numel()
239
        nonzero = image.count_nonzero()
240
        assert 0 <= nonzero <= max_n
241
        counts = image.count_labels()
242
        assert tuple(counts) == (0, 1)
243
        assert 0 <= counts[0] <= max_n
244
        assert 0 <= counts[1] <= max_n
245
246
    def test_affine_multipath(self):
247
        # https://github.com/TorchIO-project/torchio/issues/762
248
        path1 = self.get_image_path('multi1')
249
        path2 = self.get_image_path('multi2')
250
        paths = path1, path2
251
        image = tio.ScalarImage(paths)
252
        self.assert_tensor_equal(image.affine, np.eye(4))
253
254
    def test_bad_numpy_type_reader(self):
255
        # https://github.com/TorchIO-project/torchio/issues/764
256
        def numpy_reader(path):
257
            return np.load(path), np.eye(4)
258
259
        tensor = np.random.rand(1, 2, 3, 4).astype(np.uint16)
260
        test_path = self.dir / 'test_image.npy'
261
        np.save(test_path, tensor)
262
        image = tio.ScalarImage(test_path, reader=numpy_reader)
263
        image.load()
264
265
    def test_load_unload(self):
266
        path = self.get_image_path('unload')
267
        image = tio.ScalarImage(path)
268
        with self.assertRaises(RuntimeError):
269
            image.unload()
270
        image.load()
271
        assert image._loaded
272
        image.unload()
273
        assert not image._loaded
274
        assert image[tio.DATA] is None
275
        assert image[tio.AFFINE] is None
276
        assert not image._loaded
277
278
    def test_unload_no_path(self):
279
        tensor = torch.rand(1, 2, 3, 4)
280
        image = tio.ScalarImage(tensor=tensor)
281
        with self.assertRaises(RuntimeError):
282
            image.unload()
283
284
    def test_copy_no_data(self):
285
        # https://github.com/TorchIO-project/torchio/issues/974
286
        path = self.get_image_path('im_copy')
287
        my_image = tio.LabelMap(path)
288
        assert not my_image._loaded
289
        new_image = copy.copy(my_image)
290
        assert not my_image._loaded
291
        assert not new_image._loaded
292
293
        my_image.load()
294
        new_image = copy.copy(my_image)
295
        assert my_image._loaded
296
        assert new_image._loaded
297
298
    def test_slicing(self):
299
        path = self.get_image_path('im_slicing')
300
        image = tio.ScalarImage(path)
301
302
        assert image.shape == (1, 10, 20, 30)
303
304
        cropped = image[0]
305
        assert cropped.shape == (1, 1, 20, 30)
306
307
        cropped = image[:, 2:-3]
308
        assert cropped.shape == (1, 10, 15, 30)
309
310
        cropped = image[-5:, 5:]
311
        assert cropped.shape == (1, 5, 15, 30)
312
313
        with pytest.raises(NotImplementedError):
314
            image[..., 5]
315
316
        with pytest.raises(ValueError):
317
            image[0:8:-1]
318
319
        with pytest.raises(ValueError):
320
            image[3::-1]
321
322
    def test_verify_path(self):
323
        path = Path(self.get_image_path('im_verify'))
324
325
        image = tio.ScalarImage(path, verify_path=False)
326
        assert image.path == path
327
328
        image = tio.ScalarImage(path, verify_path=True)
329
        assert image.path == path
330
331
        fake_path = Path('fake_path.nii')
332
333
        image = tio.ScalarImage(fake_path, verify_path=False)
334
        assert image.path == fake_path
335
336
        with pytest.raises(FileNotFoundError):
337
            tio.ScalarImage(fake_path, verify_path=True)
338