Skip to content

Commit 4d1885a

Browse files
Stabilize point-set registration RMSE (#4965)
* Add one-shot registration RMSE fix * Stabilize registration RMSE --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 2dff3fc commit 4d1885a

2 files changed

Lines changed: 37 additions & 4 deletions

File tree

src/pyrecest/utils/_point_set_registration_common.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import numpy as np
1010

1111
# pylint: disable=no-name-in-module,no-member
12+
from pyrecest.backend import abs as backend_abs
1213
from pyrecest.backend import all as backend_all
14+
from pyrecest.backend import max as backend_max
1315
from pyrecest.backend import (
1416
array_equal,
1517
asarray,
@@ -390,10 +392,15 @@ def default_cost(transformed_reference_points, moving_points):
390392

391393

392394
def compute_rmse(matched_costs) -> float:
393-
"""Compute the RMSE over matched costs."""
394-
if matched_costs.shape[0] > 0:
395-
return float(sqrt(mean(matched_costs * matched_costs)))
396-
return float("inf")
395+
"""Compute the RMSE over matched costs without squaring overflow."""
396+
if matched_costs.shape[0] == 0:
397+
return float("inf")
398+
399+
scale = float(backend_max(backend_abs(matched_costs)))
400+
if scale == 0.0 or not math.isfinite(scale):
401+
return scale
402+
scaled_costs = matched_costs / scale
403+
return scale * float(sqrt(mean(scaled_costs * scaled_costs)))
397404

398405

399406
def summarize_assignment(assignment, costs) -> MatchSummary:
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import math
2+
import unittest
3+
4+
import numpy as np
5+
from pyrecest.backend import array, float64
6+
from pyrecest.utils._point_set_registration_common import compute_rmse
7+
8+
9+
class TestPointSetRegistrationRmseStability(unittest.TestCase):
10+
def test_rmse_preserves_maximum_finite_cost(self):
11+
largest = np.finfo(np.float64).max
12+
13+
rmse = compute_rmse(array([largest], dtype=float64))
14+
15+
self.assertTrue(math.isfinite(rmse))
16+
self.assertEqual(rmse, largest)
17+
18+
def test_rmse_preserves_zero_and_empty_contract(self):
19+
self.assertEqual(compute_rmse(array([0.0], dtype=float64)), 0.0)
20+
self.assertTrue(
21+
math.isinf(compute_rmse(array([], dtype=float64)))
22+
)
23+
24+
25+
if __name__ == "__main__":
26+
unittest.main()

0 commit comments

Comments
 (0)