Spaces:
Sleeping
Sleeping
Added threshold slider for siglip models
Browse files- app.py +47 -7
- siglip2_onnx_zeroshot.py +2 -10
- siglip_zeroshot.py +2 -10
app.py
CHANGED
|
@@ -115,7 +115,9 @@ CLASSIFIER_MAP = {
|
|
| 115 |
}
|
| 116 |
|
| 117 |
|
| 118 |
-
def run_dfine_classify(image, refs_path, dfine_threshold, dfine_model_choice,
|
|
|
|
|
|
|
| 119 |
"""Tab 2: D-FINE first, then classify crops.
|
| 120 |
Returns (group_crop_gallery, known_crop_gallery, status_message).
|
| 121 |
"""
|
|
@@ -129,16 +131,27 @@ def run_dfine_classify(image, refs_path, dfine_threshold, dfine_model_choice, mi
|
|
| 129 |
|
| 130 |
dfine_model = "large" if dfine_model_choice.strip().lower() == "large" else "medium"
|
| 131 |
classifier = CLASSIFIER_MAP.get(classifier_choice, "jina")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
group_crops, known_crops, status = run_single_image(
|
| 133 |
image,
|
| 134 |
refs_dir=refs,
|
| 135 |
dfine_model=dfine_model,
|
| 136 |
det_threshold=float(dfine_threshold),
|
| 137 |
-
conf_threshold=
|
| 138 |
-
gap_threshold=
|
| 139 |
min_side=24,
|
| 140 |
crop_dedup_iou=0.4,
|
| 141 |
-
min_display_conf=
|
| 142 |
classifier=classifier,
|
| 143 |
)
|
| 144 |
|
|
@@ -298,12 +311,13 @@ with gr.Blocks(title="Small Object Detection") as app:
|
|
| 298 |
|
| 299 |
with gr.Column(scale=1):
|
| 300 |
|
|
|
|
| 301 |
threshold_slider = gr.Slider(
|
| 302 |
minimum=0.0,
|
| 303 |
maximum=1.0,
|
| 304 |
value=0.703,
|
| 305 |
step=0.005,
|
| 306 |
-
label="
|
| 307 |
)
|
| 308 |
|
| 309 |
gap_slider = gr.Slider(
|
|
@@ -311,7 +325,17 @@ with gr.Blocks(title="Small Object Detection") as app:
|
|
| 311 |
maximum=0.02,
|
| 312 |
value=0.005,
|
| 313 |
step=0.001,
|
| 314 |
-
label="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
)
|
| 316 |
|
| 317 |
out_gallery_dfine = gr.Gallery(
|
|
@@ -334,9 +358,25 @@ with gr.Blocks(title="Small Object Detection") as app:
|
|
| 334 |
interactive=False,
|
| 335 |
)
|
| 336 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
btn_dfine.click(
|
| 338 |
fn=run_dfine_classify,
|
| 339 |
-
inputs=[inp_dfine, refs_path, dfine_threshold_slider, dfine_model_radio,
|
|
|
|
| 340 |
outputs=[out_gallery_dfine, out_gallery_known, out_status_dfine],
|
| 341 |
concurrency_limit=1,
|
| 342 |
)
|
|
|
|
| 115 |
}
|
| 116 |
|
| 117 |
|
| 118 |
+
def run_dfine_classify(image, refs_path, dfine_threshold, dfine_model_choice,
|
| 119 |
+
min_display_conf, gap_threshold, siglip_threshold,
|
| 120 |
+
classifier_choice="Jina-CLIP-v2 (few-shot)"):
|
| 121 |
"""Tab 2: D-FINE first, then classify crops.
|
| 122 |
Returns (group_crop_gallery, known_crop_gallery, status_message).
|
| 123 |
"""
|
|
|
|
| 131 |
|
| 132 |
dfine_model = "large" if dfine_model_choice.strip().lower() == "large" else "medium"
|
| 133 |
classifier = CLASSIFIER_MAP.get(classifier_choice, "jina")
|
| 134 |
+
|
| 135 |
+
# SigLIP models: use their own threshold, no gap check
|
| 136 |
+
if classifier in ("siglip", "siglip2_onnx"):
|
| 137 |
+
conf_thresh = float(siglip_threshold)
|
| 138 |
+
gap_thresh = 0.0
|
| 139 |
+
display_conf = float(siglip_threshold)
|
| 140 |
+
else:
|
| 141 |
+
conf_thresh = 0.5
|
| 142 |
+
gap_thresh = float(gap_threshold)
|
| 143 |
+
display_conf = float(min_display_conf)
|
| 144 |
+
|
| 145 |
group_crops, known_crops, status = run_single_image(
|
| 146 |
image,
|
| 147 |
refs_dir=refs,
|
| 148 |
dfine_model=dfine_model,
|
| 149 |
det_threshold=float(dfine_threshold),
|
| 150 |
+
conf_threshold=conf_thresh,
|
| 151 |
+
gap_threshold=gap_thresh,
|
| 152 |
min_side=24,
|
| 153 |
crop_dedup_iou=0.4,
|
| 154 |
+
min_display_conf=display_conf,
|
| 155 |
classifier=classifier,
|
| 156 |
)
|
| 157 |
|
|
|
|
| 311 |
|
| 312 |
with gr.Column(scale=1):
|
| 313 |
|
| 314 |
+
# --- Jina thresholds (visible when Jina selected) ---
|
| 315 |
threshold_slider = gr.Slider(
|
| 316 |
minimum=0.0,
|
| 317 |
maximum=1.0,
|
| 318 |
value=0.703,
|
| 319 |
step=0.005,
|
| 320 |
+
label="Jina: min display confidence",
|
| 321 |
)
|
| 322 |
|
| 323 |
gap_slider = gr.Slider(
|
|
|
|
| 325 |
maximum=0.02,
|
| 326 |
value=0.005,
|
| 327 |
step=0.001,
|
| 328 |
+
label="Jina: gap (top class must beat runner-up by this much)",
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
# --- SigLIP threshold (visible when SigLIP selected) ---
|
| 332 |
+
siglip_threshold_slider = gr.Slider(
|
| 333 |
+
minimum=0.0,
|
| 334 |
+
maximum=1.0,
|
| 335 |
+
value=0.05,
|
| 336 |
+
step=0.01,
|
| 337 |
+
label="SigLIP: min confidence threshold",
|
| 338 |
+
visible=False,
|
| 339 |
)
|
| 340 |
|
| 341 |
out_gallery_dfine = gr.Gallery(
|
|
|
|
| 358 |
interactive=False,
|
| 359 |
)
|
| 360 |
|
| 361 |
+
# Show/hide threshold sliders based on classifier choice
|
| 362 |
+
def update_threshold_visibility(choice):
|
| 363 |
+
is_jina = (choice == "Jina-CLIP-v2 (few-shot)")
|
| 364 |
+
return (
|
| 365 |
+
gr.update(visible=is_jina), # threshold_slider
|
| 366 |
+
gr.update(visible=is_jina), # gap_slider
|
| 367 |
+
gr.update(visible=not is_jina), # siglip_threshold_slider
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
classifier_radio.change(
|
| 371 |
+
fn=update_threshold_visibility,
|
| 372 |
+
inputs=[classifier_radio],
|
| 373 |
+
outputs=[threshold_slider, gap_slider, siglip_threshold_slider],
|
| 374 |
+
)
|
| 375 |
+
|
| 376 |
btn_dfine.click(
|
| 377 |
fn=run_dfine_classify,
|
| 378 |
+
inputs=[inp_dfine, refs_path, dfine_threshold_slider, dfine_model_radio,
|
| 379 |
+
threshold_slider, gap_slider, siglip_threshold_slider, classifier_radio],
|
| 380 |
outputs=[out_gallery_dfine, out_gallery_known, out_status_dfine],
|
| 381 |
concurrency_limit=1,
|
| 382 |
)
|
siglip2_onnx_zeroshot.py
CHANGED
|
@@ -163,20 +163,12 @@ class SigLIP2ONNXClassifier:
|
|
| 163 |
conf = float(probs[best_idx])
|
| 164 |
gap = float(probs[best_idx] - probs[second_idx])
|
| 165 |
|
| 166 |
-
|
| 167 |
-
gap_ok = gap >= gap_threshold
|
| 168 |
-
|
| 169 |
-
if conf_ok and gap_ok:
|
| 170 |
prediction = self.labels[best_idx]
|
| 171 |
status = "accepted"
|
| 172 |
else:
|
| 173 |
prediction = "unknown"
|
| 174 |
-
|
| 175 |
-
if not conf_ok:
|
| 176 |
-
reasons.append(f"conf {conf:.4f} < {conf_threshold}")
|
| 177 |
-
if not gap_ok:
|
| 178 |
-
reasons.append(f"gap {gap:.4f} < {gap_threshold}")
|
| 179 |
-
status = "rejected: " + ", ".join(reasons)
|
| 180 |
|
| 181 |
return {
|
| 182 |
"prediction": prediction,
|
|
|
|
| 163 |
conf = float(probs[best_idx])
|
| 164 |
gap = float(probs[best_idx] - probs[second_idx])
|
| 165 |
|
| 166 |
+
if conf >= conf_threshold:
|
|
|
|
|
|
|
|
|
|
| 167 |
prediction = self.labels[best_idx]
|
| 168 |
status = "accepted"
|
| 169 |
else:
|
| 170 |
prediction = "unknown"
|
| 171 |
+
status = f"rejected: conf {conf:.4f} < {conf_threshold}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
return {
|
| 174 |
"prediction": prediction,
|
siglip_zeroshot.py
CHANGED
|
@@ -62,20 +62,12 @@ class SigLIPClassifier:
|
|
| 62 |
conf = float(probs[best_idx])
|
| 63 |
gap = float(probs[best_idx] - probs[second_idx])
|
| 64 |
|
| 65 |
-
|
| 66 |
-
gap_ok = gap >= gap_threshold
|
| 67 |
-
|
| 68 |
-
if conf_ok and gap_ok:
|
| 69 |
prediction = self.labels[best_idx]
|
| 70 |
status = "accepted"
|
| 71 |
else:
|
| 72 |
prediction = "unknown"
|
| 73 |
-
|
| 74 |
-
if not conf_ok:
|
| 75 |
-
reasons.append(f"conf {conf:.4f} < {conf_threshold}")
|
| 76 |
-
if not gap_ok:
|
| 77 |
-
reasons.append(f"gap {gap:.4f} < {gap_threshold}")
|
| 78 |
-
status = "rejected: " + ", ".join(reasons)
|
| 79 |
|
| 80 |
return {
|
| 81 |
"prediction": prediction,
|
|
|
|
| 62 |
conf = float(probs[best_idx])
|
| 63 |
gap = float(probs[best_idx] - probs[second_idx])
|
| 64 |
|
| 65 |
+
if conf >= conf_threshold:
|
|
|
|
|
|
|
|
|
|
| 66 |
prediction = self.labels[best_idx]
|
| 67 |
status = "accepted"
|
| 68 |
else:
|
| 69 |
prediction = "unknown"
|
| 70 |
+
status = f"rejected: conf {conf:.4f} < {conf_threshold}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
return {
|
| 73 |
"prediction": prediction,
|