be221433ea
TECH-SPEC states BG removal defines the measurement footprint and silent corruption failure mode. Four knobs stay on capture with live overlay on the source photo. POC is client-side only; U2Net try-harder deferred, not deleted. ARCHITECTURE swimlane and clarifications match.
237 lines
10 KiB
Markdown
237 lines
10 KiB
Markdown
# Technical Spec: Deterministic Image Pipeline & Area → Bdft
|
||
|
||
Status: SPEC · 2026-08-27 · Gate A freeze 2026-08-31 · TyTech / SlabUploader
|
||
Scope: algorithms and test vectors for the deterministic core (client-authoritative).
|
||
No inference, no LLM. Everything here is a pure function of (pixels, user inputs) → outputs.
|
||
Inference, Woo payload, API, and schema live in other docs.
|
||
|
||
---
|
||
|
||
## 1. Invariants
|
||
|
||
1. **Determinism** — same inputs → bit-identical outputs. No randomness, no timestamps in math, no network calls for the math.
|
||
2. **Manual entry is authoritative** — user-entered length, thickness, SKU, and price always win over any computed value.
|
||
3. **Fallback chain is explicit** — every computed value has a defined fallback; the system never silently guesses.
|
||
- BG removal: chroma-key / threshold → four knobs (sheet, sensitivity, edge offset, feather) live on capture → retake. U2Net last-resort is deferred from POC (do not delete from architecture).
|
||
- Length axis: min-area bounding rect → user rotate/confirm → retake if the photo is unusable.
|
||
- Area: requires a single contiguous slab mask; broken mask → flag and retake.
|
||
4. **Units** — inches in 1/8" steps; sqft and bdft to 2 decimals; price half-up to 2 decimals. Those rounded values are what get stored. No sqin in the UI, tests, or product copy.
|
||
5. **Client is authoritative** — all deterministic processing runs in the browser. The server stores the numbers and files the client sends, and handles Woo sync.
|
||
|
||
## 2. Units and rounding
|
||
|
||
| Quantity | Source | Stored / displayed |
|
||
|---|---|---|
|
||
| Length | user, 1/8" steps | nearest 1/8" |
|
||
| Thickness | user pick, 1/8" steps | that pick (used in math and copy) |
|
||
| Thickness Woo attribute | same pick, rounded **up** into a Woo range | Woo term, not the 1/8" value |
|
||
| sqft | computed from mask | 2 decimals |
|
||
| bdft | sqft × thickness (inches) | 2 decimals |
|
||
| Price | species $/bdft × bdft, or override | 2 decimals, half-up |
|
||
|
||
- bdft is never rounded up to whole board feet.
|
||
- Pixel→inch scale is an implementation detail. Users never see pixels.
|
||
|
||
## 3. Background removal (load-bearing)
|
||
|
||
Background removal is the **load-bearing phase** of the image pipeline. The mask
|
||
defines the slab's pixel footprint. Length axis, 6" width samples, sqft, and bdft
|
||
all derive from that footprint. A clipped or over-inclusive mask silently shifts
|
||
the bounding rectangle and width samples with no later numeric check to catch it.
|
||
The user's eye on the overlay is the only validator. There is no confidence score
|
||
and no auto-gate on mask quality.
|
||
|
||
### 3.1 Auto-detect sheet
|
||
1. Sample a 2–5% strip on all four borders.
|
||
2. Average HSV per side.
|
||
3. Hue in [35, 85] and saturation ≥ 40 → green.
|
||
4. Else value ≤ 30 and saturation ≤ 20 → black.
|
||
5. Else prompt: pick green or black.
|
||
|
||
### 3.2 Visual feedback (required)
|
||
- Overlay the mask on the **source photo** (included vs excluded pixels), not only
|
||
a cropped result.
|
||
- Show the slab edge clearly. That edge is the measurement boundary.
|
||
- User confirms the edge before length / area run.
|
||
|
||
### 3.3 Capture knobs (inline, live, persisted)
|
||
Factory values live in code. If the user changes them, the new values persist in
|
||
localStorage until reset-to-default.
|
||
|
||
All four knobs are **on the capture screen next to the overlay**, not behind a
|
||
Settings navigation. Changing any knob **immediately re-runs** §3.4 on the current
|
||
photo and refreshes the overlay on the source image. No apply/save step. No server
|
||
round trip for mask tuning in POC.
|
||
|
||
1. **Sheet** — auto / green / black.
|
||
2. **Sensitivity** — one slider; tighter vs looser detection (green: HSV range width; black: brightness cutoff).
|
||
3. **Edge offset** — pull the mask in or out a few pixels so the cut sits on the wood, not in the sheet or into the grain.
|
||
4. **Feather** — hard cut vs a couple of pixels of alpha blend.
|
||
|
||
### 3.4 Algorithms
|
||
- **Green:** chroma-key in HSV. Border flood-fill so only sheet pixels connected to the image edge are removed (protects green-ish grain inside the slab).
|
||
- **Black:** grayscale threshold. Same flood-fill (protects dark grain).
|
||
- **POC path:** client-side only (sheet detect + four knobs). If the edge is still
|
||
wrong after tuning, **retake**.
|
||
- **Post-POC fallback (deferred, not deleted):** U2Net matting on one photo via
|
||
FastAPI. Mask returns; knobs still apply on the client. Do not ship the control
|
||
in Gate C / first listing. Do not remove the contract from architecture.
|
||
- Morphological close, then feather per the setting.
|
||
|
||
### 3.5 Edge quality
|
||
- The mask must be one contiguous slab contour.
|
||
- If the user cannot get a clean edge with knobs, retake is the POC resolution.
|
||
|
||
## 4. Length axis, widths, sqft, bdft
|
||
|
||
### 4.1 Contour and length axis
|
||
1. Binary mask M from §3.
|
||
2. Largest contour → convex hull.
|
||
3. Minimum-area bounding rectangle on the hull.
|
||
4. Long side = length axis; short side = width axis.
|
||
This follows the **prevailing** length, not a diagonal cut on one end.
|
||
|
||
### 4.2 Visual feedback (required)
|
||
- Overlay the bounding rectangle and the length axis.
|
||
- Width sample lines drawn perpendicular to that axis.
|
||
- User confirms the axis matches the slab’s prevailing length.
|
||
- **Rotate axis** if it is skewed; samples and sqft scale update live.
|
||
- Nearly square slabs: user says which axis is length.
|
||
|
||
### 4.3 Scale
|
||
User length L (inches, 1/8") is authoritative.
|
||
|
||
```
|
||
scale = longest_axis_px / L
|
||
```
|
||
|
||
### 4.4 Widths (listing only, not bdft)
|
||
- Sample every **6"** along the confirmed length axis, perpendicular to it.
|
||
- Report min, max, and average width for the description.
|
||
- Auto-dimension overlay on the photo is **v1.5**, out of this spec.
|
||
|
||
### 4.5 sqft
|
||
Projected top-down area of the mask. Live-edge thickness and the underside are not in the area.
|
||
|
||
```
|
||
sqft = (mask_pixel_count / scale²) / 144
|
||
```
|
||
|
||
Round to 2 decimals.
|
||
|
||
### 4.6 bdft
|
||
|
||
```
|
||
bdft = sqft × thickness_in
|
||
```
|
||
|
||
Round to 2 decimals. Identity: 1 sqft at 1" thick = 1 bdft.
|
||
|
||
## 5. Photo normalization
|
||
|
||
Applies to every inventory photo of the slab (first photo and the rest).
|
||
|
||
- Crop aspect: **3:4 portrait** (default; matches the WooCommerce template;
|
||
configurable in settings).
|
||
- Transparent **PNG only**.
|
||
- Slab centered. **80% fill at the extremes** (widest and tallest points of the slab). Extra margin where the slab is narrower.
|
||
- After crop, if the source has enough pixels, shorter side ≥ **1600 px**. If it cannot, warn and ask for a retake — do not invent pixels.
|
||
- Original + processed kept while draft/retry; both purged after successful publish (server storage; see Data Model).
|
||
|
||
## 6. Pricing
|
||
|
||
- Settings: sync **species** from Woo; user sets **$/bdft per species**.
|
||
- Capture: `recommendation = bdft × species.price_per_bdft`, pre-fills price.
|
||
- User may override. Override sticks for that slab.
|
||
- No rule for that species → empty price; user types it. Publish still allowed.
|
||
- Stored price: half-up to 2 decimals.
|
||
- Figure- or category-weighted price is **v1.5**.
|
||
|
||
## 7. Constants
|
||
|
||
### 7.1 Code (not in Settings)
|
||
```
|
||
FILL_TARGET = 0.80
|
||
ASPECT_W, ASPECT_H = 3, 4
|
||
OUTPUT_PX_MIN = 1600
|
||
WIDTH_SAMPLE_IN = 6
|
||
INCH_STEP = 0.125
|
||
AREA_DECIMALS = 2
|
||
BDFT_DECIMALS = 2
|
||
PRICE_DECIMALS = 2
|
||
```
|
||
|
||
### 7.2 Factory defaults for persisted BG settings
|
||
```
|
||
SHEET = auto
|
||
GREEN_HUE_LO/HI = 35, 85
|
||
GREEN_SAT_MIN = 40
|
||
GREEN_VAL_MIN = 40
|
||
BLACK_THRESH = 30
|
||
```
|
||
Sensitivity, edge offset, and feather map onto these at runtime. Users do not edit the raw HSV numbers.
|
||
|
||
## 8. Test vectors
|
||
|
||
Users think in inches, sqft, and bdft. Pixels are only how the computer gets there.
|
||
|
||
### TV-1 Green sheet
|
||
Synthetic: green border, wood in the center. Interior green-ish patch that does not touch the border stays. Coverage enough that U2Net does not run.
|
||
|
||
### TV-2 Black sheet
|
||
Same layout, black border, mid-tone wood. Dark interior wood that does not touch the border stays.
|
||
|
||
### TV-3 Scale, sqft, bdft
|
||
Slab **96" long**, **1.5" thick**. Mask measures **0.64 sqft**.
|
||
bdft = 0.64 × 1.5 = **0.96**.
|
||
|
||
### TV-4 Width samples
|
||
96" length axis, stations every 6". Widths perpendicular to the confirmed axis. Listing gets min, max, avg.
|
||
|
||
### TV-5 Angled end
|
||
One square end, one 45° cut. Length axis follows prevailing length, not the diagonal. Widths stay perpendicular after user confirm.
|
||
|
||
### TV-6 3:4 crop, 80% at extremes
|
||
Slab centered in a 3:4 frame. At the widest and tallest points, fill = 0.80. PNG with alpha. Shorter side ≥ 1600 when the source allows.
|
||
|
||
### TV-7 Too-small source
|
||
After 80% crop, shorter side would be below 1600 and the source cannot supply it. Warn. Do not upscale from nothing. That photo is not publishable until retake.
|
||
|
||
### TV-8 Pricing
|
||
bdft 0.96, species $12.50/bdft → rec **12.00**.
|
||
Override **150.00** stored as 150.00.
|
||
No rule → rec empty; manual 99.99 stored as 99.99.
|
||
|
||
### TV-9 Thickness → Woo range
|
||
User pick **1"** stored as 1". Attribute bucket is the Woo range that 1" rounds **up** into (e.g. `1"–1 1/2"`). Exact range labels come from the synced Woo attribute, not this file.
|
||
|
||
### TV-10 Rounding
|
||
Length 96.04" → **96"** (nearest 1/8").
|
||
sqft 0.641 → **0.64**.
|
||
bdft 11.525 → **11.53**.
|
||
Price 12.345 → **12.35**.
|
||
|
||
## 9. Assumptions
|
||
|
||
1. The first photo is top-down, lens parallel to the broad face. Tilted shots make sqft/bdft wrong; retake is the fix.
|
||
2. User length is the prevailing length of that face, in 1/8". The bounding-box axis is confirmed in the UI.
|
||
3. Thickness is the declared 1/8" pick, not measured from any photo.
|
||
4. sqft is the projected top-down area of the mask. Live-edge thickness and underside are not in the area.
|
||
5. bdft = sqft × thickness (inches). Not rounded up to whole board feet.
|
||
6. Sheet is uniform green or black. Odd colors → user picks sheet mode or retakes.
|
||
7. 3:4 portrait is the PIP crop, configurable in settings.
|
||
8. Client does all of this math. Server stores the numbers the client sends.
|
||
|
||
## 10. Out of scope here (covered in other docs)
|
||
|
||
| Topic | Doc |
|
||
|---|---|
|
||
| Call 1 / Call 2 prompts, taxonomy mapping | PROMPTS, CONTENT-WOO |
|
||
| Portable Call 1→Call 2 context (full resend default) | ARCHITECTURE §9, PROMPTS |
|
||
| Woo payload, publish, 409/422 recovery | CONTENT-WOO, OPENAPI |
|
||
| SQLite tables, status machine | DATA-MODEL |
|
||
| HTTP contract, taxonomy `cache_anchor` | OPENAPI |
|
||
| Client vs server topology, validation module | ARCHITECTURE |
|
||
| Auto-dimension overlay, figure-weighted price | IMPL-PLAN (v1.5) |
|
||
| Offline capture | non-goal (online-only POC; see AGENTS) |
|