msight-base 0.1.2__tar.gz
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.
- msight_base-0.1.2/PKG-INFO +10 -0
- msight_base-0.1.2/README.md +32 -0
- msight_base-0.1.2/msight_base/__init__.py +3 -0
- msight_base-0.1.2/msight_base/behavior.py +36 -0
- msight_base-0.1.2/msight_base/detection.py +42 -0
- msight_base-0.1.2/msight_base/map.py +213 -0
- msight_base-0.1.2/msight_base/road_user.py +182 -0
- msight_base-0.1.2/msight_base/trajectory.py +186 -0
- msight_base-0.1.2/msight_base/utils/__init__.py +0 -0
- msight_base-0.1.2/msight_base/utils/cls.py +9 -0
- msight_base-0.1.2/msight_base/utils/data.py +33 -0
- msight_base-0.1.2/msight_base/visualizer/__init__.py +1 -0
- msight_base-0.1.2/msight_base/visualizer/utils.py +118 -0
- msight_base-0.1.2/msight_base/visualizer/visualizer.py +289 -0
- msight_base-0.1.2/msight_base.egg-info/PKG-INFO +10 -0
- msight_base-0.1.2/msight_base.egg-info/SOURCES.txt +19 -0
- msight_base-0.1.2/msight_base.egg-info/dependency_links.txt +1 -0
- msight_base-0.1.2/msight_base.egg-info/requires.txt +4 -0
- msight_base-0.1.2/msight_base.egg-info/top_level.txt +1 -0
- msight_base-0.1.2/pyproject.toml +18 -0
- msight_base-0.1.2/setup.cfg +4 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: msight_base
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Object classes and base trajectory management for the MSight system
|
|
5
|
+
Author-email: Rusheng Zhang <zrs1990@gmail.com>
|
|
6
|
+
Requires-Python: >=3.6
|
|
7
|
+
Requires-Dist: numpy
|
|
8
|
+
Requires-Dist: opencv-python
|
|
9
|
+
Requires-Dist: tqdm
|
|
10
|
+
Requires-Dist: commonroad-io
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 🚗 MSight Base: Base library for MSight System
|
|
2
|
+
|
|
3
|
+
This library serves as the foundational module for the MSight Perception system, providing essential tools and utilities for processing, analyzing, and visualizing road object trajectories and related data. It includes core functionalities such as trajectory modeling, road object representation, behavior analysis, and visualization utilities, enabling seamless integration with higher-level perception and decision-making systems.
|
|
4
|
+
|
|
5
|
+
The library is designed to support autonomous vehicle research and development by offering reusable components that simplify working with spatial and temporal data in real-world traffic scenarios.
|
|
6
|
+
|
|
7
|
+
## 📈 Trajectory Manager
|
|
8
|
+
|
|
9
|
+
The Trajectory Manager is a core component of the MSight Base library, designed to handle the modeling, storage, and manipulation of road object trajectories. It provides a robust framework for working with spatial and temporal data, enabling efficient trajectory analysis and visualization.
|
|
10
|
+
|
|
11
|
+
The Trajectory Manager simplifies the process of managing road object trajectories by offering **Trajectory Modeling** that define and represent object trajectories with fast retrieval of their spatial and temporal attributes, along with advanced querying capabilities for frame-based analysis.
|
|
12
|
+
|
|
13
|
+
## 🎨 Visualization
|
|
14
|
+
|
|
15
|
+
The Visualization module in the MSight Base library provides tools to create graphical representations of road object trajectories and related spatial data. It is designed to help developers and researchers analyze and debug trajectory data effectively using interactive basemap rendering and real-time trajectory visualization.
|
|
16
|
+
|
|
17
|
+
## 🐳 Docker
|
|
18
|
+
|
|
19
|
+
To run MSight Base in a containerized environment:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
docker build -t msight_base .
|
|
23
|
+
docker run -it msight_base
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 🛠️ Developers
|
|
27
|
+
|
|
28
|
+
- Rusheng Zhang (rushengz@umich.edu)
|
|
29
|
+
|
|
30
|
+
## 📧 Contact
|
|
31
|
+
|
|
32
|
+
- Henry Liu (henryliu@umich.edu)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BehaviorType(Enum):
|
|
5
|
+
"""
|
|
6
|
+
Enum for different types of behaviors.
|
|
7
|
+
"""
|
|
8
|
+
UNKNOWN = -1
|
|
9
|
+
CONSTANT = 0
|
|
10
|
+
ACCELERATE = 1
|
|
11
|
+
DECELERATE = 2
|
|
12
|
+
STOP = 3
|
|
13
|
+
LANE_KEEPING = 4
|
|
14
|
+
LANE_CHANGING = 5
|
|
15
|
+
LANE_DEPARTURE = 6
|
|
16
|
+
LEFT_TURN = 7
|
|
17
|
+
RIGHT_TURN = 8
|
|
18
|
+
|
|
19
|
+
# Additional behaviors
|
|
20
|
+
U_TURN = 9
|
|
21
|
+
YIELDING = 10
|
|
22
|
+
FOLLOWING = 11
|
|
23
|
+
OVERTAKING = 12
|
|
24
|
+
PARKING = 13
|
|
25
|
+
EMERGENCY_BRAKING = 14
|
|
26
|
+
|
|
27
|
+
def __str__(self):
|
|
28
|
+
return self.name.lower()
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_name(cls, name: str):
|
|
32
|
+
try:
|
|
33
|
+
return cls[name.upper()]
|
|
34
|
+
except KeyError:
|
|
35
|
+
# Fall back to UNKNOWN
|
|
36
|
+
return cls.UNKNOWN
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
from .utils.cls import get_class_path, import_class_from_path
|
|
3
|
+
|
|
4
|
+
class DetectedObjectBase:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
def to_dict(self):
|
|
9
|
+
raise NotImplementedError("Subclasses should implement this!")
|
|
10
|
+
|
|
11
|
+
class DetectionResultBase:
|
|
12
|
+
def __init__(self, object_list: List[DetectedObjectBase], timestamp: int, sensor_type: Optional[str] = None):
|
|
13
|
+
self.object_list = object_list
|
|
14
|
+
self.timestamp = timestamp
|
|
15
|
+
self.sensor_type = sensor_type
|
|
16
|
+
|
|
17
|
+
def to_dict(self):
|
|
18
|
+
object_data_type = get_class_path(self.object_list[0].__class__) if len(self.object_list) > 0 else get_class_path(DetectedObjectBase)
|
|
19
|
+
return {
|
|
20
|
+
'object_list': [obj.to_dict() for obj in self.object_list],
|
|
21
|
+
'timestamp': self.timestamp,
|
|
22
|
+
'sensor_type': self.sensor_type,
|
|
23
|
+
'object_data_type': object_data_type,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def from_dict(data: dict) -> 'DetectionResultBase':
|
|
28
|
+
object_data_type = data.get("object_data_type")
|
|
29
|
+
if object_data_type is None:
|
|
30
|
+
raise ValueError(f"Missing 'object_data_type' in data: {data}")
|
|
31
|
+
|
|
32
|
+
obj_cls = import_class_from_path(object_data_type)
|
|
33
|
+
if data['object_list'] is not None and len(data['object_list']) > 0:
|
|
34
|
+
object_list = [obj_cls.from_dict(obj_data) for obj_data in data['object_list']]
|
|
35
|
+
else:
|
|
36
|
+
object_list = []
|
|
37
|
+
|
|
38
|
+
return DetectionResultBase(
|
|
39
|
+
object_list=object_list,
|
|
40
|
+
timestamp=data['timestamp'],
|
|
41
|
+
sensor_type=data['sensor_type'],
|
|
42
|
+
)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import List
|
|
3
|
+
from commonroad.scenario.lanelet import LaneletType, LaneletNetwork
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LaneShape(Enum):
|
|
8
|
+
"""
|
|
9
|
+
Enum for different types of lane shape.
|
|
10
|
+
"""
|
|
11
|
+
STRAIGHT = 'straight'
|
|
12
|
+
LEFT_TURN = 'left-turn'
|
|
13
|
+
RIGHT_TURN = 'right-turn'
|
|
14
|
+
U_TURN = 'u-turn'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LaneSide(Enum):
|
|
18
|
+
"""
|
|
19
|
+
Enum for different types of lane side.
|
|
20
|
+
"""
|
|
21
|
+
LEFT = 1
|
|
22
|
+
RIGHT = -1
|
|
23
|
+
ONLANE = 0
|
|
24
|
+
UNKNOWN = 'unknown'
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_name(cls, name: str):
|
|
28
|
+
try:
|
|
29
|
+
return cls[name.upper()]
|
|
30
|
+
except KeyError:
|
|
31
|
+
# Fall back to UNKNOWN
|
|
32
|
+
return cls.UNKNOWN
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MapInfo:
|
|
36
|
+
def __init__(self, lane_id, lane_point_idx, dis_to_lane_center, side: LaneSide, related_lane_id, related_route, nearest_next_lane_point_idx=None):
|
|
37
|
+
self.lane_id = lane_id
|
|
38
|
+
self.lane_point_idx = lane_point_idx
|
|
39
|
+
self.dis_to_lane_center = dis_to_lane_center
|
|
40
|
+
self.side = side
|
|
41
|
+
self.related_lane_id = related_lane_id
|
|
42
|
+
self.related_route = related_route
|
|
43
|
+
self.nearest_next_lane_point_idx = nearest_next_lane_point_idx
|
|
44
|
+
|
|
45
|
+
def __repr__(self):
|
|
46
|
+
return f"MapInfo(lane_id={self.lane_id}, lane_point_idx={self.lane_point_idx})"
|
|
47
|
+
|
|
48
|
+
def from_dict(cls, object_dict):
|
|
49
|
+
return cls(
|
|
50
|
+
lane_id=object_dict.get('lane_id', None),
|
|
51
|
+
lane_point_idx=object_dict.get('lane_point_idx', None),
|
|
52
|
+
dis_to_lane_center=object_dict.get('dis_to_lane_center', None),
|
|
53
|
+
side=LaneSide.from_name(object_dict.get('side', 'unknown')),
|
|
54
|
+
related_lane_id=object_dict.get('related_lane_id', None),
|
|
55
|
+
related_route=object_dict.get('related_route', None),
|
|
56
|
+
nearest_next_lane_point_idx=object_dict.get('nearest_next_lane_point_idx', None)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def to_dict(self):
|
|
60
|
+
return {
|
|
61
|
+
'lane_id': self.lane_id,
|
|
62
|
+
'lane_point_idx': self.lane_point_idx,
|
|
63
|
+
'dis_to_lane_center': self.dis_to_lane_center,
|
|
64
|
+
'side': self.side.value,
|
|
65
|
+
'related_lane_id': self.related_lane_id,
|
|
66
|
+
'related_route': self.related_route,
|
|
67
|
+
'nearest_next_lane_point_idx': self.nearest_next_lane_point_idx
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MapObject:
|
|
72
|
+
def __init__(self, commonroadObj: LaneletNetwork, center_point, map_lanes, lane_heading, lane_shape: List[LaneShape], lane_seg_length, background_img, corner_coords):
|
|
73
|
+
self.center_point = center_point
|
|
74
|
+
self._map_lanes = map_lanes
|
|
75
|
+
self._lane_heading = lane_heading
|
|
76
|
+
self._lane_shape = lane_shape
|
|
77
|
+
self._lane_seg_length = lane_seg_length
|
|
78
|
+
self.background_img = background_img
|
|
79
|
+
self.corner_coords = corner_coords
|
|
80
|
+
|
|
81
|
+
if isinstance(commonroadObj, LaneletNetwork):
|
|
82
|
+
self.map_polylines_ids = [lanelet.lanelet_id for lanelet in commonroadObj.lanelets]
|
|
83
|
+
self._suc_edges = [lanelet.successor for lanelet in commonroadObj.lanelets]
|
|
84
|
+
self._pre_edges = [lanelet.predecessor for lanelet in commonroadObj.lanelets]
|
|
85
|
+
self._left_edges = [lanelet.adj_left for lanelet in commonroadObj.lanelets]
|
|
86
|
+
self._left_edge_directions = [lanelet.adj_left_same_direction for lanelet in commonroadObj.lanelets]
|
|
87
|
+
self._right_edges = [lanelet.adj_right for lanelet in commonroadObj.lanelets]
|
|
88
|
+
self._right_edge_directions = [lanelet.adj_right_same_direction for lanelet in commonroadObj.lanelets]
|
|
89
|
+
self.intersection_lane_id_list = [lanelet.lanelet_id for lanelet in commonroadObj.lanelets if LaneletType.INTERSECTION in lanelet.lanelet_type]
|
|
90
|
+
self.straight_lane_id_list = [lanelet.lanelet_id for lanelet in commonroadObj.lanelets if LaneletType.INTERSECTION not in lanelet.lanelet_type]
|
|
91
|
+
else:
|
|
92
|
+
self.map_polylines_ids = []
|
|
93
|
+
self._suc_edges = []
|
|
94
|
+
self._pre_edges = []
|
|
95
|
+
self._left_edges = []
|
|
96
|
+
self._left_edge_directions = []
|
|
97
|
+
self._right_edges = []
|
|
98
|
+
self._right_edge_directions = []
|
|
99
|
+
self.intersection_lane_id_list = []
|
|
100
|
+
self.straight_lane_id_list = []
|
|
101
|
+
|
|
102
|
+
def suc_edges(self, id=-1):
|
|
103
|
+
if id == -1:
|
|
104
|
+
return self._suc_edges
|
|
105
|
+
else:
|
|
106
|
+
if id not in self.map_polylines_ids:
|
|
107
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
108
|
+
return self._suc_edges[self.map_polylines_ids.index(id)]
|
|
109
|
+
|
|
110
|
+
def pre_edges(self, id=-1):
|
|
111
|
+
if id == -1:
|
|
112
|
+
return self._pre_edges
|
|
113
|
+
else:
|
|
114
|
+
if id not in self.map_polylines_ids:
|
|
115
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
116
|
+
return self._pre_edges[self.map_polylines_ids.index(id)]
|
|
117
|
+
|
|
118
|
+
def left_edges(self, id=-1):
|
|
119
|
+
if id == -1:
|
|
120
|
+
return self._left_edges
|
|
121
|
+
else:
|
|
122
|
+
if id not in self.map_polylines_ids:
|
|
123
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
124
|
+
return self._left_edges[self.map_polylines_ids.index(id)]
|
|
125
|
+
|
|
126
|
+
def right_edges(self, id=-1):
|
|
127
|
+
if id == -1:
|
|
128
|
+
return self._right_edges
|
|
129
|
+
else:
|
|
130
|
+
if id not in self.map_polylines_ids:
|
|
131
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
132
|
+
return self._right_edges[self.map_polylines_ids.index(id)]
|
|
133
|
+
|
|
134
|
+
def left_edge_directions(self, id=-1):
|
|
135
|
+
if id == -1:
|
|
136
|
+
return self._left_edge_directions
|
|
137
|
+
else:
|
|
138
|
+
if id not in self.map_polylines_ids:
|
|
139
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
140
|
+
return self._left_edge_directions[self.map_polylines_ids.index(id)]
|
|
141
|
+
|
|
142
|
+
def right_edge_directions(self, id=-1):
|
|
143
|
+
if id == -1:
|
|
144
|
+
return self._right_edge_directions
|
|
145
|
+
else:
|
|
146
|
+
if id not in self.map_polylines_ids:
|
|
147
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
148
|
+
return self._right_edge_directions[self.map_polylines_ids.index(id)]
|
|
149
|
+
|
|
150
|
+
def lane_shape(self, id=-1):
|
|
151
|
+
if id == -1:
|
|
152
|
+
return self._lane_shape
|
|
153
|
+
else:
|
|
154
|
+
if id not in self.map_polylines_ids:
|
|
155
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
156
|
+
return self._lane_shape[self.map_polylines_ids.index(id)]
|
|
157
|
+
|
|
158
|
+
def map_lanes(self, id=-1, idx=-1):
|
|
159
|
+
if id == -1:
|
|
160
|
+
return self._map_lanes
|
|
161
|
+
else:
|
|
162
|
+
if id not in self.map_polylines_ids:
|
|
163
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
164
|
+
if idx == -1:
|
|
165
|
+
return self._map_lanes[self.map_polylines_ids.index(id)]
|
|
166
|
+
else:
|
|
167
|
+
if idx >= len(self._map_lanes[self.map_polylines_ids.index(id)]):
|
|
168
|
+
raise ValueError(f"Index {idx} out of range for lane with id {id}")
|
|
169
|
+
return self._map_lanes[self.map_polylines_ids.index(id)][idx]
|
|
170
|
+
|
|
171
|
+
def lane_heading(self, id=-1, idx=-1):
|
|
172
|
+
if id == -1:
|
|
173
|
+
return self._lane_heading
|
|
174
|
+
else:
|
|
175
|
+
if id not in self.map_polylines_ids:
|
|
176
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
177
|
+
if idx == -1:
|
|
178
|
+
return self._lane_heading[self.map_polylines_ids.index(id)]
|
|
179
|
+
else:
|
|
180
|
+
if idx >= len(self._lane_heading[self.map_polylines_ids.index(id)]):
|
|
181
|
+
raise ValueError(f"Index {idx} out of range for lane with id {id}")
|
|
182
|
+
return self._lane_heading[self.map_polylines_ids.index(id)][idx]
|
|
183
|
+
|
|
184
|
+
def lane_seg_length(self, id=-1, idx=-1):
|
|
185
|
+
if id == -1:
|
|
186
|
+
return self._lane_seg_length
|
|
187
|
+
else:
|
|
188
|
+
if id not in self.map_polylines_ids:
|
|
189
|
+
raise ValueError(f"Lanelet id {id} not found in map_polylines_ids")
|
|
190
|
+
if idx == -1:
|
|
191
|
+
return self._lane_seg_length[self.map_polylines_ids.index(id)]
|
|
192
|
+
else:
|
|
193
|
+
if idx >= len(self._lane_seg_length[self.map_polylines_ids.index(id)]):
|
|
194
|
+
raise ValueError(f"Index {idx} out of range for lane with id {id}")
|
|
195
|
+
return self._lane_seg_length[self.map_polylines_ids.index(id)][idx]
|
|
196
|
+
|
|
197
|
+
def to_dict(self):
|
|
198
|
+
return {
|
|
199
|
+
'center_point': self.center_point,
|
|
200
|
+
'map_lanes': self._map_lanes,
|
|
201
|
+
'lane_heading': self._lane_heading,
|
|
202
|
+
'map_polylines_ids': self.map_polylines_ids,
|
|
203
|
+
'suc_edges': self._suc_edges,
|
|
204
|
+
'pre_edges': self._pre_edges,
|
|
205
|
+
'left_edges': self._left_edges,
|
|
206
|
+
'left_edge_directions': self._left_edge_directions,
|
|
207
|
+
'right_edges': self._right_edges,
|
|
208
|
+
'right_edge_directions': self._right_edge_directions,
|
|
209
|
+
'lane_shape': self._lane_shape,
|
|
210
|
+
'intersection_lane_id_list': self.intersection_lane_id_list,
|
|
211
|
+
'straight_lane_id_list': self.straight_lane_id_list,
|
|
212
|
+
'corner_coords': self.corner_coords,
|
|
213
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
from msight_base.map import MapInfo
|
|
3
|
+
from msight_base.behavior import BehaviorType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RoadUserPoint:
|
|
7
|
+
def __init__(self,
|
|
8
|
+
x=None,
|
|
9
|
+
y=None,
|
|
10
|
+
speed=None,
|
|
11
|
+
acceleration=None,
|
|
12
|
+
heading=None,
|
|
13
|
+
width=None,
|
|
14
|
+
length=None,
|
|
15
|
+
height=None,
|
|
16
|
+
poly_box=None,
|
|
17
|
+
category=None,
|
|
18
|
+
confidence=None,
|
|
19
|
+
turning_signal=None,
|
|
20
|
+
map_info=None,
|
|
21
|
+
timestamp=None,
|
|
22
|
+
frame_step=None,
|
|
23
|
+
traj_id=None,
|
|
24
|
+
sensor_data={},
|
|
25
|
+
behaviors=[],
|
|
26
|
+
conf_int_2sigma=None,
|
|
27
|
+
conf_int_vel_2sigma=None,
|
|
28
|
+
heading_confidence=None,
|
|
29
|
+
yaw_rate=None):
|
|
30
|
+
self._timestamp = timestamp
|
|
31
|
+
self._frame_step = frame_step
|
|
32
|
+
self.x = x
|
|
33
|
+
self.y = y
|
|
34
|
+
self.speed = speed
|
|
35
|
+
self.acceleration = acceleration
|
|
36
|
+
self.heading = heading
|
|
37
|
+
self.width = width
|
|
38
|
+
self.length = length
|
|
39
|
+
self.height = height
|
|
40
|
+
self.poly_box = poly_box
|
|
41
|
+
self.category = category
|
|
42
|
+
self.traj = None
|
|
43
|
+
self.frame = None
|
|
44
|
+
self.prev = None
|
|
45
|
+
self.next = None
|
|
46
|
+
self.sensor_data = sensor_data # sensor id to sensor data map
|
|
47
|
+
self.confidence = confidence
|
|
48
|
+
self.turning_signal = turning_signal
|
|
49
|
+
self.map_info = map_info
|
|
50
|
+
self.behaviors = behaviors
|
|
51
|
+
self.pred_trajectory = None
|
|
52
|
+
self.conf_int_2sigma = conf_int_2sigma
|
|
53
|
+
self.conf_int_vel_2sigma = conf_int_vel_2sigma
|
|
54
|
+
self.heading_confidence = heading_confidence
|
|
55
|
+
self.yaw_rate = yaw_rate
|
|
56
|
+
|
|
57
|
+
## this is used to store the id when the object's trajectory is not yet created or assigned
|
|
58
|
+
self._traj_id = traj_id
|
|
59
|
+
|
|
60
|
+
## this is also used to store the id when the object's trajectory is not yet created or assigned
|
|
61
|
+
## for some use cases, we need a uuid instead of a trajectory id (that increments from 0)
|
|
62
|
+
self._traj_uuid = None
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def traj_id(self):
|
|
66
|
+
if self.traj is not None:
|
|
67
|
+
return self.traj.id
|
|
68
|
+
return self._traj_id
|
|
69
|
+
|
|
70
|
+
@traj_id.setter
|
|
71
|
+
def traj_id(self, value):
|
|
72
|
+
if self.traj is not None:
|
|
73
|
+
raise AttributeError("Cannot set traj_id directly when traj is assigned.")
|
|
74
|
+
self._traj_id = value
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def frame_step(self):
|
|
78
|
+
if self.frame is None:
|
|
79
|
+
return None
|
|
80
|
+
return self.frame.step
|
|
81
|
+
|
|
82
|
+
@frame_step.setter
|
|
83
|
+
def frame_step(self, value):
|
|
84
|
+
if self.frame is not None:
|
|
85
|
+
raise AttributeError("Cannot set frame_step directly when frame is assigned.")
|
|
86
|
+
self._frame_step = value
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def timestamp(self):
|
|
90
|
+
if self.frame is None:
|
|
91
|
+
return self._timestamp
|
|
92
|
+
return self.frame.timestamp
|
|
93
|
+
|
|
94
|
+
@timestamp.setter
|
|
95
|
+
def timestamp(self, value):
|
|
96
|
+
if self.frame is not None:
|
|
97
|
+
raise AttributeError("Cannot set timestamp directly when frame is assigned.")
|
|
98
|
+
self._timestamp = value
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def from_dict(object_dict):
|
|
102
|
+
return RoadUserPoint(
|
|
103
|
+
timestamp=object_dict.get('timestamp', None),
|
|
104
|
+
frame_step=object_dict.get('frame_step', None),
|
|
105
|
+
traj_id=object_dict.get('traj_id', None),
|
|
106
|
+
x=object_dict.get('x', None),
|
|
107
|
+
y=object_dict.get('y', None),
|
|
108
|
+
speed=object_dict.get('speed', None),
|
|
109
|
+
acceleration=object_dict.get('acceleration', None),
|
|
110
|
+
heading=object_dict.get('heading', None),
|
|
111
|
+
width=object_dict.get('width', None),
|
|
112
|
+
length=object_dict.get('length', None),
|
|
113
|
+
height=object_dict.get('height', None),
|
|
114
|
+
poly_box=object_dict.get('poly_box', None),
|
|
115
|
+
category=RoadUserCategory(int(object_dict['category'])) if object_dict.get('category') is not None else None,
|
|
116
|
+
confidence=object_dict.get('confidence', None),
|
|
117
|
+
turning_signal=object_dict.get('turning_signal', None),
|
|
118
|
+
map_info=MapInfo.from_dict(object_dict['map_info']) if object_dict.get('map_info') else None,
|
|
119
|
+
sensor_data=object_dict.get('sensor_data', {}),
|
|
120
|
+
behaviors=[BehaviorType[behavior] for behavior in object_dict.get('behaviors', [])],
|
|
121
|
+
conf_int_2sigma=object_dict.get('conf_int_2sigma', None),
|
|
122
|
+
conf_int_vel_2sigma=object_dict.get('conf_int_vel_2sigma', None),
|
|
123
|
+
heading_confidence=object_dict.get('heading_confidence', None),
|
|
124
|
+
yaw_rate=object_dict.get('yaw_rate', None)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def to_dict(self):
|
|
128
|
+
return {
|
|
129
|
+
'traj_id': self.traj_id,
|
|
130
|
+
'frame_step': self.frame_step,
|
|
131
|
+
'timestamp': self.timestamp,
|
|
132
|
+
'x': self.x,
|
|
133
|
+
'y': self.y,
|
|
134
|
+
'speed': self.speed,
|
|
135
|
+
'acceleration': self.acceleration,
|
|
136
|
+
'heading': self.heading,
|
|
137
|
+
'width': self.width,
|
|
138
|
+
'length': self.length,
|
|
139
|
+
'height': self.height,
|
|
140
|
+
'category': RoadUserCategory(int(self.category)) if self.category is not None else None,
|
|
141
|
+
'confidence': self.confidence,
|
|
142
|
+
'turning_signal': self.turning_signal,
|
|
143
|
+
'poly_box': self.poly_box,
|
|
144
|
+
'map_info': self.map_info.to_dict() if self.map_info else None,
|
|
145
|
+
'behaviors': [str(behavior) for behavior in self.behaviors],
|
|
146
|
+
'sensor_data': self.sensor_data,
|
|
147
|
+
'conf_int_2sigma': self.conf_int_2sigma,
|
|
148
|
+
'conf_int_vel_2sigma': self.conf_int_vel_2sigma,
|
|
149
|
+
'heading_confidence': self.heading_confidence,
|
|
150
|
+
'yaw_rate': self.yaw_rate,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
def __repr__(self):
|
|
154
|
+
return f"RoadUserPoint(x={self.x}, y={self.y}, heading={self.heading}, width={self.width}, length={self.length}, category={self.category})"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class RoadUserCategory(IntEnum):
|
|
158
|
+
"""
|
|
159
|
+
Enum for road user categories.
|
|
160
|
+
"""
|
|
161
|
+
UNKNOWN = -1
|
|
162
|
+
SEDAN = 0
|
|
163
|
+
SUV = 1
|
|
164
|
+
TRUCK = 2
|
|
165
|
+
BUS = 3
|
|
166
|
+
PEDESTRIAN = 4
|
|
167
|
+
BICYCLE = 5
|
|
168
|
+
MOTORCYCLE = 6
|
|
169
|
+
TRAILER = 7
|
|
170
|
+
VAN = 8
|
|
171
|
+
PICKUP = 9
|
|
172
|
+
|
|
173
|
+
def __str__(self):
|
|
174
|
+
return self.name.lower()
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def from_name(cls, name: str):
|
|
178
|
+
try:
|
|
179
|
+
return cls[name.upper()]
|
|
180
|
+
except KeyError:
|
|
181
|
+
# Fall back to UNKNOWN
|
|
182
|
+
return cls.UNKNOWN
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import bisect
|
|
2
|
+
from .road_user import RoadUserPoint
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
class Container:
|
|
6
|
+
def __init__(self, id=None, objects = None):
|
|
7
|
+
self.id = id
|
|
8
|
+
if objects is None:
|
|
9
|
+
objects = []
|
|
10
|
+
self.objects = objects
|
|
11
|
+
|
|
12
|
+
def __len__(self):
|
|
13
|
+
return len(self.objects)
|
|
14
|
+
|
|
15
|
+
def __iter__(self):
|
|
16
|
+
return iter(self.objects)
|
|
17
|
+
|
|
18
|
+
def __getitem__(self, item):
|
|
19
|
+
return self.objects[item]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Trajectory(Container):
|
|
23
|
+
def __init__(self, id=None):
|
|
24
|
+
self.steps = []
|
|
25
|
+
self.step_to_object_map = {}
|
|
26
|
+
super().__init__(id=id, objects=[])
|
|
27
|
+
|
|
28
|
+
def add_object(self, obj, step, insort=False):
|
|
29
|
+
if insort:
|
|
30
|
+
index = bisect.bisect_left(self.steps, step)
|
|
31
|
+
if index < len(self.steps) and self.steps[index] == step:
|
|
32
|
+
raise ValueError(f"Step {step} already exists")
|
|
33
|
+
self.steps.insert(index, step)
|
|
34
|
+
self.objects.insert(index, obj)
|
|
35
|
+
self.step_to_object_map[step] = obj
|
|
36
|
+
if index > 0:
|
|
37
|
+
self.objects[index - 1].next = obj
|
|
38
|
+
obj.prev = self.objects[index - 1]
|
|
39
|
+
if index < len(self.objects) - 1:
|
|
40
|
+
obj.next = self.objects[index + 1]
|
|
41
|
+
self.objects[index + 1].prev = obj
|
|
42
|
+
else:
|
|
43
|
+
if len(self.steps) > 0 and step < self.steps[-1]:
|
|
44
|
+
raise ValueError(f"Step {step} is less than the last step {self.steps[-1]}, if you want to insert in between use insort=True")
|
|
45
|
+
self.objects.append(obj)
|
|
46
|
+
self.step_to_object_map[step] = obj
|
|
47
|
+
self.steps.append(step)
|
|
48
|
+
if len(self.objects) > 1:
|
|
49
|
+
self.objects[-2].next = obj
|
|
50
|
+
obj.prev = self.objects[-2]
|
|
51
|
+
obj.traj = self
|
|
52
|
+
|
|
53
|
+
def get_object_at_step(self, step):
|
|
54
|
+
return self.step_to_object_map.get(step, None)
|
|
55
|
+
|
|
56
|
+
def remove_object(self, step):
|
|
57
|
+
if step not in self.steps:
|
|
58
|
+
return
|
|
59
|
+
# raise ValueError(f"Step {step} does not exist in the trajectory")
|
|
60
|
+
obj = self.get_object_at_step(step)
|
|
61
|
+
self.objects.remove(obj)
|
|
62
|
+
self.steps.remove(step)
|
|
63
|
+
del self.step_to_object_map[step]
|
|
64
|
+
obj.traj = None
|
|
65
|
+
next_obj = obj.next
|
|
66
|
+
prev_obj = obj.prev
|
|
67
|
+
if next_obj is not None:
|
|
68
|
+
next_obj.prev = prev_obj
|
|
69
|
+
if prev_obj is not None:
|
|
70
|
+
prev_obj.next = next_obj
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Frame(Container):
|
|
74
|
+
def __init__(self, step, timestamp=None):
|
|
75
|
+
self.step = step
|
|
76
|
+
self.timestamp = timestamp
|
|
77
|
+
self.traj_ids = set()
|
|
78
|
+
self.traj_id_to_obj_map = {}
|
|
79
|
+
super().__init__(None, objects=[])
|
|
80
|
+
|
|
81
|
+
def add_object(self, obj):
|
|
82
|
+
if obj.traj_id in self.traj_ids:
|
|
83
|
+
raise ValueError(f"Object with id {obj.traj_id} already exists in the frame")
|
|
84
|
+
self.objects.append(obj)
|
|
85
|
+
self.traj_id_to_obj_map[obj.traj_id] = obj
|
|
86
|
+
self.traj_ids.add(obj.traj_id)
|
|
87
|
+
obj.frame = self
|
|
88
|
+
|
|
89
|
+
def remove_object(self, obj):
|
|
90
|
+
if obj.traj_id not in self.traj_ids:
|
|
91
|
+
raise ValueError(f"Object with id {obj.traj_id} does not exist in the frame")
|
|
92
|
+
self.objects.remove(obj)
|
|
93
|
+
del self.traj_id_to_obj_map[obj.traj_id]
|
|
94
|
+
self.traj_ids.remove(obj.traj_id)
|
|
95
|
+
obj.frame = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TrajectoryManager:
|
|
99
|
+
def __init__(self, max_frames=None):
|
|
100
|
+
self.trajectories = []
|
|
101
|
+
self.traj_ids = set()
|
|
102
|
+
self.traj_id_to_traj_map = {}
|
|
103
|
+
self.frames = []
|
|
104
|
+
self.steps = []
|
|
105
|
+
self.timestamps = []
|
|
106
|
+
self.step_to_frame_map = {}
|
|
107
|
+
self.timestamp_to_frame_map = {}
|
|
108
|
+
self.max_frames = max_frames
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def last_step(self):
|
|
112
|
+
return self.steps[-1] if len(self.steps)>0 else -1
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def earliest_step(self):
|
|
116
|
+
return self.steps[0] if len(self.steps)>0 else -1
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def last_frame(self):
|
|
120
|
+
return self.frames[-1]
|
|
121
|
+
|
|
122
|
+
def delete_earliest_frame(self):
|
|
123
|
+
if not self.frames:
|
|
124
|
+
raise ValueError("No frames to delete")
|
|
125
|
+
step = self.frames[0].step
|
|
126
|
+
for obj in self.frames[0].objects:
|
|
127
|
+
# print(obj.traj, obj.traj_id)
|
|
128
|
+
if obj.traj is not None:
|
|
129
|
+
obj.traj.remove_object(step)
|
|
130
|
+
if self.frames[0].timestamp is not None:
|
|
131
|
+
del self.timestamp_to_frame_map[self.frames[0].timestamp]
|
|
132
|
+
self.timestamps.remove(self.frames[0].timestamp)
|
|
133
|
+
del self.step_to_frame_map[step]
|
|
134
|
+
self.steps.remove(step)
|
|
135
|
+
|
|
136
|
+
for traj in self.trajectories:
|
|
137
|
+
if len(traj.objects) == 0:
|
|
138
|
+
self.remove_traj(traj)
|
|
139
|
+
self.frames.pop(0)
|
|
140
|
+
|
|
141
|
+
def add_object(self, obj, traj_id, step, timestamp=None, insort=False):
|
|
142
|
+
if traj_id not in self.traj_ids:
|
|
143
|
+
traj = Trajectory(traj_id)
|
|
144
|
+
self.trajectories.append(traj)
|
|
145
|
+
self.traj_ids.add(traj_id)
|
|
146
|
+
self.traj_id_to_traj_map[traj_id] = traj
|
|
147
|
+
else:
|
|
148
|
+
traj = self.traj_id_to_traj_map[traj_id]
|
|
149
|
+
|
|
150
|
+
if step >= self.last_step + 1:
|
|
151
|
+
frame = Frame(step, timestamp)
|
|
152
|
+
self.frames.append(frame)
|
|
153
|
+
self.steps.append(step)
|
|
154
|
+
self.step_to_frame_map[step] = frame
|
|
155
|
+
if timestamp is not None:
|
|
156
|
+
self.timestamps.append(timestamp)
|
|
157
|
+
self.timestamp_to_frame_map[timestamp] = frame
|
|
158
|
+
|
|
159
|
+
elif step <= self.last_step and step >= self.earliest_step:
|
|
160
|
+
frame = self.step_to_frame_map[step]
|
|
161
|
+
else:
|
|
162
|
+
raise ValueError(f"Step {step} is not a valid step, valid steps are from 0 to {self.last_step+1}")
|
|
163
|
+
|
|
164
|
+
traj.add_object(obj, step, insort=insort)
|
|
165
|
+
frame.add_object(obj)
|
|
166
|
+
while self.max_frames is not None and len(self.frames) > self.max_frames:
|
|
167
|
+
# print(len(self.frames), self.max_frames)
|
|
168
|
+
self.delete_earliest_frame()
|
|
169
|
+
|
|
170
|
+
def add_list_as_new_frame(self, object_list: List[RoadUserPoint], timestamp=None):
|
|
171
|
+
# use this method to push a list of objects as the last frame, this is very useful when you have a list of objects that are already tracked with id assigned
|
|
172
|
+
step = self.last_step + 1
|
|
173
|
+
for obj in object_list:
|
|
174
|
+
if obj.traj_id is None:
|
|
175
|
+
raise ValueError("Object must have a traj_id to be added to a trajectory manager")
|
|
176
|
+
self.add_object(obj, obj.traj_id, step, timestamp=obj.timestamp)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def remove_traj(self, traj: Trajectory):
|
|
180
|
+
# print(f"Removing trajectory with id {traj.id}")
|
|
181
|
+
tid = traj.id
|
|
182
|
+
self.trajectories.remove(traj)
|
|
183
|
+
self.traj_ids.remove(tid)
|
|
184
|
+
del self.traj_id_to_traj_map[tid]
|
|
185
|
+
for obj in traj:
|
|
186
|
+
obj.frame.remove_object(obj)
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
def get_class_path(cls):
|
|
2
|
+
module = cls.__module__
|
|
3
|
+
class_name = cls.__name__
|
|
4
|
+
return f"{module}.{class_name}"
|
|
5
|
+
|
|
6
|
+
def import_class_from_path(path: str):
|
|
7
|
+
module_path, class_name = path.rsplit('.', 1)
|
|
8
|
+
module = __import__(module_path, fromlist=[class_name])
|
|
9
|
+
return getattr(module, class_name)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from msight_base import TrajectoryManager, RoadUserPoint
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_timestamp_from_filename(file_name: str) -> datetime:
|
|
9
|
+
# file name is like "2023-09-05 10-00-00-039784.json"
|
|
10
|
+
stem = file_name.split(".")[0]
|
|
11
|
+
return datetime.strptime(stem, "%Y-%m-%d %H-%M-%S-%f")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def read_msight_json_data(file_path: Path) -> TrajectoryManager:
|
|
15
|
+
tm = TrajectoryManager()
|
|
16
|
+
step = 0
|
|
17
|
+
for frame_file in tqdm(list(file_path.iterdir())):
|
|
18
|
+
if frame_file.suffix != ".json":
|
|
19
|
+
continue
|
|
20
|
+
timestamp = get_timestamp_from_filename(frame_file.stem)
|
|
21
|
+
with open(frame_file, "r") as f:
|
|
22
|
+
frame_data = json.load(f)
|
|
23
|
+
for obj_data in frame_data['fusion']:
|
|
24
|
+
obj = RoadUserPoint(
|
|
25
|
+
obj_data['lat'],
|
|
26
|
+
obj_data['lon'],
|
|
27
|
+
heading=obj_data['heading'],
|
|
28
|
+
width=obj_data['width'],
|
|
29
|
+
length=obj_data['length'],
|
|
30
|
+
)
|
|
31
|
+
tm.add_object(obj, obj_data['id'], step, timestamp=timestamp)
|
|
32
|
+
step += 1
|
|
33
|
+
return tm
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .visualizer import Visualizer
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import cv2
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class VideoWriter(object):
|
|
7
|
+
|
|
8
|
+
def __init__(self, name, out_size_w=960, out_size_h=720):
|
|
9
|
+
|
|
10
|
+
self.video_writer = cv2.VideoWriter(
|
|
11
|
+
name + '.mp4', cv2.VideoWriter_fourcc(*'MP4V'),
|
|
12
|
+
10.0, (out_size_w, out_size_h))
|
|
13
|
+
self.video_writer_cat = cv2.VideoWriter(
|
|
14
|
+
name + '_cat.mp4', cv2.VideoWriter_fourcc(*'MP4V'),
|
|
15
|
+
10.0, (out_size_h + out_size_w, out_size_h))
|
|
16
|
+
|
|
17
|
+
self.out_size_w = out_size_w
|
|
18
|
+
self.out_size_h = out_size_h
|
|
19
|
+
|
|
20
|
+
def save_frame(self, vis_det, vis_loc):
|
|
21
|
+
|
|
22
|
+
vis_det = cv2.resize(vis_det, (self.out_size_w, self.out_size_h))
|
|
23
|
+
vis_loc = cv2.resize(vis_loc, (self.out_size_h, self.out_size_h))
|
|
24
|
+
|
|
25
|
+
self.video_writer.write(vis_det[:, :, ::-1])
|
|
26
|
+
|
|
27
|
+
frame_cat = np.concatenate([vis_det, vis_loc], axis=1)
|
|
28
|
+
self.video_writer_cat.write(frame_cat[:, :, ::-1])
|
|
29
|
+
|
|
30
|
+
cv2.namedWindow('frame_cat', cv2.WINDOW_NORMAL)
|
|
31
|
+
cv2.imshow('frame_cat', frame_cat[:, :, ::-1])
|
|
32
|
+
cv2.waitKey(1)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
############ some helper functions ##############
|
|
36
|
+
|
|
37
|
+
class Struct:
|
|
38
|
+
def __init__(self, **entries):
|
|
39
|
+
self.__dict__.update(entries)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def parse_config(path_to_json=r'./config.json'):
|
|
43
|
+
with open(path_to_json) as f:
|
|
44
|
+
data = json.load(f)
|
|
45
|
+
return Struct(**data)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
################ BB draw on 2D image... ####################
|
|
50
|
+
|
|
51
|
+
def draw_bb_on_image(vehicle_list, img):
|
|
52
|
+
|
|
53
|
+
for i in range(len(vehicle_list)):
|
|
54
|
+
v = vehicle_list[i]
|
|
55
|
+
x_bottom_c, y_bottom_c = v.pixel_bottom_center.x, v.pixel_bottom_center.y
|
|
56
|
+
diagonal_length = v.diagonal_length_pixel
|
|
57
|
+
|
|
58
|
+
# draw box
|
|
59
|
+
pt1 = (int(x_bottom_c - diagonal_length / 2), int(y_bottom_c - diagonal_length / 2))
|
|
60
|
+
pt2 = (int(x_bottom_c + diagonal_length / 2), int(y_bottom_c + diagonal_length / 2))
|
|
61
|
+
cv2.rectangle(img, pt1=pt1, pt2=pt2, color=(255, 0, 0), thickness=1, lineType=cv2.LINE_AA)
|
|
62
|
+
box_tmp = img[pt1[1]:pt2[1], pt1[0]:pt2[0], :]
|
|
63
|
+
img[pt1[1]:pt2[1], pt1[0]:pt2[0], 1:] = (0.75 * box_tmp[:, :, 1:]).astype(np.uint8)
|
|
64
|
+
|
|
65
|
+
# draw bottom center
|
|
66
|
+
cv2.circle(img, (x_bottom_c, y_bottom_c), 2, (255, 0, 0), -1)
|
|
67
|
+
# text = 'id=%s' % v.id
|
|
68
|
+
# pt = (x_bottom_c, y_bottom_c - 5)
|
|
69
|
+
# cv2.putText(img, text=text, org=pt, fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
70
|
+
# fontScale=0.5, color=(255, 255, 0), thickness=1, lineType=cv2.LINE_AA)
|
|
71
|
+
|
|
72
|
+
return img
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
################ BB (YOLOX) draw on 2D image... ####################
|
|
76
|
+
# Instead of draw the square, we directly draw the rect predicted by YOLOX
|
|
77
|
+
|
|
78
|
+
def draw_bb_on_image_yolox(vehicle_list, img):
|
|
79
|
+
|
|
80
|
+
for i in range(len(vehicle_list)):
|
|
81
|
+
v = vehicle_list[i]
|
|
82
|
+
x_bottom_c, y_bottom_c = v.pixel_bottom_center.x, v.pixel_bottom_center.y
|
|
83
|
+
x1, y1, x2, y2 = v.pixel_bbox
|
|
84
|
+
|
|
85
|
+
# draw box
|
|
86
|
+
pt1 = (int(x1), int(y1))
|
|
87
|
+
pt2 = (int(x2), int(y2))
|
|
88
|
+
cv2.rectangle(img, pt1=pt1, pt2=pt2, color=(255, 0, 0), thickness=1, lineType=cv2.LINE_AA)
|
|
89
|
+
box_tmp = img[pt1[1]:pt2[1], pt1[0]:pt2[0], :]
|
|
90
|
+
img[pt1[1]:pt2[1], pt1[0]:pt2[0], 1:] = (0.75 * box_tmp[:, :, 1:]).astype(np.uint8)
|
|
91
|
+
|
|
92
|
+
# draw bottom center
|
|
93
|
+
cv2.circle(img, (x_bottom_c, y_bottom_c), 2, (255, 0, 0), -1)
|
|
94
|
+
# text = 'id=%s' % v.id
|
|
95
|
+
# pt = (x_bottom_c, y_bottom_c - 5)
|
|
96
|
+
# cv2.putText(img, text=text, org=pt, fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
97
|
+
# fontScale=0.5, color=(255, 255, 0), thickness=1, lineType=cv2.LINE_AA)
|
|
98
|
+
|
|
99
|
+
return img
|
|
100
|
+
|
|
101
|
+
# map center lat/lon
|
|
102
|
+
# use the following for Ann Arbor Ellsworth Roundabout
|
|
103
|
+
LAT0, LON0 = 42.229392, -83.739012
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def coord_normalization(lat, lon, center_lat=LAT0, center_lon=LON0):
|
|
107
|
+
"from lat/lon to local coordinate with unit meter"
|
|
108
|
+
lat_norm = (lat - center_lat) * 111000.
|
|
109
|
+
lon_norm = (lon - center_lon) * 111000. * np.cos(center_lat/180.*np.pi)
|
|
110
|
+
return lat_norm, lon_norm
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def coord_unnormalization(lat_norm, lon_norm, center_lat=LAT0, center_lon=LON0):
|
|
114
|
+
"from local coordinate with unit meter to lat/lon"
|
|
115
|
+
lat = lat_norm / 111000. + center_lat
|
|
116
|
+
lon = lon_norm / 111000. / np.cos(center_lat/180.*np.pi) + center_lon
|
|
117
|
+
return lat, lon
|
|
118
|
+
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import cv2
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
import math
|
|
6
|
+
from msight_base import TrajectoryManager
|
|
7
|
+
from .utils import coord_normalization, coord_unnormalization
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Struct:
|
|
11
|
+
def __init__(self, **entries):
|
|
12
|
+
self.__dict__.update(entries)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_config(path_to_json=r'./config.json'):
|
|
16
|
+
with open(path_to_json) as f:
|
|
17
|
+
data = json.load(f)
|
|
18
|
+
return Struct(**data)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Visualizer:
|
|
22
|
+
"""
|
|
23
|
+
Basemap for detection visualization. Show and map detection to base map layer.
|
|
24
|
+
Pixel image: vehicle bounding box, vehicle id
|
|
25
|
+
Map layer: location, heading, rect box, trajectory, predicted future...
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, map_image_path, map_width=1024, map_height=1024, RGB=False):
|
|
29
|
+
self.h, self.w = map_height, map_width
|
|
30
|
+
|
|
31
|
+
self.f = parse_config(os.path.splitext(map_image_path)[0] + '.json')
|
|
32
|
+
self.transform_wd2px, self.transform_px2wd = self._create_coord_mapper()
|
|
33
|
+
|
|
34
|
+
basemap = cv2.imread(map_image_path, cv2.IMREAD_COLOR)
|
|
35
|
+
if not RGB:
|
|
36
|
+
basemap = cv2.cvtColor(basemap, cv2.COLOR_BGR2RGB)
|
|
37
|
+
self.basemap = cv2.resize(basemap, (map_width, map_height))
|
|
38
|
+
self.basemap = (self.basemap.astype(np.float64) * 0.3).astype(np.uint8)
|
|
39
|
+
|
|
40
|
+
np.random.seed(0)
|
|
41
|
+
self.color_table = np.random.randint(80, 255, (10, 3))
|
|
42
|
+
np.random.seed()
|
|
43
|
+
|
|
44
|
+
self.traj_manager = TrajectoryManager(max_frames=100)
|
|
45
|
+
self.traj_layer = np.zeros([self.h, self.w, 3], dtype=np.uint8)
|
|
46
|
+
self.traj_alpha = np.zeros([self.h, self.w, 3], dtype=np.float32)
|
|
47
|
+
|
|
48
|
+
if os.path.exists(r'./vehicle_category.json'):
|
|
49
|
+
self.label_list = parse_config(r'./vehicle_category.json')
|
|
50
|
+
else:
|
|
51
|
+
self.label_list = None
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _draw_vehicle_as_point(vis, ptc, color):
|
|
55
|
+
# draw circle boundary
|
|
56
|
+
cv2.circle(vis, tuple(ptc), 10, color, -1)
|
|
57
|
+
# draw center location
|
|
58
|
+
cv2.circle(vis, tuple(ptc), 4, (255, 255, 0), -1)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def _draw_vehicle_as_box(vis, pts, color):
|
|
62
|
+
# fill rectangle
|
|
63
|
+
cv2.fillPoly(vis, [np.array(pts)], color, lineType=cv2.LINE_AA)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _draw_vehicle_heading_as_arrow(vis, ptc, heading, color):
|
|
67
|
+
# draw heading
|
|
68
|
+
# convert degree to radian
|
|
69
|
+
heading = math.radians(heading-90)
|
|
70
|
+
|
|
71
|
+
line_length = 25
|
|
72
|
+
pt1 = (int(ptc[0]), int(ptc[1]))
|
|
73
|
+
pt2 = (int(ptc[0] + line_length*np.cos(heading)),
|
|
74
|
+
int(ptc[1] + line_length*np.sin(heading)))
|
|
75
|
+
cv2.arrowedLine(vis, pt1=pt1, pt2=pt2, color=color,
|
|
76
|
+
thickness=3, line_type=cv2.LINE_AA)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _draw_predicted_future(vis, pts, color):
|
|
80
|
+
# draw mean trajectory
|
|
81
|
+
for i in range(len(pts)-1):
|
|
82
|
+
pt1 = (int(pts[i][0]), int(pts[i][1]))
|
|
83
|
+
pt2 = (int(pts[i+1][0]), int(pts[i+1][1]))
|
|
84
|
+
cv2.line(vis, pt1=pt1, pt2=pt2, color=color,
|
|
85
|
+
thickness=1, lineType=cv2.LINE_AA)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _draw_trust_region(vis, pts, r, color):
|
|
89
|
+
# draw trust regions
|
|
90
|
+
# vis: image
|
|
91
|
+
# pts: points
|
|
92
|
+
# r: radius
|
|
93
|
+
# color: color
|
|
94
|
+
for i in range(len(pts)):
|
|
95
|
+
ptc = (int(pts[i][0]), int(pts[i][1]))
|
|
96
|
+
rx, ry = int(r[i][0]), int(r[i][1]) # trust region
|
|
97
|
+
cv2.ellipse(vis, center=ptc, axes=(rx, ry), angle=0, startAngle=0, endAngle=360,
|
|
98
|
+
color=color, thickness=1, lineType=cv2.LINE_AA)
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def _print_vehicle_info(vis, ptc, v, color):
|
|
102
|
+
# print latitude
|
|
103
|
+
pt = (ptc[0] + 15, ptc[1])
|
|
104
|
+
text = "%.6f" % v.x
|
|
105
|
+
cv2.putText(vis, text=text, org=pt, fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
106
|
+
fontScale=0.5, color=color, thickness=1, lineType=cv2.LINE_AA)
|
|
107
|
+
# print longitude
|
|
108
|
+
pt = (ptc[0] + 15, ptc[1] + 20)
|
|
109
|
+
text = "%.6f" % v.y
|
|
110
|
+
cv2.putText(vis, text=text, org=pt, fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
111
|
+
fontScale=0.5, color=color, thickness=1, lineType=cv2.LINE_AA)
|
|
112
|
+
# print id
|
|
113
|
+
if hasattr(v, 'traj_id') and v.traj_id is not None:
|
|
114
|
+
pt = (ptc[0] + 15, ptc[1] + 40)
|
|
115
|
+
text = f"id: {v.traj_id}"
|
|
116
|
+
cv2.putText(vis, text=text, org=pt, fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
117
|
+
fontScale=0.5, color=color, thickness=1, lineType=cv2.LINE_AA)
|
|
118
|
+
if v.heading is not None:
|
|
119
|
+
pt= (ptc[0] + 15, ptc[1] + 60)
|
|
120
|
+
text = f"heading: {v.heading:.2f} deg"
|
|
121
|
+
cv2.putText(vis, text=text, org=pt, fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
122
|
+
fontScale=0.5, color=color, thickness=1, lineType=cv2.LINE_AA)
|
|
123
|
+
# pt = (ptc[0] + 15, ptc[1] + 60)
|
|
124
|
+
# if hasattr(v, 'lane') and v.lane is not None:
|
|
125
|
+
# text = f"lane: {v.lane}"
|
|
126
|
+
# cv2.putText(vis, text=text, org=pt, fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
127
|
+
# fontScale=0.5, color=color, thickness=1, lineType=cv2.LINE_AA)
|
|
128
|
+
# # print category
|
|
129
|
+
# if v.category is not None:
|
|
130
|
+
# pt = (ptc[0] + 15, ptc[1] + 40)
|
|
131
|
+
# if label_list is not None:
|
|
132
|
+
# text = "%s" % label_list.id2label[str(v.category)]
|
|
133
|
+
# else:
|
|
134
|
+
# text = "Category: %d" % v.category
|
|
135
|
+
# cv2.putText(vis, text=text, org=pt, fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
136
|
+
# fontScale=0.5, color=color, thickness=1, lineType=cv2.LINE_AA)
|
|
137
|
+
|
|
138
|
+
def _create_coord_mapper(self):
|
|
139
|
+
|
|
140
|
+
# wd_tl, wd_tr, wd_bl, wd_br = self.f.tl.copy(), self.f.tr.copy(), self.f.bl.copy(), self.f.br.copy()
|
|
141
|
+
px_tl, px_tr, px_bl, px_br = [0, 0], [
|
|
142
|
+
self.w-1, 0], [0, self.h-1], [self.w-1, self.h-1]
|
|
143
|
+
|
|
144
|
+
# normalize to local coordination
|
|
145
|
+
wd_tl = coord_normalization(
|
|
146
|
+
self.f.tl[0], self.f.tl[1], self.f.tl[0], self.f.tl[1])
|
|
147
|
+
wd_tr = coord_normalization(
|
|
148
|
+
self.f.tr[0], self.f.tr[1], self.f.tl[0], self.f.tl[1])
|
|
149
|
+
wd_bl = coord_normalization(
|
|
150
|
+
self.f.bl[0], self.f.bl[1], self.f.tl[0], self.f.tl[1])
|
|
151
|
+
wd_br = coord_normalization(
|
|
152
|
+
self.f.br[0], self.f.br[1], self.f.tl[0], self.f.tl[1])
|
|
153
|
+
|
|
154
|
+
wd_points = np.array([wd_tl, wd_tr, wd_bl, wd_br], np.float32)
|
|
155
|
+
px_points = np.array([px_tl, px_tr, px_bl, px_br], np.float32)
|
|
156
|
+
|
|
157
|
+
transform_wd2px = cv2.getPerspectiveTransform(
|
|
158
|
+
src=wd_points, dst=px_points)
|
|
159
|
+
transform_px2wd = cv2.getPerspectiveTransform(
|
|
160
|
+
src=px_points, dst=wd_points)
|
|
161
|
+
|
|
162
|
+
return transform_wd2px, transform_px2wd
|
|
163
|
+
|
|
164
|
+
def _world2pxl(self, pt_world, output_int=True):
|
|
165
|
+
|
|
166
|
+
# normalize to local coordination
|
|
167
|
+
pt_world = np.array(pt_world).reshape([-1, 2])
|
|
168
|
+
lat, lon = pt_world[:, 0], pt_world[:, 1]
|
|
169
|
+
lat_norm, lon_norm = coord_normalization(
|
|
170
|
+
lat, lon, self.f.tl[0], self.f.tl[1])
|
|
171
|
+
pt_world = np.array([lat_norm, lon_norm]).T
|
|
172
|
+
|
|
173
|
+
pt_world = np.array(pt_world).reshape([-1, 1, 2]).astype(np.float32)
|
|
174
|
+
pt_pixel = cv2.perspectiveTransform(pt_world, self.transform_wd2px)
|
|
175
|
+
pt_pixel = np.squeeze(pt_pixel)
|
|
176
|
+
|
|
177
|
+
if output_int:
|
|
178
|
+
pt_pixel = pt_pixel.astype(np.int32)
|
|
179
|
+
|
|
180
|
+
return pt_pixel
|
|
181
|
+
|
|
182
|
+
def _pxl2world(self, pt_pixel):
|
|
183
|
+
|
|
184
|
+
pt_pixel = np.array(pt_pixel).reshape([-1, 1, 2]).astype(np.float32)
|
|
185
|
+
pt_world = cv2.perspectiveTransform(
|
|
186
|
+
pt_pixel, self.transform_px2wd).astype(np.float64)
|
|
187
|
+
pt_world = np.squeeze(pt_world)
|
|
188
|
+
pt_world = pt_world.reshape([-1, 2])
|
|
189
|
+
|
|
190
|
+
# unnormalize to world coordination
|
|
191
|
+
lat_norm, lon_norm = pt_world[:, 0], pt_world[:, 1]
|
|
192
|
+
lat, lon = coord_unnormalization(
|
|
193
|
+
lat_norm, lon_norm, self.f.tl[0], self.f.tl[1])
|
|
194
|
+
pt_world = np.array([lat, lon]).T
|
|
195
|
+
|
|
196
|
+
return pt_world
|
|
197
|
+
|
|
198
|
+
def draw_points(self, frame, show_heading=False):
|
|
199
|
+
# TODO: draw vehicle as box when show_heading is True
|
|
200
|
+
if len(frame) == 0:
|
|
201
|
+
return self.basemap
|
|
202
|
+
|
|
203
|
+
vis = np.copy(self.basemap)
|
|
204
|
+
|
|
205
|
+
for i in range(0, len(frame)):
|
|
206
|
+
v = frame[i]
|
|
207
|
+
|
|
208
|
+
if v.x is None or v.y is None:
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
color = self.color_table[hash(v.traj_id) % 10].tolist()
|
|
212
|
+
|
|
213
|
+
ptc = self._world2pxl([v.x, v.y])
|
|
214
|
+
|
|
215
|
+
# box unavailiable, draw a circle instead
|
|
216
|
+
self._draw_vehicle_as_point(vis, ptc, color)
|
|
217
|
+
|
|
218
|
+
if show_heading:
|
|
219
|
+
self._draw_vehicle_heading_as_arrow(vis, ptc, v.heading, color)
|
|
220
|
+
|
|
221
|
+
# print vehicle info beside box
|
|
222
|
+
self._print_vehicle_info(vis, ptc, v, (255, 255, 0),)
|
|
223
|
+
|
|
224
|
+
return vis
|
|
225
|
+
|
|
226
|
+
def draw_trajectory(self, frame, linewidth=2):
|
|
227
|
+
|
|
228
|
+
# update trajectory manager
|
|
229
|
+
for v in frame:
|
|
230
|
+
self.traj_manager.add_object(v, v.traj_id, frame.step, timestamp=frame.timestamp)
|
|
231
|
+
|
|
232
|
+
# update alpha (fade out)
|
|
233
|
+
self.traj_alpha *= 0.95
|
|
234
|
+
|
|
235
|
+
# draw trajectory
|
|
236
|
+
for v in self.traj_manager.last_frame:
|
|
237
|
+
pt_map = self._world2pxl([v.x, v.y])
|
|
238
|
+
v_ = v.prev
|
|
239
|
+
if v_ is None:
|
|
240
|
+
# print('prev is None')
|
|
241
|
+
continue
|
|
242
|
+
pt_map_prev = self._world2pxl([v_.x, v_.y])
|
|
243
|
+
|
|
244
|
+
pt1 = (int(pt_map[0]), int(pt_map[1]))
|
|
245
|
+
pt2 = (int(pt_map_prev[0]), int(pt_map_prev[1]))
|
|
246
|
+
|
|
247
|
+
color = self.color_table[hash(v.traj_id) % 10].tolist()
|
|
248
|
+
|
|
249
|
+
cv2.line(self.traj_layer, pt1=pt1, pt2=pt2,
|
|
250
|
+
color=color, thickness=linewidth)
|
|
251
|
+
cv2.line(self.traj_alpha, pt1=pt1, pt2=pt2,
|
|
252
|
+
color=(0.8, 0.8, 0.8), thickness=linewidth)
|
|
253
|
+
|
|
254
|
+
return self.traj_layer, self.traj_alpha
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def layer_blending(base_layer, traj_layer, traj_alpha):
|
|
258
|
+
base_layer = base_layer.astype(np.float32)/255.
|
|
259
|
+
traj_layer = traj_layer.astype(np.float32)/255.
|
|
260
|
+
vis = base_layer*(1-traj_alpha) + traj_layer*traj_alpha
|
|
261
|
+
return vis
|
|
262
|
+
|
|
263
|
+
def draw_polygon(self, base_layer, polygon, color, thickness=2):
|
|
264
|
+
pts = self._world2pxl(polygon)
|
|
265
|
+
layer = cv2.polylines(
|
|
266
|
+
base_layer, [pts], isClosed=True, color=color, thickness=thickness)
|
|
267
|
+
return layer
|
|
268
|
+
|
|
269
|
+
def render(self, frame, with_traj=True, linewidth=2, show_heading=False):
|
|
270
|
+
base_layer = self.draw_points(
|
|
271
|
+
frame, show_heading=show_heading)
|
|
272
|
+
|
|
273
|
+
if with_traj:
|
|
274
|
+
traj_layer, traj_alpha = self.draw_trajectory(
|
|
275
|
+
frame, linewidth)
|
|
276
|
+
map_vis = self.layer_blending(base_layer, traj_layer, traj_alpha)
|
|
277
|
+
map_vis = (map_vis*255.).astype(np.uint8)
|
|
278
|
+
else:
|
|
279
|
+
map_vis = base_layer
|
|
280
|
+
|
|
281
|
+
return map_vis
|
|
282
|
+
|
|
283
|
+
def render_roaduser_points(self, list_of_points, show_heading=False):
|
|
284
|
+
## this method is used to render a list of roaduser point, this is particularly useful when you have a list of points without knowing the id (so you can't generate frame)
|
|
285
|
+
|
|
286
|
+
base_layer = self.draw_points(
|
|
287
|
+
list_of_points, show_heading=show_heading)
|
|
288
|
+
return base_layer
|
|
289
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: msight_base
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Object classes and base trajectory management for the MSight system
|
|
5
|
+
Author-email: Rusheng Zhang <zrs1990@gmail.com>
|
|
6
|
+
Requires-Python: >=3.6
|
|
7
|
+
Requires-Dist: numpy
|
|
8
|
+
Requires-Dist: opencv-python
|
|
9
|
+
Requires-Dist: tqdm
|
|
10
|
+
Requires-Dist: commonroad-io
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
msight_base/__init__.py
|
|
4
|
+
msight_base/behavior.py
|
|
5
|
+
msight_base/detection.py
|
|
6
|
+
msight_base/map.py
|
|
7
|
+
msight_base/road_user.py
|
|
8
|
+
msight_base/trajectory.py
|
|
9
|
+
msight_base.egg-info/PKG-INFO
|
|
10
|
+
msight_base.egg-info/SOURCES.txt
|
|
11
|
+
msight_base.egg-info/dependency_links.txt
|
|
12
|
+
msight_base.egg-info/requires.txt
|
|
13
|
+
msight_base.egg-info/top_level.txt
|
|
14
|
+
msight_base/utils/__init__.py
|
|
15
|
+
msight_base/utils/cls.py
|
|
16
|
+
msight_base/utils/data.py
|
|
17
|
+
msight_base/visualizer/__init__.py
|
|
18
|
+
msight_base/visualizer/utils.py
|
|
19
|
+
msight_base/visualizer/visualizer.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
msight_base
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "msight_base"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "Object classes and base trajectory management for the MSight system"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Rusheng Zhang", email = "zrs1990@gmail.com" }
|
|
11
|
+
]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"numpy",
|
|
14
|
+
"opencv-python",
|
|
15
|
+
"tqdm",
|
|
16
|
+
"commonroad-io"
|
|
17
|
+
]
|
|
18
|
+
requires-python = ">=3.6"
|