From 4600049fe1cb66f2ab0102818a61e93129ae1996 Mon Sep 17 00:00:00 2001 From: Karl Koschutnig Date: Thu, 9 Jul 2026 10:03:44 +0200 Subject: [PATCH 1/3] Fix CorpusCallosum NumPy 2 compatibility --- CorpusCallosum/shape/subsegment_contour.py | 4 +- CorpusCallosum/utils/mapping_helpers.py | 4 +- test/test_cc_numpy_compat.py | 46 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 test/test_cc_numpy_compat.py diff --git a/CorpusCallosum/shape/subsegment_contour.py b/CorpusCallosum/shape/subsegment_contour.py index 2434081df..88326c0da 100644 --- a/CorpusCallosum/shape/subsegment_contour.py +++ b/CorpusCallosum/shape/subsegment_contour.py @@ -880,8 +880,8 @@ def transform_to_acpc_standard( norms_product = np.linalg.norm(ac_pc_vec) * np.linalg.norm(posterior_vector) theta = np.arccos(dot_product / norms_product) - # Determine the sign of the angle using cross product - cross_product = np.cross(ac_pc_vec, posterior_vector) + # Determine the sign of the angle using the signed z-component of the 2D cross product. + cross_product = ac_pc_vec[0] * posterior_vector[1] - ac_pc_vec[1] * posterior_vector[0] if cross_product < 0: theta = -theta diff --git a/CorpusCallosum/utils/mapping_helpers.py b/CorpusCallosum/utils/mapping_helpers.py index 666e5e08b..c043cc542 100644 --- a/CorpusCallosum/utils/mapping_helpers.py +++ b/CorpusCallosum/utils/mapping_helpers.py @@ -84,8 +84,8 @@ def correct_nodding(ac_pt: Vector2d, pc_pt: Vector2d) -> AffineMatrix3x3: norms_product = np.linalg.norm(ac_pc_vec) * np.linalg.norm(posterior_vector) theta = np.arccos(dot_product / norms_product) - # Determine the sign of the angle using cross product - cross_product = np.cross(ac_pc_vec, posterior_vector) + # Determine the sign of the angle using the signed z-component of the 2D cross product. + cross_product = ac_pc_vec[0] * posterior_vector[1] - ac_pc_vec[1] * posterior_vector[0] if cross_product < 0: theta = -theta diff --git a/test/test_cc_numpy_compat.py b/test/test_cc_numpy_compat.py new file mode 100644 index 000000000..975c95cc7 --- /dev/null +++ b/test/test_cc_numpy_compat.py @@ -0,0 +1,46 @@ +import importlib.util +from pathlib import Path + +import numpy as np + + +def _load_module(module_name: str, relative_path: str): + module_path = Path(__file__).resolve().parents[1] / relative_path + spec = importlib.util.spec_from_file_location(module_name, module_path) + if spec is None or spec.loader is None: + raise ImportError(f"Could not load module {module_name} from {module_path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +subsegment_contour = _load_module( + "cc_subsegment_contour", + "CorpusCallosum/shape/subsegment_contour.py", +) +mapping_helpers = _load_module( + "cc_mapping_helpers", + "CorpusCallosum/utils/mapping_helpers.py", +) + +transform_to_acpc_standard = subsegment_contour.transform_to_acpc_standard +correct_nodding = mapping_helpers.correct_nodding + + +def test_transform_to_acpc_standard_accepts_2d_vectors_with_numpy2(): + contour = np.array([[0.0, 1.0, 2.0], [0.0, 0.5, 0.0]]) + ac_pt = np.array([0.0, 0.0]) + pc_pt = np.array([1.0, 0.0]) + + contour_acpc, ac_pt_acpc, pc_pt_acpc, rotate_back = transform_to_acpc_standard(contour, ac_pt, pc_pt) + + assert contour_acpc.shape == contour.shape + assert np.allclose(ac_pt_acpc, [0.0, 0.0]) + assert pc_pt_acpc.shape == (2,) + assert rotate_back(contour_acpc).shape == contour.shape + + +def test_acpc_rotation_matrix_accepts_2d_vectors_with_numpy2(): + rotation = correct_nodding(np.array([0.0, 0.0]), np.array([1.0, 0.0])) + + assert rotation.shape == (3, 3) \ No newline at end of file From 9cc1cd28349c4f506d248046b5b62a4cfa3206ba Mon Sep 17 00:00:00 2001 From: Karl Koschutnig Date: Wed, 15 Jul 2026 10:44:53 +0200 Subject: [PATCH 2/3] Add tests for _requires_cross_sectional_passthrough function --- test/quicktest/test_run_fastsurfer_bids.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test/quicktest/test_run_fastsurfer_bids.py diff --git a/test/quicktest/test_run_fastsurfer_bids.py b/test/quicktest/test_run_fastsurfer_bids.py new file mode 100644 index 000000000..0f30eb32f --- /dev/null +++ b/test/quicktest/test_run_fastsurfer_bids.py @@ -0,0 +1,13 @@ +from run_fastsurfer_bids import _requires_cross_sectional_passthrough + + +def test_requires_cross_sectional_passthrough_detects_seg_only(): + assert _requires_cross_sectional_passthrough(["--seg_only"]) + + +def test_requires_cross_sectional_passthrough_detects_surf_only(): + assert _requires_cross_sectional_passthrough(["--surf_only", "--parallel", "2"]) + + +def test_requires_cross_sectional_passthrough_allows_full_pipeline_flags(): + assert not _requires_cross_sectional_passthrough(["--parallel", "2", "--3T"]) From dd1f9fe8b8da16d1a292ac073aa34e1905182262 Mon Sep 17 00:00:00 2001 From: Karl Koschutnig Date: Thu, 16 Jul 2026 12:07:32 +0200 Subject: [PATCH 3/3] Remove tests per review feedback The BIDS passthrough test is unrelated to this fix, and the numpy compat test mainly asserted output shapes without exercising the underlying cross-product behavior, so it added little value. This also removes the only usage of _load_module, which loaded the CorpusCallosum modules by file path since they aren't installed as an importable package. Co-Authored-By: Claude Sonnet 5 --- test/quicktest/test_run_fastsurfer_bids.py | 13 ------ test/test_cc_numpy_compat.py | 46 ---------------------- 2 files changed, 59 deletions(-) delete mode 100644 test/quicktest/test_run_fastsurfer_bids.py delete mode 100644 test/test_cc_numpy_compat.py diff --git a/test/quicktest/test_run_fastsurfer_bids.py b/test/quicktest/test_run_fastsurfer_bids.py deleted file mode 100644 index 0f30eb32f..000000000 --- a/test/quicktest/test_run_fastsurfer_bids.py +++ /dev/null @@ -1,13 +0,0 @@ -from run_fastsurfer_bids import _requires_cross_sectional_passthrough - - -def test_requires_cross_sectional_passthrough_detects_seg_only(): - assert _requires_cross_sectional_passthrough(["--seg_only"]) - - -def test_requires_cross_sectional_passthrough_detects_surf_only(): - assert _requires_cross_sectional_passthrough(["--surf_only", "--parallel", "2"]) - - -def test_requires_cross_sectional_passthrough_allows_full_pipeline_flags(): - assert not _requires_cross_sectional_passthrough(["--parallel", "2", "--3T"]) diff --git a/test/test_cc_numpy_compat.py b/test/test_cc_numpy_compat.py deleted file mode 100644 index 975c95cc7..000000000 --- a/test/test_cc_numpy_compat.py +++ /dev/null @@ -1,46 +0,0 @@ -import importlib.util -from pathlib import Path - -import numpy as np - - -def _load_module(module_name: str, relative_path: str): - module_path = Path(__file__).resolve().parents[1] / relative_path - spec = importlib.util.spec_from_file_location(module_name, module_path) - if spec is None or spec.loader is None: - raise ImportError(f"Could not load module {module_name} from {module_path}") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -subsegment_contour = _load_module( - "cc_subsegment_contour", - "CorpusCallosum/shape/subsegment_contour.py", -) -mapping_helpers = _load_module( - "cc_mapping_helpers", - "CorpusCallosum/utils/mapping_helpers.py", -) - -transform_to_acpc_standard = subsegment_contour.transform_to_acpc_standard -correct_nodding = mapping_helpers.correct_nodding - - -def test_transform_to_acpc_standard_accepts_2d_vectors_with_numpy2(): - contour = np.array([[0.0, 1.0, 2.0], [0.0, 0.5, 0.0]]) - ac_pt = np.array([0.0, 0.0]) - pc_pt = np.array([1.0, 0.0]) - - contour_acpc, ac_pt_acpc, pc_pt_acpc, rotate_back = transform_to_acpc_standard(contour, ac_pt, pc_pt) - - assert contour_acpc.shape == contour.shape - assert np.allclose(ac_pt_acpc, [0.0, 0.0]) - assert pc_pt_acpc.shape == (2,) - assert rotate_back(contour_acpc).shape == contour.shape - - -def test_acpc_rotation_matrix_accepts_2d_vectors_with_numpy2(): - rotation = correct_nodding(np.array([0.0, 0.0]), np.array([1.0, 0.0])) - - assert rotation.shape == (3, 3) \ No newline at end of file