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.
Files changed (62) hide show
  1. tests/test_python.py +2 -1
  2. ultralytics/__init__.py +1 -1
  3. ultralytics/cfg/__init__.py +0 -2
  4. ultralytics/cfg/datasets/Argoverse.yaml +1 -1
  5. ultralytics/cfg/datasets/DOTAv1.5.yaml +1 -1
  6. ultralytics/cfg/datasets/DOTAv1.yaml +1 -1
  7. ultralytics/cfg/datasets/GlobalWheat2020.yaml +1 -1
  8. ultralytics/cfg/datasets/HomeObjects-3K.yaml +1 -1
  9. ultralytics/cfg/datasets/ImageNet.yaml +1 -1
  10. ultralytics/cfg/datasets/Objects365.yaml +1 -1
  11. ultralytics/cfg/datasets/SKU-110K.yaml +1 -1
  12. ultralytics/cfg/datasets/VOC.yaml +1 -1
  13. ultralytics/cfg/datasets/VisDrone.yaml +6 -3
  14. ultralytics/cfg/datasets/african-wildlife.yaml +1 -1
  15. ultralytics/cfg/datasets/brain-tumor.yaml +1 -1
  16. ultralytics/cfg/datasets/carparts-seg.yaml +1 -1
  17. ultralytics/cfg/datasets/coco-pose.yaml +1 -1
  18. ultralytics/cfg/datasets/coco.yaml +1 -1
  19. ultralytics/cfg/datasets/coco128-seg.yaml +1 -1
  20. ultralytics/cfg/datasets/coco128.yaml +1 -1
  21. ultralytics/cfg/datasets/coco8-grayscale.yaml +1 -1
  22. ultralytics/cfg/datasets/coco8-multispectral.yaml +1 -1
  23. ultralytics/cfg/datasets/coco8-pose.yaml +1 -1
  24. ultralytics/cfg/datasets/coco8-seg.yaml +1 -1
  25. ultralytics/cfg/datasets/coco8.yaml +1 -1
  26. ultralytics/cfg/datasets/crack-seg.yaml +1 -1
  27. ultralytics/cfg/datasets/dog-pose.yaml +1 -1
  28. ultralytics/cfg/datasets/dota8-multispectral.yaml +1 -1
  29. ultralytics/cfg/datasets/dota8.yaml +1 -1
  30. ultralytics/cfg/datasets/hand-keypoints.yaml +1 -1
  31. ultralytics/cfg/datasets/lvis.yaml +1 -1
  32. ultralytics/cfg/datasets/medical-pills.yaml +1 -1
  33. ultralytics/cfg/datasets/open-images-v7.yaml +1 -1
  34. ultralytics/cfg/datasets/package-seg.yaml +1 -1
  35. ultralytics/cfg/datasets/signature.yaml +1 -1
  36. ultralytics/cfg/datasets/tiger-pose.yaml +1 -1
  37. ultralytics/cfg/datasets/xView.yaml +1 -1
  38. ultralytics/data/augment.py +8 -8
  39. ultralytics/data/converter.py +3 -5
  40. ultralytics/data/dataset.py +1 -1
  41. ultralytics/data/split.py +1 -1
  42. ultralytics/engine/exporter.py +11 -2
  43. ultralytics/engine/model.py +2 -0
  44. ultralytics/engine/results.py +1 -6
  45. ultralytics/models/yolo/model.py +25 -24
  46. ultralytics/models/yolo/world/train.py +1 -1
  47. ultralytics/models/yolo/world/train_world.py +6 -6
  48. ultralytics/models/yolo/yoloe/train.py +1 -1
  49. ultralytics/nn/autobackend.py +7 -1
  50. ultralytics/solutions/heatmap.py +1 -1
  51. ultralytics/solutions/object_counter.py +9 -9
  52. ultralytics/solutions/similarity_search.py +11 -12
  53. ultralytics/solutions/solutions.py +55 -56
  54. ultralytics/utils/__init__.py +1 -4
  55. ultralytics/utils/instance.py +2 -0
  56. ultralytics/utils/metrics.py +24 -36
  57. {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/METADATA +1 -1
  58. {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/RECORD +62 -62
  59. {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/WHEEL +0 -0
  60. {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/entry_points.txt +0 -0
  61. {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/licenses/LICENSE +0 -0
  62. {ultralytics-8.3.159.dist-info → ultralytics-8.3.161.dist-info}/top_level.txt +0 -0
@@ -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
- self.predictor = (predictor or self._smart_load("predictor"))(
410
- overrides={
411
- "task": self.model.task,
412
- "mode": "predict",
413
- "save": False,
414
- "verbose": refer_image is None,
415
- "batch": 1,
416
- },
417
- _callbacks=self.callbacks,
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
- self.predictor.setup_model(model=self.model)
431
-
432
- if refer_image is None and source is not None:
433
- dataset = load_inference_source(source)
434
- if dataset.mode in {"video", "stream"}:
435
- # NOTE: set the first frame as refer image for videos/streams inference
436
- refer_image = next(iter(dataset))[1][0]
437
- if refer_image is not None and len(visual_prompts):
438
- vpe = self.predictor.get_vpe(refer_image)
439
- self.model.set_classes(self.model.names, vpe)
440
- self.task = "segment" if isinstance(self.predictor, yolo.segment.SegmentationPredictor) else "detect"
441
- self.predictor = None # reset predictor
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="../datasets/flickr30k/images",
39
- ... json_file="../datasets/flickr30k/final_flickr_separateGT_train.json",
38
+ ... img_path="flickr30k/images",
39
+ ... json_file="flickr30k/final_flickr_separateGT_train.json",
40
40
  ... ),
41
41
  ... dict(
42
- ... img_path="../datasets/GQA/images",
43
- ... json_file="../datasets/GQA/final_mixed_train_no_coco.json",
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="../datasets/flickr30k/images",
74
- ... json_file="../datasets/flickr30k/final_flickr_separateGT_train.json",
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
@@ -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("paddlepaddle-gpu" if cuda else "paddlepaddle>=3.0.0")
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)
@@ -124,6 +124,6 @@ class Heatmap(ObjectCounter):
124
124
  plot_im=plot_im,
125
125
  in_count=self.in_count,
126
126
  out_count=self.out_count,
127
- classwise_count=dict(self.classwise_counts),
127
+ classwise_count=dict(self.classwise_count),
128
128
  total_tracks=len(self.track_ids),
129
129
  )
@@ -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.classwise_counts = defaultdict(lambda: {"IN": 0, "OUT": 0}) # Dictionary for counts, categorized by class
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.classwise_counts[self.names[cls]]["IN"] += 1
88
+ self.classwise_count[self.names[cls]]["IN"] += 1
89
89
  else: # Moving left
90
90
  self.out_count += 1
91
- self.classwise_counts[self.names[cls]]["OUT"] += 1
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.classwise_counts[self.names[cls]]["IN"] += 1
95
+ self.classwise_count[self.names[cls]]["IN"] += 1
96
96
  else: # Moving upward
97
97
  self.out_count += 1
98
- self.classwise_counts[self.names[cls]]["OUT"] += 1
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.classwise_counts[self.names[cls]]["IN"] += 1
114
+ self.classwise_count[self.names[cls]]["IN"] += 1
115
115
  else: # Moving left or upward
116
116
  self.out_count += 1
117
- self.classwise_counts[self.names[cls]]["OUT"] += 1
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.classwise_counts.items()
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.classwise_counts),
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.solutions.solutions import BaseSolution
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(BaseSolution):
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(self.CFG["data"])
58
- self.device = select_device(self.CFG["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
- self.LOGGER.warning(f"{self.data_dir} not found. Downloading images.zip from {ASSETS_URL}/images.zip")
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
- self.LOGGER.info("Loading existing FAISS index...")
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
- self.LOGGER.info("Building FAISS index from images...")
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
- self.LOGGER.warning(f"Skipping {file.name}: {e}")
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
- self.LOGGER.info(f"Indexed {len(self.image_paths)} images.")
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
- self.LOGGER.info("\nRanked Results:")
154
+ LOGGER.info("\nRanked Results:")
156
155
  for name, score in results:
157
- self.LOGGER.info(f" - {name} | Similarity: {score:.4f}")
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
- if self.__class__.__name__ != "VisualAISearch":
85
- check_requirements("shapely>=2.0.0")
86
- from shapely.geometry import LineString, Point, Polygon
87
- from shapely.prepared import prep
88
-
89
- self.LineString = LineString
90
- self.Polygon = Polygon
91
- self.Point = Point
92
- self.prep = prep
93
- self.annotator = None # Initialize annotator
94
- self.tracks = None
95
- self.track_data = None
96
- self.boxes = []
97
- self.clss = []
98
- self.track_ids = []
99
- self.track_line = None
100
- self.masks = None
101
- self.r_s = None
102
- self.frame_no = -1 # Only for logging
103
-
104
- self.LOGGER.info(f"Ultralytics Solutions: ✅ {self.CFG}")
105
- self.region = self.CFG["region"] # Store region data for other classes usage
106
- self.line_width = self.CFG["line_width"]
107
-
108
- # Load Model and store additional information (classes, show_conf, show_label)
109
- if self.CFG["model"] is None:
110
- self.CFG["model"] = "yolo11n.pt"
111
- self.model = YOLO(self.CFG["model"])
112
- self.names = self.model.names
113
- self.classes = self.CFG["classes"]
114
- self.show_conf = self.CFG["show_conf"]
115
- self.show_labels = self.CFG["show_labels"]
116
- self.device = self.CFG["device"]
117
-
118
- self.track_add_args = { # Tracker additional arguments for advance configuration
119
- k: self.CFG[k] for k in ["iou", "conf", "device", "max_det", "half", "tracker"]
120
- } # verbose must be passed to track method; setting it False in YOLO still logs the track information.
121
-
122
- if is_cli and self.CFG["source"] is None:
123
- d_s = "solutions_ci_demo.mp4" if "-pose" not in self.CFG["model"] else "solution_ci_pose_demo.mp4"
124
- self.LOGGER.warning(f"source not provided. using default source {ASSETS_URL}/{d_s}")
125
- from ultralytics.utils.downloads import safe_download
126
-
127
- safe_download(f"{ASSETS_URL}/{d_s}") # download source from ultralytics assets
128
- self.CFG["source"] = d_s # set default source
129
-
130
- # Initialize environment and region setup
131
- self.env_check = check_imshow(warn=True)
132
- self.track_history = defaultdict(list)
133
-
134
- self.profilers = (
135
- ops.Profile(device=self.device), # track
136
- ops.Profile(device=self.device), # solution
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):
@@ -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
  """
@@ -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
  """
@@ -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, Union[str, float]]]:
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, Union[str, float]]]): A list of dictionaries, each representing one class with corresponding metric values.
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
- "box-p": self.box.p,
1088
- "box-r": self.box.r,
1089
- "box-f1": self.box.f1,
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
- "class_name": self.names[self.ap_class_index[i]],
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
- **scalars,
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["box-p"]))
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, Union[str, float]]]:
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, Union[str, float]]]): A list of dictionaries, each representing one class with corresponding metric values.
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
- "mask-p": self.seg.p,
1223
- "mask-r": self.seg.r,
1224
- "mask-f1": self.seg.f1,
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()}, **scalars})
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, Union[str, float]]]:
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, Union[str, float]]]): A list of dictionaries, each representing one class with corresponding metric values.
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
- "pose-p": self.pose.p,
1367
- "pose-r": self.pose.r,
1368
- "pose-f1": self.pose.f1,
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()}, **scalars})
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 [{"classify-top1": round(self.top1, decimals), "classify-top5": round(self.top5, decimals)}]
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.159
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>