12
12
from nibabel .eulerangles import euler2mat
13
13
from nibabel .affines import from_matvec
14
14
from scipy .io import loadmat
15
+ from nitransforms .linear import Affine
15
16
from ..io import (
16
17
afni ,
17
18
fsl ,
@@ -446,59 +447,36 @@ def test_regressions(file_type, test_file, data_path):
446
447
@pytest .mark .parametrize ("dir_y" , (- 1 , 1 ))
447
448
@pytest .mark .parametrize ("dir_z" , (1 , - 1 ))
448
449
@pytest .mark .parametrize ("swapaxes" , [
449
- None , # (0, 1), (1, 2), (0, 2),
450
+ None , (0 , 1 ), (1 , 2 ), (0 , 2 ),
450
451
])
451
452
def test_afni_oblique (tmpdir , parameters , swapaxes , testdata_path , dir_x , dir_y , dir_z ):
452
453
tmpdir .chdir ()
453
- img = nb .load (testdata_path / "someones_anatomy.nii.gz" )
454
- shape = np .array (img .shape [:3 ])
455
- hdr = img .header .copy ()
456
- aff = img .affine .copy ()
457
- data = np .asanyarray (img .dataobj , dtype = "uint8" )
458
-
459
- directions = (dir_x , dir_y , dir_z )
460
- if directions != (1 , 1 , 1 ):
461
- last_ijk = np .hstack ((shape - 1 , 1.0 ))
462
- last_xyz = aff @ last_ijk
463
- aff = np .diag ((dir_x , dir_y , dir_z , 1 )) @ aff
464
-
465
- for ax in range (3 ):
466
- if (directions [ax ] == - 1 ):
467
- aff [ax , 3 ] = last_xyz [ax ]
468
- data = np .flip (data , ax )
469
-
470
- hdr .set_qform (aff , code = 1 )
471
- hdr .set_sform (aff , code = 1 )
472
- img .__class__ (data , aff , hdr ).to_filename ("flips.nii.gz" )
473
-
474
- if swapaxes is not None :
475
- data = np .swapaxes (data , swapaxes [0 ], swapaxes [1 ])
476
- aff [list (reversed (swapaxes )), :] = aff [(swapaxes ), :]
477
-
478
- hdr .set_qform (aff , code = 1 )
479
- hdr .set_sform (aff , code = 1 )
480
- img .__class__ (data , aff , hdr ).to_filename ("swaps.nii.gz" )
454
+ img , R = _generate_reoriented (
455
+ testdata_path / "someones_anatomy.nii.gz" ,
456
+ (dir_x , dir_y , dir_z ),
457
+ swapaxes ,
458
+ parameters
459
+ )
460
+ img .to_filename ("orig.nii.gz" )
481
461
482
- R = from_matvec (euler2mat (** parameters ), [0.0 , 0.0 , 0.0 ])
462
+ # Run AFNI's 3drefit -deoblique
463
+ if not shutil .which ("3drefit" ):
464
+ pytest .skip ("Command 3drefit not found on host" )
483
465
484
- # img_center = aff @ np.hstack((shape * 0.5, 1.0))
485
- # R[:3, 3] += (img_center - (R @ aff @ np.hstack((shape * 0.5, 1.0))))[:3]
486
- newaff = R @ aff
487
- hdr .set_qform (newaff , code = 1 )
488
- hdr .set_sform (newaff , code = 1 )
489
- img = img .__class__ (data , newaff , hdr )
490
- img .to_filename ("oblique.nii.gz" )
466
+ shutil .copy (f"{ tmpdir } /orig.nii.gz" , f"{ tmpdir } /deob_3drefit.nii.gz" )
467
+ cmd = f"3drefit -deoblique { tmpdir } /deob_3drefit.nii.gz"
468
+ assert check_call ([cmd ], shell = True ) == 0
491
469
492
- # Run AFNI's 3dDeoblique
493
- if not shutil . which ( "3dWarp" ):
494
- pytest . skip ( "Command 3dWarp not found on host" )
470
+ # Check that nitransforms can make out the deoblique affine:
471
+ card_aff = afni . _dicom_real_to_card ( img . affine )
472
+ assert np . allclose ( card_aff , nb . load ( "deob_3drefit.nii.gz" ). affine )
495
473
496
- cmd = f"3dWarp -verb -deoblique -NN -prefix { tmpdir } /deob.nii.gz { tmpdir } /oblique.nii.gz"
474
+ # Check the target grid by 3dWarp and the affine & size interpolated by NiTransforms
475
+ cmd = f"3dWarp -verb -deoblique -NN -prefix { tmpdir } /deob.nii.gz { tmpdir } /orig.nii.gz"
497
476
assert check_call ([cmd ], shell = True ) == 0
498
477
499
- # Check the target grid by 3dWarp and the affine & size interpolated by NiTransforms
500
- deobaff , deobshape = afni ._afni_deobliqued_grid (newaff , shape )
501
478
deobnii = nb .load ("deob.nii.gz" )
479
+ deobaff , deobshape = afni ._afni_deobliqued_grid (img .affine , img .shape )
502
480
503
481
assert np .all (deobshape == deobnii .shape [:3 ])
504
482
assert np .allclose (deobaff , deobnii .affine )
@@ -509,13 +487,21 @@ def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y,
509
487
deobaff ,
510
488
deobnii .header
511
489
)).apply (img , order = 0 )
512
- ntdeobnii .to_filename ("ntdeob.nii.gz" )
490
+
491
+ # Generate an internal box to exclude border effects
492
+ box = np .zeros (img .shape , dtype = "uint8" )
493
+ box [10 :- 10 , 10 :- 10 , 10 :- 10 ] = 1
494
+ ntdeobmask = Affine (np .eye (4 ), reference = ntdeobnii ).apply (
495
+ nb .Nifti1Image (box , img .affine , img .header ),
496
+ order = 0 ,
497
+ )
498
+ mask = np .asanyarray (ntdeobmask .dataobj , dtype = bool )
499
+
513
500
diff = (
514
501
np .asanyarray (deobnii .dataobj , dtype = "uint8" )
515
502
- np .asanyarray (ntdeobnii .dataobj , dtype = "uint8" )
516
503
)
517
- deobnii .__class__ (diff , deobnii .affine , deobnii .header ).to_filename ("diff.nii.gz" )
518
- assert np .sqrt ((diff [20 :- 20 , 20 :- 20 , 20 :- 20 ] ** 2 ).mean ()) < 0.1
504
+ assert np .sqrt ((diff [mask ] ** 2 ).mean ()) < 0.1
519
505
520
506
# Confirm AFNI's rotation of axis is consistent with the one we introduced
521
507
afni_warpdrive_inv = afni ._afni_header (
@@ -526,9 +512,34 @@ def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y,
526
512
assert np .allclose (afni_warpdrive_inv [:3 , :3 ], R [:3 , :3 ])
527
513
528
514
# Check nitransforms' estimation of warpdrive with header
529
- # Still haven't gotten my head around orientation, this test should not fail
530
- # nt_warpdrive_inv = afni._afni_warpdrive(newaff, deobaff, forward=False)
531
- # assert not np.allclose(
532
- # afni_warpdrive_inv[:3, :3],
533
- # nt_warpdrive_inv[:3, :3],
534
- # )
515
+ nt_warpdrive_inv = afni ._afni_warpdrive (img .affine , forward = False )
516
+ assert not np .allclose (afni_warpdrive_inv , nt_warpdrive_inv )
517
+
518
+
519
+ def _generate_reoriented (path , directions , swapaxes , parameters ):
520
+ img = nb .load (path )
521
+ shape = np .array (img .shape [:3 ])
522
+ hdr = img .header .copy ()
523
+ aff = img .affine .copy ()
524
+ data = np .asanyarray (img .dataobj , dtype = "uint8" )
525
+
526
+ if directions != (1 , 1 , 1 ):
527
+ last_ijk = np .hstack ((shape - 1 , 1.0 ))
528
+ last_xyz = aff @ last_ijk
529
+ aff = np .diag ((* directions , 1 )) @ aff
530
+
531
+ for ax in range (3 ):
532
+ if (directions [ax ] == - 1 ):
533
+ aff [ax , 3 ] = last_xyz [ax ]
534
+ data = np .flip (data , ax )
535
+
536
+ if swapaxes is not None :
537
+ data = np .swapaxes (data , swapaxes [0 ], swapaxes [1 ])
538
+ aff [list (reversed (swapaxes )), :] = aff [(swapaxes ), :]
539
+
540
+ R = from_matvec (euler2mat (** parameters ), [0.0 , 0.0 , 0.0 ])
541
+
542
+ newaff = R @ aff
543
+ hdr .set_qform (newaff , code = 1 )
544
+ hdr .set_sform (newaff , code = 1 )
545
+ return img .__class__ (data , newaff , hdr ), R
0 commit comments