@@ -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