# Inspired by # - https://github.com/anindita127/Complextext2animation/blob/main/src/utils/visualization.py # - https://github.com/facebookresearch/QuaterNet/blob/main/common/visualization.py from typing import List, Tuple import numpy as np from mGPT.utils.joints import mmm_kinematic_tree, mmm_to_smplh_scaling_factor mmm_colors = ['black', 'magenta', 'red', 'green', 'blue'] def init_axis(fig, title, radius=1.5, dist=10): ax = fig.add_subplot(1, 1, 1, projection='3d') ax.view_init(elev=20., azim=-60) fact = 2 ax.set_xlim3d([-radius / fact, radius / fact]) ax.set_ylim3d([-radius / fact, radius / fact]) ax.set_zlim3d([0, radius]) ax.set_aspect('auto') ax.set_xticklabels([]) ax.set_yticklabels([]) ax.set_zticklabels([]) ax.set_axis_off() ax.dist = dist ax.grid(b=False) ax.set_title(title, loc='center', wrap=True) return ax def plot_floor(ax, minx, maxx, miny, maxy, minz): from mpl_toolkits.mplot3d.art3d import Poly3DCollection # Plot a plane XZ verts = [ [minx, miny, minz], [minx, maxy, minz], [maxx, maxy, minz], [maxx, miny, minz] ] xz_plane = Poly3DCollection([verts], zorder=1) xz_plane.set_facecolor((0.5, 0.5, 0.5, 1)) ax.add_collection3d(xz_plane) # Plot a bigger square plane XZ radius = max((maxx - minx), (maxy - miny)) # center +- radius minx_all = (maxx + minx) / 2 - radius maxx_all = (maxx + minx) / 2 + radius miny_all = (maxy + miny) / 2 - radius maxy_all = (maxy + miny) / 2 + radius verts = [ [minx_all, miny_all, minz], [minx_all, maxy_all, minz], [maxx_all, maxy_all, minz], [maxx_all, miny_all, minz] ] xz_plane = Poly3DCollection([verts], zorder=1) xz_plane.set_facecolor((0.5, 0.5, 0.5, 0.5)) ax.add_collection3d(xz_plane) return ax def update_camera(ax, root, radius=1.5): fact = 2 ax.set_xlim3d([-radius / fact + root[0], radius / fact + root[0]]) ax.set_ylim3d([-radius / fact + root[1], radius / fact + root[1]]) def render_animation(joints: np.ndarray, output: str = "notebook", title: str = "", fps: float = 12.5, kinematic_tree: List[List[int]] = mmm_kinematic_tree, colors: List[str] = mmm_colors, figsize: Tuple[int] = (4, 4), fontsize: int = 15): import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation import matplotlib.patheffects as pe plt.rcParams.update({'font.size': fontsize}) # Z is gravity here x, y, z = 0, 1, 2 # Convert mmm joints for visualization # into smpl-h "scale" and axis joints = joints.copy()[..., [2, 0, 1]] * mmm_to_smplh_scaling_factor # Create a figure and initialize 3d plot fig = plt.figure(figsize=figsize) ax = init_axis(fig, title) # Create spline line trajectory = joints[:, 0, [x, y]] avg_segment_length = np.mean(np.linalg.norm(np.diff(trajectory, axis=0), axis=1)) + 1e-3 draw_offset = int(25 / avg_segment_length) spline_line, = ax.plot(*trajectory.T, zorder=10, color="white") # Create a floor minx, miny, _ = joints.min(axis=(0, 1)) maxx, maxy, _ = joints.max(axis=(0, 1)) plot_floor(ax, minx, maxx, miny, maxy, 0) # Put the character on the floor height_offset = np.min(joints[:, :, z]) # Min height joints = joints.copy() joints[:, :, z] -= height_offset # Initialization for redrawing lines = [] initialized = False def update(frame): nonlocal initialized skeleton = joints[frame] root = skeleton[0] update_camera(ax, root) for index, (chain, color) in enumerate(zip(reversed(kinematic_tree), reversed(colors))): if not initialized: lines.append(ax.plot(skeleton[chain, x], skeleton[chain, y], skeleton[chain, z], linewidth=8.0, color=color, zorder=20, path_effects=[pe.SimpleLineShadow(), pe.Normal()])) else: lines[index][0].set_xdata(skeleton[chain, x]) lines[index][0].set_ydata(skeleton[chain, y]) lines[index][0].set_3d_properties(skeleton[chain, z]) left = max(frame - draw_offset, 0) right = min(frame + draw_offset, trajectory.shape[0]) spline_line.set_xdata(trajectory[left:right, 0]) spline_line.set_ydata(trajectory[left:right, 1]) spline_line.set_3d_properties(np.zeros_like(trajectory[left:right, 0])) initialized = True fig.tight_layout() frames = joints.shape[0] anim = FuncAnimation(fig, update, frames=frames, interval=1000 / fps, repeat=False) if output == "notebook": from IPython.display import HTML HTML(anim.to_jshtml()) else: anim.save(output, writer='ffmpeg', fps=fps) plt.close()