-
Notifications
You must be signed in to change notification settings - Fork 249
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
New fork, only committing 3 files, pass all tests and linters #92
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,47 +4,97 @@ | |
Travel to designated waypoint and then land at a nearby landing pad. | ||
""" | ||
|
||
# Disable for bootcamp use | ||
# pylint: disable=unused-import | ||
|
||
import sys | ||
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 | ||
# pylint: disable=unused-argument,line-too-long | ||
|
||
|
||
# All logic around the run() method | ||
# pylint: disable-next=too-few-public-methods | ||
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: | ||
def __init__(self, waypoint: location.Location, acceptance_radius: float): | ||
""" | ||
Initialize all persistent variables here with self. | ||
""" | ||
self.waypoint = waypoint | ||
print(f"Waypoint: {waypoint}") | ||
print("Waypoint: " + str(waypoint)) | ||
|
||
self.acceptance_radius = acceptance_radius | ||
|
||
# ============ | ||
# ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓ | ||
# ============ | ||
|
||
# Add your own | ||
# Initialising a boolean variable to check arrival status, and deal with unplanned halts along the way (bonus) | ||
self.arrived = False | ||
|
||
def squared_distance( | ||
self, location1: location.Location, location2: location.Location | ||
) -> float: | ||
""" | ||
Calculate the squared Euclidean distance without square roots | ||
""" | ||
distance_horizontal = location1.location_x - location2.location_x | ||
distance_vertical = location1.location_y - location2.location_y | ||
return distance_horizontal**2 + distance_vertical**2 | ||
|
||
# ============ | ||
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ | ||
# ============ | ||
|
||
def find_closest_landing_pad( | ||
self, | ||
current_position: location.Location, | ||
landing_pad_locations: "list[location.Location]", | ||
) -> location.Location: | ||
""" | ||
Find the closest landing pad | ||
""" | ||
closest_pad = None | ||
# Setting default to maximum integer value for easy troubleshooting | ||
min_distance = sys.maxsize | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use float('inf') instead to represent infinity. |
||
|
||
# Trivial sorter to determine the closest landing pad | ||
for lander in landing_pad_locations: | ||
distance = self.squared_distance(current_position, lander) | ||
if distance < min_distance: | ||
min_distance = distance | ||
closest_pad = lander | ||
|
||
return closest_pad | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if there are no pads? i.e. len(landing_pad_locations) == 0. after handling this, make sure the return type annotation is changed to reflect this. |
||
|
||
def set_destination( | ||
self, current_position: location.Location, target: location.Location | ||
) -> commands.Command: | ||
""" | ||
Calculates the relative distance and returns a command to move the drone to the target. | ||
""" | ||
distance_horizontal = target.location_x - current_position.location_x | ||
distance_vertical = target.location_y - current_position.location_y | ||
|
||
# Setting Destination | ||
return commands.Command.create_set_relative_destination_command( | ||
distance_horizontal, distance_vertical | ||
) | ||
|
||
def run( | ||
self, report: drone_report.DroneReport, landing_pad_locations: "list[location.Location]" | ||
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. | ||
|
@@ -70,6 +120,41 @@ def run( | |
|
||
# Do something based on the report and the state of this class... | ||
|
||
if not self.arrived: | ||
current_position = report.position | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is report.position assigned to this variable? seems like this variable is not needed since report.position is directly referenced later in the code. |
||
squared_distance_to_waypoint = self.squared_distance( | ||
current_position, self.waypoint | ||
) | ||
|
||
# Check acceptance radius | ||
if squared_distance_to_waypoint <= self.acceptance_radius**2: | ||
# Drone arrived! | ||
self.arrived = True | ||
|
||
else: | ||
# Dealing with unplanned halts | ||
if report.status == drone_status.DroneStatus.HALTED: | ||
return self.set_destination(report.position, self.waypoint) | ||
|
||
# Repeating process but for closest landing pad, after reaching waypoint | ||
if self.arrived: | ||
closest_pad = self.find_closest_landing_pad( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if closest_pad is None? |
||
report.position, landing_pad_locations | ||
) | ||
|
||
# Calculate squared distance to the closest landing pad | ||
squared_distance_to_pad = self.squared_distance( | ||
report.position, closest_pad | ||
) | ||
|
||
# If within the acceptance radius of the landing pad, land the drone | ||
if squared_distance_to_pad <= self.acceptance_radius**2: | ||
return commands.Command.create_land_command() | ||
|
||
# Move the drone toward the closest landing pad if it's halted | ||
if report.status == drone_status.DroneStatus.HALTED: | ||
return self.set_destination(report.position, closest_pad) | ||
|
||
# ============ | ||
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ | ||
# ============ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,18 +13,8 @@ | |
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 ↑ | ||
# ============ | ||
|
||
|
||
# This is just an interface | ||
# pylint: disable=too-few-public-methods | ||
class DetectLandingPad: | ||
""" | ||
Contains the YOLOv8 model for prediction. | ||
|
@@ -48,7 +38,7 @@ class DetectLandingPad: | |
__MODEL_NAME = "best-2n.pt" | ||
|
||
@classmethod | ||
def create(cls, model_directory: pathlib.Path) -> "tuple[bool, DetectLandingPad | None]": | ||
def create(cls, model_directory: pathlib.Path): | ||
""" | ||
model_directory: Directory to models. | ||
""" | ||
|
@@ -69,15 +59,19 @@ def create(cls, model_directory: pathlib.Path) -> "tuple[bool, DetectLandingPad | |
|
||
return True, DetectLandingPad(cls.__create_key, model) | ||
|
||
def __init__(self, class_private_create_key: object, model: ultralytics.YOLO) -> None: | ||
def __init__(self, class_private_create_key, model: ultralytics.YOLO): | ||
""" | ||
Private constructor, use create() method. | ||
""" | ||
assert class_private_create_key is DetectLandingPad.__create_key, "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]": | ||
def run( | ||
self, image: np.ndarray | ||
) -> "tuple[list[bounding_box.BoundingBox], np.ndarray]": | ||
""" | ||
Converts an image into a list of bounding boxes. | ||
|
||
|
@@ -98,31 +92,41 @@ def run(self, image: np.ndarray) -> "tuple[list[bounding_box.BoundingBox], np.nd | |
# * conf | ||
# * device | ||
# * verbose | ||
predictions = ... | ||
|
||
# Get the Result object | ||
prediction = ... | ||
# using 0.7 based on provided hint | ||
predictions = self.__model.predict(image, conf=0.7, device=self.__DEVICE) | ||
|
||
# Get the Result object from the output tensor | ||
prediction = predictions[0] | ||
|
||
# Plot the annotated image from the Result object | ||
# Include the confidence value | ||
image_annotated = ... | ||
image_annotated = prediction.plot() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. get the confidence value using the conf parameter |
||
|
||
# Get the xyxy boxes list from the Boxes object in the Result object | ||
boxes_xyxy = ... | ||
# 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_cpu = boxes_xyxy.cpu().numpy() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use the detach method to make a copy of the xyxy boxes |
||
|
||
# Loop over the boxes list and create a list of bounding boxes | ||
bounding_boxes = [] | ||
|
||
# Use a range-based for-loop based on number of elements | ||
for i in range(boxes_cpu.shape[0]): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. iterate over the list objects instead of the index |
||
box = boxes_cpu[i, :] | ||
result, box = bounding_box.BoundingBox.create(boxes_cpu[i]) | ||
if result: | ||
bounding_boxes.append(box) | ||
# Hint: .shape gets the dimensions of the numpy array | ||
# for i in range(0, ...): | ||
# # Create BoundingBox object and append to list | ||
# result, box = ... | ||
# Create BoundingBox object and append to list | ||
# result, box = ... | ||
|
||
return bounding_boxes, image_annotated | ||
|
||
return [], image_annotated | ||
# ============ | ||
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑ | ||
# ============ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice optimization trick here by not taking the square root. good job!