17
17
from io import BytesIO
18
18
from ..spatialimages import (SpatialHeader , SpatialImage , HeaderDataError ,
19
19
Header , ImageDataError )
20
+ from ..imageclasses import spatial_axes_first
20
21
21
22
from unittest import TestCase
22
23
from nose .tools import (assert_true , assert_false , assert_equal ,
@@ -385,9 +386,10 @@ def test_get_data(self):
385
386
img [0 , 0 , 0 ]
386
387
# Make sure the right message gets raised:
387
388
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]`" )
391
393
assert_true (in_data is img .dataobj )
392
394
out_data = img .get_data ()
393
395
assert_true (in_data is out_data )
@@ -411,6 +413,132 @@ def test_get_data(self):
411
413
assert_false (rt_img .get_data () is out_data )
412
414
assert_array_equal (rt_img .get_data (), in_data )
413
415
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
+
414
542
def test_api_deprecations (self ):
415
543
416
544
class FakeImage (self .image_class ):
0 commit comments