dynamicalnodes 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.
- dynamicalnodes/__init__.py +35 -0
- dynamicalnodes/dynamical_system.py +221 -0
- dynamicalnodes/ros2py_py2ros.py +1034 -0
- dynamicalnodes/rosnode.py +658 -0
- dynamicalnodes/rostools.py +372 -0
- dynamicalnodes-0.1.dist-info/METADATA +82 -0
- dynamicalnodes-0.1.dist-info/RECORD +10 -0
- dynamicalnodes-0.1.dist-info/WHEEL +5 -0
- dynamicalnodes-0.1.dist-info/licenses/LICENSE +21 -0
- dynamicalnodes-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1034 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy.typing import NDArray
|
|
3
|
+
from typing import Any, Dict, List
|
|
4
|
+
import rclpy
|
|
5
|
+
from geometry_msgs.msg import (
|
|
6
|
+
Vector3,
|
|
7
|
+
Vector3Stamped,
|
|
8
|
+
Quaternion,
|
|
9
|
+
Pose,
|
|
10
|
+
PoseStamped,
|
|
11
|
+
PoseWithCovariance,
|
|
12
|
+
PoseWithCovarianceStamped,
|
|
13
|
+
Twist,
|
|
14
|
+
TwistStamped,
|
|
15
|
+
TwistWithCovariance,
|
|
16
|
+
Wrench,
|
|
17
|
+
Transform,
|
|
18
|
+
TransformStamped,
|
|
19
|
+
Accel,
|
|
20
|
+
AccelStamped,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from turtlesim.msg import Pose
|
|
24
|
+
from nav_msgs.msg import Odometry, Path
|
|
25
|
+
from sensor_msgs.msg import (
|
|
26
|
+
Imu,
|
|
27
|
+
LaserScan,
|
|
28
|
+
Image,
|
|
29
|
+
CompressedImage,
|
|
30
|
+
JointState,
|
|
31
|
+
)
|
|
32
|
+
from std_msgs.msg import Header, Float64, Float64MultiArray, Int32, Int32MultiArray, UInt8MultiArray
|
|
33
|
+
from builtin_interfaces.msg import Time
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Helper: covariance 36 <-> (6, 6)
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
def ros2py_covariance36(cov: List[float]) -> NDArray:
|
|
41
|
+
"""Flattened 36-element list -> (6, 6) ndarray."""
|
|
42
|
+
arr = np.asarray(cov, dtype=float).ravel()
|
|
43
|
+
if arr.size != 36:
|
|
44
|
+
raise ValueError(f"Expected covariance length 36, got {arr.size}")
|
|
45
|
+
return arr.reshape(6, 6)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def py2ros_covariance36(mat: NDArray) -> List[float]:
|
|
49
|
+
"""(6, 6) ndarray -> 36-element flat list."""
|
|
50
|
+
arr = np.asarray(mat, dtype=float)
|
|
51
|
+
if arr.shape != (6, 6):
|
|
52
|
+
raise ValueError(f"Expected shape (6, 6), got {arr.shape}")
|
|
53
|
+
return arr.ravel().tolist()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# builtin_interfaces/Time <-> float seconds
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def ros2py_time(t: Time) -> float:
|
|
61
|
+
return float(t.sec) + float(t.nanosec) * 1e-9
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def py2ros_time(t: float) -> Time:
|
|
65
|
+
sec = int(np.floor(t))
|
|
66
|
+
nsec = int(round((t - sec) * 1e9))
|
|
67
|
+
if nsec >= 1_000_000_000:
|
|
68
|
+
sec += 1
|
|
69
|
+
nsec -= 1_000_000_000
|
|
70
|
+
msg = Time()
|
|
71
|
+
msg.sec = sec
|
|
72
|
+
msg.nanosec = nsec
|
|
73
|
+
return msg
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# std_msgs/Header <-> (time, frame_id)
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def ros2py_header(h: Header) -> Dict[str, Any]:
|
|
81
|
+
"""Return {'t': float_seconds, 'frame_id': str}."""
|
|
82
|
+
return {"t": ros2py_time(h.stamp), "frame_id": h.frame_id}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def py2ros_header(t: float, frame_id: str = "") -> Header:
|
|
86
|
+
msg = Header()
|
|
87
|
+
msg.stamp = py2ros_time(t)
|
|
88
|
+
msg.frame_id = frame_id
|
|
89
|
+
return msg
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# geometry_msgs/Vector3 <-> np.ndarray(3,)
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
def ros2py_vector3(msg: Vector3) -> NDArray:
|
|
97
|
+
return np.array([msg.x, msg.y, msg.z], dtype=float)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def py2ros_vector3(arr: NDArray) -> Vector3:
|
|
101
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
102
|
+
if flat.size != 3:
|
|
103
|
+
raise ValueError(f"py2ros_vector3 expected length 3, got {flat.size}")
|
|
104
|
+
x, y, z = flat.tolist()
|
|
105
|
+
msg = Vector3()
|
|
106
|
+
msg.x = float(x)
|
|
107
|
+
msg.y = float(y)
|
|
108
|
+
msg.z = float(z)
|
|
109
|
+
return msg
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# geometry_msgs/Vector3Stamped <-> np.ndarray(4,) [t, x, y, z]
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
def ros2py_vector3_stamped(msg: Vector3Stamped) -> NDArray:
|
|
117
|
+
"""Convert Vector3Stamped to [t, x, y, z]."""
|
|
118
|
+
t = ros2py_time(msg.header.stamp)
|
|
119
|
+
vec = ros2py_vector3(msg.vector)
|
|
120
|
+
return np.concatenate(([t], vec))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def py2ros_vector3_stamped(arr: NDArray, *, frame_id: str = "") -> Vector3Stamped:
|
|
124
|
+
"""Convert [t, x, y, z] to Vector3Stamped."""
|
|
125
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
126
|
+
if flat.size != 4:
|
|
127
|
+
raise ValueError(f"py2ros_vector3_stamped expected length 4, got {flat.size}")
|
|
128
|
+
|
|
129
|
+
msg = Vector3Stamped()
|
|
130
|
+
msg.header = py2ros_header(float(flat[0]), frame_id)
|
|
131
|
+
msg.vector = py2ros_vector3(flat[1:4])
|
|
132
|
+
return msg
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
# geometry_msgs/Quaternion <-> np.ndarray(4,)
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def ros2py_quaternion(msg: Quaternion) -> NDArray:
|
|
140
|
+
return np.array([msg.x, msg.y, msg.z, msg.w], dtype=float)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def py2ros_quaternion(arr: NDArray) -> Quaternion:
|
|
144
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
145
|
+
if flat.size != 4:
|
|
146
|
+
raise ValueError(f"py2ros_quaternion expected length 4, got {flat.size}")
|
|
147
|
+
x, y, z, w = flat.tolist()
|
|
148
|
+
msg = Quaternion()
|
|
149
|
+
msg.x = float(x)
|
|
150
|
+
msg.y = float(y)
|
|
151
|
+
msg.z = float(z)
|
|
152
|
+
msg.w = float(w)
|
|
153
|
+
return msg
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
# geometry_msgs/Pose <-> np.ndarray(7,)
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
def ros2py_pose(msg: Pose) -> NDArray:
|
|
161
|
+
p = msg.position
|
|
162
|
+
q = msg.orientation
|
|
163
|
+
return np.array(
|
|
164
|
+
[p.x, p.y, p.z, q.x, q.y, q.z, q.w],
|
|
165
|
+
dtype=float,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def py2ros_pose(arr: NDArray) -> Pose:
|
|
170
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
171
|
+
if flat.size != 7:
|
|
172
|
+
raise ValueError(f"py2ros_pose expected length 7, got {flat.size}")
|
|
173
|
+
px, py, pz, qx, qy, qz, qw = flat.tolist()
|
|
174
|
+
msg = Pose()
|
|
175
|
+
msg.position.x = float(px)
|
|
176
|
+
msg.position.y = float(py)
|
|
177
|
+
msg.position.z = float(pz)
|
|
178
|
+
msg.orientation.x = float(qx)
|
|
179
|
+
msg.orientation.y = float(qy)
|
|
180
|
+
msg.orientation.z = float(qz)
|
|
181
|
+
msg.orientation.w = float(qw)
|
|
182
|
+
return msg
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
# geometry_msgs/PoseStamped <-> np.ndarray(8,)
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
def ros2py_pose_stamped(msg: PoseStamped) -> NDArray:
|
|
190
|
+
"""
|
|
191
|
+
[t, pos.x, pos.y, pos.z, ori.x, ori.y, ori.z, ori.w]
|
|
192
|
+
t in seconds.
|
|
193
|
+
"""
|
|
194
|
+
t = ros2py_time(msg.header.stamp)
|
|
195
|
+
pose_arr = ros2py_pose(msg.pose)
|
|
196
|
+
return np.concatenate(([t], pose_arr))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def py2ros_pose_stamped(arr: NDArray, *, frame_id: str = "") -> PoseStamped:
|
|
200
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
201
|
+
if flat.size != 8:
|
|
202
|
+
raise ValueError(f"py2ros_pose_stamped expected length 8, got {flat.size}")
|
|
203
|
+
t = float(flat[0])
|
|
204
|
+
pose_vec = flat[1:]
|
|
205
|
+
msg = PoseStamped()
|
|
206
|
+
msg.header = py2ros_header(t, frame_id=frame_id)
|
|
207
|
+
msg.pose = py2ros_pose(pose_vec)
|
|
208
|
+
return msg
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
# geometry_msgs/PoseWithCovariance <-> (7, (6,6))
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
def ros2py_pose_with_covariance(msg: PoseWithCovariance) -> Dict[str, Any]:
|
|
216
|
+
"""
|
|
217
|
+
Returns dict with:
|
|
218
|
+
'pose': ndarray(7,)
|
|
219
|
+
'cov': ndarray(6,6)
|
|
220
|
+
"""
|
|
221
|
+
pose_vec = ros2py_pose(msg.pose)
|
|
222
|
+
cov = ros2py_covariance36(msg.covariance)
|
|
223
|
+
return {"pose": pose_vec, "cov": cov}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def py2ros_pose_with_covariance(pose: NDArray, cov: NDArray) -> PoseWithCovariance:
|
|
227
|
+
msg = PoseWithCovariance()
|
|
228
|
+
msg.pose = py2ros_pose(pose)
|
|
229
|
+
msg.covariance = py2ros_covariance36(cov)
|
|
230
|
+
return msg
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
# geometry_msgs/PoseWithCovarianceStamped <-> dict
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
def ros2py_pose_with_covariance_stamped(
|
|
238
|
+
msg: PoseWithCovarianceStamped,
|
|
239
|
+
) -> Dict[str, Any]:
|
|
240
|
+
"""
|
|
241
|
+
Returns dict:
|
|
242
|
+
't': float seconds
|
|
243
|
+
'frame_id': str
|
|
244
|
+
'pose': ndarray(7,)
|
|
245
|
+
'cov': ndarray(6,6)
|
|
246
|
+
"""
|
|
247
|
+
t = ros2py_time(msg.header.stamp)
|
|
248
|
+
frame_id = msg.header.frame_id
|
|
249
|
+
inner = ros2py_pose_with_covariance(msg.pose)
|
|
250
|
+
return {
|
|
251
|
+
"t": t,
|
|
252
|
+
"frame_id": frame_id,
|
|
253
|
+
"pose": inner["pose"],
|
|
254
|
+
"cov": inner["cov"],
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def py2ros_pose_with_covariance_stamped(
|
|
259
|
+
t: float,
|
|
260
|
+
frame_id: str,
|
|
261
|
+
pose: NDArray,
|
|
262
|
+
cov: NDArray,
|
|
263
|
+
) -> PoseWithCovarianceStamped:
|
|
264
|
+
msg = PoseWithCovarianceStamped()
|
|
265
|
+
msg.header = py2ros_header(t, frame_id=frame_id)
|
|
266
|
+
msg.pose = py2ros_pose_with_covariance(pose, cov)
|
|
267
|
+
return msg
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
# geometry_msgs/Twist <-> np.ndarray(6,)
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
def ros2py_twist(msg: Twist) -> NDArray:
|
|
275
|
+
"""
|
|
276
|
+
[linear.x, linear.y, linear.z,
|
|
277
|
+
angular.x, angular.y, angular.z]
|
|
278
|
+
"""
|
|
279
|
+
return np.array(
|
|
280
|
+
[
|
|
281
|
+
msg.linear.x,
|
|
282
|
+
msg.linear.y,
|
|
283
|
+
msg.linear.z,
|
|
284
|
+
msg.angular.x,
|
|
285
|
+
msg.angular.y,
|
|
286
|
+
msg.angular.z,
|
|
287
|
+
],
|
|
288
|
+
dtype=float,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def py2ros_twist(arr: NDArray) -> Twist:
|
|
293
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
294
|
+
if flat.size != 6:
|
|
295
|
+
raise ValueError(f"py2ros_twist expected length 6, got {flat.size}")
|
|
296
|
+
lx, ly, lz, ax, ay, az = flat.tolist()
|
|
297
|
+
msg = Twist()
|
|
298
|
+
msg.linear.x = float(lx)
|
|
299
|
+
msg.linear.y = float(ly)
|
|
300
|
+
msg.linear.z = float(lz)
|
|
301
|
+
msg.angular.x = float(ax)
|
|
302
|
+
msg.angular.y = float(ay)
|
|
303
|
+
msg.angular.z = float(az)
|
|
304
|
+
return msg
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
# geometry_msgs/TwistStamped <-> np.ndarray(7,)
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
def ros2py_twist_stamped(msg: TwistStamped) -> NDArray:
|
|
312
|
+
"""
|
|
313
|
+
[t, linear.x, linear.y, linear.z,
|
|
314
|
+
angular.x, angular.y, angular.z]
|
|
315
|
+
"""
|
|
316
|
+
t = ros2py_time(msg.header.stamp)
|
|
317
|
+
twist_vec = ros2py_twist(msg.twist)
|
|
318
|
+
return np.concatenate(([t], twist_vec))
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def py2ros_twist_stamped(arr: NDArray, *, frame_id: str = "") -> TwistStamped:
|
|
322
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
323
|
+
if flat.size != 7:
|
|
324
|
+
raise ValueError(f"py2ros_twist_stamped expected length 7, got {flat.size}")
|
|
325
|
+
t = float(flat[0])
|
|
326
|
+
twist_vec = flat[1:]
|
|
327
|
+
msg = TwistStamped()
|
|
328
|
+
msg.header = py2ros_header(t, frame_id=frame_id)
|
|
329
|
+
msg.twist = py2ros_twist(twist_vec)
|
|
330
|
+
return msg
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# ---------------------------------------------------------------------------
|
|
334
|
+
# geometry_msgs/TwistWithCovariance <-> (6, (6,6))
|
|
335
|
+
# ---------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
def ros2py_twist_with_covariance(msg: TwistWithCovariance) -> Dict[str, Any]:
|
|
338
|
+
twist_vec = ros2py_twist(msg.twist)
|
|
339
|
+
cov = ros2py_covariance36(msg.covariance)
|
|
340
|
+
return {"twist": twist_vec, "cov": cov}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def py2ros_twist_with_covariance(twist: NDArray, cov: NDArray) -> TwistWithCovariance:
|
|
344
|
+
msg = TwistWithCovariance()
|
|
345
|
+
msg.twist = py2ros_twist(twist)
|
|
346
|
+
msg.covariance = py2ros_covariance36(cov)
|
|
347
|
+
return msg
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# ---------------------------------------------------------------------------
|
|
351
|
+
# geometry_msgs/Wrench <-> np.ndarray(6,)
|
|
352
|
+
# ---------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
def ros2py_wrench(msg: Wrench) -> NDArray:
|
|
355
|
+
"""
|
|
356
|
+
[force.x, force.y, force.z,
|
|
357
|
+
torque.x, torque.y, torque.z]
|
|
358
|
+
"""
|
|
359
|
+
return np.array(
|
|
360
|
+
[
|
|
361
|
+
msg.force.x,
|
|
362
|
+
msg.force.y,
|
|
363
|
+
msg.force.z,
|
|
364
|
+
msg.torque.x,
|
|
365
|
+
msg.torque.y,
|
|
366
|
+
msg.torque.z,
|
|
367
|
+
],
|
|
368
|
+
dtype=float,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def py2ros_wrench(arr: NDArray) -> Wrench:
|
|
373
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
374
|
+
if flat.size != 6:
|
|
375
|
+
raise ValueError(f"py2ros_wrench expected length 6, got {flat.size}")
|
|
376
|
+
fx, fy, fz, tx, ty, tz = flat.tolist()
|
|
377
|
+
msg = Wrench()
|
|
378
|
+
msg.force.x = float(fx)
|
|
379
|
+
msg.force.y = float(fy)
|
|
380
|
+
msg.force.z = float(fz)
|
|
381
|
+
msg.torque.x = float(tx)
|
|
382
|
+
msg.torque.y = float(ty)
|
|
383
|
+
msg.torque.z = float(tz)
|
|
384
|
+
return msg
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# ---------------------------------------------------------------------------
|
|
388
|
+
# geometry_msgs/Transform <-> np.ndarray(7,)
|
|
389
|
+
# ---------------------------------------------------------------------------
|
|
390
|
+
|
|
391
|
+
def ros2py_transform(msg: Transform) -> NDArray:
|
|
392
|
+
"""
|
|
393
|
+
[translation.x, translation.y, translation.z,
|
|
394
|
+
rotation.x, rotation.y, rotation.z, rotation.w]
|
|
395
|
+
"""
|
|
396
|
+
t = msg.translation
|
|
397
|
+
q = msg.rotation
|
|
398
|
+
return np.array(
|
|
399
|
+
[t.x, t.y, t.z, q.x, q.y, q.z, q.w],
|
|
400
|
+
dtype=float,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def py2ros_transform(arr: NDArray) -> Transform:
|
|
405
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
406
|
+
if flat.size != 7:
|
|
407
|
+
raise ValueError(f"py2ros_transform expected length 7, got {flat.size}")
|
|
408
|
+
tx, ty, tz, qx, qy, qz, qw = flat.tolist()
|
|
409
|
+
msg = Transform()
|
|
410
|
+
msg.translation.x = float(tx)
|
|
411
|
+
msg.translation.y = float(ty)
|
|
412
|
+
msg.translation.z = float(tz)
|
|
413
|
+
msg.rotation.x = float(qx)
|
|
414
|
+
msg.rotation.y = float(qy)
|
|
415
|
+
msg.rotation.z = float(qz)
|
|
416
|
+
msg.rotation.w = float(qw)
|
|
417
|
+
return msg
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# ---------------------------------------------------------------------------
|
|
421
|
+
# geometry_msgs/TransformStamped <-> np.ndarray(8,)
|
|
422
|
+
# ---------------------------------------------------------------------------
|
|
423
|
+
|
|
424
|
+
def ros2py_transform_stamped(msg: TransformStamped) -> NDArray:
|
|
425
|
+
"""
|
|
426
|
+
[t, tx, ty, tz, qx, qy, qz, qw]
|
|
427
|
+
"""
|
|
428
|
+
t = ros2py_time(msg.header.stamp)
|
|
429
|
+
tr = ros2py_transform(msg.transform)
|
|
430
|
+
return np.concatenate(([t], tr))
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def py2ros_transform_stamped(
|
|
434
|
+
arr: NDArray,
|
|
435
|
+
*,
|
|
436
|
+
frame_id: str = "",
|
|
437
|
+
child_frame_id: str = "",
|
|
438
|
+
) -> TransformStamped:
|
|
439
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
440
|
+
if flat.size != 8:
|
|
441
|
+
raise ValueError(f"py2ros_transform_stamped expected length 8, got {flat.size}")
|
|
442
|
+
t = float(flat[0])
|
|
443
|
+
tr_vec = flat[1:]
|
|
444
|
+
msg = TransformStamped()
|
|
445
|
+
msg.header = py2ros_header(t, frame_id=frame_id)
|
|
446
|
+
msg.child_frame_id = child_frame_id
|
|
447
|
+
msg.transform = py2ros_transform(tr_vec)
|
|
448
|
+
return msg
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# ---------------------------------------------------------------------------
|
|
452
|
+
# geometry_msgs/Accel <-> np.ndarray(6,)
|
|
453
|
+
# ---------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
def ros2py_accel(msg: Accel) -> NDArray:
|
|
456
|
+
"""
|
|
457
|
+
[linear.x, linear.y, linear.z,
|
|
458
|
+
angular.x, angular.y, angular.z]
|
|
459
|
+
"""
|
|
460
|
+
return np.array(
|
|
461
|
+
[
|
|
462
|
+
msg.linear.x,
|
|
463
|
+
msg.linear.y,
|
|
464
|
+
msg.linear.z,
|
|
465
|
+
msg.angular.x,
|
|
466
|
+
msg.angular.y,
|
|
467
|
+
msg.angular.z,
|
|
468
|
+
],
|
|
469
|
+
dtype=float,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def py2ros_accel(arr: NDArray) -> Accel:
|
|
474
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
475
|
+
if flat.size != 6:
|
|
476
|
+
raise ValueError(f"py2ros_accel expected length 6, got {flat.size}")
|
|
477
|
+
lx, ly, lz, ax, ay, az = flat.tolist()
|
|
478
|
+
msg = Accel()
|
|
479
|
+
msg.linear.x = float(lx)
|
|
480
|
+
msg.linear.y = float(ly)
|
|
481
|
+
msg.linear.z = float(lz)
|
|
482
|
+
msg.angular.x = float(ax)
|
|
483
|
+
msg.angular.y = float(ay)
|
|
484
|
+
msg.angular.z = float(az)
|
|
485
|
+
return msg
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# ---------------------------------------------------------------------------
|
|
489
|
+
# geometry_msgs/AccelStamped <-> np.ndarray(7,)
|
|
490
|
+
# ---------------------------------------------------------------------------
|
|
491
|
+
|
|
492
|
+
def ros2py_accel_stamped(msg: AccelStamped) -> NDArray:
|
|
493
|
+
"""
|
|
494
|
+
[t, linear.x, linear.y, linear.z,
|
|
495
|
+
angular.x, angular.y, angular.z]
|
|
496
|
+
"""
|
|
497
|
+
t = ros2py_time(msg.header.stamp)
|
|
498
|
+
acc_vec = ros2py_accel(msg.accel)
|
|
499
|
+
return np.concatenate(([t], acc_vec))
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def py2ros_accel_stamped(arr: NDArray, *, frame_id: str = "") -> AccelStamped:
|
|
503
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
504
|
+
if flat.size != 7:
|
|
505
|
+
raise ValueError(f"py2ros_accel_stamped expected length 7, got {flat.size}")
|
|
506
|
+
t = float(flat[0])
|
|
507
|
+
acc_vec = flat[1:]
|
|
508
|
+
msg = AccelStamped()
|
|
509
|
+
msg.header = py2ros_header(t, frame_id=frame_id)
|
|
510
|
+
msg.accel = py2ros_accel(acc_vec)
|
|
511
|
+
return msg
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
# ---------------------------------------------------------------------------
|
|
515
|
+
# sensor_msgs/Imu <-> np.ndarray(10,)
|
|
516
|
+
# (orientation, angular_velocity, linear_acceleration; ignores covariances)
|
|
517
|
+
# ---------------------------------------------------------------------------
|
|
518
|
+
|
|
519
|
+
def ros2py_imu(msg: Imu) -> NDArray:
|
|
520
|
+
"""
|
|
521
|
+
[ori.x, ori.y, ori.z, ori.w,
|
|
522
|
+
gyro.x, gyro.y, gyro.z,
|
|
523
|
+
acc.x, acc.y, acc.z]
|
|
524
|
+
"""
|
|
525
|
+
return np.array(
|
|
526
|
+
[
|
|
527
|
+
msg.orientation.x,
|
|
528
|
+
msg.orientation.y,
|
|
529
|
+
msg.orientation.z,
|
|
530
|
+
msg.orientation.w,
|
|
531
|
+
msg.angular_velocity.x,
|
|
532
|
+
msg.angular_velocity.y,
|
|
533
|
+
msg.angular_velocity.z,
|
|
534
|
+
msg.linear_acceleration.x,
|
|
535
|
+
msg.linear_acceleration.y,
|
|
536
|
+
msg.linear_acceleration.z,
|
|
537
|
+
],
|
|
538
|
+
dtype=float,
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def py2ros_imu(arr: NDArray) -> Imu:
|
|
543
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
544
|
+
if flat.size != 10:
|
|
545
|
+
raise ValueError(f"py2ros_imu expected length 10, got {flat.size}")
|
|
546
|
+
ox, oy, oz, ow, wx, wy, wz, ax, ay, az = flat.tolist()
|
|
547
|
+
msg = Imu()
|
|
548
|
+
msg.orientation.x = float(ox)
|
|
549
|
+
msg.orientation.y = float(oy)
|
|
550
|
+
msg.orientation.z = float(oz)
|
|
551
|
+
msg.orientation.w = float(ow)
|
|
552
|
+
msg.angular_velocity.x = float(wx)
|
|
553
|
+
msg.angular_velocity.y = float(wy)
|
|
554
|
+
msg.angular_velocity.z = float(wz)
|
|
555
|
+
msg.linear_acceleration.x = float(ax)
|
|
556
|
+
msg.linear_acceleration.y = float(ay)
|
|
557
|
+
msg.linear_acceleration.z = float(az)
|
|
558
|
+
return msg
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# ---------------------------------------------------------------------------
|
|
562
|
+
# nav_msgs/Odometry <-> np.ndarray(13,)
|
|
563
|
+
# (pose + twist; covariances ignored)
|
|
564
|
+
# ---------------------------------------------------------------------------
|
|
565
|
+
|
|
566
|
+
def ros2py_odometry(msg: Odometry) -> NDArray:
|
|
567
|
+
"""
|
|
568
|
+
[pos.x, pos.y, pos.z,
|
|
569
|
+
ori.x, ori.y, ori.z, ori.w,
|
|
570
|
+
lin_vel.x, lin_vel.y, lin_vel.z,
|
|
571
|
+
ang_vel.x, ang_vel.y, ang_vel.z]
|
|
572
|
+
"""
|
|
573
|
+
p = msg.pose.pose.position
|
|
574
|
+
q = msg.pose.pose.orientation
|
|
575
|
+
v = msg.twist.twist.linear
|
|
576
|
+
w = msg.twist.twist.angular
|
|
577
|
+
return np.array(
|
|
578
|
+
[
|
|
579
|
+
p.x, p.y, p.z,
|
|
580
|
+
q.x, q.y, q.z, q.w,
|
|
581
|
+
v.x, v.y, v.z,
|
|
582
|
+
w.x, w.y, w.z,
|
|
583
|
+
],
|
|
584
|
+
dtype=float,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def py2ros_odometry(arr: NDArray, *, frame_id: str = "", child_frame_id: str = "") -> Odometry:
|
|
589
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
590
|
+
if flat.size != 13:
|
|
591
|
+
raise ValueError(f"py2ros_odometry expected length 13, got {flat.size}")
|
|
592
|
+
px, py, pz, qx, qy, qz, qw, vx, vy, vz, wx, wy, wz = flat.tolist()
|
|
593
|
+
msg = Odometry()
|
|
594
|
+
msg.header.frame_id = frame_id
|
|
595
|
+
msg.child_frame_id = child_frame_id
|
|
596
|
+
|
|
597
|
+
msg.pose.pose.position.x = float(px)
|
|
598
|
+
msg.pose.pose.position.y = float(py)
|
|
599
|
+
msg.pose.pose.position.z = float(pz)
|
|
600
|
+
|
|
601
|
+
msg.pose.pose.orientation.x = float(qx)
|
|
602
|
+
msg.pose.pose.orientation.y = float(qy)
|
|
603
|
+
msg.pose.pose.orientation.z = float(qz)
|
|
604
|
+
msg.pose.pose.orientation.w = float(qw)
|
|
605
|
+
|
|
606
|
+
msg.twist.twist.linear.x = float(vx)
|
|
607
|
+
msg.twist.twist.linear.y = float(vy)
|
|
608
|
+
msg.twist.twist.linear.z = float(vz)
|
|
609
|
+
|
|
610
|
+
msg.twist.twist.angular.x = float(wx)
|
|
611
|
+
msg.twist.twist.angular.y = float(wy)
|
|
612
|
+
msg.twist.twist.angular.z = float(wz)
|
|
613
|
+
|
|
614
|
+
return msg
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
# ---------------------------------------------------------------------------
|
|
618
|
+
# nav_msgs/Path <-> np.ndarray(N, 8)
|
|
619
|
+
# (sequence of PoseStamped-like vectors)
|
|
620
|
+
# ---------------------------------------------------------------------------
|
|
621
|
+
|
|
622
|
+
def ros2py_path(msg: Path) -> NDArray:
|
|
623
|
+
"""
|
|
624
|
+
Path -> (N, 8) array of PoseStamped vectors:
|
|
625
|
+
[t, px, py, pz, qx, qy, qz, qw]
|
|
626
|
+
"""
|
|
627
|
+
if not msg.poses:
|
|
628
|
+
return np.zeros((0, 8), dtype=float)
|
|
629
|
+
mats = [ros2py_pose_stamped(p) for p in msg.poses]
|
|
630
|
+
return np.vstack(mats)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def py2ros_path(arr: NDArray, *, frame_id: str = "") -> Path:
|
|
634
|
+
"""
|
|
635
|
+
arr: (N, 8) or (8,) -> Path (with poses' headers set individually)
|
|
636
|
+
"""
|
|
637
|
+
a = np.asarray(arr, dtype=float)
|
|
638
|
+
if a.ndim == 1:
|
|
639
|
+
a = a[None, :]
|
|
640
|
+
if a.shape[1] != 8:
|
|
641
|
+
raise ValueError(f"py2ros_path expected shape (N, 8), got {a.shape}")
|
|
642
|
+
msg = Path()
|
|
643
|
+
msg.header.frame_id = frame_id
|
|
644
|
+
msg.poses = [py2ros_pose_stamped(row, frame_id=frame_id) for row in a]
|
|
645
|
+
return msg
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
# ---------------------------------------------------------------------------
|
|
649
|
+
# geometry_msgs/PoseArray <-> np.ndarray(N, 7)
|
|
650
|
+
# ---------------------------------------------------------------------------
|
|
651
|
+
|
|
652
|
+
def ros2py_pose_array(msg) -> NDArray:
|
|
653
|
+
"""
|
|
654
|
+
geometry_msgs/PoseArray -> (N, 7)
|
|
655
|
+
(we don't import PoseArray to avoid version issues; pass concrete msg)
|
|
656
|
+
"""
|
|
657
|
+
poses = msg.poses
|
|
658
|
+
if not poses:
|
|
659
|
+
return np.zeros((0, 7), dtype=float)
|
|
660
|
+
mats = [ros2py_pose(p) for p in poses]
|
|
661
|
+
return np.vstack(mats)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def py2ros_pose_array(arr: NDArray, pose_array_cls: Any, *, frame_id: str = "") -> Any:
|
|
665
|
+
"""
|
|
666
|
+
np.ndarray(N, 7) -> PoseArray-like msg.
|
|
667
|
+
|
|
668
|
+
pose_array_cls: the actual PoseArray class type to instantiate.
|
|
669
|
+
"""
|
|
670
|
+
a = np.asarray(arr, dtype=float)
|
|
671
|
+
if a.ndim == 1:
|
|
672
|
+
a = a[None, :]
|
|
673
|
+
if a.shape[1] != 7:
|
|
674
|
+
raise ValueError(f"py2ros_pose_array expected shape (N, 7), got {a.shape}")
|
|
675
|
+
msg = pose_array_cls()
|
|
676
|
+
msg.header.frame_id = frame_id
|
|
677
|
+
msg.poses = [py2ros_pose(row) for row in a]
|
|
678
|
+
return msg
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
# ---------------------------------------------------------------------------
|
|
682
|
+
# sensor_msgs/JointState <-> dict of arrays
|
|
683
|
+
# ---------------------------------------------------------------------------
|
|
684
|
+
|
|
685
|
+
def ros2py_joint_state(msg: JointState) -> Dict[str, NDArray]:
|
|
686
|
+
"""
|
|
687
|
+
Returns dict with keys:
|
|
688
|
+
'name' -> np.array of dtype object (names)
|
|
689
|
+
'position' -> np.array
|
|
690
|
+
'velocity' -> np.array
|
|
691
|
+
'effort' -> np.array
|
|
692
|
+
"""
|
|
693
|
+
return {
|
|
694
|
+
"name": np.array(msg.name, dtype=object),
|
|
695
|
+
"position": np.asarray(msg.position, dtype=float),
|
|
696
|
+
"velocity": np.asarray(msg.velocity, dtype=float),
|
|
697
|
+
"effort": np.asarray(msg.effort, dtype=float),
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def py2ros_joint_state(
|
|
702
|
+
*,
|
|
703
|
+
names: List[str],
|
|
704
|
+
position: NDArray,
|
|
705
|
+
velocity: NDArray = None,
|
|
706
|
+
effort: NDArray = None,
|
|
707
|
+
) -> JointState:
|
|
708
|
+
msg = JointState()
|
|
709
|
+
msg.name = list(names)
|
|
710
|
+
msg.position = np.asarray(position, dtype=float).tolist()
|
|
711
|
+
if velocity is not None:
|
|
712
|
+
msg.velocity = np.asarray(velocity, dtype=float).tolist()
|
|
713
|
+
if effort is not None:
|
|
714
|
+
msg.effort = np.asarray(effort, dtype=float).tolist()
|
|
715
|
+
return msg
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
# ---------------------------------------------------------------------------
|
|
719
|
+
# sensor_msgs/LaserScan <-> ranges ndarray + optional metadata
|
|
720
|
+
# ---------------------------------------------------------------------------
|
|
721
|
+
|
|
722
|
+
def ros2py_laserscan(msg: LaserScan) -> Dict[str, Any]:
|
|
723
|
+
"""
|
|
724
|
+
Returns dict:
|
|
725
|
+
'ranges' -> np.ndarray(N,)
|
|
726
|
+
'intensities' -> np.ndarray(N,)
|
|
727
|
+
'angle_min', 'angle_max', 'angle_increment', 'time_increment',
|
|
728
|
+
'scan_time', 'range_min', 'range_max' -> floats
|
|
729
|
+
"""
|
|
730
|
+
return {
|
|
731
|
+
"ranges": np.asarray(msg.ranges, dtype=float),
|
|
732
|
+
"intensities": np.asarray(msg.intensities, dtype=float),
|
|
733
|
+
"angle_min": float(msg.angle_min),
|
|
734
|
+
"angle_max": float(msg.angle_max),
|
|
735
|
+
"angle_increment": float(msg.angle_increment),
|
|
736
|
+
"time_increment": float(msg.time_increment),
|
|
737
|
+
"scan_time": float(msg.scan_time),
|
|
738
|
+
"range_min": float(msg.range_min),
|
|
739
|
+
"range_max": float(msg.range_max),
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def py2ros_laserscan(
|
|
744
|
+
ranges: NDArray,
|
|
745
|
+
*,
|
|
746
|
+
angle_min: float,
|
|
747
|
+
angle_max: float,
|
|
748
|
+
angle_increment: float,
|
|
749
|
+
time_increment: float = 0.0,
|
|
750
|
+
scan_time: float = 0.0,
|
|
751
|
+
range_min: float = 0.0,
|
|
752
|
+
range_max: float = 0.0,
|
|
753
|
+
intensities: NDArray = None,
|
|
754
|
+
frame_id: str = "",
|
|
755
|
+
stamp: float = 0.0,
|
|
756
|
+
) -> LaserScan:
|
|
757
|
+
msg = LaserScan()
|
|
758
|
+
msg.header = py2ros_header(stamp, frame_id=frame_id)
|
|
759
|
+
msg.angle_min = float(angle_min)
|
|
760
|
+
msg.angle_max = float(angle_max)
|
|
761
|
+
msg.angle_increment = float(angle_increment)
|
|
762
|
+
msg.time_increment = float(time_increment)
|
|
763
|
+
msg.scan_time = float(scan_time)
|
|
764
|
+
msg.range_min = float(range_min)
|
|
765
|
+
msg.range_max = float(range_max)
|
|
766
|
+
r = np.asarray(ranges, dtype=float).ravel()
|
|
767
|
+
msg.ranges = r.tolist()
|
|
768
|
+
if intensities is not None:
|
|
769
|
+
inten = np.asarray(intensities, dtype=float).ravel()
|
|
770
|
+
if inten.size != r.size:
|
|
771
|
+
raise ValueError("intensities and ranges must have same length")
|
|
772
|
+
msg.intensities = inten.tolist()
|
|
773
|
+
return msg
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
# ---------------------------------------------------------------------------
|
|
777
|
+
# sensor_msgs/Image <-> np.ndarray(H, W, C)
|
|
778
|
+
# (simple encodings only)
|
|
779
|
+
# ---------------------------------------------------------------------------
|
|
780
|
+
|
|
781
|
+
def ros2py_image(msg: Image) -> NDArray:
|
|
782
|
+
"""
|
|
783
|
+
Convert sensor_msgs/Image to ndarray(H, W, C).
|
|
784
|
+
|
|
785
|
+
Supports common encodings like 'mono8', 'rgb8', 'bgr8'.
|
|
786
|
+
"""
|
|
787
|
+
data = np.frombuffer(msg.data, dtype=np.uint8)
|
|
788
|
+
if msg.encoding.lower() in ("mono8", "8uc1"):
|
|
789
|
+
arr = data.reshape(msg.height, msg.width)
|
|
790
|
+
elif msg.encoding.lower() in ("rgb8", "bgr8"):
|
|
791
|
+
arr = data.reshape(msg.height, msg.width, 3)
|
|
792
|
+
else:
|
|
793
|
+
# Fallback: just flatten; caller must interpret
|
|
794
|
+
arr = data
|
|
795
|
+
return arr
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def py2ros_image(
|
|
799
|
+
arr: NDArray,
|
|
800
|
+
*,
|
|
801
|
+
encoding: str = "rgb8",
|
|
802
|
+
frame_id: str = "",
|
|
803
|
+
stamp: float = 0.0,
|
|
804
|
+
) -> Image:
|
|
805
|
+
a = np.asarray(arr)
|
|
806
|
+
msg = Image()
|
|
807
|
+
msg.header = py2ros_header(stamp, frame_id=frame_id)
|
|
808
|
+
msg.encoding = encoding
|
|
809
|
+
msg.is_bigendian = 0
|
|
810
|
+
|
|
811
|
+
if encoding.lower() in ("mono8", "8uc1"):
|
|
812
|
+
if a.ndim == 2:
|
|
813
|
+
h, w = a.shape
|
|
814
|
+
elif a.ndim == 3 and a.shape[2] == 1:
|
|
815
|
+
h, w, _ = a.shape
|
|
816
|
+
a = a[:, :, 0]
|
|
817
|
+
else:
|
|
818
|
+
raise ValueError("mono8 image must be (H,W) or (H,W,1)")
|
|
819
|
+
msg.height = h
|
|
820
|
+
msg.width = w
|
|
821
|
+
msg.step = w
|
|
822
|
+
msg.data = a.astype(np.uint8).tobytes()
|
|
823
|
+
|
|
824
|
+
elif encoding.lower() in ("rgb8", "bgr8"):
|
|
825
|
+
if a.ndim != 3 or a.shape[2] != 3:
|
|
826
|
+
raise ValueError("rgb8/bgr8 image must be (H,W,3)")
|
|
827
|
+
h, w, _ = a.shape
|
|
828
|
+
msg.height = h
|
|
829
|
+
msg.width = w
|
|
830
|
+
msg.step = w * 3
|
|
831
|
+
msg.data = a.astype(np.uint8).tobytes()
|
|
832
|
+
|
|
833
|
+
else:
|
|
834
|
+
# Fallback: treat as 1D array of bytes
|
|
835
|
+
flat = a.astype(np.uint8).ravel()
|
|
836
|
+
msg.height = 1
|
|
837
|
+
msg.width = flat.size
|
|
838
|
+
msg.step = flat.size
|
|
839
|
+
msg.data = flat.tobytes()
|
|
840
|
+
|
|
841
|
+
return msg
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
# ---------------------------------------------------------------------------
|
|
845
|
+
# sensor_msgs/CompressedImage <-> bytes
|
|
846
|
+
# ---------------------------------------------------------------------------
|
|
847
|
+
|
|
848
|
+
def ros2py_compressed_image(msg: CompressedImage) -> Dict[str, Any]:
|
|
849
|
+
"""
|
|
850
|
+
Returns dict:
|
|
851
|
+
'format': str
|
|
852
|
+
'data': bytes
|
|
853
|
+
"""
|
|
854
|
+
return {"format": msg.format, "data": bytes(msg.data)}
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def py2ros_compressed_image(
|
|
858
|
+
data: bytes,
|
|
859
|
+
*,
|
|
860
|
+
format: str = "jpeg",
|
|
861
|
+
frame_id: str = "",
|
|
862
|
+
stamp: float = 0.0,
|
|
863
|
+
) -> CompressedImage:
|
|
864
|
+
msg = CompressedImage()
|
|
865
|
+
msg.header = py2ros_header(stamp, frame_id=frame_id)
|
|
866
|
+
msg.format = format
|
|
867
|
+
msg.data = data
|
|
868
|
+
return msg
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
# ---------------------------------------------------------------------------
|
|
872
|
+
# std_msgs/Float64MultiArray, Int32MultiArray, UInt8MultiArray <-> ndarray
|
|
873
|
+
# (layout ignored)
|
|
874
|
+
# ---------------------------------------------------------------------------
|
|
875
|
+
|
|
876
|
+
def ros2py_float64_multiarray(msg: Float64MultiArray) -> NDArray:
|
|
877
|
+
return np.asarray(msg.data, dtype=float)
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def py2ros_float64_multiarray(arr: NDArray) -> Float64MultiArray:
|
|
881
|
+
msg = Float64MultiArray()
|
|
882
|
+
msg.data = np.asarray(arr, dtype=float).ravel().tolist()
|
|
883
|
+
return msg
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def ros2py_int32_multiarray(msg: Int32MultiArray) -> NDArray:
|
|
887
|
+
return np.asarray(msg.data, dtype=np.int32)
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def py2ros_int32_multiarray(arr: NDArray) -> Int32MultiArray:
|
|
891
|
+
msg = Int32MultiArray()
|
|
892
|
+
msg.data = np.asarray(arr, dtype=np.int32).ravel().tolist()
|
|
893
|
+
return msg
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def ros2py_uint8_multiarray(msg: UInt8MultiArray) -> NDArray:
|
|
897
|
+
return np.asarray(msg.data, dtype=np.uint8)
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def py2ros_uint8_multiarray(arr: NDArray) -> UInt8MultiArray:
|
|
901
|
+
msg = UInt8MultiArray()
|
|
902
|
+
msg.data = np.asarray(arr, dtype=np.uint8).ravel().tolist()
|
|
903
|
+
return msg
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
# ---------------------------------------------------------------------------
|
|
907
|
+
# std_msgs/Float64 <-> scalar float (as 1D array)
|
|
908
|
+
# ---------------------------------------------------------------------------
|
|
909
|
+
|
|
910
|
+
def ros2py_float64(msg: Float64) -> NDArray:
|
|
911
|
+
"""Convert Float64 message to 1D numpy array with single element."""
|
|
912
|
+
return np.array([msg.data], dtype=float)
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def py2ros_float64(arr: NDArray) -> Float64:
|
|
916
|
+
"""Convert scalar or 1D array to Float64 message."""
|
|
917
|
+
msg = Float64()
|
|
918
|
+
arr = np.asarray(arr, dtype=float).ravel()
|
|
919
|
+
msg.data = float(arr[0]) if arr.size > 0 else 0.0
|
|
920
|
+
return msg
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
# ---------------------------------------------------------------------------
|
|
924
|
+
# std_msgs/Int32 <-> scalar int (as 1D array)
|
|
925
|
+
# ---------------------------------------------------------------------------
|
|
926
|
+
|
|
927
|
+
def ros2py_int32(msg: Int32) -> NDArray:
|
|
928
|
+
"""Convert Int32 message to 1D numpy array with single element."""
|
|
929
|
+
return np.array([msg.data], dtype=np.int32)
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def py2ros_int32(arr: NDArray) -> Int32:
|
|
933
|
+
"""Convert scalar or 1D array to Int32 message."""
|
|
934
|
+
msg = Int32()
|
|
935
|
+
arr = np.asarray(arr, dtype=np.int32).ravel()
|
|
936
|
+
msg.data = int(arr[0]) if arr.size > 0 else 0
|
|
937
|
+
return msg
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def ros2py_pose(msg: Pose) -> np.ndarray:
|
|
941
|
+
"""
|
|
942
|
+
Convert a ROS 2 turtlesim/Pose message into a NumPy array.
|
|
943
|
+
|
|
944
|
+
turtlesim/Pose fields:
|
|
945
|
+
float32 x
|
|
946
|
+
float32 y
|
|
947
|
+
float32 theta
|
|
948
|
+
float32 linear_velocity
|
|
949
|
+
float32 angular_velocity
|
|
950
|
+
|
|
951
|
+
Returns
|
|
952
|
+
-------
|
|
953
|
+
np.ndarray of shape (3,)
|
|
954
|
+
[x, y, theta]
|
|
955
|
+
"""
|
|
956
|
+
return np.array(
|
|
957
|
+
[msg.x, msg.y, msg.theta],
|
|
958
|
+
dtype=float
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
def py2ros_pose(x) -> Pose:
|
|
962
|
+
"""
|
|
963
|
+
Convert [x, y, theta] into turtlesim/Pose.
|
|
964
|
+
Accepts shapes (3,), (3,1), (1,3), or anything squeeze-able to >=3 elements.
|
|
965
|
+
"""
|
|
966
|
+
arr = np.asarray(x, dtype=float).squeeze()
|
|
967
|
+
|
|
968
|
+
# Handle common column/row vector cases
|
|
969
|
+
if arr.ndim == 2 and 1 in arr.shape:
|
|
970
|
+
arr = arr.reshape(-1)
|
|
971
|
+
|
|
972
|
+
if arr.ndim != 1 or arr.size < 3:
|
|
973
|
+
raise ValueError(
|
|
974
|
+
f"pose_py2ros expected at least 3 elements for [x,y,theta]; "
|
|
975
|
+
f"got shape {np.asarray(x).shape} after squeeze -> {arr.shape} (size={arr.size})."
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
msg = Pose()
|
|
979
|
+
msg.x = float(arr[0])
|
|
980
|
+
msg.y = float(arr[1])
|
|
981
|
+
msg.theta = float(arr[2])
|
|
982
|
+
msg.linear_velocity = 0.0
|
|
983
|
+
msg.angular_velocity = 0.0
|
|
984
|
+
return msg
|
|
985
|
+
|
|
986
|
+
# Map ROS msg type -> msg -> np.ndarray
|
|
987
|
+
ROS2PY_DEFAULT = {
|
|
988
|
+
Pose:ros2py_pose,
|
|
989
|
+
Twist: ros2py_twist,
|
|
990
|
+
PoseStamped: ros2py_pose_stamped,
|
|
991
|
+
TransformStamped: ros2py_transform_stamped,
|
|
992
|
+
Wrench: ros2py_wrench,
|
|
993
|
+
Accel: ros2py_accel,
|
|
994
|
+
AccelStamped: ros2py_accel_stamped,
|
|
995
|
+
Vector3Stamped: ros2py_vector3_stamped,
|
|
996
|
+
Imu: ros2py_imu,
|
|
997
|
+
Odometry: ros2py_odometry,
|
|
998
|
+
Path: ros2py_path,
|
|
999
|
+
JointState: ros2py_joint_state,
|
|
1000
|
+
LaserScan: ros2py_laserscan,
|
|
1001
|
+
Image: ros2py_image,
|
|
1002
|
+
CompressedImage: ros2py_compressed_image,
|
|
1003
|
+
Float64: ros2py_float64,
|
|
1004
|
+
Float64MultiArray: ros2py_float64_multiarray,
|
|
1005
|
+
Int32: ros2py_int32,
|
|
1006
|
+
Int32MultiArray: ros2py_int32_multiarray,
|
|
1007
|
+
UInt8MultiArray: ros2py_uint8_multiarray,
|
|
1008
|
+
# add more as needed
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
# Map ROS msg type -> (np.ndarray, **kwargs) -> msg
|
|
1012
|
+
PY2ROS_DEFAULT = {
|
|
1013
|
+
Pose:py2ros_pose,
|
|
1014
|
+
Twist: py2ros_twist,
|
|
1015
|
+
PoseStamped: py2ros_pose_stamped,
|
|
1016
|
+
TransformStamped: py2ros_transform_stamped,
|
|
1017
|
+
Wrench: py2ros_wrench,
|
|
1018
|
+
Accel: py2ros_accel,
|
|
1019
|
+
AccelStamped: py2ros_accel_stamped,
|
|
1020
|
+
Vector3Stamped: py2ros_vector3_stamped,
|
|
1021
|
+
Imu: py2ros_imu,
|
|
1022
|
+
Odometry: py2ros_odometry,
|
|
1023
|
+
Path: py2ros_path,
|
|
1024
|
+
JointState: py2ros_joint_state,
|
|
1025
|
+
LaserScan: py2ros_laserscan,
|
|
1026
|
+
Image: py2ros_image,
|
|
1027
|
+
CompressedImage: py2ros_compressed_image,
|
|
1028
|
+
Float64: py2ros_float64,
|
|
1029
|
+
Float64MultiArray: py2ros_float64_multiarray,
|
|
1030
|
+
Int32: py2ros_int32,
|
|
1031
|
+
Int32MultiArray: py2ros_int32_multiarray,
|
|
1032
|
+
UInt8MultiArray: py2ros_uint8_multiarray,
|
|
1033
|
+
# add more as needed
|
|
1034
|
+
}
|