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

@@ -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):
@@ -69,7 +69,7 @@ def autobatch(model, imgsz=640, fraction=0.60, batch_size=DEFAULT_CFG.batch):
69
69
  batch_sizes = [1, 2, 4, 8, 16]
70
70
  try:
71
71
  img = [torch.empty(b, 3, imgsz, imgsz) for b in batch_sizes]
72
- results = profile(img, model, n=3, device=device)
72
+ results = profile(img, model, n=1, device=device)
73
73
 
74
74
  # Fit a solution
75
75
  y = [x[2] for x in results if x] # memory [2]
@@ -89,3 +89,5 @@ def autobatch(model, imgsz=640, fraction=0.60, batch_size=DEFAULT_CFG.batch):
89
89
  except Exception as e:
90
90
  LOGGER.warning(f"{prefix}WARNING ⚠️ error detected: {e}, using default batch-size {batch_size}.")
91
91
  return batch_size
92
+ finally:
93
+ torch.cuda.empty_cache()
@@ -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):