Skip to content
Snippets Groups Projects
Commit 918f084d authored by Paul McCarthy's avatar Paul McCarthy :mountain_bicyclist:
Browse files

Merge branch 'rf/save_mmap' into 'master'

Rf/save mmap

See merge request fsl/fslpy!63
parents 3e5cf75c 24625dac
No related branches found
No related tags found
No related merge requests found
...@@ -2,6 +2,18 @@ This document contains the ``fslpy`` release history in reverse chronological ...@@ -2,6 +2,18 @@ This document contains the ``fslpy`` release history in reverse chronological
order. order.
1.10.2 (Friday September 7th 2018)
----------------------------------
Fixed
^^^^^
* The :meth:`.Image.save` method was not handling memory-mapped images
correctly.
1.10.1 (Friday August 3rd 2018) 1.10.1 (Friday August 3rd 2018)
------------------------------- -------------------------------
...@@ -16,7 +28,7 @@ Changed ...@@ -16,7 +28,7 @@ Changed
Fixed Fixed
^^^^^ ^^^^^
* The .mod:`.FEATImage.getCOPE` method was returning PE images. * The :mod:`.FEATImage.getCOPE` method was returning PE images.
1.10.0 (Wednesday July 18th 2018) 1.10.0 (Wednesday July 18th 2018)
......
...@@ -35,8 +35,10 @@ and file names: ...@@ -35,8 +35,10 @@ and file names:
import os import os
import os.path as op import os.path as op
import shutil
import string import string
import logging import logging
import tempfile
import six import six
import deprecation import deprecation
...@@ -1165,11 +1167,41 @@ class Image(Nifti): ...@@ -1165,11 +1167,41 @@ class Image(Nifti):
log.debug('Saving {} to {}'.format(self.name, filename)) log.debug('Saving {} to {}'.format(self.name, filename))
# If this Image is not managing its # If this Image is not managing its
# own file object, nibabel does all # own file object, and the image is not
# of the hard work. # memory-mapped, nibabel does all of
if self.__fileobj is None: # the hard work.
newnibimage = False
ismmap = isinstance(self[0, 0, :10], np.memmap)
if self.__fileobj is None and (not ismmap):
nib.save(self.__nibImage, filename) nib.save(self.__nibImage, filename)
# If the image is memory-mapped, we need
# to close and re-open the image
elif self.__fileobj is None:
# We save the image out to a temp file,
# then close the old image, move the
# temp file to the real destination,
# then re-open the file.
newnibimage = True
tmphd, tmpfname = tempfile.mkstemp(suffix=op.splitext(filename)[1])
os.close(tmphd)
try:
nib.save(self.__nibImage, tmpfname)
self.__nibImage = None
self.header = None
shutil.copy(tmpfname, filename)
self.__nibImage = nib.load(filename)
self.header = self.__nibImage.header
except Exception:
os.remove(tmpfname)
raise
# Otherwise we've got our own file # Otherwise we've got our own file
# handle to an IndexedGzipFile # handle to an IndexedGzipFile
else: else:
...@@ -1190,15 +1222,19 @@ class Image(Nifti): ...@@ -1190,15 +1222,19 @@ class Image(Nifti):
# compressed data. And then be able to # compressed data. And then be able to
# transfer the index generated from the # transfer the index generated from the
# write to a new read-only file handle. # write to a new read-only file handle.
newnibimage = True
nib.save(self.__nibImage, filename) nib.save(self.__nibImage, filename)
self.__fileobj.close() self.__fileobj.close()
self.__nibImage, self.__fileobj = loadIndexedImageFile(filename) self.__nibImage, self.__fileobj = loadIndexedImageFile(filename)
self.header = self.__nibImage.header self.header = self.__nibImage.header
# We have to create a new ImageWrapper # If we've created a new nibabel image,
# instance too, as we have just destroyed # we have to create a new ImageWrapper
# the nibabel image we gave to the last # instance too, as we have just destroyed
# one. # the nibabel image we gave to the last
# one.
if newnibimage:
self.__imageWrapper.deregister(self.__lName) self.__imageWrapper.deregister(self.__lName)
self.__imageWrapper = imagewrapper.ImageWrapper( self.__imageWrapper = imagewrapper.ImageWrapper(
self.nibImage, self.nibImage,
......
...@@ -825,6 +825,8 @@ def expectedShape(sliceobj, shape): ...@@ -825,6 +825,8 @@ def expectedShape(sliceobj, shape):
if start is None: start = 0 if start is None: start = 0
if stop is None: stop = shape[i] if stop is None: stop = shape[i]
stop = min(stop, shape[i])
expShape.append(stop - start) expShape.append(stop - start)
return len(expShape), expShape return len(expShape), expShape
......
...@@ -994,29 +994,29 @@ def _test_Image_save(imgtype): ...@@ -994,29 +994,29 @@ def _test_Image_save(imgtype):
make_image(filename, imgtype) make_image(filename, imgtype)
# Using mmap can cause a "Bus error" # Save to original location, and
# under docker. No idea why. # to a different location. And
img = fslimage.Image(filename, mmap=False) # test both with and without mmap
targets = [None, filename, filename2]
mmaps = [False, True]
randvoxes = randvoxes(5) for target, mmap in it.product(targets, mmaps):
randvals = [np.random.random() for i in range(5)]
for (x, y, z), v in zip(randvoxes, randvals): img = fslimage.Image(filename, mmap=mmap)
img[x, y, z] = v
if imgtype > 0: rvoxs = randvoxes(5)
img.voxToWorldMat = xform rvals = [np.random.random() for i in range(5)]
# Save to original location, and for (x, y, z), v in zip(rvoxs, rvals):
# to a different location img[x, y, z] = v
targets = [None, filename, filename2]
for t in targets: if imgtype > 0:
img.voxToWorldMat = xform
img.save(t) img.save(target)
if t is None: expDataSource = filename if target is None: expDataSource = filename
else: expDataSource = t else: expDataSource = target
assert img.saveState assert img.saveState
assert img.dataSource == expDataSource assert img.dataSource == expDataSource
...@@ -1024,7 +1024,7 @@ def _test_Image_save(imgtype): ...@@ -1024,7 +1024,7 @@ def _test_Image_save(imgtype):
if imgtype > 0: if imgtype > 0:
assert np.all(np.isclose(img.voxToWorldMat, xform)) assert np.all(np.isclose(img.voxToWorldMat, xform))
for (x, y, z), v in zip(randvoxes, randvals): for (x, y, z), v in zip(rvoxs, rvals):
assert np.isclose(img[x, y, z], v) assert np.isclose(img[x, y, z], v)
# Load the image back in # Load the image back in
...@@ -1036,7 +1036,7 @@ def _test_Image_save(imgtype): ...@@ -1036,7 +1036,7 @@ def _test_Image_save(imgtype):
if imgtype > 0: if imgtype > 0:
assert np.all(np.isclose(img.voxToWorldMat, xform)) assert np.all(np.isclose(img.voxToWorldMat, xform))
for (x, y, z), v in zip(randvoxes, randvals): for (x, y, z), v in zip(rvoxs, rvals):
assert np.isclose(img[x, y, z], v) assert np.isclose(img[x, y, z], v)
img2 = None img2 = None
......
...@@ -257,6 +257,55 @@ def coverageDataRange(data, coverage, slices=None): ...@@ -257,6 +257,55 @@ def coverageDataRange(data, coverage, slices=None):
return np.min(volmin), np.max(volmax) return np.min(volmin), np.max(volmax)
def test_expectedShape():
tests = [
((slice(None), ), (10,),
(1, (10, ))),
((slice(None), slice(None)),
(10, 10), (2, (10, 10))),
((slice(None), slice(None), slice(None)),
(10, 10, 10), (3, (10, 10, 10))),
((slice(None), slice(None), slice(None)),
(10, 10, 10), (3, (10, 10, 10))),
((slice(None), slice(None), slice(None), slice(None)),
(10, 10, 10, 10), (4, (10, 10, 10, 10))),
((1, slice(None), slice(None)),
(10, 10, 10), (2, (10, 10))),
((slice(1, 3), slice(None), slice(None)),
(10, 10, 10), (3, (2, 10, 10))),
((slice(None), 1, slice(None)),
(10, 10, 10), (2, (10, 10))),
((slice(None), slice(1, 3), slice(None)),
(10, 10, 10), (3, (10, 2, 10))),
((slice(None), slice(None), 1),
(10, 10, 10), (2, (10, 10))),
((slice(None), slice(None), slice(1, 3), ),
(10, 10, 10), (3, (10, 10, 2))),
((slice(None), slice(None), slice(1, 20), ),
(10, 10, 10), (3, (10, 10, 9))),
]
for slc, shape, exp in tests:
explen, exp = exp
gotlen, got = imagewrap.expectedShape(slc, shape)
assert explen == gotlen
assert tuple(exp) == tuple(got)
def test_sliceObjToSliceTuple(): def test_sliceObjToSliceTuple():
func = imagewrap.sliceObjToSliceTuple func = imagewrap.sliceObjToSliceTuple
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment