Skip to content

Commit

Permalink
Merge branch 'main' into fix/parsing_for_rvc4
Browse files Browse the repository at this point in the history
  • Loading branch information
kkeroo authored Aug 29, 2024
2 parents d15195a + 1043366 commit ad57871
Show file tree
Hide file tree
Showing 44 changed files with 1,976 additions and 460 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

Expand Down Expand Up @@ -162,4 +161,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

.DS_Store
.DS_Store
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ It outlines our workflow and standards for contributing to this project.

## Table of Contents

- [Developing parser](#developing-parser)
- [Pre-commit Hooks](#pre-commit-hooks)
- [Documentation](#documentation)
- [Editor Support](#editor-support)
- [Making and Reviewing Changes](#making-and-reviewing-changes)

## Developing parser

Parser should be developed so that it is consistent with other parsers. Check out other parsers to see the required structure. Additionally, pay attention to the naming of the parser's attributes. Check out [NN Archive Parameters](docs/nn_archive_parameters.md).

## Pre-commit Hooks

We use pre-commit hooks to ensure code quality and consistency:
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ The project is in an alpha state, so it may be missing some critical features or

## Installation

The `depthai_nodes` package requires Python 3.8 or later and `depthai v3` installed.
While the `depthai v3` is not yet released on PyPI, you can install it with the following command:

```bash
pip install --extra-index-url https://artifacts.luxonis.com/artifactory/luxonis-python-release-local/ depthai==3.0.0a2
```

The `depthai_nodes` package is hosted on PyPI, so you can install it with `pip`.

To install the package, run:
Expand All @@ -26,6 +33,18 @@ To install the package, run:
pip install depthai-nodes
```

Before the official release on PyPI you can install the package from the GitHub repository:

```bash
git clone git@github.com:luxonis/depthai-nodes.git
```

and then install the requirements:

```bash
pip install -r requirements.txt
```

## Contributing

If you want to contribute to this project, read the instructions in [CONTRIBUTING.md](./CONTRIBUTING.md)
4 changes: 4 additions & 0 deletions depthai_nodes/ml/messages/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .classification import Classifications
from .img_detections import ImgDetectionsWithKeypoints, ImgDetectionWithKeypoints
from .keypoints import HandKeypoints, Keypoints
from .lines import Line, Lines
from .misc import AgeGender

__all__ = [
"ImgDetectionWithKeypoints",
Expand All @@ -9,4 +11,6 @@
"Keypoints",
"Line",
"Lines",
"Classifications",
"AgeGender",
]
48 changes: 48 additions & 0 deletions depthai_nodes/ml/messages/classification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import List

import depthai as dai


class Classifications(dai.Buffer):
"""Classification class for storing the class names and their respective scores.
Attributes
----------
classes : list[str]
A list of classes.
scores : list[float]
A list of corresponding probability scores.
"""

def __init__(self):
"""Initializes the Classifications object and sets the classes and scores to
empty lists."""
dai.Buffer.__init__(self)
self._classes: List[str] = []
self._scores: List[float] = []

@property
def classes(self) -> List:
"""Returns the list of classes."""
return self._classes

@property
def scores(self) -> List:
"""Returns the list of scores."""
return self._scores

@classes.setter
def classes(self, class_names: List[str]):
"""Sets the list of classes.
@param classes: A list of class names.
"""
self._classes = class_names

@scores.setter
def scores(self, scores: List[float]):
"""Sets the list of scores.
@param scores: A list of scores.
"""
self._scores = scores
4 changes: 4 additions & 0 deletions depthai_nodes/ml/messages/creators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from .classification import create_classification_message
from .depth import create_depth_message
from .detection import create_detection_message, create_line_detection_message
from .image import create_image_message
from .keypoints import create_hand_keypoints_message, create_keypoints_message
from .misc import create_age_gender_message
from .segmentation import create_segmentation_message
from .thermal import create_thermal_message
from .tracked_features import create_tracked_features_message
Expand All @@ -16,4 +18,6 @@
"create_tracked_features_message",
"create_keypoints_message",
"create_thermal_message",
"create_classification_message",
"create_age_gender_message",
]
81 changes: 81 additions & 0 deletions depthai_nodes/ml/messages/creators/classification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from typing import List, Union

import numpy as np

from ...messages import Classifications


def create_classification_message(
classes: List, scores: Union[np.ndarray, List]
) -> Classifications:
"""Create a message for classification. The message contains the class names and
their respective scores, sorted in descending order of scores.
@param classes: A list containing class names.
@type classes: List
@param scores: A numpy array of shape (n_classes,) containing the probability score of each class.
@type scores: np.ndarray
@return: A message with attributes `classes` and `scores`. `classes` is a list of classes, sorted in descending order of scores. `scores` is a list of the corresponding scores.
@rtype: Classifications
@raises ValueError: If the provided classes are None.
@raises ValueError: If the provided classes are not a list.
@raises ValueError: If the provided classes are empty.
@raises ValueError: If the provided scores are None.
@raises ValueError: If the provided scores are not a list or a numpy array.
@raises ValueError: If the provided scores are empty.
@raises ValueError: If the provided scores are not a 1D array.
@raises ValueError: If the provided scores are not of type float.
@raises ValueError: If the provided scores do not sum to 1.
@raises ValueError: If the number of labels and scores mismatch.
"""

if type(classes) == type(None):
raise ValueError("Classes should not be None.")

if not isinstance(classes, list):
raise ValueError(f"Classes should be a list, got {type(classes)}.")

if len(classes) == 0:
raise ValueError("Classes should not be empty.")

if type(scores) == type(None):
raise ValueError("Scores should not be None.")

if not isinstance(scores, np.ndarray) and not isinstance(scores, list):
raise ValueError(
f"Scores should be a list or a numpy array, got {type(scores)}."
)

if isinstance(scores, list):
scores = np.array(scores)

if len(scores) == 0:
raise ValueError("Scores should not be empty.")

if len(scores) != len(scores.flatten()):
raise ValueError(f"Scores should be a 1D array, got {scores.shape}.")

scores = scores.flatten()

if not np.issubdtype(scores.dtype, np.floating):
raise ValueError(f"Scores should be of type float, got {scores.dtype}.")

if not np.isclose(np.sum(scores), 1.0, atol=1e-1):
raise ValueError(f"Scores should sum to 1, got {np.sum(scores)}.")

if len(scores) != len(classes):
raise ValueError(
f"Number of labels and scores mismatch. Provided {len(scores)} scores and {len(classes)} class names."
)

classification_msg = Classifications()

sorted_args = np.argsort(scores)[::-1]
scores = scores[sorted_args]

classification_msg.classes = [classes[i] for i in sorted_args]
classification_msg.scores = scores.tolist()

return classification_msg
23 changes: 13 additions & 10 deletions depthai_nodes/ml/messages/creators/depth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@
def create_depth_message(
depth_map: np.array, depth_type: Literal["relative", "metric"]
) -> dai.ImgFrame:
"""Creates a depth message in the form of an ImgFrame using the provided depth map
and depth type.
Args:
depth_map (np.array): A NumPy array representing the depth map with shape (HW).
depth_type (Literal['relative', 'metric']): A string indicating the type of depth map.
It can either be 'relative' or 'metric'.
Returns:
dai.ImgFrame: An ImgFrame object containing the depth information.
"""Create a DepthAI message for a depth map.
@param depth_map: A NumPy array representing the depth map with shape (HW).
@type depth_map: np.array
@param depth_type: A string indicating the type of depth map. It can either be
'relative' or 'metric'.
@type depth_type: Literal['relative', 'metric']
@return: An ImgFrame object containing the depth information.
@rtype: dai.ImgFrame
@raise ValueError: If the depth map is not a NumPy array.
@raise ValueError: If the depth map is not 2D.
@raise ValueError: If the depth type is not 'relative' or 'metric'.
"""

if not isinstance(depth_map, np.ndarray):
Expand Down
71 changes: 46 additions & 25 deletions depthai_nodes/ml/messages/creators/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,33 @@ def create_detection_message(
labels: List[int] = None,
keypoints: List[List[Tuple[float, float]]] = None,
) -> dai.ImgDetections:
"""Create a message for the detection. The message contains the bounding boxes,
labels, and confidence scores of detected objects. If there are no labels or we only
have one class, we can set labels to None and all detections will have label set to
0.
Args:
bboxes (np.ndarray): Detected bounding boxes of shape (N,4) meaning [...,[x_min, y_min, x_max, y_max],...].
scores (np.ndarray): Confidence scores of detected objects of shape (N,).
labels (List[int], optional): Labels of detected objects of shape (N,). Defaults to None.
keypoints (List[List[Tuple[float, float]]], optional): Keypoints of detected objects of shape (N,2). Defaults to None.
Returns:
dai.ImgDetections OR ImgDetectionsWithKeypoints: Message containing the bounding boxes, labels, confidence scores, and keypoints of detected objects.
"""Create a DepthAI message for an object detection.
@param bbox: Bounding boxes of detected objects of shape (N,4) meaning [...,[x_min, y_min, x_max, y_max],...].
@type bbox: np.ndarray
@param scores: Confidence scores of detected objects of shape (N,).
@type scores: np.ndarray
@param labels: Labels of detected objects of shape (N,).
@type labels: List[int]
@param keypoints: Keypoints of detected objects of shape (N,2).
@type keypoints: Optional[List[List[Tuple[float, float]]]]
@return: Message containing the bounding boxes, labels, confidence scores, and keypoints of detected objects.
@rtype: dai.ImgDetections OR ImgDetectionsWithKeypoints
@raise ValueError: If the bboxes are not a numpy array.
@raise ValueError: If the bboxes are not of shape (N,4).
@raise ValueError: If the bboxes 2nd dimension is not of size 4.
@raise ValueError: If the bboxes are not in format [x_min, y_min, x_max, y_max] where xmin < xmax and ymin < ymax.
@raise ValueError: If the scores are not a numpy array.
@raise ValueError: If the scores are not of shape (N,).
@raise ValueError: If the scores do not have the same length as bboxes.
@raise ValueError: If the labels are not a list.
@raise ValueError: If each label is not an integer.
@raise ValueError: If the labels do not have the same length as bboxes.
@raise ValueError: If the keypoints are not a list.
@raise ValueError: If each keypoint pair is not a tuple of two floats.
@raise ValueError: If the keypoints do not have the same length as bboxes.
"""

# checks for bboxes
Expand Down Expand Up @@ -82,9 +96,9 @@ def create_detection_message(
if keypoints is not None and len(keypoints) != 0:
if not isinstance(keypoints, List):
raise ValueError(f"keypoints should be list, got {type(keypoints)}.")
for pointcloud in keypoints:
for point in pointcloud:
if not isinstance(point, Tuple):
for object_keypoints in keypoints:
for point in object_keypoints:
if not isinstance(point, Tuple) and not isinstance(point, List):
raise ValueError(
f"keypoint pairs should be list of tuples, got {type(point)}."
)
Expand Down Expand Up @@ -122,15 +136,22 @@ def create_detection_message(


def create_line_detection_message(lines: np.ndarray, scores: np.ndarray):
"""Create a message for the line detection. The message contains the lines and
confidence scores of detected lines.
Args:
lines (np.ndarray): Detected lines of shape (N,4) meaning [...,[x_start, y_start, x_end, y_end],...].
scores (np.ndarray): Confidence scores of detected lines of shape (N,).
Returns:
dai.Lines: Message containing the lines and confidence scores of detected lines.
"""Create a DepthAI message for a line detection.
@param lines: Detected lines of shape (N,4) meaning [...,[x_start, y_start, x_end, y_end],...].
@type lines: np.ndarray
@param scores: Confidence scores of detected lines of shape (N,).
@type scores: np.ndarray
@return: Message containing the lines and confidence scores of detected lines.
@rtype: Lines
@raise ValueError: If the lines are not a numpy array.
@raise ValueError: If the lines are not of shape (N,4).
@raise ValueError: If the lines 2nd dimension is not of size E{4}.
@raise ValueError: If the scores are not a numpy array.
@raise ValueError: If the scores are not of shape (N,).
@raise ValueError: If the scores do not have the same length as lines.
"""

# checks for lines
Expand Down
11 changes: 7 additions & 4 deletions depthai_nodes/ml/messages/creators/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ def create_image_message(
image: np.array,
is_bgr: bool = True,
) -> dai.ImgFrame:
"""Create a depthai message for an image array.
"""Create a DepthAI message for an image array.
@param image: Image array in HWC or CHW format.
@type image: np.array
@ivar image: Image array in HWC or CHW format.
@param is_bgr: If True, the image is in BGR format. If False, the image is in RGB
format. Defaults to True.
@type is_bgr: bool
@ivar is_bgr: If True, the image is in BGR format. If False, the image is in RGB
format.
@return: dai.ImgFrame object containing the image information.
@rtype: dai.ImgFrame
@raise ValueError: If the image shape is not CHW or HWC.
"""

if image.shape[0] in [1, 3]:
Expand Down
Loading

0 comments on commit ad57871

Please sign in to comment.