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.
@@ -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
+ }