diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01bb140 --- /dev/null +++ b/.gitignore @@ -0,0 +1,114 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# PyCharm +.idea/ +# Vim +.vim/ +# Visual Studio Code +.vscode/ + +# Spreadsheets +*.csv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..50528e1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +repos: +- repo: https://github.com/ambv/black + rev: 21.10b0 + hooks: + - id: black + language_version: python3.8 +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 # Use the ref you want to point at + hooks: + - id: trailing-whitespace + - id: check-added-large-files + - id: check-merge-conflict + - id: check-case-conflict + - id: end-of-file-fixer + - id: check-json + - id: check-xml + - id: check-yaml + - id: check-toml + - id: mixed-line-ending + args: ['--fix=lf'] + description: Forces line endings to the UNIX LF character diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf4cd91 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Missouri S&T Multirotor Robot Design Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 7f34587..c8185f1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # SUAS-2022 Missouri S&T Multirotor Robot Design Team's code for the Association for Unmanned Vehicle Systems International's 2022 Student Unmanned Aerial Systems Competition (AUVSI SUAS 2022) + +# Setup +``` +pip install pre-commit +pre-commit install +``` + > `branch_name` should follow the convention `feature/{feature_name}`, or `hotfix/{fix_name}` diff --git a/flight/.idea/.gitignore b/flight/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/flight/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/flight/.idea/flight.iml b/flight/.idea/flight.iml new file mode 100644 index 0000000..484e44c --- /dev/null +++ b/flight/.idea/flight.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/flight/.idea/inspectionProfiles/profiles_settings.xml b/flight/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/flight/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/flight/.idea/misc.xml b/flight/.idea/misc.xml new file mode 100644 index 0000000..d1e22ec --- /dev/null +++ b/flight/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/flight/.idea/modules.xml b/flight/.idea/modules.xml new file mode 100644 index 0000000..8d274d3 --- /dev/null +++ b/flight/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/flight/.idea/vcs.xml b/flight/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/flight/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/flight/Stationary Obstacle Avoidance/main.py b/flight/Stationary Obstacle Avoidance/main.py new file mode 100644 index 0000000..4f43072 --- /dev/null +++ b/flight/Stationary Obstacle Avoidance/main.py @@ -0,0 +1,68 @@ +import obstacle_avoidance +import plotter + +SAFETY_MARGIN = 10 # meters +MAX_DISTANCE = 0 # meters + +waypoints = [ + {"latitude": 37.94883876837826, "longitude": -91.78443677093598, "altitude": 100.0}, + { + "latitude": 37.949016435151485, + "longitude": -91.78364283711778, + "altitude": 150.0, + }, + {"latitude": 37.94957481364174, "longitude": -91.78369648129468, "altitude": 300.0}, + {"latitude": 37.95004012580848, "longitude": -91.78468353414975, "altitude": 250.0}, + { + "latitude": 37.949735558177984, + "longitude": -91.78542382379104, + "altitude": 150.0, + }, + { + "latitude": 37.948790418686826, + "longitude": -91.78605823636349, + "altitude": 200.0, + }, + { + "latitude": 37.948576497594445, + "longitude": -91.78460843230208, + "altitude": 150.0, + }, +] + +obstacles = [ + { + "latitude": 37.94986244280119, + "longitude": -91.78386816205061, + "radius": 20, + "height": 400, + }, + { + "latitude": 37.949388706571064, + "longitude": -91.78569200437985, + "radius": 30, + "height": 200, + }, + { + "latitude": 37.94828884469052, + "longitude": -91.78546673913256, + "radius": 50, + "height": 150, + }, +] + +if __name__ == "__main__": + # Convert latlon data to UTM projection + waypoints = obstacle_avoidance.all_latlon_to_utm(waypoints) + obstacles = obstacle_avoidance.all_latlon_to_utm(obstacles) + + # Plot data before processing + # plotter.plot_data(waypoints, obstacles, SAFETY_MARGIN) + + # Find new safe path between all waypoints + waypoints = obstacle_avoidance.get_safe_route( + waypoints, obstacles, SAFETY_MARGIN, MAX_DISTANCE, debugging=False + ) + + # Plot data after processing + plotter.plot_data(waypoints, obstacles, SAFETY_MARGIN, flight_path_color="bo-") diff --git a/flight/Stationary Obstacle Avoidance/math_functions.py b/flight/Stationary Obstacle Avoidance/math_functions.py new file mode 100644 index 0000000..98b9118 --- /dev/null +++ b/flight/Stationary Obstacle Avoidance/math_functions.py @@ -0,0 +1,138 @@ +import math +from typing import Tuple + + +Point = Tuple[float, float] +Line = Tuple[float, float, float] + + +def distance(a: Point, b: Point) -> float: + """ + Calculates the euclidean distance between two points + + Args: + a (Point): First point + b (Point): Second point + + Returns: + float: Returns the distance between points a and b + """ + return abs(math.sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2)) + + +def tangent_points(point: Point, circle: Point, r: float) -> Tuple[Point, Point]: + """ + From a point outside ther circle, calculates the two points that are tangent to the circle + + Args: + point (Point): a point that lies outside the radius of the circle + circle (Point): center of the circle + r (float): radius of the circle + + Returns: + Tuple[Point, Point]: the coordinates of each tangent to the circle + """ + Px = point[0] + Py = point[1] + Cx = circle[0] + Cy = circle[1] + dx, dy = Px - Cx, Py - Cy + dxr, dyr = -dy, dx + d = math.sqrt(dx**2 + dy**2) + rho = r / d + ad = rho**2 + bd = rho * math.sqrt(1 - rho**2) + T1x = Cx + ad * dx + bd * dxr + T1y = Cy + ad * dy + bd * dyr + T2x = Cx + ad * dx - bd * dxr + T2y = Cy + ad * dy - bd * dyr + return ((T1x, T1y), (T2x, T2y)) + + +def line(p1: Point, p2: Point) -> Line: + """ + Finds the standard form of the equation of a line given two points + + Args: + p1 (Point): First pair of x,y coordinates + p2 (Point): Second pair of x,y coordinates + + Returns: + Line: The real numbers A, B and -C which make up the standard line equation + """ + A = p1[1] - p2[1] + B = p2[0] - p1[0] + C = p1[0] * p2[1] - p2[0] * p1[1] + return A, B, -C + + +def intersection(L1: Line, L2: Line) -> Point: + """ + Finds the intersection of two lines + + Args: + L1 (Line): equation of a line in standard form + L2 (Line): equation of a line in standard form + + Returns: + Point: coordinates of the intersection + """ + D = L1[0] * L2[1] - L1[1] * L2[0] + Dx = L1[2] * L2[1] - L1[1] * L2[2] + Dy = L1[0] * L2[2] - L1[2] * L2[0] + if D != 0: + x = Dx / D + y = Dy / D + return x, y + else: + return False + + +def resolve_closest_tangents( + start: Point, end: Point, A1: Point, A2: Point, B1: Point, B2: Point +) -> Tuple[Line, Line, Line, Line]: + """ + Calculates which pair of tangent points are on the same side of the circle + + Args: + start (Point): starting point + end (Point): ending point + A1 (Point): tangent one of the starting point + A2 (Point): tangent two of the starting point + B1 (Point): tangent one of the ending point + B2 (Point): tangent two of the ending point + + Returns: + Tuple[Line, Line, Line, Line]: Line segments from the start to the tangent to the end + """ + distances = [distance(A1, B1), distance(A1, B2), distance(A2, B1), distance(A2, B2)] + i = distances.index(min(distances)) + + if i == 0: + # then A1, B1 are on the same side, A2, B2 on the other + route1_segment1 = line(start, A1) + route1_segment2 = line(B1, end) + route2_segment1 = line(start, A2) + route2_segment2 = line(B2, end) + elif i == 1: + # then A1, B2 are on the same side, A2, B1 on the other + route1_segment1 = line(start, A1) + route1_segment2 = line(B2, end) + route2_segment1 = line(start, A2) + route2_segment2 = line(B1, end) + elif i == 2: + # then A2, B1 are on the same side, A1, B2 on the other + route1_segment1 = line(start, A2) + route1_segment2 = line(B1, end) + route2_segment1 = line(start, A1) + route2_segment2 = line(B2, end) + elif i == 3: + # then A2, B2 are on the same side, A1, B1 on the other + route1_segment1 = line(start, A2) + route1_segment2 = line(B2, end) + route2_segment1 = line(start, A1) + route2_segment2 = line(B1, end) + else: + print("Error: index was " + str(i) + ", expected index range 0-3.") + + return route1_segment1, route1_segment2, route2_segment1, route2_segment2 diff --git a/flight/Stationary Obstacle Avoidance/obstacle_avoidance.py b/flight/Stationary Obstacle Avoidance/obstacle_avoidance.py new file mode 100644 index 0000000..ae8e63c --- /dev/null +++ b/flight/Stationary Obstacle Avoidance/obstacle_avoidance.py @@ -0,0 +1,317 @@ +from typing import Tuple +from collections import namedtuple +import utm +from fractions import Fraction +from shapely import geometry +import math_functions +import plotter + + +Point = Tuple[float, float] + + +def latlon_to_utm(coords: dict) -> dict: + """ + Converts latlon coordinates to utm coordinates and adds the data to the dictionary + + Args: + coords (dict): A dictionary containing lat long coordinates + + Returns: + dict: An updated dictionary with additional keys and values with utm data + """ + utm_coords = utm.from_latlon(coords["latitude"], coords["longitude"]) + coords["utm_x"] = utm_coords[0] + coords["utm_y"] = utm_coords[1] + coords["utm_zone_number"] = utm_coords[2] + coords["utm_zone_letter"] = utm_coords[3] + return coords + + +def all_latlon_to_utm(list_of_coords: list[dict]) -> list[dict]: + """ + Converts a list of dictionarys with latlon data to add utm data + + Args: + list_of_coords (list[dict]): A list of dictionaries that contain lat long data + + Returns: + list[dict]: An updated list of dictionaries with added utm data + """ + for i in range(len(list_of_coords)): + list_of_coords[i] = latlon_to_utm(list_of_coords[i]) + return list_of_coords + + +def check_for_collision( + p1: Point, p2: Point, obstacles: list[dict], padding: float +) -> int or None: + """ + Checks if the path between two points collides with an obstacle in the given list + + Args: + p1 (Point): Starting point + p2 (Point): Ending point + obstacles (list[dict]): List of dictionaries containing obstacle data + padding (float): Saftey margin around obstacle + + Returns: + int: Index of the obstacle if a collision is found + """ + for i, obstacle in enumerate(obstacles): + x = obstacle["utm_x"] + y = obstacle["utm_y"] + radius = obstacle["radius"] + line = geometry.LineString([(p1[0], p1[1]), (p2[0], p2[1])]) + # Creates a perfect circle shape by buffering a single point by a desired radius + circle = geometry.Point(x, y).buffer(radius + padding).boundary + if circle.intersection(line): + return i + else: + return None + + +def find_new_point( + pointA: Point, + pointB: Point, + obstacle: Point, + radius: float, + padding: float, + max_distance: float = 0, + debugging: bool = False, +) -> list[dict]: + """ + Given a starting and ending point whose path intersects a given obstacle, + finds new waypoint(s) that lie outside the circle allowing causing the path to no longer intersect the obstacle + + Args: + pointA (Point): Starting point + pointB (Point): Ending point + obstacle (Point): Obstacle that lies between the starting and ending point + radius (float): Radius of the obstacle + padding (float): Saftey margin around the obstacle + max_distance (float, optional): Maximum distance the new single waypoint can be away from the circle + before splitting into two new waypoints Defaults to 0. + debugging (bool, optional): Debugging on or off. Defaults to False. + + Returns: + list[dict]: List of one or two waypoints + """ + + # rough calculation of new height + new_altitude = (pointA[2] + pointB[2]) / 2 + + # find tangents to the circle from points A and B + (tangentA1, tangentA2) = math_functions.tangent_points( + pointA, obstacle, radius + padding + ) + (tangentB1, tangentB2) = math_functions.tangent_points( + pointB, obstacle, radius + padding + ) + + # get lines between points A, B, and their respective tangents to the circle + ( + route1_segment1, + route1_segment2, + route2_segment1, + route2_segment2, + ) = math_functions.resolve_closest_tangents( + pointA, pointB, tangentA1, tangentA2, tangentB1, tangentB2 + ) + + # find intersection points between lines + route1X, route1Y = math_functions.intersection(route1_segment1, route1_segment2) + route2X, route2Y = math_functions.intersection(route2_segment1, route2_segment2) + + # calculate distance of each route on either side of circle + route1_distance = math_functions.distance( + pointA, (route1X, route1Y) + ) + math_functions.distance(pointB, (route1X, route1Y)) + route2_distance = math_functions.distance( + pointA, (route2X, route2Y) + ) + math_functions.distance(pointB, (route2X, route2Y)) + + # choose shortest path + if route1_distance < route2_distance: + new_point = (route1X, route1Y) + alt_point = (route2X, route2Y) + else: + new_point = (route2X, route2Y) + alt_point = (route1X, route1Y) + + # Handle scenario where new waypoint is too far from the circle + special_case = False + # temp_x, extra1X, extra2X, temp_y, extra1Y, extra2Y = 0,0,0,0,0,0 # init vars + waypoint_distance_from_circle = math_functions.distance( + (new_point[0], new_point[1]), obstacle + ) + if waypoint_distance_from_circle > radius + padding + max_distance: + special_case = True + + print("New waypoint is too far away, splitting and adding more waypoints") + + # Equation for finding a point along a line a certain distance from another point + t = (radius + padding) / waypoint_distance_from_circle + temp_x = ((1 - t) * obstacle[0]) + (t * new_point[0]) + temp_y = ((1 - t) * obstacle[1]) + (t * new_point[1]) + temp_point = (temp_x, temp_y) + + first_segment = math_functions.line( + (pointA[0], pointA[1]), (new_point[0], new_point[1]) + ) + second_segment = math_functions.line( + (pointB[0], pointB[1]), (new_point[0], new_point[1]) + ) + + m = -1 / ( + (temp_y - obstacle[1]) / (temp_x - obstacle[0]) + ) # slope of new middle line segment + m_frac = Fraction(str(m)) + slopeX = m_frac.denominator + slopeY = m_frac.numerator + middle_segment = math_functions.line( + (temp_x, temp_y), (temp_x + slopeX, temp_y + slopeY) + ) + + extra1X, extra1Y = math_functions.intersection(first_segment, middle_segment) + extra2X, extra2Y = math_functions.intersection(second_segment, middle_segment) + extra_1 = (extra1X, extra1Y) + extra_2 = (extra2X, extra2Y) + + # plot extra data about this specific path and obstacle + if debugging: + plotter.plot_debug( + pointA, + pointB, + obstacle, + tangentA1, + tangentA2, + tangentB1, + tangentB2, + new_point, + alt_point, + temp_point, + extra_1, + extra_2, + special_case, + padding, + ) + + zone_number = pointA[3] + zone_letter = pointA[4] + + if special_case: + return [ + { + "utm_x": extra_1[0], + "utm_y": extra_1[1], + "utm_zone_number": zone_number, + "utm_zone_letter": zone_letter, + "latitude": utm.to_latlon(*extra_1, zone_number, zone_letter)[0], + "longitude": utm.to_latlon(*extra_1, zone_number, zone_letter)[1], + "altitude": new_altitude, + }, + { + "utm_x": extra_2[0], + "utm_y": extra_2[1], + "utm_zone_number": zone_number, + "utm_zone_letter": zone_letter, + "latitude": utm.to_latlon(*extra_2, zone_number, zone_letter)[0], + "longitude": utm.to_latlon(*extra_2, zone_number, zone_letter)[1], + "altitude": new_altitude, + }, + ] + else: + return [ + { + "utm_x": new_point[0], + "utm_y": new_point[1], + "utm_zone_number": zone_number, + "utm_zone_letter": zone_letter, + "latitude": utm.to_latlon(*new_point, zone_number, zone_letter)[0], + "longitude": utm.to_latlon(*new_point, zone_number, zone_letter)[1], + "altitude": new_altitude, + } + ] + + +def get_safe_route( + waypoints: list[dict], + obstacles: list[dict], + padding: float, + max_distance: float, + debugging: bool = False, +) -> list[dict]: + """ + Given a list of obstacles and a list of waypoints in which the path between some waypoints may intersect the obstacles, + calculates a new safe route with additional waypoints that avoids the obstacles + + Args: + waypoints (list[dict]): List of waypoints with utm data + obstacles (list[dict]): List of obstacles with utm data + padding (float): Saftey margin around obstacles + max_distance (float): Maximum distance away from circle before switching from single point to two points + debugging (bool, optional): Debugging on or off. Defaults to False. + + Returns: + list[dict]: Updated list of waypoints that include additional safe waypoints if needed + """ + + i = 0 + num_waypoints = len(waypoints) - 1 + while i < num_waypoints: + point_a = ( + waypoints[i]["utm_x"], + waypoints[i]["utm_y"], + waypoints[i]["altitude"], + waypoints[i]["utm_zone_number"], + waypoints[i]["utm_zone_letter"], + ) + point_b = ( + waypoints[i + 1]["utm_x"], + waypoints[i + 1]["utm_y"], + waypoints[i + 1]["altitude"], + waypoints[i + 1]["utm_zone_number"], + waypoints[i + 1]["utm_zone_letter"], + ) + + j = check_for_collision(point_a, point_b, obstacles, padding) + + if j != None: + print( + "Path between waypoints " + + str(i) + + " and " + + str(i + 1) + + " intersect obstacle " + + str(j) + ) + obstacle = ( + obstacles[j]["utm_x"], + obstacles[j]["utm_y"], + obstacles[j]["radius"], + obstacles[j]["height"], + ) + data = find_new_point( + point_a, + point_b, + obstacle, + obstacle[2], + padding, + max_distance, + debugging, + ) + # insert between waypoints + if len(data) == 2: + waypoints.insert(i + 1, data[0]) + waypoints.insert(i + 2, data[1]) + num_waypoints += 2 # increase number of waypoints + i -= 2 # back up two waypoint in case path to new waypoint causes new conflicts + else: + waypoints.insert(i + 1, data[0]) + num_waypoints += 1 # increase number of waypoints + i -= 1 # back up one waypoint in case path to new waypoint causes new conflicts + + i += 1 + + return waypoints diff --git a/flight/Stationary Obstacle Avoidance/plotter.py b/flight/Stationary Obstacle Avoidance/plotter.py new file mode 100644 index 0000000..80c7641 --- /dev/null +++ b/flight/Stationary Obstacle Avoidance/plotter.py @@ -0,0 +1,173 @@ +import matplotlib.pyplot as plt +from typing import List, Tuple + + +Point = Tuple[float, float] + + +def plot_data( + waypoints: List[dict], + obstacles: List[dict], + padding: float, + flight_path_color: str = "ko-", +) -> None: + """ + Plots the waypoints, obstacles, and flight path between waypoints + + Args: + waypoints (List[dict]): List of dictionaries containing waypoint data + obstacles (List[dict]): List of dictionaries containing obstacle data + padding (float): Saftey margin around obstacles + flight_path_color (str, optional): Color of lines indicating flight path. Defaults to black. + """ + + # plot obstacles + for obstacle in obstacles: + x = obstacle["utm_x"] + y = obstacle["utm_y"] + radius = obstacle["radius"] + + plt.gca().add_patch( + plt.Circle((x, y), radius + padding, color="gray") + ) # padded obstacle + plt.gca().add_patch(plt.Circle((x, y), radius, color="red")) # true obstacle + + # plot waypoints + x, y = [], [] + for waypoint in waypoints: + x.append(waypoint["utm_x"]) + y.append(waypoint["utm_y"]) + plt.plot(x, y, flight_path_color) + + plt.gca().set_aspect(1) + plt.show() + + +def plot_debug( + pointA: Point, + pointB: Point, + obstacle: Point, + tangentA1: Point, + tangentA2: Point, + tangentB1: Point, + tangentB2: Point, + new_point: Point, + alt_point: Point, + temp_point: Point, + extra_1: Point, + extra_2: Point, + special_case: bool, + padding: float, +) -> None: + """ + Used for debugging. Plots two waypoints and the obstacle between them + as well as all the other points used in calculations. + + Args: + pointA (Point): Starting point + pointB (Point): Ending point + obstacle (Point): Obstacle + tangentA1 (Point): First tangent to the starting point + tangentA2 (Point): Second tangent to the starting point + tangentB1 (Point): First tangent to the ending point + tangentB2 (Point): Second tangent to the ending point + new_point (Point): Intersection point on the shorter side + alt_point (Point): Intersection point on the longer side + temp_point (Point): Point that lies on the edge of the circle inline with the center and the new_point + extra_1 (Point): Additional point if special case is true + extra_2 (Point): Additional point if special case is true + special_case (bool): Whether or not the new point is too far from the circle edge + padding (float): Saftey margin around the obstacle + """ + + radius = obstacle[2] + + # plot obstacle + plt.gca().add_patch( + plt.Circle((obstacle[0], obstacle[1]), radius + padding, color="gray") + ) # padded obstacle + plt.gca().add_patch( + plt.Circle((obstacle[0], obstacle[1]), radius, color="red") + ) # true obstacle + + # prepare point data + x = [ + pointA[0], + pointB[0], + obstacle[0], + tangentA1[0], + tangentA2[0], + tangentB1[0], + tangentB2[0], + new_point[0], + alt_point[0], + temp_point[0], + extra_1[0], + extra_2[0], + ] + y = [ + pointA[1], + pointB[1], + obstacle[1], + tangentA1[1], + tangentA2[1], + tangentB1[1], + tangentB2[1], + new_point[1], + alt_point[1], + temp_point[1], + extra_1[1], + extra_2[1], + ] + col = [ + "green", + "green", + "black", + "black", + "black", + "black", + "black", + "blue", + "blue", + "blue", + "blue", + "blue", + ] + labels = [ + "Start", + "End", + "Obstacle", + "A1", + "A2", + "B1", + "B2", + "New Waypoint", + "Alt Waypoint", + "Temp", + "Extra 1", + "Extra 2", + ] + + # plot points + for i in range(len(x)): + plt.scatter(x[i], y[i], c=col[i]) + + # plot text labels + for i, v in enumerate(labels): + plt.gca().annotate(v, (x[i], y[i])) + + if special_case: + plt.plot( + [pointA[0], extra_1[0], extra_2[0], pointB[0]], + [pointA[1], extra_1[1], extra_2[1], pointB[1]], + "go-", + ) + else: + plt.plot( + [pointA[0], new_point[0], pointB[0]], + [pointA[1], new_point[1], pointB[1]], + "go-", + ) + + plt.gca().set_aspect(1) + plt.show() diff --git a/flight/Stationary Obstacle Avoidance/sample_data.json b/flight/Stationary Obstacle Avoidance/sample_data.json new file mode 100644 index 0000000..5c47857 --- /dev/null +++ b/flight/Stationary Obstacle Avoidance/sample_data.json @@ -0,0 +1,96 @@ +{ + "id": 1, + "lostCommsPos": { + "latitude": 37.9490953, + "longitude": -91.7848293 + }, + "flyZones": [ + { + "altitudeMin": 100.0, + "altitudeMax": 400.0, + "boundaryPoints": [ + { + "latitude": 37.95120673695754, + "longitude": -91.78693406657685 + }, + { + "latitude": 37.951248325955994, + "longitude": -91.78207299965484 + }, + { + "latitude": 37.94797658615526, + "longitude": -91.78697801835733 + }, + { + "latitude": 37.948226725559984, + "longitude": -91.78191292975686 + }, + { + "latitude": 37.94711526778687, + "longitude": -91.78116415681103 + }, + { + "latitude": 37.94623824627026, + "longitude": -91.78490802154013 + } + ] + } + ], + "waypoints": [ + { + "latitude": 37.94883876837826, + "longitude": -91.78443677093598, + "altitude": 100.0 + }, + { + "latitude": 37.949016435151485, + "longitude": -91.78364283711778, + "altitude": 150.0 + }, + { + "latitude": 37.94957481364174, + "longitude": -91.78369648129468, + "altitude": 300.0 + }, + { + "latitude": 37.95004012580848, + "longitude": -91.78468353414975, + "altitude": 250.0 + }, + { + "latitude": 37.949735558177984, + "longitude": -91.78542382379104, + "altitude": 150.0 + }, + { + "latitude": 37.9487541650019, + "longitude": -91.785788604194, + "altitude": 200.0 + }, + { + "latitude": 37.948576497594445, + "longitude": -91.78460843230208, + "altitude": 150.0 + } + ], + "stationaryObstacles": [ + { + "latitude": 37.94918564120274, + "longitude": -91.78431875374679, + "radius": 30, + "height": 400 + }, + { + "latitude": 37.94966787631084, + "longitude": -91.78554184098023, + "radius": 40, + "height": 200 + }, + { + "latitude": 37.94828884469052, + "longitude": -91.78546673913256, + "radius": 20, + "height": 150 + } + ] +} diff --git a/flight/__init__.py b/flight/__init__.py new file mode 100644 index 0000000..332d8e9 --- /dev/null +++ b/flight/__init__.py @@ -0,0 +1,18 @@ +"""Initialization file for state exporting""" +from typing import Dict +from states.state import State +from states.start_state import Start +from states.pre_processing import PreProcess +from states.takeoff import Takeoff +from states.waypoints import Waypoints +from states.land import Land +from states.final_state import Final + +STATES: Dict[str, State] = { + "Start State": Start, + "Pre-Processing": PreProcess, + "Takeoff": Takeoff, + "Waypoints": Waypoints, + "Land": Land, + "Final State": Final +} diff --git a/flight/state_settings.py b/flight/state_settings.py new file mode 100644 index 0000000..322dfdc --- /dev/null +++ b/flight/state_settings.py @@ -0,0 +1,123 @@ +"""Class to contain setters and getters for settings in various flight states""" + +DEFAULT_WAYPOINTS: int = 14 # Total number of waypoints flown to in Auto. flight of SUAS2022 +DEFAULT_RUN_TITLE: str = "N/A" # Title of flight mission for logging +DEFAULT_RUN_DESCRIPTION: str = "N/A" # Description of current flight mission for logging + + +class StateSettings: + """ + Initialize settings for state machine + Functions: + __init__: Sets preliminary values for SUAS overheads + simple_takeoff() -> None: Enables access to status of simple_takeoff + -> bool: Setter to determine if testing takeoff procedure desired + num_waypoints() -> None: Enables access to number of waypoints in the competition + -> int: Sets the number of waypoints in the competition + run_title() -> None: Enables access to set name of the current flight + -> str: Sets the name of the current flight for logging + run_description() -> None: Enables access to description of current flight mission + -> str: Sets the description of current flight mission + Member Variables: + __num_waypoints: Number of waypoints on flight plan + __run_title: Title of Competition + __run_description: Description for Competition + """ + def __init__(self, takeoff_bool: bool = False, num_waypoints: int = DEFAULT_WAYPOINTS, + title: str = DEFAULT_RUN_TITLE, description: str = DEFAULT_RUN_DESCRIPTION) -> None: + """Default constructor results in default settings + Args: + takeoff_bool: bool - determines if a simple takeoff procedure should be executed for testing + num_waypoints: int - Number of waypoints, extracted from SUAS mission plan + title: str - Title of current flight mission, for logging purposes + description: str - Description of current flight mission, for logging purposes + Returns: + None + """ + self.__simple_takeoff: bool = takeoff_bool + self.__num_waypoints: int = num_waypoints + self.__run_title: str = title + self.__run_description: str = description + + # ---- Takeoff settings ---- # + @property + def simple_takeoff(self) -> bool: + """Establishes simple_takeoff as a private member variable + Args: + N/A + Returns: + bool: Status of simple_takeoff variable + """ + return self.__simple_takeoff + + @simple_takeoff.setter + def simple_takeoff(self, simple_takeoff: bool) -> None: + """Setter for whether to perform simple takeoff instead of regular takeoff + Args: + simple_takeoff: bool - True for drone to go straight up, False to behave normally + Returns: + None + """ + self.__simple_takeoff = simple_takeoff + + # ---- Waypoint Settings ---- # + @property + def num_waypoints(self) -> int: + """Establishes num_waypoints as private member variable + Args: + N/A + Returns: + int: Number of waypoints in the competition + """ + return self.__num_waypoints + + @num_waypoints.setter + def num_waypoints(self, waypoints: int) -> None: + """Set the number of waypoints given by Interop System + Args: + waypoints: int - Number of waypoints present in mission plan + Returns: + None + """ + self.__num_waypoints = waypoints + + # ---- Other settings ---- # + @property + def run_title(self) -> str: + """Set a title for the run/test to be output in logging + Args: + N/A + Returns: + str: Created title for flight mission + """ + return self.__run_title + + @run_title.setter + def run_title(self, title: str) -> None: + """Sets the title of the flight mission for logging + Args: + title: str - Desired title for the flight + Returns: + None + """ + self.__run_title = title + + @property + def run_description(self) -> str: + """Set a description for the run/test to be output in logging + Args: + N/A + Returns: + str: Desired description for flight mission + """ + return self.__run_description + + @run_description.setter + def run_description(self, description: str) -> None: + """Sets a description of the flight mission + Args: + description: str - Written description for flight mission for logs + Returns: + None + """ + self.__run_description = description diff --git a/flight/states/.idea/.gitignore b/flight/states/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/flight/states/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/flight/states/.idea/inspectionProfiles/profiles_settings.xml b/flight/states/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/flight/states/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/flight/states/.idea/misc.xml b/flight/states/.idea/misc.xml new file mode 100644 index 0000000..d1e22ec --- /dev/null +++ b/flight/states/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/flight/states/.idea/modules.xml b/flight/states/.idea/modules.xml new file mode 100644 index 0000000..0194d0c --- /dev/null +++ b/flight/states/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/flight/states/.idea/states.iml b/flight/states/.idea/states.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/flight/states/.idea/states.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/flight/states/.idea/vcs.xml b/flight/states/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/flight/states/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/flight/states/air_drop.py b/flight/states/air_drop.py new file mode 100644 index 0000000..85b1641 --- /dev/null +++ b/flight/states/air_drop.py @@ -0,0 +1,36 @@ +"""State to maneuver the drone to Air Drop location and release UGV""" +import logging +from mavsdk import System +from ..state_settings import StateSettings +from state import State +from states.land import Land + + +class AirDrop(State): + """ + State to move drone to Air Drop GPS location and drop UGV + + Attributes + ---------- + N/A + + Methods + ------- + run() -> Land + Moves drone to Air Drop GPS location and releases UGV for mission + """ + async def run(self, drone: System) -> Land: + """ + Move drone to Air Drop GPS and release UGV for ground mission + + Parameters + ---------- + drone: System + MAVSDK object for drone control + + Returns + ------- + Land : State + State in which drone returns to takeoff location and lands + """ + return Land(self.state_settings) diff --git a/flight/states/emergent_odlc.py b/flight/states/emergent_odlc.py new file mode 100644 index 0000000..576bbab --- /dev/null +++ b/flight/states/emergent_odlc.py @@ -0,0 +1,36 @@ +"""State to search for Emergent ODLC object""" +import logging +from mavsdk import System +from ..state_settings import StateSettings +from state import State +from states.offaxis_odlc import OffAxisODLC + + +class EmergentODLC(State): + """ + State where drone flies to last known GPS location and searches for Emergent ODLC + + Attributes + ---------- + N/A + + Methods + ------- + run() -> Land + State to fly to last known position and scan for ODLC + """ + async def run(self, drone: System) -> OffAxisODLC: + """ + Flies to last known location and searches around location for Emergent ODLC + + Parameters + ---------- + drone: System + MAVSDK object for drone control + + Returns + ------- + Land : State + State where drone returns to takeoff location and lands + """ + return OffAxisODLC(self.state_settings) \ No newline at end of file diff --git a/flight/states/final_state.py b/flight/states/final_state.py new file mode 100644 index 0000000..98781df --- /dev/null +++ b/flight/states/final_state.py @@ -0,0 +1,22 @@ +"""Final state to end all drone processes""" +import logging +from mavsdk import System +from state import State +from ..state_settings import StateSettings + + +class Final(State): + """Empty state to end flight + Functions: + run() -> None: Ends the state machine + Member Variables: + None + """ + async def run(self, drone: System) -> None: + """Do nothing and end + Args: + drone: System - drone object that must be passed to any function with control of the drone + Returns: + None + """ + return diff --git a/flight/states/land.py b/flight/states/land.py new file mode 100644 index 0000000..0469db1 --- /dev/null +++ b/flight/states/land.py @@ -0,0 +1,24 @@ +"""Landing the drone at home position after each other task is finished""" +import logging +from mavsdk import System +from state import State +from ..state_settings import StateSettings +from states.final_state import Final + + +class Land(State): + """ + State to land the drone safely after finishing other flight & vision tasks + Functions: + run() -> Final: Running the landing procedure after returning to home + Member Variables: + None + """ + async def run(self, drone: System) -> Final: + """Run landing function to have drone slowly return to home position + Args: + drone: System - MAVSDK drone object for direct drone control + Returns: + Final: final state for ending state machine + """ + return Final(self.state_settings) diff --git a/vision/Object Detection/object_image.py b/flight/states/main.py similarity index 100% rename from vision/Object Detection/object_image.py rename to flight/states/main.py diff --git a/flight/states/offaxis_odlc.py b/flight/states/offaxis_odlc.py new file mode 100644 index 0000000..6f9ed1b --- /dev/null +++ b/flight/states/offaxis_odlc.py @@ -0,0 +1,36 @@ +"""State to fly towards Off Axis ODLC for image capture""" +import logging +from mavsdk import System +from ..state_settings import StateSettings +from state import State +from states.land import Land + + +class OffAxisODLC(State): + """ + State that handles Off Axis ODLC by flying to closest location w/in boundary and capture image + + Attributes + ---------- + N/A + + Methods + ------- + run() -> Land + Flies to closest location within flight boundary and record off-axis ODLC + """ + async def run(self, drone: System) -> Land: + """ + Flies to closest point within flight boundary and record image of Off-Axis ODLC + + Parameters + ---------- + drone: System + MAVSDK object for drone control + + Returns + ------- + Land : State + State to return to takeoff location and land the drone + """ + return Land(self.state_settings) \ No newline at end of file diff --git a/flight/states/pre_processing.py b/flight/states/pre_processing.py new file mode 100644 index 0000000..980d915 --- /dev/null +++ b/flight/states/pre_processing.py @@ -0,0 +1,25 @@ +"""State to receive data from interop system and perform preliminary calculations""" +import logging +from mavsdk import System +from state import State +from ..state_settings import StateSettings +from takeoff import Takeoff + + +class PreProcess(State): + """ + State to accept data and plan out optimal mission structure based on relative distances + Functions: + data_retrieval() -> Takeoff: Connects to Interop and downloads JSON data from system + ... # Will add more functions as needed during dev + Member Variables: + None + """ + async def data_retrieval(self, drone: System) -> Takeoff: + """Prelim function to accept data + Args: + drone: System - MAVSDK drone object for direct drone control + Returns: + Takeoff: the next state, the Takeoff state to advance state machine + """ + return Takeoff(self.state_settings) diff --git a/flight/states/standard_odlc.py b/flight/states/standard_odlc.py new file mode 100644 index 0000000..7bddd41 --- /dev/null +++ b/flight/states/standard_odlc.py @@ -0,0 +1,36 @@ +"""State to fly in ODLC search grid in set pattern""" +import logging +from mavsdk import System +from ..state_settings import StateSettings +from state import State +from states.land import Land + + +class StandardODLC(State): + """ + State to fly ODLC search grid scanning for standard ODLC objects + + Attributes + ---------- + N/A + + Methods + ------- + run() -> Land + Maneuvers drone in set pattern within ODLC search grid + """ + async def run(self, drone: System) -> Land: + """ + Flies drone in ODLC search grid in efficient pattern + + Parameters + ---------- + drone: System + MAVSDK object for drone control + + Returns + ------- + Land : State + State in which drone returns to takeoff location and lands + """ + return Land(self.state_settings) \ No newline at end of file diff --git a/flight/states/start_state.py b/flight/states/start_state.py new file mode 100644 index 0000000..28794c1 --- /dev/null +++ b/flight/states/start_state.py @@ -0,0 +1,23 @@ +"""Beginning state of flight operation, proceed to pre-processing""" +import logging +from mavsdk import System +from state import State +from ..state_settings import StateSettings +from states.pre_processing import PreProcess + + +class Start(State): + """Preliminary state, proceed state machine into pre-processing + Functions: + begin() -> PreProcess: beginning function to start state machine, proceeding to pre-processing step + Member Variables: + None + """ + async def begin(self, drone: System) -> PreProcess: + """Initialization function to start flight state machine + Args: + drone: System - drone object for directv drone control + Returns: + PreProcess: data pre-processing state, to advance state machine + """ + return PreProcess(self.state_settings) diff --git a/flight/states/state.py b/flight/states/state.py new file mode 100644 index 0000000..1a3fbe0 --- /dev/null +++ b/flight/states/state.py @@ -0,0 +1,62 @@ +"""Base State class which all other states inherit""" +import logging +from mavsdk import System +from ..state_settings import StateSettings + + +class State: + """ + Base State class + Functions: + run() -> (State, None): Runs the code present in each state and returns the next state, or None if at the + end of the machine + _check_arm_or_arm() -> None: Determines if drone is armed, and if not, arms it. + Member Variables: + drone (System): The drone object; used for flight. + """ + + def __init__(self, state_settings: StateSettings) -> None: + """ Initializes base State class using a StateSettings class + Args: + state_settings: StateSettings - class which contains basic competition data + Returns: + None + """ + logging.info('State "%s" has begun', self.name) + self.state_settings: StateSettings = state_settings + + async def run(self, drone: System) -> None: + """ Runs the functions and code present in state for competition goals + Args: + drone: System - drone object for MAVSDK control + Returns: + None + Exception: + General: raises if no code is present in run function + """ + raise Exception("Run not implemented for state") + + async def _check_arm_or_arm(self, drone: System) -> None: + """ Verifies that the drone is armed, if not armed, arms the drone + Args: + drone: System - The drone system; used for flight operations. + Returns: + None + """ + async for is_armed in drone.telemetry.armed(): + if not is_armed: + logging.debug("Not armed. Attempting to arm") + await drone.action.arm() + else: + logging.warning("Drone armed") + break + + @property + def name(self): + """ Getter function to return the name of the class + Args: + N/A + Returns: + Name property of current state class + """ + return type(self).__name__ diff --git a/flight/states/takeoff.py b/flight/states/takeoff.py new file mode 100644 index 0000000..78a2884 --- /dev/null +++ b/flight/states/takeoff.py @@ -0,0 +1,24 @@ +"""Run takeoff function to launch drone to minimum altitude""" +import logging +from mavsdk import System +from state import State +from ..state_settings import StateSettings +from waypoints import Waypoints + + +class Takeoff(State): + """ + Runs manual takeoff process, to lift drone to 100ft altitude + Functions: + run() -> Waypoints: runs drone movement functions to rise to 100ft + Member Variables: + None + """ + async def run(self, drone: System) -> Waypoints: + """Run offboard functions to have the drone takeoff + Args: + drone: System - MAVSDK drone object for direct drone control + Returns: + Waypoints: next state, Waypoints state to perform waypoint flight plan + """ + return Waypoints(self.state_settings) diff --git a/flight/states/waypoints.py b/flight/states/waypoints.py new file mode 100644 index 0000000..2c94705 --- /dev/null +++ b/flight/states/waypoints.py @@ -0,0 +1,25 @@ +"""Run flight plan assembled in pre-processing to fly to all waypoints sequentially""" +import logging +from mavsdk import System +from state import State +from ..state_settings import StateSettings +from states.land import Land + + +class Waypoints(State): + """ + State to fly to each waypoint in mission plan, using Obstacle Avoidance during flight + Functions: + run() -> Land: For each waypoint, flies to Latlon & avoids any obstacles + Member Variables: + None + """ + async def run(self, drone: System) -> Land: + """Run waypoint path plan with obstacle avoidance running throughout + Args: + drone: System - MAVSDK drone object for direct drone control + Returns: + Land: landing state to land drone after finishing tasks # This will change once other tasks become + testable within state machine + """ + return Land(self.state_settings) diff --git a/flight/util/json_parsing.py b/flight/util/json_parsing.py new file mode 100644 index 0000000..b060c50 --- /dev/null +++ b/flight/util/json_parsing.py @@ -0,0 +1,63 @@ +"""Utility function to parse mission data and retrieve specific component data""" +from typing import Dict, List +import json + + +def waypoint_parsing(filename: str) -> List[Dict[str, float]]: + """ + Accepts name of JSON file and extracts waypoint data for SUAS mission + Parameters + --------- + filename: str + String of data file to open and access waypoint data + Returns + ------- + List[Dict[str, float]] + List of dictionaries containing latitude, longitude and altitude of each waypoint in mission + Dict[str, float] + dictionary containing specific coordinate or attribute with string label + Raises + ------ + General Exception + Designed to catch any error that could arise and prevent corruption + """ + with open(filename) as f: + try: + data_set: Dict[str, List] = json.load(f) + except: + f.close() + f.close() + + waypoint_locs: List[Dict[str, float]] = [point for point in data_set["waypoints"]] + + return waypoint_locs + + +def stationary_obstacle_parsing(filename: str) -> List[Dict[str, float]]: + """ + Opens passed JSON file and extracts the Stationary obstacle attributes + Parameters + ---------- + filename: str + String of JSON file name and file type + Returns + ------- + stationary_obs: List[Dict[str, float]] + list of dictionaries containing latitude, longitude, radius, and height of obstacles + Dict[str, float] + dictionary containing specific coordinate or attribute with string label + Raises + ------ + General Exception + Broad exception to catch any error and prevent corruption + """ + with open(filename) as f: + try: + data_set: Dict[str, List] = json.load(f) + except: + f.close() + f.close() + + stationary_obs: List[Dict[str, float]] = [obs for obs in data_set["stationaryObstacles"]] + + return stationary_obs diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..10466d0 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,797 @@ +[[package]] +name = "aiogrpc" +version = "1.8" +description = "asyncio wrapper for grpc.io" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +grpcio = ">=1.12.0" + +[[package]] +name = "astroid" +version = "2.9.3" +description = "An abstract syntax tree for Python with inference support." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} +wrapt = ">=1.11,<1.14" + +[[package]] +name = "black" +version = "21.12b0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" +tomli = ">=0.2.6,<2.0.0" +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.3)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "click" +version = "8.0.4" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorlog" +version = "6.6.0" +description = "Add colours to the output of Python's logging module." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "grpcio" +version = "1.44.0" +description = "HTTP/2-based RPC framework" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +six = ">=1.5.2" + +[package.extras] +protobuf = ["grpcio-tools (>=1.44.0)"] + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + +[[package]] +name = "lazy-object-proxy" +version = "1.7.1" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "lxml" +version = "4.8.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["beautifulsoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +name = "mavsdk" +version = "0.23.0" +description = "Python wrapper for MAVSDK" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiogrpc = ">=1.5" +grpcio = ">=1.11.0" +protobuf = ">=3.13.0" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy" +version = "0.931" +description = "Optional static typing for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "numpy" +version = "1.22.3" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = false +python-versions = ">=3.8" + +[[package]] +name = "opencv-python" +version = "4.5.5.64" +description = "Wrapper package for OpenCV python bindings." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\" or python_version >= \"3.6\" and platform_system == \"Darwin\" and platform_machine == \"arm64\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.14.5", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "pillow" +version = "9.0.1" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "platformdirs" +version = "2.5.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "protobuf" +version = "3.19.4" +description = "Protocol Buffers" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "pylint" +version = "2.12.2" +description = "python code static checker" +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +astroid = ">=2.9.0,<2.10" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.7" +platformdirs = ">=2.2.0" +toml = ">=0.9.2" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyproj" +version = "3.3.0" +description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" +category = "main" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +certifi = "*" + +[[package]] +name = "pytesseract" +version = "0.3.9" +description = "Python-tesseract is a python wrapper for Google's Tesseract-OCR" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +packaging = ">=21.3" +Pillow = ">=8.0.0" + +[[package]] +name = "pyusb" +version = "1.2.1" +description = "Python USB access module" +category = "main" +optional = false +python-versions = ">=3.6.0" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "1.2.3" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "wrapt" +version = "1.13.3" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "459c5ffe5d56da3070f1060c92904a340a98817289941ceb86f1207887cef445" + +[metadata.files] +aiogrpc = [ + {file = "aiogrpc-1.8-py3-none-any.whl", hash = "sha256:d01e6906927649646dcd938c7d46bf718f427473450db0af6343acc2ed2cfa4d"}, + {file = "aiogrpc-1.8.tar.gz", hash = "sha256:472155a52850bd4b9493a994079f9c12c65324f02fe6466e636470127fc32aaf"}, +] +astroid = [ + {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, + {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, +] +black = [ + {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, + {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, +] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +click = [ + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +colorlog = [ + {file = "colorlog-6.6.0-py2.py3-none-any.whl", hash = "sha256:351c51e866c86c3217f08e4b067a7974a678be78f07f85fc2d55b8babde6d94e"}, + {file = "colorlog-6.6.0.tar.gz", hash = "sha256:344f73204009e4c83c5b6beb00b3c45dc70fcdae3c80db919e0a4171d006fde8"}, +] +grpcio = [ + {file = "grpcio-1.44.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:11f811c0fffd84fca747fbc742464575e5eb130fd4fb4d6012ccc34febd001db"}, + {file = "grpcio-1.44.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9a86a91201f8345502ea81dee0a55ae13add5fafadf109b17acd858fe8239651"}, + {file = "grpcio-1.44.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:5f3c54ebb5d9633a557335c01d88d3d4928e9b1b131692283b6184da1edbec0b"}, + {file = "grpcio-1.44.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d47553b8e86ab1e59b0185ba6491a187f94a0239f414c8fc867a22b0405b798"}, + {file = "grpcio-1.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1e22d3a510438b7f3365c0071b810672d09febac6e8ca8a47eab657ae5f347b"}, + {file = "grpcio-1.44.0-cp310-cp310-win32.whl", hash = "sha256:41036a574cab3468f24d41d6ed2b52588fb85ed60f8feaa925d7e424a250740b"}, + {file = "grpcio-1.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:4ee51964edfd0a1293a95bb0d72d134ecf889379d90d2612cbf663623ce832b4"}, + {file = "grpcio-1.44.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:e2149077d71e060678130644670389ddf1491200bcea16c5560d4ccdc65e3f2e"}, + {file = "grpcio-1.44.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:0ac72d4b953b76924f8fa21436af060d7e6d8581e279863f30ee14f20751ac27"}, + {file = "grpcio-1.44.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:5c30a9a7d3a05920368a60b080cbbeaf06335303be23ac244034c71c03a0fd24"}, + {file = "grpcio-1.44.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:05467acd391e3fffb05991c76cb2ed2fa1309d0e3815ac379764bc5670b4b5d4"}, + {file = "grpcio-1.44.0-cp36-cp36m-manylinux_2_17_aarch64.whl", hash = "sha256:b81dc7894062ed2d25b74a2725aaa0a6895ce97ce854f432fe4e87cad5a07316"}, + {file = "grpcio-1.44.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46d4843192e7d36278884282e100b8f305cf37d1b3d8c6b4f736d4454640a069"}, + {file = "grpcio-1.44.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:898c159148f27e23c08a337fb80d31ece6b76bb24f359d83929460d813665b74"}, + {file = "grpcio-1.44.0-cp36-cp36m-win32.whl", hash = "sha256:b8d852329336c584c636caa9c2db990f3a332b19bc86a80f4646b58d27c142db"}, + {file = "grpcio-1.44.0-cp36-cp36m-win_amd64.whl", hash = "sha256:790d7493337558ae168477d1be3178f4c9b8f91d8cd9b8b719d06fd9b2d48836"}, + {file = "grpcio-1.44.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:cd61b52d9cf8fcf8d9628c0b640b9e44fdc5e93d989cc268086a858540ed370c"}, + {file = "grpcio-1.44.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:14eefcf623890f3f7dd7831decd2a2116652b5ce1e0f1d4b464b8f52110743b0"}, + {file = "grpcio-1.44.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:bebe90b8020b4248e5a2076b56154cc6ff45691bbbe980579fc9db26717ac968"}, + {file = "grpcio-1.44.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89b390b1c0de909965280d175c53128ce2f0f4f5c0f011382243dd7f2f894060"}, + {file = "grpcio-1.44.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:c122dac5cb299b8ad7308d61bd9fe0413de13b0347cce465398436b3fdf1f609"}, + {file = "grpcio-1.44.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6641a28cc826a92ef717201cca9a035c34a0185e38b0c93f3ce5f01a01a1570a"}, + {file = "grpcio-1.44.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb0a3e0e64843441793923d9532a3a23907b07b2a1e0a7a31f186dc185bb772"}, + {file = "grpcio-1.44.0-cp37-cp37m-win32.whl", hash = "sha256:be857b7ec2ac43455156e6ba89262f7d7ae60227049427d01a3fecd218a3f88d"}, + {file = "grpcio-1.44.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f6a9cf0e77f72f2ac30c9c6e086bc7446c984c51bebc6c7f50fbcd718037edba"}, + {file = "grpcio-1.44.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:19e54f0c7083c8332b5a75a9081fc5127f1dbb67b6c1a32bd7fe896ef0934918"}, + {file = "grpcio-1.44.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:bfd36b959c3c4e945119387baed1414ea46f7116886aa23de0172302b49d7ff1"}, + {file = "grpcio-1.44.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:ccd388b8f37b19d06e4152189726ce309e36dc03b53f2216a4ea49f09a7438e6"}, + {file = "grpcio-1.44.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:9075c0c003c1ff14ebce8f0ba55cc692158cb55c68da09cf8b0f9fc5b749e343"}, + {file = "grpcio-1.44.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:e898194f76212facbaeb6d7545debff29351afa23b53ff8f0834d66611af5139"}, + {file = "grpcio-1.44.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fa6584046a7cf281649975a363673fa5d9c6faf9dc923f261cc0e56713b5892"}, + {file = "grpcio-1.44.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36a7bdd6ef9bca050c7ade8cba5f0e743343ea0756d5d3d520e915098a9dc503"}, + {file = "grpcio-1.44.0-cp38-cp38-win32.whl", hash = "sha256:dc3290d0411ddd2bd49adba5793223de8de8b01588d45e9376f1a9f7d25414f4"}, + {file = "grpcio-1.44.0-cp38-cp38-win_amd64.whl", hash = "sha256:13343e7b840c20f43b44f0e6d3bbdc037c964f0aec9735d7cb685c407731c9ff"}, + {file = "grpcio-1.44.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c5c2f8417d13386e18ccc8c61467cb6a6f9667a1ff7000a2d7d378e5d7df693f"}, + {file = "grpcio-1.44.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:cf220199b7b4992729ad4d55d5d3f652f4ccfe1a35b5eacdbecf189c245e1859"}, + {file = "grpcio-1.44.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4201c597e5057a9bfef9ea5777a6d83f6252cb78044db7d57d941ec2300734a5"}, + {file = "grpcio-1.44.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:e2de61005118ae59d48d5d749283ebfd1ba4ca68cc1000f8a395cd2bdcff7ceb"}, + {file = "grpcio-1.44.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:871078218fa9117e2a378678f327e32fda04e363ed6bc0477275444273255d4d"}, + {file = "grpcio-1.44.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8d610b7b557a7609fecee80b6dd793ecb7a9a3c3497fbdce63ce7d151cdd705"}, + {file = "grpcio-1.44.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcb53e4eb8c271032c91b8981df5fc1bb974bc73e306ec2c27da41bd95c44b5"}, + {file = "grpcio-1.44.0-cp39-cp39-win32.whl", hash = "sha256:e50ddea6de76c09b656df4b5a55ae222e2a56e625c44250e501ff3c904113ec1"}, + {file = "grpcio-1.44.0-cp39-cp39-win_amd64.whl", hash = "sha256:d2ec124a986093e26420a5fb10fa3f02b2c232f924cdd7b844ddf7e846c020cd"}, + {file = "grpcio-1.44.0.tar.gz", hash = "sha256:4bae1c99896045d3062ab95478411c8d5a52cb84b91a1517312629fa6cfeb50e"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, + {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, +] +lxml = [ + {file = "lxml-4.8.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b"}, + {file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430"}, + {file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a"}, + {file = "lxml-4.8.0-cp27-cp27m-win32.whl", hash = "sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5"}, + {file = "lxml-4.8.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9"}, + {file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc"}, + {file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170"}, + {file = "lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9"}, + {file = "lxml-4.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03"}, + {file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe"}, + {file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa"}, + {file = "lxml-4.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1"}, + {file = "lxml-4.8.0-cp310-cp310-win32.whl", hash = "sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b"}, + {file = "lxml-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76"}, + {file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6"}, + {file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2"}, + {file = "lxml-4.8.0-cp35-cp35m-win32.whl", hash = "sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150"}, + {file = "lxml-4.8.0-cp35-cp35m-win_amd64.whl", hash = "sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654"}, + {file = "lxml-4.8.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613"}, + {file = "lxml-4.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33"}, + {file = "lxml-4.8.0-cp36-cp36m-win32.whl", hash = "sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429"}, + {file = "lxml-4.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63"}, + {file = "lxml-4.8.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85"}, + {file = "lxml-4.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141"}, + {file = "lxml-4.8.0-cp37-cp37m-win32.whl", hash = "sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63"}, + {file = "lxml-4.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8"}, + {file = "lxml-4.8.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9"}, + {file = "lxml-4.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68"}, + {file = "lxml-4.8.0-cp38-cp38-win32.whl", hash = "sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696"}, + {file = "lxml-4.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939"}, + {file = "lxml-4.8.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87"}, + {file = "lxml-4.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9"}, + {file = "lxml-4.8.0-cp39-cp39-win32.whl", hash = "sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea"}, + {file = "lxml-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c"}, + {file = "lxml-4.8.0-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507"}, + {file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9"}, + {file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e"}, + {file = "lxml-4.8.0-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0"}, + {file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79"}, + {file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93"}, + {file = "lxml-4.8.0.tar.gz", hash = "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23"}, +] +mavsdk = [ + {file = "mavsdk-0.23.0-py3-none-linux_armv6l.whl", hash = "sha256:2d949045cf7f9945bc59d2fa3c718e973cca943405bc8c36e40bb54bcd3d196c"}, + {file = "mavsdk-0.23.0-py3-none-linux_armv7l.whl", hash = "sha256:4dae1997b3c534425e60b925063fa143d449f50542b91c0ff72a5e1a97adb7e5"}, + {file = "mavsdk-0.23.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c77a7eae6e8b9951253ea571e2ada63357df125fc2763f739f5cfa47dad1f460"}, + {file = "mavsdk-0.23.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:3a063409660b5c64719e5b6f0c34693f3a80646af7e571bbf3c63aeec6bcad18"}, + {file = "mavsdk-0.23.0-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3083cc06f216049b082dbaf3dda75b8908b5282b84d8d676ef0f7ce4b2f629b"}, + {file = "mavsdk-0.23.0-py3-none-win32.whl", hash = "sha256:ce0f2da0b972543879a20b89c40e406c69631afb10e7b9f90dfa73fcbfc00e87"}, + {file = "mavsdk-0.23.0-py3-none-win_amd64.whl", hash = "sha256:6a641ad177386af5b9d44dc7c93ae0e34bfbd3fc9fa4a43a50c2451921d9c60a"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mypy = [ + {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, + {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, + {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, + {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, + {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, + {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, + {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, + {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, + {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, + {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, + {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, + {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, + {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, + {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, + {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, + {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, + {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, + {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, + {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, + {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +numpy = [ + {file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"}, + {file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"}, + {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e"}, + {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4"}, + {file = "numpy-1.22.3-cp310-cp310-win32.whl", hash = "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430"}, + {file = "numpy-1.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4"}, + {file = "numpy-1.22.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce"}, + {file = "numpy-1.22.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe"}, + {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5"}, + {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1"}, + {file = "numpy-1.22.3-cp38-cp38-win32.whl", hash = "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62"}, + {file = "numpy-1.22.3-cp38-cp38-win_amd64.whl", hash = "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676"}, + {file = "numpy-1.22.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123"}, + {file = "numpy-1.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802"}, + {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d"}, + {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168"}, + {file = "numpy-1.22.3-cp39-cp39-win32.whl", hash = "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"}, + {file = "numpy-1.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a"}, + {file = "numpy-1.22.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f"}, + {file = "numpy-1.22.3.zip", hash = "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18"}, +] +opencv-python = [ + {file = "opencv-python-4.5.5.64.tar.gz", hash = "sha256:f65de0446a330c3b773cd04ba10345d8ce1b15dcac3f49770204e37602d0b3f7"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:a512a0c59b6fec0fac3844b2f47d6ecb1a9d18d235e6c5491ce8dbbe0663eae8"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6138b6903910e384067d001763d40f97656875487381aed32993b076f44375"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b293ced62f4360d9f11cf72ae7e9df95320ff7bf5b834d87546f844e838c0c35"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-win32.whl", hash = "sha256:6247e584813c00c3b9ed69a795da40d2c153dc923d0182e957e1c2f00a554ac2"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-win_amd64.whl", hash = "sha256:408d5332550287aa797fd06bef47b2dfed163c6787668cc82ef9123a9484b56a"}, + {file = "opencv_python-4.5.5.64-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7787bb017ae93d5f9bb1b817ac8e13e45dd193743cb648498fcab21d00cf20a3"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +pillow = [ + {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, + {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, + {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, + {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, + {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, + {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, + {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, + {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, + {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, + {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, + {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, + {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, + {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, + {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, +] +platformdirs = [ + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, +] +protobuf = [ + {file = "protobuf-3.19.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f51d5a9f137f7a2cec2d326a74b6e3fc79d635d69ffe1b036d39fc7d75430d37"}, + {file = "protobuf-3.19.4-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09297b7972da685ce269ec52af761743714996b4381c085205914c41fcab59fb"}, + {file = "protobuf-3.19.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072fbc78d705d3edc7ccac58a62c4c8e0cec856987da7df8aca86e647be4e35c"}, + {file = "protobuf-3.19.4-cp310-cp310-win32.whl", hash = "sha256:7bb03bc2873a2842e5ebb4801f5c7ff1bfbdf426f85d0172f7644fcda0671ae0"}, + {file = "protobuf-3.19.4-cp310-cp310-win_amd64.whl", hash = "sha256:f358aa33e03b7a84e0d91270a4d4d8f5df6921abe99a377828839e8ed0c04e07"}, + {file = "protobuf-3.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1c91ef4110fdd2c590effb5dca8fdbdcb3bf563eece99287019c4204f53d81a4"}, + {file = "protobuf-3.19.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c438268eebb8cf039552897d78f402d734a404f1360592fef55297285f7f953f"}, + {file = "protobuf-3.19.4-cp36-cp36m-win32.whl", hash = "sha256:835a9c949dc193953c319603b2961c5c8f4327957fe23d914ca80d982665e8ee"}, + {file = "protobuf-3.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4276cdec4447bd5015453e41bdc0c0c1234eda08420b7c9a18b8d647add51e4b"}, + {file = "protobuf-3.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6cbc312be5e71869d9d5ea25147cdf652a6781cf4d906497ca7690b7b9b5df13"}, + {file = "protobuf-3.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54a1473077f3b616779ce31f477351a45b4fef8c9fd7892d6d87e287a38df368"}, + {file = "protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:435bb78b37fc386f9275a7035fe4fb1364484e38980d0dd91bc834a02c5ec909"}, + {file = "protobuf-3.19.4-cp37-cp37m-win32.whl", hash = "sha256:16f519de1313f1b7139ad70772e7db515b1420d208cb16c6d7858ea989fc64a9"}, + {file = "protobuf-3.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc076c03381f5c1d9bb1abdcc5503d9ca8b53cf0a9d31a9f6754ec9e6c8af0f"}, + {file = "protobuf-3.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69da7d39e39942bd52848438462674c463e23963a1fdaa84d88df7fbd7e749b2"}, + {file = "protobuf-3.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48ed3877fa43e22bcacc852ca76d4775741f9709dd9575881a373bd3e85e54b2"}, + {file = "protobuf-3.19.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd95d1dfb9c4f4563e6093a9aa19d9c186bf98fa54da5252531cc0d3a07977e7"}, + {file = "protobuf-3.19.4-cp38-cp38-win32.whl", hash = "sha256:b38057450a0c566cbd04890a40edf916db890f2818e8682221611d78dc32ae26"}, + {file = "protobuf-3.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:7ca7da9c339ca8890d66958f5462beabd611eca6c958691a8fe6eccbd1eb0c6e"}, + {file = "protobuf-3.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36cecbabbda242915529b8ff364f2263cd4de7c46bbe361418b5ed859677ba58"}, + {file = "protobuf-3.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c1068287025f8ea025103e37d62ffd63fec8e9e636246b89c341aeda8a67c934"}, + {file = "protobuf-3.19.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96bd766831596d6014ca88d86dc8fe0fb2e428c0b02432fd9db3943202bf8c5e"}, + {file = "protobuf-3.19.4-cp39-cp39-win32.whl", hash = "sha256:84123274d982b9e248a143dadd1b9815049f4477dc783bf84efe6250eb4b836a"}, + {file = "protobuf-3.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:3112b58aac3bac9c8be2b60a9daf6b558ca3f7681c130dcdd788ade7c9ffbdca"}, + {file = "protobuf-3.19.4-py2.py3-none-any.whl", hash = "sha256:8961c3a78ebfcd000920c9060a262f082f29838682b1f7201889300c1fbe0616"}, + {file = "protobuf-3.19.4.tar.gz", hash = "sha256:9df0c10adf3e83015ced42a9a7bd64e13d06c4cf45c340d2c63020ea04499d0a"}, +] +pylint = [ + {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, + {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, +] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] +pyproj = [ + {file = "pyproj-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2c41c9b7b5e1a1b0acc2b7b2f5de65b226f7b96c870888e4f679ff96322b1ed0"}, + {file = "pyproj-3.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0e1fd560b509b722db6566fa9685252f25640e93464d09e13d5190ed7ab491ba"}, + {file = "pyproj-3.3.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277072176a17471c0b1d25d6cae75401d81e9b50ea625ba546f5b79acd757dfc"}, + {file = "pyproj-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca8ecf2b6b3225d93c723e6a2f51143d9195ac407f69e979363cdde344b93bb"}, + {file = "pyproj-3.3.0-cp310-cp310-win32.whl", hash = "sha256:4d2fc49c73d9f34e932bf37926d56916ba1b6f2f693cd4d8cc1d0d9eacc0e537"}, + {file = "pyproj-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:ce1adec823738e2d7c6af019fc38f58b4204bacfc782e4430373578c672f3833"}, + {file = "pyproj-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e70a1ea6f198cace1a492397bdd0a46e640201120973293d6c48031e370d6a87"}, + {file = "pyproj-3.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:99f171da5f885efeec8d7fb2e2557175ffa8834eeb488842b1f52ac78a9a98e5"}, + {file = "pyproj-3.3.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3d28b84913cd849832a8f154c0e0c2ee4618057f7389ee68bfdb2145e7ed78cc"}, + {file = "pyproj-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab4baf781721640659db83a6b4da636fc403008f4978c668275754284c946778"}, + {file = "pyproj-3.3.0-cp38-cp38-win32.whl", hash = "sha256:4125e6704751d0e82d8d912d9851da097e8d38599d4c45f9944faaeb21771938"}, + {file = "pyproj-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b15e199c1da8fd132e11dfa68d8cf65d4812dedabc776b308df778ecd0d07658"}, + {file = "pyproj-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fcceb6736085bf19291b707bc67c8cebe05330bd02268e9b8eba6d28a1905fce"}, + {file = "pyproj-3.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf479bd481774ad217e9db5674868eee8f01dfe3868f61753328895ae7da61a"}, + {file = "pyproj-3.3.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:067a5c6099949edd66e9a10b139af4e2f65ebadb9f59583923a1d3feefac749a"}, + {file = "pyproj-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:235b52d8700ffb6be1f3638b1e25d83a9c13edcdb793236d8a98fd39227c5c27"}, + {file = "pyproj-3.3.0-cp39-cp39-win32.whl", hash = "sha256:44b5590c0b8dd002154916e170ef88f57abf91005b34bcb23faef97abb4d42c2"}, + {file = "pyproj-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b48dd9e5736957707fce1d9253fb0772bcf80480198c7790e21fed73fee61240"}, + {file = "pyproj-3.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5a105bfe37c78416d2641cd5d3368c99057d041f15f8d51ea3898953b21395c9"}, + {file = "pyproj-3.3.0.tar.gz", hash = "sha256:ce8bfbc212729e9a643f5f5d77f7a93394e032eda1e2d8799ae902d08add747e"}, +] +pytesseract = [ + {file = "pytesseract-0.3.9-py2.py3-none-any.whl", hash = "sha256:fecda37d1e4eaf744c657cd03a5daab4eb97c61506ac5550274322c8ae32eca2"}, + {file = "pytesseract-0.3.9.tar.gz", hash = "sha256:7e2bafc7f48d1bb71443ce4633a56f5e21925a98f220a36c336297edcd1956d0"}, +] +pyusb = [ + {file = "pyusb-1.2.1-py3-none-any.whl", hash = "sha256:2b4c7cb86dbadf044dfb9d3a4ff69fd217013dbe78a792177a3feb172449ea36"}, + {file = "pyusb-1.2.1.tar.gz", hash = "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, +] +typing-extensions = [ + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, +] +wrapt = [ + {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, + {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, + {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, + {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, + {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, + {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, + {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, + {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, + {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, + {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, + {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, + {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, + {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, + {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, + {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, + {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, + {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, + {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, + {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..526178b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[tool.poetry] +name = "suas-2022" +version = "0.1.0" +description = "" +authors = [] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.8" +pip = "^22.0.4" +mavsdk = "*" +numpy = "^1.21.2" +opencv-python = "^4.5.3.56" +pytesseract = "^0.3.9" +lxml = "^4.8.0" +colorlog = "^6.6.0" +pyproj = "^3.3.0" +pyusb = "^1.2.1" +mypy = "^0.931" + +[tool.poetry.dev-dependencies] +black = "^21.12b0" +pylint = "^2.12.2" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/run.py b/run.py new file mode 100644 index 0000000..da9b56c --- /dev/null +++ b/run.py @@ -0,0 +1 @@ +this_is_a_really_useful_variable_name = tuple(list(tuple(list(tuple(list([1,2,3]))))))# bad code was written on this day on this time on this year diff --git a/vision/Object Detection/object_bounding_box.py b/vision/Object Detection/object_bounding_box.py index e69de29..6480eba 100644 --- a/vision/Object Detection/object_bounding_box.py +++ b/vision/Object Detection/object_bounding_box.py @@ -0,0 +1,74 @@ +""" +Find the bounding box of the contour and crop down the image to include only the object of interest. +""" +import cv2 +import numpy as np +import argparse +import random + + +def get_bounding_image(img: np.array, contour: np.array) -> np.array: + """ + Calculate the bounding box with the given contour and then crop the given image. + + Parameters + ---------- + img: numpy.array + The input image to be cropped. + contour: numpy.array + The contour located in the given image. Used to find bounding box for cropping. + + Returns + ------- + numpy.array - Cropped image. + """ + # Find the bounding box of the contour. + x, y, w, h = cv2.boundingRect(contour) + # Return the cropped image using numpy splicing. + return img[y : y + h, x : x + w] + + +if __name__ == "__main__": + """ + Driver for testing object_bounding_box. + """ + # Create object for parsing command-line arguments. + parser = argparse.ArgumentParser( + description="Read the given input image and crop based on a randomly generated contour." + ) + # Add parser arguments. + parser.add_argument("-i", "--input", type=str, help="Path to image file.") + # Parse the command line arguments to an object. + args = parser.parse_args() + + # Check if input was given. + if args.input: + # Open the img at the given path. + try: + img = cv2.imread(args.input) + except FileNotFoundError as e: + print(f"Unable to get image file: {e}") + + # Get image size info. + h, w, c = img.shape + # Generate a random contour, then find bounding box and crop the given image. + contour = np.array( + [ + [random.randint(0, w), random.randint(0, h)], + [random.randint(0, w), random.randint(0, h)], + [random.randint(0, w), random.randint(0, h)], + ], + dtype=np.int32, + ) + + # Call bounding box function. + cropped = get_bounding_image(img, contour) + # Display new image. + cv2.imshow("bounding box", cropped) + cv2.imshow("original", img) + cv2.waitKey(0) + else: + # Throw error if no input file was given. + raise FileNotFoundError( + "No input image file has been given. For help type --help" + ) diff --git a/vision/Object Detection/standard_obj_text.py b/vision/Object Detection/standard_obj_text.py new file mode 100644 index 0000000..1f20ff7 --- /dev/null +++ b/vision/Object Detection/standard_obj_text.py @@ -0,0 +1,261 @@ +""" +Algorithms related to detecting the text of standard objects. +""" + +import cv2 +import numpy as np +import pytesseract + + +class TextCharacteristics: + """ + Class for detecting characteristics of text on standard objects. + Characteristics consist of the character, orientation, and color. + """ + + def __init__(self): + self.image = np.array([]) + + def get_text_characteristics(self, img: np.ndarray, bounds: np.ndarray) -> tuple: + """ + Gets the characteristics of the text on the standard object. + + Parameters + ---------- + img : np.ndarray + image to find characteristics of text within + bounds : np.ndarray + bounds of standard object containing text on in image + + Returns + ------- + tuple + characteristics of the text in the form (character, orientation, color) + """ + ## Get the character ## + characters = self._detect_text(img, bounds) + if len(characters) != 1: + return (None, None, None) + character, char_bounds = characters[0] + + ## Get the orientation ## + orientation = self._get_orientation(img, char_bounds) + + ## Get the color of the text ## + color = self._get_text_color(img, char_bounds) + + return (character, orientation, color) + + def _detect_text(self, img: np.ndarray, bounds: np.ndarray) -> list: + """ + Detect text within an image. + Will return string for parameter odlc.alphanumeric + + Parameters + ---------- + img : np.ndarray + image to detect text within + bounds : np.ndarray + array of tuple bounds (4 x-y coordinates) + + Returns + ------- + list + list containing detected characters and their bounds, Format of characters is ([bounds], 'character'). + """ + ## Crop and rotate the image ## + rotated_img = self._slice_rotate_img(img, bounds) + + ## Image preprocessing to make text more clear ## + processed_img = self._preprocess_img(rotated_img) + output_image = np.dstack((processed_img, processed_img, processed_img)) + + ## Detect Text ## + print("Image processing complete.") + txt_data = pytesseract.image_to_data( + output_image, + output_type=pytesseract.Output.DICT, + lang="eng", + config="--psm 10", + ) + + ## Filter detected text to find valid characters ## + found_characters = [] + for i, txt in enumerate(txt_data["text"]): + if (txt != None) and (len(txt) == 1): # length of 1 + # must be uppercase letter or number + if (txt.isalpha() and txt.isupper()) or txt.isnumeric(): + # get data for each text object detected + x = txt_data["left"][i] + y = txt_data["top"][i] + w = txt_data["width"][i] + h = txt_data["height"][i] + + # Don't continue processing if text is size of full image + img_h, img_w = np.shape(processed_img) + if not (x == 0 and y == 0 and w == img_w and h == img_h): + bounds = [(x, y), (x + w, y), (x + w, y + h), (x, y + h)] + + # add to found characters array + found_characters += [(txt, bounds)] + + return found_characters + + def _get_text_color(self, img: np.ndarray, bounds: np.ndarray) -> str: + """ + Detect the color of the text. + + Parameters + ---------- + img : np.ndarray + the image the text is in + bounds : np.ndarray + bounds of the text + + Returns + ------- + str + the color of the text + """ + ## TEMP: Implemmentation out of scope of current issue + color = "Green" + + return color + + def _get_orientation(self, img: np.ndarray, bounds: np.ndarray) -> str: + """ + Get the orientation of the text. + + Parameters + ---------- + img : np.ndarray + the image the text is in + bounds : np.ndarray + bounds of the text + """ + ## TEMP: Implemmentation out of scope of current issue + orientation = "N" + + return orientation + + def _slice_rotate_img(self, img: np.ndarray, bounds: np.ndarray) -> np.ndarray: + """ + Slice a portion of an image and rotate to be rectangular. + + Parameters + ---------- + img : np.ndarray + the image to take a splice of + bounds : np.ndarray + array of tuple bounds (4 x-y coordinates; tl-tr-br-bl) + + Returns + ------- + np.ndarray + the spliced/rotated images + """ + ## Slice image around bounds and find center point ## + x_vals = [coord[0] for coord in bounds] + y_vals = [coord[1] for coord in bounds] + min_x = np.amin(x_vals) + max_x = np.amax(x_vals) + min_y = np.amin(y_vals) + max_y = np.amax(y_vals) + + cropped_img = img[min_x:max_x][min_y:max_y][:] + + dimensions = np.shape(cropped_img) + center_pt = ( + int(dimensions[0] / 2), + int( + dimensions[1] / 2, + ), + ) + + ## Get angle of rotation ## + ## TODO: 1st index depends on how bounds stored for standard object + tl_x = bounds[0][0] + tr_x = bounds[3][0] + tl_y = bounds[0][1] + tr_y = bounds[3][1] + angle = np.rad2deg(np.arctan((tr_y - tl_y) / (tr_x - tl_x))) + + ## Rotate image ## + rot_mat = cv2.getRotationMatrix2D(center_pt, angle, 1.0) + rotated_img = cv2.warpAffine( + cropped_img, rot_mat, cropped_img.shape[1::-1], flags=cv2.INTER_LINEAR + ) + + return rotated_img + + def _preprocess_img(self, img: np.ndarray) -> np.ndarray: + """ + Preprocess image for text detection. + + Parameters + ---------- + img : np.ndarray + image to preprocess + + Returns + ------- + np.ndarray + the image after preprocessing + """ + # grayscale + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) + + # blur to remove noise + blur = cv2.medianBlur(gray, ksize=9) + + # erode and dilate to increase text clarity and reduce noise + kernel = np.ones((5, 5), np.uint8) + eroded = cv2.erode(blur, kernel=kernel, iterations=1) + dilated = cv2.dilate(eroded, kernel=kernel, iterations=1) + + # laplace edge detection + laplace_img = cv2.Laplacian(dilated, ddepth=cv2.CV_8U, ksize=5) + + # binarize image + binarized = np.where(laplace_img > 50, np.uint8(255), np.uint8(0)) + + # additional blur to remove noise + blur_2 = cv2.medianBlur(binarized, ksize=3) + + return blur_2 + + +if __name__ == "__main__": + """ + Driver for testing text detection and classification functions. + """ + import argparse + + parser = argparse.ArgumentParser( + description="Runs text characteristics algorithms. Must specify a file." + ) + + parser.add_argument( + "-f", + "--file_name", + type=str, + help="Filename of the image. Required argument.", + ) + + args = parser.parse_args() + + # no benchmark name specified, cannot continue + if not args.file_name: + raise RuntimeError("No file specified.") + file_name = args.file_name + + img = cv2.imread(file_name) + + # bounds for stock image, given by standard object detection + ## TODO: Change to function once implemented + bounds = [[77, 184], [3, 91], [120, 0], [194, 82]] + + detector = TextCharacteristics() + detected_chars = detector.get_text_characteristics(img, bounds) + + print("The following character was found in the image:", detected_chars)