|
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 |
|
|