Removed old data extraction methods and documented all the new functions.
Browse files- fbx_handler.py +197 -410
fbx_handler.py
CHANGED
|
@@ -33,37 +33,22 @@ def center_axis(a: Union[List[float], np.array]) -> np.array:
|
|
| 33 |
return a
|
| 34 |
|
| 35 |
|
| 36 |
-
def
|
| 37 |
"""
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
:
|
| 41 |
-
:return: multidimensional `np.array` with the shape: (missing, 5).
|
| 42 |
"""
|
| 43 |
-
return np.column_stack([
|
| 44 |
-
np.zeros((missing, 1), dtype=int), # 0
|
| 45 |
-
np.zeros((missing, 1), dtype=int), # 0
|
| 46 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 47 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 48 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 49 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 50 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 51 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 52 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 53 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 54 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 55 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 56 |
-
np.random.rand(missing, 1), # 0.0-1.0
|
| 57 |
-
np.random.rand(missing, 1) # 0.0-1.0
|
| 58 |
-
])
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
def append_zero(arr: np.ndarray) -> np.ndarray:
|
| 62 |
zeros = np.zeros((arr.shape[0], arr.shape[1], 1), dtype=float)
|
| 63 |
return np.concatenate((arr, zeros), axis=-1)
|
| 64 |
|
| 65 |
|
| 66 |
def append_one(arr: np.ndarray) -> np.ndarray:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
ones = np.ones((arr.shape[0], arr.shape[1], 1), dtype=float)
|
| 68 |
return np.concatenate((arr, ones), axis=-1)
|
| 69 |
|
|
@@ -74,10 +59,19 @@ def merge_tdc(actor_classes: np.array,
|
|
| 74 |
rotation_vectors: np.array,
|
| 75 |
scale_vectors: np.array,
|
| 76 |
ordered: bool = True) -> np.array:
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
# splitting it into sub arrays.
|
| 80 |
-
|
| 81 |
tdc = np.concatenate((np.expand_dims(actor_classes, -1),
|
| 82 |
np.expand_dims(marker_classes, -1),
|
| 83 |
append_zero(translation_vectors),
|
|
@@ -111,7 +105,7 @@ def sort_cloud(cloud: np.array) -> np.array:
|
|
| 111 |
Convenience function to sort a timeline dense cloud by actor and marker classes.
|
| 112 |
Not required.
|
| 113 |
:param cloud: `np.array` point cloud to sort.
|
| 114 |
-
:return:
|
| 115 |
"""
|
| 116 |
# Extract the first two elements of the third dimension
|
| 117 |
actor_classes = cloud[:, :, 0]
|
|
@@ -131,7 +125,14 @@ def sort_cloud(cloud: np.array) -> np.array:
|
|
| 131 |
return sorted_tdc
|
| 132 |
|
| 133 |
|
| 134 |
-
def create_keyframe(anim_curve: fbx.FbxAnimCurve, frame: int, value: float):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
# Create an FbxTime object with the given frame number
|
| 136 |
t = fbx.FbxTime()
|
| 137 |
t.SetFrame(frame)
|
|
@@ -139,22 +140,43 @@ def create_keyframe(anim_curve: fbx.FbxAnimCurve, frame: int, value: float):
|
|
| 139 |
# Create a new keyframe with the specified value
|
| 140 |
key_index = anim_curve.KeyAdd(t)[0]
|
| 141 |
anim_curve.KeySetValue(key_index, value)
|
| 142 |
-
return
|
| 143 |
|
| 144 |
|
| 145 |
def get_child_node_by_name(parent_node: fbx.FbxNode, name: str, ignore_namespace: bool = False) \
|
| 146 |
-> Union[fbx.FbxNode, None]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
for c in range(parent_node.GetChildCount()):
|
|
|
|
| 148 |
child = parent_node.GetChild(c)
|
|
|
|
| 149 |
if match_name(child, name, ignore_namespace):
|
|
|
|
| 150 |
return child
|
|
|
|
| 151 |
return None
|
| 152 |
|
| 153 |
|
| 154 |
def match_name(node: fbx.FbxNode, name: str, ignore_namespace: bool = True) -> bool:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
node_name = node.GetName()
|
|
|
|
| 156 |
if ignore_namespace:
|
| 157 |
node_name = node_name.split(':')[-1]
|
|
|
|
| 158 |
return node_name == name
|
| 159 |
|
| 160 |
|
|
@@ -233,36 +255,6 @@ def world_to_local_transform(node: fbx.FbxNode, world_transform: fbx.FbxAMatrix,
|
|
| 233 |
return [lcl.GetT()[t] for t in range(3)], [lcl.GetR()[r] for r in range(3)], [lcl.GetS()[s] for s in range(3)]
|
| 234 |
|
| 235 |
|
| 236 |
-
def get_world_transform(m: fbx.FbxNode, t: fbx.FbxTime, axes: str = 'trs') -> np.array:
|
| 237 |
-
"""
|
| 238 |
-
Evaluates the world translation of the given node at the given time,
|
| 239 |
-
scales it down by scale and turns it into a vector list.
|
| 240 |
-
:param m: `fbx.FbxNode` marker to evaluate the world translation of.
|
| 241 |
-
:param t: `fbx.FbxTime` time to evaluate at.
|
| 242 |
-
:param axes: `str` that contains types of info to include. Options are a combination of t, r, and s.
|
| 243 |
-
:return: Vector in the form: [tx, ty, etc..].
|
| 244 |
-
"""
|
| 245 |
-
matrix = m.EvaluateGlobalTransform(t)
|
| 246 |
-
|
| 247 |
-
# If axes is only the translation, we return a vector of (tx, ty, tz) only (useful for the training).
|
| 248 |
-
if axes == 't':
|
| 249 |
-
return np.array([matrix[i] for i in range(3)])
|
| 250 |
-
|
| 251 |
-
# Otherwise, we assemble the entire row depending on the axes.
|
| 252 |
-
world = []
|
| 253 |
-
if 't' in axes:
|
| 254 |
-
world += list(matrix.GetT())
|
| 255 |
-
world[3] = 0.0
|
| 256 |
-
if 'r' in axes:
|
| 257 |
-
world += list(matrix.GetR())
|
| 258 |
-
world[7] = 0.0
|
| 259 |
-
if 's' in axes:
|
| 260 |
-
world += list(matrix.GetS())
|
| 261 |
-
world[11] = 1.0
|
| 262 |
-
|
| 263 |
-
return np.array(world)
|
| 264 |
-
|
| 265 |
-
|
| 266 |
def isolate_actor_from_tdc(tdc: np.array, actor: int) -> np.array:
|
| 267 |
"""
|
| 268 |
Returns all markers of the given actor in the timeline dense cloud.
|
|
@@ -287,23 +279,56 @@ def split_tdc_into_actors(tdc: np.array) -> List[np.array]:
|
|
| 287 |
|
| 288 |
|
| 289 |
def get_keyed_frames_from_curve(curve: fbx.FbxAnimCurve, length: int = -1) -> List[fbx.FbxAnimCurveKey]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
frames = [curve.KeyGet(i).GetTime().GetFrameCount() for i in range(curve.KeyGetCount())]
|
|
|
|
| 291 |
dif = length - len(frames)
|
|
|
|
| 292 |
if dif > 0 and length != -1:
|
| 293 |
frames += [0.] * dif
|
|
|
|
| 294 |
return frames
|
| 295 |
|
| 296 |
|
| 297 |
-
def get_world_transforms(actor_idx: int, marker_idx: int, m: fbx.FbxNode,
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
zeros = [0.0 for _ in range(len(r))]
|
|
|
|
| 300 |
ones = [1.0 for _ in range(len(r))]
|
| 301 |
|
|
|
|
| 302 |
tx, ty, tz, rx, ry, rz, sx, sy, sz = [], [], [], [], [], [], [], [], []
|
|
|
|
| 303 |
actors = [actor_idx for _ in range(len(r))]
|
|
|
|
| 304 |
markers = [marker_idx for _ in range(len(r))]
|
|
|
|
| 305 |
t = fbx.FbxTime()
|
| 306 |
|
|
|
|
|
|
|
| 307 |
for f in r:
|
| 308 |
t.SetFrame(f)
|
| 309 |
wt = m.EvaluateGlobalTransform(t)
|
|
@@ -318,6 +343,7 @@ def get_world_transforms(actor_idx: int, marker_idx: int, m: fbx.FbxNode, r: Lis
|
|
| 318 |
sy.append(wts[1])
|
| 319 |
sz.append(wts[2])
|
| 320 |
|
|
|
|
| 321 |
if not incl_keyed:
|
| 322 |
return [
|
| 323 |
actors,
|
|
@@ -327,9 +353,14 @@ def get_world_transforms(actor_idx: int, marker_idx: int, m: fbx.FbxNode, r: Lis
|
|
| 327 |
sx, sy, sz, ones
|
| 328 |
]
|
| 329 |
|
|
|
|
|
|
|
|
|
|
| 330 |
keyed_frames = get_keyed_frames_from_curve(c)
|
|
|
|
| 331 |
keyed_bools = [1 if f in keyed_frames else 0 for f in r]
|
| 332 |
|
|
|
|
| 333 |
return [
|
| 334 |
actors,
|
| 335 |
markers,
|
|
@@ -347,9 +378,7 @@ class FBXContainer:
|
|
| 347 |
pc_size: int = 1024,
|
| 348 |
scale: float = 0.01,
|
| 349 |
debug: int = -1,
|
| 350 |
-
save_init: bool = True
|
| 351 |
-
r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 352 |
-
mode: str = 'train'):
|
| 353 |
"""
|
| 354 |
Class that stores references to important nodes in an FBX file.
|
| 355 |
Offers utility functions to quickly load animation data.
|
|
@@ -396,14 +425,13 @@ class FBXContainer:
|
|
| 396 |
self.pc_size = pc_size
|
| 397 |
|
| 398 |
self.input_fbx = fbx_file
|
| 399 |
-
# self.output_fbx = append_suffix_to_fbx(fbx_file, '_INF')
|
| 400 |
self.output_fbx = utils.append_suffix_to_file(fbx_file, '_INF')
|
| 401 |
self.valid_frames = []
|
| 402 |
|
| 403 |
# If we know that the input file has valid data,
|
| 404 |
# we can automatically call the init function and ignore missing data.
|
| 405 |
if save_init:
|
| 406 |
-
self.init(
|
| 407 |
|
| 408 |
def __init_scene(self) -> None:
|
| 409 |
"""
|
|
@@ -498,7 +526,7 @@ class FBXContainer:
|
|
| 498 |
|
| 499 |
self.markers.append(actor_markers)
|
| 500 |
|
| 501 |
-
def __init_unlabeled_markers(self, ignore_missing: bool =
|
| 502 |
"""
|
| 503 |
Looks for the Unlabeled_Markers parent node under the root and stores references to all unlabeled marker nodes.
|
| 504 |
"""
|
|
@@ -514,47 +542,86 @@ class FBXContainer:
|
|
| 514 |
raise ValueError('No unlabeled markers found.')
|
| 515 |
|
| 516 |
def init_world_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> None:
|
| 517 |
-
|
|
|
|
|
|
|
|
|
|
| 518 |
self.init_labeled_world_transforms(r=r, incl_keyed=1)
|
| 519 |
self.init_unlabeled_world_transforms(r=r)
|
| 520 |
|
| 521 |
def init_labeled_world_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 522 |
-
incl_keyed: int = 1):
|
| 523 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 524 |
r = self.convert_r(r)
|
| 525 |
labeled_data = []
|
| 526 |
|
|
|
|
| 527 |
for actor_idx in range(self.actor_count):
|
|
|
|
| 528 |
actor_data = []
|
|
|
|
| 529 |
for marker_idx, (n, m) in enumerate(self.markers[actor_idx].items()):
|
|
|
|
|
|
|
| 530 |
curve = m.LclTranslation.GetCurve(self.anim_layer, 'X', True)
|
|
|
|
| 531 |
marker_data = get_world_transforms(actor_idx + 1, marker_idx + 1, m, r, curve, incl_keyed)
|
|
|
|
| 532 |
actor_data.append(marker_data)
|
| 533 |
self._print(f'Actor {actor_idx} marker {marker_idx} done', 1)
|
|
|
|
| 534 |
labeled_data.append(actor_data)
|
| 535 |
|
|
|
|
|
|
|
| 536 |
wide_layout = np.array(labeled_data)
|
|
|
|
|
|
|
| 537 |
self.labeled_world_transforms = np.transpose(wide_layout, axes=(3, 0, 1, 2))
|
| 538 |
return self.labeled_world_transforms
|
| 539 |
|
| 540 |
def init_unlabeled_world_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> np.array:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
r = self.convert_r(r)
|
| 542 |
-
|
| 543 |
unlabeled_data = []
|
| 544 |
|
|
|
|
| 545 |
for ulm in self.unlabeled_markers:
|
|
|
|
|
|
|
| 546 |
curve = ulm.LclTranslation.GetCurve(self.anim_layer, 'X', True)
|
|
|
|
| 547 |
marker_data = get_world_transforms(0, 0, ulm, r, curve, incl_keyed=0)
|
|
|
|
| 548 |
unlabeled_data.append(marker_data)
|
| 549 |
self._print(f'Unlabeled marker {ulm.GetName()} done', 1)
|
| 550 |
|
|
|
|
|
|
|
| 551 |
wide_layout = np.array(unlabeled_data)
|
|
|
|
|
|
|
| 552 |
self.unlabeled_world_transforms = np.transpose(wide_layout, axes=(2, 0, 1))
|
| 553 |
# Returns shape (n_frames, n_unlabeled_markers, 14).
|
| 554 |
return self.unlabeled_world_transforms
|
| 555 |
|
| 556 |
-
def init(self, ignore_missing_labeled: bool = False, ignore_missing_unlabeled: bool = False
|
| 557 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
self.__init_scene()
|
| 559 |
self.__init_anim()
|
| 560 |
self.__init_actors(ignore_missing=ignore_missing_labeled)
|
|
@@ -574,120 +641,6 @@ class FBXContainer:
|
|
| 574 |
if not 0 <= actor <= self.actor_count:
|
| 575 |
raise ValueError(f'Actor index must be between 0 and {self.actor_count - 1} ({actor}).')
|
| 576 |
|
| 577 |
-
def _set_valid_frames_for_actor(self, actor: int = 0):
|
| 578 |
-
"""
|
| 579 |
-
Checks for each frame in the frame range, and for each marker, if there is a keyframe present
|
| 580 |
-
at that frame on LocalTranslation X.
|
| 581 |
-
If the keyframe is missing, removes that frame from the list of valid frames for that actor.
|
| 582 |
-
This eventually leaves a list of frames where each number is guaranteed to have a keyframe on all markers.
|
| 583 |
-
The list is appended to valid_frames, which can be indexed per actor.
|
| 584 |
-
Finally, stores a list of frames that is valid for all actors in common_frames.
|
| 585 |
-
:param actor: `int` index of the actor to find keyframes for.
|
| 586 |
-
"""
|
| 587 |
-
# Make sure the actor index is in range.
|
| 588 |
-
self._check_actor(actor)
|
| 589 |
-
|
| 590 |
-
frames = self.get_frame_range()
|
| 591 |
-
for n, marker in self.markers[actor].items():
|
| 592 |
-
# Get the animation curve for local translation x.
|
| 593 |
-
t_curve = marker.LclTranslation.GetCurve(self.anim_layer, 'X')
|
| 594 |
-
# If an actor was recorded but seems to have no animation curves, we set their valid frames to nothing.
|
| 595 |
-
# Then we return, because there is no point in further checking non-existent keyframes.
|
| 596 |
-
if t_curve is None:
|
| 597 |
-
self.valid_frames[actor] = []
|
| 598 |
-
self._print('Found no animation curve', 2)
|
| 599 |
-
return
|
| 600 |
-
|
| 601 |
-
# Get all keyframes on the animation curve and store their frame numbers.
|
| 602 |
-
self._print(f'Checking keyframes for {n}', 2)
|
| 603 |
-
keys = [t_curve.KeyGet(i).GetTime().GetFrameCount() for i in range(t_curve.KeyGetCount())]
|
| 604 |
-
# Check for each frame in frames if it is present in the list of keyframed frames.
|
| 605 |
-
for frame in frames:
|
| 606 |
-
if frame not in keys:
|
| 607 |
-
# If the frame is not present, that means there is no keyframe with that frame number,
|
| 608 |
-
# so we don't want to use that frame because it is invalid, so we remove it from the list.
|
| 609 |
-
with contextlib.suppress(ValueError):
|
| 610 |
-
frames.remove(frame)
|
| 611 |
-
|
| 612 |
-
self._print(f'Found {len(frames)}/{self.num_frames} valid frames for {self.actor_names[actor]}', 1)
|
| 613 |
-
self.valid_frames[actor] = frames
|
| 614 |
-
|
| 615 |
-
# Store all frame lists that have at least 1 frame.
|
| 616 |
-
other_lists = [r for r in self.valid_frames if r]
|
| 617 |
-
# Make one list that contains all shared frame numbers.
|
| 618 |
-
self.common_frames = [num for num in self.get_frame_range()
|
| 619 |
-
if all(num in other_list for other_list in other_lists)]
|
| 620 |
-
|
| 621 |
-
def _check_valid_frames(self, actor: int = 0):
|
| 622 |
-
"""
|
| 623 |
-
Safety check to see if the given actor has any valid frames stored.
|
| 624 |
-
If not, calls _set_valid_frames_for_actor() for that actor.
|
| 625 |
-
:param actor: `int` actor index.
|
| 626 |
-
"""
|
| 627 |
-
self._check_actor(actor)
|
| 628 |
-
|
| 629 |
-
if not len(self.valid_frames[actor]):
|
| 630 |
-
self._print(f'Getting missing valid frames for {self.actor_names[actor]}', 1)
|
| 631 |
-
self._set_valid_frames_for_actor(actor)
|
| 632 |
-
|
| 633 |
-
def get_transformed_axes(self, actor: int = 0, frame: int = 0) -> Tuple[np.array, np.array, np.array]:
|
| 634 |
-
"""
|
| 635 |
-
Evaluates all marker nodes for the given actor and modifies the resulting point cloud,
|
| 636 |
-
so it is centered and scaled properly for training.
|
| 637 |
-
:param actor: `int` actor index.
|
| 638 |
-
:param frame: `int` frame to evaluate the markers at.
|
| 639 |
-
:return: 1D list of `float` that contains the tx, ty and tz for each marker, in that order.
|
| 640 |
-
"""
|
| 641 |
-
# Set new frame to evaluate at.
|
| 642 |
-
time = fbx.FbxTime()
|
| 643 |
-
time.SetFrame(frame)
|
| 644 |
-
# Prepare arrays for each axis.
|
| 645 |
-
x, y, z = [], [], []
|
| 646 |
-
|
| 647 |
-
# For each marker, store the x, y and z global position.
|
| 648 |
-
for n, m in self.markers[actor].items():
|
| 649 |
-
t = m.EvaluateGlobalTransform(time).GetT()
|
| 650 |
-
x += [t[0] * self.scale]
|
| 651 |
-
y += [t[1] * self.scale]
|
| 652 |
-
z += [t[2] * self.scale]
|
| 653 |
-
|
| 654 |
-
# Move the point cloud to the center of the x and y axes. This will put the actor in the middle.
|
| 655 |
-
x = center_axis(x)
|
| 656 |
-
z = center_axis(z)
|
| 657 |
-
|
| 658 |
-
# Move the actor to the middle of the volume floor by adding volume_dim/2 to x and z.
|
| 659 |
-
x += self.vol_x / 2.
|
| 660 |
-
z += self.vol_z / 2.
|
| 661 |
-
|
| 662 |
-
# Squeeze the actor into the 1x1 plane for the neural network by dividing the axes.
|
| 663 |
-
x /= self.vol_x
|
| 664 |
-
z /= self.vol_z
|
| 665 |
-
y = np.array(y) / self.vol_y
|
| 666 |
-
|
| 667 |
-
# EXTRA: Add any extra modifications to the point cloud here.
|
| 668 |
-
|
| 669 |
-
return x, y, z
|
| 670 |
-
|
| 671 |
-
def get_transformed_pc(self, actor: int = 0, frame: int = 0, t: str = 'np') -> Union[np.array, List[float]]:
|
| 672 |
-
|
| 673 |
-
x, y, z = self.get_transformed_axes(actor, frame)
|
| 674 |
-
# If we need to return a numpy array, simply vstack the axes to get a shape of (3, 73).
|
| 675 |
-
# This is in preparation for PyTorch's CNN layers that use input shape (batch_size, C, H, W).
|
| 676 |
-
if t == 'np':
|
| 677 |
-
# Exports shape of (3, 9, 9).
|
| 678 |
-
# return make_pc_ghost_markers(np.vstack((x, y, z)))
|
| 679 |
-
# Exports shape of (1, 3, 73).
|
| 680 |
-
return np.vstack((x, y, z))[None, ...]
|
| 681 |
-
|
| 682 |
-
# Append all values to a new array, one axis at a time.
|
| 683 |
-
# This way it will match the column names order.
|
| 684 |
-
pose = []
|
| 685 |
-
for i in range(len(x)):
|
| 686 |
-
pose += [x[i]]
|
| 687 |
-
pose += [y[i]]
|
| 688 |
-
pose += [z[i]]
|
| 689 |
-
return pose
|
| 690 |
-
|
| 691 |
def get_frame_range(self) -> List[int]:
|
| 692 |
"""
|
| 693 |
Replacement and improvement for:
|
|
@@ -697,7 +650,12 @@ class FBXContainer:
|
|
| 697 |
"""
|
| 698 |
return list(range(self.start_frame, self.end_frame))
|
| 699 |
|
| 700 |
-
def convert_r(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 701 |
# If r is one int, use 0 as start frame. If r is higher than the total frames, limit the range.
|
| 702 |
if isinstance(r, int):
|
| 703 |
r = list(range(self.num_frames)) if r > self.num_frames else list(range(r))
|
|
@@ -782,82 +740,12 @@ class FBXContainer:
|
|
| 782 |
# Return the last node in the chain, which will be the node we were looking for.
|
| 783 |
return nodes[-1]
|
| 784 |
|
| 785 |
-
def
|
| 786 |
-
"""
|
| 787 |
-
Prints: actor name, total amount of frames in the animation, amount of valid frames for the given actor,
|
| 788 |
-
number of missing frames, and the ratio of valid/total frames.
|
| 789 |
-
:param actor: `int` actor index.
|
| 790 |
-
:return: Tuple of `str` actor name, `int` total frames, `int` amount of valid frames, `float` valid frame ratio.
|
| 791 |
-
"""
|
| 792 |
-
self._check_actor(actor)
|
| 793 |
-
self._check_valid_frames(actor)
|
| 794 |
-
|
| 795 |
-
len_valid = len(self.valid_frames[actor])
|
| 796 |
-
ratio = (len_valid / self.num_frames) * 100
|
| 797 |
-
print(f'Actor {self.actor_names[actor]}: Total: {self.num_frames}, valid: {len_valid}, missing: '
|
| 798 |
-
f'{self.num_frames - len_valid}, ratio: {ratio:.2f}% valid.')
|
| 799 |
-
|
| 800 |
-
return self.actor_names[actor], self.num_frames, len_valid, ratio
|
| 801 |
-
|
| 802 |
-
def get_valid_frames_for_actor(self, actor: int = 0) -> List[int]:
|
| 803 |
-
"""
|
| 804 |
-
Collects the valid frames for the given actor.
|
| 805 |
-
:param actor: `int` actor index.
|
| 806 |
-
:return: List of `int` frame numbers that have a keyframe on tx for all markers.
|
| 807 |
"""
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
def extract_valid_translations_per_actor(self, actor: int = 0, t: str = 'np'):
|
| 812 |
"""
|
| 813 |
-
Assembles the poses for the valid frames for the given actor as a 2D list where each row is a pose.
|
| 814 |
-
:param actor: `int` actor index.
|
| 815 |
-
:param t: If 'np', returns a (3, -1) `np.array`. Otherwise returns a list of floats.
|
| 816 |
-
:return: List of poses, where each pose is a list of `float` translations.
|
| 817 |
-
"""
|
| 818 |
-
# Ensure the actor index is within range.
|
| 819 |
-
self._check_actor(actor)
|
| 820 |
-
self._check_valid_frames(actor)
|
| 821 |
-
|
| 822 |
-
# Returns shape (n_valid_frames, 3, 73).
|
| 823 |
-
return np.vstack([self.get_transformed_pc(actor, frame) for frame in self.valid_frames[actor]])
|
| 824 |
-
|
| 825 |
-
# poses = []
|
| 826 |
-
# # Go through all valid frames for this actor.
|
| 827 |
-
# # Note that these frames can be different per actor.
|
| 828 |
-
# for frame in self.valid_frames[actor]:
|
| 829 |
-
# self._print(f' Extracting frame: {frame}', 1)
|
| 830 |
-
# # Get the centered point cloud as a 1D list.
|
| 831 |
-
# pose_at_frame = self.get_transformed_pc(actor, frame, t)
|
| 832 |
-
# poses.append(pose_at_frame)
|
| 833 |
-
#
|
| 834 |
-
# return np.array(poses) if t == 'np' else poses
|
| 835 |
-
|
| 836 |
-
def extract_all_valid_translations(self, t: str = 'np') -> Union[np.array, pd.DataFrame]:
|
| 837 |
-
"""
|
| 838 |
-
Convenience method that calls self.extract_valid_translations_per_actor() for all actors
|
| 839 |
-
and returns a `DataFrame` containing all poses after each other.
|
| 840 |
-
:param t: If 'np', returns a `np.array`. Otherwise, returns a DataFrame.
|
| 841 |
-
:return: `np.array` or `DataFrame` where each row is a pose.
|
| 842 |
-
"""
|
| 843 |
-
# Returns shape (n_total_valid_frames, 3, 73).
|
| 844 |
-
return np.vstack([self.extract_valid_translations_per_actor(i) for i in range(self.actor_count)])
|
| 845 |
-
# all_poses = []
|
| 846 |
-
# # For each actor, add their valid poses to all_poses.
|
| 847 |
-
# for i in range(self.actor_count):
|
| 848 |
-
# self._print(f'Extracting actor {self.actor_names[i]}', 0)
|
| 849 |
-
# all_poses.extend(self.extract_valid_translations_per_actor(i, t))
|
| 850 |
-
#
|
| 851 |
-
# self._print('Extracting finished')
|
| 852 |
-
# # Note that the column names are/must be in the same order as the markers.
|
| 853 |
-
# if t == 'np':
|
| 854 |
-
# # Shape: (n_poses, 3, 73).
|
| 855 |
-
# return np.array(all_poses)
|
| 856 |
-
# else:
|
| 857 |
-
# return pd.DataFrame(all_poses, columns=self.columns_from_joints())
|
| 858 |
-
|
| 859 |
-
def remove_clipping_poses(self, arr: np.array) -> np.array:
|
| 860 |
-
|
| 861 |
mask_x1 = (arr[:, :, 2] < self.hvol_x / self.scale).all(axis=1)
|
| 862 |
mask_x2 = (arr[:, :, 2] > -self.hvol_x / self.scale).all(axis=1)
|
| 863 |
mask_z1 = (arr[:, :, 4] < self.hvol_z / self.scale).all(axis=1)
|
|
@@ -867,6 +755,13 @@ class FBXContainer:
|
|
| 867 |
return arr[mask]
|
| 868 |
|
| 869 |
def extract_training_translations(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> np.array:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 870 |
if self.labeled_world_transforms is None:
|
| 871 |
self.init_labeled_world_transforms(r=r, incl_keyed=1)
|
| 872 |
|
|
@@ -896,12 +791,25 @@ class FBXContainer:
|
|
| 896 |
|
| 897 |
def extract_inf_translations(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 898 |
merged: bool = True) -> Union[np.array, Tuple[np.array, np.array]]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 899 |
if self.labeled_world_transforms is None:
|
|
|
|
| 900 |
self.init_labeled_world_transforms(r=r, incl_keyed=0)
|
| 901 |
if self.unlabeled_world_transforms is None:
|
|
|
|
| 902 |
self.init_unlabeled_world_transforms(r=r)
|
| 903 |
|
|
|
|
| 904 |
ls = self.labeled_world_transforms.shape
|
|
|
|
| 905 |
# Returns shape (n_frames, 73 * n_actors, 14).
|
| 906 |
flat_labeled = self.labeled_world_transforms.reshape(ls[0], -1, ls[-1])[..., :14]
|
| 907 |
|
|
@@ -918,7 +826,6 @@ class FBXContainer:
|
|
| 918 |
:param w: `np.array` that can either be a timeline dense cloud or translation vectors.
|
| 919 |
:return: Modified `np.array`.
|
| 920 |
"""
|
| 921 |
-
|
| 922 |
# If the last dimension has 3 elements, it is a translation vector of shape (tx, ty, tz).
|
| 923 |
# If it has 14 elements, it is a full marker row of shape (actor, marker, tx, ty, tz, tw, rx, ry, rz, tw, etc).
|
| 924 |
start = 0 if w.shape[-1] == 3 else 2
|
|
@@ -931,144 +838,15 @@ class FBXContainer:
|
|
| 931 |
|
| 932 |
# Then move the x and z to the center of the volume. Y doesn't need to be done because pose needs to stand
|
| 933 |
# on the floor.
|
| 934 |
-
#
|
| 935 |
-
|
|
|
|
|
|
|
| 936 |
w[..., start + 1] = np.clip(w[..., start + 1], -0.5, 0.5)
|
| 937 |
-
w[..., start + 2] = np.clip(w[..., start + 2], -0.5, 0.5)
|
| 938 |
|
| 939 |
return w
|
| 940 |
|
| 941 |
-
def is_kf_present(self, marker: fbx.FbxNode, time: fbx.FbxTime) -> bool:
|
| 942 |
-
"""
|
| 943 |
-
Returns True if a keyframe is found on the given node's local translation x animation curve.
|
| 944 |
-
Else returns False.
|
| 945 |
-
:param marker: `fbx.FbxNode` marker node to evaluate.
|
| 946 |
-
:param time: `fbx.FbxTime` time to evaluate at.
|
| 947 |
-
:return: True if a keyframe was found, False otherwise.
|
| 948 |
-
"""
|
| 949 |
-
curve = marker.LclTranslation.GetCurve(self.anim_layer, 'X')
|
| 950 |
-
return False if curve is None else curve.KeyFind(time) != -1
|
| 951 |
-
|
| 952 |
-
def get_sc(self, frame: int) -> np.array:
|
| 953 |
-
"""
|
| 954 |
-
For each actor at the given time, find all markers with keyframes and add their values to a point cloud.
|
| 955 |
-
:param frame: `fbx.FbxTime` time at which to evaluate the marker.
|
| 956 |
-
:return: sparse point cloud as `np.array`.
|
| 957 |
-
"""
|
| 958 |
-
time = fbx.FbxTime()
|
| 959 |
-
time.SetFrame(frame)
|
| 960 |
-
# Start with a cloud of unlabeled markers, which will use actor and marker class 0.
|
| 961 |
-
# It is important to start with these before the labeled markers,
|
| 962 |
-
# because by adding the labeled markers after (which use classes 1-74),
|
| 963 |
-
# we eventually return an array that doesn't need to be sorted anymore.
|
| 964 |
-
cloud = [
|
| 965 |
-
[0, 0, *get_world_transform(m, time)]
|
| 966 |
-
for m in self.unlabeled_markers
|
| 967 |
-
if self.is_kf_present(m, time)
|
| 968 |
-
]
|
| 969 |
-
|
| 970 |
-
# Iterate through all actors to get their markers' world translations and add them to the cloud list.
|
| 971 |
-
for actor_idx in range(self.actor_count):
|
| 972 |
-
cloud.extend(
|
| 973 |
-
# This actor's point cloud is made up of all markers that have a keyframe at the given time.
|
| 974 |
-
# For each marker, we create this row: [actor class (index+1), marker class (index+1), tx, ty, tz].
|
| 975 |
-
# We use index+1 because the unlabeled markers will use index 0 for both classes.
|
| 976 |
-
[actor_idx + 1, marker_class, *get_world_transform(m, time)]
|
| 977 |
-
for marker_class, (marker_name, m) in enumerate(
|
| 978 |
-
self.markers[actor_idx].items(), start=1
|
| 979 |
-
)
|
| 980 |
-
# Only add the marker if it has a keyframe. Missing keyframes on these markers are potentially
|
| 981 |
-
# among the keyframes on the unlabeled markers. The job of the labeler AI is to predict which
|
| 982 |
-
# point (unlabeled or labeled) is which marker.
|
| 983 |
-
if self.is_kf_present(m, time)
|
| 984 |
-
)
|
| 985 |
-
|
| 986 |
-
# If the data is extremely noisy, it might have only a few labeled markers and a lot of unlabeled markers.
|
| 987 |
-
# The returned point cloud is not allowed to be bigger than the maximum size (self.pc_size),
|
| 988 |
-
# so return the cloud as a np array that cuts off any excessive markers.
|
| 989 |
-
return np.array(cloud)[:self.pc_size]
|
| 990 |
-
|
| 991 |
-
def get_dc(self, frame: int = 0) -> np.array:
|
| 992 |
-
self._print(f'Getting sparse cloud for frame {frame}', 2)
|
| 993 |
-
cloud = self.get_sc(frame)
|
| 994 |
-
missing = self.pc_size - cloud.shape[0]
|
| 995 |
-
|
| 996 |
-
# Only bother creating ghost markers if there are any missing rows.
|
| 997 |
-
# If we need to add ghost markers, add them before the existing cloud,
|
| 998 |
-
# so that the cloud will remain a sorted array regarding the actor and marker classes.
|
| 999 |
-
if missing > 0:
|
| 1000 |
-
self._print('Making ghost markers', 2)
|
| 1001 |
-
ghost_cloud = make_ghost_markers(missing)
|
| 1002 |
-
cloud = np.vstack([ghost_cloud, cloud])
|
| 1003 |
-
|
| 1004 |
-
return cloud
|
| 1005 |
-
|
| 1006 |
-
def get_tsc(self) -> np.array:
|
| 1007 |
-
"""
|
| 1008 |
-
Convenience method that calls self.get_sparse_cloud() for all frames in the frame range
|
| 1009 |
-
and returns the combined result.
|
| 1010 |
-
:return: `np.array` that contains a sparse cloud for each frame in the frame range.
|
| 1011 |
-
"""
|
| 1012 |
-
return np.array([self.get_sc(f) for f in self.get_frame_range()])
|
| 1013 |
-
|
| 1014 |
-
def get_tdc(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> np.array:
|
| 1015 |
-
"""
|
| 1016 |
-
For each frame in the frame range, collects the point cloud that is present in the file.
|
| 1017 |
-
Then it creates a ghost cloud of random markers that are treated as unlabeled markers,
|
| 1018 |
-
and adds them together to create a dense cloud whose shape is always (self.pc_size, 5).
|
| 1019 |
-
Optionally shuffles this dense cloud before adding it to the final list.
|
| 1020 |
-
:param r: tuple of `int` that indicates the frame range to get. Default is None,
|
| 1021 |
-
resulting in the animation frame range.
|
| 1022 |
-
:return: `np.array` that contains a dense point cloud for each frame,
|
| 1023 |
-
with a shape of (self.num_frames, self.pc_size, 5).
|
| 1024 |
-
"""
|
| 1025 |
-
|
| 1026 |
-
r = self.convert_r(r)
|
| 1027 |
-
|
| 1028 |
-
# results = utils.parallel_process(r, self.get_dc)
|
| 1029 |
-
|
| 1030 |
-
return np.array([self.get_dc(f) for f in r])
|
| 1031 |
-
|
| 1032 |
-
def modify_actor_pose(self, actor: np.array) -> np.array:
|
| 1033 |
-
# Scale to cm.
|
| 1034 |
-
actor[:, 2:5] *= self.scale
|
| 1035 |
-
# Move the point cloud to the center of the x and y axes. This will put the actor in the middle.
|
| 1036 |
-
for axis in range(2, 5):
|
| 1037 |
-
actor[:, axis] = center_axis(actor[:, axis])
|
| 1038 |
-
|
| 1039 |
-
# Move the actor to the middle of the volume floor by adding volume_dim/2 to x and z.
|
| 1040 |
-
actor[:, 2] += self.vol_x / 2.
|
| 1041 |
-
actor[:, 4] += self.vol_z / 2.
|
| 1042 |
-
|
| 1043 |
-
# Squeeze the actor into the 1x1 plane for the neural network by dividing the axes.
|
| 1044 |
-
actor[:, 2] /= self.vol_x
|
| 1045 |
-
actor[:, 3] /= self.vol_y
|
| 1046 |
-
actor[:, 4] /= self.vol_z
|
| 1047 |
-
|
| 1048 |
-
def split_tdc(self, cloud: np.array = None) \
|
| 1049 |
-
-> Tuple[np.array, np.array, np.array, np.array, np.array]:
|
| 1050 |
-
"""
|
| 1051 |
-
Splits a timeline dense cloud with shape (self.num_frames, self.pc_size, 5) into 3 different
|
| 1052 |
-
arrays:
|
| 1053 |
-
1. A `np.array` with the actor classes as shape (self.num_frames, self.pc_size, 1).
|
| 1054 |
-
2. A `np.array` with the marker classes as shape (self.num_frames, self.pc_size, 1).
|
| 1055 |
-
3. A `np.array` with the translation floats as shape (self.num_frames, self.pc_size, 4).
|
| 1056 |
-
4. A `np.array` with the rotation Euler angles as shape (self.num_frames, self.pc_size, 3).
|
| 1057 |
-
:param cloud: `np.array` of shape (self.num_frames, self.pc_size, 5) that contains a dense point cloud
|
| 1058 |
-
(self.pc_size, 5) per frame in the frame range.
|
| 1059 |
-
:return: Return tuple of `np.array` as (actor classes, marker classes, translation vectors).
|
| 1060 |
-
"""
|
| 1061 |
-
if cloud is None:
|
| 1062 |
-
cloud = self.extract_inf_translations()
|
| 1063 |
-
|
| 1064 |
-
if cloud.shape[1] != self.pc_size:
|
| 1065 |
-
raise ValueError(f"Dense cloud doesn't have enough points. {cloud.shape[1]}/{self.pc_size}.")
|
| 1066 |
-
if cloud.shape[2] < 14:
|
| 1067 |
-
raise ValueError(f"Dense cloud is missing columns: {cloud.shape[2]}.")
|
| 1068 |
-
|
| 1069 |
-
# Return np arrays as (actor classes, marker classes, translation vectors, rotation vectors, scale vectors).
|
| 1070 |
-
return cloud[:, :, 0], cloud[:, :, 1], cloud[:, :, 2:5], cloud[:, :, 6:9], cloud[:, :, 10:13]
|
| 1071 |
-
|
| 1072 |
def get_split_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 1073 |
mode: str = 'train') -> Tuple[np.array, np.array, np.array, np.array, np.array]:
|
| 1074 |
"""
|
|
@@ -1102,17 +880,13 @@ class FBXContainer:
|
|
| 1102 |
|
| 1103 |
def export_train_data(self, output_file: Path, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) \
|
| 1104 |
-> Union[bytes, pd.DataFrame, np.array]:
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
|
| 1111 |
-
|
| 1112 |
-
self._print(f'Exported train data to {output_file}', 0)
|
| 1113 |
-
return array_4d
|
| 1114 |
-
|
| 1115 |
-
elif output_file.suffix == '.h5':
|
| 1116 |
array_4d = self.extract_training_translations(r)
|
| 1117 |
with h5py.File(output_file, 'w') as h5f:
|
| 1118 |
h5f.create_dataset('array_data', data=array_4d, compression='gzip', compression_opts=9)
|
|
@@ -1120,10 +894,17 @@ class FBXContainer:
|
|
| 1120 |
return array_4d
|
| 1121 |
|
| 1122 |
else:
|
| 1123 |
-
raise ValueError('Invalid file extension. Must be .
|
| 1124 |
|
| 1125 |
def export_test_data(self, output_file: Path, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 1126 |
merged: bool = True) -> Union[np.array, Tuple[np.array, np.array]]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1127 |
# Retrieve the clean world transforms.
|
| 1128 |
# If merged is True, this will be one array of shape (n_frames, pc_size, 14).
|
| 1129 |
# If merged is False, this will be two arrays, one of shape (n_frames, 73 * n_actors, 14),
|
|
@@ -1281,6 +1062,12 @@ class FBXContainer:
|
|
| 1281 |
self.set_default_lcl_scaling(marker, lcl_s)
|
| 1282 |
|
| 1283 |
def replace_keyframes_per_marker(self, marker: fbx.FbxNode, marker_keys: dict) -> None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1284 |
# Initialize empty variables for the local rotation and scaling.
|
| 1285 |
# These will be filled at the first keyframe
|
| 1286 |
self.set_default_lcl_transforms(marker, marker_keys)
|
|
|
|
| 33 |
return a
|
| 34 |
|
| 35 |
|
| 36 |
+
def append_zero(arr: np.ndarray) -> np.ndarray:
|
| 37 |
"""
|
| 38 |
+
Appends a zero array to the end of an array.
|
| 39 |
+
:param arr: `np.array` to fill.
|
| 40 |
+
:return: `np.array` with a zero array appended to the end.
|
|
|
|
| 41 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
zeros = np.zeros((arr.shape[0], arr.shape[1], 1), dtype=float)
|
| 43 |
return np.concatenate((arr, zeros), axis=-1)
|
| 44 |
|
| 45 |
|
| 46 |
def append_one(arr: np.ndarray) -> np.ndarray:
|
| 47 |
+
"""
|
| 48 |
+
Appends a one array to the end of an array.
|
| 49 |
+
:param arr: `np.array` to fill.
|
| 50 |
+
:return: `np.array` with a one array appended to the end.
|
| 51 |
+
"""
|
| 52 |
ones = np.ones((arr.shape[0], arr.shape[1], 1), dtype=float)
|
| 53 |
return np.concatenate((arr, ones), axis=-1)
|
| 54 |
|
|
|
|
| 59 |
rotation_vectors: np.array,
|
| 60 |
scale_vectors: np.array,
|
| 61 |
ordered: bool = True) -> np.array:
|
| 62 |
+
"""
|
| 63 |
+
Merges the given arrays into a single array of shape (n_frames, pc_size, 14).
|
| 64 |
+
:param actor_classes: `np.array` of actor class labels.
|
| 65 |
+
:param marker_classes: `np.array` of marker class labels.
|
| 66 |
+
:param translation_vectors: `np.array` of translation vectors (3 values each).
|
| 67 |
+
:param rotation_vectors: `np.array` of Euler rotation angles (3 values each).
|
| 68 |
+
:param scale_vectors: `np.array` of scale factors (3 values each).
|
| 69 |
+
:param ordered: Whether to sort the cloud by actor and marker classes.
|
| 70 |
+
:return: Merged `np.array` of shape (n_frames, pc_size, 14).
|
| 71 |
+
"""
|
| 72 |
+
# Actor and marker classes enter as shape (x, 1024), so use np.expand_dims to create a new dimension at the end.
|
| 73 |
+
# Return the concatenated array of shape (x, 1024, 14), which matches the original timeline dense cloud before
|
| 74 |
# splitting it into sub arrays.
|
|
|
|
| 75 |
tdc = np.concatenate((np.expand_dims(actor_classes, -1),
|
| 76 |
np.expand_dims(marker_classes, -1),
|
| 77 |
append_zero(translation_vectors),
|
|
|
|
| 105 |
Convenience function to sort a timeline dense cloud by actor and marker classes.
|
| 106 |
Not required.
|
| 107 |
:param cloud: `np.array` point cloud to sort.
|
| 108 |
+
:return: Sorted `np.array` point cloud.
|
| 109 |
"""
|
| 110 |
# Extract the first two elements of the third dimension
|
| 111 |
actor_classes = cloud[:, :, 0]
|
|
|
|
| 125 |
return sorted_tdc
|
| 126 |
|
| 127 |
|
| 128 |
+
def create_keyframe(anim_curve: fbx.FbxAnimCurve, frame: int, value: float) -> None:
|
| 129 |
+
"""
|
| 130 |
+
Creates a keyframe at the given frame number on the given animation curve.
|
| 131 |
+
:param anim_curve: `fbx.FbxAnimCurve` node to add the keyframe to.
|
| 132 |
+
:param frame: `int` frame number at which to add the keyframe.
|
| 133 |
+
:param value: `float` value that the keyframe will have.
|
| 134 |
+
:return: True
|
| 135 |
+
"""
|
| 136 |
# Create an FbxTime object with the given frame number
|
| 137 |
t = fbx.FbxTime()
|
| 138 |
t.SetFrame(frame)
|
|
|
|
| 140 |
# Create a new keyframe with the specified value
|
| 141 |
key_index = anim_curve.KeyAdd(t)[0]
|
| 142 |
anim_curve.KeySetValue(key_index, value)
|
| 143 |
+
return
|
| 144 |
|
| 145 |
|
| 146 |
def get_child_node_by_name(parent_node: fbx.FbxNode, name: str, ignore_namespace: bool = False) \
|
| 147 |
-> Union[fbx.FbxNode, None]:
|
| 148 |
+
"""
|
| 149 |
+
Gets the child node with the given name.
|
| 150 |
+
:param parent_node: `fbx.FbxNode` to get the child node from.
|
| 151 |
+
:param name: `str` name of the child node to get.
|
| 152 |
+
:param ignore_namespace: `bool` whether to ignore the namespace in the node name.
|
| 153 |
+
:return: `fbx.FbxNode` child node with the given name, if it exists, else None.
|
| 154 |
+
"""
|
| 155 |
+
# Loop through all child nodes of the parent node.
|
| 156 |
for c in range(parent_node.GetChildCount()):
|
| 157 |
+
# Get the child node.
|
| 158 |
child = parent_node.GetChild(c)
|
| 159 |
+
# Check if the name of the child node matches the given name.
|
| 160 |
if match_name(child, name, ignore_namespace):
|
| 161 |
+
# If it matches, return the child node.
|
| 162 |
return child
|
| 163 |
+
# If no child node matches the given name, return None.
|
| 164 |
return None
|
| 165 |
|
| 166 |
|
| 167 |
def match_name(node: fbx.FbxNode, name: str, ignore_namespace: bool = True) -> bool:
|
| 168 |
+
"""
|
| 169 |
+
Checks if the given node's name matches the given name.
|
| 170 |
+
:param node: `fbx.FbxNode` to check.
|
| 171 |
+
:param name: `str` name to match.
|
| 172 |
+
:param ignore_namespace: `bool` whether to ignore the namespace in the node name.
|
| 173 |
+
"""
|
| 174 |
+
# get the name of the node
|
| 175 |
node_name = node.GetName()
|
| 176 |
+
# if ignore_namespace is True, remove the namespace from the node name
|
| 177 |
if ignore_namespace:
|
| 178 |
node_name = node_name.split(':')[-1]
|
| 179 |
+
# return True if the node name matches the provided name, False otherwise
|
| 180 |
return node_name == name
|
| 181 |
|
| 182 |
|
|
|
|
| 255 |
return [lcl.GetT()[t] for t in range(3)], [lcl.GetR()[r] for r in range(3)], [lcl.GetS()[s] for s in range(3)]
|
| 256 |
|
| 257 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
def isolate_actor_from_tdc(tdc: np.array, actor: int) -> np.array:
|
| 259 |
"""
|
| 260 |
Returns all markers of the given actor in the timeline dense cloud.
|
|
|
|
| 279 |
|
| 280 |
|
| 281 |
def get_keyed_frames_from_curve(curve: fbx.FbxAnimCurve, length: int = -1) -> List[fbx.FbxAnimCurveKey]:
|
| 282 |
+
"""
|
| 283 |
+
Returns a list of all the frames on the given curve.
|
| 284 |
+
:param curve: `fbx.FbxAnimCurve` to get frames from.
|
| 285 |
+
:param length: Desired amount of frame numbers to return. If this is more than there are keyframes on the curve,
|
| 286 |
+
it pads 0s to the end. Default -1, which will not add any 0s.
|
| 287 |
+
:return: List of all the frames on the given curve.
|
| 288 |
+
"""
|
| 289 |
+
# Get all the frames on the curve.
|
| 290 |
frames = [curve.KeyGet(i).GetTime().GetFrameCount() for i in range(curve.KeyGetCount())]
|
| 291 |
+
# Calculate the difference between desired length and actual length of frames.
|
| 292 |
dif = length - len(frames)
|
| 293 |
+
# If desired length is greater than actual length and length is not -1, add 0s to the end.
|
| 294 |
if dif > 0 and length != -1:
|
| 295 |
frames += [0.] * dif
|
| 296 |
+
# Return the list of all frames on the curve.
|
| 297 |
return frames
|
| 298 |
|
| 299 |
|
| 300 |
+
def get_world_transforms(actor_idx: int, marker_idx: int, m: fbx.FbxNode,
|
| 301 |
+
r: List[int], c: fbx.FbxAnimCurve, incl_keyed: int = 1) -> List[List[float]]:
|
| 302 |
+
"""
|
| 303 |
+
For the given marker node, gets the world transform for each frame in r, and stores the translation, rotation
|
| 304 |
+
and scaling values as a list of lists. Stores the actor and marker classes at the start of this list of lists.
|
| 305 |
+
Optionally, if incl_keyed is 1, also stores the keyed frames as the last list.
|
| 306 |
+
Note: This function has to be passed the animation curve, because we need the animation layer to get the
|
| 307 |
+
animation curve. The animation layer is stored inside the FBXContainer class, to which we don't have access to here.
|
| 308 |
+
:param actor_idx: `int` actor class.
|
| 309 |
+
:param marker_idx: `int` marker class.
|
| 310 |
+
:param m: `fbx.FbxNode` to evaluate the world transform of at each frame.
|
| 311 |
+
:param r: `List[int]` list of frame numbers to evaluate the world transform at.
|
| 312 |
+
:param c: `fbx.FbxAnimCurve` node to read the keyframes from.
|
| 313 |
+
:param incl_keyed: `bool` whether to include if there was a key on a given frame or not. 0 if not.
|
| 314 |
+
:return:
|
| 315 |
+
"""
|
| 316 |
+
# Create a list of zeros with the same length as r.
|
| 317 |
zeros = [0.0 for _ in range(len(r))]
|
| 318 |
+
# Create a list of ones with the same length as r.
|
| 319 |
ones = [1.0 for _ in range(len(r))]
|
| 320 |
|
| 321 |
+
# Create empty lists for each transformation parameter.
|
| 322 |
tx, ty, tz, rx, ry, rz, sx, sy, sz = [], [], [], [], [], [], [], [], []
|
| 323 |
+
# Create a list of actor classes with the same length as r.
|
| 324 |
actors = [actor_idx for _ in range(len(r))]
|
| 325 |
+
# Create a list of marker classes with the same length as r.
|
| 326 |
markers = [marker_idx for _ in range(len(r))]
|
| 327 |
+
# Create a new FbxTime object without a frame set yet.
|
| 328 |
t = fbx.FbxTime()
|
| 329 |
|
| 330 |
+
# For each frame in the given frame range (which does not need to start at 0),
|
| 331 |
+
# evaluate the world transform at each frame and store the relevant items into their respective lists.
|
| 332 |
for f in r:
|
| 333 |
t.SetFrame(f)
|
| 334 |
wt = m.EvaluateGlobalTransform(t)
|
|
|
|
| 343 |
sy.append(wts[1])
|
| 344 |
sz.append(wts[2])
|
| 345 |
|
| 346 |
+
# If we don't need to include keyed frames, return the list of lists as is.
|
| 347 |
if not incl_keyed:
|
| 348 |
return [
|
| 349 |
actors,
|
|
|
|
| 353 |
sx, sy, sz, ones
|
| 354 |
]
|
| 355 |
|
| 356 |
+
# However, if we do need those keys, we first retrieve all the keyframed frame numbers from the curve.
|
| 357 |
+
# Note: We do this after returning the previous results, because the following lines are very slow
|
| 358 |
+
# and unnecessary for inference.
|
| 359 |
keyed_frames = get_keyed_frames_from_curve(c)
|
| 360 |
+
# Then we check if any of the frame numbers are in the keyed frames, which means it had a keyframe and should be 1.
|
| 361 |
keyed_bools = [1 if f in keyed_frames else 0 for f in r]
|
| 362 |
|
| 363 |
+
# Finally, return the complete lists of lists.
|
| 364 |
return [
|
| 365 |
actors,
|
| 366 |
markers,
|
|
|
|
| 378 |
pc_size: int = 1024,
|
| 379 |
scale: float = 0.01,
|
| 380 |
debug: int = -1,
|
| 381 |
+
save_init: bool = True):
|
|
|
|
|
|
|
| 382 |
"""
|
| 383 |
Class that stores references to important nodes in an FBX file.
|
| 384 |
Offers utility functions to quickly load animation data.
|
|
|
|
| 425 |
self.pc_size = pc_size
|
| 426 |
|
| 427 |
self.input_fbx = fbx_file
|
|
|
|
| 428 |
self.output_fbx = utils.append_suffix_to_file(fbx_file, '_INF')
|
| 429 |
self.valid_frames = []
|
| 430 |
|
| 431 |
# If we know that the input file has valid data,
|
| 432 |
# we can automatically call the init function and ignore missing data.
|
| 433 |
if save_init:
|
| 434 |
+
self.init()
|
| 435 |
|
| 436 |
def __init_scene(self) -> None:
|
| 437 |
"""
|
|
|
|
| 526 |
|
| 527 |
self.markers.append(actor_markers)
|
| 528 |
|
| 529 |
+
def __init_unlabeled_markers(self, ignore_missing: bool = True) -> None:
|
| 530 |
"""
|
| 531 |
Looks for the Unlabeled_Markers parent node under the root and stores references to all unlabeled marker nodes.
|
| 532 |
"""
|
|
|
|
| 542 |
raise ValueError('No unlabeled markers found.')
|
| 543 |
|
| 544 |
def init_world_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> None:
|
| 545 |
+
"""
|
| 546 |
+
Calls the init functions for the labeled and unlabeled world transforms.
|
| 547 |
+
:param r: Custom frame range to extract.
|
| 548 |
+
"""
|
| 549 |
self.init_labeled_world_transforms(r=r, incl_keyed=1)
|
| 550 |
self.init_unlabeled_world_transforms(r=r)
|
| 551 |
|
| 552 |
def init_labeled_world_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 553 |
+
incl_keyed: int = 1) -> np.array:
|
| 554 |
+
"""
|
| 555 |
+
For each actor, for each marker, stores a list for each element in the world transform for each frame
|
| 556 |
+
in r. This can later be used to recreate the world transform matrix.
|
| 557 |
+
:param r: Custom frame range to use.
|
| 558 |
+
:param incl_keyed: `bool` whether to check if the marker was keyed at the frame.
|
| 559 |
+
:return: `np.array` of shape (n_frames, n_markers, 14).
|
| 560 |
+
"""
|
| 561 |
r = self.convert_r(r)
|
| 562 |
labeled_data = []
|
| 563 |
|
| 564 |
+
# Iterate through all actors.
|
| 565 |
for actor_idx in range(self.actor_count):
|
| 566 |
+
# Initialize an empty list to store the results for this actor in.
|
| 567 |
actor_data = []
|
| 568 |
+
# Iterate through all markers for this actor.
|
| 569 |
for marker_idx, (n, m) in enumerate(self.markers[actor_idx].items()):
|
| 570 |
+
# Get this marker's local translation animation curve.
|
| 571 |
+
# This requires the animation layer, so we can't do it within the function itself.
|
| 572 |
curve = m.LclTranslation.GetCurve(self.anim_layer, 'X', True)
|
| 573 |
+
# Get a list of each world transform element for all frames.
|
| 574 |
marker_data = get_world_transforms(actor_idx + 1, marker_idx + 1, m, r, curve, incl_keyed)
|
| 575 |
+
# Add the result to actor_data.
|
| 576 |
actor_data.append(marker_data)
|
| 577 |
self._print(f'Actor {actor_idx} marker {marker_idx} done', 1)
|
| 578 |
+
# Add all this actor_data to the global labeled_data.
|
| 579 |
labeled_data.append(actor_data)
|
| 580 |
|
| 581 |
+
# Convert the list to a np array. This will have all frames at the last dimension because of this order:
|
| 582 |
+
# Shape (n_actors, n_markers, 14/15, n_frames).
|
| 583 |
wide_layout = np.array(labeled_data)
|
| 584 |
+
# Transpose the array so that the first dimension is the frames.
|
| 585 |
+
# Shape (n_frames, n_actors, n_markers, 14).
|
| 586 |
self.labeled_world_transforms = np.transpose(wide_layout, axes=(3, 0, 1, 2))
|
| 587 |
return self.labeled_world_transforms
|
| 588 |
|
| 589 |
def init_unlabeled_world_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> np.array:
|
| 590 |
+
"""
|
| 591 |
+
For all unlabeled markers, stores a list for each element in the world transform for each frame
|
| 592 |
+
in r. This can later be used to recreate the world transform matrix.
|
| 593 |
+
:param r: Custom frame range to use.
|
| 594 |
+
:return: `np.array` of shape (n_frames, n_unlabeled_markers, 14).
|
| 595 |
+
"""
|
| 596 |
r = self.convert_r(r)
|
|
|
|
| 597 |
unlabeled_data = []
|
| 598 |
|
| 599 |
+
# Iterate through all unlabeled markers.
|
| 600 |
for ulm in self.unlabeled_markers:
|
| 601 |
+
# Get this marker's local translation animation curve.
|
| 602 |
+
# This requires the animation layer, so we can't do it within the function itself.
|
| 603 |
curve = ulm.LclTranslation.GetCurve(self.anim_layer, 'X', True)
|
| 604 |
+
# Get a list of each world transform element for all frames.
|
| 605 |
marker_data = get_world_transforms(0, 0, ulm, r, curve, incl_keyed=0)
|
| 606 |
+
# Add the result to marker_data.
|
| 607 |
unlabeled_data.append(marker_data)
|
| 608 |
self._print(f'Unlabeled marker {ulm.GetName()} done', 1)
|
| 609 |
|
| 610 |
+
# Convert the list to a np array. This will have all frames at the last dimension because of this order:
|
| 611 |
+
# Shape (n_unlabeled_markers, 14/15, n_frames).
|
| 612 |
wide_layout = np.array(unlabeled_data)
|
| 613 |
+
# Transpose the array so that the first dimension is the frames.
|
| 614 |
+
# Shape (n_frames, n_unlabeled_markers, 14).
|
| 615 |
self.unlabeled_world_transforms = np.transpose(wide_layout, axes=(2, 0, 1))
|
| 616 |
# Returns shape (n_frames, n_unlabeled_markers, 14).
|
| 617 |
return self.unlabeled_world_transforms
|
| 618 |
|
| 619 |
+
def init(self, ignore_missing_labeled: bool = False, ignore_missing_unlabeled: bool = False) -> None:
|
| 620 |
+
"""
|
| 621 |
+
Initializes the scene.
|
| 622 |
+
:param ignore_missing_labeled: `bool` whether to ignore errors for missing labeled markers.
|
| 623 |
+
:param ignore_missing_unlabeled: `bool` whether to ignore errors for missing unlabeled markers.
|
| 624 |
+
"""
|
| 625 |
self.__init_scene()
|
| 626 |
self.__init_anim()
|
| 627 |
self.__init_actors(ignore_missing=ignore_missing_labeled)
|
|
|
|
| 641 |
if not 0 <= actor <= self.actor_count:
|
| 642 |
raise ValueError(f'Actor index must be between 0 and {self.actor_count - 1} ({actor}).')
|
| 643 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
def get_frame_range(self) -> List[int]:
|
| 645 |
"""
|
| 646 |
Replacement and improvement for:
|
|
|
|
| 650 |
"""
|
| 651 |
return list(range(self.start_frame, self.end_frame))
|
| 652 |
|
| 653 |
+
def convert_r(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> List[int]:
|
| 654 |
+
"""
|
| 655 |
+
Converts the value of r to a list of frame numbers, depending on what r is.
|
| 656 |
+
:param r: Custom frame range to use.
|
| 657 |
+
:return: List of `int` frame numbers (doesn't have to start at 0).
|
| 658 |
+
"""
|
| 659 |
# If r is one int, use 0 as start frame. If r is higher than the total frames, limit the range.
|
| 660 |
if isinstance(r, int):
|
| 661 |
r = list(range(self.num_frames)) if r > self.num_frames else list(range(r))
|
|
|
|
| 740 |
# Return the last node in the chain, which will be the node we were looking for.
|
| 741 |
return nodes[-1]
|
| 742 |
|
| 743 |
+
def remove_clipping_poses(self, arr: np.array) -> np.array:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 744 |
"""
|
| 745 |
+
Checks for each axis if it does not cross the volume limits. Returns an array without clipping poses.
|
| 746 |
+
:param arr: `np.array` to filter.
|
| 747 |
+
:return: Filtered `np.array` that only has non-clipping poses.
|
|
|
|
| 748 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 749 |
mask_x1 = (arr[:, :, 2] < self.hvol_x / self.scale).all(axis=1)
|
| 750 |
mask_x2 = (arr[:, :, 2] > -self.hvol_x / self.scale).all(axis=1)
|
| 751 |
mask_z1 = (arr[:, :, 4] < self.hvol_z / self.scale).all(axis=1)
|
|
|
|
| 755 |
return arr[mask]
|
| 756 |
|
| 757 |
def extract_training_translations(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> np.array:
|
| 758 |
+
"""
|
| 759 |
+
Manipulates the existing labeled world transform array into one that is suitable for training.
|
| 760 |
+
It does this through flattening the array to shape (n_frames, n_actors * 73, 15), then removing
|
| 761 |
+
all clipping frames and finally transforms the frames to the right location and scale.
|
| 762 |
+
:param r: Custom frame range to use if the labeled transforms are not stored yet.
|
| 763 |
+
:return: Transformed labeled world transforms.
|
| 764 |
+
"""
|
| 765 |
if self.labeled_world_transforms is None:
|
| 766 |
self.init_labeled_world_transforms(r=r, incl_keyed=1)
|
| 767 |
|
|
|
|
| 791 |
|
| 792 |
def extract_inf_translations(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 793 |
merged: bool = True) -> Union[np.array, Tuple[np.array, np.array]]:
|
| 794 |
+
"""
|
| 795 |
+
Manipulates the existing (un)labeled world transform arrays into arrays that are suitable for inference.
|
| 796 |
+
It does this through flattening the labeled world transforms to shape (n_frames, n_actors * 73, 14).
|
| 797 |
+
If merged is True, merges the unlabeled data with the labeled data.
|
| 798 |
+
:param r: Custom frame range to use if the transforms were not extracted yet.
|
| 799 |
+
:param merged: `bool` whether to merge both arrays into one or return separate arrays.
|
| 800 |
+
:return: If merged, returns one `np.array`, else flattened labeled `np.array` and unlabeled `np.array`.
|
| 801 |
+
"""
|
| 802 |
+
# If either of the arrays is None, we can initialize them with r.
|
| 803 |
if self.labeled_world_transforms is None:
|
| 804 |
+
# For inference, we don't need keyed frames, so incl_keyed is False.
|
| 805 |
self.init_labeled_world_transforms(r=r, incl_keyed=0)
|
| 806 |
if self.unlabeled_world_transforms is None:
|
| 807 |
+
# Note: Unlabeled data is already flattened.
|
| 808 |
self.init_unlabeled_world_transforms(r=r)
|
| 809 |
|
| 810 |
+
# Returns (n_frames, n_actors, 73, 14).
|
| 811 |
ls = self.labeled_world_transforms.shape
|
| 812 |
+
# Flatten the array, so we get a list of frames.
|
| 813 |
# Returns shape (n_frames, 73 * n_actors, 14).
|
| 814 |
flat_labeled = self.labeled_world_transforms.reshape(ls[0], -1, ls[-1])[..., :14]
|
| 815 |
|
|
|
|
| 826 |
:param w: `np.array` that can either be a timeline dense cloud or translation vectors.
|
| 827 |
:return: Modified `np.array`.
|
| 828 |
"""
|
|
|
|
| 829 |
# If the last dimension has 3 elements, it is a translation vector of shape (tx, ty, tz).
|
| 830 |
# If it has 14 elements, it is a full marker row of shape (actor, marker, tx, ty, tz, tw, rx, ry, rz, tw, etc).
|
| 831 |
start = 0 if w.shape[-1] == 3 else 2
|
|
|
|
| 838 |
|
| 839 |
# Then move the x and z to the center of the volume. Y doesn't need to be done because pose needs to stand
|
| 840 |
# on the floor.
|
| 841 |
+
# We do not add 0.5 here to move the pose to the middle of the capture space,
|
| 842 |
+
# because in the Dataset we still need to randomly rotate it in world space.
|
| 843 |
+
# So we keep it centered here.
|
| 844 |
+
w[..., start + 0] = np.clip(w[..., start + 0], -0.5, 0.5)
|
| 845 |
w[..., start + 1] = np.clip(w[..., start + 1], -0.5, 0.5)
|
| 846 |
+
w[..., start + 2] = np.clip(w[..., start + 2], -0.5, 0.5)
|
| 847 |
|
| 848 |
return w
|
| 849 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 850 |
def get_split_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 851 |
mode: str = 'train') -> Tuple[np.array, np.array, np.array, np.array, np.array]:
|
| 852 |
"""
|
|
|
|
| 880 |
|
| 881 |
def export_train_data(self, output_file: Path, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) \
|
| 882 |
-> Union[bytes, pd.DataFrame, np.array]:
|
| 883 |
+
"""
|
| 884 |
+
Exports train data to an HDF5 file.
|
| 885 |
+
:param output_file: `Path` to the file.
|
| 886 |
+
:param r: Custom frame range to use.
|
| 887 |
+
:return: `np.array` of shape (n_poses, 73, 14) of train data.
|
| 888 |
+
"""
|
| 889 |
+
if output_file.suffix == '.h5':
|
|
|
|
|
|
|
|
|
|
|
|
|
| 890 |
array_4d = self.extract_training_translations(r)
|
| 891 |
with h5py.File(output_file, 'w') as h5f:
|
| 892 |
h5f.create_dataset('array_data', data=array_4d, compression='gzip', compression_opts=9)
|
|
|
|
| 894 |
return array_4d
|
| 895 |
|
| 896 |
else:
|
| 897 |
+
raise ValueError('Invalid file extension. Must be .h5')
|
| 898 |
|
| 899 |
def export_test_data(self, output_file: Path, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 900 |
merged: bool = True) -> Union[np.array, Tuple[np.array, np.array]]:
|
| 901 |
+
"""
|
| 902 |
+
Exports test data to an HDF5 file.
|
| 903 |
+
:param output_file: `Path` to the file.
|
| 904 |
+
:param r: Custom frame range to use.
|
| 905 |
+
:param merged: `bool` whether to merge the test data or output an unlabeled dataset and labeled dataset.
|
| 906 |
+
:return: `np.array` of the test data.
|
| 907 |
+
"""
|
| 908 |
# Retrieve the clean world transforms.
|
| 909 |
# If merged is True, this will be one array of shape (n_frames, pc_size, 14).
|
| 910 |
# If merged is False, this will be two arrays, one of shape (n_frames, 73 * n_actors, 14),
|
|
|
|
| 1062 |
self.set_default_lcl_scaling(marker, lcl_s)
|
| 1063 |
|
| 1064 |
def replace_keyframes_per_marker(self, marker: fbx.FbxNode, marker_keys: dict) -> None:
|
| 1065 |
+
"""
|
| 1066 |
+
For the given marker, creates new keyframes on its local translation animation curves,
|
| 1067 |
+
and sets the default local rotation values.
|
| 1068 |
+
:param marker: `fbx.FbxNode` to set the default values on.
|
| 1069 |
+
:param marker_keys: `dict` of keys that contain all info needed to create world transform matrices.
|
| 1070 |
+
"""
|
| 1071 |
# Initialize empty variables for the local rotation and scaling.
|
| 1072 |
# These will be filled at the first keyframe
|
| 1073 |
self.set_default_lcl_transforms(marker, marker_keys)
|