diff --git a/GPy/util/mocap.py b/GPy/util/mocap.py index e66a36b9..0cc2f20b 100644 --- a/GPy/util/mocap.py +++ b/GPy/util/mocap.py @@ -1,6 +1,611 @@ import os import numpy as np +import math +class vertex: + def __init__(self, name, id, parents=[], children=[], meta = {}): + self.name = name + self.id = id + self.parents = parents + self.children = children + self.meta = meta + + def __str__(self): + return self.name + '(' + str(self.id) + ').' + +class tree: + def __init__(self): + self.vertices = [] + self.vertices.append(vertex(name='root', id=0)) + + def __str__(self): + index = self.find_root() + return self.branch_str(index) + + def branch_str(self, index, indent=''): + out = indent + str(self.vertices[index]) + '\n' + for child in self.vertices[index].children: + out+=self.branch_str(child, indent+' ') + return out + + def find_children(self): + """Take a tree and set the children according to the parents. + + Takes a tree structure which lists the parents of each vertex + and computes the children for each vertex and places them in.""" + for i in range(len(self.vertices)): + self.vertices[i].children = [] + for i in range(len(self.vertices)): + for parent in self.vertices[i].parents: + if i not in self.vertices[parent].children: + self.vertices[parent].children.append(i) + + def find_parents(self): + """Take a tree and set the parents according to the children + + Takes a tree structure which lists the children of each vertex + and computes the parents for each vertex and places them in.""" + for i in range(len(self.vertices)): + self.vertices[i].parents = [] + for i in range(len(self.vertices)): + for child in self.vertices[i].children: + if i not in self.vertices[child].parents: + self.vertices[child].parents.append(i) + + def find_root(self): + """Finds the index of the root node of the tree.""" + self.find_parents() + index = 0 + while len(self.vertices[index].parents)>0: + index = self.vertices[index].parents[0] + return index + + def get_index_by_id(self, id): + """Give the index associated with a given vertex id.""" + for i in range(len(self.vertices)): + if self.vertices[i].id == id: + return i + raise Error, 'Reverse look up of id failed.' + + def get_index_by_name(self, name): + """Give the index associated with a given vertex name.""" + for i in range(len(self.vertices)): + if self.vertices[i].name == name: + return i + raise Error, 'Reverse look up of name failed.' + + def order_vertices(self): + """Order vertices in the graph such that parents always have a lower index than children.""" + + ordered = False + while ordered == False: + for i in range(len(self.vertices)): + ordered = True + for parent in self.vertices[i].parents: + if parent>i: + ordered = False + self.swap_vertices(i, parent) + + + + + def swap_vertices(self, i, j): + """Swap two vertices in the tree structure array. + swap_vertex swaps the location of two vertices in a tree structure array. + ARG tree : the tree for which two vertices are to be swapped. + ARG i : the index of the first vertex to be swapped. + ARG j : the index of the second vertex to be swapped. + RETURN tree : the tree structure with the two vertex locations + swapped. + """ + store_vertex_i = self.vertices[i] + store_vertex_j = self.vertices[j] + self.vertices[j] = store_vertex_i + self.vertices[i] = store_vertex_j + for k in range(len(self.vertices)): + for swap_list in [self.vertices[k].children, self.vertices[k].parents]: + if i in swap_list: + swap_list[swap_list.index(i)] = -1 + if j in swap_list: + swap_list[swap_list.index(j)] = i + if -1 in swap_list: + swap_list[swap_list.index(-1)] = j + + + +def rotation_matrix(xangle, yangle, zangle, order='zxy', degrees=False): + + """Compute the rotation matrix for an angle in each direction. + This is a helper function for computing the rotation matrix for a given set of angles in a given order. + ARG xangle : rotation for x-axis. + ARG yangle : rotation for y-axis. + ARG zangle : rotation for z-axis. + ARG order : the order for the rotations.""" + if degrees: + xangle = math.radians(xangle) + yangle = math.radians(yangle) + zangle = math.radians(zangle) + + # Here we assume we rotate z, then x then y. + c1 = math.cos(xangle) # The x angle + c2 = math.cos(yangle) # The y angle + c3 = math.cos(zangle) # the z angle + s1 = math.sin(xangle) + s2 = math.sin(yangle) + s3 = math.sin(zangle) + + # see http://en.wikipedia.org/wiki/Rotation_matrix for + # additional info. + + if order=='zxy': + rot_mat = np.array([[c2*c3-s1*s2*s3, c2*s3+s1*s2*c3, -s2*c1],[-c1*s3, c1*c3, s1],[s2*c3+c2*s1*s3, s2*s3-c2*s1*c3, c2*c1]]) + else: + rot_mat = np.eye(3) + for i in range(len(order)): + if order[i]=='x': + rot_mat = np.dot(np.array([[1, 0, 0], [0, c1, s1], [0, -s1, c1]]),rot_mat) + elif order[i] == 'y': + rot_mat = np.dot(np.array([[c2, 0, -s2], [0, 1, 0], [s2, 0, c2]]),rot_mat) + elif order[i] == 'z': + rot_mat = np.dot(np.array([[c3, s3, 0], [-s3, c3, 0], [0, 0, 1]]),rot_mat) + + return rot_mat + + +# Motion capture data routines. +class skeleton(tree): + def __init__(self): + tree.__init__(self) + + def to_xyz(self, channels): + raise NotImplementedError, "this needs to be implemented to use the skeleton class" + + + def finalize(self): + """After loading in a skeleton ensure parents are correct, vertex orders are correct and rotation matrices are correct.""" + + self.find_parents() + self.order_vertices() + self.set_rotation_matrices() + + def smooth_angle_channels(self, channels): + """Remove discontinuities in angle channels so that they don't cause artifacts in algorithms that rely on the smoothness of the functions.""" + for vertex in self.vertices: + for col in vertex.meta['rot_ind']: + if col: + for k in range(1, channels.shape[0]): + diff=channels[k, col]-channels[k-1, col] + if abs(diff+360.)0: + start_val = end_val + end_val = end_val + len(vertex.meta['channels']) + for j in range(num_frames): + channels[j, start_val:end_val] = bones[i][j] + self.resolve_indices(i, start_val) + + self.smooth_angle_channels(channels) + return channels + + + def read_documentation(self, fid): + """Read documentation from an acclaim skeleton file stream.""" + + lin = self.read_line(fid) + while lin[0] != ':': + self.documentation.append(lin) + lin = self.read_line(fid) + return lin + + def read_hierarchy(self, fid): + """Read hierarchy information from acclaim skeleton file stream.""" + + lin = self.read_line(fid) + + while lin != 'end': + parts = lin.split() + if lin != 'begin': + ind = self.get_index_by_name(parts[0]) + for i in range(1, len(parts)): + self.vertices[ind].children.append(self.get_index_by_name(parts[i])) + lin = self.read_line(fid) + lin = self.read_line(fid) + return lin + + def read_line(self, fid): + """Read a line from a file string and check it isn't either empty or commented before returning.""" + lin = '#' + while lin[0] == '#': + lin = fid.readline().strip() + if lin == '': + return lin + return lin + + + def read_root(self, fid): + """Read the root node from an acclaim skeleton file stream.""" + lin = self.read_line(fid) + while lin[0] != ':': + parts = lin.split() + if parts[0]=='order': + order = [] + for i in range(1, len(parts)): + if parts[i].lower()=='rx': + chan = 'Xrotation' + order.append('x') + elif parts[i].lower()=='ry': + chan = 'Yrotation' + order.append('y') + elif parts[i].lower()=='rz': + chan = 'Zrotation' + order.append('z') + elif parts[i].lower()=='tx': + chan = 'Xposition' + elif parts[i].lower()=='ty': + chan = 'Yposition' + elif parts[i].lower()=='tz': + chan = 'Zposition' + elif parts[i].lower()=='l': + chan = 'length' + self.vertices[0].meta['channels'].append(chan) + # order is reversed compared to bvh + self.vertices[0].meta['order'] = order[::-1] + + elif parts[0]=='axis': + # order is reversed compared to bvh + self.vertices[0].meta['axis_order'] = parts[1][::-1].lower() + elif parts[0]=='position': + self.vertices[0].meta['offset'] = [float(parts[1]), + float(parts[2]), + float(parts[3])] + elif parts[0]=='orientation': + self.vertices[0].meta['orientation'] = [float(parts[1]), + float(parts[2]), + float(parts[3])] + lin = self.read_line(fid) + return lin + + def read_skel(self, fid): + """Loads an acclaim skeleton format from a file stream.""" + lin = self.read_line(fid) + while lin: + if lin[0]==':': + if lin[1:]== 'name': + lin = self.read_line(fid) + self.name = lin + elif lin[1:]=='units': + lin = self.read_units(fid) + elif lin[1:]=='documentation': + lin = self.read_documentation(fid) + elif lin[1:]=='root': + lin = self.read_root(fid) + elif lin[1:]=='bonedata': + lin = self.read_bonedata(fid) + elif lin[1:]=='hierarchy': + lin = self.read_hierarchy(fid) + elif lin[1:8]=='version': + lin = self.read_line(fid) + continue + else: + if not lin: + self.finalize() + return + lin = self.read_line(fid) + else: + raise Error, 'Unrecognised file format' + + def read_units(self, fid): + """Read units from an acclaim skeleton file stream.""" + lin = self.read_line(fid) + while lin[0] != ':': + parts = lin.split() + if parts[0]=='mass': + self.mass = float(parts[1]) + elif parts[0]=='length': + self.length = float(parts[1]) + elif parts[0]=='angle': + self.angle = parts[1] + lin = self.read_line(fid) + return lin + + def resolve_indices(self, index, start_val): + """Get indices for the skeleton from the channels when loading in channel data.""" + + channels = self.vertices[index].meta['channels'] + base_channel = start_val - 1 + rot_ind = np.zeros(3) + pos_ind = np.zeros(3) + for i in range(len(channels)): + if channels[i]== 'Xrotation': + rot_ind[0] = base_channel + i + elif channels[i]=='Yrotation': + rot_ind[1] = base_channel + i + elif channels[i]=='Zrotation': + rot_ind[2] = base_channel + i + elif channels[i]=='Xposition': + pos_ind[0] = base_channel + i + elif channels[i]=='Yposition': + pos_ind[1] = base_channel + i + elif channels[i]=='Zposition': + pos_ind[2] = base_channel + i + self.vertices[index].meta['rot_ind'] = list(rot_ind) + self.vertices[index].meta['pos_ind'] = list(pos_ind) + + def set_rotation_matrices(self): + """Set the meta information at each vertex to contain the correct matrices C and Cinv as prescribed by the rotations and rotation orders.""" + for i in range(len(self.vertices)): + self.vertices[i].meta['C'] = rotation_matrix(self.vertices[i].meta['axis'][0], + self.vertices[i].meta['axis'][1], + self.vertices[i].meta['axis'][2], + self.vertices[i].meta['axis_order'], + degrees=True) + # Todo: invert this by applying angle operations in reverse order + self.vertices[i].meta['Cinv'] = np.linalg.inv(self.vertices[i].meta['C']) + + +# Utilities for loading in x,y,z data. def load_text_data(dataset, directory, centre=True): """Load in a data set of marker points from the Ohio State University C3D motion capture files (http://accad.osu.edu/research/mocap/mocap_data.htm).""" @@ -72,3 +677,4 @@ def read_connections(file_name, point_names): +skel = acclaim_skeleton()