first version

This commit is contained in:
2026-04-25 16:23:09 +05:30
commit 7d3c36050b
73 changed files with 19033 additions and 0 deletions

164
docs/POLYGON_SMOOTHING.md Normal file
View File

@@ -0,0 +1,164 @@
# Tuning polygon output (point count + edge smoothness)
When SAM returns a mask, the backend converts it to a polygon before sending
it back to the Tauri annotation tool. Two things you can tune:
1. **Point count** — how many vertices the polygon has. Each vertex is
draggable in the UI, so too many makes editing painful.
2. **Edge smoothness** — how the polygon traces curved edges (smooth curves
vs. pixel staircases).
Both are controlled in one file:
[`sam2-backend/adapters/utils.py`](../sam2-backend/adapters/utils.py).
---
## Where it's called
Both adapters do the same thing in their predict path:
```python
# sam2-backend/adapters/sam2_adapter.py
return PredictResult(
polygon=_mask_to_polygon(masks_np[best]),
score=best_score,
)
# sam2-backend/adapters/sam3_adapter.py — same call site
```
The defaults are `min_points=4`, `max_points=10`, `smooth=True`. You can
override per-call:
```python
polygon=_mask_to_polygon(masks_np[best], min_points=3, max_points=15)
```
…or change the defaults in `utils.py::mask_to_polygon` and they'll apply
to every model.
---
## Knobs
### 1. Point count band — `min_points` / `max_points`
The simplifier uses DouglasPeucker (`cv2.approxPolyDP`) with a binary
search over `epsilon` to land in `[min_points, max_points]`. Within that
band it prefers **more** detail (i.e. closer to `max_points`).
| Band | Use it when… |
|------------|-----------------------------------------------------------------------------|
| `4..10` | Default. Comfortable to drag, captures common shapes. |
| `4..15` | Curvy / organic objects (animals, foliage). 15 vertices follow a curve well. |
| `3..6` | Very simple shapes (rectangles, traffic signs). Fewer drag handles. |
| `6..20` | High-fidelity work, you don't mind dragging more. |
**Avoid `max_points > 25`** — past that, the binary search needs many
iterations and the marginal benefit per vertex drops sharply. If you need
that many points, the bottleneck is mask quality, not simplification.
### 2. Mask smoothing — `smooth=True | False`
Before extracting the contour, the helper optionally runs:
1. **Morphological close + open** (ellipse kernel) — fills single-pixel
notches and removes pepper noise.
2. **Light Gaussian blur + re-threshold** — sub-pixel edge smoothing so
curves don't trace as staircases.
Pass `smooth=False` to skip both steps and trace the raw mask exactly. Use
this when:
- The mask has fine features that smoothing erases (thin antennae, sharp
90° corners that get rounded).
- You're A/B testing whether smoothing is helping or hurting.
```python
polygon=_mask_to_polygon(masks_np[best], smooth=False)
```
### 3. Smoothing kernel size — `_smooth_mask`
Inside `_smooth_mask` (called by `mask_to_polygon` when `smooth=True`),
kernel size scales with the **blob's bounding box diagonal**:
```python
k = int(round(blob_diag * 0.015)) # ← morphology kernel multiplier
k = max(3, min(k | 1, 11)) # odd, clamped 3..11
blur_k = max(3, min(k | 1, 9)) # ← Gaussian blur kernel
```
**The `0.015` multiplier is the main dial.**
| Multiplier | Effect |
|------------|-----------------------------------------------------------------|
| `0.008` | Very gentle. Preserves thin features. Use if smoothing dilates the polygon. |
| `0.015` | Default. Good balance for typical objects. |
| `0.025` | Aggressive. Use only on visibly noisy masks; will round corners. |
The 11-pixel hard cap means giant masks don't get insanely smoothed.
### 4. Drop the Gaussian, keep morphology
If hard 90° corners get rounded but you still want morphology to clean up
notches, delete just the Gaussian step in `_smooth_mask`:
```python
# remove these two lines
m = cv2.GaussianBlur(m, (blur_k, blur_k), 0)
_, m = cv2.threshold(m, 127, 255, cv2.THRESH_BINARY)
```
This keeps close+open (which is mostly noise removal) and skips the
sub-pixel curve smoothing.
---
## Diagnostic recipe — find the right knob in 3 minutes
1. **Draw a representative bbox in the Tauri app.** Screenshot the result.
2. **Set `smooth=False`** in the adapter, restart, draw the same bbox.
- If polygon is *better* (less bloat, hugs edges) → smoothing is over-aggressive. Lower the kernel multiplier or delete the Gaussian step.
- If polygon is *worse* (jagged stairs) → smoothing was helping. Move on to step 3.
3. **Bump `max_points` to 15 or 20**, restart.
- If polygon is *better* → simplification was too aggressive. Land on `max_points=12..15` as your new default.
- If polygon is the same → the mask itself is the bottleneck (see "If the raw mask is bad" below).
4. **Restore `smooth=True`** and try a model swap (SAM 2.1 large ↔ SAM 3 tracker) — the underlying mask differs noticeably between models.
---
## If the raw mask is bad
If after all the above the polygon still looks wrong, the input to the
simplifier is bad — fix that, not the polygon code. Common moves:
- **Pad the bbox before sending** to SAM (Meta's reference scripts use
`pad=10` pixels). SAM gives crisper masks with a small box margin.
- **Try a larger SAM model** (e.g. switch the dropdown from `SAM 2.1
Hiera Small` to `SAM 2.1 Hiera Large`).
- **Tighten the bbox** around the actual object — SAM tends to fill the
box, so a generous box gets a bloated mask.
---
## Quick reference — every dial in one block
```python
# sam2-backend/adapters/utils.py
mask_to_polygon(
mask,
min_points=4, # never below this
max_points=10, # never above this; fewer = easier to drag
smooth=True, # turn off to trace raw mask
)
# Inside _smooth_mask:
k = int(round(blob_diag * 0.015)) # ← MAIN DIAL
# smaller = preserve detail
# bigger = more rounded
```
If you change defaults, restart the backend (`./run.sh`). No frontend
restart needed — the polygon arrives over the existing `/predict` call.