Skip to content

Commit c800493

Browse files
committed
RF+TST: make caches specific to data type
The image cache gets reset when you ask for a new data type. For example, you might first read float64, and float64 gets cached, but then you read float32, at which point you lose the float64 cache and get a float32 cache.
1 parent 178cae2 commit c800493

File tree

3 files changed

+76
-8
lines changed

3 files changed

+76
-8
lines changed

nibabel/dataobj_images.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,8 @@ def get_fdata(self, caching='fill', dtype=np.float64):
340340
dtype = np.dtype(dtype)
341341
if not issubclass(dtype.type, np.inexact):
342342
raise ValueError('{} should be floating point type'.format(dtype))
343-
if self._fdata_cache is not None:
343+
if (self._fdata_cache is not None and
344+
self._fdata_cache.dtype.type == dtype.type):
344345
return self._fdata_cache
345346
data = np.asanyarray(self._dataobj).astype(dtype)
346347
if caching == 'fill':
@@ -350,6 +351,9 @@ def get_fdata(self, caching='fill', dtype=np.float64):
350351
@property
351352
def in_memory(self):
352353
""" True when any array data is in memory cache
354+
355+
There are separate caches for `get_data` reads and `get_fdata` reads.
356+
This property is True if either of those caches are set.
353357
"""
354358
return (isinstance(self._dataobj, np.ndarray) or
355359
self._fdata_cache is not None or

nibabel/tests/test_image_api.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ def validate_data_interface(self, imaker, params):
200200
img = imaker()
201201
assert_equal(img.shape, img.dataobj.shape)
202202
assert_data_similar(img.dataobj, params)
203-
for meth_name in ('get_fdata', 'get_data'):
203+
meth_names = ('get_fdata', 'get_data')
204+
for meth_name in meth_names:
204205
if params['is_proxy']:
205206
# Parameters assert this is an array proxy
206207
img = imaker()
@@ -220,9 +221,16 @@ def validate_data_interface(self, imaker, params):
220221
assert_false(img.in_memory)
221222
# Default load, does caching
222223
data = method()
223-
# Data now cached
224+
# Data now cached. in_memory is True if either of the get_data
225+
# or get_fdata caches are not-None
224226
assert_true(img.in_memory)
227+
# We previously got proxy_data from disk, but data, which we
228+
# have just fetched, is a fresh copy.
225229
assert_false(proxy_data is data)
230+
# asarray on dataobj, applied above, returns same numerical
231+
# values. This might not be true get_fdata operating on huge
232+
# integers, but lets assume that's not true here.
233+
assert_array_equal(proxy_data, data)
226234
# Now caching='unchanged' does nothing, returns cached version
227235
data_again = method(caching='unchanged')
228236
assert_true(data is data_again)
@@ -249,6 +257,51 @@ def validate_data_interface(self, imaker, params):
249257
assert_true(img.in_memory)
250258
data_again = method()
251259
assert_true(data is data_again)
260+
# Check the interaction of caching with get_data, get_fdata.
261+
# Caching for `get_data` should have no effect on caching for
262+
# get_fdata, and vice versa.
263+
# Modify the cached data
264+
data[:] = 43
265+
# Load using the other data fetch method
266+
other_name = set(meth_names).difference({meth_name}).pop()
267+
other_method = getattr(img, other_name)
268+
other_data = other_method()
269+
# We get the original data, not the modified cache
270+
assert_array_equal(proxy_data, other_data)
271+
assert_false(np.all(data == other_data))
272+
# We can modify the other cache, without affecting the first
273+
other_data[:] = 44
274+
assert_array_equal(other_method(), 44)
275+
assert_false(np.all(method() == other_method()))
276+
# Check that caching refreshes for new floating point type.
277+
if meth_name == 'get_fdata':
278+
img.uncache()
279+
fdata = img.get_fdata()
280+
assert_equal(fdata.dtype, np.float64)
281+
fdata[:] = 42
282+
fdata_back = img.get_fdata()
283+
assert_array_equal(fdata_back, 42)
284+
assert_equal(fdata_back.dtype, np.float64)
285+
# New data dtype, no caching, doesn't use or alter cache
286+
fdata_new_dt = img.get_fdata(caching='unchanged', dtype='f4')
287+
# We get back the original read, not the modified cache
288+
assert_array_equal(fdata_new_dt, proxy_data.astype('f4'))
289+
assert_equal(fdata_new_dt.dtype, np.float32)
290+
# The original cache stays in place, for default float64
291+
assert_array_equal(img.get_fdata(), 42)
292+
# And for not-default float32, because we haven't cached
293+
fdata_new_dt[:] = 43
294+
fdata_new_dt = img.get_fdata(caching='unchanged', dtype='f4')
295+
assert_array_equal(fdata_new_dt, proxy_data.astype('f4'))
296+
# Until we reset with caching='fill', at which point we
297+
# drop the original float64 cache, and have a float32 cache
298+
fdata_new_dt = img.get_fdata(caching='fill', dtype='f4')
299+
assert_array_equal(fdata_new_dt, proxy_data.astype('f4'))
300+
# We're using the cache, for dtype='f4' reads
301+
fdata_new_dt[:] = 43
302+
assert_array_equal(img.get_fdata(dtype='f4'), 43)
303+
# We've lost the cache for float64 reads (no longer 42)
304+
assert_array_equal(img.get_fdata(), proxy_data)
252305
else: # not proxy
253306
for caching in (None, 'fill', 'unchanged'):
254307
img = imaker()

nibabel/tests/test_spatialimages.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -315,11 +315,22 @@ def test_get_fdata(self):
315315
assert_true(in_data is img.dataobj)
316316
# The get_fdata method changes the array to floating point type
317317
assert_equal(img.get_fdata(dtype='f4').dtype, np.dtype(np.float32))
318-
assert_equal(img.get_fdata(dtype=np.float32).dtype,
319-
np.dtype(np.float32))
320-
# Caching determines data dtype
321-
out_data = img.get_fdata()
322-
assert_equal(out_data.dtype, np.dtype(np.float32))
318+
fdata_32 = img.get_fdata(dtype=np.float32)
319+
assert_equal(fdata_32.dtype, np.dtype(np.float32))
320+
# Caching is specific to data dtype. If we reload with default data
321+
# type, the cache gets reset
322+
fdata_32[:] = 99
323+
# Cache has been modified, we pick up the modifications, but only for
324+
# the cached data type
325+
assert_array_equal(img.get_fdata(dtype='f4'), 99)
326+
fdata_64 = img.get_fdata()
327+
assert_equal(fdata_64.dtype, np.dtype(np.float64))
328+
assert_array_equal(fdata_64, in_data)
329+
fdata_64[:] = 101
330+
assert_array_equal(img.get_fdata(dtype='f8'), 101)
331+
assert_array_equal(img.get_fdata(), 101)
332+
# Reloading with new data type blew away the float32 cache
333+
assert_array_equal(img.get_fdata(dtype='f4'), in_data)
323334
img.uncache()
324335
# Now recaching, is float64
325336
out_data = img.get_fdata()

0 commit comments

Comments
 (0)