From baa426fa3256ca8a79f2bf35f259f93598d28c10 Mon Sep 17 00:00:00 2001 From: Soham Dave Date: Mon, 23 Sep 2024 22:11:18 -0400 Subject: [PATCH] Add files via upload --- decision_simple_waypoint.py | 94 ++++++++++++++++++++ decision_waypoint_landing_pads.py | 97 +++++++++++++++++++++ detect_landing_pad.py | 137 ++++++++++++++++++++++++++++++ 3 files changed, 328 insertions(+) create mode 100644 decision_simple_waypoint.py create mode 100644 decision_waypoint_landing_pads.py create mode 100644 detect_landing_pad.py diff --git a/decision_simple_waypoint.py b/decision_simple_waypoint.py new file mode 100644 index 00000000..006b64b0 --- /dev/null +++ b/decision_simple_waypoint.py @@ -0,0 +1,94 @@ +""" +BOOTCAMPERS TO COMPLETE. + +Travel to designated waypoint. +""" + +from .. import commands +from .. import drone_report + +# Disable for bootcamp use +# pylint: disable-next=unused-import +from .. import drone_status +from .. import location +from ..private.decision import base_decision + + +# Disable for bootcamp use +# No enable +# pylint: disable=duplicate-code,unused-argument + + +class DecisionSimpleWaypoint(base_decision.BaseDecision): + """ + Travel to the designed waypoint. + """ + + def __init__(self, waypoint: location.Location, acceptance_radius: float) -> None: + """ + Initialize all persistent variables here with self. + """ + self.waypoint = waypoint + print(f"Waypoint: {waypoint}") + + self.acceptance_radius = acceptance_radius + + # ============ + # ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ + # ============ + + self.waypoint_found =False + self.landing_pad_found = False + + + # ============ + # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ + # ============ + + def run( + self, report: drone_report.DroneReport, landing_pad_locations: "list[location.Location]" + ) -> commands.Command: + """ + Make the drone fly to the waypoint. + + You are allowed to create as many helper methods as you want, + as long as you do not change the __init__() and run() signatures. + + This method will be called in an infinite loop, something like this: + + ```py + while True: + report, landing_pad_locations = get_input() + command = Decision.run(report, landing_pad_locations) + put_output(command) + ``` + """ + # Default command + command = commands.Command.create_null_command() + + # ============ + # ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ + # ============ + + status = report.status + current_position = report.position + waypoint = self.waypoint + + destination_x = waypoint.location_x - current_position.location_x + destination_y = waypoint.location_y - current_position.location_y + distance_squared = destination_x**2 + destination_y**2 + + if status == drone_status.DroneStatus.HALTED: + if distance_squared < self.acceptance_radius**2: + command = commands.Command.create_land_command() + print("The drone is within the accepted range") + else: command = commands.Command.create_set_relative_destination_command( + destination_x, destination_y + ) + print(f"Moving to waypoint{waypoint.location_x},{waypoint.location_y}") + + # ============ + # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ + # ============ + + return command diff --git a/decision_waypoint_landing_pads.py b/decision_waypoint_landing_pads.py new file mode 100644 index 00000000..15e3c6c8 --- /dev/null +++ b/decision_waypoint_landing_pads.py @@ -0,0 +1,97 @@ +""" +BOOTCAMPERS TO COMPLETE. + +Travel to designated waypoint and then land at a nearby landing pad. +""" + +from .. import commands +from .. import drone_report + +# Disable for bootcamp use +# pylint: disable-next=unused-import +from .. import drone_status +from .. import location +from ..private.decision import base_decision + + +# Disable for bootcamp use +# No enable +# pylint: disable=duplicate-code,unused-argument + + +class DecisionWaypointLandingPads(base_decision.BaseDecision): + """ + Travel to the designed waypoint and then land at the nearest landing pad. + """ + + def __init__(self, waypoint: location.Location, acceptance_radius: float) -> None: + """ + Initialize all persistent variables here with self. + """ + self.waypoint = waypoint + print(f"Waypoint: {waypoint}") + + self.acceptance_radius = acceptance_radius + + # ============ + # ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ + # ============ + + print(str(waypoint.location_x)+str(waypoint.location_y)) + + self.has_sent_landing_command = False + + self.find_nearest_landing_pad = False + + self.reached_waypoint = False + + self.moving_to_landing_pad = False + + self.counter = 0 + + def at_point(self, current_x: float, current_y: float): + + + distance_squared = (self.waypoint.location_x - current_x) ** 2 + ( + self.waypoint.location_y - current_y + ) ** 2 + return distance_squared <= self.acceptance_radius ** 2 + + + + # ============ + # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ + # ============ + + def run( + self, report: drone_report.DroneReport, landing_pad_locations: "list[location.Location]" + ) -> commands.Command: + """ + Make the drone fly to the waypoint and then land at the nearest landing pad. + + You are allowed to create as many helper methods as you want, + as long as you do not change the __init__() and run() signatures. + + This method will be called in an infinite loop, something like this: + + ```py + while True: + report, landing_pad_locations = get_input() + command = Decision.run(report, landing_pad_locations) + put_output(command) + ``` + """ + # Default command + command = commands.Command.create_null_command() + + # ============ + # ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ + # ============ + + # Do something based on the report and the state of this class... + + # ============ + # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ + # ============ + + return command diff --git a/detect_landing_pad.py b/detect_landing_pad.py new file mode 100644 index 00000000..f8c6e597 --- /dev/null +++ b/detect_landing_pad.py @@ -0,0 +1,137 @@ +""" +BOOTCAMPERS TO COMPLETE. + +Detects landing pads. +""" + +import pathlib + +import numpy as np +import torch +import ultralytics + +from .. import bounding_box + + +# ============ +# ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ +# ============ +# Bootcampers remove the following lines: +# Allow linters and formatters to pass for bootcamp maintainers +# No enable +# pylint: disable=unused-argument,unused-private-member,unused-variable +# ============ +# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ +# ============ + + +class DetectLandingPad: + """ + Contains the YOLOv8 model for prediction. + """ + + __create_key = object() + + # ============ + # ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ + # ============ + + # Chooses the GPU if it exists, otherwise runs on the CPU + # If you have a CUDA capable GPU but want to force it to + # run on the CPU instead, replace the right side with "cpu" + __DEVICE = 0 if torch.cuda.is_available() else "cpu" + + # ============ + # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ + # ============ + + __MODEL_NAME = "best-2n.pt" + + @classmethod + def create(cls, model_directory: pathlib.Path) -> "tuple[bool, DetectLandingPad | None]": + """ + model_directory: Directory to models. + """ + if not model_directory.is_dir(): + return False, None + + model_path = pathlib.PurePosixPath( + model_directory, + cls.__MODEL_NAME, + ) + + try: + model = ultralytics.YOLO(str(model_path)) + # Library can throw any exception + # pylint: disable-next=broad-exception-caught + except Exception: + return False, None + + return True, DetectLandingPad(cls.__create_key, model) + + def __init__(self, class_private_create_key: object, model: ultralytics.YOLO) -> None: + """ + Private constructor, use create() method. + """ + assert class_private_create_key is DetectLandingPad.__create_key, "Use create() method" + + self.__model = model + + def run(self, image: np.ndarray) -> "tuple[list[bounding_box.BoundingBox], np.ndarray]": + """ + Converts an image into a list of bounding boxes. + + image: The image to run on. + + Return: A tuple of (list of bounding boxes, annotated image) . + The list of bounding boxes can be empty. + """ + # ============ + # ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ + # ============ + + # Ultralytics has documentation and examples + + # Use the model's predict() method to run inference + # Parameters of interest: + # * source + # * conf + # * device + # * verbose + # conf threshold is around 0.7 + predictions = self.__model.predict( + source=image, conf=0.7, device=self.__DEVICE, verbose =False + ) + + # Get the Result object + prediction = predictions[0] + + # Plot the annotated image from the Result object + # Include the confidence value + image_annotated = prediction.plot(boxes=True, conf=True) + + # Get the xyxy boxes list from the Boxes object in the Result object + boxes_xyxy = prediction.boxes.xyxy + + # Detach the xyxy boxes to make a copy, + # move the copy into CPU space, + # and convert to a numpy array + boxes_cpu = boxes_xyxy.detach().cpu().numpy() + + # Loop over the boxes list and create a list of bounding boxes + bounding_boxes = [] + # Hint: .shape gets the dimensions of the numpy array + # for i in range(0, ...): + # # Create BoundingBox object and append to list + # result, box = ... + for boxes_xyxy in boxes_cpu: + success, box = bounding_box.BoundingBox.create(boxes_xyxy) + if not success: + continue + + return [], image_annotated + + + # ============ + # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ + # ============ \ No newline at end of file