Skip to content

Commit 494784a

Browse files
committed
RF: Move slicer validation into image
1 parent 1512329 commit 494784a

File tree

1 file changed

+61
-21
lines changed

1 file changed

+61
-21
lines changed

nibabel/spatialimages.py

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -330,21 +330,36 @@ class SpatialImage(DataobjImage):
330330
_spatial_dims = slice(0, 3)
331331

332332
class Slicer(object):
333+
''' Slicing interface that returns a new image with an updated affine
334+
'''
333335
def __init__(self, img):
334336
self.img = img
335337

336-
def __getitem__(self, idx):
337-
idx = self._validate(idx)
338-
dataobj = self.img.dataobj[idx]
339-
affine = self.img.slice_affine(idx)
340-
return self.img.__class__(dataobj, affine, self.img.header)
338+
def __getitem__(self, slicer):
339+
klass = self.img.__class__
340+
if not klass.makeable:
341+
raise NotImplementedError(
342+
"Cannot slice un-makeable image types")
343+
344+
slicer = self.img._check_slicing(self._arr_to_slice(slicer),
345+
self.img.shape)
346+
dataobj = self.img.dataobj[slicer]
347+
affine = self.img._slice_affine(slicer)
348+
return klass(dataobj.copy(), affine, self.img.header)
349+
350+
def _arr_to_slice(self, slicer):
351+
''' Convert single item sequence indices to slices '''
352+
if not isinstance(slicer, tuple):
353+
slicer = (slicer,)
341354

342-
def _validate(self, idx):
343-
idx = canonical_slicers(idx, self.img.shape)
344-
for slicer in idx:
345-
if isinstance(slicer, int):
346-
raise IndexError("Cannot slice image with integer")
347-
return idx
355+
out = []
356+
for subslicer in slicer:
357+
arr = np.asarray(subslicer)
358+
if arr.shape == (1,):
359+
subslicer = slice(arr[0], arr[0] + 1)
360+
out.append(subslicer)
361+
362+
return tuple(out)
348363

349364
def __init__(self, dataobj, affine, header=None,
350365
extra=None, file_map=None):
@@ -482,22 +497,49 @@ def from_image(klass, img):
482497
klass.header_class.from_header(img.header),
483498
extra=img.extra.copy())
484499

485-
def slice_affine(self, idx):
500+
def _check_slicing(self, slicer, return_spatial=False):
501+
''' Canonicalize slicers and check for scalar indices in spatial dims
502+
503+
Parameters
504+
----------
505+
slicer : object
506+
something that can be used to slice an array as in
507+
``arr[sliceobj]``
508+
return_spatial : bool
509+
return only slices along spatial dimensions (x, y, z)
510+
511+
Returns
512+
-------
513+
slicer : object
514+
Validated slicer object that will slice image's `dataobj`
515+
without collapsing spatial dimensions
516+
'''
517+
slicer = canonical_slicers(slicer, self.shape)
518+
spatial_slices = slicer[self._spatial_dims]
519+
if any(not isinstance(subslicer, (slice, None))
520+
for subslicer in spatial_slices):
521+
raise IndexError("Scalar indices disallowed in spatial dimensions; "
522+
"Use `[x]` or `x:x+1`.")
523+
return spatial_slices if return_spatial else slicer
524+
525+
def _slice_affine(self, slicer):
486526
""" Retrieve affine for current image, if sliced by a given index
487527
488528
Applies scaling if down-sampling is applied, and adjusts the intercept
489529
to account for any cropping.
490530
491531
Parameters
492532
----------
493-
idx : numpy-compatible slice index
533+
slicer : object
534+
something that can be used to slice an array as in
535+
``arr[sliceobj]``
494536
495537
Returns
496538
-------
497539
affine : (4,4) ndarray
498540
Affine with updated scale and intercept
499541
"""
500-
idx = canonical_slicers(idx, self.shape, check_inds=False)[:3]
542+
slicer = self._check_slicing(slicer, return_spatial=True)
501543

502544
# Transform:
503545
# sx 0 0 tx
@@ -506,14 +548,12 @@ def slice_affine(self, idx):
506548
# 0 0 0 1
507549
transform = np.eye(4, dtype=int)
508550

509-
for i, slicer in enumerate(idx):
510-
if isinstance(slicer, slice):
511-
if slicer.step == 0:
551+
for i, subslicer in enumerate(slicer):
552+
if isinstance(subslicer, slice):
553+
if subslicer.step == 0:
512554
raise ValueError("slice step cannot be 0")
513-
transform[i, i] = slicer.step if slicer.step is not None else 1
514-
transform[i, 3] = slicer.start or 0
515-
elif isinstance(slicer, int):
516-
transform[i, 3] = slicer
555+
transform[i, i] = subslicer.step if subslicer.step is not None else 1
556+
transform[i, 3] = subslicer.start or 0
517557
# If slicer is None, nothing to do
518558

519559
return self.affine.dot(transform)

0 commit comments

Comments
 (0)