Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- src/ring_size.py +26 -3
- web_demo/app.py +13 -3
- web_demo/supabase_client.py +5 -3
- web_demo/templates/admin.html +1 -1
src/ring_size.py
CHANGED
|
@@ -32,6 +32,12 @@ DEFAULT_RING_MODEL = "gen"
|
|
| 32 |
# Backwards-compatible alias
|
| 33 |
RING_SIZE_CHART = RING_MODELS[DEFAULT_RING_MODEL]
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
def _get_sorted_sizes(ring_model: str) -> List[Tuple[int, float]]:
|
| 37 |
chart = RING_MODELS.get(ring_model, RING_MODELS[DEFAULT_RING_MODEL])
|
|
@@ -51,7 +57,7 @@ def recommend_ring_size(diameter_cm: float, ring_model: str = DEFAULT_RING_MODEL
|
|
| 51 |
"""
|
| 52 |
diameter_mm = diameter_cm * 10.0
|
| 53 |
|
| 54 |
-
if diameter_mm <
|
| 55 |
return None
|
| 56 |
|
| 57 |
sorted_sizes = _get_sorted_sizes(ring_model)
|
|
@@ -105,15 +111,32 @@ def aggregate_ring_sizes(per_finger_results: Dict[str, Dict]) -> Dict:
|
|
| 105 |
# Build per_finger summary
|
| 106 |
per_finger: Dict[str, Dict] = {}
|
| 107 |
for name, result in per_finger_results.items():
|
| 108 |
-
failed = result.get("fail_reason") is not None or result.get("ring_size") is None
|
| 109 |
rs = result.get("ring_size")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
per_finger[name] = {
|
| 111 |
"diameter_cm": result.get("finger_outer_diameter_cm"),
|
| 112 |
"confidence": result.get("confidence", 0.0),
|
| 113 |
"best_match": rs["best_match"] if rs else None,
|
| 114 |
"range": [rs["range_min"], rs["range_max"]] if rs else None,
|
| 115 |
"status": "failed" if failed else "ok",
|
| 116 |
-
"fail_reason":
|
| 117 |
}
|
| 118 |
|
| 119 |
# Filter to succeeded fingers
|
|
|
|
| 32 |
# Backwards-compatible alias
|
| 33 |
RING_SIZE_CHART = RING_MODELS[DEFAULT_RING_MODEL]
|
| 34 |
|
| 35 |
+
# Diameter bounds for the ring-size lookup. Below MIN or above MAX,
|
| 36 |
+
# recommend_ring_size() returns None and aggregate_ring_sizes() tags the
|
| 37 |
+
# per-finger row as failed with diameter_below_chart_min / diameter_above_chart_max.
|
| 38 |
+
MIN_DIAMETER_MM = 12.0
|
| 39 |
+
MAX_DIAMETER_MM = 26.0
|
| 40 |
+
|
| 41 |
|
| 42 |
def _get_sorted_sizes(ring_model: str) -> List[Tuple[int, float]]:
|
| 43 |
chart = RING_MODELS.get(ring_model, RING_MODELS[DEFAULT_RING_MODEL])
|
|
|
|
| 57 |
"""
|
| 58 |
diameter_mm = diameter_cm * 10.0
|
| 59 |
|
| 60 |
+
if diameter_mm < MIN_DIAMETER_MM or diameter_mm > MAX_DIAMETER_MM:
|
| 61 |
return None
|
| 62 |
|
| 63 |
sorted_sizes = _get_sorted_sizes(ring_model)
|
|
|
|
| 111 |
# Build per_finger summary
|
| 112 |
per_finger: Dict[str, Dict] = {}
|
| 113 |
for name, result in per_finger_results.items():
|
|
|
|
| 114 |
rs = result.get("ring_size")
|
| 115 |
+
upstream_reason = result.get("fail_reason")
|
| 116 |
+
failed = upstream_reason is not None or rs is None
|
| 117 |
+
|
| 118 |
+
# If the measurement succeeded but ring_size is None, the diameter
|
| 119 |
+
# fell outside the chart bounds — surface that as a fail_reason so
|
| 120 |
+
# the dashboard does not show "failed, reason unknown".
|
| 121 |
+
derived_reason = upstream_reason
|
| 122 |
+
if derived_reason is None and rs is None:
|
| 123 |
+
diameter_cm = result.get("finger_outer_diameter_cm")
|
| 124 |
+
if diameter_cm is None:
|
| 125 |
+
derived_reason = "ring_size_lookup_failed"
|
| 126 |
+
elif diameter_cm * 10.0 < MIN_DIAMETER_MM:
|
| 127 |
+
derived_reason = "diameter_below_chart_min"
|
| 128 |
+
elif diameter_cm * 10.0 > MAX_DIAMETER_MM:
|
| 129 |
+
derived_reason = "diameter_above_chart_max"
|
| 130 |
+
else:
|
| 131 |
+
derived_reason = "ring_size_lookup_failed"
|
| 132 |
+
|
| 133 |
per_finger[name] = {
|
| 134 |
"diameter_cm": result.get("finger_outer_diameter_cm"),
|
| 135 |
"confidence": result.get("confidence", 0.0),
|
| 136 |
"best_match": rs["best_match"] if rs else None,
|
| 137 |
"range": [rs["range_min"], rs["range_max"]] if rs else None,
|
| 138 |
"status": "failed" if failed else "ok",
|
| 139 |
+
"fail_reason": derived_reason,
|
| 140 |
}
|
| 141 |
|
| 142 |
# Filter to succeeded fingers
|
web_demo/app.py
CHANGED
|
@@ -556,9 +556,19 @@ def _compute_stats(rows: List[Dict[str, Any]], days: int = 30) -> Dict[str, Any]
|
|
| 556 |
if m:
|
| 557 |
mode_counter[m] += 1
|
| 558 |
|
| 559 |
-
size
|
| 560 |
-
|
| 561 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
|
| 563 |
conf = row.get("confidence")
|
| 564 |
if isinstance(conf, (int, float)):
|
|
|
|
| 556 |
if m:
|
| 557 |
mode_counter[m] += 1
|
| 558 |
|
| 559 |
+
# Ring size distribution: index-finger size only, and exclude demo
|
| 560 |
+
# runs (photo_url is null when the user clicks "try with sample" —
|
| 561 |
+
# those measurements aren't the KOL's true size).
|
| 562 |
+
if row.get("photo_url"):
|
| 563 |
+
index_size = None
|
| 564 |
+
if row.get("mode") == "multi":
|
| 565 |
+
pf = (row.get("per_finger") or {}).get("index") or {}
|
| 566 |
+
if pf.get("status") == "ok":
|
| 567 |
+
index_size = pf.get("best_match")
|
| 568 |
+
elif row.get("finger_index") == "index":
|
| 569 |
+
index_size = row.get("overall_best_size")
|
| 570 |
+
if index_size not in (None, ""):
|
| 571 |
+
size_counter[str(index_size)] += 1
|
| 572 |
|
| 573 |
conf = row.get("confidence")
|
| 574 |
if isinstance(conf, (int, float)):
|
web_demo/supabase_client.py
CHANGED
|
@@ -122,15 +122,17 @@ def list_measurements(limit: int = 200, offset: int = 0) -> List[Dict[str, Any]]
|
|
| 122 |
|
| 123 |
STATS_COLUMNS = (
|
| 124 |
"id,created_at,kol_name,mode,ring_model,confidence,fail_reason,"
|
| 125 |
-
"overall_best_size,ring_fit,gt_index_size,gt_middle_size,gt_ring_size"
|
|
|
|
| 126 |
)
|
| 127 |
|
| 128 |
|
| 129 |
def list_measurements_for_stats(limit: int = 5000) -> List[Dict[str, Any]]:
|
| 130 |
"""Lightweight projection used by the admin stats endpoint.
|
| 131 |
|
| 132 |
-
Skips
|
| 133 |
-
|
|
|
|
| 134 |
"""
|
| 135 |
client = _get_client()
|
| 136 |
if client is None:
|
|
|
|
| 122 |
|
| 123 |
STATS_COLUMNS = (
|
| 124 |
"id,created_at,kol_name,mode,ring_model,confidence,fail_reason,"
|
| 125 |
+
"overall_best_size,ring_fit,gt_index_size,gt_middle_size,gt_ring_size,"
|
| 126 |
+
"finger_index,photo_url,per_finger"
|
| 127 |
)
|
| 128 |
|
| 129 |
|
| 130 |
def list_measurements_for_stats(limit: int = 5000) -> List[Dict[str, Any]]:
|
| 131 |
"""Lightweight projection used by the admin stats endpoint.
|
| 132 |
|
| 133 |
+
Skips `result_json` (the heaviest blob). `per_finger` is included so the
|
| 134 |
+
size-distribution chart can use the index-finger size and detect demo
|
| 135 |
+
runs (`photo_url` is null when the user clicks "try with sample").
|
| 136 |
"""
|
| 137 |
client = _get_client()
|
| 138 |
if client is None:
|
web_demo/templates/admin.html
CHANGED
|
@@ -211,7 +211,7 @@
|
|
| 211 |
<div class="chart-wrap"><canvas id="chartModels"></canvas></div>
|
| 212 |
</div>
|
| 213 |
<div class="chart-card">
|
| 214 |
-
<h2>Ring Size Distribution</h2>
|
| 215 |
<div class="chart-wrap"><canvas id="chartSizes"></canvas></div>
|
| 216 |
</div>
|
| 217 |
<div class="chart-card">
|
|
|
|
| 211 |
<div class="chart-wrap"><canvas id="chartModels"></canvas></div>
|
| 212 |
</div>
|
| 213 |
<div class="chart-card">
|
| 214 |
+
<h2>Ring Size Distribution (Index, real uploads)</h2>
|
| 215 |
<div class="chart-wrap"><canvas id="chartSizes"></canvas></div>
|
| 216 |
</div>
|
| 217 |
<div class="chart-card">
|