Spaces:
Running
Running
import re | |
import numpy as np | |
from visualization.Animation import Animation | |
from visualization.Quaternions import Quaternions | |
channelmap = { | |
'Xrotation': 'x', | |
'Yrotation': 'y', | |
'Zrotation': 'z' | |
} | |
channelmap_inv = { | |
'x': 'Xrotation', | |
'y': 'Yrotation', | |
'z': 'Zrotation', | |
} | |
ordermap = { | |
'x': 0, | |
'y': 1, | |
'z': 2, | |
} | |
def load(filename, start=None, end=None, order=None, world=False, need_quater=True): | |
""" | |
Reads a BVH file and constructs an animation | |
Parameters | |
---------- | |
filename: str | |
File to be opened | |
start : int | |
Optional Starting Frame | |
end : int | |
Optional Ending Frame | |
order : str | |
Optional Specifier for joint order. | |
Given as string E.G 'xyz', 'zxy' | |
world : bool | |
If set to true euler angles are applied | |
together in world space rather than local | |
space | |
Returns | |
------- | |
(animation, joint_names, frametime) | |
Tuple of loaded animation and joint names | |
""" | |
f = open(filename, "r") | |
i = 0 | |
active = -1 | |
end_site = False | |
names = [] | |
orients = Quaternions.id(0) | |
offsets = np.array([]).reshape((0, 3)) | |
parents = np.array([], dtype=int) | |
for line in f: | |
if "HIERARCHY" in line: continue | |
if "MOTION" in line: continue | |
""" Modified line read to handle mixamo data """ | |
# rmatch = re.match(r"ROOT (\w+)", line) | |
rmatch = re.match(r"ROOT (\w+:?\w+)", line) | |
if rmatch: | |
names.append(rmatch.group(1)) | |
offsets = np.append(offsets, np.array([[0, 0, 0]]), axis=0) | |
orients.qs = np.append(orients.qs, np.array([[1, 0, 0, 0]]), axis=0) | |
parents = np.append(parents, active) | |
active = (len(parents) - 1) | |
continue | |
if "{" in line: continue | |
if "}" in line: | |
if end_site: | |
end_site = False | |
else: | |
active = parents[active] | |
continue | |
offmatch = re.match(r"\s*OFFSET\s+([\-\d\.e]+)\s+([\-\d\.e]+)\s+([\-\d\.e]+)", line) | |
if offmatch: | |
if not end_site: | |
offsets[active] = np.array([list(map(float, offmatch.groups()))]) | |
continue | |
chanmatch = re.match(r"\s*CHANNELS\s+(\d+)", line) | |
if chanmatch: | |
channels = int(chanmatch.group(1)) | |
if order is None: | |
channelis = 0 if channels == 3 else 3 | |
channelie = 3 if channels == 3 else 6 | |
parts = line.split()[2 + channelis:2 + channelie] | |
if any([p not in channelmap for p in parts]): | |
continue | |
order = "".join([channelmap[p] for p in parts]) | |
continue | |
""" Modified line read to handle mixamo data """ | |
# jmatch = re.match("\s*JOINT\s+(\w+)", line) | |
jmatch = re.match("\s*JOINT\s+(\w+:?\w+)", line) | |
if jmatch: | |
names.append(jmatch.group(1)) | |
offsets = np.append(offsets, np.array([[0, 0, 0]]), axis=0) | |
orients.qs = np.append(orients.qs, np.array([[1, 0, 0, 0]]), axis=0) | |
parents = np.append(parents, active) | |
active = (len(parents) - 1) | |
continue | |
if "End Site" in line: | |
end_site = True | |
continue | |
fmatch = re.match("\s*Frames:\s+(\d+)", line) | |
if fmatch: | |
if start and end: | |
fnum = (end - start) - 1 | |
else: | |
fnum = int(fmatch.group(1)) | |
jnum = len(parents) | |
positions = offsets[np.newaxis].repeat(fnum, axis=0) | |
rotations = np.zeros((fnum, len(orients), 3)) | |
continue | |
fmatch = re.match("\s*Frame Time:\s+([\d\.]+)", line) | |
if fmatch: | |
frametime = float(fmatch.group(1)) | |
continue | |
if (start and end) and (i < start or i >= end - 1): | |
i += 1 | |
continue | |
# dmatch = line.strip().split(' ') | |
dmatch = line.strip().split() | |
if dmatch: | |
data_block = np.array(list(map(float, dmatch))) | |
N = len(parents) | |
fi = i - start if start else i | |
if channels == 3: | |
positions[fi, 0:1] = data_block[0:3] | |
rotations[fi, :] = data_block[3:].reshape(N, 3) | |
elif channels == 6: | |
data_block = data_block.reshape(N, 6) | |
positions[fi, :] = data_block[:, 0:3] | |
rotations[fi, :] = data_block[:, 3:6] | |
elif channels == 9: | |
positions[fi, 0] = data_block[0:3] | |
data_block = data_block[3:].reshape(N - 1, 9) | |
rotations[fi, 1:] = data_block[:, 3:6] | |
positions[fi, 1:] += data_block[:, 0:3] * data_block[:, 6:9] | |
else: | |
raise Exception("Too many channels! %i" % channels) | |
i += 1 | |
f.close() | |
if need_quater: | |
rotations = Quaternions.from_euler(np.radians(rotations), order=order, world=world) | |
elif order != 'xyz': | |
rotations = Quaternions.from_euler(np.radians(rotations), order=order, world=world) | |
rotations = np.degrees(rotations.euler()) | |
return Animation(rotations, positions, orients, offsets, parents, names, frametime) | |
def save(filename, anim, names=None, frametime=1.0 / 24.0, order='zyx', positions=False, mask=None, quater=False): | |
""" | |
Saves an Animation to file as BVH | |
Parameters | |
---------- | |
filename: str | |
File to be saved to | |
anim : Animation | |
Animation to save | |
names : [str] | |
List of joint names | |
order : str | |
Optional Specifier for joint order. | |
Given as string E.G 'xyz', 'zxy' | |
frametime : float | |
Optional Animation Frame time | |
positions : bool | |
Optional specfier to save bone | |
positions for each frame | |
orients : bool | |
Multiply joint orients to the rotations | |
before saving. | |
""" | |
if names is None: | |
names = ["joint_" + str(i) for i in range(len(anim.parents))] | |
with open(filename, 'w') as f: | |
t = "" | |
f.write("%sHIERARCHY\n" % t) | |
f.write("%sROOT %s\n" % (t, names[0])) | |
f.write("%s{\n" % t) | |
t += '\t' | |
f.write("%sOFFSET %f %f %f\n" % (t, anim.offsets[0, 0], anim.offsets[0, 1], anim.offsets[0, 2])) | |
f.write("%sCHANNELS 6 Xposition Yposition Zposition %s %s %s \n" % | |
(t, channelmap_inv[order[0]], channelmap_inv[order[1]], channelmap_inv[order[2]])) | |
for i in range(anim.shape[1]): | |
if anim.parents[i] == 0: | |
t = save_joint(f, anim, names, t, i, order=order, positions=positions) | |
t = t[:-1] | |
f.write("%s}\n" % t) | |
f.write("MOTION\n") | |
f.write("Frames: %i\n" % anim.shape[0]); | |
f.write("Frame Time: %f\n" % frametime); | |
# if orients: | |
# rots = np.degrees((-anim.orients[np.newaxis] * anim.rotations).euler(order=order[::-1])) | |
# else: | |
# rots = np.degrees(anim.rotations.euler(order=order[::-1])) | |
# rots = np.degrees(anim.rotations.euler(order=order[::-1])) | |
if quater: | |
rots = np.degrees(anim.rotations.euler(order=order[::-1])) | |
else: | |
rots = anim.rotations | |
poss = anim.positions | |
for i in range(anim.shape[0]): | |
for j in range(anim.shape[1]): | |
if positions or j == 0: | |
f.write("%f %f %f %f %f %f " % ( | |
poss[i, j, 0], poss[i, j, 1], poss[i, j, 2], | |
rots[i, j, ordermap[order[0]]], rots[i, j, ordermap[order[1]]], rots[i, j, ordermap[order[2]]])) | |
else: | |
if mask == None or mask[j] == 1: | |
f.write("%f %f %f " % ( | |
rots[i, j, ordermap[order[0]]], rots[i, j, ordermap[order[1]]], | |
rots[i, j, ordermap[order[2]]])) | |
else: | |
f.write("%f %f %f " % (0, 0, 0)) | |
f.write("\n") | |
def save_joint(f, anim, names, t, i, order='zyx', positions=False): | |
f.write("%sJOINT %s\n" % (t, names[i])) | |
f.write("%s{\n" % t) | |
t += '\t' | |
f.write("%sOFFSET %f %f %f\n" % (t, anim.offsets[i, 0], anim.offsets[i, 1], anim.offsets[i, 2])) | |
if positions: | |
f.write("%sCHANNELS 6 Xposition Yposition Zposition %s %s %s \n" % (t, | |
channelmap_inv[order[0]], | |
channelmap_inv[order[1]], | |
channelmap_inv[order[2]])) | |
else: | |
f.write("%sCHANNELS 3 %s %s %s\n" % (t, | |
channelmap_inv[order[0]], channelmap_inv[order[1]], | |
channelmap_inv[order[2]])) | |
end_site = True | |
for j in range(anim.shape[1]): | |
if anim.parents[j] == i: | |
t = save_joint(f, anim, names, t, j, order=order, positions=positions) | |
end_site = False | |
if end_site: | |
f.write("%sEnd Site\n" % t) | |
f.write("%s{\n" % t) | |
t += '\t' | |
f.write("%sOFFSET %f %f %f\n" % (t, 0.0, 0.0, 0.0)) | |
t = t[:-1] | |
f.write("%s}\n" % t) | |
t = t[:-1] | |
f.write("%s}\n" % t) | |
return t |