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

Himank Bootcamp PR #11

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ venv/

# Logging
*log*

# pyenv
.python-version
51 changes: 47 additions & 4 deletions modules/bootcamp/decision_simple_waypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,37 @@ def __init__(self, waypoint: location.Location, acceptance_radius: float):
# ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓
# ============

# Add your own
self.action_dict = dict(zip(("MOVE", "HALT", "LAND"), range(3,6)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Can use enum instead.


# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
# ============

def distance_between_waypoint(self, given_location:location.Location) -> "tuple[float, float]":
"""
Returns the distance between the given location and waypoint.
"""
given_loc_from_waypoint_location_x = self.waypoint.location_x - given_location.location_x
given_loc_from_waypoint_location_y = self.waypoint.location_y - given_location.location_y
return (given_loc_from_waypoint_location_x, given_loc_from_waypoint_location_y)

def check_if_near_waypoint(self, given_location:location.Location) -> bool:
"""
Checks if the given location is on the waypoint by an acceptance radius.
"""
absolute_acceptance_radius = abs(self.acceptance_radius)
difference_location_x, difference_location_y = self.distance_between_waypoint(given_location)
if abs(difference_location_x) < absolute_acceptance_radius and abs(difference_location_y) < absolute_acceptance_radius:
return True
return False

def next_relative_coordinates_to_waypoint(self, given_location:location.Location) -> "tuple[float, float]":
"""
Returns the relative x and y coordinates for drone to be sent to.
"""
relative_x, relative_y = self.distance_between_waypoint(given_location)
return (relative_x, relative_y)
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove unneeded parantheses.


def run(self,
report: drone_report.DroneReport,
landing_pad_locations: "list[location.Location]") -> commands.Command:
Expand All @@ -68,10 +93,28 @@ def run(self,
# ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓
# ============

# Do something based on the report and the state of this class...
action = None
report_status = report.status
report_position = report.position

if report_status == drone_status.DroneStatus.LANDED:
action = None
elif report_status == drone_status.DroneStatus.HALTED:
if self.check_if_near_waypoint(report_position):
action = self.action_dict["LAND"]
else:
action = self.action_dict["MOVE"]

if action is None:
pass
Comment on lines +108 to +109
Copy link
Contributor

Choose a reason for hiding this comment

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

Redundant, remove.

elif action == self.action_dict["MOVE"]:
relative_x, relative_y = self.next_relative_coordinates_to_waypoint(report_position)
command = commands.Command.create_set_relative_destination_command(relative_x, relative_y)
elif action == self.action_dict["HALT"]:
command = commands.Command.create_halt_command()
elif action == self.action_dict["LAND"]:
command = commands.Command.create_land_command()

# Remove this when done
raise NotImplementedError

# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
Expand Down
97 changes: 93 additions & 4 deletions modules/bootcamp/decision_waypoint_landing_pads.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,72 @@ def __init__(self, waypoint: location.Location, acceptance_radius: float):
# ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓
# ============

# Add your own
self.action_dict = dict(zip(("MOVE", "HALT", "LAND"), range(3,6)))
self.origin = location.Location(0,0)
Copy link
Contributor

Choose a reason for hiding this comment

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

What if the drone didn't start at (0,0) , but could start anywhere?

Copy link
Author

Choose a reason for hiding this comment

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

Then, in that case, wouldn't we need to keep track of the initial position of the drone?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. Alternatively, don't use the initial position of the drone.


# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
# ============

@staticmethod
def shortest_distance(target_location:location.Location, given_location:location.Location) -> float:
Copy link
Contributor

Choose a reason for hiding this comment

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

Good use of static method!

"""
Finds out the shortest distance between two given locations.
"""
x_1, y_1 = given_location.location_x, given_location.location_y
x_2, y_2 = target_location.location_x, target_location.location_y
x_square = (x_2 - x_1) ** 2
y_square = (y_2 - y_1) ** 2
return (x_square + y_square) ** 0.5

def relative_coordinates_of_target(self, target_location:location.Location, given_location:location.Location) -> "tuple[float, float]":
Copy link
Contributor

Choose a reason for hiding this comment

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

This can also be static.

"""
Returns the relative coordinates of target w.r.t given location.
"""
relative_location_x = target_location.location_x - given_location.location_x
relative_location_y = target_location.location_y - given_location.location_y
return (relative_location_x, relative_location_y)
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove unneeded parantheses.


def check_if_near_target(self, target_location:location.Location, given_location:location.Location) -> bool:
"""
Checks if the given location is near the target by an acceptance radius.
"""
absolute_acceptance_radius = abs(self.acceptance_radius)
difference_location_x, difference_location_y = self.relative_coordinates_of_target(target_location, given_location)
if abs(difference_location_x) < absolute_acceptance_radius and abs(difference_location_y) < absolute_acceptance_radius:
return True
return False

def next_relative_coordinates_to_target(self,
target_location:location.Location,
given_location:location.Location) -> "tuple[float, float]":
"""
Returns the relative x and y coordinates for drone to be sent to.
"""
relative_x, relative_y = self.relative_coordinates_of_target(target_location, given_location)
divider = 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this since it doesn't do anything.

if abs(relative_x) > abs(relative_y):
return (relative_x/divider, relative_y)
elif abs(relative_x) < abs(relative_y):
return (relative_x, relative_y/divider)
else:
return (relative_x/divider, relative_y/divider)

def closest_landing_pad(self,
given_location:location.Location,
landing_pad_locations: "list[location.Location]") -> location.Location:
"""
Finds out the closest landing pad from the given location by checking out their distances.
"""
closest_location = landing_pad_locations[0]
Copy link
Contributor

Choose a reason for hiding this comment

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

What if there were no landing pads? (You do not need to change anything, just something to think about).

for landing_pad in landing_pad_locations[1:]:
distance_from_given_location = DecisionWaypointLandingPads.shortest_distance(landing_pad, given_location)
distance_from_closest_location = DecisionWaypointLandingPads.shortest_distance(closest_location, given_location)
Comment on lines +99 to +100
Copy link
Contributor

Choose a reason for hiding this comment

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

Calculating the distance repeatedly for the closest landing pad is wasteful, is there a way to avoid this?

if distance_from_given_location < distance_from_closest_location:
closest_location = landing_pad
return closest_location


def run(self,
report: drone_report.DroneReport,
landing_pad_locations: "list[location.Location]") -> commands.Command:
Expand All @@ -68,10 +128,39 @@ def run(self,
# ↓ BOOTCAMPERS MODIFY BELOW THIS COMMENT ↓
# ============

# Do something based on the report and the state of this class...
action = None
report_status = report.status
report_position = report.position
target = self.waypoint

if report_status == drone_status.DroneStatus.HALTED:
landing_pad = self.closest_landing_pad(self.waypoint, landing_pad_locations)
action = self.action_dict["MOVE"]
if self.check_if_near_target(self.waypoint, report_position):
target = landing_pad
if (self.check_if_near_target(landing_pad, report_position)
and (DecisionWaypointLandingPads.shortest_distance(self.waypoint, report_position) - DecisionWaypointLandingPads.shortest_distance(self.waypoint, landing_pad) < 0.1)
and (
(self.check_if_near_target(landing_pad, self.origin) and self.check_if_near_target(self.waypoint, self.origin))
or (not self.check_if_near_target(report_position, self.origin))
)):
"""
self.check_if_near_target(landing_pad, self.origin) - if nearest landing pad is origin
self.check_if_near_target(self.waypoint, self.origin) - if waypoint is origin
self.check_if_near_target(report_position, self.origin) - if current position is origin
"""
action = self.action_dict["LAND"]

if action is None:
pass
elif action == self.action_dict["MOVE"]:
relative_x, relative_y = self.next_relative_coordinates_to_target(target, report_position)
command = commands.Command.create_set_relative_destination_command(relative_x, relative_y)
elif action == self.action_dict["HALT"]:
command = commands.Command.create_halt_command()
elif action == self.action_dict["LAND"]:
command = commands.Command.create_land_command()

# Remove this when done
raise NotImplementedError

# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
Expand Down
22 changes: 13 additions & 9 deletions modules/bootcamp/detect_landing_pad.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,32 +86,36 @@ def run(self, image: np.ndarray) -> "tuple[list[bounding_box.BoundingBox], np.nd
# * conf
# * device
# * verbose
predictions = ...
predictions = self.__model.predict(source=image,
conf=0.7,
device=self.__DEVICE,
verbose=False)

# Get the Result object
prediction = ...
prediction = predictions[0]

# Plot the annotated image from the Result object
# Include the confidence value
image_annotated = ...
image_annotated = prediction.plot(pil=True, conf=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

The expected image is a numpy ndarray, not a PIL object.

Copy link
Author

Choose a reason for hiding this comment

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

According to the Ultralytics documentation, setting img = True would return numpy.ndarray type, but that was not the case.

When I tried running pytest with pil=True, conf=True, all 3 tests passed, whereas running pytest with img=True, conf=True failed all 3 tests.

The error message was the following: AttributeError: 'bool' object has no attribute 'data'.

I do not know whether this is a bug or something else.

Copy link
Contributor

@Xierumeng Xierumeng Sep 20, 2023

Choose a reason for hiding this comment

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

The img parameter's type itself is expected to be np.ndarray . This is so that you can pass in an existing image that Ultralytics will draw over, but this bootcamp does not use it.

If you leave pil=True , what is image_annotated object? Do the test output images look fine?


# Get the xyxy boxes list from the Boxes object in the Result object
boxes_xyxy = ...
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 = prediction.boxes.cpu().xyxy.numpy()
Copy link
Contributor

Choose a reason for hiding this comment

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

Use boxes_xyxy . Also detach before moving to the CPU.

Copy link
Author

Choose a reason for hiding this comment

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

What do you mean by "detach"?

Copy link
Contributor

Choose a reason for hiding this comment

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

Pytorch tensors have a detach() method, use that.


# 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, ...):
for i in range(0, boxes_cpu.shape[0]):
# Create BoundingBox object and append to list
# result, box = ...
result, box = bounding_box.BoundingBox.create(boxes_cpu[i])
if result:
bounding_boxes.append(box)
Comment on lines +115 to +116
Copy link
Contributor

Choose a reason for hiding this comment

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

In statistics, it is preferred to remove the whole data point if there is an issue (here, the data point is an image). (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.

Does that mean removing the entire list of bounding boxes if even one bounding box has an issue?

Copy link
Contributor

@Xierumeng Xierumeng Sep 20, 2023

Choose a reason for hiding this comment

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

Correct, an empty list would be returned instead (the entire image is skipped). But you don't need to change anything.


# Remove this when done
raise NotImplementedError
return (bounding_boxes, image_annotated)

# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
Expand Down
2 changes: 1 addition & 1 deletion modules/bootcamp/tests/run_decision_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
# OpenCV ignores your display settings,
# so if the window is too small or too large,
# change this value (between 0.0 and 1.0)
DISPLAY_SCALE = 0.8
DISPLAY_SCALE = 0.7

# Seed for randomly generating the waypoint and landing pad
# Change to a constant for reproducibility (e.g. debugging)
Expand Down
4 changes: 2 additions & 2 deletions modules/bootcamp/tests/run_decision_simple_waypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
# You can probably divide it by 10 or so since ML inference isn't running
# Increase the step size if your computer is lagging
# Larger step size is smaller FPS
TIME_STEP_SIZE = 0.01 # seconds
TIME_STEP_SIZE = 0.1 # seconds

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

# Seed for randomly generating the waypoint and landing pad
# Change to a constant for reproducibility (e.g. debugging)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
# Seed for randomly generating the waypoint and landing pad
# Change to a constant for reproducibility (e.g. debugging)
# Change back to = time.time_ns() to test robustness
SEED = time.time_ns()
SEED = time.time_ns() # 1694668265382892400

# ============
# ↑ BOOTCAMPERS MODIFY ABOVE THIS COMMENT ↑
Expand Down