From 5febd95399ab859f5a4f1a6ee174d1339d30b786 Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Thu, 28 May 2026 12:02:33 +0200 Subject: [PATCH 1/4] fix(preprocessing): correct coordinate order in circle and ellipse model fitting --- sigima/tools/image/preprocessing.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sigima/tools/image/preprocessing.py b/sigima/tools/image/preprocessing.py index 8ce3fd6c..5c5d8dab 100644 --- a/sigima/tools/image/preprocessing.py +++ b/sigima/tools/image/preprocessing.py @@ -48,7 +48,8 @@ def fit_circle_model(contour: np.ndarray) -> tuple[float, float, float] | None: if _USE_NEW_SHAPE_API: model = measure.CircleModel.from_estimate(contour) if model: - return model.center[0], model.center[1], model.radius + # model.center is (row, col) = (y, x), swap to (x, y) + return model.center[1], model.center[0], model.radius else: model = measure.CircleModel() if model.estimate(contour): @@ -73,8 +74,10 @@ def fit_ellipse_model( if _USE_NEW_SHAPE_API: model = measure.EllipseModel.from_estimate(contour) if model: - xc, yc = model.center[0], model.center[1] - a, b = model.axis_lengths[0], model.axis_lengths[1] + # model.center is (row, col) = (y, x), swap to (x, y) + # model.axis_lengths is (semi_row, semi_col), swap to (semi_x, semi_y) + xc, yc = model.center[1], model.center[0] + a, b = model.axis_lengths[1], model.axis_lengths[0] return xc, yc, a, b, model.theta else: model = measure.EllipseModel() From d47c6e4076f0b1df6783d9b289df3984346b7332 Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Thu, 28 May 2026 14:44:45 +0200 Subject: [PATCH 2/4] fix(ellipse): correct sign for ellipse minor axis diameter calculation --- 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 17886e17..1e1d160d 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 57422e55fd2e54012b285b743eb8a6a27ce4f72e Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Thu, 28 May 2026 14:45:26 +0200 Subject: [PATCH 3/4] fix(geometry): scale radius and semi-axes from pixel to physical units in compute_geometry_from_obj --- sigima/proc/image/base.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sigima/proc/image/base.py b/sigima/proc/image/base.py index 36b97b8e..0528cf70 100644 --- a/sigima/proc/image/base.py +++ b/sigima/proc/image/base.py @@ -308,6 +308,17 @@ def compute_geometry_from_obj( colx, coly = 0, 1 coords[:, colx] = obj.dx * coords[:, colx] + obj.x0 coords[:, coly] = obj.dy * coords[:, coly] + obj.y0 + if coords.shape[1] % 2 != 0: + # Scale distance-like values (radius, semi-axes) from pixel to + # physical units. Use average pixel size for isotropic scaling. + pixel_scale = (obj.dx + obj.dy) / 2 + if coords.shape[1] >= 3: + # Column 2: radius (circle) or semi-axis a (ellipse) + coords[:, 2] *= pixel_scale + if coords.shape[1] >= 4: + # Column 3: semi-axis b (ellipse) + coords[:, 3] *= pixel_scale + # Column 4 (theta) is an angle: no scaling needed if obj.roi is not None: x0, y0, _x1, _y1 = obj.roi.get_single_roi(i_roi).get_bounding_box(obj) coords[:, colx] += x0 - obj.x0 From 7b4ce7caa2c8923b1e52e4760bd265b3f302ba1d Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Thu, 28 May 2026 15:03:16 +0200 Subject: [PATCH 4/4] Update release notes for version 1.1.3 with bug fixes in contour detection and visualization --- doc/release_notes/release_1.01.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/release_notes/release_1.01.md b/doc/release_notes/release_1.01.md index 82e65236..eb1bda2a 100644 --- a/doc/release_notes/release_1.01.md +++ b/doc/release_notes/release_1.01.md @@ -1,5 +1,20 @@ # Version 1.1 # +## Sigima Version 1.1.3 (unreleased) ## + +### 🛠️ Bug Fixes since version 1.1.2 ### + +* **Ellipse/circle contour detection**: Fixed swapped X/Y coordinates when using scikit-image ≥ 0.26.0 + * The new `EllipseModel`/`CircleModel` API returns center coordinates as `(row, col)` but the code was treating them as `(x, y)`, causing detected ellipses and circles to appear at wrong positions on the image + * Semi-axis lengths were also swapped for ellipses, resulting in incorrect aspect ratios + * This bug only affected scikit-image ≥ 0.26.0; older versions were handled correctly +* **Ellipse visualization**: Fixed incorrect minor axis direction in `ellipse_to_diameters` coordinate conversion + * The minor axis endpoints were computed with a wrong sign, making the minor axis non-perpendicular to the major axis for rotated ellipses (e.g., at θ=45° both axes pointed in the same direction). This caused distorted ellipse overlays in DataLab when displaying detected or annotated ellipses at non-trivial rotation angles +* **Circle/ellipse detection with calibrated images**: Fixed radius and semi-axes not being converted from pixel units to physical units + * When an image had non-default pixel calibration (e.g., dx=2 mm/pixel), center coordinates were correctly converted to physical units but the radius and semi-axes remained in pixels, causing values to be wrong by a factor equal to the pixel size + + + ## Sigima Version 1.1.2 (2026-04-20) ## ### 💥 Breaking changes since version 1.1.1 ###