Skip to content

Commit 2f309e4

Browse files
authoredMar 24, 2024··
Python formatting tools (#26)
* Formatting fixes * Configuration formatting * PR fixes * Formatting * Make module names clearer
1 parent a886a38 commit 2f309e4

18 files changed

+321
-143
lines changed
 

‎.github/workflows/run-tests.yml

+7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ jobs:
3939
- name: Install zbar library
4040
run: sudo apt-get install libzbar0
4141

42+
# Run linters and formatters
43+
- name: Linters and formatters
44+
run: |
45+
black --check .
46+
flake8 .
47+
pylint .
48+
4249
# Install dependencies and run tests with PyTest
4350
- name: Run PyTest
4451
run: pytest -vv

‎.gitignore

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
# Python
66
__pycache__/
7-
.pytest_cache/
87
venv/
98

109
# Logging
11-
*log*
10+
logs/

‎README.md

-16
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,3 @@
33
Common code across WARG repositories.
44

55
Follow the [instructions](https://uwarg-docs.atlassian.net/l/cp/2a6u0duY).
6-
7-
## comms
8-
9-
Python serialization and deserialization for Autonomy.
10-
11-
## camera
12-
13-
Python video ingest from camera device.
14-
15-
## mavlink
16-
17-
Connection to drone using the MAVLink protocol.
18-
19-
## qr
20-
21-
QR scanning and text output.

‎camera/modules/camera_device.py

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
2-
Camera device using OpenCV
2+
Camera device using OpenCV.
33
"""
4+
45
import sys
56
import time
67

@@ -10,15 +11,15 @@
1011

1112
class CameraDevice:
1213
"""
13-
Wrapper for camera
14+
Wrapper for camera.
1415
"""
1516

16-
def __init__(self, name: "int | str", save_nth_image: int = 0, save_name: str = ""):
17+
def __init__(self, name: "int | str", save_nth_image: int = 0, save_name: str = "") -> None:
1718
"""
18-
name: Device name or index (e.g. /dev/video0 )
19+
name: Device name or index (e.g. /dev/video0 ).
1920
(optional) save_nth_image: For debugging, saves every nth image.
2021
A value of 0 indicates no images should be saved
21-
(optional) save_name: For debugging, file name for saved images
22+
(optional) save_name: For debugging, file name for saved images.
2223
"""
2324
self.__camera = cv2.VideoCapture(name)
2425
assert self.__camera.isOpened()
@@ -32,7 +33,10 @@ def __init__(self, name: "int | str", save_nth_image: int = 0, save_name: str =
3233
if save_name != "":
3334
self.__filename_prefix = save_name + "_" + str(int(time.time())) + "_"
3435

35-
def __del__(self):
36+
def __del__(self) -> None:
37+
"""
38+
Destructor.
39+
"""
3640
self.__camera.release()
3741

3842
def get_image(self) -> "tuple[bool, np.ndarray]":
@@ -41,9 +45,9 @@ def get_image(self) -> "tuple[bool, np.ndarray]":
4145
"""
4246
result, image = self.__camera.read()
4347
if not result:
44-
return result, image
48+
return False, None
4549

46-
if self.__divisor != 0:
50+
if self.__filename_prefix != "" and self.__divisor != 0:
4751
if self.__counter % self.__divisor == 0:
4852
filename = self.__filename_prefix + str(self.__counter) + ".png"
4953
cv2.imwrite(filename, image)

‎camera/test_camera.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22
Test camera physically.
33
"""
44

5+
import pathlib
6+
57
import cv2
68

79
from camera.modules.camera_device import CameraDevice
810

911

10-
IMAGE_LOG_PREFIX = "log_image"
12+
IMAGE_LOG_PREFIX = pathlib.Path("logs", "log_image")
1113

1214

13-
if __name__ == "__main__":
15+
def main() -> int:
16+
"""
17+
Main function.
18+
"""
1419
device = CameraDevice(0, 100, IMAGE_LOG_PREFIX)
1520

1621
while True:
@@ -24,7 +29,15 @@
2429
cv2.imshow("Camera", image)
2530

2631
# Delay for 1 ms
27-
if cv2.waitKey(1) & 0xFF == ord('q'):
32+
if cv2.waitKey(1) & 0xFF == ord("q"):
2833
break
2934

35+
return 0
36+
37+
38+
if __name__ == "__main__":
39+
result_main = main()
40+
if result_main < 0:
41+
print(f"ERROR: Status code: {result_main}")
42+
3043
print("Done!")

‎camera_qr_example.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from qr.modules import qr_scanner
99

1010

11-
if __name__ == "__main__":
12-
11+
def main() -> int:
12+
"""
13+
Main function.
14+
"""
1315
camera = camera_device.CameraDevice(0, 0, "")
1416

1517
text = ""
@@ -30,4 +32,12 @@
3032

3133
print(text)
3234

35+
return 0
36+
37+
38+
if __name__ == "__main__":
39+
result_main = main()
40+
if result_main < 0:
41+
print(f"ERROR: Status code: {result_main}")
42+
3343
print("Done!")

‎kml/modules/ground_locations_to_kml.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Module to convert ground locations list to kml document.
33
"""
4+
45
import pathlib
56
import time
67

@@ -9,9 +10,11 @@
910
from . import location_ground
1011

1112

12-
def ground_locations_to_kml(ground_locations: "list[location_ground.LocationGround]",
13-
document_name_prefix: str,
14-
save_directory: pathlib.Path) -> "tuple[bool, pathlib.Path | None]":
13+
def ground_locations_to_kml(
14+
ground_locations: "list[location_ground.LocationGround]",
15+
document_name_prefix: str,
16+
save_directory: pathlib.Path,
17+
) -> "tuple[bool, pathlib.Path | None]":
1518
"""
1619
Generates KML file from a list of ground locations.
1720

‎kml/modules/location_ground.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Class to use instead of tuple for coordinates.
33
"""
44

5+
56
class LocationGround:
67
"""
78
LocationGround class represents a geographical ground location with
@@ -15,9 +16,10 @@ class LocationGround:
1516
Methods:
1617
__init__(name, latitude, longitude): Initializes a LocationGround object.
1718
__eq__(other): Checks if two LocationGround objects are equal.
18-
__repr__(): Returns a string representation of the LocationGround object.
19+
__str__(): Returns a string representation of the LocationGround object.
1920
"""
20-
def __init__(self, name: str, latitude: float, longitude: float):
21+
22+
def __init__(self, name: str, latitude: float, longitude: float) -> None:
2123
"""
2224
Constructor for the LocationGround object.
2325
@@ -46,9 +48,10 @@ def __eq__(self, other: "LocationGround") -> bool:
4648
and self.longitude == other.longitude
4749
)
4850

49-
def __repr__(self) -> str:
51+
def __str__(self) -> str:
5052
"""
51-
String representation
53+
To string.
5254
"""
53-
return \
55+
return (
5456
f"LocationGround: {self.name}, latitude: {self.latitude}, longitude: {self.longitude}"
57+
)

‎kml/test_ground_locations_to_kml.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Test Process.
33
"""
4+
45
import pathlib
56

67
import pytest
@@ -13,8 +14,13 @@
1314
EXPECTED_KML_DOCUMENT_PATH = pathlib.Path(PARENT_DIRECTORY, "expected_document.kml")
1415

1516

17+
# Test functions use test fixture signature names and access class privates
18+
# No enable
19+
# pylint: disable=protected-access,redefined-outer-name
20+
21+
1622
@pytest.fixture
17-
def locations():
23+
def locations() -> "list[location_ground.LocationGround]": # type: ignore
1824
"""
1925
List of LocationGround.
2026
"""
@@ -25,8 +31,9 @@ def locations():
2531
]
2632

2733

28-
def test_locations_to_kml_with_save_path(locations: "list[location_ground.LocationGround]",
29-
tmp_path: pathlib.Path):
34+
def test_locations_to_kml_with_save_path(
35+
locations: "list[location_ground.LocationGround]", tmp_path: pathlib.Path
36+
) -> None:
3037
"""
3138
Basic test case to save KML to the correct path when provided.
3239
"""
@@ -51,5 +58,6 @@ def test_locations_to_kml_with_save_path(locations: "list[location_ground.Locati
5158
assert actual_kml_file_path.suffix == ".kml"
5259

5360
# Compare the contents of the generated KML file with the static KML file
54-
assert actual_kml_file_path.read_text(encoding="utf-8") \
55-
== EXPECTED_KML_DOCUMENT_PATH.read_text(encoding="utf-8")
61+
assert actual_kml_file_path.read_text(encoding="utf-8") == EXPECTED_KML_DOCUMENT_PATH.read_text(
62+
encoding="utf-8"
63+
)

‎mavlink/modules/drone_odometry.py

+25-31
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
"""
22
Position and orientation of drone.
33
"""
4+
45
import math
56

67

7-
# Basically a struct
8-
# pylint: disable=too-few-public-methods
98
class DronePosition:
109
"""
1110
WGS 84 following ISO 6709 (latitude before longitude).
1211
"""
12+
1313
__create_key = object()
1414

1515
@classmethod
16-
def create(cls,
17-
latitude: "float | None",
18-
longitude: "float | None",
19-
altitude: "float | None") -> "tuple[bool, DronePosition | None]":
16+
def create(
17+
cls, latitude: "float | None", longitude: "float | None", altitude: "float | None"
18+
) -> "tuple[bool, DronePosition | None]":
2019
"""
2120
latitude, longitude in decimal degrees.
2221
altitude in metres.
@@ -35,11 +34,9 @@ def create(cls,
3534

3635
return True, DronePosition(cls.__create_key, latitude, longitude, altitude)
3736

38-
def __init__(self,
39-
class_private_create_key,
40-
latitude: float,
41-
longitude: float,
42-
altitude: float):
37+
def __init__(
38+
self, class_private_create_key: object, latitude: float, longitude: float, altitude: float
39+
) -> None:
4340
"""
4441
Private constructor, use create() method.
4542
"""
@@ -49,25 +46,21 @@ def __init__(self,
4946
self.longitude = longitude
5047
self.altitude = altitude
5148

52-
# pylint: enable=too-few-public-methods
53-
5449

55-
# Basically a struct
56-
# pylint: disable=too-few-public-methods
5750
class DroneOrientation:
5851
"""
5952
Yaw, pitch, roll following NED system (x forward, y right, z down).
6053
Specifically, intrinsic (Tait-Bryan) rotations in the zyx/3-2-1 order.
6154
"""
55+
6256
__create_key = object()
6357

6458
@classmethod
6559
# Required for checks
6660
# pylint: disable-next=too-many-return-statements
67-
def create(cls,
68-
yaw: "float | None",
69-
pitch: "float | None",
70-
roll: "float | None") -> "tuple[bool, DroneOrientation | None]":
61+
def create(
62+
cls, yaw: "float | None", pitch: "float | None", roll: "float | None"
63+
) -> "tuple[bool, DroneOrientation | None]":
7164
"""
7265
yaw, pitch, roll in radians.
7366
"""
@@ -91,7 +84,9 @@ def create(cls,
9184

9285
return True, DroneOrientation(cls.__create_key, yaw, pitch, roll)
9386

94-
def __init__(self, class_private_create_key, yaw: float, pitch: float, roll: float):
87+
def __init__(
88+
self, class_private_create_key: object, yaw: float, pitch: float, roll: float
89+
) -> None:
9590
"""
9691
Private constructor, use create() method.
9792
"""
@@ -101,21 +96,18 @@ def __init__(self, class_private_create_key, yaw: float, pitch: float, roll: flo
10196
self.pitch = pitch
10297
self.roll = roll
10398

104-
# pylint: enable=too-few-public-methods
105-
10699

107-
# Basically a struct
108-
# pylint: disable=too-few-public-methods
109100
class DroneOdometry:
110101
"""
111102
Wrapper for DronePosition and DroneOrientation.
112103
"""
104+
113105
__create_key = object()
114106

115107
@classmethod
116-
def create(cls,
117-
position: DronePosition,
118-
orientation: DroneOrientation) -> "tuple[bool, DroneOdometry | None]":
108+
def create(
109+
cls, position: DronePosition, orientation: DroneOrientation
110+
) -> "tuple[bool, DroneOdometry | None]":
119111
"""
120112
Position and orientation in one class.
121113
"""
@@ -127,10 +119,12 @@ def create(cls,
127119

128120
return True, DroneOdometry(cls.__create_key, position, orientation)
129121

130-
def __init__(self,
131-
class_private_create_key,
132-
position: DronePosition,
133-
orientation: DroneOrientation):
122+
def __init__(
123+
self,
124+
class_private_create_key: object,
125+
position: DronePosition,
126+
orientation: DroneOrientation,
127+
) -> None:
134128
"""
135129
Private constructor, use create() method.
136130
"""

‎mavlink/modules/flight_controller.py

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Wrapper for the flight controller.
33
"""
4+
45
import time
56

67
import dronekit
@@ -12,6 +13,7 @@ class FlightController:
1213
"""
1314
Wrapper for DroneKit-Python and MAVLink.
1415
"""
16+
1517
__create_key = object()
1618

1719
__MAVLINK_LANDING_FRAME = dronekit.mavutil.mavlink.MAV_FRAME_GLOBAL
@@ -36,7 +38,7 @@ def create(cls, address: str) -> "tuple[bool, FlightController | None]":
3638

3739
return True, FlightController(cls.__create_key, drone)
3840

39-
def __init__(self, class_private_create_key, vehicle: dronekit.Vehicle):
41+
def __init__(self, class_private_create_key: object, vehicle: dronekit.Vehicle) -> None:
4042
"""
4143
Private constructor, use create() method.
4244
"""
@@ -79,8 +81,9 @@ def get_odometry(self) -> "tuple[bool, drone_odometry.DroneOdometry | None]":
7981

8082
return True, odometry_data
8183

82-
def get_home_location(self,
83-
timeout: float) -> "tuple[bool, drone_odometry.DronePosition | None]":
84+
def get_home_location(
85+
self, timeout: float
86+
) -> "tuple[bool, drone_odometry.DronePosition | None]":
8487
"""
8588
Attempts to get the drone's home location until timeout.
8689
timeout: Seconds.
@@ -111,11 +114,11 @@ def upload_commands(self, commands: "list[dronekit.Command]") -> bool:
111114
112115
Parameters
113116
----------
114-
commands: list[dronekit.Command]
117+
commands: List of commands.
115118
116119
Returns
117120
-------
118-
bool
121+
bool: Whether the upload is successful.
119122
"""
120123
if len(commands) == 0:
121124
return False
@@ -142,16 +145,16 @@ def upload_commands(self, commands: "list[dronekit.Command]") -> bool:
142145
def upload_land_command(self, latitude: float, longitude: float) -> bool:
143146
"""
144147
Given a target latitude and longitude, overwrite the drone's current mission
145-
with a corresponding dronekit land command.
148+
with a corresponding land command.
146149
147150
Parameters
148151
----------
149-
latitude: float
150-
longitude: float
152+
latitude: Decimal degrees.
153+
longitude: Decimal degrees.
151154
152155
Returns
153156
-------
154-
bool
157+
bool: Whether the upload is successful.
155158
"""
156159
# TODO: DroneKit-Python's Command uses floating point value, which is not accurate enough for WARG. Investigate using MAVLink's integer command.
157160
landing_command = dronekit.Command(

‎mavlink/test_flight_controller.py

+23-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
Test for flight input device by printing to console.
33
"""
4-
import sys
4+
55
import time
66

77
from mavlink.modules import flight_controller
@@ -12,16 +12,19 @@
1212
TIMEOUT = 1.0 # seconds
1313

1414

15-
if __name__ == "__main__":
15+
def main() -> int:
16+
"""
17+
Main function.
18+
"""
1619
result, controller = flight_controller.FlightController.create(MISSION_PLANNER_ADDRESS)
1720
if not result:
18-
print("failed")
19-
sys.exit()
21+
print("Failed to open flight controller")
22+
return -1
2023

2124
# Get Pylance to stop complaining
2225
assert controller is not None
2326

24-
for i in range(5):
27+
for _ in range(5):
2528
result, odometry = controller.get_odometry()
2629
if result:
2730
print("lat: " + str(odometry.position.latitude))
@@ -32,23 +35,35 @@
3235
print("pitch: " + str(odometry.orientation.pitch))
3336
print("")
3437
else:
35-
print("failed")
38+
print("Failed to get odometry")
3639

3740
result, home = controller.get_home_location(TIMEOUT)
3841
if result:
3942
print("lat: " + str(home.latitude))
4043
print("lon: " + str(home.longitude))
4144
print("alt: " + str(home.altitude))
45+
else:
46+
print("Failed to get home location")
4247

4348
time.sleep(DELAY_TIME)
4449

4550
result, home = controller.get_home_location(TIMEOUT)
51+
if not result:
52+
print("Failed to get home location")
53+
return -1
4654

4755
# Create and add land command
4856
result = controller.upload_land_command(home.latitude, home.longitude)
49-
5057
if not result:
5158
print("Could not upload land command.")
52-
sys.exit()
59+
return -1
60+
61+
return 0
62+
63+
64+
if __name__ == "__main__":
65+
result_main = main()
66+
if result_main < 0:
67+
print(f"ERROR: Status code: {result_main}")
5368

5469
print("Done!")

‎mavlink/test_mission_ended.py

+22-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
Test for drone's destination at final waypoint by uploading mission and monitoring it.
33
"""
4-
import sys
4+
55
import time
66

77
import dronekit
@@ -24,8 +24,9 @@
2424

2525
# TODO: This function is to be removed when Dronekit-Python interfaces are
2626
# moved from pathing repository.
27-
def upload_mission(controller: flight_controller.FlightController,
28-
waypoints: "list[tuple[float, float, float]]") -> bool:
27+
def upload_mission(
28+
controller: flight_controller.FlightController, waypoints: "list[tuple[float, float, float]]"
29+
) -> bool:
2930
"""
3031
Add a takeoff command and waypoint following commands to the drone's
3132
command sequence, and upload them.
@@ -96,11 +97,14 @@ def upload_mission(controller: flight_controller.FlightController,
9697
return False
9798

9899

99-
if __name__ == "__main__":
100+
def main() -> int:
101+
"""
102+
Main function.
103+
"""
100104
result, controller = flight_controller.FlightController.create(MISSION_PLANNER_ADDRESS)
101105
if not result:
102106
print("Failed to create flight controller.")
103-
sys.exit()
107+
return -1
104108

105109
# Get Pylance to stop complaining
106110
assert controller is not None
@@ -117,14 +121,15 @@ def upload_mission(controller: flight_controller.FlightController,
117121
result = upload_mission(controller, waypoints)
118122
if not result:
119123
print("Failed to upload mission.")
120-
sys.exit()
124+
return -1
121125

122126
while True:
123-
result, is_drone_destination_final_waypoint \
124-
= controller.is_drone_destination_final_waypoint()
127+
result, is_drone_destination_final_waypoint = (
128+
controller.is_drone_destination_final_waypoint()
129+
)
125130
if not result:
126131
print("Failed to get if the drone's destination is the final waypoint.")
127-
sys.exit()
132+
return -1
128133

129134
# Get Pylance to stop complaining
130135
assert is_drone_destination_final_waypoint is not None
@@ -137,4 +142,12 @@ def upload_mission(controller: flight_controller.FlightController,
137142
time.sleep(DELAY_TIME)
138143

139144
print("Drone's destination is final waypoint.")
145+
return 0
146+
147+
148+
if __name__ == "__main__":
149+
result_main = main()
150+
if result_main < 0:
151+
print(f"ERROR: Status code: {result_main}")
152+
140153
print("Done!")

‎pyproject.toml

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
[tool.pylint.main]
2+
# Add files or directories matching the regular expressions patterns to the
3+
# ignore-list. The regex matches against paths and can be in Posix or Windows
4+
# format. Because '\\' represents the directory delimiter on Windows systems, it
5+
# can't be used as an escape character.
6+
ignore-paths = [
7+
# From .gitignore
8+
# IDEs
9+
".idea/",
10+
".vscode/",
11+
12+
# Python
13+
"__pycache__/",
14+
"venv/",
15+
16+
# Logging
17+
"logs/",
18+
]
19+
20+
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
21+
# number of processors available to use, and will cap the count on Windows to
22+
# avoid hangs.
23+
jobs = 0
24+
25+
# Minimum Python version to use for version dependent checks. Will default to the
26+
# version used to run pylint.
27+
py-version = "3.8"
28+
29+
# Discover python modules and packages in the file system subtree.
30+
recursive = true
31+
32+
# [tool.pylint.basic]
33+
# Good variable names which should always be accepted, separated by a comma.
34+
good-names = [
35+
"i",
36+
"j",
37+
"k",
38+
"ex",
39+
"Run",
40+
"_",
41+
42+
# Return of main()
43+
"result_main",
44+
]
45+
46+
[tool.pylint."messages control"]
47+
# Disable the message, report, category or checker with the given id(s). You can
48+
# either give multiple identifiers separated by comma (,) or put this option
49+
# multiple times (only on the command line, not in the configuration file where
50+
# it should appear only once). You can also use "--disable=all" to disable
51+
# everything first and then re-enable specific checks. For example, if you want
52+
# to run only the similarities checker, you can use "--disable=all
53+
# --enable=similarities". If you want to run only the classes checker, but have
54+
# no Warning level messages displayed, use "--disable=all --enable=classes
55+
# --disable=W".
56+
disable = [
57+
"raw-checker-failed",
58+
"bad-inline-option",
59+
"locally-disabled",
60+
"file-ignored",
61+
"suppressed-message",
62+
"useless-suppression",
63+
"deprecated-pragma",
64+
"use-symbolic-message-instead",
65+
"use-implicit-booleaness-not-comparison-to-string",
66+
"use-implicit-booleaness-not-comparison-to-zero",
67+
# WARG
68+
# Ignore TODOs
69+
"fixme",
70+
# Pylint cannot find modules
71+
"import-error",
72+
# Covered by Black formatter
73+
"line-too-long",
74+
# Pylint cannot handle 3rd party imports
75+
"no-member",
76+
# Some classes are simple
77+
"too-few-public-methods",
78+
# Function signatures
79+
"too-many-arguments",
80+
# Don't care
81+
"too-many-branches",
82+
# Line count in file
83+
"too-many-lines",
84+
# Don't care
85+
"too-many-locals",
86+
# Don't care
87+
"too-many-statements",
88+
# Don't care
89+
"too-many-return-statements",
90+
]
91+
92+
[tool.pylint.similarities]
93+
# Minimum lines number of a similarity.
94+
# Main guard
95+
min-similarity-lines = 10
96+
97+
[tool.pytest.ini_options]
98+
minversion = "6.0"
99+
100+
[tool.black]
101+
line-length = 100
102+
target-version = ["py38"]

‎qr/modules/qr_scanner.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Converts an image containing QR codes into text
2+
Converts an image containing QR codes into text.
33
"""
44

55
import numpy as np
@@ -8,18 +8,18 @@
88

99
class QrScanner:
1010
"""
11-
Wrapper for pyzbar
11+
Wrapper for pyzbar.
1212
"""
1313

14-
def __init__(self):
14+
def __init__(self) -> None:
1515
"""
16-
Nothing to do
16+
Nothing to do.
1717
"""
1818

1919
@staticmethod
2020
def get_qr_text(frame: np.ndarray) -> "tuple[bool, str | None]":
2121
"""
22-
Attempts to find and decode a QR code from the given frame
22+
Attempts to find and decode a QR code from the given frame.
2323
"""
2424
decoded_qrs = pyzbar.decode(frame)
2525
if len(decoded_qrs) == 0:

‎qr/test_qr.py

+28-28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Test on some basic QR codes.
33
"""
4+
45
import pathlib
56

67
import cv2
@@ -15,38 +16,37 @@
1516
IMAGE_2023_TASK2_ROUTES_PATH = pathlib.Path(PARENT_DIRECTORY, "2023_task2_routes.png")
1617

1718
IMAGE_2023_TASK1_ROUTE_TEXT = "Follow route: Quebec; Lima; Alpha; Tango"
18-
# Long strings
19-
# pylint: disable=[line-too-long]
20-
IMAGE_2023_TASK1_DIVERSION_TEXT = "Avoid the area bounded by: Zulu; Bravo; Tango; Uniform. Rejoin the route at Lima"
21-
IMAGE_2023_TASK2_ROUTES_TEXT = "Route number 1: 2 pers; Alpha; Hotel; 15 kg; nil; $37\n" \
22-
+ "Route number 2: 6 pers; Bravo; Oscar; 10 kg; nil; $150\n" \
23-
+ "Route number 3: 3 pers; Alpha; Papa; 15 kg; nil; $101\n" \
24-
+ "Route number 4: 2 pers; Papa; Whiskey; 15 kg; nil; $75\n" \
25-
+ "Route number 5: 4 pers; Oscar; Golf; 10 kg; nil; $83\n" \
26-
+ "Route number 6: 4 pers; Whiskey; Alpha; 15 kg; nil; $81\n" \
27-
+ "Route number 7: 2 pers; Alpha; Zulu; 15 kg; No landing Zulu – Hover for 15s; $44\n" \
28-
+ "Route number 8: 6 pers; Golf; Whiskey; 10 kg; nil; $115\n" \
29-
+ "Route number 9: 6 pers; Tango; Zulu; 15 kg; No landing Zulu – Hover for 15s; $95\n" \
30-
+ "Route number 10: 3 pers; Bravo; Tango; 10 kg; nil; $200\n" \
31-
+ "Route number 11: 5 pers; Zulu; Oscar; 15 kg; No landing Zulu – Hover for 15s; $80\n" \
32-
+ "Route number 12: 2 pers; Bravo; Papa; 10 kg; nil; $80\n" \
33-
+ "Route number 13: 2 pers; Hotel; Zulu; 15 kg; No landing Zulu – Hover for 15s; $42\n" \
34-
+ "Route number 14: 2 pers; Whiskey; Hotel; 15 kg; nil; $28\n" \
35-
+ "Route number 15: 6 pers; Tango; Oscar; 15 kg; nil; $108\n" \
36-
+ "Route number 16: 4 pers; Hotel; Tango; 15 kg; nil; $68\n" \
37-
+ "Route number 17: 3 pers; Papa; Zulu; 15 kg; No landing Zulu – Hover for 15s; $93\n" \
38-
+ "Route number 18: 2 pers; Bravo; Whiskey; 10 kg; nil; $35\n" \
39-
+ "Route number 19: 2 pers; Whiskey; Zulu; 15 kg; No landing Zulu – Hover for 15s; $110\n" \
40-
41-
# pylint: enable=[line-too-long]
19+
IMAGE_2023_TASK1_DIVERSION_TEXT = (
20+
"Avoid the area bounded by: Zulu; Bravo; Tango; Uniform. Rejoin the route at Lima"
21+
)
22+
IMAGE_2023_TASK2_ROUTES_TEXT = """Route number 1: 2 pers; Alpha; Hotel; 15 kg; nil; $37
23+
Route number 2: 6 pers; Bravo; Oscar; 10 kg; nil; $150
24+
Route number 3: 3 pers; Alpha; Papa; 15 kg; nil; $101
25+
Route number 4: 2 pers; Papa; Whiskey; 15 kg; nil; $75
26+
Route number 5: 4 pers; Oscar; Golf; 10 kg; nil; $83
27+
Route number 6: 4 pers; Whiskey; Alpha; 15 kg; nil; $81
28+
Route number 7: 2 pers; Alpha; Zulu; 15 kg; No landing Zulu – Hover for 15s; $44
29+
Route number 8: 6 pers; Golf; Whiskey; 10 kg; nil; $115
30+
Route number 9: 6 pers; Tango; Zulu; 15 kg; No landing Zulu – Hover for 15s; $95
31+
Route number 10: 3 pers; Bravo; Tango; 10 kg; nil; $200
32+
Route number 11: 5 pers; Zulu; Oscar; 15 kg; No landing Zulu – Hover for 15s; $80
33+
Route number 12: 2 pers; Bravo; Papa; 10 kg; nil; $80
34+
Route number 13: 2 pers; Hotel; Zulu; 15 kg; No landing Zulu – Hover for 15s; $42
35+
Route number 14: 2 pers; Whiskey; Hotel; 15 kg; nil; $28
36+
Route number 15: 6 pers; Tango; Oscar; 15 kg; nil; $108
37+
Route number 16: 4 pers; Hotel; Tango; 15 kg; nil; $68
38+
Route number 17: 3 pers; Papa; Zulu; 15 kg; No landing Zulu – Hover for 15s; $93
39+
Route number 18: 2 pers; Bravo; Whiskey; 10 kg; nil; $35
40+
Route number 19: 2 pers; Whiskey; Zulu; 15 kg; No landing Zulu – Hover for 15s; $110
41+
"""
4242

4343

4444
class TestScanner:
4545
"""
46-
Tests DetectTarget.run()
46+
Tests DetectTarget.run() .
4747
"""
4848

49-
def test_2023_task1_route(self):
49+
def test_2023_task1_route(self) -> None:
5050
"""
5151
Task 1 route
5252
"""
@@ -63,7 +63,7 @@ def test_2023_task1_route(self):
6363
assert actual is not None
6464
assert actual == expected
6565

66-
def test_2023_task1_diversion(self):
66+
def test_2023_task1_diversion(self) -> None:
6767
"""
6868
Task 1 diversion
6969
"""
@@ -80,7 +80,7 @@ def test_2023_task1_diversion(self):
8080
assert actual is not None
8181
assert actual == expected
8282

83-
def test_2023_task2_routes(self):
83+
def test_2023_task2_routes(self) -> None:
8484
"""
8585
Task 2 routes
8686
"""

‎requirements.txt

+10-9
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@
44
# Global
55
pytest
66

7-
# camera
7+
# Module camera
88
opencv-python
99
numpy
1010

11-
# comms
12-
libscrc
13-
pyserial
14-
15-
# mavlink
11+
# Module mavlink
1612
dronekit
1713

18-
# qr
14+
# Module qr
1915
pyzbar
2016

21-
# kml
22-
simplekml
17+
# Module kml
18+
simplekml
19+
20+
# Linters and formatters are explicitly versioned
21+
black==24.2.0
22+
flake8-annotations==3.0.1
23+
pylint==3.0.3

‎setup.cfg

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[flake8]
2+
# For annotations only
3+
select=ANN
4+
# Disable annotations for `self` and `cls`
5+
# https://github.com/sco1/flake8-annotations
6+
ignore=ANN101,ANN102
7+
# File exclusion
8+
extend-exclude=
9+
# From .gitignore
10+
# IDEs
11+
.idea/,
12+
.vscode/,
13+
14+
# Python
15+
__pycache__/,
16+
venv/,
17+
18+
# Logging
19+
logs/,

0 commit comments

Comments
 (0)
Please sign in to comment.