Spaces:
Sleeping
Sleeping
gil.simas@sea.ai
commited on
Commit
·
930daa4
1
Parent(s):
6b2433c
metric works, not fully integrated
Browse files- __pycache__/box_metrics.cpython-39.pyc +0 -0
- __pycache__/utils.cpython-39.pyc +0 -0
- box_metrics.py +229 -0
- compute.py +88 -0
- test.py +26 -0
- utils.py +173 -0
__pycache__/box_metrics.cpython-39.pyc
ADDED
|
Binary file (5.9 kB). View file
|
|
|
__pycache__/utils.cpython-39.pyc
ADDED
|
Binary file (4.85 kB). View file
|
|
|
box_metrics.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import evaluate
|
| 2 |
+
import datasets
|
| 3 |
+
import motmetrics as mm
|
| 4 |
+
import numpy as np
|
| 5 |
+
from seametrics.payload import Payload
|
| 6 |
+
import torch
|
| 7 |
+
from utils import bbox_iou, bbox_bep
|
| 8 |
+
import datasets
|
| 9 |
+
|
| 10 |
+
# _DESCRIPTION = """\
|
| 11 |
+
# The box-metrics package provides a set of metrics to evaluate
|
| 12 |
+
# the performance of object detection algorithms in ther of sizing and positioning
|
| 13 |
+
# of the bounding boxes."""
|
| 14 |
+
|
| 15 |
+
# _KWARGS_DESCRIPTION = """
|
| 16 |
+
# Calculates how good are predictions given some references, using certain scores
|
| 17 |
+
# Args:
|
| 18 |
+
# predictions: list of predictions to score. Each predictions
|
| 19 |
+
# should be a string with tokens separated by spaces.
|
| 20 |
+
# references: list of reference for each prediction. Each
|
| 21 |
+
# reference should be a string with tokens separated by spaces.
|
| 22 |
+
# max_iou (`float`, *optional*):
|
| 23 |
+
# If specified, this is the minimum Intersection over Union (IoU) threshold to consider a detection as a true positive.
|
| 24 |
+
# Default is 0.5.
|
| 25 |
+
# """
|
| 26 |
+
|
| 27 |
+
# _CITATION = """\
|
| 28 |
+
# @InProceedings{huggingface:module,
|
| 29 |
+
# title = {A great new module},
|
| 30 |
+
# authors={huggingface, Inc.},
|
| 31 |
+
# year={2020}
|
| 32 |
+
# }\
|
| 33 |
+
# @article{milan2016mot16,
|
| 34 |
+
# title={Are object detection assessment criteria ready for maritime computer vision?},
|
| 35 |
+
# author={Dilip K. Prasad1, Deepu Rajan and Chai Quek},
|
| 36 |
+
# journal={arXiv:1809.04659v1},
|
| 37 |
+
# year={2018}
|
| 38 |
+
# }
|
| 39 |
+
# """
|
| 40 |
+
|
| 41 |
+
_CITATION = """\
|
| 42 |
+
@InProceedings{huggingface:module,
|
| 43 |
+
title = {A great new module},
|
| 44 |
+
authors={huggingface, Inc.},
|
| 45 |
+
year={2020}
|
| 46 |
+
}\
|
| 47 |
+
@article{milan2016mot16,
|
| 48 |
+
title={MOT16: A benchmark for multi-object tracking},
|
| 49 |
+
author={Milan, Anton and Leal-Taix{\'e}, Laura and Reid, Ian and Roth, Stefan and Schindler, Konrad},
|
| 50 |
+
journal={arXiv preprint arXiv:1603.00831},
|
| 51 |
+
year={2016}
|
| 52 |
+
}
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
_DESCRIPTION = """\
|
| 56 |
+
The MOT Metrics module is designed to evaluate multi-object tracking (MOT)
|
| 57 |
+
algorithms by computing various metrics based on predicted and ground truth bounding
|
| 58 |
+
boxes. It serves as a crucial tool in assessing the performance of MOT systems,
|
| 59 |
+
aiding in the iterative improvement of tracking algorithms."""
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
_KWARGS_DESCRIPTION = """
|
| 63 |
+
Calculates how good are predictions given some references, using certain scores
|
| 64 |
+
Args:
|
| 65 |
+
predictions: list of predictions to score. Each predictions
|
| 66 |
+
should be a string with tokens separated by spaces.
|
| 67 |
+
references: list of reference for each prediction. Each
|
| 68 |
+
reference should be a string with tokens separated by spaces.
|
| 69 |
+
max_iou (`float`, *optional*):
|
| 70 |
+
If specified, this is the minimum Intersection over Union (IoU) threshold to consider a detection as a true positive.
|
| 71 |
+
Default is 0.5.
|
| 72 |
+
"""
|
| 73 |
+
|
| 74 |
+
# @evaluate.utils.file_utils.add_start_docstrings(_DESCRIPTION, _KWARGS_DESCRIPTION)
|
| 75 |
+
class BoxMetrics(evaluate.Metric):
|
| 76 |
+
|
| 77 |
+
def __init__(self, max_iou: float = 0.01, **kwargs):
|
| 78 |
+
# super().__init__(**kwargs)
|
| 79 |
+
self.max_iou = max_iou
|
| 80 |
+
self.boxes = {}
|
| 81 |
+
self.gt_field = "ground_truth_det"
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _info(self):
|
| 85 |
+
# TODO: Specifies the evaluate.EvaluationModuleInfo object
|
| 86 |
+
return evaluate.MetricInfo(
|
| 87 |
+
# This is the description that will appear on the modules page.
|
| 88 |
+
module_type="metric",
|
| 89 |
+
description=_DESCRIPTION,
|
| 90 |
+
citation=_CITATION,
|
| 91 |
+
inputs_description=_KWARGS_DESCRIPTION,
|
| 92 |
+
# This defines the format of each prediction and reference
|
| 93 |
+
features=datasets.Features({
|
| 94 |
+
"predictions": datasets.Sequence(
|
| 95 |
+
datasets.Sequence(datasets.Value("float"))
|
| 96 |
+
),
|
| 97 |
+
"references": datasets.Sequence(
|
| 98 |
+
datasets.Sequence(datasets.Value("float"))
|
| 99 |
+
)
|
| 100 |
+
}),
|
| 101 |
+
# Additional links to the codebase or references
|
| 102 |
+
codebase_urls=["http://github.com/path/to/codebase/of/new_module"],
|
| 103 |
+
reference_urls=["http://path.to.reference.url/new_module"]
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def add_payload(self, payload: Payload):
|
| 108 |
+
"""Convert a payload to the format of the tracking metrics library"""
|
| 109 |
+
self.add(payload)
|
| 110 |
+
|
| 111 |
+
def add(self, payload: Payload):
|
| 112 |
+
self.gt_field = payload.gt_field_name
|
| 113 |
+
for sequence in payload.sequences:
|
| 114 |
+
self.boxes[sequence] = {}
|
| 115 |
+
target = payload.sequences[sequence][self.gt_field]
|
| 116 |
+
resolution = payload.sequences[sequence]["resolution"]
|
| 117 |
+
target_tm = self.payload_labels_to_tm(target, resolution)
|
| 118 |
+
self.boxes[sequence][self.gt_field] = target_tm
|
| 119 |
+
|
| 120 |
+
for model in payload.models:
|
| 121 |
+
preds = payload.sequences[sequence][model]
|
| 122 |
+
preds_tm = self.payload_preds_to_rm(preds, resolution)
|
| 123 |
+
self.boxes[sequence][model] = preds_tm
|
| 124 |
+
|
| 125 |
+
def compute(self):
|
| 126 |
+
"""Compute the metric value"""
|
| 127 |
+
|
| 128 |
+
output = {}
|
| 129 |
+
|
| 130 |
+
for sequence in self.boxes:
|
| 131 |
+
ious = []
|
| 132 |
+
beps = []
|
| 133 |
+
bottom_x = []
|
| 134 |
+
bottom_y = []
|
| 135 |
+
widths = []
|
| 136 |
+
heights = []
|
| 137 |
+
output[sequence] = {}
|
| 138 |
+
|
| 139 |
+
target = self.boxes[sequence][self.gt_field]
|
| 140 |
+
for model in self.boxes[sequence]:
|
| 141 |
+
preds = self.boxes[sequence][model]
|
| 142 |
+
|
| 143 |
+
for i in range(len(preds)):
|
| 144 |
+
|
| 145 |
+
target_tm_bbs = target[i][:, 1:]
|
| 146 |
+
pred_tm_bbs = preds[i][:, :4]
|
| 147 |
+
|
| 148 |
+
if target_tm_bbs.shape[0] == 0 or pred_tm_bbs.shape[0] == 0:
|
| 149 |
+
continue
|
| 150 |
+
|
| 151 |
+
for t_box in target_tm_bbs:
|
| 152 |
+
iou = bbox_iou(t_box.unsqueeze(0), pred_tm_bbs, xywh=False)
|
| 153 |
+
bep = bbox_bep(t_box.unsqueeze(0), pred_tm_bbs, xywh=False)
|
| 154 |
+
matches = pred_tm_bbs[iou.squeeze(1) > self.max_iou]
|
| 155 |
+
|
| 156 |
+
bep = bep[iou>self.max_iou]
|
| 157 |
+
iou = iou[iou>self.max_iou]
|
| 158 |
+
|
| 159 |
+
if torch.any(iou <= 0):
|
| 160 |
+
raise ValueError("IoU should be greater than 0, pls contact code maintainer")
|
| 161 |
+
if torch.any(bep <= 0):
|
| 162 |
+
raise ValueError("BEP should be greater than 0, pls contact code maintainer")
|
| 163 |
+
|
| 164 |
+
ious.extend(iou.tolist())
|
| 165 |
+
beps.extend(bep.tolist())
|
| 166 |
+
|
| 167 |
+
for match in matches:
|
| 168 |
+
t_xc = (match[0].item()+match[2].item())/2
|
| 169 |
+
p_xc = (t_box[0].item()+t_box[2].item())/2
|
| 170 |
+
t_w = t_box[2].item()-t_box[0].item()
|
| 171 |
+
p_w = match[2].item()-match[0].item()
|
| 172 |
+
t_h = t_box[3].item()-t_box[1].item()
|
| 173 |
+
p_h = match[3].item()-match[1].item()
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
bottom_x.append(abs(t_xc-p_xc))
|
| 177 |
+
widths.append(abs(t_w-p_w))
|
| 178 |
+
bottom_y.append(abs(t_box[1].item()-match[1].item()))
|
| 179 |
+
heights.append(abs(t_h-p_h))
|
| 180 |
+
|
| 181 |
+
output[sequence][model] = {
|
| 182 |
+
"iou_mean": np.mean(ious),
|
| 183 |
+
"bep_mean": np.mean(beps),
|
| 184 |
+
"bottom_x_mean": np.mean(bottom_x),
|
| 185 |
+
"bottom_y_mean": np.mean(bottom_y),
|
| 186 |
+
"width_mean": np.mean(widths),
|
| 187 |
+
"height_mean": np.mean(heights),
|
| 188 |
+
"bottom_x_std": np.std(bottom_x),
|
| 189 |
+
"bottom_y_std": np.std(bottom_y),
|
| 190 |
+
"width_std": np.std(widths),
|
| 191 |
+
"height_std": np.std(heights)
|
| 192 |
+
}
|
| 193 |
+
return output
|
| 194 |
+
|
| 195 |
+
@staticmethod
|
| 196 |
+
def payload_labels_to_tm(labels, resolution):
|
| 197 |
+
"""Convert the labels of a payload sequence to the format of torch metrics"""
|
| 198 |
+
target_tm = []
|
| 199 |
+
for frame in labels:
|
| 200 |
+
target_tm_frame = []
|
| 201 |
+
for det in frame:
|
| 202 |
+
label = 0
|
| 203 |
+
box = det["bounding_box"]
|
| 204 |
+
x1, y1, x2, y2 = box[0], box[1], box[0]+box[2], box[1]+box[3]
|
| 205 |
+
x1, y1, x2, y2 = x1*resolution.width, y1*resolution.height, x2*resolution.width, y2*resolution.height
|
| 206 |
+
target_tm_frame.append([label, x1, y1, x2, y2])
|
| 207 |
+
target_tm.append(torch.tensor(target_tm_frame) if len(target_tm_frame) > 0 else torch.empty((0, 5)))
|
| 208 |
+
|
| 209 |
+
return target_tm
|
| 210 |
+
|
| 211 |
+
@staticmethod
|
| 212 |
+
def payload_preds_to_rm(preds, resolution):
|
| 213 |
+
"""Convert the predictions of a payload sequence to the format of torch metrics"""
|
| 214 |
+
preds_tm = []
|
| 215 |
+
for frame in preds:
|
| 216 |
+
pred_tm_frame = []
|
| 217 |
+
for det in frame:
|
| 218 |
+
label = 0
|
| 219 |
+
box = det["bounding_box"]
|
| 220 |
+
x1, y1, x2, y2 = box[0], box[1], box[0]+box[2], box[1]+box[3]
|
| 221 |
+
x1, y1, x2, y2 = x1*resolution.width, y1*resolution.height, x2*resolution.width, y2*resolution.height
|
| 222 |
+
conf = 1
|
| 223 |
+
pred_tm_frame.append([x1, y1, x2, y2, conf, label])
|
| 224 |
+
preds_tm.append(torch.tensor(pred_tm_frame) if len(pred_tm_frame) > 0 else torch.empty((0, 6)))
|
| 225 |
+
|
| 226 |
+
return preds_tm
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
|
compute.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
from utils import BoxMetrics, concat_labels, concat_preds
|
| 4 |
+
import fiftyone as fo
|
| 5 |
+
from seametrics.fo_utils.utils import fo_to_payload
|
| 6 |
+
from const import INDEX_MAPPING, CLASS_MAPPING, INDEX_MAPPING_INV
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
|
| 9 |
+
tags = ["WHALES"]
|
| 10 |
+
cameras = ["thermal_narrow"]
|
| 11 |
+
|
| 12 |
+
dataset_name = "SENTRY_VIDEOS_DATASET_QA"
|
| 13 |
+
#dataset_name = "SENTRY_VIDEOS_DATASET_QA"
|
| 14 |
+
model = "cerulean-level-17_11_2023_RL_SPLIT_ep147_CNN"
|
| 15 |
+
det_gt_field = "ground_truth_det"
|
| 16 |
+
|
| 17 |
+
cm = BoxMetrics(nc=10, conf=0, iou_thres=0)
|
| 18 |
+
|
| 19 |
+
if dataset_name == "SAILING_DATASET_QA":
|
| 20 |
+
cameras = ["thermal_left"]
|
| 21 |
+
dataset_view = fo.load_dataset(dataset_name).match_tags(tags).select_group_slices(cameras).filter_labels(f"{model}", True, only_matches=False)
|
| 22 |
+
sequences = dataset_view.distinct("sequence")
|
| 23 |
+
if dataset_name == "SENTRY_VIDEOS_DATASET_QA":
|
| 24 |
+
cameras = ["thermal_wide"]
|
| 25 |
+
dataset_view = fo.load_dataset(dataset_name).match_tags(tags).select_group_slices(cameras).filter_labels(f"frames.{model}", True, only_matches=False)
|
| 26 |
+
sequences = dataset_view.distinct("sequence")
|
| 27 |
+
|
| 28 |
+
for sequence in tqdm(sequences):
|
| 29 |
+
payload = fo_to_payload(dataset = dataset_name,
|
| 30 |
+
gt_field = det_gt_field,
|
| 31 |
+
models = [model],
|
| 32 |
+
tracking_mode = True,
|
| 33 |
+
sequence_list = [sequence],
|
| 34 |
+
excluded_classes = ["BIRD"],)
|
| 35 |
+
|
| 36 |
+
target = payload["sequences"][sequence][det_gt_field]
|
| 37 |
+
preds = payload["sequences"][sequence][model]
|
| 38 |
+
resolution = payload["sequences"][sequence]["resolution"]
|
| 39 |
+
target_tm = []
|
| 40 |
+
preds_tm = []
|
| 41 |
+
for frame in target:
|
| 42 |
+
target_tm_batch = []
|
| 43 |
+
for det in frame:
|
| 44 |
+
if CLASS_MAPPING[det["label"]] is not None:
|
| 45 |
+
label = INDEX_MAPPING[CLASS_MAPPING[det["label"]]]-1
|
| 46 |
+
else:
|
| 47 |
+
continue
|
| 48 |
+
box = det["bounding_box"]
|
| 49 |
+
x1, y1, x2, y2 = box[0], box[1], box[0]+box[2], box[1]+box[3]
|
| 50 |
+
x1, y1, x2, y2 = x1*resolution[1], y1*resolution[0], x2*resolution[1], y2*resolution[0]
|
| 51 |
+
target_tm_batch.append([label, x1, y1, x2, y2])
|
| 52 |
+
target_tm.append(torch.tensor(target_tm_batch) if len(target_tm_batch) > 0 else torch.empty((0, 5)))
|
| 53 |
+
|
| 54 |
+
for frame in preds:
|
| 55 |
+
pred_tm_batch = []
|
| 56 |
+
for det in frame:
|
| 57 |
+
label = INDEX_MAPPING[det["label"]]-1
|
| 58 |
+
box = det["bounding_box"]
|
| 59 |
+
x1, y1, x2, y2 = box[0], box[1], box[0]+box[2], box[1]+box[3]
|
| 60 |
+
x1, y1, x2, y2 = x1*resolution[1], y1*resolution[0], x2*resolution[1], y2*resolution[0]
|
| 61 |
+
conf = 1
|
| 62 |
+
pred_tm_batch.append([x1, y1, x2, y2, conf, label])
|
| 63 |
+
|
| 64 |
+
preds_tm.append(torch.tensor(pred_tm_batch) if len(pred_tm_batch) > 0 else torch.empty((0, 6)))
|
| 65 |
+
|
| 66 |
+
for i in range(len(target_tm)):
|
| 67 |
+
target_batch = target_tm[i]
|
| 68 |
+
pred_batch = preds_tm[i]
|
| 69 |
+
cm.process_batch(pred_batch, target_batch)
|
| 70 |
+
|
| 71 |
+
print("SUMMARY: ")
|
| 72 |
+
print("\nmodel: ", model)
|
| 73 |
+
print("\nconfusion matrix: ")
|
| 74 |
+
print(cm.matrix.astype(int))
|
| 75 |
+
|
| 76 |
+
tp = cm.matrix[:-1, :-1].sum()
|
| 77 |
+
fp = cm.matrix[:-1, -1].sum()
|
| 78 |
+
fn = cm.matrix[-1, :-1].sum()
|
| 79 |
+
print("\nTP: ", tp, "FP: ", fp, "FN: ", fn, "support: ", tp + fn)
|
| 80 |
+
#Detection Rates:
|
| 81 |
+
print("\nDetection Rates:")
|
| 82 |
+
for i in range(10):
|
| 83 |
+
tp = cm.matrix[:-1, i].sum()
|
| 84 |
+
fn = cm.matrix[-1, i].sum()
|
| 85 |
+
if tp + fn == 0:
|
| 86 |
+
print(f"{INDEX_MAPPING_INV[i+1]}: NaN")
|
| 87 |
+
else:
|
| 88 |
+
print(f"{INDEX_MAPPING_INV[i+1]}: {tp/(tp+fn)}")
|
test.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
import fiftyone as fo
|
| 4 |
+
from box_metrics import BoxMetrics
|
| 5 |
+
from seametrics.fo_utils.utils import fo_to_payload
|
| 6 |
+
from tqdm import tqdm
|
| 7 |
+
|
| 8 |
+
tags = ["WHALES"]
|
| 9 |
+
dataset_name = "SENTRY_VIDEOS_DATASET_QA"
|
| 10 |
+
model = "cerulean-level-17_11_2023_RL_SPLIT_ep147_CNN"
|
| 11 |
+
det_gt_field = "ground_truth_det"
|
| 12 |
+
|
| 13 |
+
dataset = fo.load_dataset(dataset_name)
|
| 14 |
+
dataset_view = fo.load_dataset(dataset_name).match_tags(tags) if tags else fo.load_dataset(dataset_name)
|
| 15 |
+
sequences = dataset_view.distinct("sequence")
|
| 16 |
+
|
| 17 |
+
bbox_metric = BoxMetrics(max_iou=0.01)
|
| 18 |
+
payload = fo_to_payload(dataset = dataset_name,
|
| 19 |
+
gt_field = det_gt_field,
|
| 20 |
+
models = [model],
|
| 21 |
+
tracking_mode = True,
|
| 22 |
+
sequence_list = sequences)
|
| 23 |
+
print(payload)
|
| 24 |
+
bbox_metric.add_payload(payload)
|
| 25 |
+
result = bbox_metric.compute()
|
| 26 |
+
print(result)
|
utils.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
import math
|
| 4 |
+
|
| 5 |
+
def bbox_bep(box1, box2, xywh=True, eps=1e-7, bep1 = True):
|
| 6 |
+
"""
|
| 7 |
+
Calculates bottom edge proximity between two boxes
|
| 8 |
+
|
| 9 |
+
Input shapes are box1(1,4) to box2(n,4)
|
| 10 |
+
|
| 11 |
+
Implementation of bep2 from
|
| 12 |
+
Are object detection assessment criteria ready for maritime computer vision?
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
# Get the coordinates of bounding boxes
|
| 16 |
+
if xywh: # transform from xywh to xyxy
|
| 17 |
+
(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
|
| 18 |
+
w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
|
| 19 |
+
b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
|
| 20 |
+
b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
|
| 21 |
+
else: # x1, y1, x2, y2 = box1
|
| 22 |
+
b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
|
| 23 |
+
b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
|
| 24 |
+
w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)
|
| 25 |
+
w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)
|
| 26 |
+
|
| 27 |
+
# Bottom edge distance (absolute value)
|
| 28 |
+
# xb = torch.abs(b2_x2 - b1_x1)
|
| 29 |
+
xb = torch.min(b2_x2-b1_x1, b1_x2-b2_x1)
|
| 30 |
+
xa = w2 - xb
|
| 31 |
+
xc = w1 - xb
|
| 32 |
+
ybe = torch.abs(b2_y2 - b1_y2)
|
| 33 |
+
|
| 34 |
+
X2 = xb/(xb+xa)
|
| 35 |
+
Y2 = 1-ybe/h2
|
| 36 |
+
|
| 37 |
+
X1 = xb/(xb+xa+xc+eps)
|
| 38 |
+
Y1 = 1-ybe/(torch.max(h2,h1)+eps)
|
| 39 |
+
|
| 40 |
+
bep = X1*Y1 if bep1 else X2*Y2
|
| 41 |
+
|
| 42 |
+
return bep
|
| 43 |
+
|
| 44 |
+
def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
|
| 45 |
+
"""
|
| 46 |
+
Calculates IoU, GIoU, DIoU, or CIoU between two boxes, supporting xywh/xyxy formats.
|
| 47 |
+
|
| 48 |
+
Input shapes are box1(1,4) to box2(n,4).
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
# Get the coordinates of bounding boxes
|
| 52 |
+
if xywh: # transform from xywh to xyxy
|
| 53 |
+
(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
|
| 54 |
+
w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
|
| 55 |
+
b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
|
| 56 |
+
b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
|
| 57 |
+
else: # x1, y1, x2, y2 = box1
|
| 58 |
+
b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
|
| 59 |
+
b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
|
| 60 |
+
w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)
|
| 61 |
+
w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)
|
| 62 |
+
|
| 63 |
+
# Intersection area
|
| 64 |
+
inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * (
|
| 65 |
+
b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)
|
| 66 |
+
).clamp(0)
|
| 67 |
+
|
| 68 |
+
# Union Area
|
| 69 |
+
union = w1 * h1 + w2 * h2 - inter + eps
|
| 70 |
+
|
| 71 |
+
# IoU
|
| 72 |
+
iou = inter / union
|
| 73 |
+
if CIoU or DIoU or GIoU:
|
| 74 |
+
cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width
|
| 75 |
+
ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height
|
| 76 |
+
if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
|
| 77 |
+
c2 = cw**2 + ch**2 + eps # convex diagonal squared
|
| 78 |
+
rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center dist ** 2
|
| 79 |
+
if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
|
| 80 |
+
v = (4 / math.pi**2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)
|
| 81 |
+
with torch.no_grad():
|
| 82 |
+
alpha = v / (v - iou + (1 + eps))
|
| 83 |
+
return iou - (rho2 / c2 + v * alpha) # CIoU
|
| 84 |
+
return iou - rho2 / c2 # DIoU
|
| 85 |
+
c_area = cw * ch + eps # convex area
|
| 86 |
+
return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf
|
| 87 |
+
return iou # IoU
|
| 88 |
+
|
| 89 |
+
class BoxMetrics:
|
| 90 |
+
# Updated version of https://github.com/kaanakan/object_detection_confusion_matrix
|
| 91 |
+
def __init__(self):
|
| 92 |
+
self.preds_tm = []
|
| 93 |
+
self.target_tm = []
|
| 94 |
+
self.bottom_x = []
|
| 95 |
+
self.bottom_y = []
|
| 96 |
+
self.widths = []
|
| 97 |
+
self.heights = []
|
| 98 |
+
self.ious = []
|
| 99 |
+
self.beps = []
|
| 100 |
+
|
| 101 |
+
def add_batch(self, preds, target):
|
| 102 |
+
"""
|
| 103 |
+
Return intersection-over-union (Jaccard index) of boxes.
|
| 104 |
+
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
|
| 105 |
+
Arguments:
|
| 106 |
+
detections torch(Array[N, 6]), x1, y1, x2, y2, conf, class
|
| 107 |
+
labels torch(Array[M, 5]), class, x1, y1, x2, y2
|
| 108 |
+
Returns:
|
| 109 |
+
None, updates confusion matrix accordingly
|
| 110 |
+
"""
|
| 111 |
+
self.preds_tm.extend(preds)
|
| 112 |
+
self.target_tm.extend(target)
|
| 113 |
+
|
| 114 |
+
def compute(self):
|
| 115 |
+
"""
|
| 116 |
+
Computes bbox iou, bep and location/size statistics
|
| 117 |
+
"""
|
| 118 |
+
|
| 119 |
+
for i in range(len(self.target_tm)):
|
| 120 |
+
target_batch_boxes = self.target_tm[i][:, 1:]
|
| 121 |
+
pred_batch_boxes = self.preds_tm[i][:, :4]
|
| 122 |
+
|
| 123 |
+
if pred_batch_boxes.shape[0] == 0:
|
| 124 |
+
continue
|
| 125 |
+
|
| 126 |
+
if target_batch_boxes.shape[0] == 0:
|
| 127 |
+
continue
|
| 128 |
+
|
| 129 |
+
for t_box in target_batch_boxes:
|
| 130 |
+
iou = bbox_iou(t_box.unsqueeze(0), pred_batch_boxes, xywh=False)
|
| 131 |
+
bep = bbox_bep(t_box.unsqueeze(0), pred_batch_boxes, xywh=False)
|
| 132 |
+
|
| 133 |
+
matches = pred_batch_boxes[iou.squeeze(1) > 0.1]
|
| 134 |
+
|
| 135 |
+
bep = bep[iou > 0]
|
| 136 |
+
iou = iou[iou > 0]
|
| 137 |
+
# if any iou value is 0 or less, raise error
|
| 138 |
+
if torch.any(iou <= 0):
|
| 139 |
+
raise ValueError("IoU values must be greater than 0.")
|
| 140 |
+
#same for bep
|
| 141 |
+
if torch.any(bep <= 0):
|
| 142 |
+
print(t_box.unsqueeze(0))
|
| 143 |
+
print(pred_batch_boxes)
|
| 144 |
+
print(bep)
|
| 145 |
+
print(iou)
|
| 146 |
+
raise ValueError("BEP values must be greater than 0.")
|
| 147 |
+
|
| 148 |
+
self.ious.extend(iou.tolist())
|
| 149 |
+
self.beps.extend(bep.tolist())
|
| 150 |
+
|
| 151 |
+
for match in matches:
|
| 152 |
+
t_xc = (match[0].item()+match[2].item())/2
|
| 153 |
+
p_xc = (t_box[0].item()+t_box[2].item())/2
|
| 154 |
+
t_w = t_box[2].item()-t_box[0].item()
|
| 155 |
+
p_w = match[2].item()-match[0].item()
|
| 156 |
+
t_h = t_box[3].item()-t_box[1].item()
|
| 157 |
+
p_h = match[3].item()-match[1].item()
|
| 158 |
+
|
| 159 |
+
self.bottom_x.append(p_xc - t_xc)
|
| 160 |
+
self.bottom_y.append(match[3].item()-t_box[3].item())
|
| 161 |
+
self.widths.append(p_w-t_w)
|
| 162 |
+
self.heights.append(p_h-t_h)
|
| 163 |
+
|
| 164 |
+
return {"iou_mean": np.mean(self.ious),
|
| 165 |
+
"bep_mean": np.mean(self.beps),
|
| 166 |
+
"bottom_x_std": np.std(self.bottom_x),
|
| 167 |
+
"bottom_y_std": np.std(self.bottom_y),
|
| 168 |
+
"widths_std": np.std(self.widths),
|
| 169 |
+
"heights_std": np.std(self.heights),
|
| 170 |
+
"bottom_x_mean": np.mean(self.bottom_x),
|
| 171 |
+
"bottom_y_mean": np.mean(self.bottom_y),
|
| 172 |
+
"widths_mean": np.mean(self.widths),
|
| 173 |
+
"heights_mean": np.mean(self.heights)}
|