Skip to content

Commit c233083

Browse files
committed
RF: Move to img.slicer approach
1 parent 10eb2d0 commit c233083

File tree

1 file changed

+41
-73
lines changed

1 file changed

+41
-73
lines changed

nibabel/spatialimages.py

Lines changed: 41 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
from .filebasedimages import ImageFileError # flake8: noqa; for back-compat
141141
from .viewers import OrthoSlicer3D
142142
from .volumeutils import shape_zoom_affine
143+
from .fileslice import canonical_slicers
143144
from .deprecated import deprecate_with_version
144145
from .orientations import apply_orientation, inv_ornt_aff
145146

@@ -325,6 +326,23 @@ class SpatialImage(DataobjImage):
325326
''' Template class for volumetric (3D/4D) images '''
326327
header_class = SpatialHeader
327328

329+
class Slicer(object):
330+
def __init__(self, img):
331+
self.img = img
332+
333+
def __getitem__(self, idx):
334+
idx = self._validate(idx)
335+
dataobj = self.img.dataobj[idx]
336+
affine = self.img.slice_affine(idx)
337+
return self.img.__class__(dataobj, affine, self.img.header)
338+
339+
def _validate(self, idx):
340+
idx = canonical_slicers(idx, self.img.shape)
341+
for slicer in idx:
342+
if isinstance(slicer, int):
343+
raise IndexError("Cannot slice image with integer")
344+
return idx
345+
328346
def __init__(self, dataobj, affine, header=None,
329347
extra=None, file_map=None):
330348
''' Initialize image
@@ -461,17 +479,7 @@ def from_image(klass, img):
461479
klass.header_class.from_header(img.header),
462480
extra=img.extra.copy())
463481

464-
@staticmethod
465-
def requires_downsampling(idx):
466-
for slicer in idx:
467-
if isinstance(slicer, slice) and slicer.step not in (1, None):
468-
return True
469-
elif np.asanyarray(slicer).shape != (1,):
470-
return True
471-
472-
return False
473-
474-
def slice_afffine(self, idx):
482+
def slice_affine(self, idx):
475483
""" Retrieve affine for current image, if sliced by a given index
476484
477485
Applies scaling if down-sampling is applied, and adjusts the intercept
@@ -486,93 +494,53 @@ def slice_afffine(self, idx):
486494
affine : (4,4) ndarray
487495
Affine with updated scale and intercept
488496
"""
489-
# Expand ellipsis to retrieve bounds
490-
if Ellipsis in idx:
491-
i = idx.index(Ellipsis)
492-
subslices = tuple(slice(None)
493-
for _ in range(len(self.shape) - len(idx.shape)))
494-
idx = idx[:i] + subslices + idx[i + 1:]
497+
idx = canonical_slicers(idx, self.shape, check_inds=False)[:3]
495498

496499
origin = np.array([[0], [0], [0], [1]])
497500
scales = np.eye(3, dtype=int)
498501

499502
for i, slicer in enumerate(idx[:3]):
500-
new_origin[i] = (slicer.start or 0 if isinstance(slicer, slice)
501-
else np.asanyarray(slicer)[0])
502-
if slicer.step is not None:
503-
scales[i, i] = slicer.step
503+
if isinstance(slicer, slice):
504+
origin[i] = slicer.start or 0
505+
if slicer.step is not None:
506+
scales[i, i] = slicer.step
507+
elif isinstance(slicer, int):
508+
origin[i] = slicer
509+
# If slicer is None, nothing to do
504510

505511
affine = self.affine.copy()
506512
affine[:3, :3] = scales.dot(self.affine[:3, :3])
507513
affine[:, [3]] = self.affine.dot(origin)
508514
return affine
509515

510-
def slice(self, idx, allow_downsampling=False):
511-
""" Slice image to specification
512-
513-
Nibabel performs no spatial filtering, so, in order to avoid aliasing
514-
data, only slices that specify cropping data are permitted.
515-
If the image has been filtered, or aliasing is not a concern,
516-
``allow_downsampling=True`` will disable this check and permit more
517-
complicated slices.
516+
@property
517+
def slicer(self):
518+
""" Slicer object that returns cropped and subsampled images
518519
519520
The image is resliced in the current orientation; no rotation or
520-
resampling is performed.
521+
resampling is performed, and no attempt is made to filter the image
522+
to avoid aliasing.
523+
521524
The affine matrix is updated with the new intercept (and scales, if
522525
down-sampling is used), so that all values are found at the same RAS
523526
locations.
524527
525528
Slicing may include non-spatial dimensions.
526529
However, this method does not currently adjust the repetition time in
527530
the image header.
528-
529-
Parameters
530-
----------
531-
idx : numpy-compatible slice index
532-
allow_downsampling : bool (default: False)
533-
Permit indices that specify down-sampling
534-
535-
Returns
536-
-------
537-
resliced_img : ``spatialimage``
538-
Version of `img` with resliced data array and updated affine matrix
539531
"""
540-
if not isinstance(idx, tuple):
541-
idx = (idx,)
532+
return self.Slicer(self)
542533

543-
if not allow_downsampling and self.requires_downsampling(idx):
544-
raise IndexError('slicing requires downsampling, please use '
545-
'img.slice(..., allow_downsampling=True) instead')
546-
547-
# Get bounded data, allow numpy to complain about any indexing errors
548-
dataobj = self.dataobj[slices]
549-
affine = self.slice_affine(idx)
550-
return self.__class__(dataobj, affine, self.header)
551534

552535
def __getitem__(self, idx):
553-
""" Crop image to specified slices
554-
555-
The image is cropped in the current orientation; no rotation or
556-
resampling is performed.
557-
The affine matrix is updated with the new intercept, so that all
558-
values are found at the same RAS locations.
536+
''' No slicing or dictionary interface for images
559537
560-
Cropping may include non-spatial dimensions.
561-
562-
Down-sampling and other slicing operations may be performed with
563-
``img.slice(..., allow_downsampling=True)``.
564-
565-
Parameters
566-
----------
567-
idx : tuple containing (slice(step=None), Ellipsis, array-like with size (1,))
568-
numpy-compatible slice index describing cropping only
569-
570-
Returns
571-
-------
572-
cropped_img : ``spatialimage``
573-
Version of `img` with cropped data array and updated affine matrix
574-
"""
575-
return self.slice(idx)
538+
Use the slicer attribute to perform cropping and subsampling at your
539+
own risk.
540+
'''
541+
raise TypeError("Cannot slice image objects; consider slicing image "
542+
"array data with `img.dataobj[slice]` or "
543+
"`img.get_data()[slice]`")
576544

577545
def orthoview(self):
578546
"""Plot the image using OrthoSlicer3D

0 commit comments

Comments
 (0)