diff --git a/april_vision/__init__.py b/april_vision/__init__.py index de7ce46..06dc40d 100644 --- a/april_vision/__init__.py +++ b/april_vision/__init__.py @@ -1,4 +1,5 @@ """An AprilTags wrapper with camera discovery and axis conversion.""" +# ruff: noqa: E402 import os os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0" @@ -8,8 +9,13 @@ from .detect_cameras import CalibratedCamera, find_cameras from .frame_sources import FrameSource, USBCamera from .helpers import generate_marker_size_mapping -from .marker import (CartesianCoordinates, Marker, Orientation, - PixelCoordinates, SphericalCoordinate) +from .marker import ( + CartesianCoordinates, + Marker, + Orientation, + PixelCoordinates, + SphericalCoordinate, +) from .utils import Frame from .vision import Processor diff --git a/april_vision/cli/calibrate.py b/april_vision/cli/calibrate.py index a000810..b0445c1 100644 --- a/april_vision/cli/calibrate.py +++ b/april_vision/cli/calibrate.py @@ -1,6 +1,4 @@ -""" -Camera calibration script. -""" +"""Camera calibration script.""" import argparse import logging from datetime import datetime @@ -19,7 +17,8 @@ class CalBoard: - """Class used to represent a calibration board""" + """Class used to represent a calibration board.""" + def __init__( self, rows: int, @@ -38,10 +37,11 @@ def __init__( def corners_from_id(self, marker_id: int) -> List[Tuple[float, float, float]]: """ Takes an input of a marker ID and returns the coordinates of the corners of the marker. + The coordinates are 3D real world positions, top left of the board is 0,0,0. The Z coordinate of the board is always zero. The list of co-ords are in the order: - [bottom_left, bottom_right, top_right, top_left] + bottom_left, bottom_right, top_right, top_left """ marker_pixel_size = self.marker_size / self.marker_details.width_at_border @@ -90,6 +90,7 @@ def parse_detections( ) -> Tuple[List[Tuple[float, float, float]], List[Tuple[float, float]]]: """ Pairs up 2D pixel corners with 3D real world co-ords. + Takes the input of marker detections and the board design and outputs two lists where board_obj_points[i] pairs with board_img_points[i]. """ @@ -108,6 +109,7 @@ def parse_detections( def main(args: argparse.Namespace) -> None: + """Main function for calibrate command.""" # Setup the camera video_dev = cv2.VideoCapture(args.index) @@ -188,6 +190,12 @@ def write_cal_file( avg_reprojection_error: float, vidpid: Optional[str] = None, ) -> None: + """ + Write the calibration data to an XML file. + + This file can be loaded by the detect_cameras module. + The file is also compatible with the OpenCV calibration module. + """ LOGGER.info("Generating calibration XML file") output_filename = cal_filename if not output_filename.lower().endswith(".xml"): diff --git a/april_vision/cli/live.py b/april_vision/cli/live.py index 49a4362..04f8aba 100644 --- a/april_vision/cli/live.py +++ b/april_vision/cli/live.py @@ -25,6 +25,7 @@ def parse_properties(args: argparse.Namespace) -> List[Tuple[int, int]]: + """Parse the camera properties supplied on the command line.""" props = [] if args.set_fps is not None: diff --git a/april_vision/cli/marker_generator/__init__.py b/april_vision/cli/marker_generator/__init__.py index e0e9390..d1681c7 100644 --- a/april_vision/cli/marker_generator/__init__.py +++ b/april_vision/cli/marker_generator/__init__.py @@ -17,9 +17,9 @@ def create_subparser(subparsers: argparse._SubParsersAction) -> None: """ Marker_generator command parser. + Add multiple subparsers to deal with different modes of page generation. """ - parser = subparsers.add_parser( "marker_generator", description="Generate markers", diff --git a/april_vision/cli/marker_generator/marker_modes/mode_cal.py b/april_vision/cli/marker_generator/marker_modes/mode_cal.py index 35c14dd..3d00dd2 100644 --- a/april_vision/cli/marker_generator/marker_modes/mode_cal.py +++ b/april_vision/cli/marker_generator/marker_modes/mode_cal.py @@ -1,3 +1,4 @@ +"""Marker_generator subparser CAL_BOARD used to generate a calibration board.""" import argparse import logging @@ -14,7 +15,7 @@ def main(args: argparse.Namespace) -> None: - """Generate a calibration board""" + """Generate a calibration board.""" tag_data = get_tag_family(args.marker_family) LOGGER.info(tag_data) diff --git a/april_vision/cli/marker_generator/marker_modes/mode_image.py b/april_vision/cli/marker_generator/marker_modes/mode_image.py index 27cc48e..df719ab 100644 --- a/april_vision/cli/marker_generator/marker_modes/mode_image.py +++ b/april_vision/cli/marker_generator/marker_modes/mode_image.py @@ -1,3 +1,4 @@ +"""Marker_generator subparser IMAGE used to generate an image of a marker.""" import argparse import logging @@ -14,7 +15,7 @@ def main(args: argparse.Namespace) -> None: - """Generate a marker image""" + """Generate a marker image.""" tag_data = get_tag_family(args.marker_family) LOGGER.info(tag_data) diff --git a/april_vision/cli/marker_generator/marker_modes/mode_single.py b/april_vision/cli/marker_generator/marker_modes/mode_single.py index 3cdac2e..16ed7e1 100644 --- a/april_vision/cli/marker_generator/marker_modes/mode_single.py +++ b/april_vision/cli/marker_generator/marker_modes/mode_single.py @@ -1,3 +1,4 @@ +"""Marker_generator subparser SINGLE used to generate a PDF of a marker.""" import argparse import logging @@ -7,14 +8,21 @@ from april_vision.marker import MarkerType from ..marker_tile import MarkerTile -from ..utils import (DEFAULT_COLOUR, DEFAULT_FONT, DEFAULT_FONT_SIZE, DPI, - PageSize, mm_to_pixels, parse_marker_ranges) +from ..utils import ( + DEFAULT_COLOUR, + DEFAULT_FONT, + DEFAULT_FONT_SIZE, + DPI, + PageSize, + mm_to_pixels, + parse_marker_ranges, +) LOGGER = logging.getLogger(__name__) def main(args: argparse.Namespace) -> None: - """Generate a single marker on a page with the provided arguments""" + """Generate a single marker on a page with the provided arguments.""" tag_data = get_tag_family(args.marker_family) LOGGER.info(tag_data) diff --git a/april_vision/cli/marker_generator/marker_modes/mode_tile.py b/april_vision/cli/marker_generator/marker_modes/mode_tile.py index 1c8b6d6..7816718 100644 --- a/april_vision/cli/marker_generator/marker_modes/mode_tile.py +++ b/april_vision/cli/marker_generator/marker_modes/mode_tile.py @@ -1,3 +1,4 @@ +"""Marker generator mode to generate a PDF with multiple markers per page.""" import argparse import logging @@ -7,14 +8,21 @@ from april_vision.marker import MarkerType from ..marker_tile import MarkerTile -from ..utils import (DEFAULT_COLOUR, DEFAULT_FONT, DEFAULT_FONT_SIZE, DPI, - PageSize, mm_to_pixels, parse_marker_ranges) +from ..utils import ( + DEFAULT_COLOUR, + DEFAULT_FONT, + DEFAULT_FONT_SIZE, + DPI, + PageSize, + mm_to_pixels, + parse_marker_ranges, +) LOGGER = logging.getLogger(__name__) def main(args: argparse.Namespace) -> None: - """Generate a page of multiple markers with the provided arguments""" + """Generate a page of multiple markers with the provided arguments.""" tag_data = get_tag_family(args.marker_family) LOGGER.info(tag_data) @@ -147,6 +155,7 @@ def main(args: argparse.Namespace) -> None: def create_subparser(subparsers: argparse._SubParsersAction) -> None: """ Marker_generator subparser TILE. + Used to generate a PDF with multiple markers per page. """ parser = subparsers.add_parser("TILE") diff --git a/april_vision/cli/marker_generator/marker_tile.py b/april_vision/cli/marker_generator/marker_tile.py index c411381..3d09100 100644 --- a/april_vision/cli/marker_generator/marker_tile.py +++ b/april_vision/cli/marker_generator/marker_tile.py @@ -1,3 +1,8 @@ +""" +Used to generate an image tile which can be customised. + +These image tiles can be arranged on a page in the different modes. +""" from typing import NamedTuple import numpy as np @@ -11,12 +16,16 @@ class coord(NamedTuple): + """Simple class to store coordinates.""" + x: int y: int def generate_tag_array(tag_data: ApriltagFamily, tag_id: int) -> NDArray: """ + Generate a marker array for a given tag family and tag ID. + Uses the tag family object to generate a marker, returns this data as a 2d numpy array where each of the cells is 1 pixel of the marker. """ @@ -53,8 +62,10 @@ def generate_tag_array(tag_data: ApriltagFamily, tag_id: int) -> NDArray: class MarkerTile: """ Used to generate an image tile which can be customised. - These image tiles can be arranged on a page in the different modes + + These image tiles can be arranged on a page in the different modes. """ + def __init__( self, tag_data: ApriltagFamily, @@ -64,7 +75,8 @@ def __init__( ): """ Generate a basic marker, no overlays, scaled to the correct size. - The marker PIL.Image can be accessed via MarkerTile.image + + The marker PIL.Image can be accessed via MarkerTile.image. """ self.tag_data = tag_data self.tag_id = tag_id @@ -101,8 +113,9 @@ def add_border_line( border_colour: str, ) -> None: """ - Add a line arround the board of the marker, - changes the current marker design in place. + Add a line around the border of the marker. + + This changes the current marker design in place. """ bordered_image = ImageOps.expand( self.image, @@ -129,8 +142,9 @@ def add_centre_ticks( tick_colour: str, ) -> None: """ - Add tick lines half way along the border of the marker, - changes the current marker design in place. + Add tick lines half way along the border of the marker. + + This changes the current marker design in place. """ img_size = self.image.size[0] image_draw = ImageDraw.Draw(self.image) @@ -172,8 +186,9 @@ def add_id_number( text_colour: str, ) -> None: """ - Add the ID number in the top left square of the white border, - changes the current marker design in place. + Add the ID number in the top left square of the white border. + + This changes the current marker design in place. """ # Add text to the image marker_square_size = mm_to_pixels(self.pixel_size) @@ -211,8 +226,9 @@ def add_description_border( double_text: bool = False, ) -> None: """ - Expand the marker by one marker square and add description text to this area, - changes the current marker design in place. + Expand the marker by one marker square and add description text to this area. + + This changes the current marker design in place. """ marker_square_size = mm_to_pixels(self.pixel_size) diff --git a/april_vision/cli/marker_generator/utils.py b/april_vision/cli/marker_generator/utils.py index 5e0073c..6055e9e 100644 --- a/april_vision/cli/marker_generator/utils.py +++ b/april_vision/cli/marker_generator/utils.py @@ -1,3 +1,4 @@ +"""Utility functions for the marker generator.""" import logging from enum import Enum from typing import List, Tuple @@ -17,6 +18,7 @@ def parse_marker_ranges(marker_family: ApriltagFamily, range_str: str) -> List[int]: """ Utility function to parse the provided range of markers. + Also checks bounds against the marker family. """ if range_str == "ALL": @@ -37,17 +39,14 @@ def parse_marker_ranges(marker_family: ApriltagFamily, range_str: str) -> List[i def mm_to_pixels(mm: float) -> int: - """ - Convert millimeters to pixels - """ + """Convert millimeters to pixels.""" inches = mm / 25.4 return int(inches * DPI) class PageSize(Enum): - """ - Enum to define the dimentions of different page sizes - """ + """Enum to define the dimentions of different page sizes.""" + A3 = (297, 420) A3L = (420, 297) A4 = (210, 297) @@ -55,6 +54,7 @@ class PageSize(Enum): @property def pixels(self) -> Tuple[int, int]: + """Return the page size in pixels.""" return ( mm_to_pixels(self.value[0]), mm_to_pixels(self.value[1]), diff --git a/april_vision/cli/tools/__init__.py b/april_vision/cli/tools/__init__.py index 9a66094..c37e52f 100644 --- a/april_vision/cli/tools/__init__.py +++ b/april_vision/cli/tools/__init__.py @@ -8,6 +8,7 @@ def create_subparser(subparsers: argparse._SubParsersAction) -> None: + """Subparser for the tools commands.""" parser = subparsers.add_parser( "tools", description="A collection of useful tools", diff --git a/april_vision/cli/tools/family_details.py b/april_vision/cli/tools/family_details.py index f9127de..ea11f91 100644 --- a/april_vision/cli/tools/family_details.py +++ b/april_vision/cli/tools/family_details.py @@ -1,3 +1,8 @@ +""" +Provide the details about a marker family. + +The values are extracted from the AprilTag binary. +""" import argparse import logging @@ -9,6 +14,7 @@ def main(args: argparse.Namespace) -> None: + """Provide the details about a marker family.""" tag_data = get_tag_family(args.tag_family) print(tag_data) diff --git a/april_vision/cli/tools/list_cameras.py b/april_vision/cli/tools/list_cameras.py index b9a663e..846c929 100644 --- a/april_vision/cli/tools/list_cameras.py +++ b/april_vision/cli/tools/list_cameras.py @@ -1,3 +1,4 @@ +"""List out the available cameras connected to the system.""" import argparse from tabulate import tabulate @@ -7,6 +8,7 @@ def main(args: argparse.Namespace) -> None: + """List out the available cameras connected to the system.""" cameras = find_cameras(calibrations, include_uncalibrated=True) print(tabulate(cameras, headers="keys")) diff --git a/april_vision/cli/utils.py b/april_vision/cli/utils.py index 5ed672b..52a275e 100644 --- a/april_vision/cli/utils.py +++ b/april_vision/cli/utils.py @@ -1,9 +1,12 @@ +"""Utility functions for the CLI.""" from typing import List, NamedTuple, Tuple import pyapriltags class ApriltagFamily(NamedTuple): + """Class used to represent the inforamtion about a tag family.""" + ncodes: int codes: List[int] width_at_border: int @@ -29,6 +32,7 @@ def __str__(self) -> str: def get_tag_family(family: str) -> ApriltagFamily: """ Use the C-types in pyapriltags to get the tag family object. + This object contains the required data to draw all the tags for a given family. """ d = pyapriltags.Detector(families=family) @@ -53,8 +57,9 @@ def get_tag_family(family: str) -> ApriltagFamily: def parse_ranges(ranges: str) -> List[int]: """ - Parse a comma seprated list of numbers which may include ranges - specified as hyphen-separated numbers. + Parse a comma separated list of numbers which may include ranges. + + Ranges specified as hyphen-separated numbers. From https://stackoverflow.com/questions/6405208 """ result: List[int] = [] diff --git a/april_vision/examples/camera.py b/april_vision/examples/camera.py index a7ee0f3..7da0d70 100644 --- a/april_vision/examples/camera.py +++ b/april_vision/examples/camera.py @@ -1,12 +1,22 @@ +"""An example implementation for using april_vision as a library.""" + import logging from pathlib import Path from typing import Callable, Dict, Iterable, List, Optional, Union from numpy.typing import NDArray -from april_vision import (CalibratedCamera, Frame, Marker, Processor, - USBCamera, __version__, calibrations, find_cameras, - generate_marker_size_mapping) +from april_vision import ( + CalibratedCamera, + Frame, + Marker, + Processor, + USBCamera, + __version__, + calibrations, + find_cameras, + generate_marker_size_mapping, +) from april_vision.helpers import Base64Sender LOGGER = logging.getLogger(__name__) @@ -111,9 +121,7 @@ def set_marker_sizes( self._cam.set_marker_sizes(tag_sizes) def set_detection_hook(self, callback: Callable[[Frame, List[Marker]], None]) -> None: - """ - Setup a callback to be run after each dectection. - """ + """Setup a callback to be run after each dectection.""" self._cam.detection_hook = callback diff --git a/april_vision/helpers/markers.py b/april_vision/helpers/markers.py index 6883327..5a511f0 100644 --- a/april_vision/helpers/markers.py +++ b/april_vision/helpers/markers.py @@ -7,6 +7,7 @@ def generate_marker_size_mapping( ) -> Dict[int, float]: """ Unroll a dict of iterables into a dict of floats. + To be used in pose estimation. """ tag_sizes: Dict[int, float] = {} diff --git a/april_vision/helpers/sender.py b/april_vision/helpers/sender.py index 50bf1f8..1185972 100644 --- a/april_vision/helpers/sender.py +++ b/april_vision/helpers/sender.py @@ -1,3 +1,4 @@ +"""Helper classes for sending image data.""" import base64 from threading import Thread from typing import Callable, List, Optional @@ -21,6 +22,7 @@ class Base64Sender: :param threaded: Controls whether encoding and sending the image is processed in a thread or blocks. """ + def __init__( self, publish_callback: Callable[[str, bytes], None], @@ -62,8 +64,7 @@ def annotated_frame_hook(self, frame: Frame, markers: List[Marker]) -> None: def encode_and_send(self, frame: NDArray[np.uint8]) -> None: """ - Handle converting a frame to a base64 bytestring and sending it using - the publish callback. + Convert a frame to a base64 bytestring and send it using the publish callback. Can be run as a thread target. """ diff --git a/april_vision/marker.py b/april_vision/marker.py index 087e673..58804aa 100644 --- a/april_vision/marker.py +++ b/april_vision/marker.py @@ -1,6 +1,4 @@ -""" -Classes for marker detections and various axis representations. -""" +"""Classes for marker detections and various axis representations.""" from enum import Enum from math import acos, atan2, cos, degrees, sin from typing import NamedTuple, Optional, Tuple, cast @@ -210,7 +208,8 @@ def rotation_matrix(self) -> RotationMatrix: Conversion calculation: https://w.wiki/6gbp - Returns: + Returns + ------- A 3x3 rotation matrix as a tuple of tuples. """ psi, theta, phi = self.yaw, self.pitch, self.roll @@ -235,7 +234,8 @@ def quaternion(self) -> Tuple[float, float, float, float]: Conversion calculation: https://w.wiki/6gbq - Returns: + Returns + ------- A 4-tuple hamiltonian quaternion. """ psi_2, theta_2, phi_2 = self.yaw / 2, self.pitch / 2, self.roll / 2 @@ -251,9 +251,7 @@ def quaternion(self) -> Tuple[float, float, float, float]: class Marker(NamedTuple): - """ - Wrapper of a marker detection with axis and rotation calculated. - """ + """Wrapper of a marker detection with axis and rotation calculated.""" rvec: Optional[NDArray] tvec: Optional[NDArray] @@ -281,6 +279,12 @@ def from_detection( *, aruco_orientation: bool = False, ) -> 'Marker': + """ + Create a Marker object from a pyapriltags Detection object. + + :param marker: The marker detection object. + :param aruco_orientation: Rotate marker 180 for aruco orientation. + """ _tag_size = int((marker.tag_size or 0) * 1000) _pixel_corners = tuple( @@ -334,6 +338,7 @@ def from_detection( ) def has_pose(self) -> bool: + """Return True if the marker has pose estimation data.""" return (self.rvec is not None and self.tvec is not None) def __repr__(self) -> str: diff --git a/pyproject.toml b/pyproject.toml index b580242..490a947 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,19 +62,20 @@ version_file = "april_vision/_version.py" # ### Linting Rules ### [tool.ruff] ignore = [ - "D105", - "D107", - "D401", - "D203", - "D212", + "D104", # Ignoe missing docstring in public package + "D105", # Ignore missing docstring in magic method + "D107", # Ignore missing docstring in __init__ + "D401", # Ignore first line of docstring should be in imperative mood + "D203", # Ignore 1 blank line required before class docstring + "D212", # Ignore Multi-line docstring summary should start at the first line ] line-length = 95 select = [ - "D", - "E", - "F", - "I", - "W", + "D", # pydocstyle + "E", # pycodestyle error + "F", # pyflakes + "I", # isort + "W", # pycodestyle warning ] # ### Formatting Rules ###