Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/pyrecest/utils/point_set_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,22 @@ def _validate_positive_integer(value, name: str, *, minimum: int = 1) -> int:
return value_int


def _validate_boolean_flag(value, name: str) -> bool:
"""Return a real boolean flag without accepting merely truthy values."""
error_message = f"{name} must be a boolean."
if np.ma.is_masked(value):
raise TypeError(error_message)
if isinstance(value, (bool, np.bool_)):
return bool(value)
try:
value_array = np.asarray(value)
except (TypeError, ValueError, RuntimeError, OverflowError) as exc:
raise TypeError(error_message) from exc
if value_array.shape == () and value_array.dtype == np.bool_:
return bool(value_array.item())
raise TypeError(error_message)


def estimate_transform( # pylint: disable=too-many-locals
source_points,
target_points,
Expand Down Expand Up @@ -289,6 +305,7 @@ def estimate_transform( # pylint: disable=too-many-locals
"estimate_transform is not supported on the JAX backend."
)

allow_reflection = _validate_boolean_flag(allow_reflection, "allow_reflection")
source, target = _validate_pair(source_points, target_points)
n_points, dim = source.shape
min_matches = _minimum_required_matches(model, dim)
Expand Down Expand Up @@ -405,6 +422,7 @@ def joint_registration_assignment( # pylint: disable=too-many-arguments,too-man
"joint_registration_assignment is not supported on the JAX backend."
)

allow_reflection = _validate_boolean_flag(allow_reflection, "allow_reflection")
max_iterations = _validate_positive_integer(max_iterations, "max_iterations")

reference = _as_point_array(reference_points)
Expand Down
49 changes: 49 additions & 0 deletions tests/test_point_set_registration_reflection_flag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import numpy as np
import pytest
import pyrecest.backend

from pyrecest.backend import array
from pyrecest.utils.point_set_registration import (
estimate_transform,
joint_registration_assignment,
)

pytestmark = pytest.mark.skipif(
pyrecest.backend.__backend_name__ == "jax", # pylint: disable=no-member
reason="Point-set registration is not supported on JAX.",
)

_SOURCE = array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]])
_REFLECTED = array([[0.0, 0.0], [-1.0, 0.0], [0.0, 1.0]])


@pytest.mark.parametrize("invalid_flag", ["False", "True", 0, 1, None])
def test_estimate_transform_rejects_non_boolean_allow_reflection(invalid_flag):
with pytest.raises(TypeError, match="allow_reflection must be a boolean"):
estimate_transform(
_SOURCE,
_REFLECTED,
model="rigid",
allow_reflection=invalid_flag,
)


def test_joint_registration_rejects_truthy_string_allow_reflection():
with pytest.raises(TypeError, match="allow_reflection must be a boolean"):
joint_registration_assignment(
_SOURCE,
_REFLECTED,
model="rigid",
allow_reflection="False",
)


def test_numpy_boolean_allow_reflection_is_accepted():
transform = estimate_transform(
_SOURCE,
_REFLECTED,
model="rigid",
allow_reflection=np.bool_(True),
)

assert float(np.linalg.det(np.asarray(transform.matrix))) < 0.0
Loading