Skip to content

Commit a5a2f18

Browse files
[Bug-fix]Add support for max_detection_per_image hyperparam (#543)
* add support for max_detection_per_image hyperparam * format with black * update doc strings * changing default to -1 so that it bypasses by default and filter only if max_detections is less than no of outputs
1 parent 81a4152 commit a5a2f18

4 files changed

Lines changed: 30 additions & 5 deletions

File tree

app.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def browse_dataset_path():
2727
st.session_state.setdefault("config_option", "Manual Configuration")
2828
st.session_state.setdefault("confidence_threshold", 0.5)
2929
st.session_state.setdefault("nms_threshold", 0.5)
30-
st.session_state.setdefault("max_detections", 100)
30+
st.session_state.setdefault("max_detections", -1)
3131
st.session_state.setdefault("device", best_device)
3232
st.session_state.setdefault("batch_size", 1)
3333
st.session_state.setdefault("evaluation_step", 5)
@@ -122,7 +122,7 @@ def browse_dataset_path():
122122
)
123123
st.number_input(
124124
"Max Detections/Image",
125-
min_value=1,
125+
min_value=-1,
126126
max_value=1000,
127127
step=1,
128128
key="max_detections",
@@ -307,7 +307,7 @@ def browse_dataset_path():
307307
st.session_state.get("confidence_threshold", 0.5)
308308
)
309309
nms_threshold = float(st.session_state.get("nms_threshold", 0.5))
310-
max_detections = int(st.session_state.get("max_detections", 100))
310+
max_detections = int(st.session_state.get("max_detections", -1))
311311
device = st.session_state.get("device", "cpu")
312312
batch_size = int(st.session_state.get("batch_size", 1))
313313
evaluation_step = int(st.session_state.get("evaluation_step", 5))

perceptionmetrics/models/torch_detection.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,14 @@ def forward(self, x):
298298
# Load confidence and NMS thresholds from config
299299
self.confidence_threshold = self.model_cfg.get("confidence_threshold", 0.5)
300300
self.nms_threshold = self.model_cfg.get("nms_threshold", 0.3)
301+
self.max_detections_per_image = self.model_cfg.get(
302+
"max_detections_per_image", -1
303+
)
301304

302305
self.postprocess_args = [self.confidence_threshold]
303306
if self.model_format == "yolo":
304307
self.postprocess_args.append(self.nms_threshold)
308+
self.postprocess_args.append(self.max_detections_per_image)
305309

306310
# Add reverse mapping for idx to class_name
307311
self.idx_to_class_name = {v["idx"]: k for k, v in self.ontology.items()}

perceptionmetrics/models/utils/torchvision.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
def postprocess_detection(output: dict, confidence_threshold: float = 0.5):
1+
def postprocess_detection(
2+
output: dict, confidence_threshold: float = 0.5, max_detections: int = -1
3+
):
24
"""Post-process torchvision model output.
35
46
:param output: Dictionary with keys 'boxes', 'labels', and 'scores'.
57
:type output: dict
68
:param confidence_threshold: Confidence threshold to filter boxes.
79
:type confidence_threshold: float
10+
:param max_detections: Maximum number of best detections to keep per image after filtering.
11+
:type max_detections: int
812
:return: Dictionary with keys 'boxes', 'labels', and 'scores'.
913
:rtype: dict
1014
"""
@@ -15,4 +19,13 @@ def postprocess_detection(output: dict, confidence_threshold: float = 0.5):
1519
"labels": output["labels"][keep_mask],
1620
"scores": output["scores"][keep_mask],
1721
}
22+
23+
if max_detections < output["scores"].shape[0] and max_detections > 0:
24+
limited_idx = output["scores"].argsort(descending=True)[:max_detections]
25+
output = {
26+
"boxes": output["boxes"][limited_idx],
27+
"labels": output["labels"][limited_idx],
28+
"scores": output["scores"][limited_idx],
29+
}
30+
1831
return output

perceptionmetrics/models/utils/yolo.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import torch
22
from torchvision.ops import nms
33

4-
54
CLASS_NMS_OFFSET = 7680 # offset to apply to boxes for class-wise NMS
65

76

87
def postprocess_detection(
98
output: torch.Tensor,
109
confidence_threshold: float = 0.25,
1110
nms_threshold: float = 0.45,
11+
max_detections: int = -1,
1212
):
1313
"""Post-process YOLO model output.
1414
@@ -18,6 +18,8 @@ def postprocess_detection(
1818
:type confidence_threshold: float
1919
:param nms_threshold: IoU threshold for Non-Maximum Suppression (NMS). Some models may not perform NMS (e.g. YOLOv26).
2020
:type nms_threshold: float
21+
:param max_detections: Maximum number of best detections to keep per image after filtering.
22+
:type max_detections: int
2123
:return: Dictionary with keys 'boxes', 'labels', and 'scores'.
2224
:rtype: dict
2325
"""
@@ -57,4 +59,10 @@ def postprocess_detection(
5759
scores = scores[keep_idx]
5860
labels = labels[keep_idx]
5961

62+
if max_detections > 0 and max_detections < scores.shape[0]:
63+
limited_idx = scores.argsort(descending=True)[:max_detections]
64+
boxes_xyxy = boxes_xyxy[limited_idx]
65+
scores = scores[limited_idx]
66+
labels = labels[limited_idx]
67+
6068
return {"boxes": boxes_xyxy, "labels": labels, "scores": scores}

0 commit comments

Comments
 (0)