diff --git a/src/machinevisiontoolbox/base/findpeaks.py b/src/machinevisiontoolbox/base/findpeaks.py index af45314f..fc8a2cb3 100644 --- a/src/machinevisiontoolbox/base/findpeaks.py +++ b/src/machinevisiontoolbox/base/findpeaks.py @@ -4,6 +4,8 @@ from __future__ import annotations +import warnings + import numpy as np import scipy as sp import spatialmath.base as base @@ -116,10 +118,13 @@ def findpeaks( poly, *_ = Polynomial.fit( x[i - n2 : i + n2 + 1], y[i - n2 : i + n2 + 1], interp, full=True ) - except: - # handle situation where neighbourhood falls off the data - # vector - print(f"Peak at {x[i]} couldn't be fitted, skipping") + except (ValueError, np.linalg.LinAlgError): + # A neighbourhood near the boundary can contain too few samples. + warnings.warn( + f"Peak at {x[i]} couldn't be fitted, skipping", + RuntimeWarning, + stacklevel=2, + ) continue # find the roots of the polynomial closest to the coarse peak diff --git a/tests/base/test_findpeaks.py b/tests/base/test_findpeaks.py index ac2a529b..25e1103d 100644 --- a/tests/base/test_findpeaks.py +++ b/tests/base/test_findpeaks.py @@ -1,6 +1,8 @@ import unittest +from unittest.mock import patch import numpy as np +from numpy.polynomial import Polynomial from machinevisiontoolbox import findpeaks, findpeaks2d, findpeaks3d @@ -8,6 +10,25 @@ class TestFindpeaks(unittest.TestCase): """Test cases for 1D peak finding""" + def test_failed_refinement_warns_and_skips(self): + """Warn when the fit neighbourhood extends beyond the signal.""" + y = np.array([0, 2, 0, 0, 0, 0, 0], dtype=float) + with self.assertWarnsRegex( + RuntimeWarning, "Peak at 1 couldn't be fitted, skipping" + ): + x, ymax = findpeaks(y, interp=6, npeaks=1) + self.assertEqual(x.size, 0) + self.assertEqual(ymax.size, 0) + + def test_refinement_preserves_unexpected_exceptions(self): + """Let unrelated failures and user interrupts reach the caller.""" + y = np.array([0, 1, 2, 1, 0], dtype=float) + for error in (RuntimeError("unexpected failure"), KeyboardInterrupt()): + with self.subTest(error=type(error).__name__): + with patch.object(Polynomial, "fit", side_effect=error): + with self.assertRaises(type(error)): + findpeaks(y, interp=True) + def test_simple_peak(self): """Test detection of simple peak""" y = np.array([0, 0, 1, 2, 0, 0, 0, 3, 1, 0, 0, 0, 0])