From 2adb571163559fa8ba3e14e4ef6a58c3867f9625 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Wed, 29 Jun 2016 11:55:28 +0100 Subject: [PATCH] Image uses a SafeIndexedGzipFile for indexed images. ImageWrapper can optionally be told to calculate data ranges on a separate thread. --- fsl/data/image.py | 19 ++++++++++++++---- fsl/data/imagewrapper.py | 43 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/fsl/data/image.py b/fsl/data/image.py index abc1160e5..d2d7e967a 100644 --- a/fsl/data/image.py +++ b/fsl/data/image.py @@ -370,7 +370,8 @@ class Image(Nifti1, notifier.Notifier): xform=None, loadData=True, calcRange=True, - indexed=False): + indexed=False, + threaded=False): """Create an ``Image`` object with the given image data or file name. :arg image: A string containing the name of an image file to load, @@ -406,6 +407,11 @@ class Image(Nifti1, notifier.Notifier): :arg indexed: If ``True``, and the file is gzipped, it is opened using the :mod:`indexed_gzip` package. Otherwise the file is opened by ``nibabel``. + + + :arg threaded: If ``True``, the :class:`.ImageWrapper` will use a + separate thread for data range calculation. Defaults + to ``False``. """ import nibabel as nib @@ -480,7 +486,8 @@ class Image(Nifti1, notifier.Notifier): self.__suppressDataRange = False self.__imageWrapper = imagewrapper.ImageWrapper(self.nibImage, self.name, - loadData=loadData) + loadData=loadData, + threaded=threaded) if calcRange: self.calcRange() @@ -510,7 +517,11 @@ class Image(Nifti1, notifier.Notifier): def __del__(self): - """Closes any open file handles. """ + """Closes any open file handles, and clears some references. """ + + self.__nibImage = None + self.__imageWrapper = None + if self.__fileobj is not None: self.__fileobj.close() @@ -805,7 +816,7 @@ def loadIndexedImageFile(filename): log.debug('Loading {} using indexed gzip'.format(filename)) - fobj = igzip.IndexedGzipFile( + fobj = igzip.SafeIndexedGzipFile( filename=filename, spacing=4194304, readbuf_size=131072) diff --git a/fsl/data/imagewrapper.py b/fsl/data/imagewrapper.py index 13f0dbe67..dd13a7aaf 100644 --- a/fsl/data/imagewrapper.py +++ b/fsl/data/imagewrapper.py @@ -41,6 +41,7 @@ import numpy as np import nibabel as nib import fsl.utils.notifier as notifier +import fsl.utils.async as async log = logging.getLogger(__name__) @@ -121,7 +122,12 @@ class ImageWrapper(notifier.Notifier): """ - def __init__(self, image, name=None, loadData=False, dataRange=None): + def __init__(self, + image, + name=None, + loadData=False, + dataRange=None, + threaded=False): """Create an ``ImageWrapper``. :arg image: A ``nibabel.Nifti1Image``. @@ -137,6 +143,10 @@ class ImageWrapper(notifier.Notifier): :arg dataRange: A tuple containing the initial ``(min, max)`` data range to use. See the :meth:`reset` method for important information about this parameter. + + :arg threaded: If ``True``, the data range is updated on a + :class:`.TaskThread`. Otherwise (the default), the + data range is updated directly on reads/writes. """ self.__image = image @@ -167,6 +177,22 @@ class ImageWrapper(notifier.Notifier): if loadData: self.loadData() + if not threaded: + self.__taskThread = None + else: + self.__taskThread = async.TaskThread() + self.__taskThread.start() + + + def __del__(self): + """If this ``ImageWrapper`` was created with ``threaded=True``, + the :class:`.TaskThread` is stopped. + """ + self.__image = None + if self.__taskThread is not None: + self.__taskThread.stop() + self.__taskThraed = None + def reset(self, dataRange=None): """Reset the internal state and known data range of this @@ -426,7 +452,12 @@ class ImageWrapper(notifier.Notifier): # the provided data to avoid # reading it in again. - self.__expandCoverage(slices) + if self.__taskThread is None: + self.__expandCoverage(slices) + else: + name = '{}_read_{}'.format(id(self), slices) + if not self.__taskThread.isQueued(name): + self.__taskThread.enqueue(name, self.__expandCoverage, slices) def __updateDataRangeOnWrite(self, slices, data): @@ -476,7 +507,13 @@ class ImageWrapper(notifier.Notifier): self.__coverage[:, :, vol] = np.nan self.__volRanges[ vol, :] = np.nan - self.__expandCoverage(slices) + + if self.__taskThread is None: + self.__expandCoverage(slices) + else: + name = '{}_write_{}'.format(id(self), slices) + if not self.__taskThread.isQueued(name): + self.__taskThread.enqueue(name, self.__expandCoverage, slices) def __getitem__(self, sliceobj): -- GitLab