Skip to content

Commit 97405ad

Browse files
matthew-bretteffigies
authored andcommitted
RF: split up image API tester into mixins
Make a generic file-based image API tester, and split dataobj, affine, dtype, and header shape tests into their own mixins. Mix these in to restore the original set of tests. The refactoring is to allow tests of FileBasedImage and DataobjImage.
1 parent df47554 commit 97405ad

File tree

1 file changed

+107
-60
lines changed

1 file changed

+107
-60
lines changed

nibabel/tests/test_image_api.py

Lines changed: 107 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
from ..tmpdirs import InTemporaryDirectory
4848

4949
from .test_api_validators import ValidateAPI
50-
from .test_helpers import bytesio_round_trip, assert_data_similar
50+
from .test_helpers import (bytesio_round_trip, bytesio_filemap,
51+
assert_data_similar)
5152
from .test_minc1 import EXAMPLE_IMAGES as MINC1_EXAMPLE_IMAGES
5253
from .test_minc2 import EXAMPLE_IMAGES as MINC2_EXAMPLE_IMAGES
5354
from .test_parrec import EXAMPLE_IMAGES as PARREC_EXAMPLE_IMAGES
@@ -100,38 +101,10 @@ def obj_params(self):
100101
"""
101102
raise NotImplementedError
102103

103-
def validate_affine(self, imaker, params):
104-
# Check affine API
105-
img = imaker()
106-
assert_almost_equal(img.affine, params['affine'], 6)
107-
assert_equal(img.affine.dtype, np.float64)
108-
img.affine[0, 0] = 1.5
109-
assert_equal(img.affine[0, 0], 1.5)
110-
# Read only
111-
assert_raises(AttributeError, setattr, img, 'affine', np.eye(4))
112-
113-
def validate_affine_deprecated(self, imaker, params):
114-
# Check deprecated affine API
115-
img = imaker()
116-
with clear_and_catch_warnings() as w:
117-
warnings.simplefilter('always', DeprecationWarning)
118-
assert_almost_equal(img.get_affine(), params['affine'], 6)
119-
assert_equal(len(w), 1)
120-
assert_equal(img.get_affine().dtype, np.float64)
121-
aff = img.get_affine()
122-
aff[0, 0] = 1.5
123-
assert_true(aff is img.get_affine())
124-
125104
def validate_header(self, imaker, params):
126105
# Check header API
127106
img = imaker()
128107
hdr = img.header # we can fetch it
129-
# Change shape in header, check this changes img.header
130-
shape = hdr.get_data_shape()
131-
new_shape = (shape[0] + 1,) + shape[1:]
132-
hdr.set_data_shape(new_shape)
133-
assert_true(img.header is hdr)
134-
assert_equal(img.header.get_data_shape(), new_shape)
135108
# Read only
136109
assert_raises(AttributeError, setattr, img, 'header', hdr)
137110

@@ -152,7 +125,6 @@ def validate_shape(self, imaker, params):
152125
# Same as array shape if passed
153126
if 'data' in params:
154127
assert_equal(img.shape, params['data'].shape)
155-
assert_equal(img.shape, img.get_data().shape)
156128
# Read only
157129
assert_raises(AttributeError, setattr, img, 'shape', np.eye(4))
158130

@@ -164,6 +136,51 @@ def validate_shape_deprecated(self, imaker, params):
164136
assert_equal(img.get_shape(), params['shape'])
165137
assert_equal(len(w), 1)
166138

139+
def validate_filenames(self, imaker, params):
140+
# Validate the filename, file_map interface
141+
if not self.can_save:
142+
raise SkipTest
143+
img = imaker()
144+
img.set_data_dtype(np.float32) # to avoid rounding in load / save
145+
# Make sure the object does not have a file_map
146+
img.file_map = None
147+
# The bytesio_round_trip helper tests bytesio load / save via file_map
148+
rt_img = bytesio_round_trip(img)
149+
assert_array_equal(img.shape, rt_img.shape)
150+
assert_almost_equal(img.get_data(), rt_img.get_data())
151+
# Give the image a file map
152+
klass = type(img)
153+
rt_img.file_map = bytesio_filemap(klass)
154+
# This object can now be saved and loaded from its own file_map
155+
rt_img.to_file_map()
156+
rt_rt_img = klass.from_file_map(rt_img.file_map)
157+
assert_almost_equal(img.get_data(), rt_rt_img.get_data())
158+
# get_ / set_ filename
159+
fname = 'an_image' + self.standard_extension
160+
img.set_filename(fname)
161+
assert_equal(img.get_filename(), fname)
162+
assert_equal(img.file_map['image'].filename, fname)
163+
# to_ / from_ filename
164+
fname = 'another_image' + self.standard_extension
165+
with InTemporaryDirectory():
166+
img.to_filename(fname)
167+
rt_img = img.__class__.from_filename(fname)
168+
assert_array_equal(img.shape, rt_img.shape)
169+
assert_almost_equal(img.get_data(), rt_img.get_data())
170+
del rt_img # to allow windows to delete the directory
171+
172+
def validate_no_slicing(self, imaker, params):
173+
img = imaker()
174+
assert_raises(TypeError, img.__getitem__, 'string')
175+
assert_raises(TypeError, img.__getitem__, slice(None))
176+
177+
178+
class GetSetDtypeMixin(object):
179+
""" Adds dtype tests
180+
181+
Add this one if your image has ``get_data_dtype`` and ``set_data_dtype``.
182+
"""
183+
167184
def validate_dtype(self, imaker, params):
168185
# data / storage dtype
169186
img = imaker()
@@ -182,9 +199,17 @@ def validate_dtype(self, imaker, params):
182199
rt_img = bytesio_round_trip(img)
183200
assert_equal(rt_img.get_data_dtype().type, np.float32)
184201

185-
def validate_data(self, imaker, params):
202+
203+
class DataInterfaceMixin(GetSetDtypeMixin):
204+
""" Test dataobj interface for images with array backing
205+
206+
Use this mixin if your image has a ``dataobj`` property that contains an
207+
array or an array-like thing.
208+
"""
209+
def validate_data_interface(self, imaker, params):
186210
# Check get data returns array, and caches
187211
img = imaker()
212+
assert_equal(img.shape, img.dataobj.shape)
188213
assert_data_similar(img.dataobj, params)
189214
if params['is_proxy']:
190215
assert_false(isinstance(img.dataobj, np.ndarray))
@@ -243,6 +268,8 @@ def validate_data(self, imaker, params):
243268
img.uncache()
244269
assert_array_equal(get_data_func(), 42)
245270
assert_true(img.in_memory)
271+
# Data shape is same as image shape
272+
assert_equal(img.shape, img.get_data().shape)
246273
# dataobj is read only
247274
fake_data = np.zeros(img.shape).astype(img.get_data_dtype())
248275
assert_raises(AttributeError, setattr, img, 'dataobj', fake_data)
@@ -262,37 +289,60 @@ def validate_data_deprecated(self, imaker, params):
262289
fake_data = np.zeros(img.shape).astype(img.get_data_dtype())
263290
assert_raises(AttributeError, setattr, img, '_data', fake_data)
264291

265-
def validate_filenames(self, imaker, params):
266-
# Validate the filename, file_map interface
267-
if not self.can_save:
268-
raise SkipTest
292+
293+
class HeaderShapeMixin(object):
294+
""" Tests that header shape can be set and got
295+
296+
Add this one of your header supports ``get_data_shape`` and
297+
``set_data_shape``.
298+
"""
299+
300+
def validate_header_shape(self, imaker, params):
301+
# Change shape in header, check this changes img.header
269302
img = imaker()
270-
img.set_data_dtype(np.float32) # to avoid rounding in load / save
271-
# The bytesio_round_trip helper tests bytesio load / save via file_map
272-
rt_img = bytesio_round_trip(img)
273-
assert_array_equal(img.shape, rt_img.shape)
274-
assert_almost_equal(img.get_data(), rt_img.get_data())
275-
# get_ / set_ filename
276-
fname = 'an_image' + self.standard_extension
277-
img.set_filename(fname)
278-
assert_equal(img.get_filename(), fname)
279-
assert_equal(img.file_map['image'].filename, fname)
280-
# to_ / from_ filename
281-
fname = 'another_image' + self.standard_extension
282-
with InTemporaryDirectory():
283-
img.to_filename(fname)
284-
rt_img = img.__class__.from_filename(fname)
285-
assert_array_equal(img.shape, rt_img.shape)
286-
assert_almost_equal(img.get_data(), rt_img.get_data())
287-
del rt_img # to allow windows to delete the directory
303+
hdr = img.header
304+
shape = hdr.get_data_shape()
305+
new_shape = (shape[0] + 1,) + shape[1:]
306+
hdr.set_data_shape(new_shape)
307+
assert_true(img.header is hdr)
308+
assert_equal(img.header.get_data_shape(), new_shape)
288309

289-
def validate_no_slicing(self, imaker, params):
310+
311+
class AffineMixin(object):
312+
""" Adds test of affine property, method
313+
314+
Add this one if your image has an ``affine`` property. If so, it should
315+
(for now) also have a ``get_affine`` method returning the same result.
316+
"""
317+
318+
def validate_affine(self, imaker, params):
319+
# Check affine API
290320
img = imaker()
291-
assert_raises(TypeError, img.__getitem__, 'string')
292-
assert_raises(TypeError, img.__getitem__, slice(None))
321+
assert_almost_equal(img.affine, params['affine'], 6)
322+
assert_equal(img.affine.dtype, np.float64)
323+
img.affine[0, 0] = 1.5
324+
assert_equal(img.affine[0, 0], 1.5)
325+
# Read only
326+
assert_raises(AttributeError, setattr, img, 'affine', np.eye(4))
327+
328+
def validate_affine_deprecated(self, imaker, params):
329+
# Check deprecated affine API
330+
img = imaker()
331+
with clear_and_catch_warnings() as w:
332+
warnings.simplefilter('always', DeprecationWarning)
333+
assert_almost_equal(img.get_affine(), params['affine'], 6)
334+
assert_equal(len(w), 1)
335+
assert_equal(img.get_affine().dtype, np.float64)
336+
aff = img.get_affine()
337+
aff[0, 0] = 1.5
338+
assert_true(aff is img.get_affine())
293339

294340

295-
class LoadImageAPI(GenericImageAPI):
341+
class LoadImageAPI(GenericImageAPI,
342+
DataInterfaceMixin,
343+
AffineMixin,
344+
GetSetDtypeMixin,
345+
HeaderShapeMixin):
296346
# Callable returning an image from a filename
297347
loader = None
298348
# Sequence of dictionaries, where dictionaries have keys
@@ -324,9 +374,6 @@ class MakeImageAPI(LoadImageAPI):
324374
# Example shapes for created images
325375
example_shapes = ((2,), (2, 3), (2, 3, 4), (2, 3, 4, 5))
326376

327-
def img_from_arr_aff(self, arr, aff, header=None):
328-
return self.image_maker(arr, aff, header)
329-
330377
def obj_params(self):
331378
# Return any obj_params from superclass
332379
for func, params in super(MakeImageAPI, self).obj_params():

0 commit comments

Comments
 (0)