Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement brightspot detection #228

Merged
merged 11 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions modules/detect_target/detect_target_brightspot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""
Detects bright spots in images.
"""

import time

import cv2
import numpy as np

from . import base_detect_target
from .. import detections_and_time
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
from .. import image_and_time
from ..common.modules.logger import logger


BRIGHTSPOT_PERCENTILE = 99.9

# Label for brightspots; is 1 since 0 is used for blue landing pads
DETECTION_LABEL = 1
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
# SimpleBlobDetector is a binary detector, so a detection has confidence 1.0 by default
CONFIDENCE = 1
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved


class DetectTargetBrightspot(base_detect_target.BaseDetectTarget):
"""
Detects bright spots in images.
"""

def __init__(
maxlou05 marked this conversation as resolved.
Show resolved Hide resolved
self,
local_logger: logger.Logger,
show_annotations: bool = False,
save_name: str = "",
) -> None:
"""
Initializes the bright spot detector.

show_annotations: Display annotated images.
save_name: Filename prefix for logging detections and annotated images.
"""
self.__counter = 0
self.__local_logger = local_logger
self.__show_annotations = show_annotations
self.__filename_prefix = ""
if save_name != "":
self.__filename_prefix = f"{save_name}_{int(time.time())}_"

def run(
self, data: image_and_time.ImageAndTime
) -> "tuple[True, detections_and_time.DetectionsAndTime] | tuple[False, None]":
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
"""
Runs brightspot detection on the provided image and returns the detections.

data: Image with a timestamp.

Return: Success, detections.
"""
start_time = time.time()

image = data.image
try:
grey_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Catching all exceptions for library call
# pylint: disable-next=broad-exception-caught
except Exception as exception:
self.__local_logger.error(
f"{time.time()}: Failed to convert to greyscale, exception: {exception}"
)
return False, None

brightspot_threshold = np.percentile(grey_image, BRIGHTSPOT_PERCENTILE)

# Apply thresholding to isolate bright spots
threshold_used, bw_image = cv2.threshold(
grey_image, brightspot_threshold, 255, cv2.THRESH_BINARY
)
if threshold_used == 0:
self.__local_logger.error(f"{time.time()}: Failed to threshold image.")
return False, None

# Set up SimpleBlobDetector
params = cv2.SimpleBlobDetector_Params()
params.filterByColor = True
params.blobColor = 255
params.filterByCircularity = False
params.filterByInertia = True
params.minInertiaRatio = 0.2
params.filterByConvexity = False
params.filterByArea = True
params.minArea = 50 # pixels
detector = cv2.SimpleBlobDetector_create(params)
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
keypoints = detector.detect(bw_image)
if len(keypoints) == 0:
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
self.__local_logger.info(f"{time.time()}: No brightspots detected.")
return False, None
maxlou05 marked this conversation as resolved.
Show resolved Hide resolved

# Annotate the image (green circle) with detected keypoints
image_annotated = cv2.drawKeypoints(
image, keypoints, None, (0, 255, 0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)

# Process bright spot detection
result, detections = detections_and_time.DetectionsAndTime.create(data.timestamp)
if not result:
self.__local_logger.error(f"{time.time()}: Failed to create detections for image.")
return False, None
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved

# Get Pylance to stop complaining
assert detections is not None

# Draw bounding boxes around detected keypoints
for keypoint in keypoints:
x, y = keypoint.pt
size = keypoint.size
bounds = np.array([x - size / 2, y - size / 2, x + size / 2, y + size / 2])
result, detection = detections_and_time.Detection.create(
bounds, DETECTION_LABEL, CONFIDENCE
)
if not result:
self.__local_logger.error(f"{time.time()}: Failed to create bounding boxes.")
return False, None

# Get Pylance to stop complaining
assert detections is not None

detections.append(detection)

# Logging is identical to detect_target_ultralytics
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
# pylint: disable=duplicate-code
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
end_time = time.time()

# Logging
self.__local_logger.info(
f"{time.time()}: Count: {self.__counter}. Target detection took {end_time - start_time} seconds. Objects detected: {detections}."
)

if self.__filename_prefix != "":
filename = self.__filename_prefix + str(self.__counter)

# Annotated image
cv2.imwrite(filename + ".png", image_annotated) # type: ignore

self.__counter += 1

if self.__show_annotations:
cv2.imshow("Annotated", image_annotated) # type: ignore

# pylint: enable=duplicate-code

return True, detections
8 changes: 8 additions & 0 deletions modules/detect_target/detect_target_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from . import base_detect_target
from . import detect_target_ultralytics
from . import detect_target_brightspot
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
from ..common.modules.logger import logger


Expand All @@ -15,6 +16,7 @@ class DetectTargetOption(enum.Enum):
"""

ML_ULTRALYTICS = 0
CV_BRIGHTSPOT = 1


def create_detect_target(
Expand All @@ -39,5 +41,11 @@ def create_detect_target(
show_annotations,
save_name,
)
case DetectTargetOption.CV_BRIGHTSPOT:
return True, detect_target_brightspot.DetectTargetBrightspot(
local_logger,
show_annotations,
save_name,
)

return False, None
1 change: 1 addition & 0 deletions tests/brightspot_example/bounding_box_ir1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.000000 1.000000 545.888705 202.055392 555.831266 211.997953
1 change: 1 addition & 0 deletions tests/brightspot_example/bounding_box_ir2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.000000 1.000000 443.409194 379.341292 453.529099 389.461198
1 change: 1 addition & 0 deletions tests/brightspot_example/bounding_box_ir3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.000000 1.000000 270.872054 249.021590 288.934281 267.083817
1 change: 1 addition & 0 deletions tests/brightspot_example/bounding_box_ir4.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.000000 1.000000 630.931005 406.213048 640.793971 416.076014
1 change: 1 addition & 0 deletions tests/brightspot_example/bounding_box_ir5.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.000000 1.000000 407.681973 162.778408 426.180088 181.276524
93 changes: 93 additions & 0 deletions tests/brightspot_example/generate_expected.py
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
Generates expected output for the brightspot detector.
"""

import pathlib

import cv2
import numpy as np

from modules import image_and_time
from modules.common.modules.logger import logger
from modules.detect_target import detect_target_brightspot


NUMBER_OF_IMAGES = 7
TEST_PATH = pathlib.Path("tests", "brightspot_example")
IMAGE_FILES = [pathlib.Path(f"ir{i}.png") for i in range(1, NUMBER_OF_IMAGES + 1)]
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
ANNOTATED_IMAGE_PATHS = [
pathlib.Path(TEST_PATH, f"ir{i}_annotated.png") for i in range(1, NUMBER_OF_IMAGES + 1)
]
EXPECTED_DETECTIONS_PATHS = [
pathlib.Path(TEST_PATH, f"bounding_box_ir{i}.txt") for i in range(1, NUMBER_OF_IMAGES + 1)
]


def main() -> int:
"""
Main function.
"""
result, temp_logger = logger.Logger.create("test_logger", False)
if not temp_logger:
print("ERROR: Failed to create logger.")
return 1

detector = detect_target_brightspot.DetectTargetBrightspot(
local_logger=temp_logger, show_annotations=False, save_name=""
)

for image_file, annotated_image_path, expected_detections_path in zip(
IMAGE_FILES, ANNOTATED_IMAGE_PATHS, EXPECTED_DETECTIONS_PATHS
):
image_path = pathlib.Path(TEST_PATH, image_file)
image = cv2.imread(str(image_path)) # type: ignore
result, image_data = image_and_time.ImageAndTime.create(image)
if not result:
temp_logger.error(f"Failed to load image {image_path}.")
continue

# Get Pylance to stop complaining
assert image_data is not None

result, detections = detector.run(image_data)
if not result:
temp_logger.error(f"Detection failed or returned no detections for {image_path}.")
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
continue

# Get Pylance to stop complaining
assert detections is not None

detections_list = []
image_annotated = image.copy()
for detection in detections.detections:
confidence = detection.confidence
label = detection.label
x_1 = detection.x_1
y_1 = detection.y_1
x_2 = detection.x_2
y_2 = detection.y_2
detections_list.append([confidence, label, x_1, y_1, x_2, y_2])

cv2.rectangle(image_annotated, (int(x_1), int(y_1)), (int(x_2), int(y_2)), (0, 255, 0), 1) # type: ignore

detections_array = np.array(detections_list)

np.savetxt(expected_detections_path, detections_array, fmt="%.6f")
temp_logger.info(f"Expected detections saved to {expected_detections_path}.")

result = cv2.imwrite(str(annotated_image_path), image_annotated) # type: ignore
if not result:
temp_logger.error(f"Failed to wrtie image to {annotated_image_path}.")
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
continue

temp_logger.info(f"Annotated image saved to {annotated_image_path}.")

return 0


if __name__ == "__main__":
result_main = main()
if result_main < 0:
print(f"ERROR: Status code: {result_main}")
else:
print("Done!")
Binary file added tests/brightspot_example/ir1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir1_annotated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir2_annotated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir3_annotated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir4_annotated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir5_annotated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/brightspot_example/ir7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading