Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Generate Python Documentation

on: [pull_request]

jobs:
check-docstrings:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: 'pip' # caching pip dependencies
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .[dev]
- name : Check docstrings
run : pydocstyle src/ --convention google --count

build-docs:
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v3

- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install .[dev]

- name: Generate Documentation
run: pdoc example --output-dir docs

- name: Archive Documentation
uses: actions/upload-artifact@v4
with:
name: documentation
path: docs/
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,8 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# generate documentation
docs/*.html
docs/*.js
docs/example/
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,17 @@ Then, if all tests are sucessful:
The main guidelines for the coding style is defined by [PEP8](https://peps.python.org/pep-0008/). You can directly refer to the examples in the code.

Specific choices are detailed in dedicated document:
- for the mathematical notation please refer to [Coding convention for Maths](docs/convention_maths.md)
- for the mathematical notation please refer to [Coding convention for Maths](docs/convention_maths.md)

## Documentation

The documentation is generated with [pdoc](https://pdoc.dev), automatically with the CI. To generate it locally you can run:

```
pdoc example --output-dir docs --logo https://www.pollen-robotics.com/wp-content/themes/bambi-theme-main/assets/images/pollen_robotics_logo.webp --logo-link https://www.pollen-robotics.com --docformat google
```

The documentation relies on the provided docstings with the google style. [pydocstyle](http://www.pydocstyle.org/en/stable/) is used to enforced this style.
```
pydocstyle src/ --convention google --count
```
3 changes: 3 additions & 0 deletions scripts/git_hooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ echo "-------> Flake8 passed!"
mypy .
echo "-------> Mypy passed!"

# Run pydocstring against all code in the `source_code` directory
pydocstyle --convention google src/
echo "-------> pydocstring passed!"
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ dev = black==24.10.0
coverage==7.3.2
mypy==1.8.0
isort==5.13.2
pdoc==14.7.0
pydocstyle==6.3.0

[options.entry_points]
console_scripts =
Expand Down
4 changes: 4 additions & 0 deletions src/config_files/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""config_files module.

Contains example of config files that are accessible by the modules.
"""
4 changes: 4 additions & 0 deletions src/example/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""Example module.

Illustrates various use cases.
"""
60 changes: 51 additions & 9 deletions src/example/cam_config.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,50 @@
"""Camera Configuration Management.

This module provides classes and functions for managing camera configuration data stored in JSON
files. It includes a class for reading and displaying camera configuration data, as well as
functions for retrieving the names of available configuration files and their paths.
"""

import json
from importlib.resources import files
from typing import Any, List


class CamConfig:
def __init__(
self,
cam_config_json: str,
) -> None:
"""A class to manage camera configuration data from a JSON file.

This class reads a JSON file containing camera configuration data and provides methods to
access and display the information.

Attributes:
cam_config_json (str): The path to the JSON file containing the camera configuration data.
socket_to_name (dict): A dictionary mapping socket IDs to camera names.
inverted (bool): A boolean indicating whether the camera is inverted.
fisheye (bool): A boolean indicating whether the camera is a fisheye camera.
mono (bool): A boolean indicating whether the camera is a monochrome camera.
"""

def __init__(self, cam_config_json: str) -> None:
"""Initialize the camera configuration data from the given JSON file.

Args:
cam_config_json (str): The path to the JSON file containing the camera configuration data.
"""
self.cam_config_json = cam_config_json

config = json.load(open(self.cam_config_json, "rb"))
self.socket_to_name = config["socket_to_name"]
self.inverted = config["inverted"]
self.fisheye = config["fisheye"]
self.mono = config["mono"]
with open(self.cam_config_json, "rb") as f:
config = json.load(f)
self.socket_to_name = config["socket_to_name"]
self.inverted = config["inverted"]
self.fisheye = config["fisheye"]
self.mono = config["mono"]

def to_string(self) -> str:
"""Return a string representation of the camera configuration data.

Returns:
str: A string containing the camera configuration data in a human-readable format.
"""
ret_string = "Camera Config: \n"
ret_string += "Inverted: {}\n".format(self.inverted)
ret_string += "Fisheye: {}\n".format(self.fisheye)
Expand All @@ -26,11 +54,25 @@ def to_string(self) -> str:


def get_config_files_names() -> List[str]:
"""Return a list of the names of the JSON configuration files in the config_files package.

Returns:
List[str]: A list of the names of the JSON configuration files in the config_files package.
"""
path = files("config_files")
return [file.stem for file in path.glob("**/*.json")] # type: ignore[attr-defined]


def get_config_file_path(name: str) -> Any:
"""Return the path to the JSON configuration file with the given name in the config_files package.

Args:
name (str): The name of the JSON configuration file.

Returns:
Any: The path to the JSON configuration file with the given name in the config_files package.
If the file is not found, returns None.
"""
path = files("config_files")
for file in path.glob("**/*"): # type: ignore[attr-defined]
if file.stem == name:
Expand Down
53 changes: 52 additions & 1 deletion src/example/celcius.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,81 @@
"""Celsius Temperature Conversion and Management.

This module provides a class for managing Celsius temperatures and converting them to Fahrenheit.
It also includes a main function that demonstrates the usage of the class.
"""

import logging


class Celsius:
"""Manage celcius temperature and other format."""
"""A class to manage Celsius temperature and convert it to other formats.

This class provides a way to store and manipulate Celsius temperatures, as well as convert
them to Fahrenheit. It also includes a check to ensure that the temperature is not below
absolute zero (-273.15°C).

Attributes:
_temperature (float): The current temperature in Celsius.
"""

def __init__(self, temperature: float = 0):
"""Initialize the logger and the temperature attribute.

Args:
temperature (float, optional): The initial temperature in Celsius. Defaults to 0.
"""
self._logger = logging.getLogger(__name__)
self._temperature = temperature

def to_fahrenheit(self) -> float:
"""Convert the current temperature from Celsius to Fahrenheit.

Returns:
float: The temperature in Fahrenheit.
"""
return (self._temperature * 1.8) + 32

@property
def temperature(self) -> float:
"""A property decorator that allows access to the temperature attribute.

This property decorator provides a way to access the temperature attribute from outside
the class. It also logs a message indicating that the value is being retrieved.

Returns:
float: The current temperature in Celsius.
"""
self._logger.info("Getting value...")
return self._temperature

@temperature.setter
def temperature(self, value: float) -> None:
"""A setter for the temperature property.

This method allows the value of the temperature attribute to be changed from outside the
class. It also logs a message indicating that the value is being set and checks that the
temperature is not below absolute zero.

Args:
value (float): The new temperature in Celsius.

Raises:
ValueError: If the temperature is below -273.15°C.
"""
self._logger.info("Setting value...")
if value < -273.15:
raise ValueError("Temperature below -273 is not possible")
self._temperature = value


def main() -> None:
"""The main function that demonstrates the usage of the Celsius class.

This function creates an instance of the Celsius class, sets its temperature, and prints
the equivalent temperature in Fahrenheit. It also activates logging at the INFO level.
"""
logging.basicConfig(level=logging.INFO)

print("Test entry point")
temp = Celsius(37)
temp.temperature = -30
Expand Down
29 changes: 27 additions & 2 deletions src/example/foo.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,43 @@


class Foo:
"""This is a template class"""
"""This is a template class."""

def __init__(self) -> None:
"""Set up empty slots."""
"""Set up empty slots and initialize the logger and private variable.

This method is called when an object of the class is created. It sets up the logger and
initializes the private variable.
"""
self._logger = logging.getLogger(__name__)
self._logger.info("Constructor")
self._private_variable = "private"
self.public_variable = "public"

@property
def private_variable(self) -> str:
"""A property decorator that allows access to the private variable.

This property decorator provides a way to access the private variable from outside
the class. It returns the value of the private variable.
"""
return self._private_variable

@private_variable.setter
def private_variable(self, value: str) -> None:
"""A setter for the private_variable property.

This method allows the value of the private variable to be changed from outside the
class. It sets the value of the private variable to the provided argument.
"""
self._private_variable = value

def __del__(self) -> None:
"""The destructor method called when the object is about to be destroyed.

This method is called when an object of the class is about to be destroyed. It logs a
message indicating that the destructor has been called.
"""
self._logger.info("Destructor")

@overload
Expand All @@ -32,6 +51,12 @@ def doingstuffs(self, var: int, var2: float) -> None: ...
def doingstuffs(self, var: int) -> None: ...

def doingstuffs(self, var: Any = None, var2: Any = None) -> None:
"""An overloaded method that takes one or two arguments and logs their values and types.

This method demonstrates the use of overloading in Python. It takes one or two arguments
and logs their values and types using the logger. If no arguments are provided, it does
nothing.
"""
if var is not None:
self._logger.info(f"{var} {type(var)} ")
if var2 is not None:
Expand Down
35 changes: 32 additions & 3 deletions src/example/xterrabot.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
"""XTerraBot.

Illustrate Maths notation based on
https://www.mecharithm.com/
homogenous-transformation-matrices-configurations-in-robotics/.
"""

import logging

import numpy as np
import numpy.typing as npt


class XTerraBot:
"""Illustrate Maths notation based on
https://www.mecharithm.com/
homogenous-transformation-matrices-configurations-in-robotics/."""
"""XTerraBot class.

This class illustrates the use of homogeneous transformation matrices to represent the
configuration of a robot in the context of robotics. It demonstrates the calculation of
the transformation matrix of an object with respect to the gripper frame using matrix
multiplication and inversion.
"""

def __init__(self) -> None:
"""Constructor.

Initialize the logger and the homogeneous transformation matrices representing the
configuration of the robot.
"""
self._logger = logging.getLogger(__name__)
# b is the mobile base
# d is the camera
Expand All @@ -27,6 +43,19 @@ def __init__(self) -> None:
self._T_a_d = np.array([[0, 0, -1, 400], [0, -1, 0, 50], [-1, 0, 0, 300], [0, 0, 0, 1]]) # a is the root

def get_object_in_gripper_frame(self) -> npt.NDArray[np.float64]:
"""Get the object in the gripper frame.

Calculate and return the homogeneous transformation matrix representing the
configuration of the object with respect to the gripper frame.

This method calculates the transformation matrix (T_c_e) by performing matrix
multiplication and inversion on the given transformation matrices. It returns the
resulting matrix as a NumPy array.

Returns:
np.ndarray: The homogeneous transformation matrix representing the configuration
of the object with respect to the gripper frame.
"""
T_c_e = (
np.linalg.inv(self._T_b_c)
@ np.linalg.inv(self._T_d_b)
Expand Down
Loading
Loading