HandX-demo / src /feature /bihand_motioncode.py
xusirui's picture
HandX text-to-motion Gradio demo
2680bd5 verified
Raw
History Blame Contribute Delete
10.8 kB
import numpy as np
import json
from ..constant import JOINT_NAME_INDEX_MAP
from .single_motioncode import MotionCoder, split_events, split_contact_events
def compute_topk_closest_vectors(A, B, k=10):
'''
A: (T, 3)
B: (T, n, 3)
return: (T, k, 3)
'''
A_expanded = A[:, np.newaxis, :] # (T, 1, 3)
diff = B - A_expanded # (T, n, 3)
dists = np.linalg.norm(diff, axis=2) # (T, n)
topk_indices = np.argpartition(dists, kth=k, axis=1)[:, :k] # (T, k)
row_indices = np.arange(A.shape[0])[:, np.newaxis] # (T, 1)
sorted_order = np.argsort(dists[row_indices, topk_indices], axis=1)
topk_sorted_indices = topk_indices[row_indices, sorted_order]
C = diff[row_indices, topk_sorted_indices] # (T, k, 3)
return C
def compute_closest_distances(A, B):
'''
A: (T, n, 3)
B: (T, n, 3)
return
- C: (T, n)
- C_vec: (T, n, 3)
'''
T, n, _ = A.shape
A_exp = A[:, :, np.newaxis, :] # (T, n, 1, 3)
B_exp = B[:, np.newaxis, :, :] # (T, 1, n, 3)
diff = A_exp - B_exp # (T, n, n, 3)
dists = np.linalg.norm(diff, axis=-1) # (T, n, n)
C = np.min(dists, axis=2) # (T, n)
indices = np.argmin(dists, axis=2) # (T, n)
C_vec = np.empty((T, n, 3))
for t in range(T):
for i in range(n):
j = indices[t, i]
C_vec[t, i] = -A[t, i] + B[t, j]
return C, C_vec
def topk_smallest_elements(C, k):
topk_part = np.partition(C, kth=k-1, axis=1)[:, :k]
D = np.sort(topk_part, axis=1)
return D
class BihandMotionCoder(object):
def __init__(self, motion:np.ndarray):
self.lft_motion = motion[:, 0] # (T, J, 3)
self.rgt_motion = motion[:, 1] # (T, J, 3)
# print(f"lft motion z mean: {np.mean(self.lft_motion[:, :, 2])}")
# print(f"rgt motion z mean: {np.mean(self.rgt_motion[:, :, 2])}")
self.lft_motioncode = MotionCoder(self.lft_motion, isright=False)
self.rgt_motioncode = MotionCoder(self.rgt_motion, isright=True)
self.tip_index = [
JOINT_NAME_INDEX_MAP['thumb_tip'],
JOINT_NAME_INDEX_MAP['index_tip'],
JOINT_NAME_INDEX_MAP['middle_tip'],
JOINT_NAME_INDEX_MAP['ring_tip'],
JOINT_NAME_INDEX_MAP['pinky_tip'],
]
self.unit_len = self.lft_motioncode.unit_len
def get_finger_finger_distance(self):
finger_finger_distance = []
tips = ['thumb_tip', 'index_tip', 'middle_tip', 'ring_tip', 'pinky_tip']
for i in range(len(tips)):
for j in range(len(tips)):
joint_pair = [JOINT_NAME_INDEX_MAP[tips[i]], JOINT_NAME_INDEX_MAP[tips[j]]]
dist = np.linalg.norm(self.lft_motion[:, joint_pair[0], :] - self.rgt_motion[:, joint_pair[1], :], axis=1) # (T,)
finger_finger_distance.append((
('left_' + tips[i], "right_" + tips[j]),
dist
))
# if tips[i] == 'index_tip' and tips[j] == 'index_tip':
# print(f"{joint_pair}:\n{dist}")
self.finger_finger_distance = finger_finger_distance
def get_finger_palm_distance(self):
finger_palm_distance, palm_finger_distance = [], []
tips = ['thumb_tip', 'index_tip', 'middle_tip', 'ring_tip', 'pinky_tip']
for i in range(len(tips)):
closest_vectors = compute_topk_closest_vectors(
self.lft_motion[:, JOINT_NAME_INDEX_MAP[tips[i]], :], # (T, 3)
self.rgt_motioncode.palm_points_glob, # (T, n, 3)
k=5
) # (T, k, 3)
closest_mean_dist = np.mean(np.linalg.norm(closest_vectors, axis=2), axis=1) # (T,)
finger_palm_distance.append((
('left_' + tips[i], "right_palm"),
closest_mean_dist
))
for i in range(len(tips)):
closest_vectors = compute_topk_closest_vectors(
self.rgt_motion[:, JOINT_NAME_INDEX_MAP[tips[i]], :], # (T, 3)
self.lft_motioncode.palm_points_glob, # (T, n, 3)
k=5
)
closest_mean_dist = np.mean(np.linalg.norm(closest_vectors, axis=2), axis=1)
palm_finger_distance.append((
('right_' + tips[i], "left_palm"),
closest_mean_dist
))
self.finger_palm_distance = finger_palm_distance
self.palm_finger_distance = palm_finger_distance
def get_palm_palm_distance(self):
closest_dist, closest_vec = compute_closest_distances(
self.lft_motioncode.palm_points_glob,
self.rgt_motioncode.palm_points_glob
)
self.palm_palm_vec = np.mean(self.rgt_motioncode.palm_points_glob, axis=1) - np.mean(self.lft_motioncode.palm_points_glob, axis=1) # (T, 3)
# print(f"palm_palm_vec: {self.palm_palm_vec}")
closest_dist_topk = topk_smallest_elements(closest_dist, k=30)
closest_dist_mean = np.mean(closest_dist_topk, axis=-1) # (T,)
self.palm_palm_distance = [(
('left_palm', 'right_palm'),
closest_dist_mean
)]
def extract_feats(self):
# print("GET LEFT MOTION FEATS")
self.lft_motioncode.extract_feats()
# print("GET RIGHT MOTION FEATS")
self.rgt_motioncode.extract_feats()
self.get_finger_finger_distance()
self.get_finger_palm_distance()
self.get_palm_palm_distance()
def split_finger_finger_events(self):
contact_thre = 0.020
self.finger_finger_distance_events = dict()
for pair, spacing in self.finger_finger_distance:
events = split_contact_events(spacing, contact_thre)
if len(events) > 0:
events = [
{k: v for k, v in x.items() if k != 'direction'}
for x in events
]
self.finger_finger_distance_events[', '.join(pair)] = events
def split_finger_palm_events(self):
spacing_thre = [
(0, 0.025, 'Contact'),
(0.025, 0.035, 'Near'),
(0.035, 100, 'Far')
]
vel_thre = [
(0, 0.025, 'Slow'),
(0.025, 0.07, 'Medium'),
(0.07, 100, 'Fast')
]
self.finger_palm_distance_events = dict()
for pair, spacing in self.finger_palm_distance:
events = split_events(
spacing, spacing_thre, vel_thre,
delta_thre=0.03,
min_duration=6,
diff_state=True,
unit_len=self.unit_len,
)
if len(events) > 0:
events = [
{k: v for k, v in x.items() if k != 'direction'}
for x in events
]
self.finger_palm_distance_events[', '.join(pair)] = events
for pair, spacing in self.palm_finger_distance:
events = split_events(spacing, spacing_thre, vel_thre)
if len(events) > 0:
events = [
{k: v for k, v in x.items() if k != 'direction'}
for x in events
]
self.finger_palm_distance_events[', '.join(pair)] = events
def split_palm_palm_events(self):
spacing_thre = [
(0, 0.04, 'Contact'),
(0.04, 0.08, 'Near'),
(0.08, 0.12, 'Medium'),
(0.12, 100, 'Far')
]
vel_thre = [
(0, 0.025, 'Slow'),
(0.025, 0.07, 'Medium'),
(0.07, 100, 'Fast')
]
self.palm_palm_distance_events = dict()
for pair, spacing in self.palm_palm_distance:
events = split_events(
x=spacing,
x_intervals=spacing_thre,
v_intervals=vel_thre,
delta_thre=0.05,
min_duration=6,
diff_state=True,
unit_len=self.unit_len,
)
if len(events) > 0:
events = [
{k: v for k, v in x.items() if k != 'direction'}
for x in events
]
self.palm_palm_distance_events[', '.join(pair)] = events
relation_thre = [
[
(-100, -0.02, 'right hand is to the LEFT of the left hand.'),
(-0.02, 0.02, 'right hand is ALIGNED with the left hand.'),
(0.02, 100, 'right hand is to the RIGHT of the left hand.')
],
[
(-100, -0.02, 'right hand is to the BACK of the left hand.'),
(-0.02, 0.02, 'right hand is ALIGNED with the left hand.'),
(0.02, 100, 'right hand is to the FRONT of the left hand.')],
[
(-100, -0.02, 'right hand is to the DOWN of the left hand.'),
(-0.02, 0.02, 'right hand is ALIGNED with the left hand.'),
(0.02, 100, 'right hand is to the UP of the left hand.')
]
]
self.palm_palm_relative_position_events = dict()
for i, axis in enumerate(['left-right', 'front-back', 'up-down']):
component = self.palm_palm_vec[:, i]
# print(f"axis: {axis} component:\n{component}")
events = split_events(
x=component,
x_intervals=relation_thre[i],
v_intervals=vel_thre,
delta_thre=0.05,
min_duration=4,
diff_state=True,
unit_len=self.unit_len,
)
if len(events) > 0:
events = [
{k: v for k, v in x.items() if k != 'direction'}
for x in events
]
self.palm_palm_relative_position_events[axis] = events
def extract_events(self):
self.lft_motioncode.extract_events()
self.rgt_motioncode.extract_events()
self.split_finger_finger_events()
self.split_finger_palm_events()
self.split_palm_palm_events()
def get_json(self):
events_summary = {
'frame_count': self.lft_motion.shape[0],
'left_hand_events': self.lft_motioncode.print_json(),
'right_hand_events': self.rgt_motioncode.print_json(),
'two_hand_relationships': {
'finger_tip_contact': self.finger_finger_distance_events,
'finger_palm_distance': self.finger_palm_distance_events,
'palm_palm_distance': self.palm_palm_distance_events,
'palm_palm_relative_position': self.palm_palm_relative_position_events
}
}
return events_summary
def generate_motion_codes(self):
self.extract_feats()
self.extract_events()