feat(ml): Settings → Tagging 'Crop proposers' card (#134 step 3)
Exposes the detector config (per-proposer enable + weights + confidence, caps, dedupe IoU) in Settings → Tagging, backed by MLSettings via /api/ml/settings. ml_admin adds the detector fields to _EDITABLE + GET payload + validation (conf 0..1, caps >=1, IoU 0..1). New CropProposersCard.vue (mirrors HeadsCard) with working defaults pre-filled, per-field live-save (no restart — the agent picks changes up on its next lease), weights-format help, switch-revert on error. Closes milestone #134: all three proposers are on out-of-the-box and tunable in the UI. Test: detector defaults GET + patch round-trip + range validation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
@@ -8,6 +8,26 @@ from ..models import MLSettings
|
||||
ml_admin_bp = Blueprint("ml_admin", __name__, url_prefix="/api/ml")
|
||||
|
||||
|
||||
# Crop-proposer / detector config (#134). Announced to the GPU agent in the lease
|
||||
# → tunable here with no restart. weights = ultralytics name | URL | hf_repo::file
|
||||
# (empty, or enabled off, skips that proposer).
|
||||
_DETECTOR_FIELDS = (
|
||||
"detector_person_enabled",
|
||||
"detector_person_weights",
|
||||
"detector_person_conf",
|
||||
"detector_anatomy_enabled",
|
||||
"detector_anatomy_weights",
|
||||
"detector_anatomy_conf",
|
||||
"detector_panel_enabled",
|
||||
"detector_panel_weights",
|
||||
"detector_panel_conf",
|
||||
"detector_max_figures",
|
||||
"detector_max_components",
|
||||
"detector_max_panels",
|
||||
"detector_max_regions",
|
||||
"detector_dedupe_iou",
|
||||
)
|
||||
|
||||
_EDITABLE = (
|
||||
"cpu_embed_enabled",
|
||||
"video_frame_interval_seconds",
|
||||
@@ -21,6 +41,7 @@ _EDITABLE = (
|
||||
"ccip_auto_apply_threshold",
|
||||
"embedder_model_name",
|
||||
"embedder_model_version",
|
||||
*_DETECTOR_FIELDS,
|
||||
)
|
||||
|
||||
|
||||
@@ -76,6 +97,7 @@ async def get_settings():
|
||||
"ccip_auto_apply_enabled": s.ccip_auto_apply_enabled,
|
||||
"ccip_auto_apply_threshold": s.ccip_auto_apply_threshold,
|
||||
"embedder_model_name": s.embedder_model_name,
|
||||
**{f: getattr(s, f) for f in _DETECTOR_FIELDS},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -133,6 +155,19 @@ def _validate(p: dict) -> str | None:
|
||||
for key in ("embedder_model_name", "embedder_model_version"):
|
||||
if not str(p[key]).strip():
|
||||
return f"{key} must not be empty"
|
||||
# Crop proposers (#134). Weights may be empty (that proposer is just off);
|
||||
# confidences are probabilities; caps are positive counts; IoU is [0,1].
|
||||
for key in ("detector_person_conf", "detector_anatomy_conf", "detector_panel_conf"):
|
||||
if not (0.0 <= float(p[key]) <= 1.0):
|
||||
return f"{key} must be between 0 and 1"
|
||||
for key in (
|
||||
"detector_max_figures", "detector_max_components",
|
||||
"detector_max_panels", "detector_max_regions",
|
||||
):
|
||||
if int(p[key]) < 1:
|
||||
return f"{key} must be >= 1"
|
||||
if not (0.0 <= float(p["detector_dedupe_iou"]) <= 1.0):
|
||||
return "detector_dedupe_iou must be between 0 and 1"
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user