140
140
from .filebasedimages import ImageFileError # flake8: noqa; for back-compat
141
141
from .viewers import OrthoSlicer3D
142
142
from .volumeutils import shape_zoom_affine
143
+ from .fileslice import canonical_slicers
143
144
from .deprecated import deprecate_with_version
144
145
from .orientations import apply_orientation , inv_ornt_aff
145
146
@@ -325,6 +326,23 @@ class SpatialImage(DataobjImage):
325
326
''' Template class for volumetric (3D/4D) images '''
326
327
header_class = SpatialHeader
327
328
329
+ class Slicer (object ):
330
+ def __init__ (self , img ):
331
+ self .img = img
332
+
333
+ def __getitem__ (self , idx ):
334
+ idx = self ._validate (idx )
335
+ dataobj = self .img .dataobj [idx ]
336
+ affine = self .img .slice_affine (idx )
337
+ return self .img .__class__ (dataobj , affine , self .img .header )
338
+
339
+ def _validate (self , idx ):
340
+ idx = canonical_slicers (idx , self .img .shape )
341
+ for slicer in idx :
342
+ if isinstance (slicer , int ):
343
+ raise IndexError ("Cannot slice image with integer" )
344
+ return idx
345
+
328
346
def __init__ (self , dataobj , affine , header = None ,
329
347
extra = None , file_map = None ):
330
348
''' Initialize image
@@ -461,17 +479,7 @@ def from_image(klass, img):
461
479
klass .header_class .from_header (img .header ),
462
480
extra = img .extra .copy ())
463
481
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 ):
482
+ def slice_affine (self , idx ):
475
483
""" Retrieve affine for current image, if sliced by a given index
476
484
477
485
Applies scaling if down-sampling is applied, and adjusts the intercept
@@ -486,93 +494,53 @@ def slice_afffine(self, idx):
486
494
affine : (4,4) ndarray
487
495
Affine with updated scale and intercept
488
496
"""
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 :]
497
+ idx = canonical_slicers (idx , self .shape , check_inds = False )[:3 ]
495
498
496
499
origin = np .array ([[0 ], [0 ], [0 ], [1 ]])
497
500
scales = np .eye (3 , dtype = int )
498
501
499
502
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
503
+ if isinstance (slicer , slice ):
504
+ origin [i ] = slicer .start or 0
505
+ if slicer .step is not None :
506
+ scales [i , i ] = slicer .step
507
+ elif isinstance (slicer , int ):
508
+ origin [i ] = slicer
509
+ # If slicer is None, nothing to do
504
510
505
511
affine = self .affine .copy ()
506
512
affine [:3 , :3 ] = scales .dot (self .affine [:3 , :3 ])
507
513
affine [:, [3 ]] = self .affine .dot (origin )
508
514
return affine
509
515
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.
516
+ @property
517
+ def slicer (self ):
518
+ """ Slicer object that returns cropped and subsampled images
518
519
519
520
The image is resliced in the current orientation; no rotation or
520
- resampling is performed.
521
+ resampling is performed, and no attempt is made to filter the image
522
+ to avoid aliasing.
523
+
521
524
The affine matrix is updated with the new intercept (and scales, if
522
525
down-sampling is used), so that all values are found at the same RAS
523
526
locations.
524
527
525
528
Slicing may include non-spatial dimensions.
526
529
However, this method does not currently adjust the repetition time in
527
530
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
531
"""
540
- if not isinstance (idx , tuple ):
541
- idx = (idx ,)
532
+ return self .Slicer (self )
542
533
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
534
552
535
def __getitem__ (self , idx ):
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.
536
+ ''' No slicing or dictionary interface for images
559
537
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 )
538
+ Use the slicer attribute to perform cropping and subsampling at your
539
+ own risk.
540
+ '''
541
+ raise TypeError ("Cannot slice image objects; consider slicing image "
542
+ "array data with `img.dataobj[slice]` or "
543
+ "`img.get_data()[slice]`" )
576
544
577
545
def orthoview (self ):
578
546
"""Plot the image using OrthoSlicer3D
0 commit comments