Skip to content

feat: a suggested polygon follows the mask to a pixel tolerance - #781

Merged
JArmandoAnaya merged 8 commits into
mainfrom
feat/suggest-tolerance
Aug 25, 2026
Merged

feat: a suggested polygon follows the mask to a pixel tolerance#781
JArmandoAnaya merged 8 commits into
mainfrom
feat/suggest-tolerance

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

What changed

A suggested polygon now follows the segmenter's mask to within a pixel tolerance the person chooses, and its outline is smooth rather than a staircase.

The pipeline (src/visionset/inference/masks.py). The mask is traced along its pixels' edges rather than through pixel centres, so a lone pixel is its unit square and the ring describes where the mask ends. One pass of corner cutting over those unit edges turns the staircase a pixel grid imposes into a smooth line while moving no real corner by more than half a pixel — a 51 px square still comes back as four corners. That ring, reduced once at a quarter pixel, is the contour every answer carries. The polygon is then simplified at an absolute tolerance in asset pixels, and the promise is stated as a bound the tests measure: every point of the contour lies within tolerance of the polygon. On a 250 px-radius disc that is 49 vertices at 1 px and 600 at 0.5 px; the previous diagonal-relative setting gave about 25 at its finest.

The wire. SuggestRequest.tolerance replaces the three-step detail: default 1.0, refused outside 0.2516 (never clamped, so applied.tolerance never reports a value the server did not use). parameters names "tolerance" for a polygon class and stays empty for a box class. detail is now an unknown field and is refused. openapi.json and the generated client are regenerated.

The editor. The annotator's simplifier mirrors the kernel's at the same absolute tolerance, held to it by the regenerated golden fixture at every stop from 0.25 to 16 px. [ doubles the tolerance and ] halves it along the power-of-two ladder; the slider runs on a doubling track between the two ends and a value it leaves between stops snaps to the nearest stop on the next bracket press. A coarse tolerance can leave a small object with nothing to show; the suggestions are kept, so stepping finer brings the shape back. The visible label stays "Detail".

Docs. docs/content/inference.md (the pipeline and the setting), docs/content/ui.md and docs/content/ui/annotator.md (the slider and the brackets).

Found, not fixed

  • The canonical contour is larger than before (about 1 200 points for a 600 px-radius object). It is within the design's budget and travels once per click; no wire-size gate exists.
  • A refine click made while a coarse tolerance has emptied the preview repaints the last shape for the duration of the request, which is the existing "keep the best answer anyone has" rule applied to a state that could not previously occur.

Test plan

Run locally on the branch head:

  • uv run pytest tests/inference tests/kernel tests/server — 3303 passed, 30 skipped
  • uv run lint-imports — 4 contracts kept; uv run mypy src/visionset — clean; uv run ruff check . / ruff format --check . — clean
  • pnpm -r build && pnpm -r test && pnpm -r lint — annotator 1071, ui-core 1302, app 11, all green
  • CI=1 … playwright test e2e/annotate.spec.ts — 89 passed
  • pnpm generate:client:check — generated client matches openapi.json; regenerating the simplification fixture produces no diff

New coverage: the fidelity bound is asserted for a disc, a lobed blob and a rectangle at all seven tolerances; the edge trace is pinned on a single pixel, a diagonal pinch, an enclosed hole and a two-pixel piece; the fixture proves both mirrors agree point for point; Playwright walks the ladder from the keyboard and drags the slider to both ends.

…ance

The mask is traced along its pixels' edges, smoothed once with corner cutting
over those unit edges, and reduced at a quarter pixel to form the canonical
contour. The polygon is then simplified at an absolute tolerance in asset
pixels: every point of the contour lies within it of the polygon. The
three-step detail vocabulary and its diagonal-relative tolerance are removed
from the domain.
SuggestRequest.tolerance defaults to 1.0 and is refused outside 0.25-16,
never clamped; applied.tolerance echoes it and parameters names it for a
polygon class. detail is an unknown field. openapi.json and the generated
client are regenerated.
Golden contours are now sub-pixel and each case carries the polygon at every
stop from 0.25 to 16 px; the refuse case is an empty mask.
The editor's simplifier mirrors the kernel's Douglas-Peucker at an absolute
tolerance; the brackets double and halve it along the power-of-two ladder,
snapping a slider-chosen value to the nearest stop first. A coarse tolerance
that leaves nothing keeps the suggestions, so a finer one restores the shape;
the preview and the count read nothing while there is nothing to show.
The slider's position is log2 of the tolerance between 0.25 and 16 px, the
value is rounded at the callback, and the label reads the tolerance and the
vertex count. The request carries tolerance and reads applied.tolerance back.
@JArmandoAnaya
JArmandoAnaya merged commit 3b16865 into main Aug 25, 2026
15 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the feat/suggest-tolerance branch August 25, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant