@@ -43,6 +43,8 @@ def __init__(
4343 filter_by_area : bool ,
4444 min_area_pixels : int ,
4545 max_area_pixels : int ,
46+ min_brightness_threshold : int ,
47+ min_average_brightness_threshold : int ,
4648 ) -> None :
4749 """
4850 Initializes the configuration for DetectTargetBrightspot.
@@ -62,6 +64,8 @@ def __init__(
6264 filter_by_area: Whether to filter by area.
6365 min_area_pixels: Minimum area in pixels.
6466 max_area_pixels: Maximum area in pixels.
67+ min_brightness_threshold: Minimum brightness threshold for bright spots.
68+ min_average_brightness_threshold: Minimum absolute average brightness of detected blobs.
6569 """
6670 self .brightspot_percentile_threshold = brightspot_percentile_threshold
6771 self .filter_by_color = filter_by_color
@@ -78,6 +82,8 @@ def __init__(
7882 self .filter_by_area = filter_by_area
7983 self .min_area_pixels = min_area_pixels
8084 self .max_area_pixels = max_area_pixels
85+ self .min_brightness_threshold = min_brightness_threshold
86+ self .min_average_brightness_threshold = min_average_brightness_threshold
8187
8288
8389# pylint: enable=too-many-instance-attributes
@@ -128,20 +134,24 @@ def run(
128134 # pylint: disable-next=broad-exception-caught
129135 except Exception as exception :
130136 self .__local_logger .error (
131- f"{ time . time () } : Failed to convert to greyscale, exception: { exception } "
137+ f"Failed to convert to greyscale, exception: { exception } "
132138 )
133139 return False , None
134140
141+ # Calculate the percentile threshold for bright spots
135142 brightspot_threshold = np .percentile (
136143 grey_image , self .__config .brightspot_percentile_threshold
137144 )
138145
139- # Apply thresholding to isolate bright spots
146+ # Compute the maximum of the percentile threshold and the minimum brightness threshold
147+ combined_threshold = max (brightspot_threshold , self .__config .min_brightness_threshold )
148+
149+ # Apply combined thresholding to isolate bright spots
140150 threshold_used , bw_image = cv2 .threshold (
141- grey_image , brightspot_threshold , 255 , cv2 .THRESH_BINARY
151+ grey_image , combined_threshold , 255 , cv2 .THRESH_BINARY
142152 )
143153 if threshold_used == 0 :
144- self .__local_logger .error (f" { time . time () } : Failed to threshold image." )
154+ self .__local_logger .error (" Failed to percentile threshold image." )
145155 return False , None
146156
147157 # Set up SimpleBlobDetector
@@ -166,33 +176,69 @@ def run(
166176
167177 # A lack of detections is not an error, but should still not be forwarded
168178 if len (keypoints ) == 0 :
169- self .__local_logger .info (f" { time . time () } : No brightspots detected." )
179+ self .__local_logger .info (" No brightspots detected (before blob average filter) ." )
170180 return False , None
171181
172- # Annotate the image (green circle) with detected keypoints
173- image_annotated = cv2 .drawKeypoints (
174- image , keypoints , None , (0 , 255 , 0 ), cv2 .DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
175- )
182+ # Compute the average brightness of each blob
183+ average_brightness_list = []
184+
185+ for i , keypoint in enumerate (keypoints ):
186+ x , y = keypoint .pt # Center of the blob
187+ radius = keypoint .size / 2 # Radius of the blob
188+
189+ # Define a square region of interest (ROI) around the blob
190+ x_min = int (max (0 , x - radius ))
191+ x_max = int (min (grey_image .shape [1 ], x + radius ))
192+ y_min = int (max (0 , y - radius ))
193+ y_max = int (min (grey_image .shape [0 ], y + radius ))
194+
195+ # Create a circular mask for the blob
196+ mask = np .zeros ((y_max - y_min , x_max - x_min ), dtype = np .uint8 )
197+ # Circle centered at middle of mask
198+ cv2 .circle (mask , (int (radius ), int (radius )), int (radius ), 255 , - 1 )
199+
200+ # Extract the ROI from the grayscale image
201+ roi = grey_image [y_min :y_max , x_min :x_max ]
202+
203+ # Apply the mask to the ROI
204+ masked_roi = cv2 .bitwise_and (roi , roi , mask = mask )
205+
206+ # Calculate the mean brightness of the blob
207+ mean_brightness = cv2 .mean (masked_roi , mask = mask )[0 ]
208+ # append index into list to keep track of associated keypoint
209+ average_brightness_list .append ((mean_brightness , i ))
210+
211+ # filter the blobs by their average brightness
212+ filtered_keypoints = []
213+ for brightness , idx in average_brightness_list :
214+ # Only append associated keypoint if the blob average is bright enough
215+ if brightness >= self .__config .min_average_brightness_threshold :
216+ filtered_keypoints .append (keypoints [idx ])
217+
218+ # A lack of detections is not an error, but should still not be forwarded
219+ if len (filtered_keypoints ) == 0 :
220+ self .__local_logger .info ("No brightspots detected (after blob average filter)." )
221+ return False , None
176222
177223 # Process bright spot detection
178224 result , detections = detections_and_time .DetectionsAndTime .create (data .timestamp )
179225 if not result :
180- self .__local_logger .error (f" { time . time () } : Failed to create detections for image." )
226+ self .__local_logger .error (" Failed to create detections for image." )
181227 return False , None
182228
183229 # Get Pylance to stop complaining
184230 assert detections is not None
185231
186232 # Draw bounding boxes around detected keypoints
187- for keypoint in keypoints :
233+ for keypoint in filtered_keypoints :
188234 x , y = keypoint .pt
189235 size = keypoint .size
190236 bounds = np .array ([x - size / 2 , y - size / 2 , x + size / 2 , y + size / 2 ])
191237 result , detection = detections_and_time .Detection .create (
192238 bounds , DETECTION_LABEL , CONFIDENCE
193239 )
194240 if not result :
195- self .__local_logger .error (f" { time . time () } : Failed to create bounding boxes." )
241+ self .__local_logger .error (" Failed to create bounding boxes." )
196242 return False , None
197243
198244 # Get Pylance to stop complaining
@@ -206,12 +252,21 @@ def run(
206252
207253 # Logging
208254 self .__local_logger .info (
209- f"{ time . time () } : Count: { self .__counter } . Target detection took { end_time - start_time } seconds. Objects detected: { detections } ."
255+ f"Count: { self .__counter } . Target detection took { end_time - start_time } seconds. Objects detected: { detections } ."
210256 )
211257
212258 if self .__filename_prefix != "" :
213259 filename = self .__filename_prefix + str (self .__counter )
214260
261+ # Annotate the image (green circle) with detected keypoints
262+ image_annotated = cv2 .drawKeypoints (
263+ image ,
264+ filtered_keypoints ,
265+ None ,
266+ (0 , 255 , 0 ),
267+ cv2 .DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ,
268+ )
269+
215270 # Annotated image
216271 cv2 .imwrite (filename + ".png" , image_annotated ) # type: ignore
217272
0 commit comments