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

Alex Liu - Autonomy Bootcamp 2023 #105

Closed
wants to merge 15 commits into from
28 changes: 28 additions & 0 deletions modules/bootcamp/decision_simple_waypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,28 @@ def __init__(self, waypoint: location.Location, acceptance_radius: float) -> Non
# ============

# Add your own
self.acceptance_radius_squared = self.acceptance_radius**2 # used for distance calculation

self.goals = [
commands.Command.create_set_relative_destination_command(
self.waypoint.location_x, self.waypoint.location_y
)
]
# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
# ============

@staticmethod
def calculate_distance_squared(
location_1: location.Location, location_2: location.Location
) -> float:
"""
Calculate the non-square rooted distance between two locations
"""
return (location_2.location_x - location_1.location_x) ** 2 + (
location_2.location_y - location_1.location_y
) ** 2

def run(
self, report: drone_report.DroneReport, landing_pad_locations: "list[location.Location]"
) -> commands.Command:
Expand Down Expand Up @@ -70,6 +87,17 @@ def run(

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

if report.status == drone_status.DroneStatus.HALTED:
if self.goals:

Choose a reason for hiding this comment

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

I don't really see why you have a list of "goals". You can simply check if the drone is within the acceptance radius, land if true, set relative destination if false.

command = self.goals.pop(0)
elif (
DecisionSimpleWaypoint.calculate_distance_squared(
report.position, report.destination
)
<= self.acceptance_radius_squared
):
command = commands.Command.create_land_command()

# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
# ============
Expand Down
74 changes: 74 additions & 0 deletions modules/bootcamp/decision_waypoint_landing_pads.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,60 @@ def __init__(self, waypoint: location.Location, acceptance_radius: float) -> Non
# ============

# Add your own
self.acceptance_radius_squared = self.acceptance_radius**2 # used for distance calculation

self.goals = [
commands.Command.create_set_relative_destination_command(
self.waypoint.location_x, self.waypoint.location_y
)
]

self.landing = False
# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
# ============

@staticmethod
def find_nearest_pad(
reference_location: location.Location, landing_pad_locations: list[location.Location]
) -> location.Location:
"""
Calculates returns nearest landing pad location based on a given reference location
"""
# assuming landing_pad_locations is always populated, but just in-case
closest_location = min(
landing_pad_locations,
key=lambda location: (
(location.location_x - reference_location.location_x) ** 2
+ (location.location_y - reference_location.location_y) ** 2
),
default=location.Location(0, 0),
)
return closest_location

@staticmethod
def calculate_distance_squared(
location_1: location.Location, location_2: location.Location
) -> float:
"""
Calculate the non-square rooted distance between two locations
"""
return (location_2.location_x - location_1.location_x) ** 2 + (
location_2.location_y - location_1.location_y
) ** 2

@staticmethod
def get_relative_position(
location_1: location.Location, location_2: location.Location

Choose a reason for hiding this comment

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

Use more descriptive variable names

) -> tuple[float, float]:
"""
Calculates the relative position of one position to another
"""
return (
location_2.location_x - location_1.location_x,
location_2.location_y - location_1.location_y,
)

def run(
self, report: drone_report.DroneReport, landing_pad_locations: "list[location.Location]"
) -> commands.Command:
Expand Down Expand Up @@ -70,6 +119,31 @@ def run(

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

# only execute when "drone" is ready for another instruction
if report.status == drone_status.DroneStatus.HALTED:
if self.goals:

Choose a reason for hiding this comment

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

I don't really see why you have a list of "goals". You can simply check if the drone is within the acceptance radius, land if true, set relative destination if false.

command = self.goals.pop(0)
elif not self.landing:
# try to find a landing pad
self.landing = True
nearest_landing_pad_location = DecisionWaypointLandingPads.find_nearest_pad(
report.position, landing_pad_locations
)
# tuple unwrapping
relative_x, relative_y = DecisionWaypointLandingPads.get_relative_position(
report.position, nearest_landing_pad_location
)
command = commands.Command.create_set_relative_destination_command(
relative_x, relative_y
)
elif (
DecisionWaypointLandingPads.calculate_distance_squared(
report.position, report.destination
)
<= self.acceptance_radius_squared
):
command = commands.Command.create_land_command()

# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
# ============
Expand Down
29 changes: 23 additions & 6 deletions modules/bootcamp/detect_landing_pad.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,31 +98,48 @@ def run(self, image: np.ndarray) -> "tuple[list[bounding_box.BoundingBox], np.nd
# * conf
# * device
# * verbose
predictions = ...
# BC NOTE: May need to create instantiate model object with create()
predictions = self.__model.predict(
source=image, conf=0.7, device=self.__DEVICE, verbose=False
)
# BC NOTE: See https://docs.ultralytics.com/modes/predict/#working-with-results

# Get the Result object
prediction = ...
# BOOTCAMPER NOTE: may raise ValueError
prediction = predictions[0]

# Plot the annotated image from the Result object
# Include the confidence value
image_annotated = ...
image_annotated = prediction.plot()
# BC NOTE: conf & boxes True by default

# Get the xyxy boxes list from the Boxes object in the Result object
boxes_xyxy = ...
boxes_xyxy = prediction.boxes.xyxy
# BC NOTE: Result.boxes may be None, could cause error trying to access 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.detach().cpu().numpy()

# Loop over the boxes list and create a list of bounding boxes
# BC NOTE: Need for error checking from returned tuple from BoundingBox.create()
bounding_boxes = []
for rectangle in boxes_cpu:
# unwrap returned tuple
result, box = bounding_box.BoundingBox.create(rectangle)
if result:
bounding_boxes.append(box)
else:
# return an empty list if any of them fail???
bounding_boxes.clear() # clear the boxes if any were made before the failed one
break

Choose a reason for hiding this comment

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

This solution works, but consider that

if not result:
    return [], image_annotated

achieves the same result with less code (and slightly more performance since you don't have to clear bounding_boxes). (You do not need to change anything here, just something to think about).

Copy link
Author

Choose a reason for hiding this comment

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

Mhm, this one was a bit of a 50/50 choice for me. I was thinking "if the flow goes to the return statement anyways, I'll just make sure the return tuple is correct then return". HOWEVER, I like the idea of better efficiency :P

# 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
return bounding_boxes, image_annotated
# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
# ============
2 changes: 2 additions & 0 deletions modules/bootcamp/tests/run_decision_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
# Larger step size is smaller FPS
TIME_STEP_SIZE = 0.1 # seconds

# Alex was here

# OpenCV ignores your display settings,
# so if the window is too small or too large,
# change this value (between 0.0 and 1.0)
Expand Down