From e51b6d122547422f97bd4a88860035f210918d9c Mon Sep 17 00:00:00 2001 From: luckyducky037 <155498923+luckyducky037@users.noreply.github.com> Date: Fri, 11 Oct 2024 19:52:03 -0400 Subject: [PATCH] Search area dimensions (#73) * FOV for vertical frustum * Variable frustum * Fixed trig errors * Parameter for degrees or radians * Fixed factor bug; decimal default values * Search area dimensions unit tests * Fixed linter * Removed dots * Deleted init.py * Deleted more init files * Search area dimensions (#71) * FOV for vertical frustum * Variable frustum * Fixed trig errors * Parameter for degrees or radians * Fixed factor bug; decimal default values * Added back the init files I wasn't supposed to delete * Deleted duplicate --- modules/search_area_dimensions.py | 60 +++++++++++++ tests/unit/test_search_area_dimensions.py | 103 ++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 modules/search_area_dimensions.py create mode 100644 tests/unit/test_search_area_dimensions.py diff --git a/modules/search_area_dimensions.py b/modules/search_area_dimensions.py new file mode 100644 index 0000000..8c3411b --- /dev/null +++ b/modules/search_area_dimensions.py @@ -0,0 +1,60 @@ +""" +Converts field of view to display dimensions in metres. +""" + +from math import tan, pi + + +# Measurements from https://uwarg-docs.atlassian.net/wiki/spaces/CV/pages/2236613655/200+CV+Camera +FIELD_OF_VISION_X = 0.64889 +FIELD_OF_VISION_Y = 0.41438 + + +def search_area_dimensions( + height: int, + frustum_angle_x: float, + frustum_angle_y: float, + frustum_radians: bool = True, + field_of_vision_x: float = FIELD_OF_VISION_X, + field_of_vision_y: float = FIELD_OF_VISION_Y, + field_of_vision_radians: bool = True, +) -> "tuple[float, float]": + """ + Parameters: + - height: height of the drone, in metres + - frustum_angle_x: the left-right camera direction angle w/ respect to vertical + - frustum_angle_y: the up-down camera direction angle w/ respect to vertical + - frustum_radians: Boolean for whether frustum input is in radians or not + - field_of_vision_x: the horizontal field of vision of the camera, in radians (default to measured constant) + - field_of_vision_y: the vertical field of vision of the camera, in radians (default to measured constant) + - field_of_vision_radians: Boolean for whether field of vision input is in radians or not + + Return: + - tuple containing the rectangular dimensions of the field of view of the camera, in metres + """ + frustum_factor = 1 + if not frustum_radians: + frustum_factor = pi / 180 + field_of_vision_factor = 1 + if not field_of_vision_radians: + field_of_vision_factor = pi / 180 + + left_distance = ( + tan((field_of_vision_x * field_of_vision_factor) / 2 - frustum_angle_x * frustum_factor) + * height + ) + right_distance = ( + tan((field_of_vision_x * field_of_vision_factor) / 2 + frustum_angle_x * frustum_factor) + * height + ) + horizontal_distance = left_distance + right_distance + up_distance = ( + tan((field_of_vision_y * field_of_vision_factor) / 2 - frustum_angle_y * frustum_factor) + * height + ) + down_distance = ( + tan((field_of_vision_y * field_of_vision_factor) / 2 + frustum_angle_y * frustum_factor) + * height + ) + vertical_distance = up_distance + down_distance + return horizontal_distance, vertical_distance diff --git a/tests/unit/test_search_area_dimensions.py b/tests/unit/test_search_area_dimensions.py new file mode 100644 index 0000000..4fde55d --- /dev/null +++ b/tests/unit/test_search_area_dimensions.py @@ -0,0 +1,103 @@ +""" +Test cases for the search_area_dimensions module. +""" + +from math import pi, sqrt + +from modules import search_area_dimensions + +# Test functions use test fixture signature names and access class privates +# No enable +# pylint: disable=duplicate-code,protected-access,redefined-outer-name + +THRESHOLD = 1e-6 + + +def test_no_field_of_view() -> None: + """ + Testing 0 degree field of view. + """ + height = 10 + frustum_angle_x = 10 + frustum_angle_y = 20 + field_of_vision_x = 0 + field_of_vision_y = 0 + + x, y = search_area_dimensions.search_area_dimensions( + height, frustum_angle_x, frustum_angle_y, True, field_of_vision_x, field_of_vision_y, True + ) + + assert abs(x - 0) < THRESHOLD + assert abs(y - 0) < THRESHOLD + + +def test_vertical_field_of_view() -> None: + """ + Testing straight-down camera angle. + """ + height = 20 + frustum_angle_x = 0 + frustum_angle_y = 0 + field_of_vision_x = pi / 2 + field_of_vision_y = 2 * pi / 3 + + x, y = search_area_dimensions.search_area_dimensions( + height, frustum_angle_x, frustum_angle_y, True, field_of_vision_x, field_of_vision_y, True + ) + + assert abs(x - 40) < THRESHOLD + assert abs(y - 40 * sqrt(3)) < THRESHOLD + + +def test_side_vertical_field_of_view() -> None: + """ + Test for when one side of the field of view is vertical. + """ + height = 30 + frustum_angle_x = pi / 8 + frustum_angle_y = pi / 6 + field_of_vision_x = pi / 4 + field_of_vision_y = pi / 3 + + x, y = search_area_dimensions.search_area_dimensions( + height, frustum_angle_x, frustum_angle_y, True, field_of_vision_x, field_of_vision_y, True + ) + + assert abs(x - 30) < THRESHOLD + assert abs(y - 30 * sqrt(3)) < THRESHOLD + + +def test_askew_acute_field_of_view() -> None: + """ + Test for when field of view spreads outwards on both sides. + """ + height = 40 + frustum_angle_x = pi / 12 + frustum_angle_y = pi / 24 + field_of_vision_x = pi / 2 + field_of_vision_y = 7 * pi / 12 + + x, y = search_area_dimensions.search_area_dimensions( + height, frustum_angle_x, frustum_angle_y, True, field_of_vision_x, field_of_vision_y, True + ) + + assert abs(x - (40 * sqrt(3) + 40 / sqrt(3))) < THRESHOLD + assert abs(y - (40 + 40 * sqrt(3))) < THRESHOLD + + +def test_askew_obtuse_field_of_view() -> None: + """ + Test for when field of view spreads to one side from both directions. + """ + height = 50 + frustum_angle_x = pi / 4 + frustum_angle_y = 7 * pi / 24 + field_of_vision_x = pi / 6 + field_of_vision_y = pi / 12 + + x, y = search_area_dimensions.search_area_dimensions( + height, frustum_angle_x, frustum_angle_y, True, field_of_vision_x, field_of_vision_y, True + ) + + assert abs(x - (50 * sqrt(3) - 50 / sqrt(3))) < THRESHOLD + assert abs(y - (50 * sqrt(3) - 50)) < THRESHOLD