1717from io import BytesIO
1818from ..spatialimages import (SpatialHeader , SpatialImage , HeaderDataError ,
1919 Header , ImageDataError )
20+ from ..imageclasses import spatial_axes_first
2021
2122from unittest import TestCase
2223from nose .tools import (assert_true , assert_false , assert_equal ,
@@ -385,9 +386,10 @@ def test_get_data(self):
385386 img [0 , 0 , 0 ]
386387 # Make sure the right message gets raised:
387388 assert_equal (str (exception_manager .exception ),
388- ("Cannot slice image objects; consider slicing image "
389- "array data with `img.dataobj[slice]` or "
390- "`img.get_data()[slice]`" ))
389+ "Cannot slice image objects; consider using "
390+ "`img.slicer[slice]` to generate a sliced image (see "
391+ "documentation for caveats) or slicing image array data "
392+ "with `img.dataobj[slice]` or `img.get_data()[slice]`" )
391393 assert_true (in_data is img .dataobj )
392394 out_data = img .get_data ()
393395 assert_true (in_data is out_data )
@@ -411,6 +413,132 @@ def test_get_data(self):
411413 assert_false (rt_img .get_data () is out_data )
412414 assert_array_equal (rt_img .get_data (), in_data )
413415
416+ def test_slicer (self ):
417+ img_klass = self .image_class
418+ in_data_template = np .arange (240 , dtype = np .int16 )
419+ base_affine = np .eye (4 )
420+ t_axis = None
421+ for dshape in ((4 , 5 , 6 , 2 ), # Time series
422+ (8 , 5 , 6 )): # Volume
423+ in_data = in_data_template .copy ().reshape (dshape )
424+ img = img_klass (in_data , base_affine .copy ())
425+
426+ if not spatial_axes_first (img ):
427+ with assert_raises (ValueError ):
428+ img .slicer
429+ continue
430+
431+ assert_true (hasattr (img .slicer , '__getitem__' ))
432+
433+ # Note spatial zooms are always first 3, even when
434+ spatial_zooms = img .header .get_zooms ()[:3 ]
435+
436+ # Down-sample with [::2, ::2, ::2] along spatial dimensions
437+ sliceobj = [slice (None , None , 2 )] * 3 + \
438+ [slice (None )] * (len (dshape ) - 3 )
439+ downsampled_img = img .slicer [tuple (sliceobj )]
440+ assert_array_equal (downsampled_img .header .get_zooms ()[:3 ],
441+ np .array (spatial_zooms ) * 2 )
442+
443+ max4d = (hasattr (img .header , '_structarr' ) and
444+ 'dims' in img .header ._structarr .dtype .fields and
445+ img .header ._structarr ['dims' ].shape == (4 ,))
446+ # Check newaxis and single-slice errors
447+ with assert_raises (IndexError ):
448+ img .slicer [None ]
449+ with assert_raises (IndexError ):
450+ img .slicer [0 ]
451+ # Axes 1 and 2 are always spatial
452+ with assert_raises (IndexError ):
453+ img .slicer [:, None ]
454+ with assert_raises (IndexError ):
455+ img .slicer [:, 0 ]
456+ with assert_raises (IndexError ):
457+ img .slicer [:, :, None ]
458+ with assert_raises (IndexError ):
459+ img .slicer [:, :, 0 ]
460+ if len (img .shape ) == 4 :
461+ if max4d :
462+ with assert_raises (ValueError ):
463+ img .slicer [:, :, :, None ]
464+ else :
465+ # Reorder non-spatial axes
466+ assert_equal (img .slicer [:, :, :, None ].shape ,
467+ img .shape [:3 ] + (1 ,) + img .shape [3 :])
468+ # 4D to 3D using ellipsis or slices
469+ assert_equal (img .slicer [..., 0 ].shape , img .shape [:- 1 ])
470+ assert_equal (img .slicer [:, :, :, 0 ].shape , img .shape [:- 1 ])
471+ else :
472+ # 3D Analyze/NIfTI/MGH to 4D
473+ assert_equal (img .slicer [:, :, :, None ].shape , img .shape + (1 ,))
474+ if len (img .shape ) == 3 :
475+ # Slices exceed dimensions
476+ with assert_raises (IndexError ):
477+ img .slicer [:, :, :, :, None ]
478+ elif max4d :
479+ with assert_raises (ValueError ):
480+ img .slicer [:, :, :, :, None ]
481+ else :
482+ assert_equal (img .slicer [:, :, :, :, None ].shape ,
483+ img .shape + (1 ,))
484+
485+ # Crop by one voxel in each dimension
486+ sliced_i = img .slicer [1 :]
487+ sliced_j = img .slicer [:, 1 :]
488+ sliced_k = img .slicer [:, :, 1 :]
489+ sliced_ijk = img .slicer [1 :, 1 :, 1 :]
490+
491+ # No scaling change
492+ assert_array_equal (sliced_i .affine [:3 , :3 ], img .affine [:3 , :3 ])
493+ assert_array_equal (sliced_j .affine [:3 , :3 ], img .affine [:3 , :3 ])
494+ assert_array_equal (sliced_k .affine [:3 , :3 ], img .affine [:3 , :3 ])
495+ assert_array_equal (sliced_ijk .affine [:3 , :3 ], img .affine [:3 , :3 ])
496+ # Translation
497+ assert_array_equal (sliced_i .affine [:, 3 ], [1 , 0 , 0 , 1 ])
498+ assert_array_equal (sliced_j .affine [:, 3 ], [0 , 1 , 0 , 1 ])
499+ assert_array_equal (sliced_k .affine [:, 3 ], [0 , 0 , 1 , 1 ])
500+ assert_array_equal (sliced_ijk .affine [:, 3 ], [1 , 1 , 1 , 1 ])
501+
502+ # No change to affines with upper-bound slices
503+ assert_array_equal (img .slicer [:1 , :1 , :1 ].affine , img .affine )
504+
505+ # Yell about step = 0
506+ with assert_raises (ValueError ):
507+ img .slicer [:, ::0 ]
508+ with assert_raises (ValueError ):
509+ img .slicer .slice_affine ((slice (None ), slice (None , None , 0 )))
510+
511+ # Don't permit zero-length slices
512+ with assert_raises (IndexError ):
513+ img .slicer [:0 ]
514+
515+ # No fancy indexing
516+ with assert_raises (IndexError ):
517+ img .slicer [[0 ]]
518+ with assert_raises (IndexError ):
519+ img .slicer [[- 1 ]]
520+ with assert_raises (IndexError ):
521+ img .slicer [[0 ], [- 1 ]]
522+
523+ # Check data is consistent with slicing numpy arrays
524+ slice_elems = (None , Ellipsis , 0 , 1 , - 1 , [0 ], [1 ], [- 1 ],
525+ slice (None ), slice (1 ), slice (- 1 ), slice (1 , - 1 ))
526+ for n_elems in range (6 ):
527+ for _ in range (1 if n_elems == 0 else 10 ):
528+ sliceobj = tuple (
529+ np .random .choice (slice_elems , n_elems ).tolist ())
530+ try :
531+ sliced_img = img .slicer [sliceobj ]
532+ except (IndexError , ValueError ):
533+ # Only checking valid slices
534+ pass
535+ else :
536+ sliced_data = in_data [sliceobj ]
537+ assert_array_equal (sliced_data , sliced_img .get_data ())
538+ assert_array_equal (sliced_data , sliced_img .dataobj )
539+ assert_array_equal (sliced_data , img .dataobj [sliceobj ])
540+ assert_array_equal (sliced_data , img .get_data ()[sliceobj ])
541+
414542 def test_api_deprecations (self ):
415543
416544 class FakeImage (self .image_class ):
0 commit comments