Skip to content

Commit 504edb9

Browse files
authored
Merge pull request #38 from DanielDauner/dev_v0.0.6
Merge `dev_v0.0.6` to main
2 parents a4a464e + a68475e commit 504edb9

File tree

176 files changed

+13347
-3041
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

176 files changed

+13347
-3041
lines changed

.flake8

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@ ignore =
1616
E731
1717
# Local variable name is assigned to but never used
1818
F841
19+
per-file-ignores =
20+
# imported but unused
21+
__init__.py: F401
1922
max-line-length = 120

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,10 @@
2323
*.csv
2424
*.log
2525
*.mp4
26+
27+
28+
# Sphinx documentation
29+
docs/_build/
30+
docs/build/
31+
_build/
32+
.doctrees/

d123/common/datatypes/camera/camera_parameters.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

d123/common/datatypes/detection/detection.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ def shapely_polygon(self) -> shapely.geometry.Polygon:
5757
def center(self) -> StateSE3:
5858
return self.bounding_box_se3.center
5959

60+
@property
61+
def center_se3(self) -> StateSE3:
62+
return self.bounding_box_se3.center_se3
63+
6064
@property
6165
def bounding_box(self) -> BoundingBoxSE3:
6266
return self.bounding_box_se3

d123/common/datatypes/detection/detection_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class DetectionType(SerialIntEnum):
2424
GENERIC_OBJECT = 6 # Animals, debris, pushable/pullable objects, permanent poles.
2525

2626
EGO = 7
27+
SIGN = 8 # TODO: Remove or extent
2728

2829

2930
DYNAMIC_DETECTION_TYPES: set[DetectionType] = {
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from dataclasses import dataclass
5+
from typing import Any, Dict
6+
7+
import numpy as np
8+
import numpy.typing as npt
9+
10+
from d123.common.utils.enums import SerialIntEnum
11+
12+
13+
class CameraType(SerialIntEnum):
14+
"""
15+
Enum for cameras in d123.
16+
"""
17+
18+
CAM_F0 = 0
19+
CAM_B0 = 1
20+
CAM_L0 = 2
21+
CAM_L1 = 3
22+
CAM_L2 = 4
23+
CAM_R0 = 5
24+
CAM_R1 = 6
25+
CAM_R2 = 7
26+
CAM_STEREO_L = 8
27+
CAM_STEREO_R = 9
28+
29+
30+
@dataclass
31+
class CameraMetadata:
32+
33+
camera_type: CameraType
34+
width: int
35+
height: int
36+
intrinsic: npt.NDArray[np.float64] # 3x3 matrix # TODO: don't store matrix but values.
37+
distortion: npt.NDArray[np.float64] # 5x1 vector # TODO: don't store matrix but values.
38+
39+
def to_dict(self) -> Dict[str, Any]:
40+
# TODO: remove None types. Only a placeholder for now.
41+
return {
42+
"camera_type": int(self.camera_type),
43+
"width": self.width,
44+
"height": self.height,
45+
"intrinsic": self.intrinsic.tolist() if self.intrinsic is not None else None,
46+
"distortion": self.distortion.tolist() if self.distortion is not None else None,
47+
}
48+
49+
@classmethod
50+
def from_dict(cls, json_dict: Dict[str, Any]) -> CameraMetadata:
51+
# TODO: remove None types. Only a placeholder for now.
52+
return cls(
53+
camera_type=CameraType(json_dict["camera_type"]),
54+
width=json_dict["width"],
55+
height=json_dict["height"],
56+
intrinsic=np.array(json_dict["intrinsic"]) if json_dict["intrinsic"] is not None else None,
57+
distortion=np.array(json_dict["distortion"]) if json_dict["distortion"] is not None else None,
58+
)
59+
60+
@property
61+
def aspect_ratio(self) -> float:
62+
return self.width / self.height
63+
64+
@property
65+
def fov_x(self) -> float:
66+
"""
67+
Calculates the horizontal field of view (FOV) in radian.
68+
"""
69+
fx = self.intrinsic[0, 0]
70+
fov_x_rad = 2 * np.arctan(self.width / (2 * fx))
71+
return fov_x_rad
72+
73+
@property
74+
def fov_y(self) -> float:
75+
"""
76+
Calculates the vertical field of view (FOV) in radian.
77+
"""
78+
fy = self.intrinsic[1, 1]
79+
fov_y_rad = 2 * np.arctan(self.height / (2 * fy))
80+
return fov_y_rad
81+
82+
83+
def camera_metadata_dict_to_json(camera_metadata: Dict[CameraType, CameraMetadata]) -> Dict[str, Dict[str, Any]]:
84+
"""
85+
Converts a dictionary of CameraMetadata to a JSON-serializable format.
86+
:param camera_metadata: Dictionary of CameraMetadata.
87+
:return: JSON-serializable dictionary.
88+
"""
89+
camera_metadata_dict = {
90+
camera_type.serialize(): metadata.to_dict() for camera_type, metadata in camera_metadata.items()
91+
}
92+
return json.dumps(camera_metadata_dict)
93+
94+
95+
def camera_metadata_dict_from_json(json_dict: Dict[str, Dict[str, Any]]) -> Dict[CameraType, CameraMetadata]:
96+
"""
97+
Converts a JSON-serializable dictionary back to a dictionary of CameraMetadata.
98+
:param json_dict: JSON-serializable dictionary.
99+
:return: Dictionary of CameraMetadata.
100+
"""
101+
camera_metadata_dict = json.loads(json_dict)
102+
return {
103+
CameraType.deserialize(camera_type): CameraMetadata.from_dict(metadata)
104+
for camera_type, metadata in camera_metadata_dict.items()
105+
}
106+
107+
108+
@dataclass
109+
class Camera:
110+
111+
metadata: CameraMetadata
112+
image: npt.NDArray[np.uint8]
113+
extrinsic: npt.NDArray[np.float64] # 4x4 matrix
114+
115+
def get_view_matrix(self) -> np.ndarray:
116+
# Compute the view matrix based on the camera's position and orientation
117+
pass
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from dataclasses import dataclass
5+
from typing import Dict, Optional, Type
6+
7+
import numpy as np
8+
import numpy.typing as npt
9+
10+
from d123.common.datatypes.sensor.lidar_index import LIDAR_INDEX_REGISTRY, LiDARIndex
11+
from d123.common.utils.enums import SerialIntEnum
12+
13+
14+
class LiDARType(SerialIntEnum):
15+
16+
LIDAR_UNKNOWN = 0
17+
LIDAR_MERGED = 1
18+
LIDAR_TOP = 2
19+
LIDAR_FRONT = 3
20+
LIDAR_SIDE_LEFT = 4
21+
LIDAR_SIDE_RIGHT = 5
22+
LIDAR_BACK = 6
23+
24+
25+
@dataclass
26+
class LiDARMetadata:
27+
28+
lidar_type: LiDARType
29+
lidar_index: Type[LiDARIndex]
30+
extrinsic: Optional[npt.NDArray[np.float64]] = None # 4x4 matrix
31+
32+
# TODO: add identifier if point cloud is returned in lidar or ego frame.
33+
34+
def to_dict(self) -> dict:
35+
return {
36+
"lidar_type": self.lidar_type.name,
37+
"lidar_index": self.lidar_index.__name__,
38+
"extrinsic": self.extrinsic.tolist() if self.extrinsic is not None else None,
39+
}
40+
41+
@classmethod
42+
def from_dict(cls, json_dict: dict) -> LiDARMetadata:
43+
lidar_type = LiDARType[json_dict["lidar_type"]]
44+
if json_dict["lidar_index"] not in LIDAR_INDEX_REGISTRY:
45+
raise ValueError(f"Unknown lidar index: {json_dict['lidar_index']}")
46+
lidar_index_class = LIDAR_INDEX_REGISTRY[json_dict["lidar_index"]]
47+
extrinsic = np.array(json_dict["extrinsic"]) if json_dict["extrinsic"] is not None else None
48+
return cls(lidar_type=lidar_type, lidar_index=lidar_index_class, extrinsic=extrinsic)
49+
50+
51+
def lidar_metadata_dict_to_json(lidar_metadata: Dict[LiDARType, LiDARMetadata]) -> str:
52+
"""
53+
Converts a dictionary of LiDARMetadata to a JSON-serializable format.
54+
:param lidar_metadata: Dictionary of LiDARMetadata.
55+
:return: JSON string.
56+
"""
57+
lidar_metadata_dict = {
58+
lidar_type.serialize(): metadata.to_dict() for lidar_type, metadata in lidar_metadata.items()
59+
}
60+
return json.dumps(lidar_metadata_dict)
61+
62+
63+
def lidar_metadata_dict_from_json(json_str: str) -> Dict[LiDARType, LiDARMetadata]:
64+
"""
65+
Converts a JSON string back to a dictionary of LiDARMetadata.
66+
:param json_str: JSON string.
67+
:return: Dictionary of LiDARMetadata.
68+
"""
69+
lidar_metadata_dict = json.loads(json_str)
70+
return {
71+
LiDARType.deserialize(lidar_type): LiDARMetadata.from_dict(metadata)
72+
for lidar_type, metadata in lidar_metadata_dict.items()
73+
}
74+
75+
76+
@dataclass
77+
class LiDAR:
78+
79+
metadata: LiDARMetadata
80+
point_cloud: npt.NDArray[np.float32]
81+
82+
@property
83+
def xyz(self) -> npt.NDArray[np.float32]:
84+
"""
85+
Returns the point cloud as an Nx3 array of x, y, z coordinates.
86+
"""
87+
return self.point_cloud[self.metadata.lidar_index.XYZ].T
88+
89+
@property
90+
def xy(self) -> npt.NDArray[np.float32]:
91+
"""
92+
Returns the point cloud as an Nx2 array of x, y coordinates.
93+
"""
94+
return self.point_cloud[self.metadata.lidar_index.XY].T
95+
96+
@property
97+
def intensity(self) -> Optional[npt.NDArray[np.float32]]:
98+
"""
99+
Returns the intensity values of the LiDAR point cloud if available.
100+
Returns None if intensity is not part of the point cloud.
101+
"""
102+
if hasattr(self.metadata.lidar_index, "INTENSITY"):
103+
return self.point_cloud[self.metadata.lidar_index.INTENSITY]
104+
return None
105+
106+
@property
107+
def range(self) -> Optional[npt.NDArray[np.float32]]:
108+
"""
109+
Returns the range values of the LiDAR point cloud if available.
110+
Returns None if range is not part of the point cloud.
111+
"""
112+
if hasattr(self.metadata.lidar_index, "RANGE"):
113+
return self.point_cloud[self.metadata.lidar_index.RANGE]
114+
return None
115+
116+
@property
117+
def elongation(self) -> Optional[npt.NDArray[np.float32]]:
118+
"""
119+
Returns the elongation values of the LiDAR point cloud if available.
120+
Returns None if elongation is not part of the point cloud.
121+
"""
122+
if hasattr(self.metadata.lidar_index, "ELONGATION"):
123+
return self.point_cloud[self.metadata.lidar_index.ELONGATION]
124+
return None
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from enum import IntEnum
2+
3+
from d123.common.utils.enums import classproperty
4+
5+
LIDAR_INDEX_REGISTRY = {}
6+
7+
8+
def register_lidar_index(enum_class):
9+
LIDAR_INDEX_REGISTRY[enum_class.__name__] = enum_class
10+
return enum_class
11+
12+
13+
class LiDARIndex(IntEnum):
14+
15+
@classproperty
16+
def XY(self) -> slice:
17+
"""
18+
Returns a slice for the XY coordinates of the LiDAR point cloud.
19+
"""
20+
return slice(self.X, self.Y + 1)
21+
22+
@classproperty
23+
def XYZ(self) -> slice:
24+
"""
25+
Returns a slice for the XYZ coordinates of the LiDAR point cloud.
26+
"""
27+
return slice(self.X, self.Z + 1)
28+
29+
30+
@register_lidar_index
31+
class DefaultLidarIndex(LiDARIndex):
32+
X = 0
33+
Y = 1
34+
Z = 2
35+
36+
37+
@register_lidar_index
38+
class NuplanLidarIndex(LiDARIndex):
39+
X = 0
40+
Y = 1
41+
Z = 2
42+
INTENSITY = 3
43+
RING = 4
44+
ID = 5
45+
46+
47+
@register_lidar_index
48+
class CarlaLidarIndex(LiDARIndex):
49+
X = 0
50+
Y = 1
51+
Z = 2
52+
INTENSITY = 3
53+
54+
55+
@register_lidar_index
56+
class WopdLidarIndex(LiDARIndex):
57+
RANGE = 0
58+
INTENSITY = 1
59+
ELONGATION = 2
60+
X = 3
61+
Y = 4
62+
Z = 5

d123/common/datatypes/time/time_point.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,16 @@ def __post_init__(self) -> None:
206206
"""
207207
assert self.time_us >= 0, "Time point has to be positive!"
208208

209+
@classmethod
210+
def from_ns(cls, t_ns: int) -> TimePoint:
211+
"""
212+
Constructs a TimePoint from a value in nanoseconds.
213+
:param t_ns: Time in nanoseconds.
214+
:return: TimePoint.
215+
"""
216+
assert isinstance(t_ns, int), "Nanoseconds must be an integer!"
217+
return TimePoint(time_us=t_ns // 1000)
218+
209219
@classmethod
210220
def from_us(cls, t_us: int) -> TimePoint:
211221
"""

0 commit comments

Comments
 (0)