Skip to content

Commit fb1e4f1

Browse files
committed
ENH: Enable SpatialImage.{slice,__getitem__}
1 parent 99f46b0 commit fb1e4f1

File tree

1 file changed

+111
-5
lines changed

1 file changed

+111
-5
lines changed

nibabel/spatialimages.py

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -461,12 +461,118 @@ def from_image(klass, img):
461461
klass.header_class.from_header(img.header),
462462
extra=img.extra.copy())
463463

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):
475+
""" Retrieve affine for current image, if sliced by a given index
476+
477+
Applies scaling if down-sampling is applied, and adjusts the intercept
478+
to account for any cropping.
479+
480+
Parameters
481+
----------
482+
idx : numpy-compatible slice index
483+
484+
Returns
485+
-------
486+
affine : (4,4) ndarray
487+
Affine with updated scale and intercept
488+
"""
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:]
495+
496+
origin = np.array([[0], [0], [0], [1]])
497+
scales = np.eye(3, dtype=int)
498+
499+
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
504+
505+
affine = self.affine.copy()
506+
affine[:3, :3] = scales.dot(self.affine[:3, :3])
507+
affine[:, [3]] = self.affine.dot(origin)
508+
return affine
509+
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.
518+
519+
The image is resliced in the current orientation; no rotation or
520+
resampling is performed.
521+
The affine matrix is updated with the new intercept (and scales, if
522+
down-sampling is used), so that all values are found at the same RAS
523+
locations.
524+
525+
Slicing may include non-spatial dimensions.
526+
However, this method does not currently adjust the repetition time in
527+
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
539+
"""
540+
if not isinstance(idx, tuple):
541+
idx = (idx,)
542+
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)
551+
464552
def __getitem__(self, idx):
465-
''' No slicing or dictionary interface for images
466-
'''
467-
raise TypeError("Cannot slice image objects; consider slicing image "
468-
"array data with `img.dataobj[slice]` or "
469-
"`img.get_data()[slice]`")
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.
559+
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)
470576

471577
def orthoview(self):
472578
"""Plot the image using OrthoSlicer3D

0 commit comments

Comments
 (0)