feat: a suggested polygon follows the mask to a pixel tolerance - #781
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 thecontourevery 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 withintoleranceof 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.tolerancereplaces the three-stepdetail: default1.0, refused outside0.25–16(never clamped, soapplied.tolerancenever reports a value the server did not use).parametersnames"tolerance"for a polygon class and stays empty for a box class.detailis now an unknown field and is refused.openapi.jsonand 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.mdanddocs/content/ui/annotator.md(the slider and the brackets).Found, not fixed
Test plan
Run locally on the branch head:
uv run pytest tests/inference tests/kernel tests/server— 3303 passed, 30 skippeduv run lint-imports— 4 contracts kept;uv run mypy src/visionset— clean;uv run ruff check ./ruff format --check .— cleanpnpm -r build && pnpm -r test && pnpm -r lint— annotator 1071, ui-core 1302, app 11, all greenCI=1 … playwright test e2e/annotate.spec.ts— 89 passedpnpm generate:client:check— generated client matchesopenapi.json; regenerating the simplification fixture produces no diffNew 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.