ultralytics 8.3.159__py3-none-any.whl → 8.3.161__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.
- tests/test_python.py +2 -1
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/__init__.py +0 -2
- ultralytics/cfg/datasets/Argoverse.yaml +1 -1
- ultralytics/cfg/datasets/DOTAv1.5.yaml +1 -1
- ultralytics/cfg/datasets/DOTAv1.yaml +1 -1
- ultralytics/cfg/datasets/GlobalWheat2020.yaml +1 -1
- ultralytics/cfg/datasets/HomeObjects-3K.yaml +1 -1
- ultralytics/cfg/datasets/ImageNet.yaml +1 -1
- ultralytics/cfg/datasets/Objects365.yaml +1 -1
- ultralytics/cfg/datasets/SKU-110K.yaml +1 -1
- ultralytics/cfg/datasets/VOC.yaml +1 -1
- ultralytics/cfg/datasets/VisDrone.yaml +6 -3
- ultralytics/cfg/datasets/african-wildlife.yaml +1 -1
- ultralytics/cfg/datasets/brain-tumor.yaml +1 -1
- ultralytics/cfg/datasets/carparts-seg.yaml +1 -1
- ultralytics/cfg/datasets/coco-pose.yaml +1 -1
- ultralytics/cfg/datasets/coco.yaml +1 -1
- ultralytics/cfg/datasets/coco128-seg.yaml +1 -1
- ultralytics/cfg/datasets/coco128.yaml +1 -1
- ultralytics/cfg/datasets/coco8-grayscale.yaml +1 -1
- ultralytics/cfg/datasets/coco8-multispectral.yaml +1 -1
- ultralytics/cfg/datasets/coco8-pose.yaml +1 -1
- ultralytics/cfg/datasets/coco8-seg.yaml +1 -1
- ultralytics/cfg/datasets/coco8.yaml +1 -1
- ultralytics/cfg/datasets/crack-seg.yaml +1 -1
- ultralytics/cfg/datasets/dog-pose.yaml +1 -1
- ultralytics/cfg/datasets/dota8-multispectral.yaml +1 -1
- ultralytics/cfg/datasets/dota8.yaml +1 -1
- ultralytics/cfg/datasets/hand-keypoints.yaml +1 -1
- ultralytics/cfg/datasets/lvis.yaml +1 -1
- ultralytics/cfg/datasets/medical-pills.yaml +1 -1
- ultralytics/cfg/datasets/open-images-v7.yaml +1 -1
- ultralytics/cfg/datasets/package-seg.yaml +1 -1
- ultralytics/cfg/datasets/signature.yaml +1 -1
- ultralytics/cfg/datasets/tiger-pose.yaml +1 -1
- ultralytics/cfg/datasets/xView.yaml +1 -1
- ultralytics/data/augment.py +8 -8
- ultralytics/data/converter.py +3 -5
- ultralytics/data/dataset.py +1 -1
- ultralytics/data/split.py +1 -1
- ultralytics/engine/exporter.py +11 -2
- ultralytics/engine/model.py +2 -0
- ultralytics/engine/results.py +1 -6
- ultralytics/models/yolo/model.py +25 -24
- ultralytics/models/yolo/world/train.py +1 -1
- ultralytics/models/yolo/world/train_world.py +6 -6
- ultralytics/models/yolo/yoloe/train.py +1 -1
- ultralytics/nn/autobackend.py +7 -1
- ultralytics/solutions/heatmap.py +1 -1
- ultralytics/solutions/object_counter.py +9 -9
- ultralytics/solutions/similarity_search.py +11 -12
- ultralytics/solutions/solutions.py +55 -56
- ultralytics/utils/__init__.py +1 -4
- ultralytics/utils/instance.py +2 -0
- ultralytics/utils/metrics.py +24 -36
- {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/METADATA +1 -1
- {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/RECORD +62 -62
- {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/WHEEL +0 -0
- {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/licenses/LICENSE +0 -0
- {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/top_level.txt +0 -0
ultralytics/models/yolo/model.py
CHANGED
@@ -406,18 +406,18 @@ class YOLOE(Model):
|
|
406
406
|
f"Expected equal number of bounding boxes and classes, but got {len(visual_prompts['bboxes'])} and "
|
407
407
|
f"{len(visual_prompts['cls'])} respectively"
|
408
408
|
)
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
409
|
+
if not isinstance(self.predictor, yolo.yoloe.YOLOEVPDetectPredictor):
|
410
|
+
self.predictor = (predictor or yolo.yoloe.YOLOEVPDetectPredictor)(
|
411
|
+
overrides={
|
412
|
+
"task": self.model.task,
|
413
|
+
"mode": "predict",
|
414
|
+
"save": False,
|
415
|
+
"verbose": refer_image is None,
|
416
|
+
"batch": 1,
|
417
|
+
},
|
418
|
+
_callbacks=self.callbacks,
|
419
|
+
)
|
419
420
|
|
420
|
-
if len(visual_prompts):
|
421
421
|
num_cls = (
|
422
422
|
max(len(set(c)) for c in visual_prompts["cls"])
|
423
423
|
if isinstance(source, list) and refer_image is None # means multiple images
|
@@ -426,18 +426,19 @@ class YOLOE(Model):
|
|
426
426
|
self.model.model[-1].nc = num_cls
|
427
427
|
self.model.names = [f"object{i}" for i in range(num_cls)]
|
428
428
|
self.predictor.set_prompts(visual_prompts.copy())
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
429
|
+
self.predictor.setup_model(model=self.model)
|
430
|
+
|
431
|
+
if refer_image is None and source is not None:
|
432
|
+
dataset = load_inference_source(source)
|
433
|
+
if dataset.mode in {"video", "stream"}:
|
434
|
+
# NOTE: set the first frame as refer image for videos/streams inference
|
435
|
+
refer_image = next(iter(dataset))[1][0]
|
436
|
+
if refer_image is not None:
|
437
|
+
vpe = self.predictor.get_vpe(refer_image)
|
438
|
+
self.model.set_classes(self.model.names, vpe)
|
439
|
+
self.task = "segment" if isinstance(self.predictor, yolo.segment.SegmentationPredictor) else "detect"
|
440
|
+
self.predictor = None # reset predictor
|
441
|
+
elif isinstance(self.predictor, yolo.yoloe.YOLOEVPDetectPredictor):
|
442
|
+
self.predictor = None # reset predictor if no visual prompts
|
442
443
|
|
443
444
|
return super().predict(source, stream, **kwargs)
|
@@ -158,7 +158,7 @@ class WorldTrainer(DetectionTrainer):
|
|
158
158
|
return txt_map
|
159
159
|
LOGGER.info(f"Caching text embeddings to '{cache_path}'")
|
160
160
|
assert self.model is not None
|
161
|
-
txt_feats = self.model.get_text_pe(texts, batch, cache_clip_model=False)
|
161
|
+
txt_feats = de_parallel(self.model).get_text_pe(texts, batch, cache_clip_model=False)
|
162
162
|
txt_map = dict(zip(texts, txt_feats.squeeze(0)))
|
163
163
|
torch.save(txt_map, cache_path)
|
164
164
|
return txt_map
|
@@ -35,12 +35,12 @@ class WorldTrainerFromScratch(WorldTrainer):
|
|
35
35
|
... yolo_data=["Objects365.yaml"],
|
36
36
|
... grounding_data=[
|
37
37
|
... dict(
|
38
|
-
... img_path="
|
39
|
-
... json_file="
|
38
|
+
... img_path="flickr30k/images",
|
39
|
+
... json_file="flickr30k/final_flickr_separateGT_train.json",
|
40
40
|
... ),
|
41
41
|
... dict(
|
42
|
-
... img_path="
|
43
|
-
... json_file="
|
42
|
+
... img_path="GQA/images",
|
43
|
+
... json_file="GQA/final_mixed_train_no_coco.json",
|
44
44
|
... ),
|
45
45
|
... ],
|
46
46
|
... ),
|
@@ -70,8 +70,8 @@ class WorldTrainerFromScratch(WorldTrainer):
|
|
70
70
|
... yolo_data=["Objects365.yaml"],
|
71
71
|
... grounding_data=[
|
72
72
|
... dict(
|
73
|
-
... img_path="
|
74
|
-
... json_file="
|
73
|
+
... img_path="flickr30k/images",
|
74
|
+
... json_file="flickr30k/final_flickr_separateGT_train.json",
|
75
75
|
... ),
|
76
76
|
... ],
|
77
77
|
... ),
|
@@ -222,7 +222,7 @@ class YOLOETrainerFromScratch(YOLOETrainer, WorldTrainerFromScratch):
|
|
222
222
|
return txt_map
|
223
223
|
LOGGER.info(f"Caching text embeddings to '{cache_path}'")
|
224
224
|
assert self.model is not None
|
225
|
-
txt_feats = self.model.get_text_pe(texts, batch, without_reprta=True, cache_clip_model=False)
|
225
|
+
txt_feats = de_parallel(self.model).get_text_pe(texts, batch, without_reprta=True, cache_clip_model=False)
|
226
226
|
txt_map = dict(zip(texts, txt_feats.squeeze(0)))
|
227
227
|
torch.save(txt_map, cache_path)
|
228
228
|
return txt_map
|
ultralytics/nn/autobackend.py
CHANGED
@@ -487,7 +487,13 @@ class AutoBackend(nn.Module):
|
|
487
487
|
# PaddlePaddle
|
488
488
|
elif paddle:
|
489
489
|
LOGGER.info(f"Loading {w} for PaddlePaddle inference...")
|
490
|
-
check_requirements(
|
490
|
+
check_requirements(
|
491
|
+
"paddlepaddle-gpu"
|
492
|
+
if torch.cuda.is_available()
|
493
|
+
else "paddlepaddle==3.0.0" # pin 3.0.0 for ARM64
|
494
|
+
if ARM64
|
495
|
+
else "paddlepaddle>=3.0.0"
|
496
|
+
)
|
491
497
|
import paddle.inference as pdi # noqa
|
492
498
|
|
493
499
|
w = Path(w)
|
ultralytics/solutions/heatmap.py
CHANGED
@@ -43,7 +43,7 @@ class ObjectCounter(BaseSolution):
|
|
43
43
|
self.in_count = 0 # Counter for objects moving inward
|
44
44
|
self.out_count = 0 # Counter for objects moving outward
|
45
45
|
self.counted_ids = [] # List of IDs of objects that have been counted
|
46
|
-
self.
|
46
|
+
self.classwise_count = defaultdict(lambda: {"IN": 0, "OUT": 0}) # Dictionary for counts, categorized by class
|
47
47
|
self.region_initialized = False # Flag indicating whether the region has been initialized
|
48
48
|
|
49
49
|
self.show_in = self.CFG["show_in"]
|
@@ -85,17 +85,17 @@ class ObjectCounter(BaseSolution):
|
|
85
85
|
# Vertical region: Compare x-coordinates to determine direction
|
86
86
|
if current_centroid[0] > prev_position[0]: # Moving right
|
87
87
|
self.in_count += 1
|
88
|
-
self.
|
88
|
+
self.classwise_count[self.names[cls]]["IN"] += 1
|
89
89
|
else: # Moving left
|
90
90
|
self.out_count += 1
|
91
|
-
self.
|
91
|
+
self.classwise_count[self.names[cls]]["OUT"] += 1
|
92
92
|
# Horizontal region: Compare y-coordinates to determine direction
|
93
93
|
elif current_centroid[1] > prev_position[1]: # Moving downward
|
94
94
|
self.in_count += 1
|
95
|
-
self.
|
95
|
+
self.classwise_count[self.names[cls]]["IN"] += 1
|
96
96
|
else: # Moving upward
|
97
97
|
self.out_count += 1
|
98
|
-
self.
|
98
|
+
self.classwise_count[self.names[cls]]["OUT"] += 1
|
99
99
|
self.counted_ids.append(track_id)
|
100
100
|
|
101
101
|
elif len(self.region) > 2: # Polygonal region
|
@@ -111,10 +111,10 @@ class ObjectCounter(BaseSolution):
|
|
111
111
|
and current_centroid[1] > prev_position[1]
|
112
112
|
): # Moving right or downward
|
113
113
|
self.in_count += 1
|
114
|
-
self.
|
114
|
+
self.classwise_count[self.names[cls]]["IN"] += 1
|
115
115
|
else: # Moving left or upward
|
116
116
|
self.out_count += 1
|
117
|
-
self.
|
117
|
+
self.classwise_count[self.names[cls]]["OUT"] += 1
|
118
118
|
self.counted_ids.append(track_id)
|
119
119
|
|
120
120
|
def display_counts(self, plot_im) -> None:
|
@@ -132,7 +132,7 @@ class ObjectCounter(BaseSolution):
|
|
132
132
|
labels_dict = {
|
133
133
|
str.capitalize(key): f"{'IN ' + str(value['IN']) if self.show_in else ''} "
|
134
134
|
f"{'OUT ' + str(value['OUT']) if self.show_out else ''}".strip()
|
135
|
-
for key, value in self.
|
135
|
+
for key, value in self.classwise_count.items()
|
136
136
|
if value["IN"] != 0 or value["OUT"] != 0 and (self.show_in or self.show_out)
|
137
137
|
}
|
138
138
|
if labels_dict:
|
@@ -190,6 +190,6 @@ class ObjectCounter(BaseSolution):
|
|
190
190
|
plot_im=plot_im,
|
191
191
|
in_count=self.in_count,
|
192
192
|
out_count=self.out_count,
|
193
|
-
classwise_count=dict(self.
|
193
|
+
classwise_count=dict(self.classwise_count),
|
194
194
|
total_tracks=len(self.track_ids),
|
195
195
|
)
|
@@ -9,14 +9,14 @@ from PIL import Image
|
|
9
9
|
|
10
10
|
from ultralytics.data.utils import IMG_FORMATS
|
11
11
|
from ultralytics.nn.text_model import build_text_model
|
12
|
-
from ultralytics.
|
12
|
+
from ultralytics.utils import LOGGER
|
13
13
|
from ultralytics.utils.checks import check_requirements
|
14
14
|
from ultralytics.utils.torch_utils import select_device
|
15
15
|
|
16
16
|
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" # Avoid OpenMP conflict on some systems
|
17
17
|
|
18
18
|
|
19
|
-
class VisualAISearch
|
19
|
+
class VisualAISearch:
|
20
20
|
"""
|
21
21
|
A semantic image search system that leverages OpenCLIP for generating high-quality image and text embeddings and
|
22
22
|
FAISS for fast similarity-based retrieval.
|
@@ -48,19 +48,18 @@ class VisualAISearch(BaseSolution):
|
|
48
48
|
|
49
49
|
def __init__(self, **kwargs: Any) -> None:
|
50
50
|
"""Initialize the VisualAISearch class with FAISS index and CLIP model."""
|
51
|
-
super().__init__(**kwargs)
|
52
51
|
check_requirements("faiss-cpu")
|
53
52
|
|
54
53
|
self.faiss = __import__("faiss")
|
55
54
|
self.faiss_index = "faiss.index"
|
56
55
|
self.data_path_npy = "paths.npy"
|
57
|
-
self.data_dir = Path(
|
58
|
-
self.device = select_device(
|
56
|
+
self.data_dir = Path(kwargs.get("data", "images"))
|
57
|
+
self.device = select_device(kwargs.get("device", "cpu"))
|
59
58
|
|
60
59
|
if not self.data_dir.exists():
|
61
60
|
from ultralytics.utils import ASSETS_URL
|
62
61
|
|
63
|
-
|
62
|
+
LOGGER.warning(f"{self.data_dir} not found. Downloading images.zip from {ASSETS_URL}/images.zip")
|
64
63
|
from ultralytics.utils.downloads import safe_download
|
65
64
|
|
66
65
|
safe_download(url=f"{ASSETS_URL}/images.zip", unzip=True, retry=3)
|
@@ -91,13 +90,13 @@ class VisualAISearch(BaseSolution):
|
|
91
90
|
"""
|
92
91
|
# Check if the FAISS index and corresponding image paths already exist
|
93
92
|
if Path(self.faiss_index).exists() and Path(self.data_path_npy).exists():
|
94
|
-
|
93
|
+
LOGGER.info("Loading existing FAISS index...")
|
95
94
|
self.index = self.faiss.read_index(self.faiss_index) # Load the FAISS index from disk
|
96
95
|
self.image_paths = np.load(self.data_path_npy) # Load the saved image path list
|
97
96
|
return # Exit the function as the index is successfully loaded
|
98
97
|
|
99
98
|
# If the index doesn't exist, start building it from scratch
|
100
|
-
|
99
|
+
LOGGER.info("Building FAISS index from images...")
|
101
100
|
vectors = [] # List to store feature vectors of images
|
102
101
|
|
103
102
|
# Iterate over all image files in the data directory
|
@@ -110,7 +109,7 @@ class VisualAISearch(BaseSolution):
|
|
110
109
|
vectors.append(self.extract_image_feature(file))
|
111
110
|
self.image_paths.append(file.name) # Store the corresponding image name
|
112
111
|
except Exception as e:
|
113
|
-
|
112
|
+
LOGGER.warning(f"Skipping {file.name}: {e}")
|
114
113
|
|
115
114
|
# If no vectors were successfully created, raise an error
|
116
115
|
if not vectors:
|
@@ -124,7 +123,7 @@ class VisualAISearch(BaseSolution):
|
|
124
123
|
self.faiss.write_index(self.index, self.faiss_index) # Save the newly built FAISS index to disk
|
125
124
|
np.save(self.data_path_npy, np.array(self.image_paths)) # Save the list of image paths to disk
|
126
125
|
|
127
|
-
|
126
|
+
LOGGER.info(f"Indexed {len(self.image_paths)} images.")
|
128
127
|
|
129
128
|
def search(self, query: str, k: int = 30, similarity_thresh: float = 0.1) -> List[str]:
|
130
129
|
"""
|
@@ -152,9 +151,9 @@ class VisualAISearch(BaseSolution):
|
|
152
151
|
]
|
153
152
|
results.sort(key=lambda x: x[1], reverse=True)
|
154
153
|
|
155
|
-
|
154
|
+
LOGGER.info("\nRanked Results:")
|
156
155
|
for name, score in results:
|
157
|
-
|
156
|
+
LOGGER.info(f" - {name} | Similarity: {score:.4f}")
|
158
157
|
|
159
158
|
return [r[0] for r in results]
|
160
159
|
|
@@ -81,60 +81,59 @@ class BaseSolution:
|
|
81
81
|
self.CFG = vars(SolutionConfig().update(**kwargs))
|
82
82
|
self.LOGGER = LOGGER # Store logger object to be used in multiple solution classes
|
83
83
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
self.
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
if
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
self.
|
135
|
-
|
136
|
-
|
137
|
-
)
|
84
|
+
check_requirements("shapely>=2.0.0")
|
85
|
+
from shapely.geometry import LineString, Point, Polygon
|
86
|
+
from shapely.prepared import prep
|
87
|
+
|
88
|
+
self.LineString = LineString
|
89
|
+
self.Polygon = Polygon
|
90
|
+
self.Point = Point
|
91
|
+
self.prep = prep
|
92
|
+
self.annotator = None # Initialize annotator
|
93
|
+
self.tracks = None
|
94
|
+
self.track_data = None
|
95
|
+
self.boxes = []
|
96
|
+
self.clss = []
|
97
|
+
self.track_ids = []
|
98
|
+
self.track_line = None
|
99
|
+
self.masks = None
|
100
|
+
self.r_s = None
|
101
|
+
self.frame_no = -1 # Only for logging
|
102
|
+
|
103
|
+
self.LOGGER.info(f"Ultralytics Solutions: ✅ {self.CFG}")
|
104
|
+
self.region = self.CFG["region"] # Store region data for other classes usage
|
105
|
+
self.line_width = self.CFG["line_width"]
|
106
|
+
|
107
|
+
# Load Model and store additional information (classes, show_conf, show_label)
|
108
|
+
if self.CFG["model"] is None:
|
109
|
+
self.CFG["model"] = "yolo11n.pt"
|
110
|
+
self.model = YOLO(self.CFG["model"])
|
111
|
+
self.names = self.model.names
|
112
|
+
self.classes = self.CFG["classes"]
|
113
|
+
self.show_conf = self.CFG["show_conf"]
|
114
|
+
self.show_labels = self.CFG["show_labels"]
|
115
|
+
self.device = self.CFG["device"]
|
116
|
+
|
117
|
+
self.track_add_args = { # Tracker additional arguments for advance configuration
|
118
|
+
k: self.CFG[k] for k in ["iou", "conf", "device", "max_det", "half", "tracker"]
|
119
|
+
} # verbose must be passed to track method; setting it False in YOLO still logs the track information.
|
120
|
+
|
121
|
+
if is_cli and self.CFG["source"] is None:
|
122
|
+
d_s = "solutions_ci_demo.mp4" if "-pose" not in self.CFG["model"] else "solution_ci_pose_demo.mp4"
|
123
|
+
self.LOGGER.warning(f"source not provided. using default source {ASSETS_URL}/{d_s}")
|
124
|
+
from ultralytics.utils.downloads import safe_download
|
125
|
+
|
126
|
+
safe_download(f"{ASSETS_URL}/{d_s}") # download source from ultralytics assets
|
127
|
+
self.CFG["source"] = d_s # set default source
|
128
|
+
|
129
|
+
# Initialize environment and region setup
|
130
|
+
self.env_check = check_imshow(warn=True)
|
131
|
+
self.track_history = defaultdict(list)
|
132
|
+
|
133
|
+
self.profilers = (
|
134
|
+
ops.Profile(device=self.device), # track
|
135
|
+
ops.Profile(device=self.device), # solution
|
136
|
+
)
|
138
137
|
|
139
138
|
def adjust_box_label(self, cls: int, conf: float, track_id: Optional[int] = None) -> Optional[str]:
|
140
139
|
"""
|
@@ -808,10 +807,10 @@ class SolutionResults:
|
|
808
807
|
filled_slots (int): The number of filled slots in a monitored area.
|
809
808
|
email_sent (bool): A flag indicating whether an email notification was sent.
|
810
809
|
total_tracks (int): The total number of tracked objects.
|
811
|
-
region_counts (Dict): The count of objects within a specific region.
|
810
|
+
region_counts (Dict[str, int]): The count of objects within a specific region.
|
812
811
|
speed_dict (Dict[str, float]): A dictionary containing speed information for tracked objects.
|
813
812
|
total_crop_objects (int): Total number of cropped objects using ObjectCropper class.
|
814
|
-
speed (Dict): Performance timing information for tracking and solution processing.
|
813
|
+
speed (Dict[str, float]): Performance timing information for tracking and solution processing.
|
815
814
|
"""
|
816
815
|
|
817
816
|
def __init__(self, **kwargs):
|
ultralytics/utils/__init__.py
CHANGED
@@ -255,11 +255,8 @@ class DataExportMixin:
|
|
255
255
|
Notes:
|
256
256
|
Requires `lxml` package to be installed.
|
257
257
|
"""
|
258
|
-
from ultralytics.utils.checks import check_requirements
|
259
|
-
|
260
|
-
check_requirements("lxml")
|
261
258
|
df = self.to_df(normalize=normalize, decimals=decimals)
|
262
|
-
return '<?xml version="1.0" encoding="utf-8"?>\n<root></root>' if df.empty else df.to_xml()
|
259
|
+
return '<?xml version="1.0" encoding="utf-8"?>\n<root></root>' if df.empty else df.to_xml(parser="etree")
|
263
260
|
|
264
261
|
def to_html(self, normalize=False, decimals=5, index=False):
|
265
262
|
"""
|
ultralytics/utils/instance.py
CHANGED
@@ -406,6 +406,8 @@ class Instances:
|
|
406
406
|
| (self.keypoints[..., 1] < 0)
|
407
407
|
| (self.keypoints[..., 1] > h)
|
408
408
|
] = 0.0
|
409
|
+
self.keypoints[..., 0] = self.keypoints[..., 0].clip(0, w)
|
410
|
+
self.keypoints[..., 1] = self.keypoints[..., 1].clip(0, h)
|
409
411
|
|
410
412
|
def remove_zero_area_boxes(self):
|
411
413
|
"""
|
ultralytics/utils/metrics.py
CHANGED
@@ -1061,7 +1061,7 @@ class DetMetrics(SimpleClass, DataExportMixin):
|
|
1061
1061
|
"""Return dictionary of computed performance metrics and statistics."""
|
1062
1062
|
return self.box.curves_results
|
1063
1063
|
|
1064
|
-
def summary(self, normalize: bool = True, decimals: int = 5) -> List[Dict[str,
|
1064
|
+
def summary(self, normalize: bool = True, decimals: int = 5) -> List[Dict[str, Any]]:
|
1065
1065
|
"""
|
1066
1066
|
Generate a summarized representation of per-class detection metrics as a list of dictionaries. Includes shared
|
1067
1067
|
scalar metrics (mAP, mAP50, mAP75) alongside precision, recall, and F1-score for each class.
|
@@ -1071,30 +1071,28 @@ class DetMetrics(SimpleClass, DataExportMixin):
|
|
1071
1071
|
decimals (int): Number of decimal places to round the metrics values to.
|
1072
1072
|
|
1073
1073
|
Returns:
|
1074
|
-
(List[Dict[str,
|
1074
|
+
(List[Dict[str, Any]]): A list of dictionaries, each representing one class with corresponding metric values.
|
1075
1075
|
|
1076
1076
|
Examples:
|
1077
1077
|
>>> results = model.val(data="coco8.yaml")
|
1078
1078
|
>>> detection_summary = results.summary()
|
1079
1079
|
>>> print(detection_summary)
|
1080
1080
|
"""
|
1081
|
-
scalars = {
|
1082
|
-
"box-map": round(self.box.map, decimals),
|
1083
|
-
"box-map50": round(self.box.map50, decimals),
|
1084
|
-
"box-map75": round(self.box.map75, decimals),
|
1085
|
-
}
|
1086
1081
|
per_class = {
|
1087
|
-
"
|
1088
|
-
"
|
1089
|
-
"
|
1082
|
+
"Box-P": self.box.p,
|
1083
|
+
"Box-R": self.box.r,
|
1084
|
+
"Box-F1": self.box.f1,
|
1090
1085
|
}
|
1091
1086
|
return [
|
1092
1087
|
{
|
1093
|
-
"
|
1088
|
+
"Class": self.names[self.ap_class_index[i]],
|
1089
|
+
"Images": self.nt_per_image[self.ap_class_index[i]],
|
1090
|
+
"Instances": self.nt_per_class[self.ap_class_index[i]],
|
1094
1091
|
**{k: round(v[i], decimals) for k, v in per_class.items()},
|
1095
|
-
|
1092
|
+
"mAP50": round(self.class_result(i)[2], decimals),
|
1093
|
+
"mAP50-95": round(self.class_result(i)[3], decimals),
|
1096
1094
|
}
|
1097
|
-
for i in range(len(per_class["
|
1095
|
+
for i in range(len(per_class["Box-P"]))
|
1098
1096
|
]
|
1099
1097
|
|
1100
1098
|
|
@@ -1196,7 +1194,7 @@ class SegmentMetrics(DetMetrics):
|
|
1196
1194
|
"""Return dictionary of computed performance metrics and statistics."""
|
1197
1195
|
return DetMetrics.curves_results.fget(self) + self.seg.curves_results
|
1198
1196
|
|
1199
|
-
def summary(self, normalize: bool = True, decimals: int = 5) -> List[Dict[str,
|
1197
|
+
def summary(self, normalize: bool = True, decimals: int = 5) -> List[Dict[str, Any]]:
|
1200
1198
|
"""
|
1201
1199
|
Generate a summarized representation of per-class segmentation metrics as a list of dictionaries. Includes both
|
1202
1200
|
box and mask scalar metrics (mAP, mAP50, mAP75) alongside precision, recall, and F1-score for each class.
|
@@ -1206,26 +1204,21 @@ class SegmentMetrics(DetMetrics):
|
|
1206
1204
|
decimals (int): Number of decimal places to round the metrics values to.
|
1207
1205
|
|
1208
1206
|
Returns:
|
1209
|
-
(List[Dict[str,
|
1207
|
+
(List[Dict[str, Any]]): A list of dictionaries, each representing one class with corresponding metric values.
|
1210
1208
|
|
1211
1209
|
Examples:
|
1212
1210
|
>>> results = model.val(data="coco8-seg.yaml")
|
1213
1211
|
>>> seg_summary = results.summary(decimals=4)
|
1214
1212
|
>>> print(seg_summary)
|
1215
1213
|
"""
|
1216
|
-
scalars = {
|
1217
|
-
"mask-map": round(self.seg.map, decimals),
|
1218
|
-
"mask-map50": round(self.seg.map50, decimals),
|
1219
|
-
"mask-map75": round(self.seg.map75, decimals),
|
1220
|
-
}
|
1221
1214
|
per_class = {
|
1222
|
-
"
|
1223
|
-
"
|
1224
|
-
"
|
1215
|
+
"Mask-P": self.seg.p,
|
1216
|
+
"Mask-R": self.seg.r,
|
1217
|
+
"Mask-F1": self.seg.f1,
|
1225
1218
|
}
|
1226
1219
|
summary = DetMetrics.summary(self, normalize, decimals) # get box summary
|
1227
1220
|
for i, s in enumerate(summary):
|
1228
|
-
s.update({**{k: round(v[i], decimals) for k, v in per_class.items()}
|
1221
|
+
s.update({**{k: round(v[i], decimals) for k, v in per_class.items()}})
|
1229
1222
|
return summary
|
1230
1223
|
|
1231
1224
|
|
@@ -1340,7 +1333,7 @@ class PoseMetrics(DetMetrics):
|
|
1340
1333
|
"""Return dictionary of computed performance metrics and statistics."""
|
1341
1334
|
return DetMetrics.curves_results.fget(self) + self.pose.curves_results
|
1342
1335
|
|
1343
|
-
def summary(self, normalize: bool = True, decimals: int = 5) -> List[Dict[str,
|
1336
|
+
def summary(self, normalize: bool = True, decimals: int = 5) -> List[Dict[str, Any]]:
|
1344
1337
|
"""
|
1345
1338
|
Generate a summarized representation of per-class pose metrics as a list of dictionaries. Includes both box and
|
1346
1339
|
pose scalar metrics (mAP, mAP50, mAP75) alongside precision, recall, and F1-score for each class.
|
@@ -1350,26 +1343,21 @@ class PoseMetrics(DetMetrics):
|
|
1350
1343
|
decimals (int): Number of decimal places to round the metrics values to.
|
1351
1344
|
|
1352
1345
|
Returns:
|
1353
|
-
(List[Dict[str,
|
1346
|
+
(List[Dict[str, Any]]): A list of dictionaries, each representing one class with corresponding metric values.
|
1354
1347
|
|
1355
1348
|
Examples:
|
1356
1349
|
>>> results = model.val(data="coco8-pose.yaml")
|
1357
1350
|
>>> pose_summary = results.summary(decimals=4)
|
1358
1351
|
>>> print(pose_summary)
|
1359
1352
|
"""
|
1360
|
-
scalars = {
|
1361
|
-
"pose-map": round(self.pose.map, decimals),
|
1362
|
-
"pose-map50": round(self.pose.map50, decimals),
|
1363
|
-
"pose-map75": round(self.pose.map75, decimals),
|
1364
|
-
}
|
1365
1353
|
per_class = {
|
1366
|
-
"
|
1367
|
-
"
|
1368
|
-
"
|
1354
|
+
"Pose-P": self.pose.p,
|
1355
|
+
"Pose-R": self.pose.r,
|
1356
|
+
"Pose-F1": self.pose.f1,
|
1369
1357
|
}
|
1370
1358
|
summary = DetMetrics.summary(self, normalize, decimals) # get box summary
|
1371
1359
|
for i, s in enumerate(summary):
|
1372
|
-
s.update({**{k: round(v[i], decimals) for k, v in per_class.items()}
|
1360
|
+
s.update({**{k: round(v[i], decimals) for k, v in per_class.items()}})
|
1373
1361
|
return summary
|
1374
1362
|
|
1375
1363
|
|
@@ -1445,7 +1433,7 @@ class ClassifyMetrics(SimpleClass, DataExportMixin):
|
|
1445
1433
|
>>> classify_summary = results.summary(decimals=4)
|
1446
1434
|
>>> print(classify_summary)
|
1447
1435
|
"""
|
1448
|
-
return [{"
|
1436
|
+
return [{"top1_acc": round(self.top1, decimals), "top5_acc": round(self.top5, decimals)}]
|
1449
1437
|
|
1450
1438
|
|
1451
1439
|
class OBBMetrics(DetMetrics):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ultralytics
|
3
|
-
Version: 8.3.
|
3
|
+
Version: 8.3.161
|
4
4
|
Summary: Ultralytics YOLO 🚀 for SOTA object detection, multi-object tracking, instance segmentation, pose estimation and image classification.
|
5
5
|
Author-email: Glenn Jocher <glenn.jocher@ultralytics.com>, Jing Qiu <jing.qiu@ultralytics.com>
|
6
6
|
Maintainer-email: Ultralytics <hello@ultralytics.com>
|