depthai-nodes 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- depthai_nodes/__init__.py +3 -0
- depthai_nodes/ml/__init__.py +0 -0
- depthai_nodes/ml/messages/__init__.py +21 -0
- depthai_nodes/ml/messages/classification.py +48 -0
- depthai_nodes/ml/messages/creators/__init__.py +24 -0
- depthai_nodes/ml/messages/creators/classification.py +79 -0
- depthai_nodes/ml/messages/creators/depth.py +93 -0
- depthai_nodes/ml/messages/creators/detection.py +235 -0
- depthai_nodes/ml/messages/creators/image.py +53 -0
- depthai_nodes/ml/messages/creators/keypoints.py +198 -0
- depthai_nodes/ml/messages/creators/misc.py +49 -0
- depthai_nodes/ml/messages/creators/segmentation.py +61 -0
- depthai_nodes/ml/messages/creators/thermal.py +46 -0
- depthai_nodes/ml/messages/creators/tracked_features.py +94 -0
- depthai_nodes/ml/messages/img_detections.py +139 -0
- depthai_nodes/ml/messages/keypoints.py +103 -0
- depthai_nodes/ml/messages/lines.py +137 -0
- depthai_nodes/ml/messages/misc.py +34 -0
- depthai_nodes/ml/messages/segmentation.py +41 -0
- depthai_nodes/ml/parsers/__init__.py +37 -0
- depthai_nodes/ml/parsers/age_gender.py +43 -0
- depthai_nodes/ml/parsers/classification.py +97 -0
- depthai_nodes/ml/parsers/fastsam.py +223 -0
- depthai_nodes/ml/parsers/hrnet.py +80 -0
- depthai_nodes/ml/parsers/image_output.py +81 -0
- depthai_nodes/ml/parsers/keypoints.py +107 -0
- depthai_nodes/ml/parsers/mediapipe_hand_landmarker.py +99 -0
- depthai_nodes/ml/parsers/mediapipe_palm_detection.py +155 -0
- depthai_nodes/ml/parsers/mlsd.py +102 -0
- depthai_nodes/ml/parsers/monocular_depth.py +94 -0
- depthai_nodes/ml/parsers/scrfd.py +184 -0
- depthai_nodes/ml/parsers/segmentation.py +95 -0
- depthai_nodes/ml/parsers/superanimal_landmarker.py +88 -0
- depthai_nodes/ml/parsers/thermal_image.py +48 -0
- depthai_nodes/ml/parsers/utils/__init__.py +9 -0
- depthai_nodes/ml/parsers/utils/decode_detections.py +108 -0
- depthai_nodes/ml/parsers/utils/denormalize.py +25 -0
- depthai_nodes/ml/parsers/utils/fastsam.py +320 -0
- depthai_nodes/ml/parsers/utils/medipipe.py +382 -0
- depthai_nodes/ml/parsers/utils/mlsd.py +80 -0
- depthai_nodes/ml/parsers/utils/nms.py +43 -0
- depthai_nodes/ml/parsers/utils/scrfd.py +140 -0
- depthai_nodes/ml/parsers/utils/superanimal.py +62 -0
- depthai_nodes/ml/parsers/utils/xfeat.py +354 -0
- depthai_nodes/ml/parsers/utils/yolo.py +351 -0
- depthai_nodes/ml/parsers/xfeat.py +133 -0
- depthai_nodes/ml/parsers/yolo.py +235 -0
- depthai_nodes/ml/parsers/yunet.py +168 -0
- depthai_nodes-0.0.1.dist-info/LICENSE +201 -0
- depthai_nodes-0.0.1.dist-info/METADATA +287 -0
- depthai_nodes-0.0.1.dist-info/RECORD +80 -0
- depthai_nodes-0.0.1.dist-info/WHEEL +5 -0
- depthai_nodes-0.0.1.dist-info/top_level.txt +3 -0
- examples/main.py +78 -0
- examples/utils/__init__.py +0 -0
- examples/utils/arguments.py +43 -0
- examples/utils/model.py +87 -0
- examples/utils/parser.py +137 -0
- examples/visualization/__init__.py +0 -0
- examples/visualization/classification.py +57 -0
- examples/visualization/colors.py +66 -0
- examples/visualization/detection.py +177 -0
- examples/visualization/image.py +15 -0
- examples/visualization/keypoints.py +23 -0
- examples/visualization/mapping.py +28 -0
- examples/visualization/messages.py +64 -0
- examples/visualization/segmentation.py +41 -0
- examples/visualization/visualize.py +12 -0
- tests/unittests/__init__.py +0 -0
- tests/unittests/test_creators/test_classification.py +168 -0
- tests/unittests/test_creators/test_depth.py +120 -0
- tests/unittests/test_creators/test_detections.py +191 -0
- tests/unittests/test_creators/test_hand_keypoints.py +137 -0
- tests/unittests/test_creators/test_image.py +51 -0
- tests/unittests/test_creators/test_keypoints.py +167 -0
- tests/unittests/test_creators/test_line_detections.py +103 -0
- tests/unittests/test_creators/test_misc.py +55 -0
- tests/unittests/test_creators/test_segmentation.py +44 -0
- tests/unittests/test_creators/test_thermal.py +45 -0
- tests/unittests/test_creators/test_tracked_features.py +99 -0
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .classification import Classifications
|
|
2
|
+
from .img_detections import (
|
|
3
|
+
ImgDetectionExtended,
|
|
4
|
+
ImgDetectionsExtended,
|
|
5
|
+
)
|
|
6
|
+
from .keypoints import HandKeypoints, Keypoints
|
|
7
|
+
from .lines import Line, Lines
|
|
8
|
+
from .misc import AgeGender
|
|
9
|
+
from .segmentation import SegmentationMasks
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ImgDetectionExtended",
|
|
13
|
+
"ImgDetectionsExtended",
|
|
14
|
+
"HandKeypoints",
|
|
15
|
+
"Keypoints",
|
|
16
|
+
"Line",
|
|
17
|
+
"Lines",
|
|
18
|
+
"Classifications",
|
|
19
|
+
"SegmentationMasks",
|
|
20
|
+
"AgeGender",
|
|
21
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import depthai as dai
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Classifications(dai.Buffer):
|
|
7
|
+
"""Classification class for storing the class names and their respective scores.
|
|
8
|
+
|
|
9
|
+
Attributes
|
|
10
|
+
----------
|
|
11
|
+
classes : list[str]
|
|
12
|
+
A list of classes.
|
|
13
|
+
scores : list[float]
|
|
14
|
+
A list of corresponding probability scores.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""Initializes the Classifications object and sets the classes and scores to
|
|
19
|
+
empty lists."""
|
|
20
|
+
dai.Buffer.__init__(self)
|
|
21
|
+
self._classes: List[str] = []
|
|
22
|
+
self._scores: List[float] = []
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def classes(self) -> List:
|
|
26
|
+
"""Returns the list of classes."""
|
|
27
|
+
return self._classes
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def scores(self) -> List:
|
|
31
|
+
"""Returns the list of scores."""
|
|
32
|
+
return self._scores
|
|
33
|
+
|
|
34
|
+
@classes.setter
|
|
35
|
+
def classes(self, class_names: List[str]):
|
|
36
|
+
"""Sets the list of classes.
|
|
37
|
+
|
|
38
|
+
@param classes: A list of class names.
|
|
39
|
+
"""
|
|
40
|
+
self._classes = class_names
|
|
41
|
+
|
|
42
|
+
@scores.setter
|
|
43
|
+
def scores(self, scores: List[float]):
|
|
44
|
+
"""Sets the list of scores.
|
|
45
|
+
|
|
46
|
+
@param scores: A list of scores.
|
|
47
|
+
"""
|
|
48
|
+
self._scores = scores
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .classification import create_classification_message
|
|
2
|
+
from .depth import create_depth_message
|
|
3
|
+
from .detection import create_detection_message, create_line_detection_message
|
|
4
|
+
from .image import create_image_message
|
|
5
|
+
from .keypoints import create_hand_keypoints_message, create_keypoints_message
|
|
6
|
+
from .misc import create_age_gender_message
|
|
7
|
+
from .segmentation import create_sam_message, create_segmentation_message
|
|
8
|
+
from .thermal import create_thermal_message
|
|
9
|
+
from .tracked_features import create_tracked_features_message
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"create_image_message",
|
|
13
|
+
"create_segmentation_message",
|
|
14
|
+
"create_hand_keypoints_message",
|
|
15
|
+
"create_detection_message",
|
|
16
|
+
"create_depth_message",
|
|
17
|
+
"create_line_detection_message",
|
|
18
|
+
"create_tracked_features_message",
|
|
19
|
+
"create_keypoints_message",
|
|
20
|
+
"create_thermal_message",
|
|
21
|
+
"create_classification_message",
|
|
22
|
+
"create_sam_message",
|
|
23
|
+
"create_age_gender_message",
|
|
24
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from ...messages import Classifications
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_classification_message(
|
|
9
|
+
classes: List, scores: Union[np.ndarray, List]
|
|
10
|
+
) -> Classifications:
|
|
11
|
+
"""Create a message for classification. The message contains the class names and
|
|
12
|
+
their respective scores, sorted in descending order of scores.
|
|
13
|
+
|
|
14
|
+
@param classes: A list containing class names.
|
|
15
|
+
@type classes: List
|
|
16
|
+
@param scores: A numpy array of shape (n_classes,) containing the probability score of each class.
|
|
17
|
+
@type scores: np.ndarray
|
|
18
|
+
|
|
19
|
+
@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.
|
|
20
|
+
@rtype: Classifications
|
|
21
|
+
|
|
22
|
+
@raises ValueError: If the provided classes are None.
|
|
23
|
+
@raises ValueError: If the provided classes are not a list.
|
|
24
|
+
@raises ValueError: If the provided classes are empty.
|
|
25
|
+
@raises ValueError: If the provided scores are None.
|
|
26
|
+
@raises ValueError: If the provided scores are not a list or a numpy array.
|
|
27
|
+
@raises ValueError: If the provided scores are empty.
|
|
28
|
+
@raises ValueError: If the provided scores are not a 1D array.
|
|
29
|
+
@raises ValueError: If the provided scores are not of type float.
|
|
30
|
+
@raises ValueError: If the provided scores do not sum to 1.
|
|
31
|
+
@raises ValueError: If the number of labels and scores mismatch.
|
|
32
|
+
"""
|
|
33
|
+
if isinstance(classes, type(None)):
|
|
34
|
+
raise ValueError("Classes should not be None.")
|
|
35
|
+
|
|
36
|
+
if not isinstance(classes, list):
|
|
37
|
+
raise ValueError(f"Classes should be a list, got {type(classes)}.")
|
|
38
|
+
|
|
39
|
+
if len(classes) == 0:
|
|
40
|
+
raise ValueError("Classes should not be empty.")
|
|
41
|
+
|
|
42
|
+
if type(scores) == type(None):
|
|
43
|
+
raise ValueError("Scores should not be None.")
|
|
44
|
+
|
|
45
|
+
if not isinstance(scores, np.ndarray) and not isinstance(scores, list):
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"Scores should be a list or a numpy array, got {type(scores)}."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if isinstance(scores, list):
|
|
51
|
+
scores = np.array(scores)
|
|
52
|
+
|
|
53
|
+
if len(scores) == 0:
|
|
54
|
+
raise ValueError("Scores should not be empty.")
|
|
55
|
+
|
|
56
|
+
if len(scores) != len(scores.flatten()):
|
|
57
|
+
raise ValueError(f"Scores should be a 1D array, got {scores.shape}.")
|
|
58
|
+
|
|
59
|
+
scores = scores.flatten()
|
|
60
|
+
|
|
61
|
+
if not np.issubdtype(scores.dtype, np.floating):
|
|
62
|
+
raise ValueError(f"Scores should be of type float, got {scores.dtype}.")
|
|
63
|
+
|
|
64
|
+
if not np.isclose(np.sum(scores), 1.0, atol=1e-2):
|
|
65
|
+
raise ValueError(f"Scores should sum to 1, got {np.sum(scores)}.")
|
|
66
|
+
|
|
67
|
+
if len(scores) != len(classes):
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"Number of labels and scores mismatch. Provided {len(scores)} scores and {len(classes)} class names."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
classification_msg = Classifications()
|
|
73
|
+
sorted_args = np.argsort(-scores, kind="stable")
|
|
74
|
+
scores = scores[sorted_args]
|
|
75
|
+
|
|
76
|
+
classification_msg.classes = [classes[i] for i in sorted_args]
|
|
77
|
+
classification_msg.scores = scores.tolist()
|
|
78
|
+
|
|
79
|
+
return classification_msg
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import depthai as dai
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
UINT16_MAX_VALUE = 65535
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_depth_message(
|
|
10
|
+
depth_map: np.ndarray,
|
|
11
|
+
depth_type: Literal["relative", "metric"],
|
|
12
|
+
depth_limit: float = 0.0,
|
|
13
|
+
) -> dai.ImgFrame:
|
|
14
|
+
"""Create a DepthAI message for a depth map.
|
|
15
|
+
|
|
16
|
+
@param depth_map: A NumPy array representing the depth map with shape HW or NHW/HWN.
|
|
17
|
+
Here N stands for batch dimension.
|
|
18
|
+
@type depth_map: np.array
|
|
19
|
+
@param depth_type: A string indicating the type of depth map. It can either be
|
|
20
|
+
'relative' or 'metric'.
|
|
21
|
+
@type depth_type: Literal['relative', 'metric']
|
|
22
|
+
@param depth_limit: The maximum depth value (in meters) to be used in the depth map.
|
|
23
|
+
The default value is 0, which means no limit.
|
|
24
|
+
@type depth_limit: float
|
|
25
|
+
@return: An ImgFrame object containing the depth information.
|
|
26
|
+
@rtype: dai.ImgFrame
|
|
27
|
+
@raise ValueError: If the depth map is not a NumPy array.
|
|
28
|
+
@raise ValueError: If the depth map is not 2D or 3D.
|
|
29
|
+
@raise ValueError: If the depth map shape is not NHW or HWN.
|
|
30
|
+
@raise ValueError: If the depth type is not 'relative' or 'metric'.
|
|
31
|
+
@raise ValueError: If the depth limit is not 0 and the depth type is 'relative'.
|
|
32
|
+
@raise ValueError: If the depth limit is 0 and the depth type is 'metric'.
|
|
33
|
+
@raise ValueError: If the depth limit is negative.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if not isinstance(depth_map, np.ndarray):
|
|
37
|
+
raise ValueError(f"Expected numpy array, got {type(depth_map)}.")
|
|
38
|
+
|
|
39
|
+
if len(depth_map.shape) == 3:
|
|
40
|
+
if depth_map.shape[0] == 1:
|
|
41
|
+
depth_map = depth_map[0, :, :] # NHW to HW
|
|
42
|
+
elif depth_map.shape[2] == 1:
|
|
43
|
+
depth_map = depth_map[:, :, 0] # HWN to HW
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"Unexpected image shape. Expected NHW or HWN, got {depth_map.shape}."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if len(depth_map.shape) != 2:
|
|
50
|
+
raise ValueError(f"Expected 2D or 3D input, got {len(depth_map.shape)}D input.")
|
|
51
|
+
|
|
52
|
+
if not (depth_type == "relative" or depth_type == "metric"):
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Invalid depth type: {depth_type}. Only 'relative' and 'metric' are supported."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if depth_type == "relative" and depth_limit != 0:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"Invalid depth limit: {depth_limit}. For relative depth, depth limit must be equal to 0."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if depth_type == "metric" and depth_limit == 0:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"Invalid depth limit: {depth_limit}. For metric depth, depth limit must be bigger than 0."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if depth_limit < 0:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"Invalid depth limit: {depth_limit}. Depth limit must be bigger than 0."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
data_type = dai.ImgFrame.Type.RAW16
|
|
73
|
+
|
|
74
|
+
min_val = depth_map.min() if depth_type == "relative" else 0
|
|
75
|
+
max_val = depth_map.max() if depth_type == "relative" else depth_limit
|
|
76
|
+
|
|
77
|
+
# clip values bigger than max_val
|
|
78
|
+
depth_map = np.clip(depth_map, a_min=None, a_max=max_val)
|
|
79
|
+
|
|
80
|
+
# normalize depth map to UINT16 range [0, UINT16_MAX_VALUE]
|
|
81
|
+
if min_val == max_val: # avoid division by zero
|
|
82
|
+
depth_map = np.zeros_like(depth_map)
|
|
83
|
+
else:
|
|
84
|
+
depth_map = (depth_map - min_val) / (max_val - min_val) * UINT16_MAX_VALUE
|
|
85
|
+
depth_map = depth_map.astype(np.uint16)
|
|
86
|
+
|
|
87
|
+
imgFrame = dai.ImgFrame()
|
|
88
|
+
imgFrame.setFrame(depth_map)
|
|
89
|
+
imgFrame.setWidth(depth_map.shape[1])
|
|
90
|
+
imgFrame.setHeight(depth_map.shape[0])
|
|
91
|
+
imgFrame.setType(data_type)
|
|
92
|
+
|
|
93
|
+
return imgFrame
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
from typing import List, Tuple, Union
|
|
2
|
+
|
|
3
|
+
import depthai as dai
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from ...messages import (
|
|
7
|
+
ImgDetectionExtended,
|
|
8
|
+
ImgDetectionsExtended,
|
|
9
|
+
Line,
|
|
10
|
+
Lines,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_detection_message(
|
|
15
|
+
bboxes: np.ndarray,
|
|
16
|
+
scores: np.ndarray,
|
|
17
|
+
labels: List[int] = None,
|
|
18
|
+
keypoints: Union[
|
|
19
|
+
List[Tuple[float, float]], List[Tuple[float, float, float]]
|
|
20
|
+
] = None,
|
|
21
|
+
masks: List[np.ndarray] = None,
|
|
22
|
+
) -> dai.ImgDetections:
|
|
23
|
+
"""Create a DepthAI message for an object detection.
|
|
24
|
+
|
|
25
|
+
@param bbox: Bounding boxes of detected objects of shape (N,4) meaning [...,[x_min, y_min, x_max, y_max],...].
|
|
26
|
+
@type bbox: np.ndarray
|
|
27
|
+
@param scores: Confidence scores of detected objects of shape (N,).
|
|
28
|
+
@type scores: np.ndarray
|
|
29
|
+
@param labels: Labels of detected objects of shape (N,).
|
|
30
|
+
@type labels: List[int]
|
|
31
|
+
@param keypoints: Keypoints of detected objects of shape (N,2) or (N,3).
|
|
32
|
+
@type keypoints: Optional[Union[List[Tuple[float, float]], List[Tuple[float, float, float]]]]
|
|
33
|
+
@param masks: Masks of detected objects of shape (N, H, W).
|
|
34
|
+
@type masks: List[np.ndarray]
|
|
35
|
+
|
|
36
|
+
@return: Message containing the bounding boxes, labels, confidence scores, and keypoints of detected objects.
|
|
37
|
+
@rtype: dai.ImgDetections OR ImgDetectionsExtended
|
|
38
|
+
|
|
39
|
+
@raise ValueError: If the bboxes are not a numpy array.
|
|
40
|
+
@raise ValueError: If the bboxes are not of shape (N,4).
|
|
41
|
+
@raise ValueError: If the bboxes 2nd dimension is not of size 4.
|
|
42
|
+
@raise ValueError: If the bboxes are not in format [x_min, y_min, x_max, y_max] where xmin < xmax and ymin < ymax.
|
|
43
|
+
@raise ValueError: If the scores are not a numpy array.
|
|
44
|
+
@raise ValueError: If the scores are not of shape (N,).
|
|
45
|
+
@raise ValueError: If the scores do not have the same length as bboxes.
|
|
46
|
+
@raise ValueError: If the labels are not a list.
|
|
47
|
+
@raise ValueError: If each label is not an integer.
|
|
48
|
+
@raise ValueError: If the labels do not have the same length as bboxes.
|
|
49
|
+
@raise ValueError: If the keypoints are not a list.
|
|
50
|
+
@raise ValueError: If each keypoint pair is not a tuple of two or three floats.
|
|
51
|
+
@raise ValueError: If the keypoints do not have the same length as bboxes.
|
|
52
|
+
@raise ValueError: If the masks are not a list.
|
|
53
|
+
@raise ValueError: If each mask is not a 2D numpy array.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# checks for bboxes
|
|
57
|
+
if not isinstance(bboxes, np.ndarray):
|
|
58
|
+
raise ValueError(f"Bounding boxes should be numpy array, got {type(bboxes)}.")
|
|
59
|
+
|
|
60
|
+
if len(bboxes) != 0:
|
|
61
|
+
if len(bboxes.shape) != 2:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"Bounding boxes should be of shape (N,4) meaning [...,[x_min, y_min, x_max, y_max],...], got {bboxes.shape}."
|
|
64
|
+
)
|
|
65
|
+
if bboxes.shape[1] != 4:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"Bounding boxes 2nd dimension should be of size 4 e.g. [x_min, y_min, x_max, y_max] got {bboxes.shape[1]}."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
x_valid = bboxes[:, 0] < bboxes[:, 2]
|
|
71
|
+
y_valid = bboxes[:, 1] < bboxes[:, 3]
|
|
72
|
+
if not (np.all(x_valid) and np.all(y_valid)):
|
|
73
|
+
raise ValueError(
|
|
74
|
+
"Bounding boxes should be in format [x_min, y_min, x_max, y_max] where xmin < xmax and ymin < ymax."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# checks for scores
|
|
78
|
+
if not isinstance(scores, np.ndarray):
|
|
79
|
+
raise ValueError(f"Scores should be numpy array, got {type(scores)}.")
|
|
80
|
+
|
|
81
|
+
if len(scores) != 0:
|
|
82
|
+
if len(scores.shape) != 1:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Scores should be of shape (N,) meaning, got {scores.shape}."
|
|
85
|
+
)
|
|
86
|
+
if scores.shape[0] != bboxes.shape[0]:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"Scores should have same length as bboxes, got {scores.shape[0]} and {bboxes.shape[0]}."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# checks for labels
|
|
92
|
+
if labels is not None:
|
|
93
|
+
if not isinstance(labels, List):
|
|
94
|
+
raise ValueError(f"Labels should be list, got {type(labels)}.")
|
|
95
|
+
for label in labels:
|
|
96
|
+
if not isinstance(label, int):
|
|
97
|
+
raise ValueError(
|
|
98
|
+
f"Labels should be list of integers, got {type(label)}."
|
|
99
|
+
)
|
|
100
|
+
if len(labels) != bboxes.shape[0]:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Labels should have same length as bboxes, got {len(labels)} and {bboxes.shape[0]}."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# checks for keypoints
|
|
106
|
+
if keypoints is not None:
|
|
107
|
+
if not isinstance(keypoints, List):
|
|
108
|
+
raise ValueError(f"Keypoints should be list, got {type(keypoints)}.")
|
|
109
|
+
if len(keypoints) != bboxes.shape[0]:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"Keypoints should have same length as bboxes, got {len(keypoints)} and {bboxes.shape[0]}."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
for object_keypoints in keypoints:
|
|
115
|
+
dim = len(object_keypoints[0]) if len(object_keypoints) != 0 else 0
|
|
116
|
+
for point in object_keypoints:
|
|
117
|
+
if not isinstance(point, (Tuple, List)):
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"Keypoint pairs should be list of tuples, got {type(point)}."
|
|
120
|
+
)
|
|
121
|
+
if len(point) not in [2, 3]:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"Keypoint pairs should be list of tuples of length 2 or 3, got {len(point)}."
|
|
124
|
+
)
|
|
125
|
+
if len(point) != dim:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
"All keypoints should be of same dimension e.g. [x, y] or [x, y, z], got mixed inner dimensions."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if masks is not None and len(masks) != 0:
|
|
131
|
+
if not isinstance(masks, List):
|
|
132
|
+
raise ValueError(f"masks should be list, got {type(masks)}.")
|
|
133
|
+
for mask in masks:
|
|
134
|
+
if not isinstance(mask, np.ndarray):
|
|
135
|
+
raise ValueError(f"mask should be numpy array, got {type(mask)}.")
|
|
136
|
+
if len(mask.shape) != 2:
|
|
137
|
+
raise ValueError(f"mask should be a 2D matrix, got {mask.shape}.")
|
|
138
|
+
|
|
139
|
+
if len(masks) != bboxes.shape[0]:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
f"masks should have same length as bboxes, got {len(masks)} and {bboxes.shape[0]}."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if keypoints is not None or masks is not None:
|
|
145
|
+
img_detection = ImgDetectionExtended
|
|
146
|
+
img_detections = ImgDetectionsExtended
|
|
147
|
+
else:
|
|
148
|
+
img_detection = dai.ImgDetection
|
|
149
|
+
img_detections = dai.ImgDetections
|
|
150
|
+
|
|
151
|
+
detections = []
|
|
152
|
+
for i in range(bboxes.shape[0]):
|
|
153
|
+
detection = img_detection()
|
|
154
|
+
detection.xmin = bboxes[i][0]
|
|
155
|
+
detection.ymin = bboxes[i][1]
|
|
156
|
+
detection.xmax = bboxes[i][2]
|
|
157
|
+
detection.ymax = bboxes[i][3]
|
|
158
|
+
detection.confidence = scores[i]
|
|
159
|
+
if labels is None:
|
|
160
|
+
detection.label = 0
|
|
161
|
+
else:
|
|
162
|
+
detection.label = labels[i]
|
|
163
|
+
if keypoints is not None:
|
|
164
|
+
detection.keypoints = keypoints[i]
|
|
165
|
+
if masks is not None:
|
|
166
|
+
detection.mask = masks[i]
|
|
167
|
+
detections.append(detection)
|
|
168
|
+
|
|
169
|
+
detections_msg = img_detections()
|
|
170
|
+
detections_msg.detections = detections
|
|
171
|
+
return detections_msg
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def create_line_detection_message(lines: np.ndarray, scores: np.ndarray):
|
|
175
|
+
"""Create a DepthAI message for a line detection.
|
|
176
|
+
|
|
177
|
+
@param lines: Detected lines of shape (N,4) meaning [...,[x_start, y_start, x_end, y_end],...].
|
|
178
|
+
@type lines: np.ndarray
|
|
179
|
+
@param scores: Confidence scores of detected lines of shape (N,).
|
|
180
|
+
@type scores: np.ndarray
|
|
181
|
+
|
|
182
|
+
@return: Message containing the lines and confidence scores of detected lines.
|
|
183
|
+
@rtype: Lines
|
|
184
|
+
|
|
185
|
+
@raise ValueError: If the lines are not a numpy array.
|
|
186
|
+
@raise ValueError: If the lines are not of shape (N,4).
|
|
187
|
+
@raise ValueError: If the lines 2nd dimension is not of size E{4}.
|
|
188
|
+
@raise ValueError: If the scores are not a numpy array.
|
|
189
|
+
@raise ValueError: If the scores are not of shape (N,).
|
|
190
|
+
@raise ValueError: If the scores do not have the same length as lines.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
# checks for lines
|
|
194
|
+
if not isinstance(lines, np.ndarray):
|
|
195
|
+
raise ValueError(f"Lines should be numpy array, got {type(lines)}.")
|
|
196
|
+
if len(lines) != 0:
|
|
197
|
+
if len(lines.shape) != 2:
|
|
198
|
+
raise ValueError(
|
|
199
|
+
f"Lines should be of shape (N,4) meaning [...,[x_start, y_start, x_end, y_end],...], got {lines.shape}."
|
|
200
|
+
)
|
|
201
|
+
if lines.shape[1] != 4:
|
|
202
|
+
raise ValueError(
|
|
203
|
+
f"Lines 2nd dimension should be of size 4 e.g. [x_start, y_start, x_end, y_end] got {lines.shape[1]}."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# checks for scores
|
|
207
|
+
if not isinstance(scores, np.ndarray):
|
|
208
|
+
raise ValueError(f"Scores should be numpy array, got {type(scores)}.")
|
|
209
|
+
|
|
210
|
+
if len(scores) != 0:
|
|
211
|
+
if len(scores.shape) != 1:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f"Scores should be of shape (N,) meaning, got {scores.shape}."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
for score in scores:
|
|
217
|
+
if not isinstance(score, (float, np.floating)):
|
|
218
|
+
raise ValueError(f"Scores should be of type float, got {type(score)}.")
|
|
219
|
+
|
|
220
|
+
if scores.shape[0] != lines.shape[0]:
|
|
221
|
+
raise ValueError(
|
|
222
|
+
f"Scores should have same length as lines, got {scores.shape[0]} and {lines.shape[0]}."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
line_detections = []
|
|
226
|
+
for i, line in enumerate(lines):
|
|
227
|
+
line_detection = Line()
|
|
228
|
+
line_detection.start_point = dai.Point2f(line[0], line[1])
|
|
229
|
+
line_detection.end_point = dai.Point2f(line[2], line[3])
|
|
230
|
+
line_detection.confidence = float(scores[i])
|
|
231
|
+
line_detections.append(line_detection)
|
|
232
|
+
|
|
233
|
+
lines_msg = Lines()
|
|
234
|
+
lines_msg.lines = line_detections
|
|
235
|
+
return lines_msg
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import depthai as dai
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_image_message(
|
|
7
|
+
image: np.ndarray,
|
|
8
|
+
is_bgr: bool = True,
|
|
9
|
+
) -> dai.ImgFrame:
|
|
10
|
+
"""Create a DepthAI message for an image array.
|
|
11
|
+
|
|
12
|
+
@param image: Image array in HWC or CHW format.
|
|
13
|
+
@type image: np.array
|
|
14
|
+
@param is_bgr: If True, the image is in BGR format. If False, the image is in RGB
|
|
15
|
+
format. Defaults to True.
|
|
16
|
+
@type is_bgr: bool
|
|
17
|
+
@return: dai.ImgFrame object containing the image information.
|
|
18
|
+
@rtype: dai.ImgFrame
|
|
19
|
+
@raise ValueError: If the image shape is not CHW or HWC.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
if image.shape[0] in [1, 3]:
|
|
23
|
+
hwc = False
|
|
24
|
+
elif image.shape[2] in [1, 3]:
|
|
25
|
+
hwc = True
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
f"Unexpected image shape. Expected CHW or HWC, got {image.shape}"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if not hwc:
|
|
32
|
+
image = np.transpose(image, (1, 2, 0))
|
|
33
|
+
|
|
34
|
+
if isinstance(image[0, 0, 0], (float, np.floating)):
|
|
35
|
+
raise ValueError(f"Expected int type, got {type(image[0, 0, 0])}.")
|
|
36
|
+
|
|
37
|
+
if image.shape[2] == 1: # grayscale
|
|
38
|
+
image = image[:, :, 0]
|
|
39
|
+
img_frame_type = dai.ImgFrame.Type.GRAY8 # HW image
|
|
40
|
+
height, width = image.shape
|
|
41
|
+
else:
|
|
42
|
+
if not is_bgr:
|
|
43
|
+
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
|
44
|
+
img_frame_type = dai.ImgFrame.Type.BGR888i # HWC BGR image
|
|
45
|
+
height, width, _ = image.shape
|
|
46
|
+
|
|
47
|
+
imgFrame = dai.ImgFrame()
|
|
48
|
+
imgFrame.setFrame(image)
|
|
49
|
+
imgFrame.setWidth(width)
|
|
50
|
+
imgFrame.setHeight(height)
|
|
51
|
+
imgFrame.setType(img_frame_type)
|
|
52
|
+
|
|
53
|
+
return imgFrame
|