3.2 Experiment: Transitions#
This notebook is an appendix to the 3_generating_gaits.ipynb notebook. It contains some experiments with transitions in and out of gaits.
The next step is configuring matplotlib backend. Widget backend allows to interact with the plots in the notebook and is supported in Google Colab and VSCode. SVG format is used for the plots to make them look good in the hosted sphinx documentation.
This gives a nice forward locomotive gait. However some changes are needed to the generated gait. Right now it starts with legs on the ground, but with maxed out X offsets. To mitigate this we need to introduce a transition stage that will take legs from whatever position they are in to the starting position.
A good starting point for the transition is a 0.25 phase mark where all legs have zero offsets in X axis, however group A is lifted up. In order to start from all the legs on the ground we need to compress Z phase to quarter of the original cycle.
So here is the plan for transition stage:
It runs for 0.25 of the phase
We start with X cycle at 0.25
We start Z cycle 0, but compress first 0.5 of it to 0.25.
At 0.5 both cycles sync up and cycle continues till 1.0
Then we start the full cycle.
Transition out of gait is similar, but starts at 0 and ends at 0.25 or X and Z cycles is compressed to 0.25.
Below is the implementation of the transition. This kind of code works for the animation and is suitable for tripod gait, but with increase of gait complexity and with joystick style controls it would be impossible to implement it this way. We are going to explore a different approach later in this notebook.
from drqp_brain.parametric_gait_generator import GaitType, ParametricGaitGenerator
from drqp_kinematics.models import HexapodModel
hexapod = HexapodModel()
hexapod.forward_kinematics(0, -25, 110)
gait_gen = ParametricGaitGenerator(step_length=120, step_height=50)
gait_gen.current_gait = GaitType.tripod
from drqp_kinematics.geometry import Point3D
import numpy as np
from plotting import animate_plot, plot_hexapod, update_hexapod_plot
def animate_hexapod_gait_with_transitions(
hexapod: HexapodModel,
gaits_gen,
interactive=False,
skip=False,
total_steps=60,
interval=16,
view_elev=7.0,
view_azim=-112,
repeat=2,
):
if skip:
return
transition_time_in = 0.75
transition_steps_in = int(total_steps * transition_time_in)
transition_steps_intro = int(total_steps * 0.25)
transition_steps_rest = int(transition_steps_in - transition_steps_intro)
phase_in_x_steps = np.concatenate(
(
np.linspace(0.25, 0.5, transition_steps_intro),
np.linspace(0.5, 1.0, transition_steps_rest),
)
)
phase_in_z_steps = np.concatenate(
(
np.linspace(0.0, 0.5, transition_steps_intro),
np.linspace(0.5, 1.0, transition_steps_rest),
)
)
transition_time_out = 0.25
transition_steps_out = int(total_steps * transition_time_out)
phase_out_x_steps = np.linspace(0.0, 0.25, transition_steps_out + 1)
phase_out_z_steps = np.linspace(0.0, 0.5, transition_steps_out + 1)
transition_steps_end = total_steps * repeat + transition_steps_in
total_frames = total_steps * repeat + transition_steps_in + transition_steps_out
leg_tips = [leg.tibia_end.copy() for leg in hexapod.legs]
def set_pose(step):
if step < transition_steps_in:
phase_x = phase_in_x_steps[step]
phase_z = phase_in_z_steps[step]
offsets = {}
for leg in hexapod.legs:
off = gaits_gen.get_offsets_at_phase_for_leg(leg.label, phase_x)
offsets[leg.label] = Point3D([off.x, 0, 0])
for leg in hexapod.legs:
off = gaits_gen.get_offsets_at_phase_for_leg(leg.label, phase_z)
offsets[leg.label] += Point3D([0, 0, off.z])
for leg, leg_tip in zip(hexapod.legs, leg_tips):
leg.move_to(leg_tip + offsets[leg.label])
elif step < transition_steps_end:
step = step - transition_steps_in
step = step % total_steps # handle repeats
phase = step / total_steps # interpolation phase
for leg, leg_tip in zip(hexapod.legs, leg_tips):
offsets = gaits_gen.get_offsets_at_phase_for_leg(leg.label, phase)
leg.move_to(leg_tip + offsets)
else:
end_step = step - transition_steps_end
phase_x = phase_out_x_steps[end_step]
phase_z = phase_out_z_steps[end_step]
offsets = {}
for leg in hexapod.legs:
off = gaits_gen.get_offsets_at_phase_for_leg(leg.label, phase_x)
offsets[leg.label] = Point3D([off.x, 0, 0])
for leg in hexapod.legs:
off = gaits_gen.get_offsets_at_phase_for_leg(leg.label, phase_z)
offsets[leg.label] += Point3D([0, 0, off.z])
for leg, leg_tip in zip(hexapod.legs, leg_tips):
leg.move_to(leg_tip + offsets[leg.label])
fig, ax, plot_data = plot_hexapod(hexapod)
ax.view_init(elev=view_elev, azim=view_azim)
def update(frame=0):
set_pose(frame)
update_hexapod_plot(hexapod, plot_data)
fig.canvas.draw_idle()
animate_plot(
fig,
update,
_interactive=interactive,
_frames=total_frames,
_interval=interval,
)
hexapod = HexapodModel()
hexapod.forward_kinematics(0, -25, 110)
anim = animate_hexapod_gait_with_transitions(
hexapod, gait_gen, interactive=True, skip=False, view_elev=25
)
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/drqp/checkouts/latest/.venv/lib/python3.12/site-packages/IPython/core/formatters.py:406, in BaseFormatter.__call__(self, obj)
404 method = get_real_method(obj, self.print_method)
405 if method is not None:
--> 406 return method()
407 return None
408 else:
File ~/checkouts/readthedocs.org/user_builds/drqp/checkouts/latest/.venv/lib/python3.12/site-packages/matplotlib/animation.py:1385, in Animation._repr_html_(self)
1383 fmt = mpl.rcParams['animation.html']
1384 if fmt == 'html5':
-> 1385 return self.to_html5_video()
1386 elif fmt == 'jshtml':
1387 return self.to_jshtml()
File ~/checkouts/readthedocs.org/user_builds/drqp/checkouts/latest/.venv/lib/python3.12/site-packages/matplotlib/animation.py:1302, in Animation.to_html5_video(self, embed_limit)
1299 path = Path(tmpdir, "temp.m4v")
1300 # We create a writer manually so that we can get the
1301 # appropriate size for the tag
-> 1302 Writer = writers[mpl.rcParams['animation.writer']]
1303 writer = Writer(codec='h264',
1304 bitrate=mpl.rcParams['animation.bitrate'],
1305 fps=1000. / self._interval)
1306 self.save(str(path), writer=writer)
File ~/checkouts/readthedocs.org/user_builds/drqp/checkouts/latest/.venv/lib/python3.12/site-packages/matplotlib/animation.py:121, in MovieWriterRegistry.__getitem__(self, name)
119 if self.is_available(name):
120 return self._registered[name]
--> 121 raise RuntimeError(f"Requested MovieWriter ({name}) not available")
RuntimeError: Requested MovieWriter (ffmpeg) not available
<matplotlib.animation.FuncAnimation at 0x7392ff387d70>