ultralytics 8.3.5__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.5"
3
+ __version__ = "8.3.6"
4
4
 
5
5
  import os
6
6
 
@@ -10,3 +10,7 @@ show: True # Flag to control whether to display output image or not
10
10
  show_in: True # Flag to display objects moving *into* the defined region
11
11
  show_out: True # Flag to display objects moving *out of* the defined region
12
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
@@ -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")
@@ -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
@@ -1,249 +1,93 @@
1
1
  # Ultralytics YOLO 🚀, AGPL-3.0 license
2
2
 
3
- from collections import defaultdict
4
-
5
3
  import cv2
6
4
  import numpy as np
7
5
 
8
- from ultralytics.utils.checks import check_imshow, check_requirements
6
+ from ultralytics.solutions.object_counter import ObjectCounter # Import object counter class
9
7
  from ultralytics.utils.plotting import Annotator
10
8
 
11
- check_requirements("shapely>=2.0.0")
12
-
13
- from shapely.geometry import LineString, Point, Polygon
14
-
15
9
 
16
- class Heatmap:
10
+ class Heatmap(ObjectCounter):
17
11
  """A class to draw heatmaps in real-time video stream based on their tracks."""
18
12
 
19
- def __init__(
20
- self,
21
- names,
22
- colormap=cv2.COLORMAP_JET,
23
- view_img=False,
24
- view_in_counts=True,
25
- view_out_counts=True,
26
- count_reg_pts=None,
27
- count_txt_color=(0, 0, 0),
28
- count_bg_color=(255, 255, 255),
29
- count_reg_color=(255, 0, 255),
30
- region_thickness=5,
31
- line_dist_thresh=15,
32
- line_thickness=2,
33
- shape="circle",
34
- ):
35
- """Initializes the heatmap class with default values for Visual, Image, track, count and heatmap parameters."""
36
- # Visual information
37
- self.annotator = None
38
- self.view_img = view_img
39
- self.shape = shape
40
-
41
- self.initialized = False
42
- self.names = names # Classes names
43
-
44
- # Image information
45
- self.im0 = None
46
- self.tf = line_thickness
47
- self.view_in_counts = view_in_counts
48
- self.view_out_counts = view_out_counts
49
-
50
- # Heatmap colormap and heatmap np array
51
- self.colormap = colormap
52
- self.heatmap = None
53
-
54
- # Predict/track information
55
- self.boxes = []
56
- self.track_ids = []
57
- self.clss = []
58
- self.track_history = defaultdict(list)
59
-
60
- # Region & Line Information
61
- self.counting_region = None
62
- self.line_dist_thresh = line_dist_thresh
63
- self.region_thickness = region_thickness
64
- self.region_color = count_reg_color
65
-
66
- # Object Counting Information
67
- self.in_counts = 0
68
- self.out_counts = 0
69
- self.count_ids = []
70
- self.class_wise_count = {}
71
- self.count_txt_color = count_txt_color
72
- self.count_bg_color = count_bg_color
73
- self.cls_txtdisplay_gap = 50
74
-
75
- # Check if environment supports imshow
76
- self.env_check = check_imshow(warn=True)
77
-
78
- # Region and line selection
79
- self.count_reg_pts = count_reg_pts
80
- print(self.count_reg_pts)
81
- if self.count_reg_pts is not None:
82
- if len(self.count_reg_pts) == 2:
83
- print("Line Counter Initiated.")
84
- self.counting_region = LineString(self.count_reg_pts)
85
- elif len(self.count_reg_pts) >= 3:
86
- print("Polygon Counter Initiated.")
87
- self.counting_region = Polygon(self.count_reg_pts)
88
- else:
89
- print("Invalid Region points provided, region_points must be 2 for lines or >= 3 for polygons.")
90
- print("Using Line Counter Now")
91
- self.counting_region = LineString(self.count_reg_pts)
92
-
93
- # Shape of heatmap, if not selected
94
- if self.shape not in {"circle", "rect"}:
95
- print("Unknown shape value provided, 'circle' & 'rect' supported")
96
- print("Using Circular shape now")
97
- self.shape = "circle"
98
-
99
- def extract_results(self, tracks):
100
- """
101
- Extracts results from the provided data.
13
+ def __init__(self, **kwargs):
14
+ """Initializes function for heatmap class with default values."""
15
+ super().__init__(**kwargs)
102
16
 
103
- Args:
104
- tracks (list): List of tracks obtained from the object tracking process.
105
- """
106
- if tracks[0].boxes.id is not None:
107
- self.boxes = tracks[0].boxes.xyxy.cpu()
108
- self.clss = tracks[0].boxes.cls.tolist()
109
- self.track_ids = tracks[0].boxes.id.int().tolist()
17
+ self.initialized = False # bool variable for heatmap initialization
18
+ if self.region is not None: # check if user provided the region coordinates
19
+ self.initialize_region()
20
+
21
+ # store colormap
22
+ self.colormap = cv2.COLORMAP_PARULA if self.CFG["colormap"] is None else self.CFG["colormap"]
110
23
 
111
- def generate_heatmap(self, im0, tracks):
24
+ def heatmap_effect(self, box):
112
25
  """
113
- Generate heatmap based on tracking data.
26
+ Efficient calculation of heatmap area and effect location for applying colormap.
114
27
 
115
28
  Args:
116
- im0 (nd array): Image
117
- tracks (list): List of tracks obtained from the object tracking process.
29
+ box (list): Bounding Box coordinates data [x0, y0, x1, y1]
118
30
  """
119
- self.im0 = im0
31
+ x0, y0, x1, y1 = map(int, box)
32
+ radius_squared = (min(x1 - x0, y1 - y0) // 2) ** 2
120
33
 
121
- # Initialize heatmap only once
122
- if not self.initialized:
123
- self.heatmap = np.zeros((int(self.im0.shape[0]), int(self.im0.shape[1])), dtype=np.float32)
124
- self.initialized = True
34
+ # Create a meshgrid with region of interest (ROI) for vectorized distance calculations
35
+ xv, yv = np.meshgrid(np.arange(x0, x1), np.arange(y0, y1))
125
36
 
126
- self.heatmap *= 0.99 # decay factor
37
+ # Calculate squared distances from the center
38
+ dist_squared = (xv - ((x0 + x1) // 2)) ** 2 + (yv - ((y0 + y1) // 2)) ** 2
127
39
 
128
- self.extract_results(tracks)
129
- self.annotator = Annotator(self.im0, self.tf, None)
40
+ # Create a mask of points within the radius
41
+ within_radius = dist_squared <= radius_squared
130
42
 
131
- if self.track_ids:
132
- # Draw counting region
133
- if self.count_reg_pts is not None:
134
- self.annotator.draw_region(
135
- reg_pts=self.count_reg_pts, color=self.region_color, thickness=self.region_thickness
136
- )
43
+ # Update only the values within the bounding box in a single vectorized operation
44
+ self.heatmap[y0:y1, x0:x1][within_radius] += 2
137
45
 
138
- for box, cls, track_id in zip(self.boxes, self.clss, self.track_ids):
139
- # Store class info
140
- if self.names[cls] not in self.class_wise_count:
141
- self.class_wise_count[self.names[cls]] = {"IN": 0, "OUT": 0}
142
-
143
- if self.shape == "circle":
144
- center = (int((box[0] + box[2]) // 2), int((box[1] + box[3]) // 2))
145
- radius = min(int(box[2]) - int(box[0]), int(box[3]) - int(box[1])) // 2
46
+ def generate_heatmap(self, im0):
47
+ """
48
+ Generate heatmap for each frame using Ultralytics.
146
49
 
147
- y, x = np.ogrid[0 : self.heatmap.shape[0], 0 : self.heatmap.shape[1]]
148
- mask = (x - center[0]) ** 2 + (y - center[1]) ** 2 <= radius**2
50
+ Args:
51
+ im0 (ndarray): Input image array for processing
52
+ Returns:
53
+ im0 (ndarray): Processed image for further usage
54
+ """
55
+ self.heatmap = np.zeros_like(im0, dtype=np.float32) * 0.99 if not self.initialized else self.heatmap
56
+ self.initialized = True # Initialize heatmap only once
149
57
 
150
- self.heatmap[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] += (
151
- 2 * mask[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])]
152
- )
58
+ self.annotator = Annotator(im0, line_width=self.line_width) # Initialize annotator
59
+ self.extract_tracks(im0) # Extract tracks
153
60
 
154
- else:
155
- self.heatmap[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] += 2
61
+ # Iterate over bounding boxes, track ids and classes index
62
+ for box, track_id, cls in zip(self.boxes, self.track_ids, self.clss):
63
+ # Draw bounding box and counting region
64
+ self.heatmap_effect(box)
156
65
 
157
- # Store tracking hist
158
- track_line = self.track_history[track_id]
159
- track_line.append((float((box[0] + box[2]) / 2), float((box[1] + box[3]) / 2)))
160
- if len(track_line) > 30:
161
- track_line.pop(0)
66
+ if self.region is not None:
67
+ self.annotator.draw_region(reg_pts=self.region, color=(104, 0, 123), thickness=self.line_width * 2)
68
+ self.store_tracking_history(track_id, box) # Store track history
69
+ self.store_classwise_counts(cls) # store classwise counts in dict
162
70
 
71
+ # Store tracking previous position and perform object counting
163
72
  prev_position = self.track_history[track_id][-2] if len(self.track_history[track_id]) > 1 else None
73
+ self.count_objects(self.track_line, box, track_id, prev_position, cls) # Perform object counting
164
74
 
165
- if self.count_reg_pts is not None:
166
- # Count objects in any polygon
167
- if len(self.count_reg_pts) >= 3:
168
- is_inside = self.counting_region.contains(Point(track_line[-1]))
169
-
170
- if prev_position is not None and is_inside and track_id not in self.count_ids:
171
- self.count_ids.append(track_id)
172
-
173
- if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
174
- self.in_counts += 1
175
- self.class_wise_count[self.names[cls]]["IN"] += 1
176
- else:
177
- self.out_counts += 1
178
- self.class_wise_count[self.names[cls]]["OUT"] += 1
179
-
180
- # Count objects using line
181
- elif len(self.count_reg_pts) == 2:
182
- if prev_position is not None and track_id not in self.count_ids:
183
- distance = Point(track_line[-1]).distance(self.counting_region)
184
- if distance < self.line_dist_thresh and track_id not in self.count_ids:
185
- self.count_ids.append(track_id)
186
-
187
- if (box[0] - prev_position[0]) * (
188
- self.counting_region.centroid.x - prev_position[0]
189
- ) > 0:
190
- self.in_counts += 1
191
- self.class_wise_count[self.names[cls]]["IN"] += 1
192
- else:
193
- self.out_counts += 1
194
- self.class_wise_count[self.names[cls]]["OUT"] += 1
195
-
196
- else:
197
- for box, cls in zip(self.boxes, self.clss):
198
- if self.shape == "circle":
199
- center = (int((box[0] + box[2]) // 2), int((box[1] + box[3]) // 2))
200
- radius = min(int(box[2]) - int(box[0]), int(box[3]) - int(box[1])) // 2
201
-
202
- y, x = np.ogrid[0 : self.heatmap.shape[0], 0 : self.heatmap.shape[1]]
203
- mask = (x - center[0]) ** 2 + (y - center[1]) ** 2 <= radius**2
204
-
205
- self.heatmap[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] += (
206
- 2 * mask[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])]
207
- )
208
-
209
- else:
210
- self.heatmap[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] += 2
211
-
212
- if self.count_reg_pts is not None:
213
- labels_dict = {}
214
-
215
- for key, value in self.class_wise_count.items():
216
- if value["IN"] != 0 or value["OUT"] != 0:
217
- if not self.view_in_counts and not self.view_out_counts:
218
- continue
219
- elif not self.view_in_counts:
220
- labels_dict[str.capitalize(key)] = f"OUT {value['OUT']}"
221
- elif not self.view_out_counts:
222
- labels_dict[str.capitalize(key)] = f"IN {value['IN']}"
223
- else:
224
- labels_dict[str.capitalize(key)] = f"IN {value['IN']} OUT {value['OUT']}"
225
-
226
- if labels_dict is not None:
227
- self.annotator.display_analytics(self.im0, labels_dict, self.count_txt_color, self.count_bg_color, 10)
75
+ self.display_counts(im0) if self.region is not None else None # Display the counts on the frame
228
76
 
229
77
  # Normalize, apply colormap to heatmap and combine with original image
230
- heatmap_normalized = cv2.normalize(self.heatmap, None, 0, 255, cv2.NORM_MINMAX)
231
- heatmap_colored = cv2.applyColorMap(heatmap_normalized.astype(np.uint8), self.colormap)
232
- self.im0 = cv2.addWeighted(self.im0, 0.5, heatmap_colored, 0.5, 0)
233
-
234
- if self.env_check and self.view_img:
235
- self.display_frames()
236
-
237
- return self.im0
238
-
239
- def display_frames(self):
240
- """Display frame."""
241
- cv2.imshow("Ultralytics Heatmap", self.im0)
242
-
243
- if cv2.waitKey(1) & 0xFF == ord("q"):
244
- return
245
-
246
-
247
- if __name__ == "__main__":
248
- classes_names = {0: "person", 1: "car"} # example class names
249
- heatmap = Heatmap(classes_names)
78
+ im0 = (
79
+ im0
80
+ if self.track_data.id is None
81
+ else cv2.addWeighted(
82
+ im0,
83
+ 0.5,
84
+ cv2.applyColorMap(
85
+ cv2.normalize(self.heatmap, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8), self.colormap
86
+ ),
87
+ 0.5,
88
+ 0,
89
+ )
90
+ )
91
+
92
+ self.display_output(im0) # display output with base class function
93
+ return im0 # return output image for more usage
@@ -19,8 +19,7 @@ class ObjectCounter(BaseSolution):
19
19
  self.out_count = 0 # Counter for objects moving outward
20
20
  self.counted_ids = [] # List of IDs of objects that have been counted
21
21
  self.classwise_counts = {} # Dictionary for counts, categorized by object class
22
-
23
- self.initialize_region() # Setup region and counting areas
22
+ self.region_initialized = False # Bool variable for region initialization
24
23
 
25
24
  self.show_in = self.CFG["show_in"]
26
25
  self.show_out = self.CFG["show_out"]
@@ -99,6 +98,10 @@ class ObjectCounter(BaseSolution):
99
98
  Returns
100
99
  im0 (ndarray): The processed image for more usage
101
100
  """
101
+ if not self.region_initialized:
102
+ self.initialize_region()
103
+ self.region_initialized = True
104
+
102
105
  self.annotator = Annotator(im0, line_width=self.line_width) # Initialize annotator
103
106
  self.extract_tracks(im0) # Extract tracks
104
107
 
@@ -107,21 +110,20 @@ class ObjectCounter(BaseSolution):
107
110
  ) # Draw region
108
111
 
109
112
  # Iterate over bounding boxes, track ids and classes index
110
- if self.track_data is not None and self.track_data.id is not None:
111
- for box, track_id, cls in zip(self.boxes, self.track_ids, self.clss):
112
- # Draw bounding box and counting region
113
- self.annotator.box_label(box, label=self.names[cls], color=colors(track_id, True))
114
- self.store_tracking_history(track_id, box) # Store track history
115
- self.store_classwise_counts(cls) # store classwise counts in dict
116
-
117
- # Draw centroid of objects
118
- self.annotator.draw_centroid_and_tracks(
119
- self.track_line, color=colors(int(track_id), True), track_thickness=self.line_width
120
- )
121
-
122
- # store previous position of track for object counting
123
- prev_position = self.track_history[track_id][-2] if len(self.track_history[track_id]) > 1 else None
124
- self.count_objects(self.track_line, box, track_id, prev_position, cls) # Perform object counting
113
+ for box, track_id, cls in zip(self.boxes, self.track_ids, self.clss):
114
+ # Draw bounding box and counting region
115
+ self.annotator.box_label(box, label=self.names[cls], color=colors(track_id, True))
116
+ self.store_tracking_history(track_id, box) # Store track history
117
+ self.store_classwise_counts(cls) # store classwise counts in dict
118
+
119
+ # Draw centroid of objects
120
+ self.annotator.draw_centroid_and_tracks(
121
+ self.track_line, color=colors(int(track_id), True), track_thickness=self.line_width
122
+ )
123
+
124
+ # store previous position of track for object counting
125
+ prev_position = self.track_history[track_id][-2] if len(self.track_history[track_id]) > 1 else None
126
+ self.count_objects(self.track_line, box, track_id, prev_position, cls) # Perform object counting
125
127
 
126
128
  self.display_counts(im0) # Display the counts on the frame
127
129
  self.display_output(im0) # display output with base class function
@@ -4,11 +4,13 @@ from collections import defaultdict
4
4
  from pathlib import Path
5
5
 
6
6
  import cv2
7
- from shapely.geometry import LineString, Polygon
8
7
 
9
8
  from ultralytics import YOLO
10
- from ultralytics.utils import yaml_load
11
- from ultralytics.utils.checks import check_imshow
9
+ from ultralytics.utils import LOGGER, yaml_load
10
+ from ultralytics.utils.checks import check_imshow, check_requirements
11
+
12
+ check_requirements("shapely>=2.0.0")
13
+ from shapely.geometry import LineString, Polygon
12
14
 
13
15
  DEFAULT_SOL_CFG_PATH = Path(__file__).resolve().parents[1] / "cfg/solutions/default.yaml"
14
16
 
@@ -25,7 +27,7 @@ class BaseSolution:
25
27
  # Load config and update with args
26
28
  self.CFG = yaml_load(DEFAULT_SOL_CFG_PATH)
27
29
  self.CFG.update(kwargs)
28
- print("Ultralytics Solutions: ✅", self.CFG)
30
+ LOGGER.info(f"Ultralytics Solutions: ✅ {self.CFG}")
29
31
 
30
32
  self.region = self.CFG["region"] # Store region data for other classes usage
31
33
  self.line_width = self.CFG["line_width"] # Store line_width for usage
@@ -54,6 +56,9 @@ class BaseSolution:
54
56
  self.boxes = self.track_data.xyxy.cpu()
55
57
  self.clss = self.track_data.cls.cpu().tolist()
56
58
  self.track_ids = self.track_data.id.int().cpu().tolist()
59
+ else:
60
+ LOGGER.warning("WARNING ⚠️ no tracks found!")
61
+ self.boxes, self.clss, self.track_ids = [], [], []
57
62
 
58
63
  def store_tracking_history(self, track_id, box):
59
64
  """
@@ -989,55 +989,56 @@ def set_sentry():
989
989
  Additionally, the function sets custom tags and user information for Sentry events.
990
990
  """
991
991
  if (
992
- SETTINGS["sync"]
993
- and RANK in {-1, 0}
994
- and Path(ARGV[0]).name == "yolo"
995
- and not TESTS_RUNNING
996
- and ONLINE
997
- and IS_PIP_PACKAGE
998
- and not IS_GIT_DIR
992
+ not SETTINGS["sync"]
993
+ or RANK not in {-1, 0}
994
+ or Path(ARGV[0]).name != "yolo"
995
+ or TESTS_RUNNING
996
+ or not ONLINE
997
+ or not IS_PIP_PACKAGE
998
+ or IS_GIT_DIR
999
999
  ):
1000
- # If sentry_sdk package is not installed then return and do not use Sentry
1001
- try:
1002
- import sentry_sdk # noqa
1003
- except ImportError:
1004
- return
1005
-
1006
- def before_send(event, hint):
1007
- """
1008
- Modify the event before sending it to Sentry based on specific exception types and messages.
1000
+ return
1001
+ # If sentry_sdk package is not installed then return and do not use Sentry
1002
+ try:
1003
+ import sentry_sdk # noqa
1004
+ except ImportError:
1005
+ return
1006
+
1007
+ def before_send(event, hint):
1008
+ """
1009
+ Modify the event before sending it to Sentry based on specific exception types and messages.
1009
1010
 
1010
- Args:
1011
- event (dict): The event dictionary containing information about the error.
1012
- hint (dict): A dictionary containing additional information about the error.
1011
+ Args:
1012
+ event (dict): The event dictionary containing information about the error.
1013
+ hint (dict): A dictionary containing additional information about the error.
1013
1014
 
1014
- Returns:
1015
- dict: The modified event or None if the event should not be sent to Sentry.
1016
- """
1017
- if "exc_info" in hint:
1018
- exc_type, exc_value, _ = hint["exc_info"]
1019
- if exc_type in {KeyboardInterrupt, FileNotFoundError} or "out of memory" in str(exc_value):
1020
- return None # do not send event
1021
-
1022
- event["tags"] = {
1023
- "sys_argv": ARGV[0],
1024
- "sys_argv_name": Path(ARGV[0]).name,
1025
- "install": "git" if IS_GIT_DIR else "pip" if IS_PIP_PACKAGE else "other",
1026
- "os": ENVIRONMENT,
1027
- }
1028
- return event
1029
-
1030
- sentry_sdk.init(
1031
- dsn="https://888e5a0778212e1d0314c37d4b9aae5d@o4504521589325824.ingest.us.sentry.io/4504521592406016",
1032
- debug=False,
1033
- auto_enabling_integrations=False,
1034
- traces_sample_rate=1.0,
1035
- release=__version__,
1036
- environment="production", # 'dev' or 'production'
1037
- before_send=before_send,
1038
- ignore_errors=[KeyboardInterrupt, FileNotFoundError],
1039
- )
1040
- sentry_sdk.set_user({"id": SETTINGS["uuid"]}) # SHA-256 anonymized UUID hash
1015
+ Returns:
1016
+ dict: The modified event or None if the event should not be sent to Sentry.
1017
+ """
1018
+ if "exc_info" in hint:
1019
+ exc_type, exc_value, _ = hint["exc_info"]
1020
+ if exc_type in {KeyboardInterrupt, FileNotFoundError} or "out of memory" in str(exc_value):
1021
+ return None # do not send event
1022
+
1023
+ event["tags"] = {
1024
+ "sys_argv": ARGV[0],
1025
+ "sys_argv_name": Path(ARGV[0]).name,
1026
+ "install": "git" if IS_GIT_DIR else "pip" if IS_PIP_PACKAGE else "other",
1027
+ "os": ENVIRONMENT,
1028
+ }
1029
+ return event
1030
+
1031
+ sentry_sdk.init(
1032
+ dsn="https://888e5a0778212e1d0314c37d4b9aae5d@o4504521589325824.ingest.us.sentry.io/4504521592406016",
1033
+ debug=False,
1034
+ auto_enabling_integrations=False,
1035
+ traces_sample_rate=1.0,
1036
+ release=__version__,
1037
+ environment="production", # 'dev' or 'production'
1038
+ before_send=before_send,
1039
+ ignore_errors=[KeyboardInterrupt, FileNotFoundError],
1040
+ )
1041
+ sentry_sdk.set_user({"id": SETTINGS["uuid"]}) # SHA-256 anonymized UUID hash
1041
1042
 
1042
1043
 
1043
1044
  class JSONDict(dict):
@@ -593,20 +593,29 @@ def collect_system_info():
593
593
  import psutil
594
594
 
595
595
  from ultralytics.utils import ENVIRONMENT # scope to avoid circular import
596
- from ultralytics.utils.torch_utils import get_cpu_info
596
+ from ultralytics.utils.torch_utils import get_cpu_info, get_gpu_info
597
597
 
598
- ram_info = psutil.virtual_memory().total / (1024**3) # Convert bytes to GB
598
+ gib = 1 << 30 # bytes per GiB
599
+ cuda = torch and torch.cuda.is_available()
599
600
  check_yolo()
600
- LOGGER.info(
601
- f"\n{'OS':<20}{platform.platform()}\n"
602
- f"{'Environment':<20}{ENVIRONMENT}\n"
603
- f"{'Python':<20}{PYTHON_VERSION}\n"
604
- f"{'Install':<20}{'git' if IS_GIT_DIR else 'pip' if IS_PIP_PACKAGE else 'other'}\n"
605
- f"{'RAM':<20}{ram_info:.2f} GB\n"
606
- f"{'CPU':<20}{get_cpu_info()}\n"
607
- f"{'CUDA':<20}{torch.version.cuda if torch and torch.cuda.is_available() else None}\n"
608
- )
601
+ total, used, free = shutil.disk_usage("/")
602
+
603
+ info_dict = {
604
+ "OS": platform.platform(),
605
+ "Environment": ENVIRONMENT,
606
+ "Python": PYTHON_VERSION,
607
+ "Install": "git" if IS_GIT_DIR else "pip" if IS_PIP_PACKAGE else "other",
608
+ "RAM": f"{psutil.virtual_memory().total / gib:.2f} GB",
609
+ "Disk": f"{(total - free) / gib:.1f}/{total / gib:.1f} GB",
610
+ "CPU": get_cpu_info(),
611
+ "CPU count": os.cpu_count(),
612
+ "GPU": get_gpu_info(index=0) if cuda else None,
613
+ "GPU count": torch.cuda.device_count() if cuda else None,
614
+ "CUDA": torch.version.cuda if cuda else None,
615
+ }
616
+ LOGGER.info("\n" + "\n".join(f"{k:<20}{v}" for k, v in info_dict.items()) + "\n")
609
617
 
618
+ package_info = {}
610
619
  for r in parse_requirements(package="ultralytics"):
611
620
  try:
612
621
  current = metadata.version(r.name)
@@ -614,17 +623,24 @@ def collect_system_info():
614
623
  except metadata.PackageNotFoundError:
615
624
  current = "(not installed)"
616
625
  is_met = "❌ "
617
- LOGGER.info(f"{r.name:<20}{is_met}{current}{r.specifier}")
626
+ package_info[r.name] = f"{is_met}{current}{r.specifier}"
627
+ LOGGER.info(f"{r.name:<20}{package_info[r.name]}")
628
+
629
+ info_dict["Package Info"] = package_info
618
630
 
619
631
  if is_github_action_running():
620
- LOGGER.info(
621
- f"\nRUNNER_OS: {os.getenv('RUNNER_OS')}\n"
622
- f"GITHUB_EVENT_NAME: {os.getenv('GITHUB_EVENT_NAME')}\n"
623
- f"GITHUB_WORKFLOW: {os.getenv('GITHUB_WORKFLOW')}\n"
624
- f"GITHUB_ACTOR: {os.getenv('GITHUB_ACTOR')}\n"
625
- f"GITHUB_REPOSITORY: {os.getenv('GITHUB_REPOSITORY')}\n"
626
- f"GITHUB_REPOSITORY_OWNER: {os.getenv('GITHUB_REPOSITORY_OWNER')}\n"
627
- )
632
+ github_info = {
633
+ "RUNNER_OS": os.getenv("RUNNER_OS"),
634
+ "GITHUB_EVENT_NAME": os.getenv("GITHUB_EVENT_NAME"),
635
+ "GITHUB_WORKFLOW": os.getenv("GITHUB_WORKFLOW"),
636
+ "GITHUB_ACTOR": os.getenv("GITHUB_ACTOR"),
637
+ "GITHUB_REPOSITORY": os.getenv("GITHUB_REPOSITORY"),
638
+ "GITHUB_REPOSITORY_OWNER": os.getenv("GITHUB_REPOSITORY_OWNER"),
639
+ }
640
+ LOGGER.info("\n" + "\n".join(f"{k}: {v}" for k, v in github_info.items()))
641
+ info_dict["GitHub Info"] = github_info
642
+
643
+ return info_dict
628
644
 
629
645
 
630
646
  def check_amp(model):
@@ -697,14 +697,13 @@ class Annotator:
697
697
  angle = 360 - angle
698
698
  return angle
699
699
 
700
- def draw_specific_points(self, keypoints, indices=None, shape=(640, 640), radius=2, conf_thres=0.25):
700
+ def draw_specific_points(self, keypoints, indices=None, radius=2, conf_thres=0.25):
701
701
  """
702
702
  Draw specific keypoints for gym steps counting.
703
703
 
704
704
  Args:
705
705
  keypoints (list): Keypoints data to be plotted.
706
706
  indices (list, optional): Keypoint indices to be plotted. Defaults to [2, 5, 7].
707
- shape (tuple, optional): Image size for model inference. Defaults to (640, 640).
708
707
  radius (int, optional): Keypoint radius. Defaults to 2.
709
708
  conf_thres (float, optional): Confidence threshold for keypoints. Defaults to 0.25.
710
709
 
@@ -715,90 +714,71 @@ class Annotator:
715
714
  Keypoint format: [x, y] or [x, y, confidence].
716
715
  Modifies self.im in-place.
717
716
  """
718
- if indices is None:
719
- indices = [2, 5, 7]
720
- for i, k in enumerate(keypoints):
721
- if i in indices:
722
- x_coord, y_coord = k[0], k[1]
723
- if x_coord % shape[1] != 0 and y_coord % shape[0] != 0:
724
- if len(k) == 3:
725
- conf = k[2]
726
- if conf < conf_thres:
727
- continue
728
- cv2.circle(self.im, (int(x_coord), int(y_coord)), radius, (0, 255, 0), -1, lineType=cv2.LINE_AA)
717
+ indices = indices or [2, 5, 7]
718
+ points = [(int(k[0]), int(k[1])) for i, k in enumerate(keypoints) if i in indices and k[2] >= conf_thres]
719
+
720
+ # Draw lines between consecutive points
721
+ for start, end in zip(points[:-1], points[1:]):
722
+ cv2.line(self.im, start, end, (0, 255, 0), 2, lineType=cv2.LINE_AA)
723
+
724
+ # Draw circles for keypoints
725
+ for pt in points:
726
+ cv2.circle(self.im, pt, radius, (0, 0, 255), -1, lineType=cv2.LINE_AA)
727
+
729
728
  return self.im
730
729
 
731
- def plot_angle_and_count_and_stage(
732
- self, angle_text, count_text, stage_text, center_kpt, color=(104, 31, 17), txt_color=(255, 255, 255)
733
- ):
730
+ def plot_workout_information(self, display_text, position, color=(104, 31, 17), txt_color=(255, 255, 255)):
734
731
  """
735
- Plot the pose angle, count value and step stage.
732
+ Draw text with a background on the image.
736
733
 
737
734
  Args:
738
- angle_text (str): angle value for workout monitoring
739
- count_text (str): counts value for workout monitoring
740
- stage_text (str): stage decision for workout monitoring
741
- center_kpt (list): centroid pose index for workout monitoring
742
- color (tuple): text background color for workout monitoring
743
- txt_color (tuple): text foreground color for workout monitoring
735
+ display_text (str): The text to be displayed.
736
+ position (tuple): Coordinates (x, y) on the image where the text will be placed.
737
+ color (tuple, optional): Text background color
738
+ txt_color (tuple, optional): Text foreground color
744
739
  """
745
- angle_text, count_text, stage_text = (f" {angle_text:.2f}", f"Steps : {count_text}", f" {stage_text}")
740
+ (text_width, text_height), _ = cv2.getTextSize(display_text, 0, self.sf, self.tf)
746
741
 
747
- # Draw angle
748
- (angle_text_width, angle_text_height), _ = cv2.getTextSize(angle_text, 0, self.sf, self.tf)
749
- angle_text_position = (int(center_kpt[0]), int(center_kpt[1]))
750
- angle_background_position = (angle_text_position[0], angle_text_position[1] - angle_text_height - 5)
751
- angle_background_size = (angle_text_width + 2 * 5, angle_text_height + 2 * 5 + (self.tf * 2))
742
+ # Draw background rectangle
752
743
  cv2.rectangle(
753
744
  self.im,
754
- angle_background_position,
755
- (
756
- angle_background_position[0] + angle_background_size[0],
757
- angle_background_position[1] + angle_background_size[1],
758
- ),
745
+ (position[0], position[1] - text_height - 5),
746
+ (position[0] + text_width + 10, position[1] - text_height - 5 + text_height + 10 + self.tf),
759
747
  color,
760
748
  -1,
761
749
  )
762
- cv2.putText(self.im, angle_text, angle_text_position, 0, self.sf, txt_color, self.tf)
763
-
764
- # Draw Counts
765
- (count_text_width, count_text_height), _ = cv2.getTextSize(count_text, 0, self.sf, self.tf)
766
- count_text_position = (angle_text_position[0], angle_text_position[1] + angle_text_height + 20)
767
- count_background_position = (
768
- angle_background_position[0],
769
- angle_background_position[1] + angle_background_size[1] + 5,
770
- )
771
- count_background_size = (count_text_width + 10, count_text_height + 10 + self.tf)
750
+ # Draw text
751
+ cv2.putText(self.im, display_text, position, 0, self.sf, txt_color, self.tf)
772
752
 
773
- cv2.rectangle(
774
- self.im,
775
- count_background_position,
776
- (
777
- count_background_position[0] + count_background_size[0],
778
- count_background_position[1] + count_background_size[1],
779
- ),
780
- color,
781
- -1,
782
- )
783
- cv2.putText(self.im, count_text, count_text_position, 0, self.sf, txt_color, self.tf)
753
+ return text_height
784
754
 
785
- # Draw Stage
786
- (stage_text_width, stage_text_height), _ = cv2.getTextSize(stage_text, 0, self.sf, self.tf)
787
- stage_text_position = (int(center_kpt[0]), int(center_kpt[1]) + angle_text_height + count_text_height + 40)
788
- stage_background_position = (stage_text_position[0], stage_text_position[1] - stage_text_height - 5)
789
- stage_background_size = (stage_text_width + 10, stage_text_height + 10)
755
+ def plot_angle_and_count_and_stage(
756
+ self, angle_text, count_text, stage_text, center_kpt, color=(104, 31, 17), txt_color=(255, 255, 255)
757
+ ):
758
+ """
759
+ Plot the pose angle, count value, and step stage.
790
760
 
791
- cv2.rectangle(
792
- self.im,
793
- stage_background_position,
794
- (
795
- stage_background_position[0] + stage_background_size[0],
796
- stage_background_position[1] + stage_background_size[1],
797
- ),
798
- color,
799
- -1,
761
+ Args:
762
+ angle_text (str): Angle value for workout monitoring
763
+ count_text (str): Counts value for workout monitoring
764
+ stage_text (str): Stage decision for workout monitoring
765
+ center_kpt (list): Centroid pose index for workout monitoring
766
+ color (tuple, optional): Text background color
767
+ txt_color (tuple, optional): Text foreground color
768
+ """
769
+ # Format text
770
+ angle_text, count_text, stage_text = f" {angle_text:.2f}", f"Steps : {count_text}", f" {stage_text}"
771
+
772
+ # Draw angle, count and stage text
773
+ angle_height = self.plot_workout_information(
774
+ angle_text, (int(center_kpt[0]), int(center_kpt[1])), color, txt_color
775
+ )
776
+ count_height = self.plot_workout_information(
777
+ count_text, (int(center_kpt[0]), int(center_kpt[1]) + angle_height + 20), color, txt_color
778
+ )
779
+ self.plot_workout_information(
780
+ stage_text, (int(center_kpt[0]), int(center_kpt[1]) + angle_height + count_height + 40), color, txt_color
800
781
  )
801
- cv2.putText(self.im, stage_text, stage_text_position, 0, self.sf, txt_color, self.tf)
802
782
 
803
783
  def seg_bbox(self, mask, mask_color=(255, 0, 255), label=None, txt_color=(255, 255, 255)):
804
784
  """
@@ -123,6 +123,12 @@ def get_cpu_info():
123
123
  return PERSISTENT_CACHE.get("cpu_info", "unknown")
124
124
 
125
125
 
126
+ def get_gpu_info(index):
127
+ """Return a string with system GPU information, i.e. 'Tesla T4, 15102MiB'."""
128
+ properties = torch.cuda.get_device_properties(index)
129
+ return f"{properties.name}, {properties.total_memory / (1 << 20):.0f}MiB"
130
+
131
+
126
132
  def select_device(device="", batch=0, newline=False, verbose=True):
127
133
  """
128
134
  Selects the appropriate PyTorch device based on the provided arguments.
@@ -208,8 +214,7 @@ def select_device(device="", batch=0, newline=False, verbose=True):
208
214
  )
209
215
  space = " " * (len(s) + 1)
210
216
  for i, d in enumerate(devices):
211
- p = torch.cuda.get_device_properties(i)
212
- s += f"{'' if i == 0 else space}CUDA:{d} ({p.name}, {p.total_memory / (1 << 20):.0f}MiB)\n" # bytes to MB
217
+ s += f"{'' if i == 0 else space}CUDA:{d} ({get_gpu_info(i)})\n" # bytes to MB
213
218
  arg = "cuda:0"
214
219
  elif mps and TORCH_2_0 and torch.backends.mps.is_available():
215
220
  # Prefer MPS if available
@@ -1,8 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ultralytics
3
- Version: 8.3.5
3
+ Version: 8.3.6
4
4
  Summary: Ultralytics YOLO for SOTA object detection, multi-object tracking, instance segmentation, pose estimation and image classification.
5
- Author: Ayush Chaurasia
6
5
  Author-email: Glenn Jocher <glenn.jocher@ultralytics.com>, Jing Qiu <jing.qiu@ultralytics.com>
7
6
  Maintainer-email: Ultralytics <hello@ultralytics.com>
8
7
  License: AGPL-3.0
@@ -99,8 +98,8 @@ Requires-Dist: dvclive>=2.12.0; extra == "logging"
99
98
  <a href="https://github.com/ultralytics/ultralytics/actions/workflows/ci.yaml"><img src="https://github.com/ultralytics/ultralytics/actions/workflows/ci.yaml/badge.svg" alt="Ultralytics CI"></a>
100
99
  <a href="https://zenodo.org/badge/latestdoi/264818686"><img src="https://zenodo.org/badge/264818686.svg" alt="Ultralytics YOLO Citation"></a>
101
100
  <a href="https://hub.docker.com/r/ultralytics/ultralytics"><img src="https://img.shields.io/docker/pulls/ultralytics/ultralytics?logo=docker" alt="Ultralytics Docker Pulls"></a>
102
- <a href="https://ultralytics.com/discord"><img alt="Ultralytics Discord" src="https://img.shields.io/discord/1089800235347353640?logo=discord&logoColor=white&label=Discord&color=blue"></a>
103
- <a href="https://community.ultralytics.com"><img alt="Ultralytics Forums" src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue"></a>
101
+ <a href="https://discord.com/invite/ultralytics"><img alt="Ultralytics Discord" src="https://img.shields.io/discord/1089800235347353640?logo=discord&logoColor=white&label=Discord&color=blue"></a>
102
+ <a href="https://community.ultralytics.com/"><img alt="Ultralytics Forums" src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue"></a>
104
103
  <a href="https://reddit.com/r/ultralytics"><img alt="Ultralytics Reddit" src="https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue"></a>
105
104
  <br>
106
105
  <a href="https://console.paperspace.com/github/ultralytics/ultralytics"><img src="https://assets.paperspace.io/img/gradient-badge.svg" alt="Run Ultralytics on Gradient"></a>
@@ -111,7 +110,7 @@ Requires-Dist: dvclive>=2.12.0; extra == "logging"
111
110
 
112
111
  [Ultralytics](https://www.ultralytics.com/) [YOLO11](https://github.com/ultralytics/ultralytics) is a cutting-edge, state-of-the-art (SOTA) model that builds upon the success of previous YOLO versions and introduces new features and improvements to further boost performance and flexibility. YOLO11 is designed to be fast, accurate, and easy to use, making it an excellent choice for a wide range of object detection and tracking, instance segmentation, image classification and pose estimation tasks.
113
112
 
114
- We hope that the resources here will help you get the most out of YOLO. Please browse the Ultralytics <a href="https://docs.ultralytics.com/">Docs</a> for details, raise an issue on <a href="https://github.com/ultralytics/ultralytics/issues/new/choose">GitHub</a> for support, questions, or discussions, become a member of the Ultralytics <a href="https://ultralytics.com/discord">Discord</a>, <a href="https://reddit.com/r/ultralytics">Reddit</a> and <a href="https://community.ultralytics.com">Forums</a>!
113
+ We hope that the resources here will help you get the most out of YOLO. Please browse the Ultralytics <a href="https://docs.ultralytics.com/">Docs</a> for details, raise an issue on <a href="https://github.com/ultralytics/ultralytics/issues/new/choose">GitHub</a> for support, questions, or discussions, become a member of the Ultralytics <a href="https://discord.com/invite/ultralytics">Discord</a>, <a href="https://reddit.com/r/ultralytics">Reddit</a> and <a href="https://community.ultralytics.com/">Forums</a>!
115
114
 
116
115
  To request an Enterprise License please complete the form at [Ultralytics Licensing](https://www.ultralytics.com/license).
117
116
 
@@ -130,7 +129,7 @@ To request an Enterprise License please complete the form at [Ultralytics Licens
130
129
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="2%" alt="space">
131
130
  <a href="https://ultralytics.com/bilibili"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-bilibili.png" width="2%" alt="Ultralytics BiliBili"></a>
132
131
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="2%" alt="space">
133
- <a href="https://ultralytics.com/discord"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-discord.png" width="2%" alt="Ultralytics Discord"></a>
132
+ <a href="https://discord.com/invite/ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-discord.png" width="2%" alt="Ultralytics Discord"></a>
134
133
  </div>
135
134
  </div>
136
135
 
@@ -299,7 +298,7 @@ See [OBB Docs](https://docs.ultralytics.com/tasks/obb/) for usage examples with
299
298
  Our key integrations with leading AI platforms extend the functionality of Ultralytics' offerings, enhancing tasks like dataset labeling, training, visualization, and model management. Discover how Ultralytics, in collaboration with [Roboflow](https://roboflow.com/?ref=ultralytics), ClearML, [Comet](https://bit.ly/yolov8-readme-comet), Neural Magic and [OpenVINO](https://docs.ultralytics.com/integrations/openvino/), can optimize your AI workflow.
300
299
 
301
300
  <br>
302
- <a href="https://ultralytics.com/hub" target="_blank">
301
+ <a href="https://www.ultralytics.com/hub" target="_blank">
303
302
  <img width="100%" src="https://github.com/ultralytics/assets/raw/main/yolov8/banner-integrations.png" alt="Ultralytics active learning integrations"></a>
304
303
  <br>
305
304
  <br>
@@ -326,7 +325,7 @@ Our key integrations with leading AI platforms extend the functionality of Ultra
326
325
 
327
326
  Experience seamless AI with [Ultralytics HUB](https://www.ultralytics.com/hub) ⭐, the all-in-one solution for data visualization, YOLO11 🚀 model training and deployment, without any coding. Transform images into actionable insights and bring your AI visions to life with ease using our cutting-edge platform and user-friendly [Ultralytics App](https://www.ultralytics.com/app-install). Start your journey for **Free** now!
328
327
 
329
- <a href="https://ultralytics.com/hub" target="_blank">
328
+ <a href="https://www.ultralytics.com/hub" target="_blank">
330
329
  <img width="100%" src="https://github.com/ultralytics/assets/raw/main/im/ultralytics-hub.png" alt="Ultralytics HUB preview image"></a>
331
330
 
332
331
  ## <div align="center">Contribute</div>
@@ -363,5 +362,5 @@ For Ultralytics bug reports and feature requests please visit [GitHub Issues](ht
363
362
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
364
363
  <a href="https://ultralytics.com/bilibili"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-bilibili.png" width="3%" alt="Ultralytics BiliBili"></a>
365
364
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
366
- <a href="https://ultralytics.com/discord"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-discord.png" width="3%" alt="Ultralytics Discord"></a>
365
+ <a href="https://discord.com/invite/ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-discord.png" width="3%" alt="Ultralytics Discord"></a>
367
366
  </div>
@@ -7,8 +7,8 @@ tests/test_explorer.py,sha256=9EeMtt4-K3-MeGnAc7NemTg3uTo-Xr6AYJlTJZJJeF8,2572
7
7
  tests/test_exports.py,sha256=fpTKEVBUGLF3WiZPNKRs-IEcIY4cfxgvgKjUNfodjww,8042
8
8
  tests/test_integrations.py,sha256=f5-QCUk1SU_-qn4mBCZwS3GN3tXEBIIXo4z2EhExbHw,6126
9
9
  tests/test_python.py,sha256=I1RRdCwLdrc3jX06huVxct8HX8ccQOmQgVpuEflRl0U,23560
10
- tests/test_solutions.py,sha256=Hjedlp6Qkqb4zyQ0y3DvfNValcguOMl8xbrDVpIFPfU,3304
11
- ultralytics/__init__.py,sha256=cPwEVFFgAAExxMMRGjRidE4mFdGDLx7Pr-tKFsQX5E4,693
10
+ tests/test_solutions.py,sha256=GYOjUXor2pHGPFwvZrmqrxNjs9wYz4r3_XWt8DMAVaM,3132
11
+ ultralytics/__init__.py,sha256=WlkyTS3L-3ozTAF5fT2vLMSf4tQNvOaCxGsICIQf7mA,693
12
12
  ultralytics/assets/bus.jpg,sha256=wCAZxJecGR63Od3ZRERe9Aja1Weayrb9Ug751DS_vGM,137419
13
13
  ultralytics/assets/zidane.jpg,sha256=Ftc4aeMmen1O0A3o6GCDO9FlfBslLpTAw0gnetx7bts,50427
14
14
  ultralytics/cfg/__init__.py,sha256=62PSSAa0W4-gAEcRNKoKbcxUWBeFNs0ss2O4XJQhOPY,33145
@@ -86,7 +86,7 @@ ultralytics/cfg/models/v9/yolov9e.yaml,sha256=dhaR47WxuLOrZWDCceS4bQG00sQdrMc8FQ
86
86
  ultralytics/cfg/models/v9/yolov9m.yaml,sha256=l6CmivzNu44sRVmkQXk4-tXflbV1nWnk5MSc8su2vhs,1311
87
87
  ultralytics/cfg/models/v9/yolov9s.yaml,sha256=lPWcu-6ub1kCBD6zIDFwthYZ3RvdJfODWKy3vEQWRjo,1291
88
88
  ultralytics/cfg/models/v9/yolov9t.yaml,sha256=qL__kr6GoefpQWP4jV0jdzwTp46bdFUcqtPRnfDbkY8,1275
89
- ultralytics/cfg/solutions/default.yaml,sha256=Z3hzSeoEhsVuGGc2WMVRAYukuXFGqKjAIefL6JS8P8k,599
89
+ ultralytics/cfg/solutions/default.yaml,sha256=H4pXUoA-IafiHL6NNNTyWXrlHAjMRqaItGK1U5amNE4,825
90
90
  ultralytics/cfg/trackers/botsort.yaml,sha256=8B0xNbnG_E-9DCUpap72PWkUgBb1AjuApEn7gHiVngE,916
91
91
  ultralytics/cfg/trackers/bytetrack.yaml,sha256=8vpTZ2x9mhRXJymoJvs1G8kTXo_HxbSwHup2FQALT3A,721
92
92
  ultralytics/data/__init__.py,sha256=VGe-ATG7j35F4A4r8Jmzffjlhve4JAJPgRa5ahKTU18,616
@@ -105,11 +105,11 @@ ultralytics/data/explorer/utils.py,sha256=EvvukQiQUTBrsZznmMnyEX2EqTuwZo_Geyc8yf
105
105
  ultralytics/data/explorer/gui/__init__.py,sha256=mHtJuK4hwF8cuV-VHDc7tp6u6D1gHz2Z7JI8grmQDTs,42
106
106
  ultralytics/data/explorer/gui/dash.py,sha256=vZ476NaUH4FKU08rAJ1K9WNyKtg0soMyJJxqg176yWc,10498
107
107
  ultralytics/engine/__init__.py,sha256=mHtJuK4hwF8cuV-VHDc7tp6u6D1gHz2Z7JI8grmQDTs,42
108
- ultralytics/engine/exporter.py,sha256=qhuPMBjBDVj9Qaa2qJYR954a-YS4BJtVN9jJeyFzyOg,57527
108
+ ultralytics/engine/exporter.py,sha256=DeHW_T_Zd3A21BLQYV1-FnS5EcmepMOy9nrussYNieU,57505
109
109
  ultralytics/engine/model.py,sha256=TDuy9JzzyvOaq5aKVljL_MFRKBDMCFwaLo3JD_d45CU,51462
110
110
  ultralytics/engine/predictor.py,sha256=MgMWHUJdRcVCaVmOyvdy2Gjk_EyRHv-ar0SSGxQe8F4,17471
111
111
  ultralytics/engine/results.py,sha256=8RJlN8J-_9w-mrDZm9wC-DZJTPBS7v1c_r_R173QyRM,75043
112
- ultralytics/engine/trainer.py,sha256=O2xCZ6mriLfPhU2IRe8XCCyZiI5A_AknjpQw3O5bAIE,36983
112
+ ultralytics/engine/trainer.py,sha256=ZCEXUPbJG_8Hzn2mLergk3WV-41ei0LT84Tspk0le30,37147
113
113
  ultralytics/engine/tuner.py,sha256=gPqDTHH7vRB2O3YyH26m1BjVKbXxuA2XAlPRzTKFZsc,11838
114
114
  ultralytics/engine/validator.py,sha256=2C_qXI36Z9rLOpmS0YR8Qe3ka4p23YiH2w5ai7-XBwE,14811
115
115
  ultralytics/hub/__init__.py,sha256=3SKvZ5aRina3h94xMPQIB3D4maF62qFcyIqPPHRHNAc,5644
@@ -185,14 +185,14 @@ ultralytics/nn/modules/head.py,sha256=x0Y8lTKFqYC4oAN1JTJ-yQ43sIXEIp35dmC14vdtQn
185
185
  ultralytics/nn/modules/transformer.py,sha256=tGiK8NmPfswwW1rbF21r5ILUkkZQ6Nk4s8j16vFBmps,18069
186
186
  ultralytics/nn/modules/utils.py,sha256=a88cKl2wz1nMVSEBiajtvaCbDBQIkESWOKTZ_WAJy90,3195
187
187
  ultralytics/solutions/__init__.py,sha256=6RDeXWO1QSaMgCq8YrWXaj2xvPw2sJwJL_a0dgjCvz0,648
188
- ultralytics/solutions/ai_gym.py,sha256=MgD_4DciCqXquM2Y6yjIIRkGWIg3rNfSuXrFqYzOCaI,4719
188
+ ultralytics/solutions/ai_gym.py,sha256=lBAkWV8vrEdKAXcBFVbugPeZZ08MOjGYTdnFlG22vKM,3772
189
189
  ultralytics/solutions/analytics.py,sha256=bGuZes11D7DNiTsHdwu6PJ0QA0vCiqMMAtZ7NyEkshY,11568
190
190
  ultralytics/solutions/distance_calculation.py,sha256=o_DAHk4JX8n2Vt7E68MX67mREOBZuy5skbXtVZ6iu_4,5228
191
- ultralytics/solutions/heatmap.py,sha256=mSssM7bZa_MTx5404tQPL0TMZRguowQa4DSAbcpivRM,10090
192
- ultralytics/solutions/object_counter.py,sha256=lBSWW0Ev6FOeV1MQNESBpub9Wzyulb9s1VBGbRRd8Sk,5485
191
+ ultralytics/solutions/heatmap.py,sha256=2C4s_rVFcOc5oSWxb0pNxNoCawe4lxajpTDNFd4tVL8,3850
192
+ ultralytics/solutions/object_counter.py,sha256=uuA7B-v9u-ElyEg1xCuNRgcnxpRpEfBWCdLs2ppjzzk,5497
193
193
  ultralytics/solutions/parking_management.py,sha256=VgYyhoSEo7fnPegIhNUqnFL0jlMEevALx0QQbzJ3vGI,9049
194
194
  ultralytics/solutions/queue_management.py,sha256=yKPGc2-fN-lMpNddkxjN7xYGIJwMdoU-VIDRxQ1KPow,4869
195
- ultralytics/solutions/solutions.py,sha256=bIt32FLj4ny5kG43bUKZiwyh-7qye6NOApxAvioklIA,3248
195
+ ultralytics/solutions/solutions.py,sha256=y6A2ZelsUj9RgN0GZNFBc_01UakoByT_jLG8-FiiLyI,3461
196
196
  ultralytics/solutions/speed_estimation.py,sha256=c9OPGpDU9x6Dj4SobNc-sO90EZTPTGeKkW5u6C6Zj7g,4623
197
197
  ultralytics/solutions/streamlit_inference.py,sha256=qA2EtwUC7ADOQ8P-zs3VPyrIoRArhcZz9CxkFbH63bw,5699
198
198
  ultralytics/trackers/__init__.py,sha256=j72IgH2dZHQArMPK4YwcV5ieIw94fYvlGdQjB9cOQKw,227
@@ -204,10 +204,10 @@ ultralytics/trackers/utils/__init__.py,sha256=mHtJuK4hwF8cuV-VHDc7tp6u6D1gHz2Z7J
204
204
  ultralytics/trackers/utils/gmc.py,sha256=VcURuY041qGCeWUGMxHZBr10T16LtcMqyv7AmTfE1MY,14557
205
205
  ultralytics/trackers/utils/kalman_filter.py,sha256=cH9zD3fwkuezP97H9mw8cSBN7a8hHKx_Sx1j7t3oYGs,21349
206
206
  ultralytics/trackers/utils/matching.py,sha256=3Ie1WNNRZ4_q3365F03XD7Nr9juZB_08mw4yUKC3w74,7162
207
- ultralytics/utils/__init__.py,sha256=pdYHDaJMacya7WWq5RNEw-UmidG3i0kQyWrbFNnAVtc,48887
207
+ ultralytics/utils/__init__.py,sha256=XAfItx7avPCi7fpT7rRyQQqgjh2OwoSEkvkp01BbtYc,48760
208
208
  ultralytics/utils/autobatch.py,sha256=AXboYfNSnTGsYj5FmgGYPQd0crfkeleyms6QXQfZGQ4,4194
209
209
  ultralytics/utils/benchmarks.py,sha256=8FYp5WPzcxcDaeg8ol2sgzRBHVGYatEO7f3MrmPF6nI,25097
210
- ultralytics/utils/checks.py,sha256=tiwVY1SCf7AlDOUQDh6fJlmhQ3CxQEqLUrXRvwRBoKs,28998
210
+ ultralytics/utils/checks.py,sha256=7peQ6Ra7mgcu5Xt1XbYiMEJkO-8aYPHco7CBVRQ_oR4,29559
211
211
  ultralytics/utils/dist.py,sha256=NDFga-uKxkBX2zLxFHSene_cCiGQJoyOeCXcN9JIOIk,2358
212
212
  ultralytics/utils/downloads.py,sha256=97JitihZqvIMS6_TX5rJAG7BI8eYHlu5g8YXlI0RkR4,21998
213
213
  ultralytics/utils/errors.py,sha256=GqP_Jgj_n0paxn8OMhn3DTCgoNkB2WjUcUaqs-M6SQk,816
@@ -217,9 +217,9 @@ ultralytics/utils/loss.py,sha256=SW3FVFFp8Ki_LCT8wIdFbm6KmyPcQn3RmKNcvVAhMQI,341
217
217
  ultralytics/utils/metrics.py,sha256=UgLGudWp57uXDMlMUJy4gsz6cfVjcq7tYmHeto3TqvM,53927
218
218
  ultralytics/utils/ops.py,sha256=dsXNdyrYx_p6io6zezig9p84dxS7U-10vceHNVu2IL0,32888
219
219
  ultralytics/utils/patches.py,sha256=J-iOwIRbfUs-inBZerhnXby5tUKjYcOIyvhLTS352JE,3270
220
- ultralytics/utils/plotting.py,sha256=Sqs9Q7mhenCsFed_oyw_64wgvd0TTae9L3Lc4g2_lSI,62296
220
+ ultralytics/utils/plotting.py,sha256=UQMgubdCKkIcKLLIXkE6uM9dhL7NlFRka6xXgfCMFn8,61153
221
221
  ultralytics/utils/tal.py,sha256=ECsu95xEqOItmxMDN4YTD3FsUiIsQNWy0pZC3TfvFfk,16877
222
- ultralytics/utils/torch_utils.py,sha256=RsTzm3__J4K1OUaxqc32O9WT6azcl4hPNkDdxhEp3q4,29792
222
+ ultralytics/utils/torch_utils.py,sha256=Dji6ELzywm4yq1D4AbUhOsanmoo9-pwxx5GBlYdIgqU,29956
223
223
  ultralytics/utils/triton.py,sha256=gg1finxno_tY2Ge9PMhmu7PI9wvoFZoiicdT4Bhqv3w,3936
224
224
  ultralytics/utils/tuner.py,sha256=AtEtK6pOt9xVTyx864OpNRVxNdAxz5aKHzveiXwkD1A,6250
225
225
  ultralytics/utils/callbacks/__init__.py,sha256=YrWqC3BVVaTLob4iCPR6I36mUxIUOpPJW7B_LjT78Qw,214
@@ -233,9 +233,9 @@ ultralytics/utils/callbacks/neptune.py,sha256=5Z3ua5YBTUS56FH8VQKQG1aaIo9fH8GEyz
233
233
  ultralytics/utils/callbacks/raytune.py,sha256=ODVYzy-CoM4Uge0zjkh3Hnh9nF2M0vhDrSenXnvcizw,705
234
234
  ultralytics/utils/callbacks/tensorboard.py,sha256=0kn4IR10no99UCIheojWRujgybmUHSx5fPI6Vsq6l_g,4135
235
235
  ultralytics/utils/callbacks/wb.py,sha256=9-fjQIdLjr3b73DTE3rHO171KvbH1VweJ-bmbv-rqTw,6747
236
- ultralytics-8.3.5.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
237
- ultralytics-8.3.5.dist-info/METADATA,sha256=vRIip27lGy6ai-vDDQrkLt_2hjxmd6TbIeur2uCcNdI,34685
238
- ultralytics-8.3.5.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
239
- ultralytics-8.3.5.dist-info/entry_points.txt,sha256=YM_wiKyTe9yRrsEfqvYolNO5ngwfoL4-NwgKzc8_7sI,93
240
- ultralytics-8.3.5.dist-info/top_level.txt,sha256=XP49TwiMw4QGsvTLSYiJhz1xF_k7ev5mQ8jJXaXi45Q,12
241
- ultralytics-8.3.5.dist-info/RECORD,,
236
+ ultralytics-8.3.6.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
237
+ ultralytics-8.3.6.dist-info/METADATA,sha256=8c1KpR7WhT8HlvpimiFOS4G3vd8KtQ8YxBUHYnQ_dDc,34699
238
+ ultralytics-8.3.6.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
239
+ ultralytics-8.3.6.dist-info/entry_points.txt,sha256=YM_wiKyTe9yRrsEfqvYolNO5ngwfoL4-NwgKzc8_7sI,93
240
+ ultralytics-8.3.6.dist-info/top_level.txt,sha256=XP49TwiMw4QGsvTLSYiJhz1xF_k7ev5mQ8jJXaXi45Q,12
241
+ ultralytics-8.3.6.dist-info/RECORD,,