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

New fork, only committing 3 files, pass all tests and linters #92

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
39 changes: 31 additions & 8 deletions modules/bootcamp/decision_simple_waypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,34 @@
Travel to designated waypoint.
"""

# Disable for bootcamp use
# pylint: disable=unused-import


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 DecisionSimpleWaypoint(base_decision.BaseDecision):
"""
Travel to the designed waypoint.
"""

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

Expand All @@ -44,7 +46,9 @@ def __init__(self, waypoint: location.Location, acceptance_radius: float) -> Non
# ============

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.
Expand All @@ -70,6 +74,25 @@ def run(

# Do something based on the report and the state of this class...

# Get access to ccordinates and desired waypoint
waypoint = self.waypoint
current_position = report.position

distance_horizontal = waypoint.location_x - current_position.location_x
distance_vertical = waypoint.location_y - current_position.location_y

squared_distance_to_waypoint = (distance_horizontal**2) + (distance_vertical**2)

# Compare shortest distance to acceptance radius w/o square roots for computational efficiency
if squared_distance_to_waypoint <= self.acceptance_radius**2:
Copy link

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!

return commands.Command.create_land_command()

# Handles BONUS: Ensures halted drone moves by relative amount when halted
if report.status == drone_status.DroneStatus.HALTED:
return commands.Command.create_set_relative_destination_command(
distance_horizontal, distance_vertical
)

# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
# ============
Expand Down
103 changes: 94 additions & 9 deletions modules/bootcamp/decision_waypoint_landing_pads.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link

@ashum68 ashum68 Sep 12, 2024

Choose a reason for hiding this comment

The 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
Copy link

Choose a reason for hiding this comment

The 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.
Expand All @@ -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
Copy link

@ashum68 ashum68 Sep 12, 2024

Choose a reason for hiding this comment

The 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(
Copy link

Choose a reason for hiding this comment

The 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 ↑
# ============
Expand Down
56 changes: 30 additions & 26 deletions modules/bootcamp/detect_landing_pad.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
"""
Expand All @@ -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.

Expand All @@ -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()
Copy link

Choose a reason for hiding this comment

The 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()
Copy link

Choose a reason for hiding this comment

The 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]):
Copy link

Choose a reason for hiding this comment

The 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 ↑
# ============
Loading