@@ -322,24 +322,95 @@ class ImageDataError(Exception):
322322 pass
323323
324324
325- class SpatialImage (DataobjImage ):
326- ''' Template class for volumetric (3D/4D) images '''
327- header_class = SpatialHeader
325+ class SpatialFirstSlicer (object ):
326+ ''' Slicing interface that returns a new image with an updated affine
327+
328+ Checks that an image's first three axes are spatial
329+ '''
330+ def __init__ (self , img ):
331+ from .imageclasses import spatial_axes_first
332+ if not spatial_axes_first (img ):
333+ raise ValueError ("Cannot predict position of spatial axes for "
334+ "Image type " + img .__class__ .__name__ )
335+ self .img = img
336+
337+ def __getitem__ (self , slicer ):
338+ try :
339+ slicer = self .check_slicing (slicer )
340+ except ValueError as err :
341+ raise IndexError (* err .args )
342+ dataobj = self .img .dataobj [slicer ]
343+ affine = self .slice_affine (slicer )
344+ return self .img .__class__ (dataobj .copy (), affine , self .img .header )
345+
346+ def check_slicing (self , slicer , return_spatial = False ):
347+ ''' Canonicalize slicers and check for scalar indices in spatial dims
348+
349+ Parameters
350+ ----------
351+ slicer : object
352+ something that can be used to slice an array as in
353+ ``arr[sliceobj]``
354+ return_spatial : bool
355+ return only slices along spatial dimensions (x, y, z)
328356
329- class Slicer (object ):
330- ''' Slicing interface that returns a new image with an updated affine
357+ Returns
358+ -------
359+ slicer : object
360+ Validated slicer object that will slice image's `dataobj`
361+ without collapsing spatial dimensions
331362 '''
332- def __init__ (self , img ):
333- self .img = img
363+ slicer = canonical_slicers (slicer , self .img .shape )
364+ spatial_slices = slicer [:3 ]
365+ for subslicer in spatial_slices :
366+ if subslicer is None :
367+ raise IndexError ("New axis not permitted in spatial dimensions" )
368+ elif isinstance (subslicer , int ):
369+ raise IndexError ("Scalar indices disallowed in spatial dimensions; "
370+ "Use `[x]` or `x:x+1`." )
371+ return spatial_slices if return_spatial else slicer
334372
335- def __getitem__ (self , slicer ):
336- try :
337- slicer = self .img ._check_slicing (slicer )
338- except ValueError as err :
339- raise IndexError (* err .args )
340- dataobj = self .img .dataobj [slicer ]
341- affine = self .img ._slice_affine (slicer )
342- return self .img .__class__ (dataobj .copy (), affine , self .img .header )
373+ def slice_affine (self , slicer ):
374+ """ Retrieve affine for current image, if sliced by a given index
375+
376+ Applies scaling if down-sampling is applied, and adjusts the intercept
377+ to account for any cropping.
378+
379+ Parameters
380+ ----------
381+ slicer : object
382+ something that can be used to slice an array as in
383+ ``arr[sliceobj]``
384+
385+ Returns
386+ -------
387+ affine : (4,4) ndarray
388+ Affine with updated scale and intercept
389+ """
390+ slicer = self .check_slicing (slicer , return_spatial = True )
391+
392+ # Transform:
393+ # sx 0 0 tx
394+ # 0 sy 0 ty
395+ # 0 0 sz tz
396+ # 0 0 0 1
397+ transform = np .eye (4 , dtype = int )
398+
399+ for i , subslicer in enumerate (slicer ):
400+ if isinstance (subslicer , slice ):
401+ if subslicer .step == 0 :
402+ raise ValueError ("slice step cannot be 0" )
403+ transform [i , i ] = subslicer .step if subslicer .step is not None else 1
404+ transform [i , 3 ] = subslicer .start or 0
405+ # If slicer is None, nothing to do
406+
407+ return self .img .affine .dot (transform )
408+
409+
410+ class SpatialImage (DataobjImage ):
411+ ''' Template class for volumetric (3D/4D) images '''
412+ header_class = SpatialHeader
413+ ImageSlicer = SpatialFirstSlicer
343414
344415 def __init__ (self , dataobj , affine , header = None ,
345416 extra = None , file_map = None ):
@@ -477,69 +548,6 @@ def from_image(klass, img):
477548 klass .header_class .from_header (img .header ),
478549 extra = img .extra .copy ())
479550
480- def _check_slicing (self , slicer , return_spatial = False ):
481- ''' Canonicalize slicers and check for scalar indices in spatial dims
482-
483- Parameters
484- ----------
485- slicer : object
486- something that can be used to slice an array as in
487- ``arr[sliceobj]``
488- return_spatial : bool
489- return only slices along spatial dimensions (x, y, z)
490-
491- Returns
492- -------
493- slicer : object
494- Validated slicer object that will slice image's `dataobj`
495- without collapsing spatial dimensions
496- '''
497- slicer = canonical_slicers (slicer , self .shape )
498- spatial_slices = slicer [:3 ]
499- for subslicer in spatial_slices :
500- if subslicer is None :
501- raise IndexError ("New axis not permitted in spatial dimensions" )
502- elif isinstance (subslicer , int ):
503- raise IndexError ("Scalar indices disallowed in spatial dimensions; "
504- "Use `[x]` or `x:x+1`." )
505- return spatial_slices if return_spatial else slicer
506-
507- def _slice_affine (self , slicer ):
508- """ Retrieve affine for current image, if sliced by a given index
509-
510- Applies scaling if down-sampling is applied, and adjusts the intercept
511- to account for any cropping.
512-
513- Parameters
514- ----------
515- slicer : object
516- something that can be used to slice an array as in
517- ``arr[sliceobj]``
518-
519- Returns
520- -------
521- affine : (4,4) ndarray
522- Affine with updated scale and intercept
523- """
524- slicer = self ._check_slicing (slicer , return_spatial = True )
525-
526- # Transform:
527- # sx 0 0 tx
528- # 0 sy 0 ty
529- # 0 0 sz tz
530- # 0 0 0 1
531- transform = np .eye (4 , dtype = int )
532-
533- for i , subslicer in enumerate (slicer ):
534- if isinstance (subslicer , slice ):
535- if subslicer .step == 0 :
536- raise ValueError ("slice step cannot be 0" )
537- transform [i , i ] = subslicer .step if subslicer .step is not None else 1
538- transform [i , 3 ] = subslicer .start or 0
539- # If slicer is None, nothing to do
540-
541- return self .affine .dot (transform )
542-
543551 @property
544552 def slicer (self ):
545553 """ Slicer object that returns cropped and subsampled images
@@ -558,11 +566,7 @@ def slicer(self):
558566
559567 .. _aliasing: https://en.wikipedia.org/wiki/Aliasing
560568 """
561- from .imageclasses import spatial_axes_first
562- if not spatial_axes_first (self ):
563- raise ValueError ("Cannot predict position of spatial axes for "
564- "Image type " + self .__class__ .__name__ )
565- return self .Slicer (self )
569+ return self .ImageSlicer (self )
566570
567571
568572 def __getitem__ (self , idx ):
0 commit comments