Files
SlabUploader/docs/TECH-SPEC-PIPELINE.md
T
grokbot_ty fc5df28b8c docs: pair locks for #14 #2 #17 #18 (PPM product OK)
Two taxonomy stamps, validation/module + 412 hash mismatch, SKU charset
vs UTF-8 titles, 409 open_existing required, draft = phone form + failed row.
2026-09-02 00:34:44 +00:00

237 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `taxonomy_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) |