-
Notifications
You must be signed in to change notification settings - Fork 39
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
Search pattern #168
Conversation
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.
Reviewed.
modules/decision/search_pattern.py
Outdated
self.target_posy = self.search_origin.east + self.relative_target_posy | ||
|
||
def distance_to_target_squared(self, | ||
current_position: odometry_and_time.OdometryAndTime |
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.
Nit: Add a comma at the end here
modules/decision/search_pattern.py
Outdated
Camera's field of view, measured in degrees. Use the smallest measurement available. | ||
Is essentially assumed to be a circle |
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.
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
modules/decision/search_pattern.py
Outdated
self.search_radius = 0 | ||
self.acceptable_variance_squared = acceptable_variance_squared | ||
|
||
# Store the origin of the search and |
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.
Nit: comments should be complete sentences (with periods at the end)
modules/decision/search_pattern.py
Outdated
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)) |
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.
remove extra brackets in these lines
modules/decision/search_pattern.py
Outdated
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)) |
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.
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.
modules/decision/search_pattern.py
Outdated
def distance_to_target_squared(self, | ||
current_position: odometry_and_time.OdometryAndTime |
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.
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)
modules/decision/search_pattern.py
Outdated
search_height: float, | ||
search_overlap: float, | ||
current_position: odometry_and_time.OdometryAndTime, | ||
acceptable_variance_squared: float): |
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.
variance refers more to a probability/statistics concept, could you rename this to something like distance_square_threshold?
tests/test_search_pattern.py
Outdated
distance = search_maker.distance_to_target_squared(drone_odometry) | ||
assert distance <= 0.1 |
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.
set up tests in the format
- setup - get the objects you need and set the expected value, hard code the expected value with expected = True
- run the functions to get the actual value - distance = search_maker ..., and set actual = distance <= 0.1
- assertions - assert that actual == expected
this is just an example for this test case, do this for the other test cases too
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.
Reviewed
modules/decision/search_pattern.py
Outdated
from .. import decision_command | ||
from .. import odometry_and_time | ||
from math import tan, pi, ceil |
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.
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)
modules/decision/search_pattern.py
Outdated
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) |
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.
make sure you denote these as private variables by adding __ (double underscore) to the start of each variable
modules/decision/search_pattern.py
Outdated
distance_squared_threshold: float, | ||
small_adjustment: float): | ||
|
||
# Store values to be used later |
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.
Can we have a more descriptive comment here? maybe "Set local constants"
modules/decision/search_pattern.py
Outdated
adjustment = 0 | ||
if self.search_gap_depth < self.search_width: | ||
adjustment = (self.search_gap_width - self.search_gap_depth) / 2 |
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.
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?
modules/decision/search_pattern.py
Outdated
self.calculate_square_corners() | ||
self.calculate_side_of_square() | ||
|
||
def calculate_square_corners(self): |
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.
make sure to set this as a private function by adding double underscore to the beginning
modules/decision/search_pattern.py
Outdated
square_size = (min(self.search_gap_depth, self.search_gap_width) | ||
+ (self.current_square - 1) * self.search_gap_width) |
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.
why do we use the min of depth and width for the first square but always width for the next squares?
modules/decision/search_pattern.py
Outdated
(self.search_origin_x - square_size, self.search_origin_y - square_size - adjustment), | ||
] | ||
|
||
def calculate_side_of_square(self): |
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.
make sure to set this as a private function by adding double underscore to the beginning
modules/decision/search_pattern.py
Outdated
if self.moving_horizontally: | ||
side_length = next_corner[0] - self.current_corner[0] | ||
else: | ||
side_length = next_corner[1] - self.current_corner[1] |
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.
is the side length not a multiple of width (if moving horizontally), or depth (if moving vertically)?
modules/decision/search_pattern.py
Outdated
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) |
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.
the number of stops should just be the multiple right?
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.
reviewed
modules/decision/search_pattern.py
Outdated
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) |
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.
make these 1 step or use *=
modules/decision/search_pattern.py
Outdated
adjustment = 0 | ||
if self.__search_gap_depth < self.__search_width: | ||
adjustment = (self.__search_gap_width - self.__search_gap_depth) / 2 |
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.
make this an if else, with else: adjustment = 0
modules/decision/search_pattern.py
Outdated
|
||
# 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 = [ |
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.
a few options here to make it clear which is the top left, top right, bottom left, bottom right:
- use an object instead that stores the current state (tl, tr, bl, br) with enums
- use a dict with an enum class for the keys
- 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
modules/decision/search_pattern.py
Outdated
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 |
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.
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
Outdated
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 |
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.
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
tests/test_search_pattern.py
Outdated
distance_squared_threshold=2.0, | ||
small_adjustment=1, |
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.
if you use these numbers to initialize every instance of the class, use a constant
tests/test_search_pattern.py
Outdated
yield search_pattern_instance | ||
|
||
|
||
def create_drone_odometry(pos_x, pos_y, height): |
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.
use a fixture for this
tests/test_search_pattern.py
Outdated
|
||
|
||
@pytest.fixture() | ||
def search_pattern_width_greater_height(): |
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.
if you call it depth in the class, keep the naming consistent
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.
reviewed!
tests/test_search_pattern.py
Outdated
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] |
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.
set this up with the testing convention
- inputs and expected - expected_move = newPos, etc.
- actual values - actual_pos = move[0]
- assert actual == expected
tests/test_search_pattern.py
Outdated
""" | ||
Test first 20 positions of search pattern where drone has reached point before being called | ||
""" | ||
coordinates = [ |
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.
use expected_coordinates as the name here to follow testing convention and make it clear it is the expected output
tests/test_search_pattern.py
Outdated
odometry = drone_odometry(coordinates[i][0], coordinates[i][1], 100) | ||
move = search_pattern_width_greater_depth.continue_search(odometry) |
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.
use actual_coordinates or another descriptive name here to make it clear this is the output
…ges in tests to reflect these changes
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.
LGTM!
No description provided.