ultralytics 8.3.87__py3-none-any.whl → 8.3.88__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. tests/test_solutions.py +34 -45
  2. ultralytics/__init__.py +1 -1
  3. ultralytics/cfg/__init__.py +46 -39
  4. ultralytics/data/augment.py +2 -2
  5. ultralytics/solutions/__init__.py +14 -6
  6. ultralytics/solutions/ai_gym.py +39 -28
  7. ultralytics/solutions/analytics.py +22 -18
  8. ultralytics/solutions/distance_calculation.py +25 -25
  9. ultralytics/solutions/heatmap.py +40 -38
  10. ultralytics/solutions/instance_segmentation.py +69 -0
  11. ultralytics/solutions/object_blurrer.py +89 -0
  12. ultralytics/solutions/object_counter.py +35 -33
  13. ultralytics/solutions/object_cropper.py +84 -0
  14. ultralytics/solutions/parking_management.py +21 -9
  15. ultralytics/solutions/queue_management.py +20 -39
  16. ultralytics/solutions/region_counter.py +54 -51
  17. ultralytics/solutions/security_alarm.py +40 -30
  18. ultralytics/solutions/solutions.py +594 -16
  19. ultralytics/solutions/speed_estimation.py +34 -31
  20. ultralytics/solutions/streamlit_inference.py +34 -28
  21. ultralytics/solutions/trackzone.py +29 -18
  22. ultralytics/solutions/vision_eye.py +69 -0
  23. ultralytics/trackers/utils/kalman_filter.py +23 -23
  24. ultralytics/utils/instance.py +3 -3
  25. ultralytics/utils/plotting.py +0 -414
  26. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/METADATA +1 -1
  27. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/RECORD +31 -27
  28. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/LICENSE +0 -0
  29. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/WHEEL +0 -0
  30. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/entry_points.txt +0 -0
  31. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,84 @@
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from ultralytics.solutions.solutions import BaseSolution, SolutionResults
7
+ from ultralytics.utils.plotting import save_one_box
8
+
9
+
10
+ class ObjectCropper(BaseSolution):
11
+ """
12
+ A class to manage the cropping of detected objects in a real-time video stream or images.
13
+
14
+ This class extends the BaseSolution class and provides functionality for cropping objects based on detected bounding
15
+ boxes. The cropped images are saved to a specified directory for further analysis or usage.
16
+
17
+ Attributes:
18
+ crop_dir (str): Directory where cropped object images are stored.
19
+ crop_idx (int): Counter for the total number of cropped objects.
20
+ iou (float): IoU (Intersection over Union) threshold for non-maximum suppression.
21
+ conf (float): Confidence threshold for filtering detections.
22
+
23
+ Methods:
24
+ process: Crops detected objects from the input image and saves them to the output directory.
25
+
26
+ Examples:
27
+ >>> cropper = ObjectCropper()
28
+ >>> frame = cv2.imread("frame.jpg")
29
+ >>> processed_results = cropper.process(frame)
30
+ >>> print(f"Total cropped objects: {cropper.crop_idx}")
31
+ """
32
+
33
+ def __init__(self, **kwargs):
34
+ """
35
+ Initializes the ObjectCropper class for cropping objects from detected bounding boxes.
36
+
37
+ Args:
38
+ **kwargs (Any): Keyword arguments passed to the parent class and used for configuration.
39
+ crop_dir (str): Path to the directory for saving cropped object images.
40
+ """
41
+ super().__init__(**kwargs)
42
+
43
+ self.crop_dir = kwargs.get("crop_dir", "cropped-detections") # Directory for storing cropped detections
44
+ if not os.path.exists(self.crop_dir):
45
+ os.mkdir(self.crop_dir) # Create directory if it does not exist
46
+ if self.CFG["show"]:
47
+ self.LOGGER.info(
48
+ f"⚠️ show=True disabled for crop solution, results will be saved in the directory named: {self.crop_dir}"
49
+ )
50
+ self.crop_idx = 0 # Initialize counter for total cropped objects
51
+ self.iou = self.CFG["iou"]
52
+ self.conf = self.CFG["conf"] if self.CFG["conf"] is not None else 0.25
53
+
54
+ def process(self, im0):
55
+ """
56
+ Crops detected objects from the input image and saves them as separate images.
57
+
58
+ Args:
59
+ im0 (numpy.ndarray): The input image containing detected objects.
60
+
61
+ Returns:
62
+ (SolutionResults): A SolutionResults object containing the total number of cropped objects and processed image.
63
+
64
+ Examples:
65
+ >>> cropper = ObjectCropper()
66
+ >>> frame = cv2.imread("image.jpg")
67
+ >>> results = cropper.process(frame)
68
+ >>> print(f"Total cropped objects: {results.total_crop_objects}")
69
+ """
70
+ results = self.model.predict(
71
+ im0, classes=self.classes, conf=self.conf, iou=self.iou, device=self.CFG["device"]
72
+ )[0]
73
+
74
+ for box in results.boxes:
75
+ self.crop_idx += 1
76
+ save_one_box(
77
+ box.xyxy,
78
+ im0,
79
+ file=Path(self.crop_dir) / f"crop_{self.crop_idx}.jpg",
80
+ BGR=True,
81
+ )
82
+
83
+ # Return SolutionResults
84
+ return SolutionResults(plot_im=im0, total_crop_objects=self.crop_idx)
@@ -5,10 +5,9 @@ import json
5
5
  import cv2
6
6
  import numpy as np
7
7
 
8
- from ultralytics.solutions.solutions import BaseSolution
8
+ from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
9
9
  from ultralytics.utils import LOGGER
10
10
  from ultralytics.utils.checks import check_imshow
11
- from ultralytics.utils.plotting import Annotator
12
11
 
13
12
 
14
13
  class ParkingPtsSelection:
@@ -189,7 +188,7 @@ class ParkingManagement(BaseSolution):
189
188
  dc (Tuple[int, int, int]): RGB color tuple for centroid visualization of detected objects.
190
189
 
191
190
  Methods:
192
- process_data: Processes model data for parking lot management and visualization.
191
+ process: Processes the input image for parking lot management and visualization.
193
192
 
194
193
  Examples:
195
194
  >>> from ultralytics.solutions import ParkingManagement
@@ -216,9 +215,9 @@ class ParkingManagement(BaseSolution):
216
215
  self.occ = (0, 255, 0) # occupied region color
217
216
  self.dc = (255, 0, 189) # centroid color for each box
218
217
 
219
- def process_data(self, im0):
218
+ def process(self, im0):
220
219
  """
221
- Processes the model data for parking lot management.
220
+ Processes the input image for parking lot management and visualization.
222
221
 
223
222
  This function analyzes the input image, extracts tracks, and determines the occupancy status of parking
224
223
  regions defined in the JSON file. It annotates the image with occupied and available parking spots,
@@ -227,14 +226,18 @@ class ParkingManagement(BaseSolution):
227
226
  Args:
228
227
  im0 (np.ndarray): The input inference image.
229
228
 
229
+ Returns:
230
+ (SolutionResults): Contains processed image `plot_im`, 'filled_slots' (number of occupied parking slots),
231
+ 'available_slots' (number of available parking slots), and 'total_tracks' (total number of tracked objects).
232
+
230
233
  Examples:
231
234
  >>> parking_manager = ParkingManagement(json_file="parking_regions.json")
232
235
  >>> image = cv2.imread("parking_lot.jpg")
233
- >>> parking_manager.process_data(image)
236
+ >>> results = parking_manager.process(image)
234
237
  """
235
238
  self.extract_tracks(im0) # extract tracks from im0
236
239
  es, fs = len(self.json), 0 # empty slots, filled slots
237
- annotator = Annotator(im0, self.line_width) # init annotator
240
+ annotator = SolutionAnnotator(im0, self.line_width) # init annotator
238
241
 
239
242
  for region in self.json:
240
243
  # Convert points to a NumPy array with the correct dtype and reshape properly
@@ -257,5 +260,14 @@ class ParkingManagement(BaseSolution):
257
260
  self.pr_info["Occupancy"], self.pr_info["Available"] = fs, es
258
261
 
259
262
  annotator.display_analytics(im0, self.pr_info, (104, 31, 17), (255, 255, 255), 10)
260
- self.display_output(im0) # display output with base class function
261
- return im0 # return output image for more usage
263
+
264
+ plot_im = annotator.result()
265
+ self.display_output(plot_im) # display output with base class function
266
+
267
+ # Return SolutionResults
268
+ return SolutionResults(
269
+ plot_im=plot_im,
270
+ filled_slots=self.pr_info["Occupancy"],
271
+ available_slots=self.pr_info["Available"],
272
+ total_tracks=len(self.track_ids),
273
+ )
@@ -1,7 +1,7 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
- from ultralytics.solutions.solutions import BaseSolution
4
- from ultralytics.utils.plotting import Annotator, colors
3
+ from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
4
+ from ultralytics.utils.plotting import colors
5
5
 
6
6
 
7
7
  class QueueManager(BaseSolution):
@@ -15,13 +15,12 @@ class QueueManager(BaseSolution):
15
15
  counts (int): The current count of objects in the queue.
16
16
  rect_color (Tuple[int, int, int]): RGB color tuple for drawing the queue region rectangle.
17
17
  region_length (int): The number of points defining the queue region.
18
- annotator (Annotator): An instance of the Annotator class for drawing on frames.
19
18
  track_line (List[Tuple[int, int]]): List of track line coordinates.
20
19
  track_history (Dict[int, List[Tuple[int, int]]]): Dictionary storing tracking history for each object.
21
20
 
22
21
  Methods:
23
22
  initialize_region: Initializes the queue region.
24
- process_queue: Processes a single frame for queue management.
23
+ process: Processes a single frame for queue management.
25
24
  extract_tracks: Extracts object tracks from the current frame.
26
25
  store_tracking_history: Stores the tracking history for an object.
27
26
  display_output: Displays the processed output.
@@ -33,18 +32,18 @@ class QueueManager(BaseSolution):
33
32
  >>> success, im0 = cap.read()
34
33
  >>> if not success:
35
34
  >>> break
36
- >>> out = queue.process_queue(im0)
35
+ >>> results = queue_manager.process(im0)
37
36
  """
38
37
 
39
38
  def __init__(self, **kwargs):
40
39
  """Initializes the QueueManager with parameters for tracking and counting objects in a video stream."""
41
40
  super().__init__(**kwargs)
42
41
  self.initialize_region()
43
- self.counts = 0 # Queue counts Information
44
- self.rect_color = (255, 255, 255) # Rectangle color
42
+ self.counts = 0 # Queue counts information
43
+ self.rect_color = (255, 255, 255) # Rectangle color for visualization
45
44
  self.region_length = len(self.region) # Store region length for further usage
46
45
 
47
- def process_queue(self, im0):
46
+ def process(self, im0):
48
47
  """
49
48
  Processes the queue management for a single frame of video.
50
49
 
@@ -52,48 +51,28 @@ class QueueManager(BaseSolution):
52
51
  im0 (numpy.ndarray): Input image for processing, typically a frame from a video stream.
53
52
 
54
53
  Returns:
55
- (numpy.ndarray): Processed image with annotations, bounding boxes, and queue counts.
56
-
57
- This method performs the following steps:
58
- 1. Resets the queue count for the current frame.
59
- 2. Initializes an Annotator object for drawing on the image.
60
- 3. Extracts tracks from the image.
61
- 4. Draws the counting region on the image.
62
- 5. For each detected object:
63
- - Draws bounding boxes and labels.
64
- - Stores tracking history.
65
- - Draws centroids and tracks.
66
- - Checks if the object is inside the counting region and updates the count.
67
- 6. Displays the queue count on the image.
68
- 7. Displays the processed output.
54
+ (SolutionResults): Contains processed image `im0`, 'queue_count' (int, number of objects in the queue) and
55
+ 'total_tracks' (int, total number of tracked objects).
69
56
 
70
57
  Examples:
71
58
  >>> queue_manager = QueueManager()
72
59
  >>> frame = cv2.imread("frame.jpg")
73
- >>> processed_frame = queue_manager.process_queue(frame)
60
+ >>> results = queue_manager.process(frame)
74
61
  """
75
62
  self.counts = 0 # Reset counts every frame
76
- self.annotator = Annotator(im0, line_width=self.line_width) # Initialize annotator
77
- self.extract_tracks(im0) # Extract tracks
78
-
79
- self.annotator.draw_region(
80
- reg_pts=self.region, color=self.rect_color, thickness=self.line_width * 2
81
- ) # Draw region
63
+ self.extract_tracks(im0) # Extract tracks from the current frame
64
+ annotator = SolutionAnnotator(im0, line_width=self.line_width) # Initialize annotator
65
+ annotator.draw_region(reg_pts=self.region, color=self.rect_color, thickness=self.line_width * 2) # Draw region
82
66
 
83
67
  for box, track_id, cls in zip(self.boxes, self.track_ids, self.clss):
84
68
  # Draw bounding box and counting region
85
- self.annotator.box_label(box, label=self.names[cls], color=colors(track_id, True))
69
+ annotator.box_label(box, label=self.names[cls], color=colors(track_id, True))
86
70
  self.store_tracking_history(track_id, box) # Store track history
87
71
 
88
- # Draw tracks of objects
89
- self.annotator.draw_centroid_and_tracks(
90
- self.track_line, color=colors(int(track_id), True), track_thickness=self.line_width
91
- )
92
-
93
72
  # Cache frequently accessed attributes
94
73
  track_history = self.track_history.get(track_id, [])
95
74
 
96
- # store previous position of track and check if the object is inside the counting region
75
+ # Store previous position of track and check if the object is inside the counting region
97
76
  prev_position = None
98
77
  if len(track_history) > 1:
99
78
  prev_position = track_history[-2]
@@ -101,12 +80,14 @@ class QueueManager(BaseSolution):
101
80
  self.counts += 1
102
81
 
103
82
  # Display queue counts
104
- self.annotator.queue_counts_display(
83
+ annotator.queue_counts_display(
105
84
  f"Queue Counts : {str(self.counts)}",
106
85
  points=self.region,
107
86
  region_color=self.rect_color,
108
87
  txt_color=(104, 31, 17),
109
88
  )
110
- self.display_output(im0) # display output with base class function
89
+ plot_im = annotator.result()
90
+ self.display_output(plot_im) # Display output with base class function
111
91
 
112
- return im0 # return output image for more usage
92
+ # Return a SolutionResults object with processed data
93
+ return SolutionResults(plot_im=plot_im, queue_count=self.counts, total_tracks=len(self.track_ids))
@@ -1,33 +1,33 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
- from ultralytics.solutions.solutions import BaseSolution
4
- from ultralytics.utils import LOGGER
5
- from ultralytics.utils.plotting import Annotator, colors
3
+ import numpy as np
4
+
5
+ from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
6
+ from ultralytics.utils.plotting import colors
6
7
 
7
8
 
8
9
  class RegionCounter(BaseSolution):
9
10
  """
10
- A class designed for real-time counting of objects within user-defined regions in a video stream.
11
+ A class for real-time counting of objects within user-defined regions in a video stream.
11
12
 
12
- This class inherits from `BaseSolution` and offers functionalities to define polygonal regions in a video
13
- frame, track objects, and count those objects that pass through each defined region. This makes it useful
14
- for applications that require counting in specified areas, such as monitoring zones or segmented sections.
13
+ This class inherits from `BaseSolution` and provides functionality to define polygonal regions in a video frame,
14
+ track objects, and count those objects that pass through each defined region. Useful for applications requiring
15
+ counting in specified areas, such as monitoring zones or segmented sections.
15
16
 
16
17
  Attributes:
17
- region_template (dict): A template for creating new counting regions with default attributes including
18
- the name, polygon coordinates, and display colors.
19
- counting_regions (list): A list storing all defined regions, where each entry is based on `region_template`
20
- and includes specific region settings like name, coordinates, and color.
18
+ region_template (Dict): Template for creating new counting regions with default attributes including name,
19
+ polygon coordinates, and display colors.
20
+ counting_regions (List): List storing all defined regions, where each entry is based on `region_template`
21
+ and includes specific region settings like name, coordinates, and color.
22
+ region_counts (Dict): Dictionary storing the count of objects for each named region.
21
23
 
22
24
  Methods:
23
- add_region: Adds a new counting region with specified attributes, such as the region's name, polygon points,
24
- region color, and text color.
25
- count: Processes video frames to count objects in each region, drawing regions and displaying counts
26
- on the frame. Handles object detection, region definition, and containment checks.
25
+ add_region: Adds a new counting region with specified attributes.
26
+ process: Processes video frames to count objects in each region.
27
27
  """
28
28
 
29
29
  def __init__(self, **kwargs):
30
- """Initializes the RegionCounter class for real-time counting in different regions of the video streams."""
30
+ """Initializes the RegionCounter class for real-time counting in different regions of video streams."""
31
31
  super().__init__(**kwargs)
32
32
  self.region_template = {
33
33
  "name": "Default Region",
@@ -37,6 +37,7 @@ class RegionCounter(BaseSolution):
37
37
  "region_color": (255, 255, 255),
38
38
  "text_color": (0, 0, 0),
39
39
  }
40
+ self.region_counts = {}
40
41
  self.counting_regions = []
41
42
 
42
43
  def add_region(self, name, polygon_points, region_color, text_color):
@@ -45,9 +46,9 @@ class RegionCounter(BaseSolution):
45
46
 
46
47
  Args:
47
48
  name (str): Name assigned to the new region.
48
- polygon_points (list[tuple]): List of (x, y) coordinates defining the region's polygon.
49
- region_color (tuple): BGR color for region visualization.
50
- text_color (tuple): BGR color for the text within the region.
49
+ polygon_points (List[Tuple]): List of (x, y) coordinates defining the region's polygon.
50
+ region_color (Tuple): BGR color for region visualization.
51
+ text_color (Tuple): BGR color for the text within the region.
51
52
  """
52
53
  region = self.region_template.copy()
53
54
  region.update(
@@ -60,57 +61,59 @@ class RegionCounter(BaseSolution):
60
61
  )
61
62
  self.counting_regions.append(region)
62
63
 
63
- def count(self, im0):
64
+ def process(self, im0):
64
65
  """
65
66
  Processes the input frame to detect and count objects within each defined region.
66
67
 
67
68
  Args:
68
- im0 (numpy.ndarray): Input image frame where objects and regions are annotated.
69
+ im0 (np.ndarray): Input image frame where objects and regions are annotated.
69
70
 
70
71
  Returns:
71
- im0 (numpy.ndarray): Processed image frame with annotated counting information.
72
+ (SolutionResults): Contains processed image `plot_im`, 'total_tracks' (int, total number of tracked objects),
73
+ and 'region_counts' (Dict, counts of objects per region).
72
74
  """
73
- self.annotator = Annotator(im0, line_width=self.line_width)
74
75
  self.extract_tracks(im0)
76
+ annotator = SolutionAnnotator(im0, line_width=self.line_width)
77
+
78
+ # Ensure self.region is initialized and structured as a dictionary
79
+ if not isinstance(self.region, dict):
80
+ self.region = {"Region#01": self.region or self.initialize_region()}
75
81
 
76
- # Region initialization and conversion
77
- if self.region is None:
78
- self.initialize_region()
79
- regions = {"Region#01": self.region}
80
- else:
81
- regions = self.region if isinstance(self.region, dict) else {"Region#01": self.region}
82
-
83
- # Draw regions and process counts for each defined area
84
- for idx, (region_name, reg_pts) in enumerate(regions.items(), start=1):
85
- if not isinstance(reg_pts, list) or not all(isinstance(pt, tuple) for pt in reg_pts):
86
- LOGGER.warning(f"Invalid region points for {region_name}: {reg_pts}")
87
- continue # Skip invalid entries
82
+ # Draw only valid regions
83
+ for idx, (region_name, reg_pts) in enumerate(self.region.items(), start=1):
88
84
  color = colors(idx, True)
89
- self.annotator.draw_region(reg_pts=reg_pts, color=color, thickness=self.line_width * 2)
90
- self.add_region(region_name, reg_pts, color, self.annotator.get_txt_color())
85
+ annotator.draw_region(reg_pts, color, self.line_width * 2)
86
+ self.add_region(region_name, reg_pts, color, annotator.get_txt_color())
91
87
 
92
- # Prepare regions for containment check
88
+ # Prepare regions for containment check (only process valid ones)
93
89
  for region in self.counting_regions:
94
- region["prepared_polygon"] = self.prep(region["polygon"])
90
+ if "prepared_polygon" not in region:
91
+ region["prepared_polygon"] = self.prep(region["polygon"])
92
+
93
+ # Convert bounding boxes to NumPy array for center points
94
+ boxes_np = np.array([((box[0] + box[2]) / 2, (box[1] + box[3]) / 2) for box in self.boxes], dtype=np.float32)
95
+ points = [self.Point(pt) for pt in boxes_np] # Convert centers to Point objects
95
96
 
96
- # Process bounding boxes and count objects within each region
97
- for box, cls in zip(self.boxes, self.clss):
98
- self.annotator.box_label(box, label=self.names[cls], color=colors(cls, True))
99
- bbox_center = ((box[0] + box[2]) / 2, (box[1] + box[3]) / 2)
97
+ # Process bounding boxes & check containment
98
+ if points:
99
+ for (point, cls), box in zip(zip(points, self.clss), self.boxes):
100
+ annotator.box_label(box, label=self.names[cls], color=colors(cls))
100
101
 
101
- for region in self.counting_regions:
102
- if region["prepared_polygon"].contains(self.Point(bbox_center)):
103
- region["counts"] += 1
102
+ for region in self.counting_regions:
103
+ if region["prepared_polygon"].contains(point):
104
+ region["counts"] += 1
105
+ self.region_counts[region["name"]] = region["counts"]
104
106
 
105
- # Display counts in each region
107
+ # Display region counts
106
108
  for region in self.counting_regions:
107
- self.annotator.text_label(
109
+ annotator.text_label(
108
110
  region["polygon"].bounds,
109
111
  label=str(region["counts"]),
110
112
  color=region["region_color"],
111
113
  txt_color=region["text_color"],
112
114
  )
113
- region["counts"] = 0 # Reset count for next frame
115
+ region["counts"] = 0 # Reset for next frame
116
+ plot_im = annotator.result()
117
+ self.display_output(plot_im)
114
118
 
115
- self.display_output(im0)
116
- return im0
119
+ return SolutionResults(plot_im=plot_im, total_tracks=len(self.track_ids), region_counts=self.region_counts)
@@ -1,36 +1,44 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
- from ultralytics.solutions.solutions import BaseSolution
3
+ from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
4
4
  from ultralytics.utils import LOGGER
5
- from ultralytics.utils.plotting import Annotator, colors
5
+ from ultralytics.utils.plotting import colors
6
6
 
7
7
 
8
8
  class SecurityAlarm(BaseSolution):
9
9
  """
10
10
  A class to manage security alarm functionalities for real-time monitoring.
11
11
 
12
- This class extends the BaseSolution class and provides features to monitor
13
- objects in a frame, send email notifications when specific thresholds are
14
- exceeded for total detections, and annotate the output frame for visualization.
12
+ This class extends the BaseSolution class and provides features to monitor objects in a frame, send email
13
+ notifications when specific thresholds are exceeded for total detections, and annotate the output frame for
14
+ visualization.
15
15
 
16
16
  Attributes:
17
- email_sent (bool): Flag to track if an email has already been sent for the current event.
18
- records (int): Threshold for the number of detected objects to trigger an alert.
17
+ email_sent (bool): Flag to track if an email has already been sent for the current event.
18
+ records (int): Threshold for the number of detected objects to trigger an alert.
19
+ server (smtplib.SMTP): SMTP server connection for sending email alerts.
20
+ to_email (str): Recipient's email address for alerts.
21
+ from_email (str): Sender's email address for alerts.
19
22
 
20
23
  Methods:
21
- authenticate: Sets up email server authentication for sending alerts.
22
- send_email: Sends an email notification with details and an image attachment.
23
- monitor: Monitors the frame, processes detections, and triggers alerts if thresholds are crossed.
24
+ authenticate: Sets up email server authentication for sending alerts.
25
+ send_email: Sends an email notification with details and an image attachment.
26
+ process: Monitors the frame, processes detections, and triggers alerts if thresholds are crossed.
24
27
 
25
28
  Examples:
26
29
  >>> security = SecurityAlarm()
27
30
  >>> security.authenticate("abc@gmail.com", "1111222233334444", "xyz@gmail.com")
28
31
  >>> frame = cv2.imread("frame.jpg")
29
- >>> processed_frame = security.monitor(frame)
32
+ >>> results = security.process(frame)
30
33
  """
31
34
 
32
35
  def __init__(self, **kwargs):
33
- """Initializes the SecurityAlarm class with parameters for real-time object monitoring."""
36
+ """
37
+ Initializes the SecurityAlarm class with parameters for real-time object monitoring.
38
+
39
+ Args:
40
+ **kwargs (Any): Additional keyword arguments passed to the parent class.
41
+ """
34
42
  super().__init__(**kwargs)
35
43
  self.email_sent = False
36
44
  self.records = self.CFG["records"]
@@ -47,8 +55,7 @@ class SecurityAlarm(BaseSolution):
47
55
  password (str): Password for the sender's email account.
48
56
  to_email (str): Recipient's email address.
49
57
 
50
- This method initializes a secure connection with the SMTP server
51
- and logs in using the provided credentials.
58
+ This method initializes a secure connection with the SMTP server and logs in using the provided credentials.
52
59
 
53
60
  Examples:
54
61
  >>> alarm = SecurityAlarm()
@@ -70,8 +77,8 @@ class SecurityAlarm(BaseSolution):
70
77
  im0 (numpy.ndarray): The input image or frame to be attached to the email.
71
78
  records (int): The number of detected objects to be included in the email message.
72
79
 
73
- This method encodes the input image, composes the email message with
74
- details about the detection, and sends it to the specified recipient.
80
+ This method encodes the input image, composes the email message with details about the detection, and sends it
81
+ to the specified recipient.
75
82
 
76
83
  Examples:
77
84
  >>> alarm = SecurityAlarm()
@@ -105,40 +112,43 @@ class SecurityAlarm(BaseSolution):
105
112
  self.server.send_message(message)
106
113
  LOGGER.info("✅ Email sent successfully!")
107
114
  except Exception as e:
108
- print(f"❌ Failed to send email: {e}")
115
+ LOGGER.error(f"❌ Failed to send email: {e}")
109
116
 
110
- def monitor(self, im0):
117
+ def process(self, im0):
111
118
  """
112
119
  Monitors the frame, processes object detections, and triggers alerts if thresholds are exceeded.
113
120
 
114
121
  Args:
115
122
  im0 (numpy.ndarray): The input image or frame to be processed and annotated.
116
123
 
117
- This method processes the input frame, extracts detections, annotates the frame
118
- with bounding boxes, and sends an email notification if the number of detected objects
119
- surpasses the specified threshold and an alert has not already been sent.
120
-
121
124
  Returns:
122
- (numpy.ndarray): The processed frame with annotations.
125
+ (SolutionResults): Contains processed image `plot_im`, 'total_tracks' (total number of tracked objects) and
126
+ 'email_sent' (whether an email alert was triggered).
127
+
128
+ This method processes the input frame, extracts detections, annotates the frame with bounding boxes, and sends
129
+ an email notification if the number of detected objects surpasses the specified threshold and an alert has not
130
+ already been sent.
123
131
 
124
132
  Examples:
125
133
  >>> alarm = SecurityAlarm()
126
134
  >>> frame = cv2.imread("path/to/image.jpg")
127
- >>> processed_frame = alarm.monitor(frame)
135
+ >>> results = alarm.process(frame)
128
136
  """
129
- self.annotator = Annotator(im0, line_width=self.line_width) # Initialize annotator
130
137
  self.extract_tracks(im0) # Extract tracks
138
+ annotator = SolutionAnnotator(im0, line_width=self.line_width) # Initialize annotator
131
139
 
132
- # Iterate over bounding boxes, track ids and classes index
140
+ # Iterate over bounding boxes and classes index
133
141
  for box, cls in zip(self.boxes, self.clss):
134
142
  # Draw bounding box
135
- self.annotator.box_label(box, label=self.names[cls], color=colors(cls, True))
143
+ annotator.box_label(box, label=self.names[cls], color=colors(cls, True))
136
144
 
137
145
  total_det = len(self.clss)
138
- if total_det > self.records and not self.email_sent: # Only send email If not sent before
146
+ if total_det > self.records and not self.email_sent: # Only send email if not sent before
139
147
  self.send_email(im0, total_det)
140
148
  self.email_sent = True
141
149
 
142
- self.display_output(im0) # display output with base class function
150
+ plot_im = annotator.result()
151
+ self.display_output(plot_im) # Display output with base class function
143
152
 
144
- return im0 # return output image for more usage
153
+ # Return a SolutionResults
154
+ return SolutionResults(plot_im=plot_im, total_tracks=len(self.track_ids), email_sent=self.email_sent)