@@ -672,3 +672,109 @@ def test_itk_linear_h5(tmpdir, data_path, testdata_path):
672
672
# Error raised if the we try to use the single affine loader
673
673
with pytest .raises (TransformIOError ):
674
674
itk .ITKLinearTransform .from_filename ("test.h5" )
675
+
676
+ # Added tests for h5 orientation bug
677
+
678
+
679
+ @pytest .mark .xfail (
680
+ reason = "GH-137/GH-171: displacement field dimension order is wrong" ,
681
+ strict = False ,
682
+ )
683
+ def test_itk_h5_field_order (tmp_path ):
684
+ """Displacement fields stored in row-major order should fail to round-trip."""
685
+ shape = (3 , 4 , 5 )
686
+ vals = np .arange (np .prod (shape ), dtype = float ).reshape (shape )
687
+ field = np .stack ([vals , vals + 100 , vals + 200 ], axis = 0 )
688
+
689
+ params = field .reshape (- 1 , order = "C" )
690
+ fixed = np .array (list (shape ) + [0 , 0 , 0 ] + [1 , 1 , 1 ] + list (np .eye (3 ).ravel ()), dtype = float )
691
+ fname = tmp_path / "field.h5"
692
+ with H5File (fname , "w" ) as f :
693
+ grp = f .create_group ("TransformGroup" )
694
+ grp .create_group ("0" )["TransformType" ] = np .array ([b"CompositeTransform_double_3_3" ])
695
+ g1 = grp .create_group ("1" )
696
+ g1 ["TransformType" ] = np .array ([b"DisplacementFieldTransform_float_3_3" ])
697
+ g1 ["TransformFixedParameters" ] = fixed
698
+ g1 ["TransformParameters" ] = params
699
+
700
+ img = itk .ITKCompositeH5 .from_filename (fname )[0 ]
701
+ expected = np .moveaxis (field , 0 , - 1 )
702
+ expected [..., (0 , 1 )] *= - 1
703
+ assert np .allclose (img .get_fdata (), expected )
704
+
705
+
706
+ def _load_composite_testdata (data_path ):
707
+ """Return the composite HDF5 and displacement field from regressions."""
708
+ h5file = data_path / "regressions" / "ants_t1_to_mniComposite.h5"
709
+ # Generated using
710
+ # CompositeTransformUtil --disassemble ants_t1_to_mniComposite.h5 \
711
+ # ants_t1_to_mniComposite
712
+ warpfile = data_path / "regressions" / (
713
+ "01_ants_t1_to_mniComposite_DisplacementFieldTransform.nii.gz"
714
+ )
715
+ if not (h5file .exists () and warpfile .exists ()):
716
+ pytest .skip ("Composite transform test data not available" )
717
+ return h5file , warpfile
718
+
719
+
720
+ @pytest .mark .xfail (
721
+ reason = "GH-137/GH-171: displacement field dimension order is wrong" ,
722
+ strict = False ,
723
+ )
724
+ def test_itk_h5_displacement_mismatch (testdata_path ):
725
+ """Composite displacements should match the standalone field"""
726
+ h5file , warpfile = _load_composite_testdata (testdata_path )
727
+ xforms = itk .ITKCompositeH5 .from_filename (h5file )
728
+ field_h5 = xforms [1 ]
729
+ field_img = itk .ITKDisplacementsField .from_filename (warpfile )
730
+
731
+ np .testing .assert_array_equal (
732
+ np .asanyarray (field_h5 .dataobj ), np .asanyarray (field_img .dataobj )
733
+ )
734
+
735
+
736
+ def test_itk_h5_transpose_fix (testdata_path ):
737
+ """Check the displacement field orientation explicitly.
738
+
739
+ ITK stores displacement fields with the vector dimension leading in
740
+ Fortran (column-major) order [1]_. Transposing the parameters from the HDF5
741
+ composite file accordingly should match the standalone displacement image.
742
+
743
+ References
744
+ ----------
745
+ .. [1] ITK Software Guide. https://itk.org/ItkSoftwareGuide.pdf
746
+ """
747
+ h5file , warpfile = _load_composite_testdata (testdata_path )
748
+
749
+ with H5File (h5file , "r" ) as f :
750
+ group = f ["TransformGroup" ]["2" ]
751
+ size = group ["TransformFixedParameters" ][:3 ].astype (int )
752
+ params = group ["TransformParameters" ][:].reshape (* size , 3 )
753
+
754
+ img = nb .load (warpfile )
755
+ ref = np .squeeze (np .asanyarray (img .dataobj ))
756
+
757
+ np .testing .assert_array_equal (params .transpose (2 , 1 , 0 , 3 ), ref )
758
+
759
+
760
+ def test_itk_h5_field_order_fortran (tmp_path ):
761
+ """Verify Fortran-order displacement fields load correctly"""
762
+ shape = (3 , 4 , 5 )
763
+ vals = np .arange (np .prod (shape ), dtype = float ).reshape (shape )
764
+ field = np .stack ([vals , vals + 100 , vals + 200 ], axis = 0 )
765
+
766
+ params = field .reshape (- 1 , order = "F" )
767
+ fixed = np .array (list (shape ) + [0 , 0 , 0 ] + [1 , 1 , 1 ] + list (np .eye (3 ).ravel ()), dtype = float )
768
+ fname = tmp_path / "field_f.h5"
769
+ with H5File (fname , "w" ) as f :
770
+ grp = f .create_group ("TransformGroup" )
771
+ grp .create_group ("0" )["TransformType" ] = np .array ([b"CompositeTransform_double_3_3" ])
772
+ g1 = grp .create_group ("1" )
773
+ g1 ["TransformType" ] = np .array ([b"DisplacementFieldTransform_float_3_3" ])
774
+ g1 ["TransformFixedParameters" ] = fixed
775
+ g1 ["TransformParameters" ] = params
776
+
777
+ img = itk .ITKCompositeH5 .from_filename (fname )[0 ]
778
+ expected = np .moveaxis (field , 0 , - 1 )
779
+ expected [..., (0 , 1 )] *= - 1
780
+ assert np .allclose (img .get_fdata (), expected )
0 commit comments