Moved the core functionality of FBXContainer to a new class called FBXContainerBase.
Browse filesextract_inf_translations() will now replace the transforms of nodes without a keyframe with np.inf upon extraction.
This is so when we load the test data, we can easily replace this data with random data to serve as unlabeled markers.
- fbx_handler.py +273 -230
fbx_handler.py
CHANGED
|
@@ -5,7 +5,6 @@ from typing import List, Union, Tuple
|
|
| 5 |
import h5py
|
| 6 |
|
| 7 |
# Import util libs.
|
| 8 |
-
import contextlib
|
| 9 |
import fbx
|
| 10 |
import itertools
|
| 11 |
|
|
@@ -371,69 +370,32 @@ def get_world_transforms(actor_idx: int, marker_idx: int, m: fbx.FbxNode,
|
|
| 371 |
]
|
| 372 |
|
| 373 |
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
|
|
|
|
|
|
|
|
|
| 382 |
"""
|
| 383 |
Class that stores references to important nodes in an FBX file.
|
| 384 |
Offers utility functions to quickly load animation data.
|
| 385 |
:param fbx_file: `Path` to the file to load.
|
| 386 |
-
:param volume_dims: `tuple` of `float` that represent the dimensions of the capture volume in meters.
|
| 387 |
-
:param max_actors: `int` maximum amount of actors to expect in a point cloud.
|
| 388 |
-
:param pc_size: `int` amount of points in a point cloud.
|
| 389 |
-
:param debug: If higher than -1, will print out debugging statements.
|
| 390 |
-
:param save_init: If the file is guaranteed to have all data, set to True to automatically call self.init().
|
| 391 |
-
:param r: Optional frame range that will be passed to init_transforms.
|
| 392 |
-
:param mode: `str` to indicate whether to store world transforms for inference only. Default 'train'.
|
| 393 |
"""
|
| 394 |
-
|
| 395 |
-
raise ValueError('Point cloud size must be large enough to contain the maximum amount of actors * 73'
|
| 396 |
-
f' markers: {pc_size}/{max_actors * 73}.')
|
| 397 |
-
|
| 398 |
self.debug = debug
|
| 399 |
-
# Python ENUM of the C++ time modes.
|
| 400 |
-
self.time_modes = globals.get_time_modes()
|
| 401 |
-
# Ordered list of marker names. Note: rearrange this in globals.py.
|
| 402 |
-
self.marker_names = globals.get_marker_names()
|
| 403 |
|
| 404 |
# Initiate empty lists to store references to nodes.
|
| 405 |
-
self.
|
| 406 |
-
self.
|
| 407 |
# Store names of the actors (all parent nodes that have the first 4 markers as children).
|
| 408 |
-
self.
|
| 409 |
-
|
| 410 |
-
self.labeled_world_transforms = None
|
| 411 |
-
self.unlabeled_world_transforms = None
|
| 412 |
-
|
| 413 |
-
# Split the dimensions tuple into its axes for easier access.
|
| 414 |
-
self.vol_x = volume_dims[0]
|
| 415 |
-
self.vol_y = volume_dims[1]
|
| 416 |
-
self.vol_z = volume_dims[2]
|
| 417 |
-
self.hvol_x = volume_dims[0] / 2
|
| 418 |
-
self.hvol_y = volume_dims[1] / 2
|
| 419 |
-
self.hvol_z = volume_dims[2] / 2
|
| 420 |
-
|
| 421 |
-
self.scale = scale
|
| 422 |
-
|
| 423 |
-
self.max_actors = max_actors
|
| 424 |
-
# Maximum point cloud size = 73 * max_actors + unlabeled markers.
|
| 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
|
| 437 |
"""
|
| 438 |
Stores scene, root, and time_mode properties.
|
| 439 |
Destroys the importer to remove the reference to the loaded file.
|
|
@@ -456,7 +418,7 @@ class FBXContainer:
|
|
| 456 |
# This will allow us to delete the uploaded file.
|
| 457 |
importer.Destroy()
|
| 458 |
|
| 459 |
-
def
|
| 460 |
"""
|
| 461 |
Stores the anim_stack, num_frames, start_frame, end_frame properties.
|
| 462 |
"""
|
|
@@ -474,12 +436,11 @@ class FBXContainer:
|
|
| 474 |
self.start_frame = local_time_span.GetStart().GetFrameCount()
|
| 475 |
self.end_frame = local_time_span.GetStop().GetFrameCount()
|
| 476 |
|
| 477 |
-
def
|
| 478 |
"""
|
| 479 |
Goes through all root children (generation 1).
|
| 480 |
If a child has 4 markers as children, it is considered an actor (Shogun subject) and appended to actors
|
| 481 |
and actor_names list properties.
|
| 482 |
-
Also initializes an empty valid_frames list for each found actor.
|
| 483 |
"""
|
| 484 |
ts = fbx.FbxTime()
|
| 485 |
ts.SetFrame(self.start_frame)
|
|
@@ -487,46 +448,234 @@ class FBXContainer:
|
|
| 487 |
te = fbx.FbxTime()
|
| 488 |
te.SetFrame(self.end_frame)
|
| 489 |
|
| 490 |
-
names_to_look_for = list(self.marker_names[:4])
|
| 491 |
# Find all parent nodes (/System, /Unlabeled_Markers, /Actor1, etc).
|
| 492 |
gen1_nodes = [self.root.GetChild(i) for i in range(self.root.GetChildCount())]
|
| 493 |
for gen1_node in gen1_nodes:
|
| 494 |
gen2_nodes = [gen1_node.GetChild(i) for i in
|
| 495 |
range(gen1_node.GetChildCount())] # Actor nodes (/Mimi/Hips, /Mimi/ARIEL, etc)
|
| 496 |
|
| 497 |
-
# If the
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
self.
|
|
|
|
| 501 |
|
| 502 |
-
if len(self.
|
| 503 |
-
raise ValueError('No
|
| 504 |
-
'if it has the following
|
| 505 |
', '.join(names_to_look_for) + '.')
|
| 506 |
|
| 507 |
-
self.
|
| 508 |
-
self.valid_frames = [[] for _ in range(self.actor_count)]
|
| 509 |
|
| 510 |
-
def
|
| 511 |
"""
|
| 512 |
-
Goes through all
|
| 513 |
"""
|
| 514 |
-
for
|
| 515 |
-
|
| 516 |
-
for
|
| 517 |
-
for
|
| 518 |
-
child =
|
| 519 |
# Child name might have namespaces in it like this: Vera:ARIEL
|
| 520 |
# We want to match only on the actual name, so ignore namespaces.
|
| 521 |
-
if match_name(child,
|
| 522 |
-
|
|
|
|
|
|
|
|
|
|
| 523 |
|
| 524 |
-
|
| 525 |
-
raise ValueError(f'{actor_node.GetName()} does not have all markers.')
|
| 526 |
|
| 527 |
-
|
|
|
|
|
|
|
| 528 |
|
| 529 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
"""
|
| 531 |
Looks for the Unlabeled_Markers parent node under the root and stores references to all unlabeled marker nodes.
|
| 532 |
"""
|
|
@@ -538,9 +687,6 @@ class FBXContainer:
|
|
| 538 |
self.unlabeled_markers = [gen1_node.GetChild(um) for um in range(gen1_node.GetChildCount())]
|
| 539 |
return
|
| 540 |
|
| 541 |
-
if not ignore_missing:
|
| 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.
|
|
@@ -562,11 +708,11 @@ class FBXContainer:
|
|
| 562 |
labeled_data = []
|
| 563 |
|
| 564 |
# Iterate through all actors.
|
| 565 |
-
for actor_idx in range(self.
|
| 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.
|
| 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)
|
|
@@ -586,11 +732,13 @@ class FBXContainer:
|
|
| 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
|
|
|
|
| 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)
|
|
@@ -602,7 +750,7 @@ class FBXContainer:
|
|
| 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=
|
| 606 |
# Add the result to marker_data.
|
| 607 |
unlabeled_data.append(marker_data)
|
| 608 |
self._print(f'Unlabeled marker {ulm.GetName()} done', 1)
|
|
@@ -616,71 +764,16 @@ class FBXContainer:
|
|
| 616 |
# Returns shape (n_frames, n_unlabeled_markers, 14).
|
| 617 |
return self.unlabeled_world_transforms
|
| 618 |
|
| 619 |
-
def init(self
|
| 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)
|
| 628 |
-
self.__init_markers(ignore_missing=ignore_missing_labeled)
|
| 629 |
-
self.__init_unlabeled_markers(ignore_missing=ignore_missing_unlabeled)
|
| 630 |
-
self._print('Init done', 0)
|
| 631 |
-
|
| 632 |
-
def _print(self, txt: str, lvl: int = 0) -> None:
|
| 633 |
-
if lvl <= self.debug:
|
| 634 |
-
print(txt)
|
| 635 |
-
|
| 636 |
-
def _check_actor(self, actor: int = 0):
|
| 637 |
-
"""
|
| 638 |
-
Safety check to see if the actor `int` is a valid number (to avoid out of range errors).
|
| 639 |
-
:param actor: `int` actor index, which should be between 0-max_actors.
|
| 640 |
-
"""
|
| 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:
|
| 647 |
-
`list(range(self.num_frames))`
|
| 648 |
-
If the animation does not start at frame 0, this will return a list that has the correct frames.
|
| 649 |
-
:return: List of `int` frame numbers that are between the start and end frame of the animation.
|
| 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 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
# If the requested frame range is longer than the total frames, limit the range.
|
| 666 |
-
if r[1] - r[0] > self.num_frames:
|
| 667 |
-
r = list(range(r[0], r[0] + self.num_frames))
|
| 668 |
-
else:
|
| 669 |
-
r = list(range(r[0], r[1]))
|
| 670 |
-
|
| 671 |
-
# A tuple of 3 indicates a frame range with step.
|
| 672 |
-
elif isinstance(r, tuple) and len(r) == 3:
|
| 673 |
-
# If the requested frame range is longer than the total frames, limit the range.
|
| 674 |
-
if r[1] - r[0] > self.num_frames:
|
| 675 |
-
r = list(range(r[0], r[0] + self.num_frames, r[2]))
|
| 676 |
-
else:
|
| 677 |
-
r = list(range(r[0], r[1], r[2]))
|
| 678 |
-
|
| 679 |
-
# If r is None, return the default frame range.
|
| 680 |
-
else:
|
| 681 |
-
r = self.get_frame_range()
|
| 682 |
-
|
| 683 |
-
return r
|
| 684 |
|
| 685 |
def columns_from_joints(self) -> List[str]:
|
| 686 |
"""
|
|
@@ -688,58 +781,11 @@ class FBXContainer:
|
|
| 688 |
:return: List of column names, in the form of [node1_tx, node1_ty, node1_tz, node2_tx, node2_ty, node2_tz..].
|
| 689 |
"""
|
| 690 |
columns = []
|
| 691 |
-
for name in self.
|
| 692 |
columns += [f'{name}x', f'{name}y', f'{name}z']
|
| 693 |
|
| 694 |
return columns
|
| 695 |
|
| 696 |
-
def get_marker_by_name(self, actor: int, name: str):
|
| 697 |
-
"""
|
| 698 |
-
Returns the reference to the actor's marker.
|
| 699 |
-
:param actor: `int` actor index.
|
| 700 |
-
:param name: `str` marker name.
|
| 701 |
-
:return: `fbx.FbxNode` reference.
|
| 702 |
-
"""
|
| 703 |
-
self._check_actor(actor)
|
| 704 |
-
return self.markers[actor][name]
|
| 705 |
-
|
| 706 |
-
def get_parent_node_by_name(self, parent_name: str, ignore_namespace: bool = True) -> Union[fbx.FbxNode, None]:
|
| 707 |
-
"""
|
| 708 |
-
Utility function to get a parent node reference by name.
|
| 709 |
-
:param parent_name: `str` name that will be looked for in the node name.
|
| 710 |
-
:param ignore_namespace: `bool` Whether to ignore namespaces in a node's name.
|
| 711 |
-
:return:
|
| 712 |
-
"""
|
| 713 |
-
# Find all parent nodes (/System, /Unlabeled_Markers, /Actor1, etc).
|
| 714 |
-
parent_nodes = [self.root.GetChild(i) for i in range(self.root.GetChildCount())]
|
| 715 |
-
|
| 716 |
-
return next(
|
| 717 |
-
(
|
| 718 |
-
parent_node
|
| 719 |
-
for parent_node in parent_nodes
|
| 720 |
-
if match_name(parent_node, parent_name, ignore_namespace=ignore_namespace)
|
| 721 |
-
),
|
| 722 |
-
None,
|
| 723 |
-
)
|
| 724 |
-
|
| 725 |
-
def get_node_by_path(self, path: str) -> fbx.FbxNode:
|
| 726 |
-
"""
|
| 727 |
-
Utility function to retrieve a node reference from a path like /Actor1/Hips/UpperLeg_l.
|
| 728 |
-
:param path: `str` path with forward slashes to follow.
|
| 729 |
-
:return: `fbx.FbxNode` reference to that node.
|
| 730 |
-
"""
|
| 731 |
-
# Split the path into node names.
|
| 732 |
-
node_names = [x for x in path.split('/') if x]
|
| 733 |
-
# Start the list of node references with the parent node that has its own function.
|
| 734 |
-
nodes = [self.get_parent_node_by_name(node_names[0], False)]
|
| 735 |
-
# Extend the list with each following child node of the previous parent.
|
| 736 |
-
nodes.extend(
|
| 737 |
-
get_child_node_by_name(nodes[idx], node_name)
|
| 738 |
-
for idx, node_name in enumerate(node_names[1:])
|
| 739 |
-
)
|
| 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.
|
|
@@ -767,9 +813,11 @@ class FBXContainer:
|
|
| 767 |
|
| 768 |
# Returns (n_frames, n_actors, 73, 15).
|
| 769 |
l_shape = self.labeled_world_transforms.shape
|
|
|
|
| 770 |
# Flatten the array, so we get a list of frames.
|
| 771 |
# Reshape to (n_frames * n_actors, 73, 15).
|
| 772 |
flattened = self.labeled_world_transforms.reshape(-1, l_shape[2], l_shape[3])
|
|
|
|
| 773 |
# Isolates the poses with all keyframes present by checking the last elements.
|
| 774 |
# Start with the mask.
|
| 775 |
# Returns shape of (n_frames * n_actors, 73, 15).
|
|
@@ -777,6 +825,7 @@ class FBXContainer:
|
|
| 777 |
# We only need a filter for the first dimension, so use .all to check if all markers
|
| 778 |
# have a keyframe. This results in shape (n_frames * n_actors,).
|
| 779 |
mask = mask.all(axis=1)
|
|
|
|
| 780 |
# Now isolate the right frames with the mask and remove the last element of the last dimension,
|
| 781 |
# because it won't be useful anymore.
|
| 782 |
# Also remove any frames that cross the limits of the volume.
|
|
@@ -802,16 +851,29 @@ class FBXContainer:
|
|
| 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=
|
| 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,
|
| 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,
|
| 814 |
-
flat_labeled = self.labeled_world_transforms.reshape(ls[0], -1, ls[-1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
|
| 816 |
if merged:
|
| 817 |
return utils.merge_labeled_and_unlabeled_data(labeled=flat_labeled,
|
|
@@ -832,9 +894,9 @@ class FBXContainer:
|
|
| 832 |
|
| 833 |
# First multiply by self.scale, which turns centimeters to meters.
|
| 834 |
# Then divide by volume dimensions, to normalize to the total area of the capture volume.
|
| 835 |
-
w[..., start + 0] = w[..., start + 0] * self.scale / self.
|
| 836 |
-
w[..., start + 1] = w[..., start + 1] * self.scale / self.
|
| 837 |
-
w[..., start + 2] = w[..., start + 2] * self.scale / self.
|
| 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.
|
|
@@ -862,21 +924,21 @@ class FBXContainer:
|
|
| 862 |
# Return np arrays as (actor classes, marker classes, translation vectors, rotation vectors, scale vectors).
|
| 863 |
return cloud[:, :, 0], cloud[:, :, 1], cloud[:, :, 2:5], cloud[:, :, 6:9], cloud[:, :, 10:13]
|
| 864 |
|
| 865 |
-
def
|
| 866 |
"""
|
| 867 |
Returns the actor name based on the class value.
|
| 868 |
:param c: `float` actor class index.
|
| 869 |
:return: `str` actor name.
|
| 870 |
"""
|
| 871 |
-
return 'UNLABELED' if int(c) == 0 else self.
|
| 872 |
|
| 873 |
-
def
|
| 874 |
"""
|
| 875 |
Returns the marker name based on the class value.
|
| 876 |
:param c: `float` marker class index.
|
| 877 |
:return: `str` marker name.
|
| 878 |
"""
|
| 879 |
-
return 'UNLABELED' if int(c) == 0 else self.
|
| 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]:
|
|
@@ -957,25 +1019,6 @@ class FBXContainer:
|
|
| 957 |
|
| 958 |
return True
|
| 959 |
|
| 960 |
-
def remove_node(self, node: fbx.FbxNode, recursive: bool = False) -> bool:
|
| 961 |
-
"""
|
| 962 |
-
Removes a node by reference from the scene.
|
| 963 |
-
:param node: `fbx.FbxNode` to remove.
|
| 964 |
-
:param recursive: `bool` Apply deletion recursively.
|
| 965 |
-
:return: True if success.
|
| 966 |
-
"""
|
| 967 |
-
if recursive:
|
| 968 |
-
children = [node.GetChild(c) for c in range(node.GetChildCount())]
|
| 969 |
-
for child in children:
|
| 970 |
-
self.remove_node(child, True)
|
| 971 |
-
# Disconnect the marker node from its parent
|
| 972 |
-
node.GetParent().RemoveChild(node)
|
| 973 |
-
|
| 974 |
-
# Remove the marker node from the scene
|
| 975 |
-
self.scene.RemoveNode(node)
|
| 976 |
-
|
| 977 |
-
return True
|
| 978 |
-
|
| 979 |
def remove_unlabeled_markers(self) -> None:
|
| 980 |
"""
|
| 981 |
Uses self.remove_node() to delete all unlabeled markers from the scene.
|
|
@@ -1091,7 +1134,7 @@ class FBXContainer:
|
|
| 1091 |
:param actor: `int` actor index to apply to. Index starts at 0.
|
| 1092 |
:param actor_keys: `dict` with all marker keys for this actor.
|
| 1093 |
"""
|
| 1094 |
-
for marker_class, (marker_name, marker) in enumerate(self.
|
| 1095 |
marker_keys = actor_keys.get(marker_class)
|
| 1096 |
if marker_keys:
|
| 1097 |
self._print(f'Replacing keys for {marker_name}', 1)
|
|
@@ -1102,7 +1145,7 @@ class FBXContainer:
|
|
| 1102 |
For all actors, uses self.replace_keyframes_per_actor() to set keyframes on each actor's marker nodes.
|
| 1103 |
:param key_dict: `dict` with all actor keyframes.
|
| 1104 |
"""
|
| 1105 |
-
for actor_idx in range(self.
|
| 1106 |
actor_dict = key_dict.get(actor_idx + 1)
|
| 1107 |
if actor_dict:
|
| 1108 |
self._print(f'Replacing keys for actor {actor_idx}', 1)
|
|
|
|
| 5 |
import h5py
|
| 6 |
|
| 7 |
# Import util libs.
|
|
|
|
| 8 |
import fbx
|
| 9 |
import itertools
|
| 10 |
|
|
|
|
| 370 |
]
|
| 371 |
|
| 372 |
|
| 373 |
+
def get_children_of_parent(parent: fbx.FbxNode) -> List[fbx.FbxNode]:
|
| 374 |
+
"""
|
| 375 |
+
Returns a list of all the children of the given parent.
|
| 376 |
+
:param parent: `fbx.FbxNode` to get children from.
|
| 377 |
+
:return: List of `fbx.FbxNode` children.
|
| 378 |
+
"""
|
| 379 |
+
return [parent.GetChild(i) for i in range(parent.GetChildCount())]
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
class FBXContainerBase:
|
| 383 |
+
def __init__(self, fbx_file: Path, debug: int = -1) -> None:
|
| 384 |
"""
|
| 385 |
Class that stores references to important nodes in an FBX file.
|
| 386 |
Offers utility functions to quickly load animation data.
|
| 387 |
:param fbx_file: `Path` to the file to load.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
"""
|
| 389 |
+
self.input_fbx = fbx_file
|
|
|
|
|
|
|
|
|
|
| 390 |
self.debug = debug
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
|
| 392 |
# Initiate empty lists to store references to nodes.
|
| 393 |
+
self.parents = []
|
| 394 |
+
self.children = []
|
| 395 |
# Store names of the actors (all parent nodes that have the first 4 markers as children).
|
| 396 |
+
self.parent_names = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
|
| 398 |
+
def _init_scene(self) -> None:
|
| 399 |
"""
|
| 400 |
Stores scene, root, and time_mode properties.
|
| 401 |
Destroys the importer to remove the reference to the loaded file.
|
|
|
|
| 418 |
# This will allow us to delete the uploaded file.
|
| 419 |
importer.Destroy()
|
| 420 |
|
| 421 |
+
def _init_anim(self) -> None:
|
| 422 |
"""
|
| 423 |
Stores the anim_stack, num_frames, start_frame, end_frame properties.
|
| 424 |
"""
|
|
|
|
| 436 |
self.start_frame = local_time_span.GetStart().GetFrameCount()
|
| 437 |
self.end_frame = local_time_span.GetStop().GetFrameCount()
|
| 438 |
|
| 439 |
+
def _init_parents(self, names_to_look_for: List[str]) -> None:
|
| 440 |
"""
|
| 441 |
Goes through all root children (generation 1).
|
| 442 |
If a child has 4 markers as children, it is considered an actor (Shogun subject) and appended to actors
|
| 443 |
and actor_names list properties.
|
|
|
|
| 444 |
"""
|
| 445 |
ts = fbx.FbxTime()
|
| 446 |
ts.SetFrame(self.start_frame)
|
|
|
|
| 448 |
te = fbx.FbxTime()
|
| 449 |
te.SetFrame(self.end_frame)
|
| 450 |
|
|
|
|
| 451 |
# Find all parent nodes (/System, /Unlabeled_Markers, /Actor1, etc).
|
| 452 |
gen1_nodes = [self.root.GetChild(i) for i in range(self.root.GetChildCount())]
|
| 453 |
for gen1_node in gen1_nodes:
|
| 454 |
gen2_nodes = [gen1_node.GetChild(i) for i in
|
| 455 |
range(gen1_node.GetChildCount())] # Actor nodes (/Mimi/Hips, /Mimi/ARIEL, etc)
|
| 456 |
|
| 457 |
+
# If the list of names_to_look_for are children of this parent, it must be a parent.
|
| 458 |
+
gen2_names = [node.GetName().split(':')[-1] for node in gen2_nodes]
|
| 459 |
+
if all(name in gen2_names for name in names_to_look_for):
|
| 460 |
+
self.parent_names.append(gen1_node.GetName())
|
| 461 |
+
self.parents.append(gen1_node)
|
| 462 |
|
| 463 |
+
if len(self.parents) == 0:
|
| 464 |
+
raise ValueError('No parents found. A node is considered a parent ' +
|
| 465 |
+
'if it has the following child nodes: ' +
|
| 466 |
', '.join(names_to_look_for) + '.')
|
| 467 |
|
| 468 |
+
self.parent_count = len(self.parents)
|
|
|
|
| 469 |
|
| 470 |
+
def _init_children(self, child_names: List[str]) -> None:
|
| 471 |
"""
|
| 472 |
+
Goes through all parent nodes and stores references to its child nodes.
|
| 473 |
"""
|
| 474 |
+
for parent_node in self.parents:
|
| 475 |
+
family = {}
|
| 476 |
+
for child_name in child_names:
|
| 477 |
+
for parent_idx in range(parent_node.GetChildCount()):
|
| 478 |
+
child = parent_node.GetChild(parent_idx)
|
| 479 |
# Child name might have namespaces in it like this: Vera:ARIEL
|
| 480 |
# We want to match only on the actual name, so ignore namespaces.
|
| 481 |
+
if match_name(child, child_name, ignore_namespace=True):
|
| 482 |
+
family[child_name] = child
|
| 483 |
+
|
| 484 |
+
if len(family) != len(child_names):
|
| 485 |
+
raise ValueError(f'{parent_node.GetName()} does not have all children.')
|
| 486 |
|
| 487 |
+
self.children.append(family)
|
|
|
|
| 488 |
|
| 489 |
+
def _print(self, txt: str, lvl: int = 0) -> None:
|
| 490 |
+
if lvl <= self.debug:
|
| 491 |
+
print(txt)
|
| 492 |
|
| 493 |
+
def get_frame_range(self) -> List[int]:
|
| 494 |
+
"""
|
| 495 |
+
Replacement and improvement for:
|
| 496 |
+
`list(range(self.num_frames))`
|
| 497 |
+
If the animation does not start at frame 0, this will return a list that has the correct frames.
|
| 498 |
+
:return: List of `int` frame numbers that are between the start and end frame of the animation.
|
| 499 |
+
"""
|
| 500 |
+
return list(range(self.start_frame, self.end_frame))
|
| 501 |
+
|
| 502 |
+
def _check_parent(self, parent: int = 0):
|
| 503 |
+
"""
|
| 504 |
+
Safety check to see if the actor `int` is a valid number (to avoid out of range errors).
|
| 505 |
+
:param parent: `int` actor index, which should be between 0-max_actors.
|
| 506 |
+
"""
|
| 507 |
+
if not 0 <= parent <= self.parent_count:
|
| 508 |
+
raise ValueError(f'Actor index must be between 0 and {self.parent_count - 1} ({parent}).')
|
| 509 |
+
|
| 510 |
+
def convert_r(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> List[int]:
|
| 511 |
+
"""
|
| 512 |
+
Converts the value of r to a list of frame numbers, depending on what r is.
|
| 513 |
+
:param r: Custom frame range to use.
|
| 514 |
+
:return: List of `int` frame numbers (doesn't have to start at 0).
|
| 515 |
+
"""
|
| 516 |
+
# If r is one int, use 0 as start frame. If r is higher than the total frames, limit the range.
|
| 517 |
+
if isinstance(r, int):
|
| 518 |
+
r = list(range(self.num_frames)) if r > self.num_frames else list(range(r))
|
| 519 |
+
|
| 520 |
+
# A tuple of 2 indicates a frame range without step.
|
| 521 |
+
elif isinstance(r, tuple) and len(r) == 2:
|
| 522 |
+
# If the requested frame range is longer than the total frames, limit the range.
|
| 523 |
+
if r[1] - r[0] > self.num_frames:
|
| 524 |
+
r = list(range(r[0], r[0] + self.num_frames))
|
| 525 |
+
else:
|
| 526 |
+
r = list(range(r[0], r[1]))
|
| 527 |
+
|
| 528 |
+
# A tuple of 3 indicates a frame range with step.
|
| 529 |
+
elif isinstance(r, tuple) and len(r) == 3:
|
| 530 |
+
# If the requested frame range is longer than the total frames, limit the range.
|
| 531 |
+
if r[1] - r[0] > self.num_frames:
|
| 532 |
+
r = list(range(r[0], r[0] + self.num_frames, r[2]))
|
| 533 |
+
else:
|
| 534 |
+
r = list(range(r[0], r[1], r[2]))
|
| 535 |
+
|
| 536 |
+
# If r is None, return the default frame range.
|
| 537 |
+
else:
|
| 538 |
+
r = self.get_frame_range()
|
| 539 |
+
|
| 540 |
+
return r
|
| 541 |
+
|
| 542 |
+
def get_parent_node_by_name(self, parent_name: str, ignore_namespace: bool = True) -> Union[fbx.FbxNode, None]:
|
| 543 |
+
"""
|
| 544 |
+
Utility function to get a parent node reference by name.
|
| 545 |
+
:param parent_name: `str` name that will be looked for in the node name.
|
| 546 |
+
:param ignore_namespace: `bool` Whether to ignore namespaces in a node's name.
|
| 547 |
+
:return:
|
| 548 |
+
"""
|
| 549 |
+
# Find all parent nodes (/System, /Unlabeled_Markers, /Actor1, etc).
|
| 550 |
+
parent_nodes = [self.root.GetChild(i) for i in range(self.root.GetChildCount())]
|
| 551 |
+
|
| 552 |
+
return next(
|
| 553 |
+
(
|
| 554 |
+
parent_node
|
| 555 |
+
for parent_node in parent_nodes
|
| 556 |
+
if match_name(parent_node, parent_name, ignore_namespace=ignore_namespace)
|
| 557 |
+
),
|
| 558 |
+
None,
|
| 559 |
+
)
|
| 560 |
+
|
| 561 |
+
def get_child_by_name(self, parent: int, name: str):
|
| 562 |
+
"""
|
| 563 |
+
Returns the reference to the parent's direct child.
|
| 564 |
+
:param parent: `int` parent index.
|
| 565 |
+
:param name: `str` child name.
|
| 566 |
+
:return: `fbx.FbxNode` reference.
|
| 567 |
+
"""
|
| 568 |
+
self._check_parent(parent)
|
| 569 |
+
return self.children[parent][name]
|
| 570 |
+
|
| 571 |
+
def get_node_by_path(self, path: str) -> fbx.FbxNode:
|
| 572 |
+
"""
|
| 573 |
+
Utility function to retrieve a node reference from a path like /Actor1/Hips/UpperLeg_l.
|
| 574 |
+
:param path: `str` path with forward slashes to follow.
|
| 575 |
+
:return: `fbx.FbxNode` reference to that node.
|
| 576 |
+
"""
|
| 577 |
+
# Split the path into node names.
|
| 578 |
+
node_names = [x for x in path.split('/') if x]
|
| 579 |
+
# Start the list of node references with the parent node that has its own function.
|
| 580 |
+
nodes = [self.get_parent_node_by_name(node_names[0], False)]
|
| 581 |
+
# Extend the list with each following child node of the previous parent.
|
| 582 |
+
nodes.extend(
|
| 583 |
+
get_child_node_by_name(nodes[idx], node_name)
|
| 584 |
+
for idx, node_name in enumerate(node_names[1:])
|
| 585 |
+
)
|
| 586 |
+
# Return the last node in the chain, which will be the node we were looking for.
|
| 587 |
+
return nodes[-1]
|
| 588 |
+
|
| 589 |
+
def get_hierarchy(self, start: fbx.FbxNode, hierarchy: List = None) -> List[fbx.FbxNode]:
|
| 590 |
+
"""
|
| 591 |
+
Returns the hierarchy under the start node.
|
| 592 |
+
:param hierarchy: Hierarchical `List` of `fbx.FbxNode` references.
|
| 593 |
+
:param start: `fbx.FbxNode` that will be used as starting point for finding the leaf nodes.
|
| 594 |
+
:return:
|
| 595 |
+
"""
|
| 596 |
+
if hierarchy is None:
|
| 597 |
+
hierarchy = []
|
| 598 |
+
hierarchy.append(start)
|
| 599 |
+
children = get_children_of_parent(start)
|
| 600 |
+
for child in children:
|
| 601 |
+
self.get_hierarchy(child, hierarchy)
|
| 602 |
+
|
| 603 |
+
return hierarchy
|
| 604 |
+
|
| 605 |
+
def remove_node(self, node: fbx.FbxNode, recursive: bool = False) -> bool:
|
| 606 |
+
"""
|
| 607 |
+
Removes a node by reference from the scene.
|
| 608 |
+
:param node: `fbx.FbxNode` to remove.
|
| 609 |
+
:param recursive: `bool` Apply deletion recursively.
|
| 610 |
+
:return: True if success.
|
| 611 |
+
"""
|
| 612 |
+
if recursive:
|
| 613 |
+
children = [node.GetChild(c) for c in range(node.GetChildCount())]
|
| 614 |
+
for child in children:
|
| 615 |
+
self.remove_node(child, True)
|
| 616 |
+
# Disconnect the marker node from its parent
|
| 617 |
+
node.GetParent().RemoveChild(node)
|
| 618 |
+
|
| 619 |
+
# Remove the marker node from the scene
|
| 620 |
+
self.scene.RemoveNode(node)
|
| 621 |
+
|
| 622 |
+
return True
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
class FBXContainer(FBXContainerBase):
|
| 626 |
+
def __init__(self, fbx_file: Path,
|
| 627 |
+
volume_dims: Tuple[float] = (10., 10., 10.),
|
| 628 |
+
max_actors: int = 8,
|
| 629 |
+
pc_size: int = 1024,
|
| 630 |
+
scale: float = 0.01,
|
| 631 |
+
debug: int = -1):
|
| 632 |
+
"""
|
| 633 |
+
Class that stores references to important nodes in an FBX file.
|
| 634 |
+
Offers utility functions to quickly load animation data.
|
| 635 |
+
:param fbx_file: `Path` to the file to load.
|
| 636 |
+
:param volume_dims: `tuple` of `float` that represent the dimensions of the capture volume in meters.
|
| 637 |
+
:param max_actors: `int` maximum amount of actors to expect in a point cloud.
|
| 638 |
+
:param pc_size: `int` amount of points in a point cloud.
|
| 639 |
+
:param debug: If higher than -1, will print out debugging statements.
|
| 640 |
+
"""
|
| 641 |
+
super().__init__(fbx_file=fbx_file, debug=debug)
|
| 642 |
+
|
| 643 |
+
if pc_size < max_actors * 73:
|
| 644 |
+
raise ValueError('Point cloud size must be large enough to contain the maximum amount of actors * 73'
|
| 645 |
+
f' markers: {pc_size}/{max_actors * 73}.')
|
| 646 |
+
|
| 647 |
+
self.debug = debug
|
| 648 |
+
# Python ENUM of the C++ time modes.
|
| 649 |
+
self.time_modes = globals.get_time_modes()
|
| 650 |
+
# Ordered list of marker names. Note: rearrange this in globals.py.
|
| 651 |
+
self.child_names = globals.get_marker_names()
|
| 652 |
+
|
| 653 |
+
# Initiate empty lists to store references to nodes.
|
| 654 |
+
self.children = []
|
| 655 |
+
|
| 656 |
+
self.labeled_world_transforms = None
|
| 657 |
+
self.unlabeled_world_transforms = None
|
| 658 |
+
|
| 659 |
+
# Split the dimensions tuple into its axes for easier access.
|
| 660 |
+
self.vol_x = volume_dims[0]
|
| 661 |
+
self.vol_y = volume_dims[1]
|
| 662 |
+
self.vol_z = volume_dims[2]
|
| 663 |
+
self.hvol_x = volume_dims[0] / 2
|
| 664 |
+
self.hvol_y = volume_dims[1] / 2
|
| 665 |
+
self.hvol_z = volume_dims[2] / 2
|
| 666 |
+
|
| 667 |
+
self.scale = scale
|
| 668 |
+
|
| 669 |
+
self.max_actors = max_actors
|
| 670 |
+
# Maximum point cloud size = 73 * max_actors + unlabeled markers.
|
| 671 |
+
self.pc_size = pc_size
|
| 672 |
+
|
| 673 |
+
self.output_fbx = utils.append_suffix_to_file(fbx_file, '_INF')
|
| 674 |
+
self.valid_frames = []
|
| 675 |
+
|
| 676 |
+
self.init()
|
| 677 |
+
|
| 678 |
+
def __init_unlabeled_markers(self) -> None:
|
| 679 |
"""
|
| 680 |
Looks for the Unlabeled_Markers parent node under the root and stores references to all unlabeled marker nodes.
|
| 681 |
"""
|
|
|
|
| 687 |
self.unlabeled_markers = [gen1_node.GetChild(um) for um in range(gen1_node.GetChildCount())]
|
| 688 |
return
|
| 689 |
|
|
|
|
|
|
|
|
|
|
| 690 |
def init_world_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) -> None:
|
| 691 |
"""
|
| 692 |
Calls the init functions for the labeled and unlabeled world transforms.
|
|
|
|
| 708 |
labeled_data = []
|
| 709 |
|
| 710 |
# Iterate through all actors.
|
| 711 |
+
for actor_idx in range(self.parent_count):
|
| 712 |
# Initialize an empty list to store the results for this actor in.
|
| 713 |
actor_data = []
|
| 714 |
# Iterate through all markers for this actor.
|
| 715 |
+
for marker_idx, (n, m) in enumerate(self.children[actor_idx].items()):
|
| 716 |
# Get this marker's local translation animation curve.
|
| 717 |
# This requires the animation layer, so we can't do it within the function itself.
|
| 718 |
curve = m.LclTranslation.GetCurve(self.anim_layer, 'X', True)
|
|
|
|
| 732 |
self.labeled_world_transforms = np.transpose(wide_layout, axes=(3, 0, 1, 2))
|
| 733 |
return self.labeled_world_transforms
|
| 734 |
|
| 735 |
+
def init_unlabeled_world_transforms(self, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None,
|
| 736 |
+
incl_keyed: int = 0) -> np.array:
|
| 737 |
"""
|
| 738 |
For all unlabeled markers, stores a list for each element in the world transform for each frame
|
| 739 |
in r. This can later be used to recreate the world transform matrix.
|
| 740 |
:param r: Custom frame range to use.
|
| 741 |
+
:param incl_keyed: `bool` whether to check if the marker was keyed at the frame.
|
| 742 |
:return: `np.array` of shape (n_frames, n_unlabeled_markers, 14).
|
| 743 |
"""
|
| 744 |
r = self.convert_r(r)
|
|
|
|
| 750 |
# This requires the animation layer, so we can't do it within the function itself.
|
| 751 |
curve = ulm.LclTranslation.GetCurve(self.anim_layer, 'X', True)
|
| 752 |
# Get a list of each world transform element for all frames.
|
| 753 |
+
marker_data = get_world_transforms(0, 0, ulm, r, curve, incl_keyed=incl_keyed)
|
| 754 |
# Add the result to marker_data.
|
| 755 |
unlabeled_data.append(marker_data)
|
| 756 |
self._print(f'Unlabeled marker {ulm.GetName()} done', 1)
|
|
|
|
| 764 |
# Returns shape (n_frames, n_unlabeled_markers, 14).
|
| 765 |
return self.unlabeled_world_transforms
|
| 766 |
|
| 767 |
+
def init(self) -> None:
|
| 768 |
"""
|
| 769 |
Initializes the scene.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
"""
|
| 771 |
+
self._init_scene()
|
| 772 |
+
self._init_anim()
|
| 773 |
+
self._init_parents(list(self.child_names[:4]))
|
| 774 |
+
self._init_children(self.child_names)
|
| 775 |
+
self.__init_unlabeled_markers()
|
| 776 |
+
self._print('Init done', 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 777 |
|
| 778 |
def columns_from_joints(self) -> List[str]:
|
| 779 |
"""
|
|
|
|
| 781 |
:return: List of column names, in the form of [node1_tx, node1_ty, node1_tz, node2_tx, node2_ty, node2_tz..].
|
| 782 |
"""
|
| 783 |
columns = []
|
| 784 |
+
for name in self.child_names:
|
| 785 |
columns += [f'{name}x', f'{name}y', f'{name}z']
|
| 786 |
|
| 787 |
return columns
|
| 788 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 789 |
def remove_clipping_poses(self, arr: np.array) -> np.array:
|
| 790 |
"""
|
| 791 |
Checks for each axis if it does not cross the volume limits. Returns an array without clipping poses.
|
|
|
|
| 813 |
|
| 814 |
# Returns (n_frames, n_actors, 73, 15).
|
| 815 |
l_shape = self.labeled_world_transforms.shape
|
| 816 |
+
|
| 817 |
# Flatten the array, so we get a list of frames.
|
| 818 |
# Reshape to (n_frames * n_actors, 73, 15).
|
| 819 |
flattened = self.labeled_world_transforms.reshape(-1, l_shape[2], l_shape[3])
|
| 820 |
+
|
| 821 |
# Isolates the poses with all keyframes present by checking the last elements.
|
| 822 |
# Start with the mask.
|
| 823 |
# Returns shape of (n_frames * n_actors, 73, 15).
|
|
|
|
| 825 |
# We only need a filter for the first dimension, so use .all to check if all markers
|
| 826 |
# have a keyframe. This results in shape (n_frames * n_actors,).
|
| 827 |
mask = mask.all(axis=1)
|
| 828 |
+
|
| 829 |
# Now isolate the right frames with the mask and remove the last element of the last dimension,
|
| 830 |
# because it won't be useful anymore.
|
| 831 |
# Also remove any frames that cross the limits of the volume.
|
|
|
|
| 851 |
# If either of the arrays is None, we can initialize them with r.
|
| 852 |
if self.labeled_world_transforms is None:
|
| 853 |
# For inference, we don't need keyed frames, so incl_keyed is False.
|
| 854 |
+
self.init_labeled_world_transforms(r=r, incl_keyed=1)
|
| 855 |
if self.unlabeled_world_transforms is None:
|
| 856 |
# Note: Unlabeled data is already flattened.
|
| 857 |
+
self.init_unlabeled_world_transforms(r=r, incl_keyed=1)
|
| 858 |
|
| 859 |
+
# Returns (n_frames, n_actors, 73, 15).
|
| 860 |
ls = self.labeled_world_transforms.shape
|
| 861 |
# Flatten the array, so we get a list of frames.
|
| 862 |
+
# Returns shape (n_frames, 73 * n_actors, 15).
|
| 863 |
+
flat_labeled = self.labeled_world_transforms.reshape(ls[0], -1, ls[-1])
|
| 864 |
+
|
| 865 |
+
# Find all labeled markers that have their keyed value set to 0 (which means they had no keyframe on tx),
|
| 866 |
+
# and set their transforms to np.inf.
|
| 867 |
+
mask = flat_labeled[..., -1] == 0
|
| 868 |
+
flat_labeled[mask, 2:] = np.inf
|
| 869 |
+
|
| 870 |
+
# Do the same for the unlabeled markers.
|
| 871 |
+
mask = self.unlabeled_world_transforms[..., -1] == 0
|
| 872 |
+
self.unlabeled_world_transforms[mask, 2:] = np.inf
|
| 873 |
+
|
| 874 |
+
# Remove the last element of the last dimension of both arrays, because it won't be useful anymore.
|
| 875 |
+
flat_labeled = flat_labeled[..., :-1]
|
| 876 |
+
self.unlabeled_world_transforms = self.unlabeled_world_transforms[..., :-1]
|
| 877 |
|
| 878 |
if merged:
|
| 879 |
return utils.merge_labeled_and_unlabeled_data(labeled=flat_labeled,
|
|
|
|
| 894 |
|
| 895 |
# First multiply by self.scale, which turns centimeters to meters.
|
| 896 |
# Then divide by volume dimensions, to normalize to the total area of the capture volume.
|
| 897 |
+
w[..., start + 0] = w[..., start + 0] * self.scale / self.vol_x
|
| 898 |
+
w[..., start + 1] = w[..., start + 1] * self.scale / self.vol_y
|
| 899 |
+
w[..., start + 2] = w[..., start + 2] * self.scale / self.vol_z
|
| 900 |
|
| 901 |
# Then move the x and z to the center of the volume. Y doesn't need to be done because pose needs to stand
|
| 902 |
# on the floor.
|
|
|
|
| 924 |
# Return np arrays as (actor classes, marker classes, translation vectors, rotation vectors, scale vectors).
|
| 925 |
return cloud[:, :, 0], cloud[:, :, 1], cloud[:, :, 2:5], cloud[:, :, 6:9], cloud[:, :, 10:13]
|
| 926 |
|
| 927 |
+
def convert_class_to_parent(self, c: float = 0):
|
| 928 |
"""
|
| 929 |
Returns the actor name based on the class value.
|
| 930 |
:param c: `float` actor class index.
|
| 931 |
:return: `str` actor name.
|
| 932 |
"""
|
| 933 |
+
return 'UNLABELED' if int(c) == 0 else self.parent_names[int(c) - 1]
|
| 934 |
|
| 935 |
+
def convert_class_to_child(self, c: float = 0):
|
| 936 |
"""
|
| 937 |
Returns the marker name based on the class value.
|
| 938 |
:param c: `float` marker class index.
|
| 939 |
:return: `str` marker name.
|
| 940 |
"""
|
| 941 |
+
return 'UNLABELED' if int(c) == 0 else self.child_names[int(c) - 1]
|
| 942 |
|
| 943 |
def export_train_data(self, output_file: Path, r: Union[int, Tuple[int, int], Tuple[int, int, int]] = None) \
|
| 944 |
-> Union[bytes, pd.DataFrame, np.array]:
|
|
|
|
| 1019 |
|
| 1020 |
return True
|
| 1021 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1022 |
def remove_unlabeled_markers(self) -> None:
|
| 1023 |
"""
|
| 1024 |
Uses self.remove_node() to delete all unlabeled markers from the scene.
|
|
|
|
| 1134 |
:param actor: `int` actor index to apply to. Index starts at 0.
|
| 1135 |
:param actor_keys: `dict` with all marker keys for this actor.
|
| 1136 |
"""
|
| 1137 |
+
for marker_class, (marker_name, marker) in enumerate(self.children[actor].items(), start=1):
|
| 1138 |
marker_keys = actor_keys.get(marker_class)
|
| 1139 |
if marker_keys:
|
| 1140 |
self._print(f'Replacing keys for {marker_name}', 1)
|
|
|
|
| 1145 |
For all actors, uses self.replace_keyframes_per_actor() to set keyframes on each actor's marker nodes.
|
| 1146 |
:param key_dict: `dict` with all actor keyframes.
|
| 1147 |
"""
|
| 1148 |
+
for actor_idx in range(self.parent_count):
|
| 1149 |
actor_dict = key_dict.get(actor_idx + 1)
|
| 1150 |
if actor_dict:
|
| 1151 |
self._print(f'Replacing keys for actor {actor_idx}', 1)
|