From 36d74e15ddca136958f7fb17bec99968800d1c84 Mon Sep 17 00:00:00 2001 From: Demetrios Chiuratto Agourakis Date: Sun, 25 Oct 2026 00:38:51 +0000 Subject: [PATCH 1/3] test: avoid ambiguous axis labels at the oblique boundary Move the exact-label fixtures to 30 degrees and retain the 45-degree input in geometry tests that check complete voxel, shape, and affine references for the two accepted nearest-axis assignments per target. Independent reference calculations use C++23. Mathematical review completed through Moonshot Kimi K3 and Google Gemini, with Gemini substituting for the unavailable Grok review. Review limitations are retained in the private audit. Assisted-by: OpenAI Codex Signed-off-by: Demetrios Chiuratto Agourakis --- tests/transforms/test_orientation.py | 70 +++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/tests/transforms/test_orientation.py b/tests/transforms/test_orientation.py index 31acc18c5d3..ad9694c7110 100644 --- a/tests/transforms/test_orientation.py +++ b/tests/transforms/test_orientation.py @@ -26,6 +26,55 @@ from tests.lazy_transforms_utils import test_resampler_lazy from tests.test_utils import TEST_DEVICES, assert_allclose +# At 45 degrees, two nearest-axis assignments preserve the same physical image. +# Keep both full voxel/affine references instead of requiring a unique label. +_OBLIQUE_BOUNDARY_REFERENCES = { + "LPS": [ + { + "shape": [2, 3, 1, 2], + "values": [2, 5, 1, 4, 0, 3, 8, 11, 7, 10, 6, 9], + "affine": [ + [-1, 0, 0, 12], + [0, -0.707106781186547524, -0.707106781186547524, 20], + [0, -0.707106781186547524, 0.707106781186547524, 30], + [0, 0, 0, 1], + ], + }, + { + "shape": [2, 3, 2, 1], + "values": [2, 5, 1, 4, 0, 3, 8, 11, 7, 10, 6, 9], + "affine": [ + [-1, 0, 0, 12], + [0, -0.707106781186547524, 0.707106781186547524, 20], + [0, 0.707106781186547524, 0.707106781186547524, 30], + [0, 0, 0, 1], + ], + }, + ], + "RAS": [ + { + "shape": [2, 3, 1, 2], + "values": [0, 3, 1, 4, 2, 5, 6, 9, 7, 10, 8, 11], + "affine": [ + [1, 0, 0, 10], + [0, 0.707106781186547524, -0.707106781186547524, 20], + [0, 0.707106781186547524, 0.707106781186547524, 30], + [0, 0, 0, 1], + ], + }, + { + "shape": [2, 3, 2, 1], + "values": [3, 0, 4, 1, 5, 2, 9, 6, 10, 7, 11, 8], + "affine": [ + [1, 0, 0, 10], + [0, 0.707106781186547524, 0.707106781186547524, 19.2928932188134525], + [0, -0.707106781186547524, 0.707106781186547524, 30.7071067811865475], + [0, 0, 0, 1], + ], + }, + ], +} + TESTS = [] for device in TEST_DEVICES: TESTS.append( @@ -166,7 +215,7 @@ torch.arange(12).reshape((2, 1, 2, 3)), torch.as_tensor( create_translate(3, (10, 20, 30)) - @ create_rotate(3, (np.pi / 2, np.pi / 2, np.pi / 4)) + @ create_rotate(3, (np.pi / 2, np.pi / 2, np.pi / 6)) @ np.diag([-1, 1, 1, 1]) ), torch.tensor([[[[2, 5]], [[1, 4]], [[0, 3]]], [[[8, 11]], [[7, 10]], [[6, 9]]]]), @@ -181,7 +230,7 @@ torch.arange(12).reshape((2, 1, 2, 3)), torch.as_tensor( create_translate(3, (10, 20, 30)) - @ create_rotate(3, (np.pi / 2, np.pi / 2, np.pi / 4)) + @ create_rotate(3, (np.pi / 2, np.pi / 2, np.pi / 6)) @ np.diag([-1, 1, 1, 1]) ), torch.tensor([[[[0, 3]], [[1, 4]], [[2, 5]]], [[[6, 9]], [[7, 10]], [[8, 11]]]]), @@ -276,6 +325,23 @@ def test_ornt_meta( new_code = nib.orientations.aff2axcodes(res.affine.cpu(), labels=labels) # type: ignore self.assertEqual("".join(new_code), expected_code) + @parameterized.expand([("LPS", {"axcodes": "LPS"}), ("RAS", {"as_closest_canonical": True})]) + def test_oblique_boundary_geometry(self, target, init_param): + affine = torch.as_tensor( + create_translate(3, (10, 20, 30)) + @ create_rotate(3, (np.pi / 2, np.pi / 2, np.pi / 4)) + @ np.diag([-1, 1, 1, 1]) + ) + img = MetaTensor(torch.arange(12).reshape((2, 1, 2, 3)), affine=affine) + result = Orientation(**init_param)(img) + for reference in _OBLIQUE_BOUNDARY_REFERENCES[target]: + expected = torch.tensor(reference["values"], dtype=result.dtype).reshape(reference["shape"]) + if result.shape == expected.shape and torch.equal(result.as_tensor(), expected): + expected_affine = torch.tensor(reference["affine"], dtype=result.affine.dtype) + assert_allclose(result.affine, expected_affine, rtol=1e-12, atol=1e-12) + return + self.fail(f"Unexpected {target} voxel ordering at the oblique boundary: {result}") + @parameterized.expand(TESTS_TORCH) def test_ornt_torch(self, init_param, img: torch.Tensor, track_meta: bool, device): set_track_meta(track_meta) From e39623974b844c986d5a3d6455cb00cba829f98f Mon Sep 17 00:00:00 2001 From: Demetrios Agourakis Date: Tue, 27 Oct 2026 19:50:07 -0300 Subject: [PATCH 2/3] test: document oblique boundary orientation references Format the author-supplied explanation of the two accepted boundary arrangements and the test arguments, return value and assertion failure. Assisted-by: Codex:GPT-6.1-Sol Signed-off-by: Demetrios Chiuratto Agourakis --- tests/transforms/test_orientation.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/transforms/test_orientation.py b/tests/transforms/test_orientation.py index ad9694c7110..d86f6ddf887 100644 --- a/tests/transforms/test_orientation.py +++ b/tests/transforms/test_orientation.py @@ -327,6 +327,18 @@ def test_ornt_meta( @parameterized.expand([("LPS", {"axcodes": "LPS"}), ("RAS", {"as_closest_canonical": True})]) def test_oblique_boundary_geometry(self, target, init_param): + """Allow two arrangements at the 45-degree tie in this oblique boundary test. + + Args: + target: Chooses the LPS or RAS references. + init_param: Configures the orientation. + + Returns: + None if the test succeeds. + + Raises: + AssertionError: If the result diverges from the references. + """ affine = torch.as_tensor( create_translate(3, (10, 20, 30)) @ create_rotate(3, (np.pi / 2, np.pi / 2, np.pi / 4)) From 6e87f552e9323fb2bf24aa095eac921649693191 Mon Sep 17 00:00:00 2001 From: Demetrios Agourakis Date: Tue, 27 Oct 2026 19:50:47 -0300 Subject: [PATCH 3/3] DCO Remediation Commit for Demetrios Agourakis I, Demetrios Agourakis , hereby add my Signed-off-by to this commit: e39623974b844c986d5a3d6455cb00cba829f98f Assisted-by: Codex:GPT-6.1-Sol Signed-off-by: Demetrios Agourakis