From 3f5dc84131fc19ed1daaaa75cd3fa6b33b998484 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Sat, 4 Jun 2016 16:53:00 +0100 Subject: [PATCH] Documentation for Image and ImageWrapper classes. --- fsl/data/image.py | 53 +++++++++++++++++++++++++++------------- fsl/data/imagewrapper.py | 46 ++++++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 25 deletions(-) diff --git a/fsl/data/image.py b/fsl/data/image.py index 9f9777292..8bdde463f 100644 --- a/fsl/data/image.py +++ b/fsl/data/image.py @@ -285,7 +285,8 @@ class Nifti1(object): class Image(Nifti1, notifier.Notifier): """Class which represents a 3D/4D NIFTI1 image. Internally, the image - is loaded/stored using :mod:`nibabel`. + is loaded/stored using a :mod:`nibabel.nifti1.Nifti1Image`, and data + access managed by a :class:`.ImageWrapper`. .. todo:: If the image appears to be large, it is loaded using the @@ -294,7 +295,7 @@ class Image(Nifti1, notifier.Notifier): In addition to the attributes added by the :meth:`Nifti1.__init__` method, - the following attributes are present on an ``Image`` instance as + the following read-only properties are present on an ``Image`` instance as properties (https://docs.python.org/2/library/functions.html#property): @@ -312,9 +313,11 @@ class Image(Nifti1, notifier.Notifier): saved to disk, ``False`` if it is in-memory, or has been edited. - ``dataRange`` The minimum/maximum values in the image. This may not - be accurate, and may also change as more image data - is loaded from disk. + ``dataRange`` The minimum/maximum values in the image. Depending upon + the value of the ``calcRange`` parameter to + :meth:`__init__`, this may be calculated when the ``Image`` + is created, or may be incrementally updated as more image + data is loaded from disk. ============== ====================================================== @@ -323,6 +326,7 @@ class Image(Nifti1, notifier.Notifier): by registering on the following _topic_ names (see the :class:`.Notifier` class documentation): + =============== ====================================================== ``'data'`` This topic is notified whenever the image data changes (via the :meth:`__setitem__` method). @@ -342,7 +346,7 @@ class Image(Nifti1, notifier.Notifier): header=None, xform=None, loadData=True, - calcRange=False): + calcRange=True): """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, @@ -369,10 +373,10 @@ class Image(Nifti1, notifier.Notifier): The data may be loaded into memory later on via the :meth:`loadData` method. - :arg calcRange: If ``True``, the image range is calculated immediately. - Otherwise (the default), the image range is - incrementally updated as more data is read from memory - or disk. + :arg calcRange: If ``True`` (the default), the image range is + calculated immediately (vi a call to :meth:`calcRange`). + Otherwise, the image range is incrementally updated as + more data is read from memory or disk. """ import nibabel as nib @@ -456,21 +460,21 @@ class Image(Nifti1, notifier.Notifier): @property def name(self): - """ - """ + """Returns the name of this ``Image``. """ return self.__name @property def dataSource(self): - """ + """Returns the data source (e.g. file name) that this ``Image`` was + loaded from (``None`` if this image only exists in memory). """ return self.__dataSource @property def nibImage(self): - """ + """Returns a reference to the ``nibabel.nifti1.Nifti1Image`` instance. """ return self.__nibImage @@ -485,12 +489,17 @@ class Image(Nifti1, notifier.Notifier): @property def dataRange(self): - """ + """Returns the image data range as a ``(min, max)`` tuple. If the + ``calcRange`` parameter to :meth:`__init__` was ``False``, these + values may not be accurate, and may change as more image data is + accessed. + + If the data range has not been no data has been accessed, + ``(None, None)`` is returned. """ if self.__imageWrapper is None: drange = (None, None) else: drange = self.__imageWrapper.dataRange - # Fall back to the cal_min/max # fields in the NIFTI1 header # if we don't yet know anything @@ -521,6 +530,14 @@ class Image(Nifti1, notifier.Notifier): def calcRange(self, sizethres=None): + """Forces calculation of the image data range. + + :arg sizethres: If not ``None``, specifies an image size threshold + (threshold on the number of values). If the number + of values in the image is greater than this threshold, + the range is calculated on a sample (the first volume + for a 4D image, or slice for a 3D image). + """ # The ImageWrapper automatically calculates # the range of the specified slice, whenever @@ -542,7 +559,9 @@ class Image(Nifti1, notifier.Notifier): def __getitem__(self, sliceobj): - """ + """Access the image data with the specified ``sliceobj``. + + :arg sliceobj: Something which can slice the image data. """ return self.__imageWrapper.__getitem__(sliceobj) diff --git a/fsl/data/imagewrapper.py b/fsl/data/imagewrapper.py index bd8e73d9b..a666b51cc 100644 --- a/fsl/data/imagewrapper.py +++ b/fsl/data/imagewrapper.py @@ -4,7 +4,8 @@ # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # -"""This module provides the :class:`ImageWrapper` class, +"""This module provides the :class:`ImageWrapper` class, which can be used +to manage data access to ``nibabel`` NIFTI images. """ @@ -21,13 +22,27 @@ log = logging.getLogger(__name__) class ImageWrapper(notifier.Notifier): - """ + """The ``ImageWrapper`` class is a convenience class which manages data + access to ``nibabel`` NIFTI images. The ``ImageWrapper`` class can be + used to: + + - Control whether the image is loaded into memory, or kept on disk + + - Incrementally update the known image data range, as more image + data is read in. + + + The ``ImageWrapper`` implements the :class:`.Notifier` interface. + Listeners can register to be notified whenever the known image data range + is updated. The data range can be accessed via the :attr:`dataRange` + property. - Incrementally updates the data range as more image data is accessed. + + .. todo:: Figure out if NIFTI2 can be supported as well. """ def __init__(self, image, name=None, loadData=False): - """ + """Create an ``ImageWrapper``. :arg image: A ``nibabel.Nifti1Image``. @@ -137,7 +152,11 @@ class ImageWrapper(notifier.Notifier): def __updateSliceCoverage(self, slices): - """ + """Updates the known portion of the image (with respect to the image + data range) according to the given set of slice indices. + + :arg slices: A sequence of ``(low, high)`` index pairs, one for each + dimension in the image. """ for dim, (lowSlc, highSlc) in enumerate(slices): @@ -155,7 +174,10 @@ class ImageWrapper(notifier.Notifier): @memoize.Instanceify(memoize.memoize(args=[0])) def __updateDataRangeOnRead(self, slices, data): - """ + """Called by :meth:`__getitem__`. Calculates the minimum/maximum + values of the given data (which has been extracted from the portion of + the image specified by ``slices``), and updates the known data range + of the image. :arg slices: A sequence of ``(low, high)`` index pairs, one for each dimension in the image. Tuples are used instead of @@ -194,14 +216,22 @@ class ImageWrapper(notifier.Notifier): def __getitem__(self, sliceobj): - """ + """Returns the image data for the given ``sliceobj``, and updates + the known image data range if necessary. + + .. note:: If the image data is in memory, it is accessed + directly, via the ``nibabel.Nifti1Image.get_data`` + method. Otherwise the image data is accessed through + the ``nibabel.Nifti1Image.dataobj`` array proxy. + + :arg sliceobj: Something which can slice the image data. """ sliceobj = nib.fileslice.canonical_slicers( sliceobj, self.__image.shape) # TODO Cache 3D images for large 4D volumes, - # so you don't have to hit the disk + # so you don't have to hit the disk? # If the image has not been loaded # into memory, we can use the nibabel -- GitLab