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

Search pattern #168

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open

Search pattern #168

wants to merge 16 commits into from

Conversation

nsmartinx
Copy link

No description provided.

Copy link
Contributor

@mgupta27 mgupta27 left a comment

Choose a reason for hiding this comment

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

Reviewed.

modules/decision/search_pattern.py Show resolved Hide resolved
modules/decision/search_pattern.py Outdated Show resolved Hide resolved
self.target_posy = self.search_origin.east + self.relative_target_posy

def distance_to_target_squared(self,
current_position: odometry_and_time.OdometryAndTime
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Add a comma at the end here

Comment on lines 13 to 14
Camera's field of view, measured in degrees. Use the smallest measurement available.
Is essentially assumed to be a circle
Copy link
Contributor

Choose a reason for hiding this comment

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

does the field of view refer to the the angle between the line to the furthest point the camera can see and straight down, or double that angle (I'm assuming it's double the angle)? You don't have to change anything here, just remember to put this in the documentation

self.search_radius = 0
self.acceptable_variance_squared = acceptable_variance_squared

# Store the origin of the search and
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: comments should be complete sentences (with periods at the end)

Comment on lines 59 to 60
self.max_pos_in_ring = (ceil(self.search_radius * 2 * pi / self.search_gap))
self.angle_between_positions = ((2 * pi) / (self.max_pos_in_ring + 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 extra brackets in these lines

Comment on lines 59 to 60
self.max_pos_in_ring = (ceil(self.search_radius * 2 * pi / self.search_gap))
self.angle_between_positions = ((2 * pi) / (self.max_pos_in_ring + 1))
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you walk me through the math you did here? since the search pattern isn't actually a circle but an inscribed polygon, the circumference self.search_radius * 2 * pi would be bigger than the perimeter of the polygon right? This gives us too many search points - we're going in more than a circle.

Comment on lines 74 to 75
def distance_to_target_squared(self,
current_position: odometry_and_time.OdometryAndTime
Copy link
Contributor

Choose a reason for hiding this comment

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

Make this a private static method since it's a helper function and isn't changing the class - update the name to be __distance_to_target_squared (double underscore indicates private method)

search_height: float,
search_overlap: float,
current_position: odometry_and_time.OdometryAndTime,
acceptable_variance_squared: float):
Copy link
Contributor

Choose a reason for hiding this comment

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

variance refers more to a probability/statistics concept, could you rename this to something like distance_square_threshold?

Comment on lines 68 to 69
distance = search_maker.distance_to_target_squared(drone_odometry)
assert distance <= 0.1
Copy link
Contributor

Choose a reason for hiding this comment

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

set up tests in the format

  1. setup - get the objects you need and set the expected value, hard code the expected value with expected = True
  2. run the functions to get the actual value - distance = search_maker ..., and set actual = distance <= 0.1
  3. assertions - assert that actual == expected

this is just an example for this test case, do this for the other test cases too

Copy link
Contributor

@TongguangZhang TongguangZhang left a comment

Choose a reason for hiding this comment

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

Reviewed

Comment on lines 5 to 7
from .. import decision_command
from .. import odometry_and_time
from math import tan, pi, ceil
Copy link
Contributor

Choose a reason for hiding this comment

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

follow this import convention https://uwarg-docs.atlassian.net/wiki/spaces/CV/pages/2253226033/Python+Style+Convention#Imports
(python default modules first, then installed modules, then local modules)

Comment on lines 50 to 71
self.distance_squared_threshold = distance_squared_threshold
self.small_adjustment = small_adjustment

# The search will be centred around wherever it is initialized
self.search_origin_x = current_position_x
self.search_origin_y = current_position_y

# Drones current target
self.target_posx = self.search_origin_x
self.target_posy = self.search_origin_y

# Initialize the drone to the first position in the search pattern
self.current_square = 1
self.current_side_in_square = 0
self.current_pos_on_side = -1

# Calculate the gap between positions search_gap_width is the left/right distance
# search_gap_depth is the forwards/backwards distance
self.search_width = 2 * search_height * tan((camera_fov_sideways * pi / 180) / 2)
self.search_depth = 2 * search_height * tan((camera_fov_forwards * pi / 180) / 2)
self.search_gap_width = self.search_width * (1 - search_overlap)
self.search_gap_depth = self.search_depth * (1 - search_overlap)
Copy link
Contributor

Choose a reason for hiding this comment

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

make sure you denote these as private variables by adding __ (double underscore) to the start of each variable

distance_squared_threshold: float,
small_adjustment: float):

# Store values to be used later
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we have a more descriptive comment here? maybe "Set local constants"

Comment on lines 87 to 89
adjustment = 0
if self.search_gap_depth < self.search_width:
adjustment = (self.search_gap_width - self.search_gap_depth) / 2
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems like we're using the adjustment to decrease the square size to just depth if it's less than the width - why don't we use the min between depth and width every time?

self.calculate_square_corners()
self.calculate_side_of_square()

def calculate_square_corners(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

make sure to set this as a private function by adding double underscore to the beginning

Comment on lines 83 to 84
square_size = (min(self.search_gap_depth, self.search_gap_width)
+ (self.current_square - 1) * self.search_gap_width)
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we use the min of depth and width for the first square but always width for the next squares?

(self.search_origin_x - square_size, self.search_origin_y - square_size - adjustment),
]

def calculate_side_of_square(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

make sure to set this as a private function by adding double underscore to the beginning

Comment on lines 112 to 115
if self.moving_horizontally:
side_length = next_corner[0] - self.current_corner[0]
else:
side_length = next_corner[1] - self.current_corner[1]
Copy link
Contributor

Choose a reason for hiding this comment

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

is the side length not a multiple of width (if moving horizontally), or depth (if moving vertically)?

side_length = next_corner[1] - self.current_corner[1]

# Calculate the number of stops needed along the current side
self.max_pos_on_side = ceil(abs(side_length) / self.search_gap_depth)
Copy link
Contributor

Choose a reason for hiding this comment

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

the number of stops should just be the multiple right?

Copy link
Contributor

@TongguangZhang TongguangZhang left a comment

Choose a reason for hiding this comment

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

reviewed

Comment on lines 74 to 81
self.__search_width = (
2 * search_height * tan((camera_fov_sideways * pi / 180) / 2)
)
self.__search_depth = (
2 * search_height * tan((camera_fov_forwards * pi / 180) / 2)
)
self.__search_gap_width = self.__search_width * (1 - search_overlap)
self.__search_gap_depth = self.__search_depth * (1 - search_overlap)
Copy link
Contributor

Choose a reason for hiding this comment

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

make these 1 step or use *=

modules/decision/search_pattern.py Show resolved Hide resolved
Comment on lines 102 to 104
adjustment = 0
if self.__search_gap_depth < self.__search_width:
adjustment = (self.__search_gap_width - self.__search_gap_depth) / 2
Copy link
Contributor

Choose a reason for hiding this comment

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

make this an if else, with else: adjustment = 0


# Calculate the corners based on the offsets and the search origin. Top left corner is moved
# right by search_gap_width as the final side of the square will instead cover that part
self.__square_corners = [
Copy link
Contributor

Choose a reason for hiding this comment

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

a few options here to make it clear which is the top left, top right, bottom left, bottom right:

  1. use an object instead that stores the current state (tl, tr, bl, br) with enums
  2. use a dict with an enum class for the keys
  3. have these as class variables with a state variable that's assigned enum values

by enums I mean have an object with constants for the possible states so instead of assigning state = "top left", you would have state = enum.TOP_LEFT

Comment on lines 135 to 139
self.__current_corner = self.__square_corners[self.__current_side_in_square]
next_corner = self.__square_corners[(self.__current_side_in_square + 1) % 4]

# Determine if the drone is moving horizontally or vertically along the current side
self.__moving_horizontally = self.__current_side_in_square % 2 == 0
Copy link
Contributor

Choose a reason for hiding this comment

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

from above - having it hardcoded would make this more readable so we can tell which direction (clockwise vs counter clockwise) we're going

modules/decision/search_pattern.py Show resolved Hide resolved
Comment on lines 205 to 209
dist_to_move = self.__travel_gap * self.__current_pos_on_side
if self.__moving_horizontally:
self.__target_posx = self.__current_corner[0] + dist_to_move
else:
self.__target_posy = self.__current_corner[1] + dist_to_move
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer having using x += self.__travel_gap and y += self.__travel_gap since these should represent the current state of the drone to avoid the multiplications and reassigning x and y

Comment on lines 25 to 26
distance_squared_threshold=2.0,
small_adjustment=1,
Copy link
Contributor

Choose a reason for hiding this comment

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

if you use these numbers to initialize every instance of the class, use a constant

yield search_pattern_instance


def create_drone_odometry(pos_x, pos_y, height):
Copy link
Contributor

Choose a reason for hiding this comment

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

use a fixture for this



@pytest.fixture()
def search_pattern_width_greater_height():
Copy link
Contributor

Choose a reason for hiding this comment

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

if you call it depth in the class, keep the naming consistent

Copy link
Contributor

@TongguangZhang TongguangZhang left a comment

Choose a reason for hiding this comment

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

reviewed!

Comment on lines +75 to +83
assert move[0] == newPos
assert (
move[1].get_command_type()
== decision_command.DecisionCommand.CommandType.MOVE_TO_ABSOLUTE_POSITION
)
pos = move[1].get_command_position()
assert pytest.approx(target_x, 0.1) == pos[0]
assert pytest.approx(target_y, 0.1) == pos[1]
assert pytest.approx(target_z, 0.1) == pos[2]
Copy link
Contributor

Choose a reason for hiding this comment

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

set this up with the testing convention

  1. inputs and expected - expected_move = newPos, etc.
  2. actual values - actual_pos = move[0]
  3. assert actual == expected

"""
Test first 20 positions of search pattern where drone has reached point before being called
"""
coordinates = [
Copy link
Contributor

Choose a reason for hiding this comment

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

use expected_coordinates as the name here to follow testing convention and make it clear it is the expected output

Comment on lines +122 to +123
odometry = drone_odometry(coordinates[i][0], coordinates[i][1], 100)
move = search_pattern_width_greater_depth.continue_search(odometry)
Copy link
Contributor

Choose a reason for hiding this comment

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

use actual_coordinates or another descriptive name here to make it clear this is the output

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants