From dfe62a30c89d1a3f34da4584ab1492fb0d25d70c Mon Sep 17 00:00:00 2001 From: Thomas MALLET Date: Thu, 4 Jun 2026 16:45:32 +0200 Subject: [PATCH 1/4] fix sign after regression --- sigima/tools/coordinates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sigima/tools/coordinates.py b/sigima/tools/coordinates.py index 1e1d160d..17886e17 100644 --- a/sigima/tools/coordinates.py +++ b/sigima/tools/coordinates.py @@ -98,7 +98,7 @@ def ellipse_to_diameters( Ellipse X/Y diameters (major/minor axes) coordinates """ dxa, dya = a * np.cos(theta), a * np.sin(theta) - dxb, dyb = -b * np.sin(theta), b * np.cos(theta) + dxb, dyb = b * np.sin(theta), b * np.cos(theta) x0, y0, x1, y1 = xc - dxa, yc - dya, xc + dxa, yc + dya x2, y2, x3, y3 = xc - dxb, yc - dyb, xc + dxb, yc + dyb return x0, y0, x1, y1, x2, y2, x3, y3 @@ -117,7 +117,7 @@ def array_ellipse_to_diameters(data: np.ndarray) -> np.ndarray: """ xc, yc, a, b, theta = data[:, 0], data[:, 1], data[:, 2], data[:, 3], data[:, 4] dxa, dya = a * np.cos(theta), a * np.sin(theta) - dxb, dyb = -b * np.sin(theta), b * np.cos(theta) + dxb, dyb = b * np.sin(theta), b * np.cos(theta) x0, y0, x1, y1 = xc - dxa, yc - dya, xc + dxa, yc + dya x2, y2, x3, y3 = xc - dxb, yc - dyb, xc + dxb, yc + dyb result = np.column_stack((x0, y0, x1, y1, x2, y2, x3, y3)).astype(float) From 8e04e39cd876caaa5af4a29f2af52bd67e80cdf4 Mon Sep 17 00:00:00 2001 From: Thomas MALLET Date: Thu, 4 Jun 2026 17:27:39 +0200 Subject: [PATCH 2/4] fadd dedicated test with computed ground truth for circle and ellipse contour detection --- .../image/preprocessing_tools_unit_test.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/sigima/tests/image/preprocessing_tools_unit_test.py b/sigima/tests/image/preprocessing_tools_unit_test.py index 220adf00..c1c19fe5 100644 --- a/sigima/tests/image/preprocessing_tools_unit_test.py +++ b/sigima/tests/image/preprocessing_tools_unit_test.py @@ -84,6 +84,49 @@ def test_fit_ellipse_model_failure() -> None: assert result is None or isinstance(result, tuple) +def test_fit_circle_model_ground_truth() -> None: + """Verify that ``fit_circle_model`` recovers **all** circle parameters + (xc, yc, radius) from a noise-free contour. + + Note: the functions swap x/y because scikit-image models interpret the + contour columns as (row, col), so the returned ``xc`` corresponds to the + contour's second column and ``yc`` to the first. + """ + xc_in, yc_in, r_in = 7.0, -5.0, 12.0 + contour = _circle_contour(xc_in, yc_in, r_in, n=256) + result = fit_circle_model(contour) + assert result is not None + # xc/yc are swapped by the row/col → x/y conversion + yc, xc, r = result + assert xc == pytest.approx(xc_in, abs=1e-6) + assert yc == pytest.approx(yc_in, abs=1e-6) + assert r == pytest.approx(r_in, abs=1e-6) + + +def test_fit_ellipse_model_ground_truth() -> None: + """Verify that ``fit_ellipse_model`` recovers **all** ellipse parameters + (xc, yc, a, b, theta) from a noise-free contour. + + Note: the functions swap x/y and a/b because scikit-image models interpret + the contour columns as (row, col), so centre and semi-axes are transposed. + """ + xc_in, yc_in = -3.0, 5.0 + a_in, b_in = 4.0, 8.0 + theta_in = np.pi / 6 + contour = _ellipse_contour(xc_in, yc_in, a_in, b_in, theta0=theta_in, n=256) + result = fit_ellipse_model(contour) + assert result is not None + # xc/yc are swapped by the row/col → x/y conversion + yc, xc, a, b, theta = result + assert xc == pytest.approx(xc_in, abs=1e-4) + assert yc == pytest.approx(yc_in, abs=1e-4) + assert a == pytest.approx(a_in, abs=1e-4) + assert b == pytest.approx(b_in, abs=1e-4) + # The fitted angle is expected to differ by π/2, + # theta along y axis instead of x axis + assert theta == pytest.approx(theta_in + np.pi / 2, abs=1e-4) + + # =========================================================================== # get_absolute_level # =========================================================================== From 4caf11ccd82fb416d9989bfd6245009306654451 Mon Sep 17 00:00:00 2001 From: Thomas MALLET Date: Thu, 4 Jun 2026 17:56:48 +0200 Subject: [PATCH 3/4] prevent test to run with scikit-image < v0.26 --- sigima/tests/image/preprocessing_tools_unit_test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sigima/tests/image/preprocessing_tools_unit_test.py b/sigima/tests/image/preprocessing_tools_unit_test.py index c1c19fe5..c3d43d66 100644 --- a/sigima/tests/image/preprocessing_tools_unit_test.py +++ b/sigima/tests/image/preprocessing_tools_unit_test.py @@ -11,6 +11,7 @@ from sigima.enums import BinningOperation from sigima.tools.image.preprocessing import ( + _USE_NEW_SHAPE_API, binning, distance_matrix, fit_circle_model, @@ -92,6 +93,8 @@ def test_fit_circle_model_ground_truth() -> None: contour columns as (row, col), so the returned ``xc`` corresponds to the contour's second column and ``yc`` to the first. """ + if not _USE_NEW_SHAPE_API: + pytest.skip() xc_in, yc_in, r_in = 7.0, -5.0, 12.0 contour = _circle_contour(xc_in, yc_in, r_in, n=256) result = fit_circle_model(contour) @@ -110,6 +113,8 @@ def test_fit_ellipse_model_ground_truth() -> None: Note: the functions swap x/y and a/b because scikit-image models interpret the contour columns as (row, col), so centre and semi-axes are transposed. """ + if not _USE_NEW_SHAPE_API: + pytest.skip() xc_in, yc_in = -3.0, 5.0 a_in, b_in = 4.0, 8.0 theta_in = np.pi / 6 From fb7fdc0f443c30cd2ac081e097b41ca289f26dda Mon Sep 17 00:00:00 2001 From: Thomas MALLET Date: Thu, 4 Jun 2026 17:59:09 +0200 Subject: [PATCH 4/4] bump to v1.1.4 and update changelog --- doc/release_notes/release_1.01.md | 8 ++++++++ sigima/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/release_notes/release_1.01.md b/doc/release_notes/release_1.01.md index 6cda14e5..7187dc8e 100644 --- a/doc/release_notes/release_1.01.md +++ b/doc/release_notes/release_1.01.md @@ -1,5 +1,13 @@ # Version 1.1 # +## Sigima Version 1.1.4 ## + +### 🛠️ Bug Fixes since version 1.1.3 ### + +* **Ellipse/circle contour detection**: Fixed regression in axis orientation after the v1.1.3 coordinate swap fix. This closes [Issue #27](https://github.com/DataLab-Platform/Sigima/issues/27). + * The sign correction applied in v1.1.3 for the `(row, col)` → `(x, y)` conversion introduced a regression in ellipse axis orientation, causing the semi-major and semi-minor axes to be transposed + * Added ground-truth unit tests for `fit_circle_model` and `fit_ellipse_model` verifying all output parameters (center coordinates, radius/semi-axes, and rotation angle) + ## Sigima Version 1.1.3 ## ### 🛠️ Bug Fixes since version 1.1.2 ### diff --git a/sigima/__init__.py b/sigima/__init__.py index 0c466b49..7f2bbff9 100644 --- a/sigima/__init__.py +++ b/sigima/__init__.py @@ -144,7 +144,7 @@ # Set validation mode to ENABLED by default (issue warnings for invalid inputs) set_validation_mode(ValidationMode.ENABLED) -__version__ = "1.1.3" +__version__ = "1.1.4" __docurl__ = "https://sigima.readthedocs.io/" __homeurl__ = "https://github.com/DataLab-Platform/Sigima" __supporturl__ = "https://github.com/DataLab-Platform/sigima/issues/new/choose"