diff --git a/modules/bootcamp/decision_simple_waypoint.py b/modules/bootcamp/decision_simple_waypoint.py index 26098c2e..1474c409 100644 --- a/modules/bootcamp/decision_simple_waypoint.py +++ b/modules/bootcamp/decision_simple_waypoint.py @@ -13,7 +13,6 @@ from .. import location from ..private.decision import base_decision - # Disable for bootcamp use # No enable # pylint: disable=duplicate-code,unused-argument @@ -37,7 +36,8 @@ def __init__(self, waypoint: location.Location, acceptance_radius: float) -> Non # ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ # ============ - # Add your own + self.waypoint_found = False + self.landing_pad_found = False # ============ # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ @@ -48,7 +48,6 @@ def run( ) -> 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. @@ -61,14 +60,31 @@ def run( 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... + status = report.status + + current_position = report.position + diff_distance_x = self.waypoint.location_x - current_position.location_x + diff_distance_y = self.waypoint.location_y - current_position.location_y + distance_net_squared = (diff_distance_x**2) + (diff_distance_y**2) + + if status == drone_status.DroneStatus.HALTED: + if distance_net_squared <= self.acceptance_radius**2: # squaring the acceptance radius + command = commands.Command.create_land_command() + print("Drone is landing!") + + else: + command = commands.Command.create_set_relative_destination_command( + diff_distance_x, diff_distance_y + ) + print("Going to waypoint!") + + else: + command = commands.Command.create_null_command() # ============ # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ diff --git a/modules/bootcamp/decision_waypoint_landing_pads.py b/modules/bootcamp/decision_waypoint_landing_pads.py index ade6f118..9bd73ffc 100644 --- a/modules/bootcamp/decision_waypoint_landing_pads.py +++ b/modules/bootcamp/decision_waypoint_landing_pads.py @@ -1,7 +1,7 @@ """ BOOTCAMPERS TO COMPLETE. -Travel to designated waypoint and then land at a nearby landing pad. +Travel to the designated waypoint and then land at a nearby landing pad. """ from .. import commands @@ -21,7 +21,7 @@ class DecisionWaypointLandingPads(base_decision.BaseDecision): """ - Travel to the designed waypoint and then land at the nearest landing pad. + Travel to the designated waypoint and then land at the nearest landing pad. """ def __init__(self, waypoint: location.Location, acceptance_radius: float) -> None: @@ -33,15 +33,18 @@ def __init__(self, waypoint: location.Location, acceptance_radius: float) -> Non self.acceptance_radius = acceptance_radius - # ============ - # ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ - # ============ + self.halt_at_initialization = True + self.has_reached_waypoint = False + self.closest_landing_pad = None - # Add your own - - # ============ - # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ - # ============ + def squared_distance(self, point1: location.Location, point2: location.Location) -> float: + """ + Calculate the squared distance between two locations. + """ + diff_distance_x = point1.location_x - point2.location_x + diff_distance_y = point1.location_y - point2.location_y + distance_net_squared = (diff_distance_x**2) + (diff_distance_y**2) + return distance_net_squared def run( self, report: drone_report.DroneReport, landing_pad_locations: "list[location.Location]" @@ -49,9 +52,6 @@ def run( """ 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 @@ -61,17 +61,55 @@ def run( 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 ↑ - # ============ - + # get current position + current_position = report.position + distance_net_squared = self.squared_distance(self.waypoint, current_position) + + if report.status == drone_status.DroneStatus.HALTED: + # check if at the waypoint + if not self.has_reached_waypoint and distance_net_squared <= self.acceptance_radius**2: + command = commands.Command.create_land_command() + print("Drone is landing at the waypoint!") + self.has_reached_waypoint = True + + elif not self.has_reached_waypoint: + diff_distance_x = self.waypoint.location_x - current_position.location_x + diff_distance_y = self.waypoint.location_y - current_position.location_y + command = commands.Command.create_set_relative_destination_command( + diff_distance_x, diff_distance_y + ) + print("Going to waypoint!") + self.has_reached_waypoint = False + + # find the closest landing pad + if self.has_reached_waypoint and not self.closest_landing_pad: + closest_distance_squared = float("inf") # infinity + + for landing_pad in landing_pad_locations: + distance_from_waypoint_squared = self.squared_distance( + landing_pad, self.waypoint + ) + + # update closest_landing_pad + if distance_from_waypoint_squared < closest_distance_squared: + closest_distance_squared = distance_from_waypoint_squared + self.closest_landing_pad = landing_pad + + # EGDE CASE: landing pad is on the waypoint + if closest_distance_squared == 0: + command = commands.Command.create_land_command() + print("Drone is landing directly on the waypoint!") + else: + diff_distance_x = ( + self.closest_landing_pad.location_x - current_position.location_x + ) + diff_distance_y = ( + self.closest_landing_pad.location_y - current_position.location_y + ) + command = commands.Command.create_set_relative_destination_command( + diff_distance_x, diff_distance_y + ) + print("Moving to closest landing pad!") return command diff --git a/modules/bootcamp/detect_landing_pad.py b/modules/bootcamp/detect_landing_pad.py index f17aa677..db99c52b 100644 --- a/modules/bootcamp/detect_landing_pad.py +++ b/modules/bootcamp/detect_landing_pad.py @@ -19,8 +19,7 @@ # 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 ↑ # ============ @@ -94,35 +93,38 @@ def run(self, image: np.ndarray) -> "tuple[list[bounding_box.BoundingBox], np.nd # Use the model's predict() method to run inference # Parameters of interest: - # * source - # * conf - # * device + # * source: image + # * conf: 0.7 as outlined by the bootcamp + # * device: # * verbose - predictions = ... + predictions = self.__model.predict(image, conf=0.7, device=self.__DEVICE, verbose=False) - # Get the Result object - prediction = ... + # Get the Result object: need to loop through the predictions + prediction = predictions[0] # Plot the annotated image from the Result object - # Include the confidence value - image_annotated = ... + # Include the confidence value --> defaulted to be true anyways + image_annotated = prediction.plot() # Get the xyxy boxes list from the Boxes object in the Result object - boxes_xyxy = ... + boxes_xyxy = prediction.boxes.xyxy + # boxes.xyxyn not needed # Detach the xyxy boxes to make a copy, # move the copy into CPU space, # and convert to a numpy array - boxes_cpu = ... + 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 = ... - return [], image_annotated + for i in range(0, boxes_cpu.shape[0]): + # Create BoundingBox object and append to list + result, box = bounding_box.BoundingBox.create(boxes_cpu[i]) + if result: # this way only valid boxes are added + bounding_boxes.append(box) + + return bounding_boxes, image_annotated # ============ # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ # ============ diff --git a/requirements.txt b/requirements.txt index fa1ed662..c98debf1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,6 @@ pytest # If you are on MacOS, don't have a CUDA capable GPU, # or if things don't work and you just want to use the CPU, # comment out the "--extra-index-url" option ---extra-index-url https://download.pytorch.org/whl/cu124 # ============ # ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑