@@ -322,24 +322,95 @@ class ImageDataError(Exception):
322
322
pass
323
323
324
324
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)
328
356
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
331
362
'''
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
334
372
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
343
414
344
415
def __init__ (self , dataobj , affine , header = None ,
345
416
extra = None , file_map = None ):
@@ -477,69 +548,6 @@ def from_image(klass, img):
477
548
klass .header_class .from_header (img .header ),
478
549
extra = img .extra .copy ())
479
550
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
-
543
551
@property
544
552
def slicer (self ):
545
553
""" Slicer object that returns cropped and subsampled images
@@ -558,11 +566,7 @@ def slicer(self):
558
566
559
567
.. _aliasing: https://en.wikipedia.org/wiki/Aliasing
560
568
"""
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 )
566
570
567
571
568
572
def __getitem__ (self , idx ):
0 commit comments