1212from nibabel .eulerangles import euler2mat
1313from nibabel .affines import from_matvec
1414from scipy .io import loadmat
15+ from nitransforms .linear import Affine
1516from ..io import (
1617 afni ,
1718 fsl ,
@@ -446,59 +447,36 @@ def test_regressions(file_type, test_file, data_path):
446447@pytest .mark .parametrize ("dir_y" , (- 1 , 1 ))
447448@pytest .mark .parametrize ("dir_z" , (1 , - 1 ))
448449@pytest .mark .parametrize ("swapaxes" , [
449- None , # (0, 1), (1, 2), (0, 2),
450+ None , (0 , 1 ), (1 , 2 ), (0 , 2 ),
450451])
451452def test_afni_oblique (tmpdir , parameters , swapaxes , testdata_path , dir_x , dir_y , dir_z ):
452453 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" )
481461
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" )
483465
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
491469
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 )
495473
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"
497476 assert check_call ([cmd ], shell = True ) == 0
498477
499- # Check the target grid by 3dWarp and the affine & size interpolated by NiTransforms
500- deobaff , deobshape = afni ._afni_deobliqued_grid (newaff , shape )
501478 deobnii = nb .load ("deob.nii.gz" )
479+ deobaff , deobshape = afni ._afni_deobliqued_grid (img .affine , img .shape )
502480
503481 assert np .all (deobshape == deobnii .shape [:3 ])
504482 assert np .allclose (deobaff , deobnii .affine )
@@ -509,13 +487,21 @@ def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y,
509487 deobaff ,
510488 deobnii .header
511489 )).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+
513500 diff = (
514501 np .asanyarray (deobnii .dataobj , dtype = "uint8" )
515502 - np .asanyarray (ntdeobnii .dataobj , dtype = "uint8" )
516503 )
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
519505
520506 # Confirm AFNI's rotation of axis is consistent with the one we introduced
521507 afni_warpdrive_inv = afni ._afni_header (
@@ -526,9 +512,34 @@ def test_afni_oblique(tmpdir, parameters, swapaxes, testdata_path, dir_x, dir_y,
526512 assert np .allclose (afni_warpdrive_inv [:3 , :3 ], R [:3 , :3 ])
527513
528514 # 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