feat: per-class max box fraction (REQ-188)

- labeling/preview/autolabel: max_box_frac + per-class max_box_fracs
  ceiling filter (0=none, 1=off) before NMS, mirrors min_box_frac
- exemplar review-assist: ceiling before max_detections truncation;
  ExemplarLabelRequest + filter panel 'Max box size' slider
- ClassParamsTable: MaxBox column; copy line now REQ-186 amended
  order: class <name> conf <v> iou <v> minbox <v> maxbox <v> container
- docs: design/ui-spec/tasks updated (incl. stale 4-slider narrative)
This commit is contained in:
asus committed 2026-10-02 17:27:11 +07:00
1 parent dee58e4ae5
commit d3a6aa49b6
13 files changed
+90 -25

No files matched your search

+8 -2
View File
@@ -27,7 +27,7 @@ def _parse_class_params(raw) -> dict:
if not isinstance(values, dict):
continue
entry = {}
for key in ("threshold", "iou_threshold", "min_box_frac"):
for key in ("threshold", "iou_threshold", "min_box_frac", "max_box_frac"):
if key in values:
try:
entry[key] = float(values[key])
@@ -229,6 +229,9 @@ def _run_autolabel(job) -> None:
mb = cp.get("min_box_frac")
if mb and mb > 0 and (xyxyn[2]-xyxyn[0])*(xyxyn[3]-xyxyn[1]) < mb:
continue
xb = cp.get("max_box_frac")
if xb and xb < 1 and (xyxyn[2]-xyxyn[0])*(xyxyn[3]-xyxyn[1]) > xb:
continue
all_raw_detections.append(labeling.Detection(
class_id=target_class_id,
@@ -240,7 +243,7 @@ def _run_autolabel(job) -> None:
if selected_engine == "sam3" and sam3_target_classes:
prompts = [(c.get("prompt") or c["name"]).strip() for c in sam3_target_classes]
thr_list = iou_list = mb_list = None
thr_list = iou_list = mb_list = mx_list = None
if per_class:
names = [c["name"].strip().lower() for c in sam3_target_classes]
if any("threshold" in v for v in per_class.values()):
@@ -249,12 +252,15 @@ def _run_autolabel(job) -> None:
iou_list = [per_class.get(n, {}).get("iou_threshold", iou_thresh) for n in names]
if any("min_box_frac" in v for v in per_class.values()):
mb_list = [per_class.get(n, {}).get("min_box_frac", job.params.get("min_box_frac", 0.0)) for n in names]
if any("max_box_frac" in v for v in per_class.values()):
mx_list = [per_class.get(n, {}).get("max_box_frac", 1.0) for n in names]
res = labeling.label_image(
frame_file, frame["filename"], prompts, conf,
iou_threshold=iou_thresh, min_box_frac=job.params.get("min_box_frac", 0.0),
thresholds=thr_list,
iou_by_class=dict(enumerate(iou_list)) if iou_list else None,
min_box_fracs=mb_list,
max_box_fracs=mx_list,
container_ids=prompt_container_ids,
)
if not res.error and res.detections: