You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An audit carried out before the 1.1.6 release showed that the ellipse convention is inconsistent across the whole chain — fitting, coordinate conversion, shape objects, result headers and downstream consumers. Two independent defects partially compensate each other, which is why the existing tests pass and why the problem has survived a fix-then-revert cycle.
This issue deliberately covers the entire ellipse chain at once, rather than patching a single function, so that the convention is settled once and for all.
Defect 1 — fit_ellipse_model violates its documented contract
sigima/tools/image/preprocessing.py::fit_ellipse_model documents its return value as (xc, yc, a, b, theta) "where a and b are semi-major and semi-minor axes".
Measured on synthetic ellipses with true semi-axes of 30 (major) and 10 (minor):
true θ
a returned
b returned
θ returned
90° − θ_returned
0°
9.934
30.108
90.00°
0.00°
20°
10.020
29.686
70.25°
19.75°
30°
10.026
30.067
59.93°
30.07°
45°
10.060
29.718
45.00°
45.00°
70°
10.019
29.686
19.74°
70.26°
So a is always the semi-minor axis, b the semi-major axis, and θ is mirrored (90° − θ_true).
Both scikit-image branches swap the axes:
new API: a, b = model.axis_lengths[1], model.axis_lengths[0], with a comment claiming axis_lengths is (semi_row, semi_col) — it is in fact (semi-major, semi-minor), so the swap is unjustified;
legacy API: yc, xc, b, a, theta = model.params — same inversion.
The TypeError fallback _estimate_ellipse_params is inconsistent with both: it swaps x/y and width/height but returns phiunmirrored.
Defect 2 — the minor-axis diameter is not perpendicular to the major axis
sigima/tools/coordinates.py, lines 101 and 120:
dxa, dya=a*np.cos(theta), a*np.sin(theta)
dxb, dyb=b*np.sin(theta), b*np.cos(theta) # missing minus sign
The dot product of the two returned axes is a·b·sin(2θ), which is non-zero. Measured angle between the two axes returned by ellipse_to_diameters:
θ
0°
15°
30°
45°
135°
angle between axes
90°
60°
30°
0°
180°
At θ = 45° the two "axes" are collinear. The correct form is dxb, dyb = -b * np.sin(theta), b * np.cos(theta).
Why this was not caught
ellipse_to_center_axes_angle (and its array variant) derives θ and both axis lengths from the major-axis endpoints only. The round-trip test sigima/tests/common/tools_coordinates_geom_unit_test.py::test_ellipse_roundtrip_scalar therefore passes while completely ignoring the faulty quantity.
sigima/tests/image/preprocessing_tools_unit_test.py::test_fit_ellipse_model_success unpacks yc, xc, a, b, theta — i.e. it was written to match the implementation, not the documented contract.
History
d47c6e4 (2026-05-28), "fix(ellipse): correct sign for ellipse minor axis diameter calculation", applied the correct -b*sin form and shipped in v1.1.3.
dfe62a3 (2026-06-04), "fix sign after regression", reverted it, with no accompanying test and no release-note change. It shipped in v1.1.4 and v1.1.5.
doc/release_notes/release_1.01.md still claims, in its v1.1.3 section, that this bug is fixed.
Defect 1 is almost certainly the reason defect 2 was reintroduced: because fit_ellipse_model returns a mirrored angle, the wrong sign in coordinates.py makes the major axis look right on screen, which was mistaken for a regression when the correct sign was applied.
Blocking prerequisite
The _USE_NEW_SHAPE_API branch of fit_ellipse_model is never exercised in the current development environment (installed scikit-image is < 0.26, so EllipseModel.from_estimate raises AttributeError). Fixing the axis convention there would be done blind — which is exactly how the previous fix-then-revert cycle happened.
A CI matrix covering both scikit-image < 0.26 and >= 0.26 is a prerequisite for this issue.
Scope
Add a CI matrix covering scikit-image < 0.26 and >= 0.26
sigima/tools/image/preprocessing.py::fit_ellipse_model — stop swapping a/b in both branches, mirror the angle into the (x, y) convention, enforce a >= b, fix the misleading comments, document that the input contour is (row, col)
sigima/tools/image/preprocessing.py::_estimate_ellipse_params — make the fallback consistent with the library branches
sigima/tools/coordinates.py lines 101 and 120 — restore the perpendicular minor axis
sigima/tools/coordinates.py::ellipse_to_center_axes_angle (and array variant) — stop deriving the minor axis from the major-axis endpoints
sigima/objects/shape.py::EllipseCoordinates — docstring and behaviour
sigima/objects/scalar/geometry.py — user-visible ["x", "y", "a", "b", "θ"] result headers
Open mirror issues in DataLab and DataLab-Web (ellipse result columns and overlays)
Impact
User-visible: the a and b columns of ellipse geometry results are swapped, the θ column is mirrored, and the minor-axis overlay is drawn non-perpendicular. Present since v1.1.4.
Fixing this is a behaviour change for every consumer of ellipse results and must be announced as such.
Summary
An audit carried out before the 1.1.6 release showed that the ellipse convention is inconsistent across the whole chain — fitting, coordinate conversion, shape objects, result headers and downstream consumers. Two independent defects partially compensate each other, which is why the existing tests pass and why the problem has survived a fix-then-revert cycle.
This issue deliberately covers the entire ellipse chain at once, rather than patching a single function, so that the convention is settled once and for all.
Defect 1 —
fit_ellipse_modelviolates its documented contractsigima/tools/image/preprocessing.py::fit_ellipse_modeldocuments its return value as(xc, yc, a, b, theta)"where a and b are semi-major and semi-minor axes".Measured on synthetic ellipses with true semi-axes of 30 (major) and 10 (minor):
areturnedbreturnedθreturnedSo
ais always the semi-minor axis,bthe semi-major axis, andθis mirrored (90° − θ_true).Both scikit-image branches swap the axes:
a, b = model.axis_lengths[1], model.axis_lengths[0], with a comment claimingaxis_lengthsis(semi_row, semi_col)— it is in fact(semi-major, semi-minor), so the swap is unjustified;yc, xc, b, a, theta = model.params— same inversion.The
TypeErrorfallback_estimate_ellipse_paramsis inconsistent with both: it swapsx/yandwidth/heightbut returnsphiunmirrored.Defect 2 — the minor-axis diameter is not perpendicular to the major axis
sigima/tools/coordinates.py, lines 101 and 120:The dot product of the two returned axes is
a·b·sin(2θ), which is non-zero. Measured angle between the two axes returned byellipse_to_diameters:At θ = 45° the two "axes" are collinear. The correct form is
dxb, dyb = -b * np.sin(theta), b * np.cos(theta).Why this was not caught
ellipse_to_center_axes_angle(and its array variant) derives θ and both axis lengths from the major-axis endpoints only. The round-trip testsigima/tests/common/tools_coordinates_geom_unit_test.py::test_ellipse_roundtrip_scalartherefore passes while completely ignoring the faulty quantity.sigima/tests/image/preprocessing_tools_unit_test.py::test_fit_ellipse_model_successunpacksyc, xc, a, b, theta— i.e. it was written to match the implementation, not the documented contract.History
d47c6e4(2026-05-28), "fix(ellipse): correct sign for ellipse minor axis diameter calculation", applied the correct-b*sinform and shipped in v1.1.3.dfe62a3(2026-06-04), "fix sign after regression", reverted it, with no accompanying test and no release-note change. It shipped in v1.1.4 and v1.1.5.doc/release_notes/release_1.01.mdstill claims, in its v1.1.3 section, that this bug is fixed.Defect 1 is almost certainly the reason defect 2 was reintroduced: because
fit_ellipse_modelreturns a mirrored angle, the wrong sign incoordinates.pymakes the major axis look right on screen, which was mistaken for a regression when the correct sign was applied.Blocking prerequisite
The
_USE_NEW_SHAPE_APIbranch offit_ellipse_modelis never exercised in the current development environment (installed scikit-image is < 0.26, soEllipseModel.from_estimateraisesAttributeError). Fixing the axis convention there would be done blind — which is exactly how the previous fix-then-revert cycle happened.A CI matrix covering both scikit-image < 0.26 and >= 0.26 is a prerequisite for this issue.
Scope
sigima/tools/image/preprocessing.py::fit_ellipse_model— stop swappinga/bin both branches, mirror the angle into the(x, y)convention, enforcea >= b, fix the misleading comments, document that the input contour is(row, col)sigima/tools/image/preprocessing.py::_estimate_ellipse_params— make the fallback consistent with the library branchessigima/tools/coordinates.pylines 101 and 120 — restore the perpendicular minor axissigima/tools/coordinates.py::ellipse_to_center_axes_angle(and array variant) — stop deriving the minor axis from the major-axis endpointssigima/objects/shape.py::EllipseCoordinates— docstring and behavioursigima/objects/scalar/geometry.py— user-visible["x", "y", "a", "b", "θ"]result headerssigima/viz/viz_plotpy.py,sigima/tools/image/detection.py,sigima/tools/image/geometry.pyais the semi-major axis,θis not mirrored, the two returned axes are orthogonalsigima/tests/image/contour_unit_test.pyImpact
User-visible: the
aandbcolumns of ellipse geometry results are swapped, theθcolumn is mirrored, and the minor-axis overlay is drawn non-perpendicular. Present since v1.1.4.Fixing this is a behaviour change for every consumer of ellipse results and must be announced as such.