dgenerate-ultralytics-headless 8.3.137__py3-none-any.whl → 8.3.224__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/METADATA +41 -34
- dgenerate_ultralytics_headless-8.3.224.dist-info/RECORD +285 -0
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/WHEEL +1 -1
- tests/__init__.py +7 -6
- tests/conftest.py +15 -39
- tests/test_cli.py +17 -17
- tests/test_cuda.py +17 -8
- tests/test_engine.py +36 -10
- tests/test_exports.py +98 -37
- tests/test_integrations.py +12 -15
- tests/test_python.py +126 -82
- tests/test_solutions.py +319 -135
- ultralytics/__init__.py +27 -9
- ultralytics/cfg/__init__.py +83 -87
- ultralytics/cfg/datasets/Argoverse.yaml +4 -4
- ultralytics/cfg/datasets/DOTAv1.5.yaml +2 -2
- ultralytics/cfg/datasets/DOTAv1.yaml +2 -2
- ultralytics/cfg/datasets/GlobalWheat2020.yaml +2 -2
- ultralytics/cfg/datasets/HomeObjects-3K.yaml +4 -5
- ultralytics/cfg/datasets/ImageNet.yaml +3 -3
- ultralytics/cfg/datasets/Objects365.yaml +24 -20
- ultralytics/cfg/datasets/SKU-110K.yaml +9 -9
- ultralytics/cfg/datasets/VOC.yaml +10 -13
- ultralytics/cfg/datasets/VisDrone.yaml +43 -33
- ultralytics/cfg/datasets/african-wildlife.yaml +5 -5
- ultralytics/cfg/datasets/brain-tumor.yaml +4 -5
- ultralytics/cfg/datasets/carparts-seg.yaml +5 -5
- ultralytics/cfg/datasets/coco-pose.yaml +26 -4
- ultralytics/cfg/datasets/coco.yaml +4 -4
- ultralytics/cfg/datasets/coco128-seg.yaml +2 -2
- ultralytics/cfg/datasets/coco128.yaml +2 -2
- ultralytics/cfg/datasets/coco8-grayscale.yaml +103 -0
- ultralytics/cfg/datasets/coco8-multispectral.yaml +2 -2
- ultralytics/cfg/datasets/coco8-pose.yaml +23 -2
- ultralytics/cfg/datasets/coco8-seg.yaml +2 -2
- ultralytics/cfg/datasets/coco8.yaml +2 -2
- ultralytics/cfg/datasets/construction-ppe.yaml +32 -0
- ultralytics/cfg/datasets/crack-seg.yaml +5 -5
- ultralytics/cfg/datasets/dog-pose.yaml +32 -4
- ultralytics/cfg/datasets/dota8-multispectral.yaml +2 -2
- ultralytics/cfg/datasets/dota8.yaml +2 -2
- ultralytics/cfg/datasets/hand-keypoints.yaml +29 -4
- ultralytics/cfg/datasets/lvis.yaml +9 -9
- ultralytics/cfg/datasets/medical-pills.yaml +4 -5
- ultralytics/cfg/datasets/open-images-v7.yaml +7 -10
- ultralytics/cfg/datasets/package-seg.yaml +5 -5
- ultralytics/cfg/datasets/signature.yaml +4 -4
- ultralytics/cfg/datasets/tiger-pose.yaml +20 -4
- ultralytics/cfg/datasets/xView.yaml +5 -5
- ultralytics/cfg/default.yaml +96 -93
- ultralytics/cfg/trackers/botsort.yaml +16 -17
- ultralytics/cfg/trackers/bytetrack.yaml +9 -11
- ultralytics/data/__init__.py +4 -4
- ultralytics/data/annotator.py +12 -12
- ultralytics/data/augment.py +531 -564
- ultralytics/data/base.py +76 -81
- ultralytics/data/build.py +206 -42
- ultralytics/data/converter.py +179 -78
- ultralytics/data/dataset.py +121 -121
- ultralytics/data/loaders.py +114 -91
- ultralytics/data/split.py +28 -15
- ultralytics/data/split_dota.py +67 -48
- ultralytics/data/utils.py +110 -89
- ultralytics/engine/exporter.py +422 -460
- ultralytics/engine/model.py +224 -252
- ultralytics/engine/predictor.py +94 -89
- ultralytics/engine/results.py +345 -595
- ultralytics/engine/trainer.py +231 -134
- ultralytics/engine/tuner.py +279 -73
- ultralytics/engine/validator.py +53 -46
- ultralytics/hub/__init__.py +26 -28
- ultralytics/hub/auth.py +30 -16
- ultralytics/hub/google/__init__.py +34 -36
- ultralytics/hub/session.py +53 -77
- ultralytics/hub/utils.py +23 -109
- ultralytics/models/__init__.py +1 -1
- ultralytics/models/fastsam/__init__.py +1 -1
- ultralytics/models/fastsam/model.py +36 -18
- ultralytics/models/fastsam/predict.py +33 -44
- ultralytics/models/fastsam/utils.py +4 -5
- ultralytics/models/fastsam/val.py +12 -14
- ultralytics/models/nas/__init__.py +1 -1
- ultralytics/models/nas/model.py +16 -20
- ultralytics/models/nas/predict.py +12 -14
- ultralytics/models/nas/val.py +4 -5
- ultralytics/models/rtdetr/__init__.py +1 -1
- ultralytics/models/rtdetr/model.py +9 -9
- ultralytics/models/rtdetr/predict.py +22 -17
- ultralytics/models/rtdetr/train.py +20 -16
- ultralytics/models/rtdetr/val.py +79 -59
- ultralytics/models/sam/__init__.py +8 -2
- ultralytics/models/sam/amg.py +53 -38
- ultralytics/models/sam/build.py +29 -31
- ultralytics/models/sam/model.py +33 -38
- ultralytics/models/sam/modules/blocks.py +159 -182
- ultralytics/models/sam/modules/decoders.py +38 -47
- ultralytics/models/sam/modules/encoders.py +114 -133
- ultralytics/models/sam/modules/memory_attention.py +38 -31
- ultralytics/models/sam/modules/sam.py +114 -93
- ultralytics/models/sam/modules/tiny_encoder.py +268 -291
- ultralytics/models/sam/modules/transformer.py +59 -66
- ultralytics/models/sam/modules/utils.py +55 -72
- ultralytics/models/sam/predict.py +745 -341
- ultralytics/models/utils/loss.py +118 -107
- ultralytics/models/utils/ops.py +118 -71
- ultralytics/models/yolo/__init__.py +1 -1
- ultralytics/models/yolo/classify/predict.py +28 -26
- ultralytics/models/yolo/classify/train.py +50 -81
- ultralytics/models/yolo/classify/val.py +68 -61
- ultralytics/models/yolo/detect/predict.py +12 -15
- ultralytics/models/yolo/detect/train.py +56 -46
- ultralytics/models/yolo/detect/val.py +279 -223
- ultralytics/models/yolo/model.py +167 -86
- ultralytics/models/yolo/obb/predict.py +7 -11
- ultralytics/models/yolo/obb/train.py +23 -25
- ultralytics/models/yolo/obb/val.py +107 -99
- ultralytics/models/yolo/pose/__init__.py +1 -1
- ultralytics/models/yolo/pose/predict.py +12 -14
- ultralytics/models/yolo/pose/train.py +31 -69
- ultralytics/models/yolo/pose/val.py +119 -254
- ultralytics/models/yolo/segment/predict.py +21 -25
- ultralytics/models/yolo/segment/train.py +12 -66
- ultralytics/models/yolo/segment/val.py +126 -305
- ultralytics/models/yolo/world/train.py +53 -45
- ultralytics/models/yolo/world/train_world.py +51 -32
- ultralytics/models/yolo/yoloe/__init__.py +7 -7
- ultralytics/models/yolo/yoloe/predict.py +30 -37
- ultralytics/models/yolo/yoloe/train.py +89 -71
- ultralytics/models/yolo/yoloe/train_seg.py +15 -17
- ultralytics/models/yolo/yoloe/val.py +56 -41
- ultralytics/nn/__init__.py +9 -11
- ultralytics/nn/autobackend.py +179 -107
- ultralytics/nn/modules/__init__.py +67 -67
- ultralytics/nn/modules/activation.py +8 -7
- ultralytics/nn/modules/block.py +302 -323
- ultralytics/nn/modules/conv.py +61 -104
- ultralytics/nn/modules/head.py +488 -186
- ultralytics/nn/modules/transformer.py +183 -123
- ultralytics/nn/modules/utils.py +15 -20
- ultralytics/nn/tasks.py +327 -203
- ultralytics/nn/text_model.py +81 -65
- ultralytics/py.typed +1 -0
- ultralytics/solutions/__init__.py +12 -12
- ultralytics/solutions/ai_gym.py +19 -27
- ultralytics/solutions/analytics.py +36 -26
- ultralytics/solutions/config.py +29 -28
- ultralytics/solutions/distance_calculation.py +23 -24
- ultralytics/solutions/heatmap.py +17 -19
- ultralytics/solutions/instance_segmentation.py +21 -19
- ultralytics/solutions/object_blurrer.py +16 -17
- ultralytics/solutions/object_counter.py +48 -53
- ultralytics/solutions/object_cropper.py +22 -16
- ultralytics/solutions/parking_management.py +61 -58
- ultralytics/solutions/queue_management.py +19 -19
- ultralytics/solutions/region_counter.py +63 -50
- ultralytics/solutions/security_alarm.py +22 -25
- ultralytics/solutions/similarity_search.py +107 -60
- ultralytics/solutions/solutions.py +343 -262
- ultralytics/solutions/speed_estimation.py +35 -31
- ultralytics/solutions/streamlit_inference.py +104 -40
- ultralytics/solutions/templates/similarity-search.html +31 -24
- ultralytics/solutions/trackzone.py +24 -24
- ultralytics/solutions/vision_eye.py +11 -12
- ultralytics/trackers/__init__.py +1 -1
- ultralytics/trackers/basetrack.py +18 -27
- ultralytics/trackers/bot_sort.py +48 -39
- ultralytics/trackers/byte_tracker.py +94 -94
- ultralytics/trackers/track.py +7 -16
- ultralytics/trackers/utils/gmc.py +37 -69
- ultralytics/trackers/utils/kalman_filter.py +68 -76
- ultralytics/trackers/utils/matching.py +13 -17
- ultralytics/utils/__init__.py +251 -275
- ultralytics/utils/autobatch.py +19 -7
- ultralytics/utils/autodevice.py +68 -38
- ultralytics/utils/benchmarks.py +169 -130
- ultralytics/utils/callbacks/base.py +12 -13
- ultralytics/utils/callbacks/clearml.py +14 -15
- ultralytics/utils/callbacks/comet.py +139 -66
- ultralytics/utils/callbacks/dvc.py +19 -27
- ultralytics/utils/callbacks/hub.py +8 -6
- ultralytics/utils/callbacks/mlflow.py +6 -10
- ultralytics/utils/callbacks/neptune.py +11 -19
- ultralytics/utils/callbacks/platform.py +73 -0
- ultralytics/utils/callbacks/raytune.py +3 -4
- ultralytics/utils/callbacks/tensorboard.py +9 -12
- ultralytics/utils/callbacks/wb.py +33 -30
- ultralytics/utils/checks.py +163 -114
- ultralytics/utils/cpu.py +89 -0
- ultralytics/utils/dist.py +24 -20
- ultralytics/utils/downloads.py +176 -146
- ultralytics/utils/errors.py +11 -13
- ultralytics/utils/events.py +113 -0
- ultralytics/utils/export/__init__.py +7 -0
- ultralytics/utils/{export.py → export/engine.py} +81 -63
- ultralytics/utils/export/imx.py +294 -0
- ultralytics/utils/export/tensorflow.py +217 -0
- ultralytics/utils/files.py +33 -36
- ultralytics/utils/git.py +137 -0
- ultralytics/utils/instance.py +105 -120
- ultralytics/utils/logger.py +404 -0
- ultralytics/utils/loss.py +99 -61
- ultralytics/utils/metrics.py +649 -478
- ultralytics/utils/nms.py +337 -0
- ultralytics/utils/ops.py +263 -451
- ultralytics/utils/patches.py +70 -31
- ultralytics/utils/plotting.py +253 -223
- ultralytics/utils/tal.py +48 -61
- ultralytics/utils/torch_utils.py +244 -251
- ultralytics/utils/tqdm.py +438 -0
- ultralytics/utils/triton.py +22 -23
- ultralytics/utils/tuner.py +11 -10
- dgenerate_ultralytics_headless-8.3.137.dist-info/RECORD +0 -272
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/entry_points.txt +0 -0
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/licenses/LICENSE +0 -0
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/top_level.txt +0 -0
|
@@ -1,38 +1,39 @@
|
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import os
|
|
4
6
|
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
5
8
|
|
|
6
9
|
import numpy as np
|
|
7
10
|
import torch
|
|
11
|
+
import torch.distributed as dist
|
|
8
12
|
|
|
9
13
|
from ultralytics.data import build_dataloader, build_yolo_dataset, converter
|
|
10
14
|
from ultralytics.engine.validator import BaseValidator
|
|
11
|
-
from ultralytics.utils import LOGGER, ops
|
|
15
|
+
from ultralytics.utils import LOGGER, RANK, nms, ops
|
|
12
16
|
from ultralytics.utils.checks import check_requirements
|
|
13
17
|
from ultralytics.utils.metrics import ConfusionMatrix, DetMetrics, box_iou
|
|
14
|
-
from ultralytics.utils.plotting import
|
|
18
|
+
from ultralytics.utils.plotting import plot_images
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
class DetectionValidator(BaseValidator):
|
|
18
|
-
"""
|
|
19
|
-
A class extending the BaseValidator class for validation based on a detection model.
|
|
22
|
+
"""A class extending the BaseValidator class for validation based on a detection model.
|
|
20
23
|
|
|
21
24
|
This class implements validation functionality specific to object detection tasks, including metrics calculation,
|
|
22
25
|
prediction processing, and visualization of results.
|
|
23
26
|
|
|
24
27
|
Attributes:
|
|
25
|
-
nt_per_class (np.ndarray): Number of targets per class.
|
|
26
|
-
nt_per_image (np.ndarray): Number of targets per image.
|
|
27
28
|
is_coco (bool): Whether the dataset is COCO.
|
|
28
29
|
is_lvis (bool): Whether the dataset is LVIS.
|
|
29
|
-
class_map (list): Mapping from model class indices to dataset class indices.
|
|
30
|
+
class_map (list[int]): Mapping from model class indices to dataset class indices.
|
|
30
31
|
metrics (DetMetrics): Object detection metrics calculator.
|
|
31
32
|
iouv (torch.Tensor): IoU thresholds for mAP calculation.
|
|
32
33
|
niou (int): Number of IoU thresholds.
|
|
33
|
-
lb (list): List for storing ground truth labels for hybrid saving.
|
|
34
|
-
jdict (list): List for storing JSON detection results.
|
|
35
|
-
stats (dict): Dictionary for storing statistics during validation.
|
|
34
|
+
lb (list[Any]): List for storing ground truth labels for hybrid saving.
|
|
35
|
+
jdict (list[dict[str, Any]]): List for storing JSON detection results.
|
|
36
|
+
stats (dict[str, list[torch.Tensor]]): Dictionary for storing statistics during validation.
|
|
36
37
|
|
|
37
38
|
Examples:
|
|
38
39
|
>>> from ultralytics.models.yolo.detect import DetectionValidator
|
|
@@ -41,48 +42,41 @@ class DetectionValidator(BaseValidator):
|
|
|
41
42
|
>>> validator()
|
|
42
43
|
"""
|
|
43
44
|
|
|
44
|
-
def __init__(self, dataloader=None, save_dir=None,
|
|
45
|
-
"""
|
|
46
|
-
Initialize detection validator with necessary variables and settings.
|
|
45
|
+
def __init__(self, dataloader=None, save_dir=None, args=None, _callbacks=None) -> None:
|
|
46
|
+
"""Initialize detection validator with necessary variables and settings.
|
|
47
47
|
|
|
48
48
|
Args:
|
|
49
49
|
dataloader (torch.utils.data.DataLoader, optional): Dataloader to use for validation.
|
|
50
50
|
save_dir (Path, optional): Directory to save results.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
_callbacks (list, optional): List of callback functions.
|
|
51
|
+
args (dict[str, Any], optional): Arguments for the validator.
|
|
52
|
+
_callbacks (list[Any], optional): List of callback functions.
|
|
54
53
|
"""
|
|
55
|
-
super().__init__(dataloader, save_dir,
|
|
56
|
-
self.nt_per_class = None
|
|
57
|
-
self.nt_per_image = None
|
|
54
|
+
super().__init__(dataloader, save_dir, args, _callbacks)
|
|
58
55
|
self.is_coco = False
|
|
59
56
|
self.is_lvis = False
|
|
60
57
|
self.class_map = None
|
|
61
58
|
self.args.task = "detect"
|
|
62
|
-
self.metrics = DetMetrics(save_dir=self.save_dir)
|
|
63
59
|
self.iouv = torch.linspace(0.5, 0.95, 10) # IoU vector for mAP@0.5:0.95
|
|
64
60
|
self.niou = self.iouv.numel()
|
|
61
|
+
self.metrics = DetMetrics()
|
|
65
62
|
|
|
66
|
-
def preprocess(self, batch):
|
|
67
|
-
"""
|
|
68
|
-
Preprocess batch of images for YOLO validation.
|
|
63
|
+
def preprocess(self, batch: dict[str, Any]) -> dict[str, Any]:
|
|
64
|
+
"""Preprocess batch of images for YOLO validation.
|
|
69
65
|
|
|
70
66
|
Args:
|
|
71
|
-
batch (dict): Batch containing images and annotations.
|
|
67
|
+
batch (dict[str, Any]): Batch containing images and annotations.
|
|
72
68
|
|
|
73
69
|
Returns:
|
|
74
|
-
(dict): Preprocessed batch.
|
|
70
|
+
(dict[str, Any]): Preprocessed batch.
|
|
75
71
|
"""
|
|
76
|
-
|
|
72
|
+
for k, v in batch.items():
|
|
73
|
+
if isinstance(v, torch.Tensor):
|
|
74
|
+
batch[k] = v.to(self.device, non_blocking=self.device.type == "cuda")
|
|
77
75
|
batch["img"] = (batch["img"].half() if self.args.half else batch["img"].float()) / 255
|
|
78
|
-
for k in ["batch_idx", "cls", "bboxes"]:
|
|
79
|
-
batch[k] = batch[k].to(self.device)
|
|
80
|
-
|
|
81
76
|
return batch
|
|
82
77
|
|
|
83
|
-
def init_metrics(self, model):
|
|
84
|
-
"""
|
|
85
|
-
Initialize evaluation metrics for YOLO detection validation.
|
|
78
|
+
def init_metrics(self, model: torch.nn.Module) -> None:
|
|
79
|
+
"""Initialize evaluation metrics for YOLO detection validation.
|
|
86
80
|
|
|
87
81
|
Args:
|
|
88
82
|
model (torch.nn.Module): Model to validate.
|
|
@@ -99,28 +93,26 @@ class DetectionValidator(BaseValidator):
|
|
|
99
93
|
self.names = model.names
|
|
100
94
|
self.nc = len(model.names)
|
|
101
95
|
self.end2end = getattr(model, "end2end", False)
|
|
102
|
-
self.metrics.names = self.names
|
|
103
|
-
self.metrics.plot = self.args.plots
|
|
104
|
-
self.confusion_matrix = ConfusionMatrix(nc=self.nc, conf=self.args.conf)
|
|
105
96
|
self.seen = 0
|
|
106
97
|
self.jdict = []
|
|
107
|
-
self.
|
|
98
|
+
self.metrics.names = model.names
|
|
99
|
+
self.confusion_matrix = ConfusionMatrix(names=model.names, save_matches=self.args.plots and self.args.visualize)
|
|
108
100
|
|
|
109
|
-
def get_desc(self):
|
|
101
|
+
def get_desc(self) -> str:
|
|
110
102
|
"""Return a formatted string summarizing class metrics of YOLO model."""
|
|
111
103
|
return ("%22s" + "%11s" * 6) % ("Class", "Images", "Instances", "Box(P", "R", "mAP50", "mAP50-95)")
|
|
112
104
|
|
|
113
|
-
def postprocess(self, preds):
|
|
114
|
-
"""
|
|
115
|
-
Apply Non-maximum suppression to prediction outputs.
|
|
105
|
+
def postprocess(self, preds: torch.Tensor) -> list[dict[str, torch.Tensor]]:
|
|
106
|
+
"""Apply Non-maximum suppression to prediction outputs.
|
|
116
107
|
|
|
117
108
|
Args:
|
|
118
109
|
preds (torch.Tensor): Raw predictions from the model.
|
|
119
110
|
|
|
120
111
|
Returns:
|
|
121
|
-
(
|
|
112
|
+
(list[dict[str, torch.Tensor]]): Processed predictions after NMS, where each dict contains 'bboxes', 'conf',
|
|
113
|
+
'cls', and 'extra' tensors.
|
|
122
114
|
"""
|
|
123
|
-
|
|
115
|
+
outputs = nms.non_max_suppression(
|
|
124
116
|
preds,
|
|
125
117
|
self.args.conf,
|
|
126
118
|
self.args.iou,
|
|
@@ -131,17 +123,17 @@ class DetectionValidator(BaseValidator):
|
|
|
131
123
|
end2end=self.end2end,
|
|
132
124
|
rotated=self.args.task == "obb",
|
|
133
125
|
)
|
|
126
|
+
return [{"bboxes": x[:, :4], "conf": x[:, 4], "cls": x[:, 5], "extra": x[:, 6:]} for x in outputs]
|
|
134
127
|
|
|
135
|
-
def _prepare_batch(self, si, batch):
|
|
136
|
-
"""
|
|
137
|
-
Prepare a batch of images and annotations for validation.
|
|
128
|
+
def _prepare_batch(self, si: int, batch: dict[str, Any]) -> dict[str, Any]:
|
|
129
|
+
"""Prepare a batch of images and annotations for validation.
|
|
138
130
|
|
|
139
131
|
Args:
|
|
140
132
|
si (int): Batch index.
|
|
141
|
-
batch (dict): Batch data containing images and annotations.
|
|
133
|
+
batch (dict[str, Any]): Batch data containing images and annotations.
|
|
142
134
|
|
|
143
135
|
Returns:
|
|
144
|
-
(dict): Prepared batch with processed annotations.
|
|
136
|
+
(dict[str, Any]): Prepared batch with processed annotations.
|
|
145
137
|
"""
|
|
146
138
|
idx = batch["batch_idx"] == si
|
|
147
139
|
cls = batch["cls"][idx].squeeze(-1)
|
|
@@ -149,149 +141,154 @@ class DetectionValidator(BaseValidator):
|
|
|
149
141
|
ori_shape = batch["ori_shape"][si]
|
|
150
142
|
imgsz = batch["img"].shape[2:]
|
|
151
143
|
ratio_pad = batch["ratio_pad"][si]
|
|
152
|
-
if
|
|
144
|
+
if cls.shape[0]:
|
|
153
145
|
bbox = ops.xywh2xyxy(bbox) * torch.tensor(imgsz, device=self.device)[[1, 0, 1, 0]] # target boxes
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
146
|
+
return {
|
|
147
|
+
"cls": cls,
|
|
148
|
+
"bboxes": bbox,
|
|
149
|
+
"ori_shape": ori_shape,
|
|
150
|
+
"imgsz": imgsz,
|
|
151
|
+
"ratio_pad": ratio_pad,
|
|
152
|
+
"im_file": batch["im_file"][si],
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
def _prepare_pred(self, pred: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
|
|
156
|
+
"""Prepare predictions for evaluation against ground truth.
|
|
160
157
|
|
|
161
158
|
Args:
|
|
162
|
-
pred (torch.Tensor):
|
|
163
|
-
pbatch (dict): Prepared batch information.
|
|
159
|
+
pred (dict[str, torch.Tensor]): Post-processed predictions from the model.
|
|
164
160
|
|
|
165
161
|
Returns:
|
|
166
|
-
(torch.Tensor): Prepared predictions in native space.
|
|
162
|
+
(dict[str, torch.Tensor]): Prepared predictions in native space.
|
|
167
163
|
"""
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
) # native-space pred
|
|
172
|
-
return predn
|
|
164
|
+
if self.args.single_cls:
|
|
165
|
+
pred["cls"] *= 0
|
|
166
|
+
return pred
|
|
173
167
|
|
|
174
|
-
def update_metrics(self, preds, batch):
|
|
175
|
-
"""
|
|
176
|
-
Update metrics with new predictions and ground truth.
|
|
168
|
+
def update_metrics(self, preds: list[dict[str, torch.Tensor]], batch: dict[str, Any]) -> None:
|
|
169
|
+
"""Update metrics with new predictions and ground truth.
|
|
177
170
|
|
|
178
171
|
Args:
|
|
179
|
-
preds (
|
|
180
|
-
batch (dict): Batch data containing ground truth.
|
|
172
|
+
preds (list[dict[str, torch.Tensor]]): List of predictions from the model.
|
|
173
|
+
batch (dict[str, Any]): Batch data containing ground truth.
|
|
181
174
|
"""
|
|
182
175
|
for si, pred in enumerate(preds):
|
|
183
176
|
self.seen += 1
|
|
184
|
-
npr = len(pred)
|
|
185
|
-
stat = dict(
|
|
186
|
-
conf=torch.zeros(0, device=self.device),
|
|
187
|
-
pred_cls=torch.zeros(0, device=self.device),
|
|
188
|
-
tp=torch.zeros(npr, self.niou, dtype=torch.bool, device=self.device),
|
|
189
|
-
)
|
|
190
177
|
pbatch = self._prepare_batch(si, batch)
|
|
191
|
-
|
|
192
|
-
nl = len(cls)
|
|
193
|
-
stat["target_cls"] = cls
|
|
194
|
-
stat["target_img"] = cls.unique()
|
|
195
|
-
if npr == 0:
|
|
196
|
-
if nl:
|
|
197
|
-
for k in self.stats.keys():
|
|
198
|
-
self.stats[k].append(stat[k])
|
|
199
|
-
if self.args.plots:
|
|
200
|
-
self.confusion_matrix.process_batch(detections=None, gt_bboxes=bbox, gt_cls=cls)
|
|
201
|
-
continue
|
|
202
|
-
|
|
203
|
-
# Predictions
|
|
204
|
-
if self.args.single_cls:
|
|
205
|
-
pred[:, 5] = 0
|
|
206
|
-
predn = self._prepare_pred(pred, pbatch)
|
|
207
|
-
stat["conf"] = predn[:, 4]
|
|
208
|
-
stat["pred_cls"] = predn[:, 5]
|
|
178
|
+
predn = self._prepare_pred(pred)
|
|
209
179
|
|
|
180
|
+
cls = pbatch["cls"].cpu().numpy()
|
|
181
|
+
no_pred = predn["cls"].shape[0] == 0
|
|
182
|
+
self.metrics.update_stats(
|
|
183
|
+
{
|
|
184
|
+
**self._process_batch(predn, pbatch),
|
|
185
|
+
"target_cls": cls,
|
|
186
|
+
"target_img": np.unique(cls),
|
|
187
|
+
"conf": np.zeros(0) if no_pred else predn["conf"].cpu().numpy(),
|
|
188
|
+
"pred_cls": np.zeros(0) if no_pred else predn["cls"].cpu().numpy(),
|
|
189
|
+
}
|
|
190
|
+
)
|
|
210
191
|
# Evaluate
|
|
211
|
-
if nl:
|
|
212
|
-
stat["tp"] = self._process_batch(predn, bbox, cls)
|
|
213
192
|
if self.args.plots:
|
|
214
|
-
self.confusion_matrix.process_batch(predn,
|
|
215
|
-
|
|
216
|
-
|
|
193
|
+
self.confusion_matrix.process_batch(predn, pbatch, conf=self.args.conf)
|
|
194
|
+
if self.args.visualize:
|
|
195
|
+
self.confusion_matrix.plot_matches(batch["img"][si], pbatch["im_file"], self.save_dir)
|
|
196
|
+
|
|
197
|
+
if no_pred:
|
|
198
|
+
continue
|
|
217
199
|
|
|
218
200
|
# Save
|
|
201
|
+
if self.args.save_json or self.args.save_txt:
|
|
202
|
+
predn_scaled = self.scale_preds(predn, pbatch)
|
|
219
203
|
if self.args.save_json:
|
|
220
|
-
self.pred_to_json(
|
|
204
|
+
self.pred_to_json(predn_scaled, pbatch)
|
|
221
205
|
if self.args.save_txt:
|
|
222
206
|
self.save_one_txt(
|
|
223
|
-
|
|
207
|
+
predn_scaled,
|
|
224
208
|
self.args.save_conf,
|
|
225
209
|
pbatch["ori_shape"],
|
|
226
|
-
self.save_dir / "labels" / f"{Path(
|
|
210
|
+
self.save_dir / "labels" / f"{Path(pbatch['im_file']).stem}.txt",
|
|
227
211
|
)
|
|
228
212
|
|
|
229
|
-
def finalize_metrics(self
|
|
230
|
-
"""
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
*args (Any): Variable length argument list.
|
|
235
|
-
**kwargs (Any): Arbitrary keyword arguments.
|
|
236
|
-
"""
|
|
213
|
+
def finalize_metrics(self) -> None:
|
|
214
|
+
"""Set final values for metrics speed and confusion matrix."""
|
|
215
|
+
if self.args.plots:
|
|
216
|
+
for normalize in True, False:
|
|
217
|
+
self.confusion_matrix.plot(save_dir=self.save_dir, normalize=normalize, on_plot=self.on_plot)
|
|
237
218
|
self.metrics.speed = self.speed
|
|
238
219
|
self.metrics.confusion_matrix = self.confusion_matrix
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
220
|
+
self.metrics.save_dir = self.save_dir
|
|
221
|
+
|
|
222
|
+
def gather_stats(self) -> None:
|
|
223
|
+
"""Gather stats from all GPUs."""
|
|
224
|
+
if RANK == 0:
|
|
225
|
+
gathered_stats = [None] * dist.get_world_size()
|
|
226
|
+
dist.gather_object(self.metrics.stats, gathered_stats, dst=0)
|
|
227
|
+
merged_stats = {key: [] for key in self.metrics.stats.keys()}
|
|
228
|
+
for stats_dict in gathered_stats:
|
|
229
|
+
for key in merged_stats.keys():
|
|
230
|
+
merged_stats[key].extend(stats_dict[key])
|
|
231
|
+
gathered_jdict = [None] * dist.get_world_size()
|
|
232
|
+
dist.gather_object(self.jdict, gathered_jdict, dst=0)
|
|
233
|
+
self.jdict = []
|
|
234
|
+
for jdict in gathered_jdict:
|
|
235
|
+
self.jdict.extend(jdict)
|
|
236
|
+
self.metrics.stats = merged_stats
|
|
237
|
+
self.seen = len(self.dataloader.dataset) # total image count from dataset
|
|
238
|
+
elif RANK > 0:
|
|
239
|
+
dist.gather_object(self.metrics.stats, None, dst=0)
|
|
240
|
+
dist.gather_object(self.jdict, None, dst=0)
|
|
241
|
+
self.jdict = []
|
|
242
|
+
self.metrics.clear_stats()
|
|
243
|
+
|
|
244
|
+
def get_stats(self) -> dict[str, Any]:
|
|
245
|
+
"""Calculate and return metrics statistics.
|
|
243
246
|
|
|
244
247
|
Returns:
|
|
245
|
-
(dict): Dictionary containing metrics results.
|
|
248
|
+
(dict[str, Any]): Dictionary containing metrics results.
|
|
246
249
|
"""
|
|
247
|
-
|
|
248
|
-
self.
|
|
249
|
-
self.nt_per_image = np.bincount(stats["target_img"].astype(int), minlength=self.nc)
|
|
250
|
-
stats.pop("target_img", None)
|
|
251
|
-
if len(stats):
|
|
252
|
-
self.metrics.process(**stats, on_plot=self.on_plot)
|
|
250
|
+
self.metrics.process(save_dir=self.save_dir, plot=self.args.plots, on_plot=self.on_plot)
|
|
251
|
+
self.metrics.clear_stats()
|
|
253
252
|
return self.metrics.results_dict
|
|
254
253
|
|
|
255
|
-
def print_results(self):
|
|
254
|
+
def print_results(self) -> None:
|
|
256
255
|
"""Print training/validation set metrics per class."""
|
|
257
256
|
pf = "%22s" + "%11i" * 2 + "%11.3g" * len(self.metrics.keys) # print format
|
|
258
|
-
LOGGER.info(pf % ("all", self.seen, self.nt_per_class.sum(), *self.metrics.mean_results()))
|
|
259
|
-
if self.nt_per_class.sum() == 0:
|
|
257
|
+
LOGGER.info(pf % ("all", self.seen, self.metrics.nt_per_class.sum(), *self.metrics.mean_results()))
|
|
258
|
+
if self.metrics.nt_per_class.sum() == 0:
|
|
260
259
|
LOGGER.warning(f"no labels found in {self.args.task} set, can not compute metrics without labels")
|
|
261
260
|
|
|
262
261
|
# Print results per class
|
|
263
|
-
if self.args.verbose and not self.training and self.nc > 1 and len(self.stats):
|
|
262
|
+
if self.args.verbose and not self.training and self.nc > 1 and len(self.metrics.stats):
|
|
264
263
|
for i, c in enumerate(self.metrics.ap_class_index):
|
|
265
264
|
LOGGER.info(
|
|
266
|
-
pf
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
265
|
+
pf
|
|
266
|
+
% (
|
|
267
|
+
self.names[c],
|
|
268
|
+
self.metrics.nt_per_image[c],
|
|
269
|
+
self.metrics.nt_per_class[c],
|
|
270
|
+
*self.metrics.class_result(i),
|
|
271
|
+
)
|
|
273
272
|
)
|
|
274
273
|
|
|
275
|
-
def _process_batch(self,
|
|
276
|
-
"""
|
|
277
|
-
Return correct prediction matrix.
|
|
274
|
+
def _process_batch(self, preds: dict[str, torch.Tensor], batch: dict[str, Any]) -> dict[str, np.ndarray]:
|
|
275
|
+
"""Return correct prediction matrix.
|
|
278
276
|
|
|
279
277
|
Args:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
gt_bboxes (torch.Tensor): Tensor of shape (M, 4) representing ground-truth bounding box coordinates. Each
|
|
283
|
-
bounding box is of the format: (x1, y1, x2, y2).
|
|
284
|
-
gt_cls (torch.Tensor): Tensor of shape (M,) representing target class indices.
|
|
278
|
+
preds (dict[str, torch.Tensor]): Dictionary containing prediction data with 'bboxes' and 'cls' keys.
|
|
279
|
+
batch (dict[str, Any]): Batch dictionary containing ground truth data with 'bboxes' and 'cls' keys.
|
|
285
280
|
|
|
286
281
|
Returns:
|
|
287
|
-
(
|
|
282
|
+
(dict[str, np.ndarray]): Dictionary containing 'tp' key with correct prediction matrix of shape (N, 10) for
|
|
283
|
+
10 IoU levels.
|
|
288
284
|
"""
|
|
289
|
-
|
|
290
|
-
|
|
285
|
+
if batch["cls"].shape[0] == 0 or preds["cls"].shape[0] == 0:
|
|
286
|
+
return {"tp": np.zeros((preds["cls"].shape[0], self.niou), dtype=bool)}
|
|
287
|
+
iou = box_iou(batch["bboxes"], preds["bboxes"])
|
|
288
|
+
return {"tp": self.match_predictions(preds["cls"], batch["cls"], iou).cpu().numpy()}
|
|
291
289
|
|
|
292
|
-
def build_dataset(self, img_path, mode="val", batch=None):
|
|
293
|
-
"""
|
|
294
|
-
Build YOLO Dataset.
|
|
290
|
+
def build_dataset(self, img_path: str, mode: str = "val", batch: int | None = None) -> torch.utils.data.Dataset:
|
|
291
|
+
"""Build YOLO Dataset.
|
|
295
292
|
|
|
296
293
|
Args:
|
|
297
294
|
img_path (str): Path to the folder containing images.
|
|
@@ -303,9 +300,8 @@ class DetectionValidator(BaseValidator):
|
|
|
303
300
|
"""
|
|
304
301
|
return build_yolo_dataset(self.args, img_path, batch, self.data, mode=mode, stride=self.stride)
|
|
305
302
|
|
|
306
|
-
def get_dataloader(self, dataset_path, batch_size):
|
|
307
|
-
"""
|
|
308
|
-
Construct and return dataloader.
|
|
303
|
+
def get_dataloader(self, dataset_path: str, batch_size: int) -> torch.utils.data.DataLoader:
|
|
304
|
+
"""Construct and return dataloader.
|
|
309
305
|
|
|
310
306
|
Args:
|
|
311
307
|
dataset_path (str): Path to the dataset.
|
|
@@ -315,53 +311,66 @@ class DetectionValidator(BaseValidator):
|
|
|
315
311
|
(torch.utils.data.DataLoader): Dataloader for validation.
|
|
316
312
|
"""
|
|
317
313
|
dataset = self.build_dataset(dataset_path, batch=batch_size, mode="val")
|
|
318
|
-
return build_dataloader(
|
|
314
|
+
return build_dataloader(
|
|
315
|
+
dataset,
|
|
316
|
+
batch_size,
|
|
317
|
+
self.args.workers,
|
|
318
|
+
shuffle=False,
|
|
319
|
+
rank=-1,
|
|
320
|
+
drop_last=self.args.compile,
|
|
321
|
+
pin_memory=self.training,
|
|
322
|
+
)
|
|
319
323
|
|
|
320
|
-
def plot_val_samples(self, batch, ni):
|
|
321
|
-
"""
|
|
322
|
-
Plot validation image samples.
|
|
324
|
+
def plot_val_samples(self, batch: dict[str, Any], ni: int) -> None:
|
|
325
|
+
"""Plot validation image samples.
|
|
323
326
|
|
|
324
327
|
Args:
|
|
325
|
-
batch (dict): Batch containing images and annotations.
|
|
328
|
+
batch (dict[str, Any]): Batch containing images and annotations.
|
|
326
329
|
ni (int): Batch index.
|
|
327
330
|
"""
|
|
328
331
|
plot_images(
|
|
329
|
-
batch
|
|
330
|
-
batch["batch_idx"],
|
|
331
|
-
batch["cls"].squeeze(-1),
|
|
332
|
-
batch["bboxes"],
|
|
332
|
+
labels=batch,
|
|
333
333
|
paths=batch["im_file"],
|
|
334
334
|
fname=self.save_dir / f"val_batch{ni}_labels.jpg",
|
|
335
335
|
names=self.names,
|
|
336
336
|
on_plot=self.on_plot,
|
|
337
337
|
)
|
|
338
338
|
|
|
339
|
-
def plot_predictions(
|
|
340
|
-
|
|
341
|
-
|
|
339
|
+
def plot_predictions(
|
|
340
|
+
self, batch: dict[str, Any], preds: list[dict[str, torch.Tensor]], ni: int, max_det: int | None = None
|
|
341
|
+
) -> None:
|
|
342
|
+
"""Plot predicted bounding boxes on input images and save the result.
|
|
342
343
|
|
|
343
344
|
Args:
|
|
344
|
-
batch (dict): Batch containing images and annotations.
|
|
345
|
-
preds (
|
|
345
|
+
batch (dict[str, Any]): Batch containing images and annotations.
|
|
346
|
+
preds (list[dict[str, torch.Tensor]]): List of predictions from the model.
|
|
346
347
|
ni (int): Batch index.
|
|
347
|
-
|
|
348
|
+
max_det (Optional[int]): Maximum number of detections to plot.
|
|
349
|
+
"""
|
|
350
|
+
# TODO: optimize this
|
|
351
|
+
for i, pred in enumerate(preds):
|
|
352
|
+
pred["batch_idx"] = torch.ones_like(pred["conf"]) * i # add batch index to predictions
|
|
353
|
+
keys = preds[0].keys()
|
|
354
|
+
max_det = max_det or self.args.max_det
|
|
355
|
+
batched_preds = {k: torch.cat([x[k][:max_det] for x in preds], dim=0) for k in keys}
|
|
356
|
+
# TODO: fix this
|
|
357
|
+
batched_preds["bboxes"][:, :4] = ops.xyxy2xywh(batched_preds["bboxes"][:, :4]) # convert to xywh format
|
|
348
358
|
plot_images(
|
|
349
|
-
batch["img"],
|
|
350
|
-
|
|
359
|
+
images=batch["img"],
|
|
360
|
+
labels=batched_preds,
|
|
351
361
|
paths=batch["im_file"],
|
|
352
362
|
fname=self.save_dir / f"val_batch{ni}_pred.jpg",
|
|
353
363
|
names=self.names,
|
|
354
364
|
on_plot=self.on_plot,
|
|
355
365
|
) # pred
|
|
356
366
|
|
|
357
|
-
def save_one_txt(self, predn, save_conf, shape, file):
|
|
358
|
-
"""
|
|
359
|
-
Save YOLO detections to a txt file in normalized coordinates in a specific format.
|
|
367
|
+
def save_one_txt(self, predn: dict[str, torch.Tensor], save_conf: bool, shape: tuple[int, int], file: Path) -> None:
|
|
368
|
+
"""Save YOLO detections to a txt file in normalized coordinates in a specific format.
|
|
360
369
|
|
|
361
370
|
Args:
|
|
362
|
-
predn (torch.Tensor):
|
|
371
|
+
predn (dict[str, torch.Tensor]): Dictionary containing predictions with keys 'bboxes', 'conf', and 'cls'.
|
|
363
372
|
save_conf (bool): Whether to save confidence scores.
|
|
364
|
-
shape (tuple): Shape of the original image.
|
|
373
|
+
shape (tuple[int, int]): Shape of the original image (height, width).
|
|
365
374
|
file (Path): File path to save the detections.
|
|
366
375
|
"""
|
|
367
376
|
from ultralytics.engine.results import Results
|
|
@@ -370,82 +379,129 @@ class DetectionValidator(BaseValidator):
|
|
|
370
379
|
np.zeros((shape[0], shape[1]), dtype=np.uint8),
|
|
371
380
|
path=None,
|
|
372
381
|
names=self.names,
|
|
373
|
-
boxes=predn[
|
|
382
|
+
boxes=torch.cat([predn["bboxes"], predn["conf"].unsqueeze(-1), predn["cls"].unsqueeze(-1)], dim=1),
|
|
374
383
|
).save_txt(file, save_conf=save_conf)
|
|
375
384
|
|
|
376
|
-
def pred_to_json(self, predn,
|
|
377
|
-
"""
|
|
378
|
-
Serialize YOLO predictions to COCO json format.
|
|
385
|
+
def pred_to_json(self, predn: dict[str, torch.Tensor], pbatch: dict[str, Any]) -> None:
|
|
386
|
+
"""Serialize YOLO predictions to COCO json format.
|
|
379
387
|
|
|
380
388
|
Args:
|
|
381
|
-
predn (torch.Tensor): Predictions
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
389
|
+
predn (dict[str, torch.Tensor]): Predictions dictionary containing 'bboxes', 'conf', and 'cls' keys with
|
|
390
|
+
bounding box coordinates, confidence scores, and class predictions.
|
|
391
|
+
pbatch (dict[str, Any]): Batch dictionary containing 'imgsz', 'ori_shape', 'ratio_pad', and 'im_file'.
|
|
392
|
+
|
|
393
|
+
Examples:
|
|
394
|
+
>>> result = {
|
|
395
|
+
... "image_id": 42,
|
|
396
|
+
... "file_name": "42.jpg",
|
|
397
|
+
... "category_id": 18,
|
|
398
|
+
... "bbox": [258.15, 41.29, 348.26, 243.78],
|
|
399
|
+
... "score": 0.236,
|
|
400
|
+
... }
|
|
401
|
+
"""
|
|
402
|
+
path = Path(pbatch["im_file"])
|
|
403
|
+
stem = path.stem
|
|
385
404
|
image_id = int(stem) if stem.isnumeric() else stem
|
|
386
|
-
box = ops.xyxy2xywh(predn[
|
|
405
|
+
box = ops.xyxy2xywh(predn["bboxes"]) # xywh
|
|
387
406
|
box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
|
|
388
|
-
for
|
|
407
|
+
for b, s, c in zip(box.tolist(), predn["conf"].tolist(), predn["cls"].tolist()):
|
|
389
408
|
self.jdict.append(
|
|
390
409
|
{
|
|
391
410
|
"image_id": image_id,
|
|
392
|
-
"
|
|
411
|
+
"file_name": path.name,
|
|
412
|
+
"category_id": self.class_map[int(c)],
|
|
393
413
|
"bbox": [round(x, 3) for x in b],
|
|
394
|
-
"score": round(
|
|
414
|
+
"score": round(s, 5),
|
|
395
415
|
}
|
|
396
416
|
)
|
|
397
417
|
|
|
398
|
-
def
|
|
399
|
-
"""
|
|
400
|
-
|
|
418
|
+
def scale_preds(self, predn: dict[str, torch.Tensor], pbatch: dict[str, Any]) -> dict[str, torch.Tensor]:
|
|
419
|
+
"""Scales predictions to the original image size."""
|
|
420
|
+
return {
|
|
421
|
+
**predn,
|
|
422
|
+
"bboxes": ops.scale_boxes(
|
|
423
|
+
pbatch["imgsz"],
|
|
424
|
+
predn["bboxes"].clone(),
|
|
425
|
+
pbatch["ori_shape"],
|
|
426
|
+
ratio_pad=pbatch["ratio_pad"],
|
|
427
|
+
),
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
def eval_json(self, stats: dict[str, Any]) -> dict[str, Any]:
|
|
431
|
+
"""Evaluate YOLO output in JSON format and return performance statistics.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
stats (dict[str, Any]): Current statistics dictionary.
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
(dict[str, Any]): Updated statistics dictionary with COCO/LVIS evaluation results.
|
|
438
|
+
"""
|
|
439
|
+
pred_json = self.save_dir / "predictions.json" # predictions
|
|
440
|
+
anno_json = (
|
|
441
|
+
self.data["path"]
|
|
442
|
+
/ "annotations"
|
|
443
|
+
/ ("instances_val2017.json" if self.is_coco else f"lvis_v1_{self.args.split}.json")
|
|
444
|
+
) # annotations
|
|
445
|
+
return self.coco_evaluate(stats, pred_json, anno_json)
|
|
446
|
+
|
|
447
|
+
def coco_evaluate(
|
|
448
|
+
self,
|
|
449
|
+
stats: dict[str, Any],
|
|
450
|
+
pred_json: str,
|
|
451
|
+
anno_json: str,
|
|
452
|
+
iou_types: str | list[str] = "bbox",
|
|
453
|
+
suffix: str | list[str] = "Box",
|
|
454
|
+
) -> dict[str, Any]:
|
|
455
|
+
"""Evaluate COCO/LVIS metrics using faster-coco-eval library.
|
|
456
|
+
|
|
457
|
+
Performs evaluation using the faster-coco-eval library to compute mAP metrics for object detection. Updates the
|
|
458
|
+
provided stats dictionary with computed metrics including mAP50, mAP50-95, and LVIS-specific metrics if
|
|
459
|
+
applicable.
|
|
401
460
|
|
|
402
461
|
Args:
|
|
403
|
-
stats (dict):
|
|
462
|
+
stats (dict[str, Any]): Dictionary to store computed metrics and statistics.
|
|
463
|
+
pred_json (str | Path]): Path to JSON file containing predictions in COCO format.
|
|
464
|
+
anno_json (str | Path]): Path to JSON file containing ground truth annotations in COCO format.
|
|
465
|
+
iou_types (str | list[str]]): IoU type(s) for evaluation. Can be single string or list of strings. Common
|
|
466
|
+
values include "bbox", "segm", "keypoints". Defaults to "bbox".
|
|
467
|
+
suffix (str | list[str]]): Suffix to append to metric names in stats dictionary. Should correspond to
|
|
468
|
+
iou_types if multiple types provided. Defaults to "Box".
|
|
404
469
|
|
|
405
470
|
Returns:
|
|
406
|
-
(dict): Updated
|
|
471
|
+
(dict[str, Any]): Updated stats dictionary containing the computed COCO/LVIS evaluation metrics.
|
|
407
472
|
"""
|
|
408
473
|
if self.args.save_json and (self.is_coco or self.is_lvis) and len(self.jdict):
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
self.data["path"]
|
|
412
|
-
/ "annotations"
|
|
413
|
-
/ ("instances_val2017.json" if self.is_coco else f"lvis_v1_{self.args.split}.json")
|
|
414
|
-
) # annotations
|
|
415
|
-
pkg = "pycocotools" if self.is_coco else "lvis"
|
|
416
|
-
LOGGER.info(f"\nEvaluating {pkg} mAP using {pred_json} and {anno_json}...")
|
|
417
|
-
try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
|
|
474
|
+
LOGGER.info(f"\nEvaluating faster-coco-eval mAP using {pred_json} and {anno_json}...")
|
|
475
|
+
try:
|
|
418
476
|
for x in pred_json, anno_json:
|
|
419
477
|
assert x.is_file(), f"{x} file not found"
|
|
420
|
-
|
|
421
|
-
if
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
val
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
478
|
+
iou_types = [iou_types] if isinstance(iou_types, str) else iou_types
|
|
479
|
+
suffix = [suffix] if isinstance(suffix, str) else suffix
|
|
480
|
+
check_requirements("faster-coco-eval>=1.6.7")
|
|
481
|
+
from faster_coco_eval import COCO, COCOeval_faster
|
|
482
|
+
|
|
483
|
+
anno = COCO(anno_json)
|
|
484
|
+
pred = anno.loadRes(pred_json)
|
|
485
|
+
for i, iou_type in enumerate(iou_types):
|
|
486
|
+
val = COCOeval_faster(
|
|
487
|
+
anno, pred, iouType=iou_type, lvis_style=self.is_lvis, print_function=LOGGER.info
|
|
488
|
+
)
|
|
489
|
+
val.params.imgIds = [int(Path(x).stem) for x in self.dataloader.dataset.im_files] # images to eval
|
|
490
|
+
val.evaluate()
|
|
491
|
+
val.accumulate()
|
|
492
|
+
val.summarize()
|
|
493
|
+
|
|
494
|
+
# update mAP50-95 and mAP50
|
|
495
|
+
stats[f"metrics/mAP50({suffix[i][0]})"] = val.stats_as_dict["AP_50"]
|
|
496
|
+
stats[f"metrics/mAP50-95({suffix[i][0]})"] = val.stats_as_dict["AP_all"]
|
|
497
|
+
|
|
498
|
+
if self.is_lvis:
|
|
499
|
+
stats[f"metrics/APr({suffix[i][0]})"] = val.stats_as_dict["APr"]
|
|
500
|
+
stats[f"metrics/APc({suffix[i][0]})"] = val.stats_as_dict["APc"]
|
|
501
|
+
stats[f"metrics/APf({suffix[i][0]})"] = val.stats_as_dict["APf"]
|
|
502
|
+
|
|
444
503
|
if self.is_lvis:
|
|
445
|
-
stats["metrics/
|
|
446
|
-
stats["metrics/APc(B)"] = val.results["APc"]
|
|
447
|
-
stats["metrics/APf(B)"] = val.results["APf"]
|
|
448
|
-
stats["fitness"] = val.results["AP"]
|
|
504
|
+
stats["fitness"] = stats["metrics/mAP50-95(B)"] # always use box mAP50-95 for fitness
|
|
449
505
|
except Exception as e:
|
|
450
|
-
LOGGER.warning(f"
|
|
506
|
+
LOGGER.warning(f"faster-coco-eval unable to run: {e}")
|
|
451
507
|
return stats
|