A Python library for working with motion data in NumPy or PyTorch.
Project description
PyMotion: A Python Library for Motion Data
PyMotion is a Python library that provides various functions for manipulating and processing motion data in NumPy or PyTorch. It is designed to facilitate the development of neural networks for character animation.
Some features of PyMotion are:
- A comprehensive set of quaternion operations and conversions to other rotation representations, such as rotation matrix, axis-angle, euler, and 6D representation
- A dual quaternion representation for rigid displacements, which can help neural networks better understand poses, as proposed by Andreou et al. [2022] and later adopted by Ponton et al. [2023]
- A continuous 6D rotation representation, as introduced by Zhou et al. [2019]
- A BVH file reader and preprocessor for loading and transforming motion data
- Skeletal operations such as Forward Kinematics for computing global joint positions from local joint rotations
- [Beta] A plotly-based visualizer for debugging and visualizing character animation directly in Python
- [Beta] A blender visualizer for debugging and visualizing character animation
- NumPy and PyTorch implementations and tests for all functions
Contents
Installation
-
[Optional] Install PyTorch using Pip as instructed in their webpage.
-
Install PyMotion:
pip install upc-pymotion
- [Optional] Install Plotly and Dash for the visualizer:
pip install upc-pymotion[viewer]
Examples
Read and save a BVH file
import numpy as np
from pymotion.io.bvh import BVH
bvh = BVH()
bvh.load("test.bvh")
print(bvh.data["names"])
# Example Output: ['Hips', 'LeftHip', 'LeftKnee', 'LeftAnkle', 'LeftToe', 'RightHip', 'RightKnee', 'RightAnkle', 'RightToe', 'Chest', 'Chest3', 'Chest4', 'Neck', 'Head', 'LeftCollar', 'LeftShoulder', 'LeftElbow', 'LeftWrist', 'RightCollar', 'RightShoulder', 'RightElbow', 'RightWrist']
# Move root joint to (0, 0, 0)
local_rotations, local_positions, parents, offsets, end_sites, end_sites_parents = bvh.get_data()
local_positions[:, 0, :] = np.zeros((local_positions.shape[0], 3))
bvh.set_data(local_rotations, local_positions)
# Scale the skeleton
bvh.set_scale(0.75)
bvh.save("test_out.bvh")
Compute world positions and rotations from a BVH file
NumPy
from pymotion.io.bvh import BVH
from pymotion.ops.forward_kinematics import fk
bvh = BVH()
bvh.load("test.bvh")
local_rotations, local_positions, parents, offsets, end_sites, end_sites_parents = bvh.get_data()
global_positions = local_positions[:, 0, :] # root joint
pos, rotmats = fk(local_rotations, global_positions, offsets, parents)
PyTorch
from pymotion.io.bvh import BVH
from pymotion.ops.forward_kinematics_torch import fk
import torch
bvh = BVH()
bvh.load("test.bvh")
local_rotations, local_positions, parents, offsets, end_sites, end_sites_parents = bvh.get_data()
global_positions = local_positions[:, 0, :] # root joint
pos, rotmats = fk(
torch.from_numpy(local_rotations),
torch.from_numpy(global_positions),
torch.from_numpy(offsets),
torch.from_numpy(parents),
)
Quaternion conversion to other representations
NumPy
import pymotion.rotations.quat as quat
import numpy as np
angles = np.array([np.pi / 2, np.pi, np.pi / 4])[..., np.newaxis]
# angles.shape = [3, 1]
axes = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
# axes.shape = [3, 3]
q = quat.from_angle_axis(angles, axes)
rotmats = quat.to_matrix(q)
euler = quat.to_euler(q, np.array([["x", "y", "z"], ["z", "y", "x"], ["y", "z", "x"]]))
euler_degrees = np.degrees(euler)
scaled_axis = quat.to_scaled_angle_axis(q)
PyTorch
import pymotion.rotations.quat_torch as quat
import numpy as np
import torch
angles = torch.Tensor([torch.pi / 2, torch.pi, torch.pi / 4]).unsqueeze(-1)
# angles.shape = [3, 1]
axes = torch.Tensor([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
# axes.shape = [3, 3]
q = quat.from_angle_axis(angles, axes)
rotmats = quat.to_matrix(q)
euler = quat.to_euler(q, np.array([["x", "y", "z"], ["z", "y", "x"], ["y", "z", "x"]]))
euler_degrees = torch.rad2deg(euler)
scaled_axis = quat.to_scaled_angle_axis(q)
Root-centered dual quaternions from a BVH file
NumPy
from pymotion.io.bvh import BVH
import pymotion.ops.skeleton as sk
import numpy as np
bvh = BVH()
bvh.load("test.bvh")
local_rotations, local_positions, parents, offsets, end_sites, end_sites_parents = bvh.get_data()
root_dual_quats = sk.to_root_dual_quat(
local_rotations, local_positions[:, 0, :], parents, offsets
)
local_translations, local_rotations = sk.from_root_dual_quat(root_dual_quats, parents)
global_positions = local_translations[:, 0, :]
offsets = local_translations.copy()
offsets[:, 0, :] = np.zeros((offsets.shape[0], 3))
PyTorch
from pymotion.io.bvh import BVH
import pymotion.ops.skeleton_torch as sk
import torch
bvh = BVH()
bvh.load("test.bvh")
local_rotations, local_positions, parents, offsets, end_sites, end_sites_parents = bvh.get_data()
root_dual_quats = sk.to_root_dual_quat(
torch.from_numpy(local_rotations),
torch.from_numpy(local_positions[:, 0, :]),
torch.from_numpy(parents),
torch.from_numpy(offsets),
)
local_translations, local_rotations = sk.from_root_dual_quat(root_dual_quats, parents)
global_positions = local_translations[:, 0, :]
offsets = local_translations.clone()
offsets[:, 0, :] = torch.zeros((offsets.shape[0], 3))
6D representation from a BVH file
NumPy
from pymotion.io.bvh import BVH
import pymotion.rotations.ortho6d as sixd
bvh = BVH()
bvh.load("test.bvh")
local_rotations, _, _, _, _, _ = bvh.get_data()
continuous = sixd.from_quat(local_rotations)
local_rotations = sixd.to_quat(continuous)
PyTorch
from pymotion.io.bvh import BVH
import pymotion.rotations.ortho6d_torch as sixd
import torch
bvh = BVH()
bvh.load("test.bvh")
local_rotations, _, _, _, _, _ = bvh.get_data()
continuous = sixd.from_quat(torch.from_numpy(local_rotations))
local_rotations = sixd.to_quat(continuous)
Visualize motion in Python
from pymotion.render.viewer import Viewer
from pymotion.io.bvh import BVH
from pymotion.ops.forward_kinematics import fk
bvh = BVH()
bvh.load("test.bvh")
local_rotations, local_positions, parents, offsets, _, _ = bvh.get_data()
global_positions = local_positions[:, 0, :] # root joint
pos, rotmats = fk(local_rotations, global_positions, offsets, parents)
viewer = Viewer(use_reloader=True, xy_size=5)
viewer.add_skeleton(pos, parents)
# add additional info using add_sphere(...) and/or add_line(...), examples:
# viewer.add_sphere(sphere_pos, color="green")
# viewer.add_line(start_pos, end_pos, color="green")
viewer.add_floor()
viewer.run()
Visualize a pose in Blender
-
Open the Test Exitor window in Blender
-
Open the the file
blender/pymotion_blender.py
that can be found in this repository -
Run the script (Blender will freeze)
-
Run the following Python code in a seperate environment:
from pymotion.io.bvh import BVH
from pymotion.ops.forward_kinematics import fk
from pymotion.visualizer.blender import BlenderConnection
bvh = BVH()
bvh.load("test.bvh")
local_rotations, local_positions, parents, offsets, end_sites, end_sites_parents = bvh.get_data()
global_positions = local_positions[:, 0, :] # root joint
pos, _ = fk(local_rotations, global_positions, offsets, parents)
# Render points
frame = 0
conn = BlenderConnection("127.0.0.1", 2222)
conn.render_points(pos[0])
conn.close()
- Press ESC key in Blender to stop the server
Roadmap
This repository is authored and maintained by Jose Luis Ponton as part of his Ph.D.
Features will be added when new operations or rotation representations are needed in the development of research projects. Here it is a list of possible features and improvements for the future:
- Extend documentation and add examples in the description of each function.
- Include new animation importers such as FBX
- Improve the usability of the Blender visualization workflow
- Include useful operations for data augmentation such as animation mirroring
- Create an Inverse Kinematics module
License
This work is licensed under the MIT license. Please, see the LICENSE for further details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for upc_pymotion-0.1.2-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7511abef9d5a0271ad3f23611be4509341dc938265783306bee581ccb7c0e4e4 |
|
MD5 | 9fcb91e68a86348877cabb32dc165932 |
|
BLAKE2b-256 | 7028cfffdcc8ef9687ea57c4ea5d7c997e4bc83b201bd6deb4e298681259bfb7 |