2
2
import jax .numpy as jnp
3
3
from jax import jit , vmap
4
4
5
+ jax .config .update ("jax_enable_x64" , True ) # double precision
6
+
5
7
import jsrm
6
8
from jsrm .systems .class_planar_hsa import PlanarHSA
7
9
from jsrm .parameters .hsa_params import PARAMS_FPU_CONTROL , PARAMS_FPU_HYSTERESIS_CONTROL
8
10
9
- from typing import Callable
10
11
from jax import Array
11
12
12
13
import numpy as onp
13
14
14
15
from diffrax import Tsit5
16
+ import matplotlib .pyplot as plt
17
+
15
18
import cv2 # importing cv2
16
19
17
20
from pathlib import Path
18
21
19
- jax .config .update ("jax_enable_x64" , True ) # double precision
22
+ from os import PathLike
23
+
20
24
jnp .set_printoptions (
21
25
threshold = jnp .inf ,
22
26
linewidth = jnp .inf ,
23
27
formatter = {"float_kind" : lambda x : "0" if x == 0 else f"{ x :.2e} " },
24
28
)
25
29
30
+ COLORS = {
31
+ "base" : "white" ,
32
+ "backbone" : "black" ,
33
+ "rod" : "blue" ,
34
+ "platform" : "green" ,
35
+ }
26
36
27
37
def draw_robot (
28
38
robot : PlanarHSA ,
29
39
q : Array ,
30
- width : int = 700 ,
31
- height : int = 700 ,
40
+ width : int = 700 ,
41
+ height : int = 700 ,
32
42
num_points : int = 50 ,
43
+ show : bool = False ,
33
44
) -> onp .ndarray :
34
45
"""
35
46
Draw the robot in OpenCV.
36
47
Args:
37
- robot:
48
+ robot: PlanarHSA instance
38
49
q: configuration as shape (3, )
39
50
width: image width
40
51
height: image height
@@ -43,7 +54,7 @@ def draw_robot(
43
54
# plotting in OpenCV
44
55
h , w = height , width # img height and width
45
56
ppm = h / (
46
- 2.0 * jnp .sum (robot .params [ " lpc" ] + robot .params [ "l" ] + robot .params [ " ldc" ] )
57
+ 2.0 * jnp .sum (robot .lpc + robot .L + robot .ldc )
47
58
) # pixel per meter
48
59
base_color = (0 , 0 , 0 ) # black base color in BGR
49
60
backbone_color = (255 , 0 , 0 ) # blue robot color in BGR
@@ -52,27 +63,33 @@ def draw_robot(
52
63
53
64
batched_forward_kinematics_virtual_backbone_fn = vmap (
54
65
robot .forward_kinematics_virtual_backbone_fn ,
55
- in_axes = (None , 0 ), out_axes = - 1
66
+ in_axes = (None , 0 ),
67
+ out_axes = - 1
56
68
)
57
69
batched_forward_kinematics_rod_fn = vmap (
58
70
robot .forward_kinematics_rod_fn ,
59
- in_axes = (None , 0 , None ), out_axes = - 1
71
+ in_axes = (None , 0 , None ),
72
+ out_axes = - 1
60
73
)
61
74
batched_forward_kinematics_platform_fn = vmap (
62
75
robot .forward_kinematics_platform_fn ,
63
- in_axes = (None , 0 ), out_axes = 0
76
+ in_axes = (None , 0 ),
77
+ out_axes = 0
64
78
)
65
79
66
- L_max = jnp .sum (robot .params ["l" ]) # total length of the robot
67
80
# we use for plotting N points along the length of the robot
68
- s_ps = jnp .linspace (0 , L_max , num_points )
81
+ s_ps = jnp .linspace (0 , robot . Lmax , num_points )
69
82
70
83
# poses along the robot of shape (3, N)
71
- chiv_ps = batched_forward_kinematics_virtual_backbone_fn (q , s_ps ) # poses of virtual backbone
84
+ chiv_ps = batched_forward_kinematics_virtual_backbone_fn (
85
+ q , s_ps
86
+ ) # poses of virtual backbone
72
87
chiL_ps = batched_forward_kinematics_rod_fn (q , s_ps , 0 ) # poses of left rod
73
88
chiR_ps = batched_forward_kinematics_rod_fn (q , s_ps , 1 ) # poses of left rod
74
89
# poses of the platforms
75
- chip_ps = batched_forward_kinematics_platform_fn (q , jnp .arange (0 , robot .num_segments ))
90
+ chip_ps = batched_forward_kinematics_platform_fn (
91
+ q , jnp .arange (0 , robot .num_segments )
92
+ )
76
93
77
94
img = 255 * onp .ones ((w , h , 3 ), dtype = jnp .uint8 ) # initialize background to white
78
95
uv_robot_origin = onp .array (
@@ -106,15 +123,15 @@ def chi2u(chi: Array) -> Array:
106
123
# add the first point of the proximal cap and the last point of the distal cap
107
124
chiv_ps = jnp .concatenate (
108
125
[
109
- (chiv_ps [:, 0 ] - jnp .array ([0.0 , 0.0 , params [ " lpc" ] [0 ]])).reshape (3 , 1 ),
126
+ (chiv_ps [:, 0 ] - jnp .array ([0.0 , 0.0 , robot . lpc [0 ]])).reshape (3 , 1 ),
110
127
chiv_ps ,
111
128
(
112
129
chiv_ps [:, - 1 ]
113
130
+ jnp .array (
114
131
[
115
132
chiv_ps [2 , - 1 ],
116
- - jnp .sin (chiv_ps [2 , - 1 ]) * params [ " ldc" ] [- 1 ],
117
- jnp .cos (chiv_ps [2 , - 1 ]) * params [ " ldc" ] [- 1 ],
133
+ - jnp .sin (chiv_ps [2 , - 1 ]) * robot . ldc [- 1 ],
134
+ jnp .cos (chiv_ps [2 , - 1 ]) * robot . ldc [- 1 ],
118
135
]
119
136
)
120
137
).reshape (3 , 1 ),
@@ -130,15 +147,15 @@ def chi2u(chi: Array) -> Array:
130
147
# add the first point of the proximal cap and the last point of the distal cap
131
148
chiL_ps = jnp .concatenate (
132
149
[
133
- (chiL_ps [:, 0 ] - jnp .array ([0.0 , 0.0 , params [ " lpc" ] [0 ]])).reshape (3 , 1 ),
150
+ (chiL_ps [:, 0 ] - jnp .array ([0.0 , 0.0 , robot . lpc [0 ]])).reshape (3 , 1 ),
134
151
chiL_ps ,
135
152
(
136
153
chiL_ps [:, - 1 ]
137
154
+ jnp .array (
138
155
[
139
156
chiL_ps [2 , - 1 ],
140
- - jnp .sin (chiL_ps [2 , - 1 ]) * params [ " ldc" ] [- 1 ],
141
- jnp .cos (chiL_ps [2 , - 1 ]) * params [ " ldc" ] [- 1 ],
157
+ - jnp .sin (chiL_ps [2 , - 1 ]) * robot . ldc [- 1 ],
158
+ jnp .cos (chiL_ps [2 , - 1 ]) * robot . ldc [- 1 ],
142
159
]
143
160
)
144
161
).reshape (3 , 1 ),
@@ -152,20 +169,20 @@ def chi2u(chi: Array) -> Array:
152
169
isClosed = False ,
153
170
color = rod_color ,
154
171
thickness = 10 ,
155
- # thickness=2*int(ppm * params["rout"].mean(axis=0)[0])
172
+ # thickness=2*int(ppm * robot. params["rout"].mean(axis=0)[0])
156
173
)
157
174
# add the first point of the proximal cap and the last point of the distal cap
158
175
chiR_ps = jnp .concatenate (
159
176
[
160
- (chiR_ps [:, 0 ] - jnp .array ([0.0 , 0.0 , params [ " lpc" ] [0 ]])).reshape (3 , 1 ),
177
+ (chiR_ps [:, 0 ] - jnp .array ([0.0 , 0.0 , robot . lpc [0 ]])).reshape (3 , 1 ),
161
178
chiR_ps ,
162
179
(
163
180
chiR_ps [:, - 1 ]
164
181
+ jnp .array (
165
182
[
166
183
chiR_ps [2 , - 1 ],
167
- - jnp .sin (chiR_ps [2 , - 1 ]) * params [ " ldc" ] [- 1 ],
168
- jnp .cos (chiR_ps [2 , - 1 ]) * params [ " ldc" ] [- 1 ],
184
+ - jnp .sin (chiR_ps [2 , - 1 ]) * robot . ldc [- 1 ],
185
+ jnp .cos (chiR_ps [2 , - 1 ]) * robot . ldc [- 1 ],
169
186
]
170
187
)
171
188
).reshape (3 , 1 ),
@@ -174,38 +191,43 @@ def chi2u(chi: Array) -> Array:
174
191
)
175
192
curve_rod_right = onp .array (batched_chi2u (chiR_ps ))
176
193
cv2 .polylines (img , [curve_rod_right ], isClosed = False , color = rod_color , thickness = 10 )
177
-
194
+
178
195
# draw the platform
179
196
for i in range (chip_ps .shape [0 ]):
180
197
# iterate over the platforms
181
198
platform_R = jnp .array (
182
199
[
183
- [jnp .cos (chip_ps [i , 0 ]), - jnp .sin (chip_ps [i , 0 ])],
184
- [jnp .sin (chip_ps [i , 0 ]), jnp .cos (chip_ps [i , 0 ])],
200
+ [1 , 0 , 0 ],
201
+ [0 ,jnp .cos (chip_ps [i , 0 ]), - jnp .sin (chip_ps [i , 0 ])],
202
+ [0 ,jnp .sin (chip_ps [i , 0 ]), jnp .cos (chip_ps [i , 0 ])],
185
203
]
186
204
) # rotation matrix for the platform
187
- platform_llc = chip_ps [i , 1 :] + platform_R @ jnp .array (
188
- [
189
- - params ["pcudim" ][i , 1 ] / 2 , # go half the width to the left
190
- - params ["pcudim" ][i , 2 ] / 2 , # go half the height down
205
+ platform_llc = chip_ps [i , :] + platform_R @ jnp .array (
206
+ [
207
+ 0 ,
208
+ - robot .pcudim [i , 0 ] / 2 , # go half the width to the left
209
+ - robot .pcudim [i , 1 ] / 2 , # go half the height down
191
210
]
192
211
) # lower left corner of the platform
193
- platform_ulc = chip_ps [i , 1 :] + platform_R @ jnp .array (
212
+ platform_ulc = chip_ps [i , :] + platform_R @ jnp .array (
194
213
[
195
- - params ["pcudim" ][i , 1 ] / 2 , # go half the width to the left
196
- + params ["pcudim" ][i , 2 ] / 2 , # go half the height down
214
+ 0 ,
215
+ - robot .pcudim [i , 0 ] / 2 , # go half the width to the left
216
+ + robot .pcudim [i , 1 ] / 2 , # go half the height down
197
217
]
198
- ) # upper left corner of the platform
199
- platform_urc = chip_ps [i , 1 :] + platform_R @ jnp .array (
218
+ ) # upper left corner of the platform
219
+ platform_urc = chip_ps [i , :] + platform_R @ jnp .array (
200
220
[
201
- + params ["pcudim" ][i , 1 ] / 2 , # go half the width to the left
202
- + params ["pcudim" ][i , 2 ] / 2 , # go half the height down
221
+ 0 ,
222
+ + robot .pcudim [i , 0 ] / 2 , # go half the width to the left
223
+ + robot .pcudim [i , 1 ] / 2 , # go half the height down
203
224
]
204
225
) # upper right corner of the platform
205
- platform_lrc = chip_ps [i , 1 :] + platform_R @ jnp .array (
226
+ platform_lrc = chip_ps [i , :] + platform_R @ jnp .array (
206
227
[
207
- + params ["pcudim" ][i , 1 ] / 2 , # go half the width to the left
208
- - params ["pcudim" ][i , 2 ] / 2 , # go half the height down
228
+ 0 ,
229
+ + robot .pcudim [i , 0 ] / 2 , # go half the width to the left
230
+ - robot .pcudim [i , 1 ] / 2 , # go half the height down
209
231
]
210
232
) # lower right corner of the platform
211
233
platform_curve = jnp .stack (
@@ -216,9 +238,59 @@ def chi2u(chi: Array) -> Array:
216
238
cv2 .fillPoly (
217
239
img , [onp .array (batched_chi2u (platform_curve ))], color = platform_color
218
240
)
241
+
242
+ if show :
243
+ win = "Planar HSA"
244
+ # fenêtre redimensionnable (utile sur macOS/Linux/HiDPI)
245
+ cv2 .namedWindow (win , cv2 .WINDOW_NORMAL )
246
+ cv2 .imshow (win , img )
247
+ # attend jusqu'à une touche (ferme si on appuie sur ESC ou 'q')
248
+ key = cv2 .waitKey (0 ) & 0xFF
249
+ if key in (27 , ord ('q' )):
250
+ cv2 .destroyWindow (win )
219
251
220
252
return img
221
253
254
+ def animate_robot ( #TODO: correct this implementation
255
+ robot : PlanarHSA ,
256
+ filepath : PathLike ,
257
+ video_ts : Array ,
258
+ q_ts : Array ,
259
+ video_width : int = 700 ,
260
+ video_height : int = 700 ,
261
+ ):
262
+ """
263
+ Animate the robot and save the video.
264
+ Args:
265
+ filepath: path to save the video
266
+ video_ts: time stamps of the video
267
+ q_ts: configuration time series of shape (N, 3)
268
+ video_width: video width
269
+ video_height: video height
270
+ """
271
+ # create video
272
+ fourcc = cv2 .VideoWriter_fourcc (* "mp4v" )
273
+ filepath .parent .mkdir (parents = True , exist_ok = True )
274
+ video_dt = jnp .mean (video_ts [1 :] - video_ts [:- 1 ]).item ()
275
+ print (f"Rendering video with dt={ video_dt } and { video_ts .shape [0 ]} frames" )
276
+ video = cv2 .VideoWriter (
277
+ str (filepath ),
278
+ fourcc ,
279
+ 1 / video_dt , # fps
280
+ (video_width , video_height ),
281
+ )
282
+
283
+ for time_idx , t in enumerate (video_ts ):
284
+ q = q_ts [time_idx ]
285
+ img = draw_robot (
286
+ robot ,
287
+ q ,
288
+ video_width ,
289
+ video_height ,
290
+ )
291
+ video .write (img )
292
+
293
+ video .release ()
222
294
223
295
if __name__ == "__main__" :
224
296
num_segments = 1
@@ -250,6 +322,7 @@ def chi2u(chi: Array) -> Array:
250
322
strain_selector = strain_selector ,
251
323
consider_hysteresis = consider_hysteresis ,
252
324
)
325
+ print (f"Planar HSA with { num_segments } segments and { num_rods_per_segment } rods per segment initialized." )
253
326
254
327
# =====================================================
255
328
# Simulation upon time
@@ -266,8 +339,15 @@ def chi2u(chi: Array) -> Array:
266
339
img = draw_robot (
267
340
robot ,
268
341
q = q0 ,
342
+ show = False ,
269
343
)
270
344
345
+ win = "Planar HSA"
346
+ cv2 .namedWindow (win , cv2 .WINDOW_NORMAL )
347
+ cv2 .imshow (win , img )
348
+ key = cv2 .waitKey (0 ) & 0xFF
349
+ if key in (27 , ord ('q' )):
350
+ cv2 .destroyWindow (win )
271
351
272
352
# Simulation time parameters
273
353
t0 = 0.0
@@ -276,4 +356,49 @@ def chi2u(chi: Array) -> Array:
276
356
skip_step = 100 # how many time steps to skip in between video frames
277
357
278
358
# Solver
279
- solver = Tsit5 ()
359
+ solver = Tsit5 ()
360
+
361
+ actuation_args = (phi , None , True ) # actuation arguments
362
+
363
+ ts , q_ts , q_d_ts = robot .resolve_upon_time (
364
+ q0 = q0 ,
365
+ qd0 = qd0 ,
366
+ u0 = phi ,
367
+ t0 = t0 ,
368
+ t1 = t1 ,
369
+ dt = dt ,
370
+ skip_steps = skip_step ,
371
+ solver = solver ,
372
+ max_steps = None ,
373
+ )
374
+
375
+ # create video
376
+ video_width , video_height = 700 , 700 # img height and width
377
+ video_path = Path (__file__ ).parent / "videos" / "planar_hsa.mp4"
378
+ video_path .parent .mkdir (parents = True , exist_ok = True )
379
+
380
+ animate_robot (
381
+ robot ,
382
+ video_path ,
383
+ video_ts = ts ,
384
+ q_ts = q_ts ,
385
+ video_width = video_width ,
386
+ video_height = video_height ,
387
+ )
388
+ print (f"Video saved at { video_path } " )
389
+
390
+ # Lecture et affichage de la vidéo générée
391
+ cap = cv2 .VideoCapture (str (video_path ))
392
+ if not cap .isOpened ():
393
+ print (f"Erreur lors de l'ouverture de la vidéo { video_path } " )
394
+ else :
395
+ while True :
396
+ ret , frame = cap .read ()
397
+ if not ret :
398
+ break
399
+ cv2 .imshow ("Animation Planar HSA" , frame )
400
+ key = cv2 .waitKey (int (1000 / (1 / dt / skip_step )))
401
+ if key in (27 , ord ('q' )):
402
+ break
403
+ cap .release ()
404
+ cv2 .destroyAllWindows ()
0 commit comments