Summary
Per-species detection tuning from each Wildbook's IA.json (sensitivity, nms_thresh, nms_aware) is not applied by ml-service on either integration path. Detection instead runs at hardcoded/global defaults. This already affects the three migrated Wildbooks, and will silently discard tuned values for every Wildbook migrated from here on (IoT sea turtles is next).
Found while investigating doubled bounding-box reports from Internet of Turtles users.
Path A — /pipeline/ (used by the migrated Wildbooks today)
PipelineRequest exposes predict_model_params for exactly this purpose (app/routers/pipeline_router.py:31-34), and it is correctly merged over the model config before inference (pipeline_router.py:164-166).
But Wildbook never populates it. MlServiceClient.buildPipelinePayload() sends only image_uri, predict_model_id, classify_model_id, extract_model_id, orientation_model_id — no predict_model_params, and no bbox_score_threshold.
Net effect on the pipeline path:
sensitivity → replaced by bbox_score_threshold, which falls back to its default of 0.5 (pipeline_router.py:32)
nms_thresh / nms_aware → dropped; NMS runs at whatever the server-side model_config.json sets (LightNet defaults to 0.4, lightnet_model.py:40,45)
Path B — /api/engine/detect/cnn/lightnet/ (WBIA-compat shim, for upcoming migrations)
WbiaDetectRequest declares both fields (app/routers/wbia_compat_router.py:140-141):
sensitivity: Optional[float] = None
nms_thresh: Optional[float] = None
nms_aware: Optional[str] = None
nms_thresh and nms_aware are then never read. _run_detection() takes only sensitivity (wbia_compat_router.py:199,234) and applies it as a post-hoc score filter. Accepting a parameter and silently ignoring it is worse than rejecting it — the caller gets no signal that its tuning was discarded.
Polarity warning for whoever fixes this
Do not pass IA.json's nms_thresh straight through. WBIA inverts it before use:
# wbia/other/ibsfuncs.py:9883
nms_thresh = 1.0 - nms_thresh
keep = detectcore.nms(coord_list, confs_list, nms_thresh) # py_cpu_nms keeps IoU <= thresh
So in IA.json a higher value means more suppression — the reverse of the usual convention, and the reverse of lightnet's own NonMaxSupression (ious > nms_thresh). A correct port must map effective_iou = 1.0 - ia_json_nms_thresh.
Note also that WBIA disables lightnet's internal NMS entirely (lightnet.py:319, nms_thresh = 1.0) and does suppression itself at the depc layer, whereas ml-service relies on lightnet's internal pass. nms_aware: "ispart" (suppress within body-vs-part pools separately) is closely approximated by lightnet's class_nms=True for models whose part classes are distinct labels, but it is not identical for byclass configs.
Affected migrated models
| Wildbook |
predict_model_id |
IA.json sensitivity → effective |
IA.json nms_thresh (→ WBIA effective IoU) |
| giraffespotter.org |
giraffe_v1 |
0.58 → 0.5 |
0.5 (→0.5), nms_aware unset |
| zebra.wildme.org |
ggr2 |
0.40 → 0.5 |
0.4 (→0.6), nms_aware unset |
| Sharkbook.ai |
fins_v1_dorsal |
0.53 → 0.5 |
0.6 (→0.4), nms_aware: byclass |
| Sharkbook.ai |
whaleshark_v0 |
0.65 → 0.5 |
0.6 (→0.4) |
| Sharkbook.ai |
shark_v0 |
0.65 → 0.5 |
0.55 (→0.45) |
| Sharkbook.ai |
leopard_shark_v0 |
0.65 → 0.5 |
0.5 (→0.5) |
The Sharkbook entries are the most consequential: three of them were tuned to sensitivity 0.65 and now run at 0.5, which should measurably increase low-confidence detections. fins_v1_dorsal additionally loses nms_aware: byclass.
(sharkfin-v1 and tigershark-v1 set neither value in IA.json, so they are unaffected.)
Suggested fix
/pipeline/ — have Wildbook forward the tuned values in predict_model_params and bbox_score_threshold; the server side already supports this, so this is mostly a MlServiceClient.buildPipelinePayload() change plus an agreed key mapping.
/api/engine/detect/cnn/lightnet/ — either honor nms_thresh / nms_aware, or return a 4xx/warning when they are supplied, so silent divergence is impossible.
- Apply the
1.0 - x polarity conversion at the boundary and document it in one place.
- Consider logging the resolved detection params per request; this class of drop is otherwise invisible in production.
Happy to send a PR for (2) if that's a useful starting point.
Summary
Per-species detection tuning from each Wildbook's
IA.json(sensitivity,nms_thresh,nms_aware) is not applied by ml-service on either integration path. Detection instead runs at hardcoded/global defaults. This already affects the three migrated Wildbooks, and will silently discard tuned values for every Wildbook migrated from here on (IoT sea turtles is next).Found while investigating doubled bounding-box reports from Internet of Turtles users.
Path A —
/pipeline/(used by the migrated Wildbooks today)PipelineRequestexposespredict_model_paramsfor exactly this purpose (app/routers/pipeline_router.py:31-34), and it is correctly merged over the model config before inference (pipeline_router.py:164-166).But Wildbook never populates it.
MlServiceClient.buildPipelinePayload()sends onlyimage_uri,predict_model_id,classify_model_id,extract_model_id,orientation_model_id— nopredict_model_params, and nobbox_score_threshold.Net effect on the pipeline path:
sensitivity→ replaced bybbox_score_threshold, which falls back to its default of0.5(pipeline_router.py:32)nms_thresh/nms_aware→ dropped; NMS runs at whatever the server-sidemodel_config.jsonsets (LightNet defaults to0.4,lightnet_model.py:40,45)Path B —
/api/engine/detect/cnn/lightnet/(WBIA-compat shim, for upcoming migrations)WbiaDetectRequestdeclares both fields (app/routers/wbia_compat_router.py:140-141):nms_threshandnms_awareare then never read._run_detection()takes onlysensitivity(wbia_compat_router.py:199,234) and applies it as a post-hoc score filter. Accepting a parameter and silently ignoring it is worse than rejecting it — the caller gets no signal that its tuning was discarded.Polarity warning for whoever fixes this
Do not pass
IA.json'snms_threshstraight through. WBIA inverts it before use:So in
IA.jsona higher value means more suppression — the reverse of the usual convention, and the reverse of lightnet's ownNonMaxSupression(ious > nms_thresh). A correct port must mapeffective_iou = 1.0 - ia_json_nms_thresh.Note also that WBIA disables lightnet's internal NMS entirely (
lightnet.py:319,nms_thresh = 1.0) and does suppression itself at the depc layer, whereas ml-service relies on lightnet's internal pass.nms_aware: "ispart"(suppress within body-vs-part pools separately) is closely approximated by lightnet'sclass_nms=Truefor models whose part classes are distinct labels, but it is not identical forbyclassconfigs.Affected migrated models
predict_model_idsensitivity→ effectivenms_thresh(→ WBIA effective IoU)giraffe_v1nms_awareunsetggr2nms_awareunsetfins_v1_dorsalnms_aware: byclasswhaleshark_v0shark_v0leopard_shark_v0The Sharkbook entries are the most consequential: three of them were tuned to
sensitivity 0.65and now run at0.5, which should measurably increase low-confidence detections.fins_v1_dorsaladditionally losesnms_aware: byclass.(
sharkfin-v1andtigershark-v1set neither value inIA.json, so they are unaffected.)Suggested fix
/pipeline/— have Wildbook forward the tuned values inpredict_model_paramsandbbox_score_threshold; the server side already supports this, so this is mostly aMlServiceClient.buildPipelinePayload()change plus an agreed key mapping./api/engine/detect/cnn/lightnet/— either honornms_thresh/nms_aware, or return a 4xx/warning when they are supplied, so silent divergence is impossible.1.0 - xpolarity conversion at the boundary and document it in one place.Happy to send a PR for (2) if that's a useful starting point.