ultralytics 8.3.4__py3-none-any.whl → 8.3.6__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.

Potentially problematic release.


This version of ultralytics might be problematic. Click here for more details.

tests/test_solutions.py CHANGED
@@ -19,8 +19,8 @@ def test_major_solutions():
19
19
  cap = cv2.VideoCapture("solutions_ci_demo.mp4")
20
20
  assert cap.isOpened(), "Error reading video file"
21
21
  region_points = [(20, 400), (1080, 404), (1080, 360), (20, 360)]
22
- counter = solutions.ObjectCounter(reg_pts=region_points, names=names, view_img=False)
23
- heatmap = solutions.Heatmap(colormap=cv2.COLORMAP_PARULA, names=names, view_img=False)
22
+ counter = solutions.ObjectCounter(region=region_points, model="yolo11n.pt", show=False)
23
+ heatmap = solutions.Heatmap(colormap=cv2.COLORMAP_PARULA, model="yolo11n.pt", show=False)
24
24
  speed = solutions.SpeedEstimator(reg_pts=region_points, names=names, view_img=False)
25
25
  queue = solutions.QueueManager(names=names, reg_pts=region_points, view_img=False)
26
26
  while cap.isOpened():
@@ -29,8 +29,8 @@ def test_major_solutions():
29
29
  break
30
30
  original_im0 = im0.copy()
31
31
  tracks = model.track(im0, persist=True, show=False)
32
- _ = counter.start_counting(original_im0.copy(), tracks)
33
- _ = heatmap.generate_heatmap(original_im0.copy(), tracks)
32
+ _ = counter.count(original_im0.copy())
33
+ _ = heatmap.generate_heatmap(original_im0.copy())
34
34
  _ = speed.estimate_speed(original_im0.copy(), tracks)
35
35
  _ = queue.process_queue(original_im0.copy(), tracks)
36
36
  cap.release()
@@ -41,16 +41,14 @@ def test_major_solutions():
41
41
  def test_aigym():
42
42
  """Test the workouts monitoring solution."""
43
43
  safe_download(url=WORKOUTS_SOLUTION_DEMO)
44
- model = YOLO("yolo11n-pose.pt")
45
44
  cap = cv2.VideoCapture("solution_ci_pose_demo.mp4")
46
45
  assert cap.isOpened(), "Error reading video file"
47
- gym_object = solutions.AIGym(line_thickness=2, pose_type="squat", kpts_to_check=[5, 11, 13])
46
+ gym = solutions.AIGym(line_width=2, kpts=[5, 11, 13])
48
47
  while cap.isOpened():
49
48
  success, im0 = cap.read()
50
49
  if not success:
51
50
  break
52
- results = model.track(im0, verbose=False)
53
- _ = gym_object.start_counting(im0, results)
51
+ _ = gym.monitor(im0)
54
52
  cap.release()
55
53
  cv2.destroyAllWindows()
56
54
 
ultralytics/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ultralytics YOLO 🚀, AGPL-3.0 license
2
2
 
3
- __version__ = "8.3.4"
3
+ __version__ = "8.3.6"
4
4
 
5
5
  import os
6
6
 
@@ -1,7 +1,7 @@
1
1
  # Ultralytics YOLO 🚀, AGPL-3.0 license
2
2
  # Default training settings and hyperparameters for medium-augmentation COCO training
3
3
 
4
- task: detect # (str) YOLO task, i.e. detect, segment, classify, pose
4
+ task: detect # (str) YOLO task, i.e. detect, segment, classify, pose, obb
5
5
  mode: train # (str) YOLO mode, i.e. train, val, predict, export, track, benchmark
6
6
 
7
7
  # Train settings -------------------------------------------------------------------------------------------------------
@@ -0,0 +1,16 @@
1
+ # Ultralytics YOLO 🚀, AGPL-3.0 license
2
+
3
+ # Configuration for Ultralytics Solutions
4
+
5
+ model: "yolo11n.pt" # The Ultralytics YOLO11 model to be used (e.g., yolo11n.pt for YOLO11 nano version)
6
+
7
+ region: # Object counting, queue or speed estimation region points
8
+ line_width: 2 # Thickness of the lines used to draw regions on the image/video frames
9
+ show: True # Flag to control whether to display output image or not
10
+ show_in: True # Flag to display objects moving *into* the defined region
11
+ show_out: True # Flag to display objects moving *out of* the defined region
12
+ classes: # To count specific classes
13
+ up_angle: 145.0 # Workouts up_angle for counts, 145.0 is default value
14
+ down_angle: 90 # Workouts down_angle for counts, 90 is default value
15
+ kpts: [6, 8, 10] # Keypoints for workouts monitoring
16
+ colormap: # Colormap for heatmap
ultralytics/data/base.py CHANGED
@@ -90,13 +90,15 @@ class BaseDataset(Dataset):
90
90
  self.ims, self.im_hw0, self.im_hw = [None] * self.ni, [None] * self.ni, [None] * self.ni
91
91
  self.npy_files = [Path(f).with_suffix(".npy") for f in self.im_files]
92
92
  self.cache = cache.lower() if isinstance(cache, str) else "ram" if cache is True else None
93
- if (self.cache == "ram" and self.check_cache_ram()) or self.cache == "disk":
94
- if self.cache == "ram" and hyp.deterministic:
93
+ if self.cache == "ram" and self.check_cache_ram():
94
+ if hyp.deterministic:
95
95
  LOGGER.warning(
96
96
  "WARNING ⚠️ cache='ram' may produce non-deterministic training results. "
97
97
  "Consider cache='disk' as a deterministic alternative if your disk space allows."
98
98
  )
99
99
  self.cache_images()
100
+ elif self.cache == "disk" and self.check_cache_disk():
101
+ self.cache_images()
100
102
 
101
103
  # Transforms
102
104
  self.transforms = self.build_transforms(hyp=hyp)
@@ -206,25 +208,55 @@ class BaseDataset(Dataset):
206
208
  if not f.exists():
207
209
  np.save(f.as_posix(), cv2.imread(self.im_files[i]), allow_pickle=False)
208
210
 
211
+ def check_cache_disk(self, safety_margin=0.5):
212
+ """Check image caching requirements vs available disk space."""
213
+ import shutil
214
+
215
+ b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
216
+ n = min(self.ni, 30) # extrapolate from 30 random images
217
+ for _ in range(n):
218
+ im_file = random.choice(self.im_files)
219
+ im = cv2.imread(im_file)
220
+ if im is None:
221
+ continue
222
+ b += im.nbytes
223
+ if not os.access(Path(im_file).parent, os.W_OK):
224
+ self.cache = None
225
+ LOGGER.info(f"{self.prefix}Skipping caching images to disk, directory not writeable ⚠️")
226
+ return False
227
+ disk_required = b * self.ni / n * (1 + safety_margin) # bytes required to cache dataset to disk
228
+ total, used, free = shutil.disk_usage(Path(self.im_files[0]).parent)
229
+ if disk_required > free:
230
+ self.cache = None
231
+ LOGGER.info(
232
+ f"{self.prefix}{disk_required / gb:.1f}GB disk space required, "
233
+ f"with {int(safety_margin * 100)}% safety margin but only "
234
+ f"{free / gb:.1f}/{total / gb:.1f}GB free, not caching images to disk ⚠️"
235
+ )
236
+ return False
237
+ return True
238
+
209
239
  def check_cache_ram(self, safety_margin=0.5):
210
240
  """Check image caching requirements vs available memory."""
211
241
  b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
212
242
  n = min(self.ni, 30) # extrapolate from 30 random images
213
243
  for _ in range(n):
214
244
  im = cv2.imread(random.choice(self.im_files)) # sample image
245
+ if im is None:
246
+ continue
215
247
  ratio = self.imgsz / max(im.shape[0], im.shape[1]) # max(h, w) # ratio
216
248
  b += im.nbytes * ratio**2
217
249
  mem_required = b * self.ni / n * (1 + safety_margin) # GB required to cache dataset into RAM
218
250
  mem = psutil.virtual_memory()
219
- success = mem_required < mem.available # to cache or not to cache, that is the question
220
- if not success:
251
+ if mem_required > mem.available:
221
252
  self.cache = None
222
253
  LOGGER.info(
223
254
  f"{self.prefix}{mem_required / gb:.1f}GB RAM required to cache images "
224
255
  f"with {int(safety_margin * 100)}% safety margin but only "
225
256
  f"{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, not caching images ⚠️"
226
257
  )
227
- return success
258
+ return False
259
+ return True
228
260
 
229
261
  def set_rectangle(self):
230
262
  """Sets the shape of bounding boxes for YOLO detections as rectangles."""
ultralytics/data/utils.py CHANGED
@@ -216,7 +216,7 @@ def polygons2masks_overlap(imgsz, segments, downsample_ratio=1):
216
216
  ms = []
217
217
  for si in range(len(segments)):
218
218
  mask = polygon2mask(imgsz, [segments[si].reshape(-1)], downsample_ratio=downsample_ratio, color=1)
219
- ms.append(mask)
219
+ ms.append(mask.astype(masks.dtype))
220
220
  areas.append(mask.sum())
221
221
  areas = np.asarray(areas)
222
222
  index = np.argsort(-areas)
@@ -452,12 +452,12 @@ class HUBDatasetStats:
452
452
  path = Path(path).resolve()
453
453
  LOGGER.info(f"Starting HUB dataset checks for {path}....")
454
454
 
455
- self.task = task # detect, segment, pose, classify
455
+ self.task = task # detect, segment, pose, classify, obb
456
456
  if self.task == "classify":
457
457
  unzip_dir = unzip_file(path)
458
458
  data = check_cls_dataset(unzip_dir)
459
459
  data["path"] = unzip_dir
460
- else: # detect, segment, pose
460
+ else: # detect, segment, pose, obb
461
461
  _, data_dir, yaml_path = self._unzip(Path(path))
462
462
  try:
463
463
  # Load YAML with checks
@@ -183,11 +183,10 @@ class Exporter:
183
183
 
184
184
  # Get the closest match if format is invalid
185
185
  matches = difflib.get_close_matches(fmt, fmts, n=1, cutoff=0.6) # 60% similarity required to match
186
- if matches:
187
- LOGGER.warning(f"WARNING ⚠️ Invalid export format='{fmt}', updating to format='{matches[0]}'")
188
- fmt = matches[0]
189
- else:
186
+ if not matches:
190
187
  raise ValueError(f"Invalid export format='{fmt}'. Valid formats are {fmts}")
188
+ LOGGER.warning(f"WARNING ⚠️ Invalid export format='{fmt}', updating to format='{matches[0]}'")
189
+ fmt = matches[0]
191
190
  flags = [x == fmt for x in fmts]
192
191
  if sum(flags) != 1:
193
192
  raise ValueError(f"Invalid export format='{fmt}'. Valid formats are {fmts}")
@@ -469,11 +469,11 @@ class BaseTrainer:
469
469
 
470
470
  if RANK in {-1, 0}:
471
471
  # Do final val with best.pt
472
- LOGGER.info(
473
- f"\n{epoch - self.start_epoch + 1} epochs completed in "
474
- f"{(time.time() - self.train_time_start) / 3600:.3f} hours."
475
- )
472
+ epochs = epoch - self.start_epoch + 1 # total training epochs
473
+ seconds = time.time() - self.train_time_start # total training seconds
474
+ LOGGER.info(f"\n{epochs} epochs completed in {seconds / 3600:.3f} hours.")
476
475
  self.final_eval()
476
+ self.validator.metrics.training = {"epochs": epochs, "seconds": seconds} # add training speed
477
477
  if self.args.plots:
478
478
  self.plot_metrics()
479
479
  self.run_callbacks("on_train_end")
@@ -119,6 +119,8 @@ class BaseValidator:
119
119
  self.args.plots &= trainer.stopper.possible_stop or (trainer.epoch == trainer.epochs - 1)
120
120
  model.eval()
121
121
  else:
122
+ if str(self.args.model).endswith(".yaml"):
123
+ LOGGER.warning("WARNING ⚠️ validating an untrained model YAML will result in 0 mAP.")
122
124
  callbacks.add_integration_callbacks(self)
123
125
  model = AutoBackend(
124
126
  weights=model or self.args.model,
@@ -1,127 +1,79 @@
1
1
  # Ultralytics YOLO 🚀, AGPL-3.0 license
2
2
 
3
- import cv2
4
-
5
- from ultralytics.utils.checks import check_imshow
3
+ from ultralytics.solutions.solutions import BaseSolution # Import a parent class
6
4
  from ultralytics.utils.plotting import Annotator
7
5
 
8
6
 
9
- class AIGym:
7
+ class AIGym(BaseSolution):
10
8
  """A class to manage the gym steps of people in a real-time video stream based on their poses."""
11
9
 
12
- def __init__(
13
- self,
14
- kpts_to_check,
15
- line_thickness=2,
16
- view_img=False,
17
- pose_up_angle=145.0,
18
- pose_down_angle=90.0,
19
- pose_type="pullup",
20
- ):
10
+ def __init__(self, **kwargs):
11
+ """Initialization function for AiGYM class, a child class of BaseSolution class, can be used for workouts
12
+ monitoring.
21
13
  """
22
- Initializes the AIGym class with the specified parameters.
23
-
24
- Args:
25
- kpts_to_check (list): Indices of keypoints to check.
26
- line_thickness (int, optional): Thickness of the lines drawn. Defaults to 2.
27
- view_img (bool, optional): Flag to display the image. Defaults to False.
28
- pose_up_angle (float, optional): Angle threshold for the 'up' pose. Defaults to 145.0.
29
- pose_down_angle (float, optional): Angle threshold for the 'down' pose. Defaults to 90.0.
30
- pose_type (str, optional): Type of pose to detect ('pullup', 'pushup', 'abworkout'). Defaults to "pullup".
14
+ # Check if the model name ends with '-pose'
15
+ if "model" in kwargs and "-pose" not in kwargs["model"]:
16
+ kwargs["model"] = "yolo11n-pose.pt"
17
+ elif "model" not in kwargs:
18
+ kwargs["model"] = "yolo11n-pose.pt"
19
+
20
+ super().__init__(**kwargs)
21
+ self.count = [] # List for counts, necessary where there are multiple objects in frame
22
+ self.angle = [] # List for angle, necessary where there are multiple objects in frame
23
+ self.stage = [] # List for stage, necessary where there are multiple objects in frame
24
+
25
+ # Extract details from CFG single time for usage later
26
+ self.initial_stage = None
27
+ self.up_angle = float(self.CFG["up_angle"]) # Pose up predefined angle to consider up pose
28
+ self.down_angle = float(self.CFG["down_angle"]) # Pose down predefined angle to consider down pose
29
+ self.kpts = self.CFG["kpts"] # User selected kpts of workouts storage for further usage
30
+ self.lw = self.CFG["line_width"] # Store line_width for usage
31
+
32
+ def monitor(self, im0):
31
33
  """
32
- # Image and line thickness
33
- self.im0 = None
34
- self.tf = line_thickness
35
-
36
- # Keypoints and count information
37
- self.keypoints = None
38
- self.poseup_angle = pose_up_angle
39
- self.posedown_angle = pose_down_angle
40
- self.threshold = 0.001
41
-
42
- # Store stage, count and angle information
43
- self.angle = None
44
- self.count = None
45
- self.stage = None
46
- self.pose_type = pose_type
47
- self.kpts_to_check = kpts_to_check
48
-
49
- # Visual Information
50
- self.view_img = view_img
51
- self.annotator = None
52
-
53
- # Check if environment supports imshow
54
- self.env_check = check_imshow(warn=True)
55
- self.count = []
56
- self.angle = []
57
- self.stage = []
58
-
59
- def start_counting(self, im0, results):
60
- """
61
- Function used to count the gym steps.
34
+ Monitor the workouts using Ultralytics YOLOv8 Pose Model: https://docs.ultralytics.com/tasks/pose/.
62
35
 
63
36
  Args:
64
- im0 (ndarray): Current frame from the video stream.
65
- results (list): Pose estimation data.
37
+ im0 (ndarray): The input image that will be used for processing
38
+ Returns
39
+ im0 (ndarray): The processed image for more usage
66
40
  """
67
- self.im0 = im0
68
-
69
- if not len(results[0]):
70
- return self.im0
71
-
72
- if len(results[0]) > len(self.count):
73
- new_human = len(results[0]) - len(self.count)
74
- self.count += [0] * new_human
75
- self.angle += [0] * new_human
76
- self.stage += ["-"] * new_human
77
-
78
- self.keypoints = results[0].keypoints.data
79
- self.annotator = Annotator(im0, line_width=self.tf)
80
-
81
- for ind, k in enumerate(reversed(self.keypoints)):
82
- # Estimate angle and draw specific points based on pose type
83
- if self.pose_type in {"pushup", "pullup", "abworkout", "squat"}:
84
- self.angle[ind] = self.annotator.estimate_pose_angle(
85
- k[int(self.kpts_to_check[0])].cpu(),
86
- k[int(self.kpts_to_check[1])].cpu(),
87
- k[int(self.kpts_to_check[2])].cpu(),
88
- )
89
- self.im0 = self.annotator.draw_specific_points(k, self.kpts_to_check, shape=(640, 640), radius=10)
90
-
91
- # Check and update pose stages and counts based on angle
92
- if self.pose_type in {"abworkout", "pullup"}:
93
- if self.angle[ind] > self.poseup_angle:
94
- self.stage[ind] = "down"
95
- if self.angle[ind] < self.posedown_angle and self.stage[ind] == "down":
96
- self.stage[ind] = "up"
97
- self.count[ind] += 1
98
-
99
- elif self.pose_type in {"pushup", "squat"}:
100
- if self.angle[ind] > self.poseup_angle:
101
- self.stage[ind] = "up"
102
- if self.angle[ind] < self.posedown_angle and self.stage[ind] == "up":
103
- self.stage[ind] = "down"
41
+ # Extract tracks
42
+ tracks = self.model.track(source=im0, persist=True, classes=self.CFG["classes"])[0]
43
+
44
+ if tracks.boxes.id is not None:
45
+ # Extract and check keypoints
46
+ if len(tracks) > len(self.count):
47
+ new_human = len(tracks) - len(self.count)
48
+ self.angle += [0] * new_human
49
+ self.count += [0] * new_human
50
+ self.stage += ["-"] * new_human
51
+
52
+ # Initialize annotator
53
+ self.annotator = Annotator(im0, line_width=self.lw)
54
+
55
+ # Enumerate over keypoints
56
+ for ind, k in enumerate(reversed(tracks.keypoints.data)):
57
+ # Get keypoints and estimate the angle
58
+ kpts = [k[int(self.kpts[i])].cpu() for i in range(3)]
59
+ self.angle[ind] = self.annotator.estimate_pose_angle(*kpts)
60
+ im0 = self.annotator.draw_specific_points(k, self.kpts, radius=self.lw * 3)
61
+
62
+ # Determine stage and count logic based on angle thresholds
63
+ if self.angle[ind] < self.down_angle:
64
+ if self.stage[ind] == "up":
104
65
  self.count[ind] += 1
66
+ self.stage[ind] = "down"
67
+ elif self.angle[ind] > self.up_angle:
68
+ self.stage[ind] = "up"
105
69
 
70
+ # Display angle, count, and stage text
106
71
  self.annotator.plot_angle_and_count_and_stage(
107
- angle_text=self.angle[ind],
108
- count_text=self.count[ind],
109
- stage_text=self.stage[ind],
110
- center_kpt=k[int(self.kpts_to_check[1])],
72
+ angle_text=self.angle[ind], # angle text for display
73
+ count_text=self.count[ind], # count text for workouts
74
+ stage_text=self.stage[ind], # stage position text
75
+ center_kpt=k[int(self.kpts[1])], # center keypoint for display
111
76
  )
112
77
 
113
- # Draw keypoints
114
- self.annotator.kpts(k, shape=(640, 640), radius=1, kpt_line=True)
115
-
116
- # Display the image if environment supports it and view_img is True
117
- if self.env_check and self.view_img:
118
- cv2.imshow("Ultralytics YOLOv8 AI GYM", self.im0)
119
- if cv2.waitKey(1) & 0xFF == ord("q"):
120
- return
121
-
122
- return self.im0
123
-
124
-
125
- if __name__ == "__main__":
126
- kpts_to_check = [0, 1, 2] # example keypoints
127
- aigym = AIGym(kpts_to_check)
78
+ self.display_output(im0) # Display output image, if environment support display
79
+ return im0 # return an image for writing or further usage