Skip to content
Snippets Groups Projects
Commit a9ff9e33 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

New function sliceOverlap which does a similar job to sliceCovered but

offers a more fine-grained result. Adjusted sliceCovered - function it
doesn't need to know the image shape. updateDataRangeOnRead is no longer
memoized - slice coverage is checked before it is called, so it
shouldn't need to be memoized.
parent d8133b17
No related branches found
No related tags found
No related merge requests found
...@@ -36,7 +36,6 @@ import numpy as np ...@@ -36,7 +36,6 @@ import numpy as np
import nibabel as nib import nibabel as nib
import fsl.utils.notifier as notifier import fsl.utils.notifier as notifier
import fsl.utils.memoize as memoize
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -218,7 +217,7 @@ class ImageWrapper(notifier.Notifier): ...@@ -218,7 +217,7 @@ class ImageWrapper(notifier.Notifier):
# we have to read from the in-memory # we have to read from the in-memory
# array to get changed values. # array to get changed values.
if self.__image.in_memory: return self.__image.get_data()[sliceobj] if self.__image.in_memory: return self.__image.get_data()[sliceobj]
else: return self.__image.dataobj[ sliceobj] else: return self.__image.dataobj[ sliceobj]
def __imageIsCovered(self): def __imageIsCovered(self):
...@@ -228,10 +227,9 @@ class ImageWrapper(notifier.Notifier): ...@@ -228,10 +227,9 @@ class ImageWrapper(notifier.Notifier):
shape = self.__image.shape shape = self.__image.shape
slices = zip([0] * len(shape), shape) slices = zip([0] * len(shape), shape)
return sliceCovered(slices, self.__coverage, shape) return sliceCovered(slices, self.__coverage)
@memoize.Instanceify(memoize.memoize(args=[0]))
def __updateDataRangeOnRead(self, slices, data): def __updateDataRangeOnRead(self, slices, data):
"""Called by :meth:`__getitem__`. Calculates the minimum/maximum """Called by :meth:`__getitem__`. Calculates the minimum/maximum
values of the given data (which has been extracted from the portion of values of the given data (which has been extracted from the portion of
...@@ -243,25 +241,19 @@ class ImageWrapper(notifier.Notifier): ...@@ -243,25 +241,19 @@ class ImageWrapper(notifier.Notifier):
:arg data: The image data at the given ``slices`` (as a ``numpy`` :arg data: The image data at the given ``slices`` (as a ``numpy``
array). array).
.. note:: Tuples must be used instead of lists or ``slice`` objects fo
the ``slices`` argument, because this method is memoized
(and ``slice``/``list`` objects are unhashable).
""" """
oldmin, oldmax = self.__range
log.debug('Updating image {} data range (current range: ' log.debug('Updating image {} data range (current range: '
'[{}, {}]; current coverage: {})'.format( '[{}, {}]; current coverage: {})'.format(
self.__name, self.__name,
self.__range[0], self.__range[0],
self.__range[1], self.__range[1],
self.__coverage)) self.__coverage))
volumes, expansions = calcExpansion(slices, self.__coverage) volumes, expansions = calcExpansion(slices, self.__coverage)
newmin = oldmin oldmin, oldmax = self.__range
newmax = oldmax newmin, newmax = oldmin, oldmax
for vol, exp in zip(volumes, expansions): for vol, exp in zip(volumes, expansions):
...@@ -317,7 +309,7 @@ class ImageWrapper(notifier.Notifier): ...@@ -317,7 +309,7 @@ class ImageWrapper(notifier.Notifier):
slices = sliceObjToSliceTuple(sliceobj, self.__image.shape) slices = sliceObjToSliceTuple(sliceobj, self.__image.shape)
if not sliceCovered(slices, self.__coverage, self.__image.shape): if not sliceCovered(slices, self.__coverage):
self.__updateDataRangeOnRead(slices, data) self.__updateDataRangeOnRead(slices, data)
return data return data
...@@ -405,7 +397,91 @@ def adjustCoverage(oldCoverage, slices): ...@@ -405,7 +397,91 @@ def adjustCoverage(oldCoverage, slices):
return newCoverage return newCoverage
def sliceCovered(slices, coverage, shape): OVERLAP_ALL = 0
"""Indicates that the slice is wholly contained within the coverage. This is
a return code for the :func:`sliceOverlap` function.
"""
OVERLAP_SOME = 1
"""Indicates that the slice partially overlaps with the coverage. This is a
return code for the :func:`sliceOverlap` function.
"""
OVERLAP_NONE = 2
"""Indicates that the slice does not overlap with the coverage. This is a
return code for the :func:`sliceOverlap` function.
"""
def sliceOverlap(slices, coverage):
"""Determines whether the given ``slices`` overlap with the given
``coverage``.
:arg slices: A sequence of (low, high) index pairs, assumed to cover
all image dimensions.
:arg coverage: A ``numpy`` array of shape ``(2, nd, nv)`` (where ``nd``
is the number of dimensions being covered, and ``nv`` is
the number of volumes (or vectors/slices) in the image,
which contains the (low, high) index pairs describing
the current image coverage.
:returns: One of the following codes:
.. autosummary::
OVERLAP_ALL
OVERLAP_SOME
OVERLAP_NONE
"""
numDims = coverage.shape[1]
lowVol, highVol = slices[numDims]
# Overlap state is calculated for each volume
overlapStates = np.zeros(highVol - lowVol)
for i, vol in enumerate(range(lowVol, highVol)):
state = OVERLAP_ALL
for dim in range(numDims):
lowCover, highCover = coverage[:, dim, vol]
lowSlice, highSlice = slices[ dim]
# No coverage
if np.isnan(lowCover) or np.isnan(highCover):
state = OVERLAP_NONE
break
# The slice is contained within the
# coverage on this dimension - check
# the other dimensions.
if lowSlice >= lowCover and highSlice <= highCover:
continue
# The slice does not overlap at all
# with the coverage on this dimension
# (or at all). No overlap - no need
# to check the other dimensions.
if lowSlice >= highCover or highSlice <= lowCover:
state = OVERLAP_NONE
break
# There is some overlap between the
# slice and coverage on this dimension
# - check the other dimensions.
state = OVERLAP_SOME
overlapStates[i] = state
if np.any(overlapStates == OVERLAP_SOME): return OVERLAP_SOME
elif np.all(overlapStates == OVERLAP_NONE): return OVERLAP_NONE
elif np.all(overlapStates == OVERLAP_ALL): return OVERLAP_ALL
def sliceCovered(slices, coverage):
"""Returns ``True`` if the portion of the image data calculated by """Returns ``True`` if the portion of the image data calculated by
the given ``slices` has already been calculated, ``False`` otherwise. the given ``slices` has already been calculated, ``False`` otherwise.
...@@ -416,16 +492,14 @@ def sliceCovered(slices, coverage, shape): ...@@ -416,16 +492,14 @@ def sliceCovered(slices, coverage, shape):
the number of volumes (or vectors/slices) in the image, the number of volumes (or vectors/slices) in the image,
which contains the (low, high) index pairs describing which contains the (low, high) index pairs describing
the current image coverage. the current image coverage.
:arg shape: The full image shape.
""" """
numDims = coverage.shape[1] numDims = coverage.shape[1]
lowVol, highVol = slices[numDims] lowVol, highVol = slices[numDims]
shape = shape[:numDims]
for vol in range(lowVol, highVol): for vol in range(lowVol, highVol):
for dim, size in enumerate(shape): for dim in range(numDims):
lowCover, highCover = coverage[:, dim, vol] lowCover, highCover = coverage[:, dim, vol]
lowSlice, highSlice = slices[ dim] lowSlice, highSlice = slices[ dim]
...@@ -433,9 +507,6 @@ def sliceCovered(slices, coverage, shape): ...@@ -433,9 +507,6 @@ def sliceCovered(slices, coverage, shape):
if np.isnan(lowCover) or np.isnan(highCover): if np.isnan(lowCover) or np.isnan(highCover):
return False return False
if lowSlice is None: lowSlice = 0
if highSlice is None: highSlice = size
if lowSlice < lowCover: return False if lowSlice < lowCover: return False
if highSlice > highCover: return False if highSlice > highCover: return False
......
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