Coder Social home page Coder Social logo

wasserwecken / bvhio Goto Github PK

View Code? Open in Web Editor NEW
28.0 4.0 4.0 2.99 MB

Read, write, edit and create .bvh files with hierarchical 3D transforms

License: MIT License

Python 80.51% Jupyter Notebook 19.49%
animation research armature python bvh-builder glm matrix opengl parser pypi

bvhio's Introduction

PyPI version PyPI download month License: MIT Build Main Build Preview

bvhio

Lightweight libary for reading, editing and creating Biovision .bvh files. Deserializes files into a hierarchical spatial structure like transforms in Unity or Unreal.

Data for each joint is provided in local and world space and does support modifing the hierarchy itself without losing the keyframe data. The spatial structure does also allow for editing the motion or rest pose data. This libary supports also deserializing and serialising .bvh files into a simplified structure that represents the key data from the file.

Install

pip install bvhio

Why and intention

This libary is a side product of my master thesis, in order to extract conveniently local and world data features from a humanoid skeleton hierarchy. I could not find any libary that could do that, without bloat or the features I required for extraction or modification.

Notes

  • The package spatial-transform is used as base object for joints and provides the most properties and methods.
  • The package PyGLM is used for matrix, quaternion and vector calculations.
  • Same coordination space as openGL and GLM is used, which is right-handed, where Y+ is up and Z- is forward.
  • Positive rotations are counter clockwise when viewed from the origin looking in the positive direction.

Features

  • Read/Write/Edit
    • Read and write .bvh files as simplified structure (BvhJoint).
    • Read and write .bvh files as transform hierarchy (Joint).
    • Animation data can be modified with both methods.
    • The transform hierarchy allows for easy modifications of rest and motion data.
  • Animation
    • Supports modifing keyframe, rest positon and final pose data.
    • Supports joint special modifications, like changing the joint-roll
    • Keyframes are stored in local space and as difference to the rest pose.
    • Keyframes support Position, Rotation and Scale.
  • Python
    • Every method is documented in code with docstrings.
    • Every method has type hinting.
    • (Fluent Interface) design.

Examples

Read bvh as simple structure and modify channels

import bvhio
# Loads the file into a deserialized tree structure.
bvh = bvhio.readAsBvh('bvhio/tests/example.bvh')

# Iterate through joints for changes
for joint, index, depth in bvh.Root.layout():

    # Keep positons for the hip
    joint.Channels = ['Xposition', 'Yposition', 'Zposition'] if joint.Name == "Hips" else []

    # Set order for euler rotations
    # Bvhio will calculate the correct euler angles.
    # Partially given channels are also uspported.
    joint.Channels.extend(['Zrotation', 'Yrotation', 'Xrotation'])

# Stores the modified bvh
bvhio.writeBvh('test.bvh', bvh, percision=6)

Read bvh as transform hierarchy

import bvhio

# Reads the file into a transform tree structure and converts all data to build proper local and world spaces.
# This structure allows for extensive read of properties and spaces and does also support modifications of the animation.
root = bvhio.readAsHierarchy('bvhio/tests/example.bvh')
root.printTree()

# load rest pose and print data
root.loadRestPose(recursive=True)
print('\nRest pose position and Y-direction of each joint in world space ')
for joint, index, depth in root.layout():
    print(f'{joint.PositionWorld} {joint.UpWorld} {joint.Name}')

# --------------------------- OUTPUT ---------------------------
# Hips
# +- Chest
# |  +- Neck
# |  |  +- Head
# |  +- LeftCollar
# |  |  +- LeftUpArm
# |  |     +- LeftLowArm
# |  |        +- LeftHand
# |  +- RightCollar
# |     +- RightUpArm
# |        +- RightLowArm
# |           +- RightHand
# +- LeftUpLeg
# |  +- LeftLowLeg
# |     +- LeftFoot
# +- RightUpLeg
#    +- RightLowLeg
#       +- RightFoot

# Rest pose position and Y-direction of each joint in world space
# vec3(            0,            0,            0 ) vec3(            0,            1,            0 ) Hips
# vec3(            0,         5.21,            0 ) vec3(            0,     0.997333,    0.0729792 ) Chest
# vec3(            0,        23.86,  1.19209e-07 ) vec3(            0,            1,            0 ) Neck
# vec3(            0,        29.31,  1.19209e-07 ) vec3(            0,            1,            0 ) Head
# vec3(         1.12,        21.44,         1.87 ) vec3(            1,  5.96046e-08,            0 ) LeftCollar
# vec3(         6.66,        21.44,         1.87 ) vec3(            0,           -1,            0 ) LeftUpArm
# vec3(         6.66,         9.48,         1.87 ) vec3(            0,           -1,            0 ) LeftLowArm
# vec3(         6.66,    -0.450002,         1.87 ) vec3(            0,           -1,            0 ) LeftHand
# vec3(        -1.12,        21.44,         1.87 ) vec3(           -1,  5.96046e-08,            0 ) RightCollar
# vec3(        -7.19,        21.44,         1.87 ) vec3(            0,           -1,            0 ) RightUpArm
# vec3(        -7.19,         9.62,         1.87 ) vec3(            0,           -1,            0 ) RightLowArm
# vec3(        -7.19,        -1.03,         1.87 ) vec3(            0,           -1,            0 ) RightHand
# vec3(         3.91,            0,            0 ) vec3(            0,           -1,            0 ) LeftUpLeg
# vec3(         3.91,       -18.34,            0 ) vec3(            0,           -1,            0 ) LeftLowLeg
# vec3(         3.91,       -35.71,            0 ) vec3(            0,           -1,            0 ) LeftFoot
# vec3(        -3.91,            0,            0 ) vec3(            0,           -1,            0 ) RightUpLeg
# vec3(        -3.91,       -17.63,            0 ) vec3(            0,           -1,            0 ) RightLowLeg
# vec3(        -3.91,       -34.77,            0 ) vec3(            0,           -1,            0 ) RightFoot

Bvh deserialized properties and methods

import bvhio

# Reads the file into a deserialized tree structure.
bvh = bvhio.readAsBvh('bvhio/tests/example.bvh')
print(f'Root: {bvh.Root}')
print(f'Frames: {bvh.FrameCount}')
print(f'Frame time: {bvh.FrameTime}')

# Properties of joints in the bvh tree structure.
bvh.Root.Name
bvh.Root.Offset
bvh.Root.Channels
bvh.Root.EndSite
bvh.Root.Keyframes
bvh.Root.Children

# Calculated properties that depend on the hierarchy.
bvh.Root.getRotation()
bvh.Root.getLength()
bvh.Root.getTip()

# --------------------------- OUTPUT ---------------------------
# Root: Hips
# Frames: 2
# Frame time: 0.033333

bvhio joint properties and methods

import bvhio

# The 'Joint' object allows for reading and modifing animations.
# Most of the functionality is based on the package 'spatial-transform'.
hierarchy = bvhio.readAsHierarchy('bvhio/tests/example.bvh')

# joints from the hierarchy can be selected by their name
joint = hierarchy.filter('Head')[0]

# Some methods and properties have been added to work with keyframe and joint data
joint.Keyframes         # list of local animation data
joint.loadPose(0)        # sets the transform data to a specific keyframe
joint.writePose(0)       # writes the current transform data into a keyframe
joint.roll(0)            # changes the rotation of a bone around its own axis without affcting the children

# Some methods do also update the keyframes to no destroy the animation data
# Please refer to the package 'spatial-transform' for their behaviour
joint.clearParent()
joint.clearChildren()
joint.attach()
joint.detach()
joint.applyPosition()
joint.applyRotation()
joint.applyScale()

Interacting with joints and animation

import bvhio

# The package allows to make modifcation on the animation data very conviniently.
root = bvhio.readAsHierarchy('bvhio/tests/example.bvh')

# Add a root bone to the hierarchy and set itself as 'root'.
root = bvhio.Joint('Root').attach(root, keep=['position', 'rotation', 'scale'])

# Scale so the data represent roughly meters, assuming the data is in inches.
# Because the scale is on the root and the rest pose, it is applied to all world space data.
root.RestPose.Scale = 0.0254

# this bakes the rest pos scale of 0.0254 into the positions,
# so that the scale can be reseted to 1 again.
root.applyRestposeScale(recursive=True, bakeKeyframes=True)

# tursn the animation by 180 degrees.
# Keep in mind that local keyframe and child rest pose data is still untouched.
root.RestPose.addEuler((0, 180, 0))

# Set all joints to the first keyframe.
# The animation pose is calculated by -> Pose = RestPose + Keyframe.
root.loadPose(0)

# print info
print('\nPosition and Y-direction of each joint in world space ')
for joint, index, depth in root.layout():
    print(f'{joint.PositionWorld} {joint.UpWorld} {joint.Name}')

# --------------------------- OUTPUT ---------------------------
# Position and Y-direction of each joint in world space
# vec3(            0,            0,            0 ) vec3(            0,            1,            0 ) Root
# vec3(    -0.203962,     0.889254,     -2.24434 ) vec3(   -0.0575126,     0.965201,    -0.255108 ) Hips
# vec3(    -0.211573,      1.01698,      -2.2781 ) vec3(   -0.0481175,     0.852046,     0.521251 ) Chest
# vec3(    -0.232769,      1.43762,     -2.06126 ) vec3(    -0.163858,     0.314978,     0.934847 ) Neck
# vec3(    -0.255451,      1.48122,     -1.93185 ) vec3(    -0.136333,     0.863658,     0.485292 ) Head
# vec3(    -0.203905,      1.36171,     -2.04548 ) vec3(     0.967669,     0.251636,   -0.0172419 ) LeftCollar
# vec3(   -0.0677384,      1.39712,     -2.04791 ) vec3(     0.901112,     0.170623,     0.398605 ) LeftUpArm
# vec3(     0.206005,      1.44895,     -1.92682 ) vec3(    -0.212169,    -0.689803,     0.692213 ) LeftLowArm
# vec3(     0.152491,      1.27497,     -1.75223 ) vec3(     -0.21588,    -0.681082,     0.699661 ) LeftHand
# vec3(    -0.260679,       1.3607,     -2.04907 ) vec3(    -0.953783,     0.278612,     -0.11258 ) RightCollar
# vec3(    -0.407731,      1.40366,     -2.06643 ) vec3(    -0.992285,     0.105528,    0.0650799 ) RightUpArm
# vec3(    -0.705642,      1.43534,     -2.04689 ) vec3(    -0.105734,     0.764633,     0.635733 ) RightLowArm
# vec3(    -0.734245,      1.64218,     -1.87492 ) vec3(    -0.117056,     0.781042,      0.61341 ) RightHand
# vec3(    -0.108093,      0.88812,     -2.27025 ) vec3(     0.182967,    -0.963475,    -0.195552 ) LeftUpLeg
# vec3(   -0.0228604,     0.439299,     -2.36134 ) vec3(    0.0743764,    -0.450022,    -0.889915 ) LeftLowLeg
# vec3(   0.00995436,      0.24075,     -2.75397 ) vec3(     0.085988,    -0.463928,     -0.88169 ) LeftFoot
# vec3(    -0.299832,     0.890388,     -2.21844 ) vec3(    -0.170185,    -0.858689,     0.483414 ) RightUpLeg
# vec3(    -0.376041,     0.505865,     -2.00197 ) vec3(    -0.135822,     -0.89424,    -0.426482 ) RightLowLeg
# vec3(    -0.435172,     0.116553,     -2.18764 ) vec3(    -0.188425,    -0.981787,    -0.024278 ) RightFoot

Compare pose data

import bvhio

root = bvhio.readAsHierarchy('bvhio/tests/example.bvh')
root = bvhio.Joint('Root', restPose=bvhio.Transform(scale=2.54)).attach(root)

# Load poses, then extract from all joints their position in world space.
pose0positions = [joint.PositionWorld for (joint, index, depth) in root.loadPose(0).layout()]
pose1positions = [joint.PositionWorld for (joint, index, depth) in root.loadPose(1).layout()]

print('Change in positions in centimeters between frame 0 and 1:')
for (joint, index, depth) in root.layout():
    print(f'{pose1positions[index] - pose0positions[index]} {joint.Name}')

# --------------------------- OUTPUT ---------------------------
# Change in positions in centimeters between frame 0 and 1:
# vec3(            0,            0,            0 ) Root
# vec3(    -0.558798,       0.2286,      -4.8006 ) Hips
# vec3(    -0.469618,     0.324989,     -5.21318 ) Chest
# vec3(     0.296169,     -1.28726,     -8.01178 ) Neck
# vec3(     0.350996,     -2.16693,     -8.26605 ) Head
# vec3(     0.161901,     -1.32426,     -7.49876 ) LeftCollar
# vec3(     0.790674,  0.000457764,     -10.1185 ) LeftUpArm
# vec3(      1.05577,      3.92007,     -8.26009 ) LeftLowArm
# vec3(     0.350739,      5.54311,     -9.92455 ) LeftHand
# vec3(     0.158957,     -1.40071,     -7.48221 ) RightCollar
# vec3(    -0.532215,     -1.32915,     -4.50168 ) RightUpArm
# vec3(    -0.389343,     -4.22717,     -4.83904 ) RightLowArm
# vec3(      2.33563,     -6.29668,     -6.45831 ) RightHand
# vec3(    -0.659554,     0.479576,     -5.20877 ) LeftUpLeg
# vec3(     -2.30601,    0.0990372,     -10.0871 ) LeftLowLeg
# vec3(      1.48517,     0.579193,     -9.71515 ) LeftFoot
# vec3(     -0.45804,    -0.022377,     -4.39243 ) RightUpLeg
# vec3(     0.278942,    -0.908985,     -2.43927 ) RightLowLeg
# vec3(     0.042263,      0.13649,    -0.323273 ) RightFoot

Convert between bvh and hierarchy structure

import bvhio

# load data as simple deserialized structure
bvhRoot = bvhio.readAsBvh('bvhio/tests/example.bvh').Root

# convert to a joint hierarchy
hierarchyRoot = bvhio.convertBvhToHierarchy(bvhRoot)

# convert them back to simple deserialized structure.
# the frame count needs to be given, and the max frame id is selected.
bvhRoot = bvhio.convertHierarchyToBvh(hierarchyRoot, hierarchyRoot.getKeyframeRange()[1] + 1)

# writes the data back into a .bvh file
bvhio.writeBvh('test.bvh', bvhio.BvhContainer(bvhRoot, len(bvhRoot.Keyframes), 1/30))

Interpolate between keyframes

import bvhio

# load data
root = bvhio.readAsHierarchy('bvhio/tests/example.bvh')

# scales the frame id of the two given frames.
# this will restult in the ids 0 and 100.
# frames without keyframe are linearly interpolated.
for joint, index, depth in root.layout():
    joint.Keyframes = [(frame * 100, key) for frame, key in joint.Keyframes]

# persists the interpolations automatically
bvhio.writeHierarchy('test.bvh', root, 1/30)

Merge BVH files

# THIS WILL ONLY WORKs IF THE REST POSE IS THE SAME!
# You cannot merge different skeletons / hierarchies.
import bvhio

# load data
file1 = bvhio.readAsBvh('bvhio/tests/example.bvh')
file2 = bvhio.readAsBvh('bvhio/tests/example.bvh')

# get hierarchy of both files
data1 = file1.Root.layout()
data2 = file2.Root.layout()

# append the animation to the end of the original one
for joint, index, _ in data1:
    joint.Keyframes.extend(data2[index][0].Keyframes)

# update framecount to write all animation
file1.FrameCount += file2.FrameCount

bvhio.writeBvh('test.bvh', file1, 4)

Create/build animations from code

import bvhio

# create custom hierarchy.
root = bvhio.Joint('Root', (0,2,0)).setEuler((0,0,0)).attach(
    bvhio.Joint('UpperLegL', (+.3,2.1,0)).setEuler((0,0,180)).attach(
        bvhio.Joint('LowerLegL', (+.3,1,0)).setEuler((0,0,180))
    ),
    bvhio.Joint('UpperLegR', (-.3,2.1,0)).setEuler((0,0,180)).attach(
        bvhio.Joint('LowerLegR', (-.3,1,0)).setEuler((0,0,180))
    ),
)

# sets current layout as rest pose
root.writeRestPose(recursive=True)

# change the pose of the hierarchy to save it alter as key frame
# this will add a rotation to each leg joint for left and right side
for joint in root.filter('LegL'):
    joint.Rotation *= bvhio.Euler.toQuatFrom((+0.523599,0,0))
for joint in root.filter('LegR'):
    joint.Rotation *= bvhio.Euler.toQuatFrom((-0.523599,0,0))

# Persists the current pose as pose.
# This will calculate the keyframe differences to the rest pose.
root.writePose(0, recursive=True)

# The rest pose is loaded first to have the base pose again, this is not necessary.
root.loadRestPose(recursive=True)

# Now the same thing again with other rotations two have two keyframes.
for joint in root.filter('LegL'):
    joint.Rotation *= bvhio.Euler.toQuatFrom((-0.523599,0,0))
for joint in root.filter('LegR'):
    joint.Rotation *= bvhio.Euler.toQuatFrom((+0.523599,0,0))

# persists the current pose again as new pose.
# All keyframes between the first and this pose are linearly interpolated.
root.writePose(20, recursive=True)

# store the animation
bvhio.writeHierarchy('test.bvh', root, 1/30, percision=4)


# --------------------------- OUTPUT (.bvh file) ---------------------------
# HIERARCHY
# ROOT Root
# {
#   OFFSET 0.0 2.0 0.0
#   CHANNELS 0
#   JOINT UpperLegL
#   {
#     OFFSET 0.3 0.1 0.0
#     CHANNELS 3 Zrotation Xrotation Yrotation
#     JOINT LowerLegL
#     {
#       OFFSET 0.0 -1.1 0.0
#       CHANNELS 3 Zrotation Xrotation Yrotation
#       End Site
#       {
#         OFFSET 0.0 -0.33 0.0
#       }
#     }
#   }
#   JOINT UpperLegR
#   {
#     OFFSET -0.3 0.1 0.0
#     CHANNELS 3 Zrotation Xrotation Yrotation
#     JOINT LowerLegR
#     {
#       OFFSET 0.0 -1.1 0.0
#       CHANNELS 3 Zrotation Xrotation Yrotation
#       End Site
#       {
#         OFFSET 0.0 -0.33 0.0
#       }
#     }
#   }
# }
# MOTION
# Frames: 21
# Frame Time: 0.03333333333333333
# -0.0 -30.0 -0.0 -0.0 -30.0 -0.0 -0.0 30.0 0.0 -0.0 30.0 0.0
# -0.0 -26.7437 -0.0 -0.0 -26.7437 -0.0 -0.0 26.7437 0.0 -0.0 26.7437 0.0
# -0.0 -23.5782 -0.0 -0.0 -23.5782 -0.0 -0.0 23.5782 0.0 -0.0 23.5782 0.0
# -0.0 -20.4873 -0.0 -0.0 -20.4873 -0.0 -0.0 20.4873 0.0 -0.0 20.4873 0.0
# -0.0 -17.4576 -0.0 -0.0 -17.4576 -0.0 -0.0 17.4576 0.0 -0.0 17.4576 0.0
# -0.0 -14.4775 -0.0 -0.0 -14.4775 -0.0 -0.0 14.4775 0.0 -0.0 14.4775 0.0
# -0.0 -11.537 -0.0 -0.0 -11.537 -0.0 -0.0 11.537 0.0 -0.0 11.537 0.0
# -0.0 -8.6269 -0.0 -0.0 -8.6269 -0.0 -0.0 8.6269 0.0 -0.0 8.6269 0.0
# -0.0 -5.7392 -0.0 -0.0 -5.7392 -0.0 -0.0 5.7392 0.0 -0.0 5.7392 0.0
# -0.0 -2.866 -0.0 -0.0 -2.866 -0.0 -0.0 2.866 0.0 -0.0 2.866 0.0
# -0.0 0.0 -0.0 -0.0 0.0 -0.0 -0.0 0.0 -0.0 -0.0 0.0 -0.0
# -0.0 2.866 0.0 -0.0 2.866 0.0 -0.0 -2.866 -0.0 -0.0 -2.866 -0.0
# -0.0 5.7392 0.0 -0.0 5.7392 0.0 -0.0 -5.7392 -0.0 -0.0 -5.7392 -0.0
# -0.0 8.6269 0.0 -0.0 8.6269 0.0 -0.0 -8.6269 -0.0 -0.0 -8.6269 -0.0
# -0.0 11.537 0.0 -0.0 11.537 0.0 -0.0 -11.537 -0.0 -0.0 -11.537 -0.0
# -0.0 14.4775 0.0 -0.0 14.4775 0.0 -0.0 -14.4775 -0.0 -0.0 -14.4775 -0.0
# -0.0 17.4576 0.0 -0.0 17.4576 0.0 -0.0 -17.4576 -0.0 -0.0 -17.4576 -0.0
# -0.0 20.4873 0.0 -0.0 20.4873 0.0 -0.0 -20.4873 -0.0 -0.0 -20.4873 -0.0
# -0.0 23.5782 0.0 -0.0 23.5782 0.0 -0.0 -23.5782 -0.0 -0.0 -23.5782 -0.0
# -0.0 26.7437 0.0 -0.0 26.7437 0.0 -0.0 -26.7437 -0.0 -0.0 -26.7437 -0.0
# -0.0 30.0 0.0 -0.0 30.0 0.0 -0.0 -30.0 -0.0 -0.0 -30.0 -0.0

bvhio's People

Contributors

keenua avatar reactai avatar wasserwecken avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

bvhio's Issues

Processing of Bandai-Namco-Research-Motion Dataset

Hello, I am using your method to process Bandai-Namco-Research-Motion Dataset. Thank you very much for your great work!
But I wonder if there is any way to converting non-root 6 channels to 3, while keeping the channel with root joint at 6? And how to ensure that the three channels of non- root are in the order of "Zrotation Yrotation Xrotation"?
As the picture below shows:
Snipaste_2023-09-13_21-40-15
Thank you very much!

Rotation axes order and channels definition order

Hi @Wasserwecken !
(excuse me for not responding/closing the other issues - I will do so when I find time)

I have 2 issues:

  • first one is more general and pertains to how .bvhs should be parsed in general
  • the second is about capabilities of bvhio

1. issue

Do you know, whether the order of rotations channels matter in .bvh? I've always thought that .bvh parsers ignore this order and instead apply rotations in some prespecified universal order.

However, this preconception of mine has been shattered recently ๐Ÿ˜ญ
In the attachement the order is (ZXY). I swapped the channels to ZYX in both HIERARCHY and in MOTION, but the result is broken (in every 3D soft it's displayed the same way).

This is the script I've used for swapping XY to YX:

import bvhio
from pathlib import Path
from tqdm import tqdm

def reorder_channels(path):
    "reorder rotation channels defitions in our .bvh (ZXY) to match the order in LaFAN .bvhs (ZYX)"

    # read .bvh
    with open(path, "r") as f:
        lines = f.readlines()

    # find line number of "MOTION" section
    motion = None
    for i, line in enumerate(lines):
        if "MOTION" in line:
            motion = i + 3
            break

    # reorder channels in HIERARCHY
    for i in range(0, motion):
        lines[i] = lines[i].replace(
            "Zrotation Xrotation Yrotation", "Zrotation Yrotation Xrotation"
        )

    # reorder channels in MOTION
    for i in range(motion, len(lines)):
        tokens = lines[i].split(" ")
        # Hips rotation
        tokens[4], tokens[5] = tokens[5], tokens[4]
        # all the rest
        for j in range(8, len(tokens), 3):
            tokens[j], tokens[j - 1] = tokens[j - 1], tokens[j]
        lines[i] = " ".join(tokens)

    # write .bvh
    with open(path, "w") as f:
        f.writelines(lines)


input_paths = Path(r"./original").glob("*.bvh")
output_path = r"./processed"
Path(output_path).mkdir(parents=True, exist_ok=True)

for path in tqdm(list(input_paths)):
     reorder_channels(path)

This is the order of my .bvh:
image

This is what I want to achieve (ignore offsets, only the rotations order matter):
image

2. issue

It is related to the first issue: does bvhio has some functions for switching the rotation channels definiton order?

Why I need this?

Well, 3D Animation Deep Learning models repositories tend to implement their own .bvh parsers and they are sometimes very unreliable and hardcoded for parsing the specific .bvh dataset they are using. I simply need a way to adjust my .bvhs in such a way as to be as similar as possible to the .bvhs used by these Deep Learning models.

README: What does "- Y+ is up" mean

Can you change the following fragment of the README file: "Right-Handed, - Y+ is up, Z- is forward and positive rotations are counter clockwise." The problem is the "- Y+ is up" part. Perhaps you want "Right-Handed, Y+ is up, Z- is forward...".

Also, "positive rotations are counter clockwise" is ambiguous, since it depends on whether you are viewing the rotation looking in the positive or negative direction of the axis. You could say, "positive rotations are counter clockwise when viewed from the origin looking in the positive direction".

Interpolating between frames

Hi @Wasserwecken !
(No hurry, btw., don't feel pressured to answer)

I have an animation file and I want to cut a range of frames for it.

Let's say this animation file has 100 frames and I want to only leave the part from frame 25 to frame 75 (both-inclusive)

cuts = {
"<path-to-bvh>" : [24,74]
}

for k,v in cuts.items():
    for start, end in v:
        bvh = bvhio.readAsBvh(k)
        root = bvhio.convertBvhToHierarchy(bvh.Root)

        # write ground truth
        path = Path(output_path) / f"{Path(k).name}_{start}_{end}_GROUND_TRUTH.bvh"
        for joint, _, _ in root.layout():
            joint.Keyframes = zip(range(0, end-start+1), [joint.Keyframes[i][1] for i in range(start, end+1)])
        bvhio.writeHierarchy(path, root, bvh.FrameTime, end - start + 1)

        # write linear interpolation
        path = Path(output_path) / f"{Path(k).name}_{start}_{end}_LIN_INTERP.bvh"
        for joint, _, _ in root.layout():
            joint.Keyframes = [(0, root.Keyframes[0][1]), (end-start, root.Keyframes[-1][1]) ]
        bvhio.writeHierarchy(path, root,  bvh.FrameTime,  end - start + 1)

The so called "ground-truth" is written correctly, however the linear interpolation doesn't work - it produces a completely messed up animation. Am I doing the interpolation wrong?

No module named `SpatialTransform`

When I install the latest version of bvhio via pip, a ModuleNotFoundError occurs. Also, it seems like glm is not listed in the dependencies.

Some questions about the processing of BVH data.

Hello, I am not familiar with the processing of BVH data and would like to consult with you. Do you know how to unify different BVH files into the same hierarchy? Can it be achieved with the bvhio you proposed?
I encountered a problem when re-implement the code of a paper. The BVH files in my dataset have 31 joints, but the BVH files in the paper's dataset only have 24 joints. Therefore, I would like to ask is there any way to convert a bvh file with 31 joints into a bvh file with 24 joints?
The specific files are like follows.
bvhData.zip
Thank you very much for your answer!

Converting non- root 6 channels to 3

Hi @Wasserwecken !
Great repo! Loving it!

I was wandering whether it is possible to change a .bvh having 6 channels for all joints to an equivalent one having only 3 channels (only rotations) for all non-root joints.

Tbh I couldn't find any documentation on how parsers parse such .bvh and I would love to know! Are extra positions modifying poses each keyframe?

I know MotionBuilder can do it, but i don't want to rely on it :)

Rest-pose relative issue

Your project is really a good work. In LAFAN1 dataset, the rest pose is not T-Pose, but something like the following image.
image
But I'm using SMPL model, you know, the rest pose is T-Pose.
So I can't directly apply the joint rotation in the bvh files of LAFAN1 to the SMPL model, because their rest poses are different.
In my python project, I load a pose rotation of LAFAN1, how do I change it to the counterpart of SMPL?
Thanks very much, looking forward to your reply.

How to mirror about X axis?

Hi @Wasserwecken !
Qucik a question - how to mirror the whole animation around X axis?
I would think that scaling the Restpose with (-1,1,1) would do the job, but If I'm not mistaken I can only assign a scalar value to Scale property.

Getting bone roll

Hi @Wasserwecken !

Any idea if bvhio could get me a bone roll (i.e. rotation along longitudinal axis)?
I know that Blender arbitrarily assigns the Y axis as the roll axis. How is it in bvhio?

In the worst case I would need to get the bone roll from joint.Rotation, right?

Bvh files aren't setting as T pose

After importing related file (dataset-1_bow_active_001.bvh) and copying your code into python console in Blender, it gives the error which I'll leave;

`import bpy
from bpy import data as D
from bpy import context as C
from mathutils import *
from math import *

#~ PYTHON INTERACTIVE CONSOLE 3.10.8 (main, Oct 24 2022, 20:47:11) [GCC 9.3.1 20200408 (Red Hat 9.3.1-2)]
#~
#~ Builtin Modules: bpy, bpy.data, bpy.ops, bpy.props, bpy.types, bpy.context, bpy.utils, bgl, blf, mathutils
#~ Convenience Imports: from mathutils import *; from math import *
#~ Convenience Variables: C = bpy.context, D = bpy.data
#~

requires two packages:

pip install PyGLM

pip install bvhio

this script will correct the rest pose of the .bvh files from the bandai namco motion set

https://github.com/BandaiNamcoResearchInc/Bandai-Namco-Research-Motiondataset

import glm
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! ModuleNotFoundError: No module named 'glm'
#!
import bvhio
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! ModuleNotFoundError: No module named 'bvhio'
#!

root = bvhio.readAsHierarchy('.\MoCap\Bandai\dataset\Bandai-Namco-Research-Motiondataset-1\data\dataset-1_bow_active_001.bvh')
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'bvhio' is not defined
#!
layout = root.layout()
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'root' is not defined
#!

set up T-pose

root.loadRestPose()
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'root' is not defined
#!
layout[ 0][0].setEuler(( 0, 0, 0)) # joint_Root
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[ 1][0].setEuler(( 0, 0, 0)) # Hips
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[ 1][0].Position = (0, 94, 0) # Hips
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[ 2][0].setEuler(( 0, 0, 0)) # Spine
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[ 3][0].setEuler(( 0, +90, 0)).roll(-90) # Chest
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[ 4][0].setEuler(( 0, 0, 0)) # Neck
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[ 5][0].setEuler(( 0, 0, 0)) # Head
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!

layout[ 6][0].setEuler(( 0, 0, -90)) # Shoulder_L
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[ 7][0].setEuler(( 0, 0, 0)) # UpperArm_L
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[ 8][0].setEuler(( 0, 0, 0)) # LowerArm_L
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[ 9][0].setEuler(( 0, 0, 0)) # Hand_L
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!

layout[10][0].setEuler(( 0, 0, +90)) # Shoulder_R
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[11][0].setEuler(( 0, 0, 0)) # UpperArm_R
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[12][0].setEuler(( 0, 0, 0)) # LowerArm_R
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[13][0].setEuler(( 0, 0, 0)) # Hand_R
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!

layout[14][0].setEuler(( 0, 0, 180)) # UpperLeg_L
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[15][0].setEuler(( 0, 0, 0)) # LowerLeg_L
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[16][0].setEuler(( 0, 0, 0)) # Foot_L
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[17][0].setEuler(( 0, 0, 0)) # Toes_L
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!

layout[18][0].setEuler(( 0, 0, 180)) # UpperLeg_R
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[19][0].setEuler(( 0, 0, 0)) # LowerLeg_R
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[20][0].setEuler(( 0, 0, 0)) # Foot_R
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
layout[21][0].setEuler(( 0, 0, 0)) # Toes_R
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'layout' is not defined
#!
root.writeRestPose(recursive=True, keep=['position', 'rotation', 'scale'])
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'root' is not defined
#!

key frame corrections, turns joints so than Z- axis always points forward

for frame in range(*root.getKeyframeRange()):
root.loadPose(frame, recursive=True)
layout[ 2][0].roll(-90) # Spine
layout[ 3][0].roll(-90) # Chest
layout[ 4][0].roll(-90) # Neck
layout[ 5][0].roll(-90) # Head
layout[10][0].roll(180, recursive=True) # Shoulder_R
layout[18][0].roll(180, recursive=True) # UpperLeg_R

#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'root' is not defined
#!
layout[ 5][0].Rotation *= glm.angleAxis(glm.radians(-90), (1, 0, 0)) # Head
#! File "<blender_console>", line 1
#! layout[ 5][0].Rotation *= glm.angleAxis(glm.radians(-90), (1, 0, 0)) # Head
#! IndentationError: unexpected indent
#!
layout[ 9][0].Rotation *= glm.angleAxis(glm.radians(-90), (0, 0, 1)) # Hand_L
#! File "<blender_console>", line 1
#! layout[ 9][0].Rotation *= glm.angleAxis(glm.radians(-90), (0, 0, 1)) # Hand_L
#! IndentationError: unexpected indent
#!
layout[13][0].Rotation *= glm.angleAxis(glm.radians(-90), (0, 0, 1)) # Hand_R
#! File "<blender_console>", line 1
#! layout[13][0].Rotation *= glm.angleAxis(glm.radians(-90), (0, 0, 1)) # Hand_R
#! IndentationError: unexpected indent
#!
layout[17][0].Rotation *= glm.angleAxis(glm.radians(-90), (0, 0, 1)) # Toes_L
#! File "<blender_console>", line 1
#! layout[17][0].Rotation *= glm.angleAxis(glm.radians(-90), (0, 0, 1)) # Toes_L
#! IndentationError: unexpected indent
#!
layout[21][0].Rotation *= glm.angleAxis(glm.radians(-90), (0, 0, 1)) # Toes_R
#! File "<blender_console>", line 1
#! layout[21][0].Rotation *= glm.angleAxis(glm.radians(-90), (0, 0, 1)) # Toes_R
#! IndentationError: unexpected indent
#!
root.writePose(frame, recursive=True)
#! File "<blender_console>", line 1
#! root.writePose(frame, recursive=True)
#! IndentationError: unexpected indent
#!

scale to meters

root.RestPose.Scale = 0.012
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'root' is not defined
#!
root.applyRestposeScale(recursive=True, bake=False, bakeKeyframes=True)
#! Traceback (most recent call last):
#! File "<blender_console>", line 1, in
#! NameError: name 'root' is not defined
#!
`

bvhio and Azure Kinect Data

Hello,
I'm trying to create animations in Blender using Azure Kinect Body Tracking data. I'm attempting to transfer the coordinates of the body joints into a BVH file. I'm having trouble with the motion part. Your package seems quite good and well-documented, but some things are unclear to me.

In your example, there's:
joint.Rotation *= bvhio.Euler.toQuatFrom((+0.523599,0,0))

Do I input the difference in Euler angles here?

It probably is straightforward, given that I already have the motion data and even quaternions per frame by the Azure Kinect. Do you perhaps have a simple way to do this with your package?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.