| | import numpy as np |
| | |
| | import visualization.Animation as Animation |
| |
|
| |
|
| | """ Family Functions """ |
| |
|
| |
|
| | def joints(parents): |
| | """ |
| | Parameters |
| | ---------- |
| | |
| | parents : (J) ndarray |
| | parents array |
| | |
| | Returns |
| | ------- |
| | |
| | joints : (J) ndarray |
| | Array of joint indices |
| | """ |
| | return np.arange(len(parents), dtype=int) |
| |
|
| |
|
| | def joints_list(parents): |
| | """ |
| | Parameters |
| | ---------- |
| | |
| | parents : (J) ndarray |
| | parents array |
| | |
| | Returns |
| | ------- |
| | |
| | joints : [ndarray] |
| | List of arrays of joint idices for |
| | each joint |
| | """ |
| | return list(joints(parents)[:, np.newaxis]) |
| |
|
| |
|
| | def parents_list(parents): |
| | """ |
| | Parameters |
| | ---------- |
| | |
| | parents : (J) ndarray |
| | parents array |
| | |
| | Returns |
| | ------- |
| | |
| | parents : [ndarray] |
| | List of arrays of joint idices for |
| | the parents of each joint |
| | """ |
| | return list(parents[:, np.newaxis]) |
| |
|
| |
|
| | def children_list(parents): |
| | """ |
| | Parameters |
| | ---------- |
| | |
| | parents : (J) ndarray |
| | parents array |
| | |
| | Returns |
| | ------- |
| | |
| | children : [ndarray] |
| | List of arrays of joint indices for |
| | the children of each joint |
| | """ |
| |
|
| | def joint_children(i): |
| | return [j for j, p in enumerate(parents) if p == i] |
| |
|
| | return list(map(lambda j: np.array(joint_children(j)), joints(parents))) |
| |
|
| |
|
| | def descendants_list(parents): |
| | """ |
| | Parameters |
| | ---------- |
| | |
| | parents : (J) ndarray |
| | parents array |
| | |
| | Returns |
| | ------- |
| | |
| | descendants : [ndarray] |
| | List of arrays of joint idices for |
| | the descendants of each joint |
| | """ |
| |
|
| | children = children_list(parents) |
| |
|
| | def joint_descendants(i): |
| | return sum([joint_descendants(j) for j in children[i]], list(children[i])) |
| |
|
| | return list(map(lambda j: np.array(joint_descendants(j)), joints(parents))) |
| |
|
| |
|
| | def ancestors_list(parents): |
| | """ |
| | Parameters |
| | ---------- |
| | |
| | parents : (J) ndarray |
| | parents array |
| | |
| | Returns |
| | ------- |
| | |
| | ancestors : [ndarray] |
| | List of arrays of joint idices for |
| | the ancestors of each joint |
| | """ |
| |
|
| | decendants = descendants_list(parents) |
| |
|
| | def joint_ancestors(i): |
| | return [j for j in joints(parents) if i in decendants[j]] |
| |
|
| | return list(map(lambda j: np.array(joint_ancestors(j)), joints(parents))) |
| |
|
| |
|
| | """ Mask Functions """ |
| |
|
| |
|
| | def mask(parents, filter): |
| | """ |
| | Constructs a Mask for a give filter |
| | |
| | A mask is a (J, J) ndarray truth table for a given |
| | condition over J joints. For example there |
| | may be a mask specifying if a joint N is a |
| | child of another joint M. |
| | |
| | This could be constructed into a mask using |
| | `m = mask(parents, children_list)` and the condition |
| | of childhood tested using `m[N, M]`. |
| | |
| | Parameters |
| | ---------- |
| | |
| | parents : (J) ndarray |
| | parents array |
| | |
| | filter : (J) ndarray -> [ndarray] |
| | function that outputs a list of arrays |
| | of joint indices for some condition |
| | |
| | Returns |
| | ------- |
| | |
| | mask : (N, N) ndarray |
| | boolean truth table of given condition |
| | """ |
| | m = np.zeros((len(parents), len(parents))).astype(bool) |
| | jnts = joints(parents) |
| | fltr = filter(parents) |
| | for i, f in enumerate(fltr): m[i, :] = np.any(jnts[:, np.newaxis] == f[np.newaxis, :], axis=1) |
| | return m |
| |
|
| |
|
| | def joints_mask(parents): return np.eye(len(parents)).astype(bool) |
| |
|
| |
|
| | def children_mask(parents): return mask(parents, children_list) |
| |
|
| |
|
| | def parents_mask(parents): return mask(parents, parents_list) |
| |
|
| |
|
| | def descendants_mask(parents): return mask(parents, descendants_list) |
| |
|
| |
|
| | def ancestors_mask(parents): return mask(parents, ancestors_list) |
| |
|
| |
|
| | """ Search Functions """ |
| |
|
| |
|
| | def joint_chain_ascend(parents, start, end): |
| | chain = [] |
| | while start != end: |
| | chain.append(start) |
| | start = parents[start] |
| | chain.append(end) |
| | return np.array(chain, dtype=int) |
| |
|
| |
|
| | """ Constraints """ |
| |
|
| |
|
| | def constraints(anim, **kwargs): |
| | """ |
| | Constraint list for Animation |
| | |
| | This constraint list can be used in the |
| | VerletParticle solver to constrain |
| | a animation global joint positions. |
| | |
| | Parameters |
| | ---------- |
| | |
| | anim : Animation |
| | Input animation |
| | |
| | masses : (F, J) ndarray |
| | Optional list of masses |
| | for joints J across frames F |
| | defaults to weighting by |
| | vertical height |
| | |
| | Returns |
| | ------- |
| | |
| | constraints : [(int, int, (F, J) ndarray, (F, J) ndarray, (F, J) ndarray)] |
| | A list of constraints in the format: |
| | (Joint1, Joint2, Masses1, Masses2, Lengths) |
| | |
| | """ |
| |
|
| | masses = kwargs.pop('masses', None) |
| |
|
| | children = children_list(anim.parents) |
| | constraints = [] |
| |
|
| | points_offsets = Animation.offsets_global(anim) |
| | points = Animation.positions_global(anim) |
| |
|
| | if masses is None: |
| | masses = 1.0 / (0.1 + np.absolute(points_offsets[:, 1])) |
| | masses = masses[np.newaxis].repeat(len(anim), axis=0) |
| |
|
| | for j in range(anim.shape[1]): |
| |
|
| | """ Add constraints between all joints and their children """ |
| | for c0 in children[j]: |
| |
|
| | dists = np.sum((points[:, c0] - points[:, j]) ** 2.0, axis=1) ** 0.5 |
| | constraints.append((c0, j, masses[:, c0], masses[:, j], dists)) |
| |
|
| | """ Add constraints between all children of joint """ |
| | for c1 in children[j]: |
| | if c0 == c1: continue |
| |
|
| | dists = np.sum((points[:, c0] - points[:, c1]) ** 2.0, axis=1) ** 0.5 |
| | constraints.append((c0, c1, masses[:, c0], masses[:, c1], dists)) |
| |
|
| | return constraints |
| |
|
| |
|
| | """ Graph Functions """ |
| |
|
| |
|
| | def graph(anim): |
| | """ |
| | Generates a weighted adjacency matrix |
| | using local joint distances along |
| | the skeletal structure. |
| | |
| | Joints which are not connected |
| | are assigned the weight `0`. |
| | |
| | Joints which actually have zero distance |
| | between them, but are still connected, are |
| | perturbed by some minimal amount. |
| | |
| | The output of this routine can be used |
| | with the `scipy.sparse.csgraph` |
| | routines for graph analysis. |
| | |
| | Parameters |
| | ---------- |
| | |
| | anim : Animation |
| | input animation |
| | |
| | Returns |
| | ------- |
| | |
| | graph : (N, N) ndarray |
| | weight adjacency matrix using |
| | local distances along the |
| | skeletal structure from joint |
| | N to joint M. If joints are not |
| | directly connected are assigned |
| | the weight `0`. |
| | """ |
| |
|
| | graph = np.zeros(anim.shape[1], anim.shape[1]) |
| | lengths = np.sum(anim.offsets ** 2.0, axis=1) ** 0.5 + 0.001 |
| |
|
| | for i, p in enumerate(anim.parents): |
| | if p == -1: continue |
| | graph[i, p] = lengths[p] |
| | graph[p, i] = lengths[p] |
| |
|
| | return graph |
| |
|
| |
|
| | def distances(anim): |
| | """ |
| | Generates a distance matrix for |
| | pairwise joint distances along |
| | the skeletal structure |
| | |
| | Parameters |
| | ---------- |
| | |
| | anim : Animation |
| | input animation |
| | |
| | Returns |
| | ------- |
| | |
| | distances : (N, N) ndarray |
| | array of pairwise distances |
| | along skeletal structure |
| | from some joint N to some |
| | joint M |
| | """ |
| |
|
| | distances = np.zeros((anim.shape[1], anim.shape[1])) |
| | generated = distances.copy().astype(bool) |
| |
|
| | joint_lengths = np.sum(anim.offsets ** 2.0, axis=1) ** 0.5 |
| | joint_children = children_list(anim) |
| | joint_parents = parents_list(anim) |
| |
|
| | def find_distance(distances, generated, prev, i, j): |
| |
|
| | """ If root, identity, or already generated, return """ |
| | if j == -1: return (0.0, True) |
| | if j == i: return (0.0, True) |
| | if generated[i, j]: return (distances[i, j], True) |
| |
|
| | """ Find best distances along parents and children """ |
| | par_dists = [(joint_lengths[j], find_distance(distances, generated, j, i, p)) for p in joint_parents[j] if |
| | p != prev] |
| | out_dists = [(joint_lengths[c], find_distance(distances, generated, j, i, c)) for c in joint_children[j] if |
| | c != prev] |
| |
|
| | """ Check valid distance and not dead end """ |
| | par_dists = [a + d for (a, (d, f)) in par_dists if f] |
| | out_dists = [a + d for (a, (d, f)) in out_dists if f] |
| |
|
| | """ All dead ends """ |
| | if (out_dists + par_dists) == []: return (0.0, False) |
| |
|
| | """ Get minimum path """ |
| | dist = min(out_dists + par_dists) |
| | distances[i, j] = dist; |
| | distances[j, i] = dist |
| | generated[i, j] = True; |
| | generated[j, i] = True |
| |
|
| | for i in range(anim.shape[1]): |
| | for j in range(anim.shape[1]): |
| | find_distance(distances, generated, -1, i, j) |
| |
|
| | return distances |
| |
|
| |
|
| | def edges(parents): |
| | """ |
| | Animation structure edges |
| | |
| | Parameters |
| | ---------- |
| | |
| | parents : (J) ndarray |
| | parents array |
| | |
| | Returns |
| | ------- |
| | |
| | edges : (M, 2) ndarray |
| | array of pairs where each |
| | pair contains two indices of a joints |
| | which corrisponds to an edge in the |
| | joint structure going from parent to child. |
| | """ |
| |
|
| | return np.array(list(zip(parents, joints(parents)))[1:]) |
| |
|
| |
|
| | def incidence(parents): |
| | """ |
| | Incidence Matrix |
| | |
| | Parameters |
| | ---------- |
| | |
| | parents : (J) ndarray |
| | parents array |
| | |
| | Returns |
| | ------- |
| | |
| | incidence : (N, M) ndarray |
| | |
| | Matrix of N joint positions by |
| | M edges which each entry is either |
| | 1 or -1 and multiplication by the |
| | joint positions returns the an |
| | array of vectors along each edge |
| | of the structure |
| | """ |
| |
|
| | es = edges(parents) |
| |
|
| | inc = np.zeros((len(parents) - 1, len(parents))).astype(np.int) |
| | for i, e in enumerate(es): |
| | inc[i, e[0]] = 1 |
| | inc[i, e[1]] = -1 |
| |
|
| | return inc.T |
| |
|