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

main_2024.py detect target #156

Merged
merged 36 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2dbe3c9
added annotated image option
Ethan118 Nov 13, 2023
10c489e
integrated in main_2024
Ethan118 Nov 13, 2023
bfa1978
added cli option
Ethan118 Nov 27, 2023
ad9ac08
added window closing
Ethan118 Nov 27, 2023
f23f62d
fixed unit tests
Ethan118 Nov 28, 2023
dd6e5a1
fixed image annotation and modified test cases
Ethan118 Nov 28, 2023
3ee9905
converted unit tests to use bounding boxes
Ethan118 Nov 30, 2023
d7ca078
added device to predict arg
Ethan118 Nov 30, 2023
8819c3c
added annotated image option
Ethan118 Nov 13, 2023
d08e556
integrated in main_2024
Ethan118 Nov 13, 2023
bd2a59d
added cli option
Ethan118 Nov 27, 2023
a49c660
added window closing
Ethan118 Nov 27, 2023
20e4018
fixed unit tests
Ethan118 Nov 28, 2023
0da5e01
fixed image annotation and modified test cases
Ethan118 Nov 28, 2023
37a37bc
converted unit tests to use bounding boxes
Ethan118 Nov 30, 2023
cb186f4
added device to predict arg
Ethan118 Nov 30, 2023
724de57
remove annotated image from function return
Ethan118 Dec 4, 2023
5e6c8e7
added hardcoded values for testing target detection
Ethan118 Jan 16, 2024
0133d97
fixed conflicts
Ethan118 Jan 16, 2024
479a361
Changed tests to use absolute error tolerance, Added checks for numbe…
Ethan118 Jan 27, 2024
864bcaa
Merge branch 'main' into detect_target-integration
Ethan118 Jan 27, 2024
0b2f4b7
edited paths to use pathlib
Ethan118 Feb 5, 2024
ba05393
Fixed tests to use detections and time object
Ethan118 Feb 5, 2024
108c7c6
merged with main
Ethan118 Feb 5, 2024
ca549ba
fixed path errors in build
Ethan118 Feb 5, 2024
0ab48c9
Offloaded fixture setup to a separate function and added comments
Ethan118 Feb 6, 2024
435992c
Cleaned up test_detect_target and generate_expected
Ethan118 Feb 7, 2024
73a28a8
Fixed formatting
Ethan118 Feb 7, 2024
69be32a
Fixed renamed variables
Ethan118 Feb 7, 2024
5117781
Changed tolerance
Ethan118 Feb 8, 2024
dea615a
Merge branch 'main' into detect_target-integration
Ethan118 Feb 8, 2024
3080db0
fixed create detections
Ethan118 Feb 8, 2024
6b00c7e
Set tolerance to 0 and fixed formatting
Ethan118 Feb 9, 2024
ef80b03
Changed formatting mostly multiline
Ethan118 Feb 14, 2024
68fab82
Final formatting changes, Updated test_detect_target_worker to use de…
Ethan118 Feb 17, 2024
f8359ec
Some refactoring of main_2024 and final formatting changes
Ethan118 Feb 17, 2024
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
35 changes: 25 additions & 10 deletions main_2024.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove line 12 import (no longer being used).

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import cv2
import yaml

# Used in type annotation of flight interface output
# pylint: disable-next=unused-import
from modules import odometry_and_time
from modules.detect_target import detect_target_worker
from modules.flight_interface import flight_interface_worker
Expand All @@ -18,10 +20,10 @@
from utilities.workers import worker_manager



CONFIG_FILE_PATH = pathlib.Path("config.yaml")


# Main Function
# pylint: disable-next=too-many-locals,too-many-statements
def main() -> int:
"""
Main function for airside code.
Expand All @@ -45,6 +47,11 @@ def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--cpu", action="store_true", help="option to force cpu")
parser.add_argument("--full", action="store_true", help="option to force full precision")
parser.add_argument(
"--show-annotated",
action="store_true",
help="option to show annotated image",
)
args = parser.parse_args()

# Set constants
Expand All @@ -64,6 +71,7 @@ def main() -> int:
DETECT_TARGET_OVERRIDE_FULL_PRECISION = args.full
DETECT_TARGET_SAVE_NAME_PREFIX = config["detect_target"]["save_prefix"]
DETECT_TARGET_SAVE_PREFIX = f"{LOG_DIRECTORY_PATH}/{DETECT_TARGET_SAVE_NAME_PREFIX}"
DETECT_TARGET_SHOW_ANNOTATED = args.show_annotated

FLIGHT_INTERFACE_ADDRESS = config["flight_interface"]["address"]
FLIGHT_INTERFACE_TIMEOUT = config["flight_interface"]["timeout"]
Expand Down Expand Up @@ -112,6 +120,7 @@ def main() -> int:
DETECT_TARGET_DEVICE,
DETECT_TARGET_MODEL_PATH,
DETECT_TARGET_OVERRIDE_FULL_PRECISION,
DETECT_TARGET_SHOW_ANNOTATED,
DETECT_TARGET_SAVE_PREFIX,
video_input_to_detect_target_queue,
detect_target_to_main_queue,
Expand Down Expand Up @@ -139,9 +148,17 @@ def main() -> int:

while True:
try:
image = detect_target_to_main_queue.queue.get_nowait()
detections = detect_target_to_main_queue.queue.get_nowait()
except queue.Empty:
image = None
detections = None
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved

if detections is not None:
print("timestamp: " + str(detections.timestamp))
print("detections: " + str(len(detections.detections)))
for detection in detections.detections:
print(" label: " + str(detection.label))
print(" confidence: " + str(detection.confidence))
print("")

odometry_and_time_info: "odometry_and_time.OdometryAndTime | None" = \
flight_interface_to_main_queue.queue.get()
Expand All @@ -160,12 +177,8 @@ def main() -> int:
print("pitch: " + str(orientation.pitch))
print("")

if image is None:
continue

cv2.imshow("Landing Pad Detector", image)

if cv2.waitKey(1) & 0xFF == ord('q'):
if cv2.waitKey(1) == ord('q'):
print("Exiting main loop")
break

# Teardown
Expand All @@ -179,6 +192,8 @@ def main() -> int:
detect_target_manager.join_workers()
flight_interface_manager.join_workers()

cv2.destroyAllWindows()

return 0


Expand Down
35 changes: 26 additions & 9 deletions modules/detect_target/detect_target.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert to when you were displaying the image in run() . Return only the detections.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amy mentioned above that this causes issues. Is there a work around?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work on you end? If it does then maybe I missed something in the set up?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you try adding cv2.waitKey(1) after the imshow() ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you try adding cv2.waitKey(1) after the imshow() ?

Oh this works! @Ethan118 you can do this instead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove line 7 import (no longer being used).

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import time

import cv2
import numpy as np # TODO: Remove
import ultralytics

from .. import image_and_time
Expand All @@ -17,27 +16,43 @@ class DetectTarget:
"""
Contains the YOLOv8 model for prediction.
"""
def __init__(self, device: "str | int", model_path: str, override_full: bool, save_name: str = ""):
# Required for logging
# pylint: disable-next=too-many-arguments
def __init__(self,
device: "str | int",
model_path: str,
override_full: bool,
show_annotations: bool = False,
save_name: str = ""):
"""
device: name of target device to run inference on (i.e. "cpu" or cuda device 0, 1, 2, 3).
model_path: path to the YOLOv8 model.
override_full: Force full precision floating point calculations.
show_annotations: Display annotated images.
save_name: filename prefix for logging detections and annotated images.
"""
self.__device = device
self.__model = ultralytics.YOLO(model_path)
self.__counter = 0
self.__enable_half_precision = False if self.__device == "cpu" else True
self.__show_annotations = show_annotations
if override_full:
self.__enable_half_precision = False
self.__filename_prefix = ""
if save_name != "":
self.__filename_prefix = save_name + "_" + str(int(time.time())) + "_"

def run(self, data: image_and_time.ImageAndTime) -> "tuple[bool, np.ndarray | None]":
# Required for logging
# pylint: disable-next=too-many-locals
def run(self,
data: image_and_time.ImageAndTime) \
-> "tuple[bool, detections_and_time.DetectionsAndTime | None]":
"""
Returns annotated image.
TODO: Change to DetectionsAndTime
Runs object detection on the provided image and returns the detections.

data: Image with a timestamp.

Return: Success and the detections.
"""
image = data.image
predictions = self.__model.predict(
Expand All @@ -50,7 +65,6 @@ def run(self, data: image_and_time.ImageAndTime) -> "tuple[bool, np.ndarray | No
if len(predictions) == 0:
return False, None

# TODO: Change this to DetectionsAndTime for image and telemetry merge for 2024
image_annotated = predictions[0].plot(conf=True)

# Processing object detection
Expand All @@ -64,6 +78,7 @@ def run(self, data: image_and_time.ImageAndTime) -> "tuple[bool, np.ndarray | No
if not result:
return False, None

# Get Pylance to stop complaining
assert detections is not None

for i in range(0, boxes.shape[0]):
Expand All @@ -80,7 +95,7 @@ def run(self, data: image_and_time.ImageAndTime) -> "tuple[bool, np.ndarray | No
filename = self.__filename_prefix + str(self.__counter)

# Object detections
with open(filename + ".txt", "w") as file:
with open(filename + ".txt", "w", encoding="utf-8") as file:
# Use internal string representation
file.write(repr(detections))

Expand All @@ -89,7 +104,9 @@ def run(self, data: image_and_time.ImageAndTime) -> "tuple[bool, np.ndarray | No

self.__counter += 1

# TODO: Change this to DetectionsAndTime
return True, image_annotated
if self.__show_annotations:
cv2.imshow("Annotated", image_annotated)

return True, detections

# pylint: enable=too-few-public-methods
11 changes: 9 additions & 2 deletions modules/detect_target/detect_target_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,25 @@
def detect_target_worker(device: "str | int",
model_path: str,
override_full: bool,
show_annotations: bool,
save_name: str,
input_queue: queue_proxy_wrapper.QueueProxyWrapper,
output_queue: queue_proxy_wrapper.QueueProxyWrapper,
controller: worker_controller.WorkerController):
"""
Worker process.

device, model_path, override_full, and save_name are initial settings.
device, model_path, override_full, show_annotations, and save_name are initial settings.
input_queue and output_queue are data queues.
controller is how the main process communicates to this worker process.
"""
detector = detect_target.DetectTarget(device, model_path, override_full, save_name)
detector = detect_target.DetectTarget(
device,
model_path,
override_full,
show_annotations,
save_name,
)

while not controller.is_exit_requested():
controller.check_pause()
Expand Down
5 changes: 5 additions & 0 deletions tests/model_example/bounding_box_bus.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
9.213867187500000000e-01 5.000000000000000000e+00 1.518750000000000000e+01 2.286562500000000000e+02 8.083125000000000000e+02 7.484062500000000000e+02
8.930664062500000000e-01 0.000000000000000000e+00 6.674062500000000000e+02 3.898125000000000000e+02 8.091562500000000000e+02 8.783437500000000000e+02
8.867187500000000000e-01 0.000000000000000000e+00 4.978125000000000000e+01 4.016250000000000000e+02 2.444765625000000000e+02 9.019687500000000000e+02
8.779296875000000000e-01 0.000000000000000000e+00 2.223281250000000000e+02 4.083750000000000000e+02 3.455156250000000000e+02 8.606250000000000000e+02
6.113281250000000000e-01 0.000000000000000000e+00 1.582031250000000000e-01 5.509687500000000000e+02 6.517968750000000000e+01 8.690625000000000000e+02
3 changes: 3 additions & 0 deletions tests/model_example/bounding_box_zidane.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
8.896484375000000000e-01 0.000000000000000000e+00 7.480000000000000000e+02 4.150000000000000000e+01 1.140000000000000000e+03 7.130000000000000000e+02
8.847656250000000000e-01 0.000000000000000000e+00 1.450000000000000000e+02 2.000000000000000000e+02 1.108000000000000000e+03 7.130000000000000000e+02
7.167968750000000000e-01 2.700000000000000000e+01 4.375000000000000000e+02 4.347500000000000000e+02 5.300000000000000000e+02 7.170000000000000000e+02
43 changes: 36 additions & 7 deletions tests/model_example/generate_expected.py
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
"""
Generates expected output using pretrained default model and images.
TODO: PointsAndTime
"""
import pathlib

import cv2
import numpy as np
import ultralytics


TEST_PATH = pathlib.Path("tests", "model_example")
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved

# Downloaded from: https://github.com/ultralytics/assets/releases
MODEL_PATH = "tests/model_example/yolov8s_ultralytics_pretrained_default.pt"
MODEL_PATH = pathlib.Path(TEST_PATH, "yolov8s_ultralytics_pretrained_default.pt")

BUS_IMAGE_PATH = pathlib.Path(TEST_PATH, "bus.jpg")
ZIDANE_IMAGE_PATH = pathlib.Path(TEST_PATH, "zidane.jpg")

BUS_IMAGE_ANNOTATED_PATH = pathlib.Path(TEST_PATH, "bus_annotated.png")
ZIDANE_IMAGE_ANNOTATED_PATH = pathlib.Path(TEST_PATH, "zidane_annotated.png")
BUS_BOUNDING_BOX_PATH = pathlib.Path(TEST_PATH, "bounding_box_bus.txt")
ZIDANE_BOUNDING_BOX_PATH = pathlib.Path(TEST_PATH, "bounding_box_zidane.txt")


if __name__ == "__main__":
model = ultralytics.YOLO(MODEL_PATH)
image_bus = cv2.imread("tests/model_example/bus.jpg")
image_zidane = cv2.imread("tests/model_example/zidane.jpg")
image_bus = cv2.imread(BUS_IMAGE_PATH)
image_zidane = cv2.imread(ZIDANE_IMAGE_PATH)
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved

# ultralytics saves as .jpg , bad for testing reproducibility
# Ultralytics saves as .jpg , bad for testing reproducibility
results_bus = model.predict(
source=image_bus,
half=True,
Expand All @@ -34,7 +45,25 @@
image_zidane_annotated = results_zidane[0].plot(conf=True)

# Save image
cv2.imwrite("tests/model_example/bus_annotated.png", image_bus_annotated)
cv2.imwrite("tests/model_example/zidane_annotated.png", image_zidane_annotated)
cv2.imwrite(BUS_IMAGE_ANNOTATED_PATH, image_bus_annotated)
cv2.imwrite(ZIDANE_IMAGE_ANNOTATED_PATH, image_zidane_annotated)

# Generate expected
bounding_box_bus = results_bus[0].boxes.xyxy.detach().cpu().numpy()
bounding_box_zidane = results_zidane[0].boxes.xyxy.detach().cpu().numpy()

conf_bus = results_bus[0].boxes.conf.detach().cpu().numpy()
conf_zidane = results_zidane[0].boxes.conf.detach().cpu().numpy()

labels_bus = results_bus[0].boxes.cls.detach().cpu().numpy()
labels_zidane = results_zidane[0].boxes.cls.detach().cpu().numpy()

predictions_bus = np.insert(bounding_box_bus, 0, [conf_bus, labels_bus], axis=1)
predictions_zidane = np.insert(bounding_box_zidane, 0, [conf_zidane, labels_zidane], axis=1)

# Save expected to text file
# Format: [confidence, label, x1, y1, x2, y2]
np.savetxt(BUS_BOUNDING_BOX_PATH, predictions_bus)
Xierumeng marked this conversation as resolved.
Show resolved Hide resolved
np.savetxt(ZIDANE_BOUNDING_BOX_PATH, predictions_zidane)

print("Done!")
Binary file modified tests/model_example/zidane_annotated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading