@@ -672,3 +672,109 @@ def test_itk_linear_h5(tmpdir, data_path, testdata_path):
672672 # Error raised if the we try to use the single affine loader
673673 with pytest .raises (TransformIOError ):
674674 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