Skip to content

Commit 522661e

Browse files
authored
Feature/issue 160 core nms remove ensemble boxes dependency (#161)
* Remove ensemble_boxes dependency * Remove package from setup
1 parent 28f0b24 commit 522661e

4 files changed

Lines changed: 50 additions & 18 deletions

File tree

pyodi/apps/crops/crops_merge.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ def crops_merge(
1919
output_file: str,
2020
predictions_file: Optional[str] = None,
2121
apply_nms: bool = True,
22-
nms_mode: str = "nms",
2322
score_thr: float = 0.0,
2423
iou_thr: float = 0.5,
2524
) -> str:
@@ -33,7 +32,6 @@ def crops_merge(
3332
If not None, the annotations of predictions_file will be merged instead of ground_truth_file's.
3433
apply_nms: Whether to apply Non Maximum Supression to the merged predictions of
3534
each image. Defaults to True.
36-
nms_mode: Non Maximum Supression mode. Defaults to "nms".
3735
score_thr: Predictions bellow `score_thr` will be filtered. Only used if
3836
`apply_nms`. Defaults to 0.0.
3937
iou_thr: None of the filtered predictions will have an iou above `iou_thr` to
@@ -76,12 +74,10 @@ def crops_merge(
7674
crop["original_image_shape"] = stem_to_original_shape[stem]
7775

7876
if apply_nms:
79-
annotations = nms_predictions(
80-
annotations, score_thr=score_thr, nms_mode=nms_mode, iou_thr=iou_thr
81-
)
77+
annotations = nms_predictions(annotations, score_thr=score_thr, iou_thr=iou_thr)
8278
output_file = str(
8379
Path(output_file).parent
84-
/ f"{Path(output_file).stem}_{nms_mode}_{score_thr}_{iou_thr}.json"
80+
/ f"{Path(output_file).stem}_{score_thr}_{iou_thr}.json"
8581
)
8682

8783
if predictions_file is not None:

pyodi/core/nms.py

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,61 @@
11
from collections import defaultdict
22
from typing import Any, Dict, List, Tuple
33

4-
import ensemble_boxes
54
import numpy as np
65
from loguru import logger
6+
from numba import jit
77

88
from pyodi.core.boxes import coco_to_corners, corners_to_coco, denormalize, normalize
99

1010

11+
@jit(nopython=True)
12+
def nms(dets: np.ndarray, scores: np.ndarray, iou_thr: float) -> np.ndarray:
13+
"""Non Maximum supression algorithm from https://github.com/ZFTurbo/Weighted-Boxes-Fusion/blob/master/ensemble_boxes/ensemble_boxes_nms.py.
14+
15+
Args:
16+
dets: Array of predictions in corner format.
17+
scores: Array of scores for each prediction.
18+
iou_thr: None of the filtered predictions will have an iou above `iou_thr`
19+
to any other.
20+
21+
Returns:
22+
List of filtered predictions in COCO format.
23+
24+
"""
25+
x1 = dets[:, 0]
26+
y1 = dets[:, 1]
27+
x2 = dets[:, 2]
28+
y2 = dets[:, 3]
29+
30+
areas = (x2 - x1) * (y2 - y1)
31+
order = scores.argsort()[::-1]
32+
33+
keep = []
34+
while order.size > 0:
35+
i = order[0]
36+
keep.append(i)
37+
xx1 = np.maximum(x1[i], x1[order[1:]])
38+
yy1 = np.maximum(y1[i], y1[order[1:]])
39+
xx2 = np.minimum(x2[i], x2[order[1:]])
40+
yy2 = np.minimum(y2[i], y2[order[1:]])
41+
42+
w = np.maximum(0.0, xx2 - xx1)
43+
h = np.maximum(0.0, yy2 - yy1)
44+
inter = w * h
45+
ovr = inter / (areas[i] + areas[order[1:]] - inter)
46+
inds = np.where(ovr <= iou_thr)[0]
47+
order = order[inds + 1]
48+
49+
return keep
50+
51+
1152
def nms_predictions(
12-
predictions: List[Dict[Any, Any]],
13-
nms_mode: str = "nms",
14-
score_thr: float = 0.0,
15-
iou_thr: float = 0.5,
53+
predictions: List[Dict[Any, Any]], score_thr: float = 0.0, iou_thr: float = 0.5,
1654
) -> List[Dict[Any, Any]]:
1755
"""Apply Non Maximum supression to all the images in a COCO `predictions` list.
1856
1957
Args:
2058
predictions: List of predictions in COCO format.
21-
nms_mode: Non Maximum Supression mode. Defaults to "nms".
2259
score_thr: Predictions below `score_thr` will be filtered. Defaults to 0.0.
2360
iou_thr: None of the filtered predictions will have an iou above `iou_thr`
2461
to any other. Defaults to 0.5.
@@ -27,7 +64,6 @@ def nms_predictions(
2764
List of filtered predictions in COCO format.
2865
2966
"""
30-
nms = getattr(ensemble_boxes, nms_mode)
3167
new_predictions = []
3268
image_id_to_all_boxes: Dict[str, List[List[float]]] = defaultdict(list)
3369
image_id_to_shape: Dict[str, Tuple[int, int]] = dict()
@@ -51,9 +87,10 @@ def nms_predictions(
5187
boxes = normalize(coco_to_corners(boxes), image_width, image_height)
5288

5389
logger.info(f"Before nms: {boxes.shape}")
54-
boxes, scores, categories = nms(
55-
[boxes], [scores], [categories], iou_thr=iou_thr
56-
)
90+
keep = nms(boxes, scores, iou_thr=iou_thr)
91+
# Filter out predictions
92+
boxes, categories, scores = boxes[keep], categories[keep], scores[keep]
93+
5794
logger.info(f"After nms: {boxes.shape}")
5895

5996
logger.info(f"Before score threshold: {boxes.shape}")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ exclude = '''
2222

2323
[tool.isort]
2424
profile = "black"
25-
known_third_party = ["PIL", "ensemble_boxes", "loguru", "matplotlib", "numba", "numpy", "pandas", "plotly", "pycocotools", "pytest", "setuptools", "sklearn", "typer"]
25+
known_third_party = ["PIL", "loguru", "matplotlib", "numba", "numpy", "pandas", "plotly", "pycocotools", "pytest", "setuptools", "sklearn", "typer"]

setup.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ packages = find_namespace:
1212
python_requires = >=3.7
1313
install_requires =
1414
numpy==1.20.1
15-
ensemble-boxes==1.0.4
1615
loguru==0.5.3
1716
matplotlib==3.3.4
1817
numba==0.52.0

0 commit comments

Comments
 (0)