From 63ad217f832d8a0d56e78f20ec637f66854622ec Mon Sep 17 00:00:00 2001 From: cleong110 Date: Fri, 8 Mar 2024 11:42:08 -0500 Subject: [PATCH 1/9] CDL: minor doc typo fix --- docs/specs/v0.1.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/specs/v0.1.md b/docs/specs/v0.1.md index b3ea36b..32b2be1 100644 --- a/docs/specs/v0.1.md +++ b/docs/specs/v0.1.md @@ -24,10 +24,16 @@ \[`unsigned short` Green] \[`unsigned short` Blue] + +[Testing comment syntax from https://stackoverflow.com/a/32190021]: # + # Body -\[`unsined short` FPS] -\[`unsined short` Number of frames] # THIS IS A PROBLEM -\[`unsined short` Number of people] +\[`unsigned short` FPS] + + +[THIS IS A PROBLEM]: # +\[`unsigned short` Number of frames] +\[`unsigned short` Number of people] ## For every frame #### For every person: From 6d18ed642f883ab128e783915193f27900eef975 Mon Sep 17 00:00:00 2001 From: Colin Leong <122366389+cleong110@users.noreply.github.com> Date: Thu, 19 Dec 2024 09:39:31 -0500 Subject: [PATCH 2/9] Undoing some changes that got mixed in --- docs/specs/v0.1.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/specs/v0.1.md b/docs/specs/v0.1.md index a75b0a2..2ee3aaa 100644 --- a/docs/specs/v0.1.md +++ b/docs/specs/v0.1.md @@ -26,12 +26,9 @@ # Body -\[`unsigned short` FPS] - - -[THIS IS A PROBLEM]: # -\[`unsigned short` Number of frames] -\[`unsigned short` Number of people] +\[`unsined short` FPS] +\[`unsined short` Number of frames] # THIS IS A PROBLEM +\[`unsined short` Number of people] ## For every frame #### For every person: From 48332f9e4754066eb78893b4601f96fdae5421b6 Mon Sep 17 00:00:00 2001 From: Colin Leong <122366389+cleong110@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:44:46 -0500 Subject: [PATCH 3/9] Add Pose .copy() and .remove_components() --- src/python/pose_format/pose.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/python/pose_format/pose.py b/src/python/pose_format/pose.py index 06dbfab..98fef70 100644 --- a/src/python/pose_format/pose.py +++ b/src/python/pose_format/pose.py @@ -202,6 +202,28 @@ def frame_dropout_normal(self, dropout_mean: float = 0.5, dropout_std: float = 0 """ body, selected_indexes = self.body.frame_dropout_normal(dropout_mean=dropout_mean, dropout_std=dropout_std) return Pose(header=self.header, body=body), selected_indexes + + + def remove_components(self, components_to_remove: List[str], points_to_remove: Union[Dict[str, List[str]],None] = None): + + if isinstance(components_to_remove, str): + components_to_remove = [components_to_remove] + + components_to_keep = [] + points_dict = {} + + for component in self.header.components: + if component.name not in components_to_remove: + components_to_keep.append(component.name) + points_dict[component.name] = [] + if points_to_remove is not None: + for point in component.points: + if point not in points_to_remove[component.name]: + points_dict[component.name].append(point) + + return self.get_components(components_to_keep, points_dict) + + def get_components(self, components: List[str], points: Union[Dict[str, List[str]],None] = None): """ @@ -253,6 +275,10 @@ def get_components(self, components: List[str], points: Union[Dict[str, List[str new_body = self.body.get_points(flat_indexes) return Pose(header=new_header, body=new_body) + + + def copy(self): + return self.get_components([c.name for c in self.header.components]) def bbox(self): """ From 3e5b0025d77011f5940919c43beb51b6a1e773c0 Mon Sep 17 00:00:00 2001 From: Colin Leong <122366389+cleong110@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:06:36 -0500 Subject: [PATCH 4/9] fix type annotation in pose remove_components --- src/python/pose_format/pose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/pose_format/pose.py b/src/python/pose_format/pose.py index 98fef70..c798c21 100644 --- a/src/python/pose_format/pose.py +++ b/src/python/pose_format/pose.py @@ -204,7 +204,7 @@ def frame_dropout_normal(self, dropout_mean: float = 0.5, dropout_std: float = 0 return Pose(header=self.header, body=body), selected_indexes - def remove_components(self, components_to_remove: List[str], points_to_remove: Union[Dict[str, List[str]],None] = None): + def remove_components(self, components_to_remove: Union[str, List[str]], points_to_remove: Union[Dict[str, List[str]],None] = None): if isinstance(components_to_remove, str): components_to_remove = [components_to_remove] From ca284ad2ee9b77513f1108fad7130aa6ccc5270c Mon Sep 17 00:00:00 2001 From: Colin Leong <122366389+cleong110@users.noreply.github.com> Date: Wed, 29 Jan 2025 12:05:12 -0500 Subject: [PATCH 5/9] Adding copy functions to posebodies, and tests for this --- src/python/pose_format/numpy/pose_body.py | 6 + src/python/pose_format/pose.py | 2 +- src/python/pose_format/pose_body.py | 28 +-- .../pose_format/tensorflow/pose_body.py | 13 ++ src/python/pose_format/torch/pose_body.py | 15 ++ src/python/tests/pose_test.py | 176 +++++++++++++++++- 6 files changed, 225 insertions(+), 15 deletions(-) diff --git a/src/python/pose_format/numpy/pose_body.py b/src/python/pose_format/numpy/pose_body.py index 38fed36..ccd0e14 100644 --- a/src/python/pose_format/numpy/pose_body.py +++ b/src/python/pose_format/numpy/pose_body.py @@ -128,6 +128,12 @@ def write(self, version: float, buffer: BinaryIO): buffer.write(np.array(self.data.data, dtype=np.float32).tobytes()) buffer.write(np.array(self.confidence, dtype=np.float32).tobytes()) + def copy(self) -> PoseBody: + return type(self)(fps=self.fps, + data=self.data.copy(), + confidence=self.confidence.copy() + ) + @property def mask(self): """ Returns mask associated with data. """ diff --git a/src/python/pose_format/pose.py b/src/python/pose_format/pose.py index c798c21..5031f64 100644 --- a/src/python/pose_format/pose.py +++ b/src/python/pose_format/pose.py @@ -278,7 +278,7 @@ def get_components(self, components: List[str], points: Union[Dict[str, List[str def copy(self): - return self.get_components([c.name for c in self.header.components]) + return self.__class__(self.header, self.body.copy()) def bbox(self): """ diff --git a/src/python/pose_format/pose_body.py b/src/python/pose_format/pose_body.py index ca0a0da..b9e7f84 100644 --- a/src/python/pose_format/pose_body.py +++ b/src/python/pose_format/pose_body.py @@ -1,5 +1,5 @@ from random import sample -from typing import BinaryIO, List, Tuple +from typing import BinaryIO, List, Tuple, Optional import numpy as np import math @@ -93,8 +93,8 @@ def read_v0_1_frames(cls, frames: int, shape: List[int], reader: BufferReader, - start_frame: int = None, - end_frame: int = None): + start_frame: Optional[int] = None, + end_frame: Optional[int] = None): """ Reads frame data for version 0.1 from a buffer. @@ -149,8 +149,8 @@ def read_v0_1_frames(cls, def read_v0_1(cls, header: PoseHeader, reader: BufferReader, - start_frame: int = None, - end_frame: int = None, + start_frame: Optional[int] = None, + end_frame: Optional[int] = None, **unused_kwargs) -> "PoseBody": """ Reads pose data for version 0.1 from a buffer. @@ -191,10 +191,10 @@ def read_v0_1(cls, def read_v0_2(cls, header: PoseHeader, reader: BufferReader, - start_frame: int = None, - end_frame: int = None, - start_time: int = None, - end_time: int = None, + start_frame: Optional[int] = None, + end_frame: Optional[int] = None, + start_time: Optional[int] = None, + end_time: Optional[int] = None, **unused_kwargs) -> "PoseBody": """ Reads pose data for version 0.2 from a buffer. @@ -256,6 +256,12 @@ def write(self, version: float, buffer: BinaryIO): Buffer to write the pose data to. """ raise NotImplementedError("'write' not implemented on '%s'" % self.__class__) + + def copy(self)->"PoseBody": + return type(self)(fps=self.fps, + data=self.data, + confidence=self.confidence + ) def __getitem__(self, index): """ @@ -306,7 +312,7 @@ def torch(self): Raises ------ NotImplementedError - If toch is not implemented. + If torch is not implemented. """ raise NotImplementedError("'torch' not implemented on '%s'" % self.__class__) @@ -474,7 +480,7 @@ def get_points(self, indexes: List[int]) -> __qualname__: Returns ------- PoseBody - PoseBody instance containing only choosen points. + PoseBody instance containing only chosen points. Raises ------ diff --git a/src/python/pose_format/tensorflow/pose_body.py b/src/python/pose_format/tensorflow/pose_body.py index b804cf8..208674c 100644 --- a/src/python/pose_format/tensorflow/pose_body.py +++ b/src/python/pose_format/tensorflow/pose_body.py @@ -152,6 +152,19 @@ def points_perspective(self) -> MaskedTensor: """ return self.data.transpose(perm=POINTS_DIMS) + def copy(self) -> PoseBody: + # Ensure copies are fully detached from the TF computation graph by round-trip through numpy + detached_data = tf.convert_to_tensor(self.data.tensor.numpy()) + detached_mask = tf.convert_to_tensor(self.data.mask.numpy()) + data_copy = MaskedTensor(detached_data, detached_mask) + confidence_copy = tf.convert_to_tensor(self.confidence.numpy()) + + return self.__class__( + fps=self.fps, + data=data_copy, + confidence=confidence_copy, + ) + def get_points(self, indexes: List[int]): """ Gets and returns points from pose data based on indexes diff --git a/src/python/pose_format/torch/pose_body.py b/src/python/pose_format/torch/pose_body.py index a062cea..0fd467f 100644 --- a/src/python/pose_format/torch/pose_body.py +++ b/src/python/pose_format/torch/pose_body.py @@ -32,6 +32,18 @@ def cuda(self): self.data = self.data.cuda() self.confidence = self.confidence.cuda() + def copy(self) -> PoseBody: + data_copy = MaskedTensor(tensor=self.data.tensor.detach().clone().to(self.data.tensor.device), + mask=self.data.mask.detach().clone().to(self.data.mask.device), + ) + confidence_copy = self.confidence.detach().clone().to(self.confidence.device) + + return self.__class__(fps=self.fps, + data=data_copy, + confidence=confidence_copy + ) + + def zero_filled(self) -> 'TorchPoseBody': """ Fill invalid values with zeros. @@ -120,3 +132,6 @@ def flatten(self): scalar = torch.ones(len(shape) + shape[-1], device=data.device) scalar[0] = 1 / self.fps return flat * scalar + + + diff --git a/src/python/tests/pose_test.py b/src/python/tests/pose_test.py index 22f7526..8f2fa17 100644 --- a/src/python/tests/pose_test.py +++ b/src/python/tests/pose_test.py @@ -1,18 +1,21 @@ import random import string -from typing import Optional, Tuple +from typing import Optional, Tuple, cast from unittest import TestCase import numpy as np import numpy.ma as ma import tensorflow as tf +import torch from pose_format.numpy.pose_body import NumPyPoseBody from pose_format.pose import Pose from pose_format.pose_header import (PoseHeader, PoseHeaderComponent, PoseHeaderDimensions) -from pose_format.tensorflow.masked.tensor import MaskedTensor +from pose_format.tensorflow.masked.tensor import MaskedTensor as TF_MaskedTensor from pose_format.tensorflow.pose_body import TensorflowPoseBody +from pose_format.torch.pose_body import TorchPoseBody +from pose_format.torch.masked import MaskedTensor as PoseFormatTorchMaskedTensor def _create_pose_header_component(name: str, num_keypoints: int) -> PoseHeaderComponent: @@ -179,6 +182,77 @@ def _create_random_numpy_data(frames_min: Optional[int] = None, return tensor, mask, confidence +def _create_random_torch_data(frames_min: Optional[int] = None, + frames_max: Optional[int] = None, + num_frames: Optional[int] = None, + num_keypoints: int = 137, + num_dimensions: int = 2) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Creates random PyTorch data for testing. + + Parameters + ---------- + frames_min : Optional[int], default=None + Minimum number of frames for random generation if `num_frames` is not specified. + frames_max : Optional[int], default=None + Maximum number of frames for random generation if `num_frames` is not specified. + num_frames : Optional[int], default=None + Specific number of frames. + num_keypoints : int, default=137 + Number of keypoints in the pose data. + num_dimensions : int, default=2 + Number of dimensions in the pose data. + + Returns + ------- + Tuple[torch.Tensor, torch.Tensor, torch.Tensor] + Random tensor data, mask, and confidence values. + """ + if num_frames is None: + assert None not in [frames_min, frames_max] + num_frames = np.random.randint(frames_min, frames_max + 1) + else: + assert frames_min is None and frames_max is None + + # Avoid a mean of zero to test certain pose methods + tensor = torch.randn((num_frames, 1, num_keypoints, num_dimensions)) + 1.0 # Shape: (Frames, People, Points, Dims) + + confidence = torch.rand((num_frames, 1, num_keypoints), dtype=torch.float32) # Shape: (Frames, People, Points) + + mask = torch.randint(0, 2, (num_frames, 1, num_keypoints, num_dimensions), dtype=torch.bool) # Bool mask + + return tensor, mask, confidence + + +def _get_random_pose_object_with_torch_posebody(num_keypoints: int, frames_min: int = 1, frames_max: int = 10) -> Pose: + """ + Generates a random Pose object with PyTorch pose body for testing. + + Parameters + ---------- + num_keypoints : int + Number of keypoints in the pose data. + frames_min : int, default=1 + Minimum number of frames for random generation. + frames_max : int, default=10 + Maximum number of frames for random generation. + + Returns + ------- + Pose + Randomly generated Pose object. + """ + + tensor, mask, confidence = _create_random_torch_data(frames_min=frames_min, + frames_max=frames_max, + num_keypoints=num_keypoints) + + masked_tensor = PoseFormatTorchMaskedTensor(tensor=tensor, mask=mask) + body = TorchPoseBody(fps=10, data=masked_tensor, confidence=confidence) + + header = _create_pose_header(width=10, height=7, depth=0, num_components=3, num_keypoints=num_keypoints) + + return Pose(header=header, body=body) def _get_random_pose_object_with_tf_posebody(num_keypoints: int, frames_min: int = 1, frames_max: int = 10) -> Pose: """ @@ -203,7 +277,7 @@ def _get_random_pose_object_with_tf_posebody(num_keypoints: int, frames_min: int frames_max=frames_max, num_keypoints=num_keypoints) - masked_tensor = MaskedTensor(tensor=tensor, mask=mask) + masked_tensor = TF_MaskedTensor(tensor=tensor, mask=mask) body = TensorflowPoseBody(fps=10, data=masked_tensor, confidence=confidence) header = _create_pose_header(width=10, height=7, depth=0, num_components=3, num_keypoints=num_keypoints) @@ -255,6 +329,7 @@ def test_pose_object_should_be_callable(self): assert callable(Pose) + class TestPoseTensorflowPoseBody(TestCase): """ Tests for Pose objects containing TensorFlow PoseBody data. @@ -399,6 +474,39 @@ def create_pose_and_frame_dropout_uniform(example: tf.Tensor) -> tf.Tensor: return example dataset.map(create_pose_and_frame_dropout_uniform) + + + def test_pose_tf_posebody_copy_creates_deepcopy(self): + pose = _get_random_pose_object_with_tf_posebody(num_keypoints=5) + self.assertIsInstance(pose.body, TensorflowPoseBody) + self.assertIsInstance(pose.body.data, TF_MaskedTensor) + + pose_copy = pose.copy() + self.assertIsInstance(pose_copy.body, TensorflowPoseBody) + self.assertIsInstance(pose_copy.body.data, TF_MaskedTensor) + + # Check that pose and pose_copy are not the same object + self.assertNotEqual(pose, pose_copy, "Copy of pose should not be 'equal' to original") + + # Ensure the data tensors are equal but independent + self.assertTrue(tf.reduce_all(pose.body.data == pose_copy.body.data), "Copy's data should match original") + + # Modify original pose's data and check that copy remains unchanged + pose.body.data = TF_MaskedTensor(tf.zeros_like(pose.body.data.tensor), tf.zeros_like(pose.body.data.mask)) + + self.assertFalse(tf.reduce_all(pose.body.data == pose_copy.body.data), "Copy's data should not match original after original is replaced") + + # Create another copy and ensure it matches the first copy + pose = pose_copy.copy() + + self.assertTrue(tf.reduce_all(pose.body.data == pose_copy.body.data), "Copy's data should match original again") + + # Modify the copy and check that the original remains unchanged + pose_copy.body.data.tensor = tf.zeros(pose_copy.body.data.tensor.shape) + + self.assertFalse(tf.reduce_all(pose.body.data == pose_copy.body.data), "Copy's data should not match original after copy is modified") + + class TestPoseNumpyPoseBody(TestCase): @@ -445,3 +553,65 @@ def test_pose_numpy_posebody_frame_dropout_uniform_eager_mode_num_frames_not_zer num_frames = pose_after_dropout.body.data.shape[0] self.assertNotEqual(num_frames, 0, "Number of frames after dropout can never be 0.") + + def test_pose_numpy_posebody_copy_creates_deepcopy(self): + + pose = _get_random_pose_object_with_numpy_posebody(num_keypoints=5, frames_min=3) + + pose_copy = pose.copy() + + self.assertNotEqual(pose, pose_copy, "Copy of pose should not be 'equal' to original") + + self.assertTrue(np.array_equal(pose.body.data, pose_copy.body.data), "Copy's data should match original") + + pose.body.data = ma.zeros(pose.body.data.shape) + + self.assertFalse(np.array_equal(pose.body.data, pose_copy.body.data), "Copy's data should not match original after original is replaced") + + pose = pose_copy.copy() + + self.assertTrue(np.array_equal(pose.body.data, pose_copy.body.data), "Copy's data should match original again") + + pose_copy.body.data[:] = 3.14 + + self.assertFalse(np.array_equal(pose.body.data, pose_copy.body.data), "Copy's data should not match original after copy is modified") + + + + +class TestPoseTorchPoseBody(TestCase): + + def test_pose_torch_posebody_copy_tensors_detached(self): + pose = _get_random_pose_object_with_torch_posebody(num_keypoints=5) + pose_copy = pose.copy() + + self.assertFalse(pose.body.data.data.requires_grad, "Copied data should be detached from computation graph") + self.assertFalse(pose.body.data.mask.requires_grad, "Copied mask should be detached from computation graph") + + def test_pose_torch_posebody_copy_creates_deepcopy(self): + pose = _get_random_pose_object_with_torch_posebody(num_keypoints=5) + self.assertIsInstance(pose.body, TorchPoseBody) + self.assertIsInstance(pose.body.data, PoseFormatTorchMaskedTensor) + + + pose_copy = pose.copy() + self.assertIsInstance(pose_copy.body, TorchPoseBody) + self.assertIsInstance(pose_copy.body.data, PoseFormatTorchMaskedTensor) + + self.assertNotEqual(pose, pose_copy, "Copy of pose should not be 'equal' to original") + self.assertTrue(pose.body.data.tensor.equal(pose_copy.body.data.tensor), "Copy's data should match original") + # self.assertTrue(pose.body.data.mask == pose_copy.body.data.mask, "Copy's mask should match original") + + # pose.body.data = torch.masked.MaskedTensor(data=torch.zeros_like(pose.body.data.tensor), + # mask=torch.ones_like(pose.body.data.mask)) + + # self.assertFalse(pose.body.data.data == pose_copy.body.data.data, "Copy's data should not match original after original is replaced") + # self.assertFalse(pose.body.data.mask == pose_copy.body.data.mask, "Copy's mask should not match original after original is replaced") + + # pose = pose_copy.copy() + + # self.assertTrue(pose.body.data.data == pose_copy.body.data.data, "Copy's data should match original again") + + # pose_copy.body.data.fill_(3.14) + + # self.assertFalse(pose.body.data.data == pose_copy.body.data.data, "Copy's data should not match original after copy is modified") From dceb543f882f009a57ec57e0112fa439d9fa0336 Mon Sep 17 00:00:00 2001 From: Colin Leong <122366389+cleong110@users.noreply.github.com> Date: Wed, 29 Jan 2025 12:12:03 -0500 Subject: [PATCH 6/9] Some pylint changes --- src/python/pose_format/pose_body.py | 9 +++++---- src/python/pose_format/tensorflow/pose_body.py | 9 ++++----- src/python/pose_format/torch/pose_body.py | 2 -- src/python/tests/pose_test.py | 6 +++--- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/python/pose_format/pose_body.py b/src/python/pose_format/pose_body.py index b9e7f84..8349b88 100644 --- a/src/python/pose_format/pose_body.py +++ b/src/python/pose_format/pose_body.py @@ -1,8 +1,9 @@ +import math from random import sample from typing import BinaryIO, List, Tuple, Optional import numpy as np -import math + from pose_format.pose_header import PoseHeader from pose_format.utils.reader import BufferReader, ConstStructs @@ -60,9 +61,9 @@ def read(cls, header: PoseHeader, reader: BufferReader, **kwargs) -> "PoseBody": if header.version == 0: return cls.read_v0_0(header, reader, **kwargs) - elif round(header.version, 3) == 0.1: + if round(header.version, 3) == 0.1: return cls.read_v0_1(header, reader, **kwargs) - elif round(header.version, 3) == 0.2: + if round(header.version, 3) == 0.2: return cls.read_v0_2(header, reader, **kwargs) raise NotImplementedError("Unknown version - %f" % header.version) @@ -176,7 +177,7 @@ def read_v0_1(cls, fps, _frames = reader.unpack(ConstStructs.double_ushort) _people = reader.unpack(ConstStructs.ushort) - _points = sum([len(c.points) for c in header.components]) + _points = sum(len(c.points) for c in header.components) _dims = header.num_dims() # _frames is defined as short, which sometimes is not enough! TODO change to int diff --git a/src/python/pose_format/tensorflow/pose_body.py b/src/python/pose_format/tensorflow/pose_body.py index 208674c..5c8691f 100644 --- a/src/python/pose_format/tensorflow/pose_body.py +++ b/src/python/pose_format/tensorflow/pose_body.py @@ -153,18 +153,17 @@ def points_perspective(self) -> MaskedTensor: return self.data.transpose(perm=POINTS_DIMS) def copy(self) -> PoseBody: - # Ensure copies are fully detached from the TF computation graph by round-trip through numpy + # Ensure copies are fully detached from the TF computation graph by round-trip through numpy detached_data = tf.convert_to_tensor(self.data.tensor.numpy()) - detached_mask = tf.convert_to_tensor(self.data.mask.numpy()) + detached_mask = tf.convert_to_tensor(self.data.mask.numpy()) data_copy = MaskedTensor(detached_data, detached_mask) confidence_copy = tf.convert_to_tensor(self.confidence.numpy()) - return self.__class__( fps=self.fps, data=data_copy, - confidence=confidence_copy, + confidence=confidence_copy, ) - + def get_points(self, indexes: List[int]): """ Gets and returns points from pose data based on indexes diff --git a/src/python/pose_format/torch/pose_body.py b/src/python/pose_format/torch/pose_body.py index 0fd467f..f14ffa5 100644 --- a/src/python/pose_format/torch/pose_body.py +++ b/src/python/pose_format/torch/pose_body.py @@ -4,8 +4,6 @@ import torch from ..pose_body import POINTS_DIMS, PoseBody -from ..pose_header import PoseHeader -from ..utils.reader import BufferReader from .masked.tensor import MaskedTensor diff --git a/src/python/tests/pose_test.py b/src/python/tests/pose_test.py index 8f2fa17..de6fa31 100644 --- a/src/python/tests/pose_test.py +++ b/src/python/tests/pose_test.py @@ -1,10 +1,10 @@ import random import string -from typing import Optional, Tuple, cast +from typing import Optional, Tuple from unittest import TestCase import numpy as np -import numpy.ma as ma +from numpy import ma import tensorflow as tf import torch @@ -586,7 +586,7 @@ def test_pose_torch_posebody_copy_tensors_detached(self): pose_copy = pose.copy() self.assertFalse(pose.body.data.data.requires_grad, "Copied data should be detached from computation graph") - self.assertFalse(pose.body.data.mask.requires_grad, "Copied mask should be detached from computation graph") + self.assertFalse(pose_copy.body.data.mask.requires_grad, "Copied mask should be detached from computation graph") def test_pose_torch_posebody_copy_creates_deepcopy(self): pose = _get_random_pose_object_with_torch_posebody(num_keypoints=5) From 8d598320c1b910bcc95e71148147c6101d8be4fd Mon Sep 17 00:00:00 2001 From: Colin Leong <122366389+cleong110@users.noreply.github.com> Date: Fri, 31 Jan 2025 09:26:17 -0500 Subject: [PATCH 7/9] Fix return type annotations, use copy() in zero_filled --- src/python/pose_format/numpy/pose_body.py | 10 +++++----- src/python/pose_format/pose_body.py | 5 ++--- src/python/pose_format/tensorflow/pose_body.py | 14 +++++++------- src/python/pose_format/torch/pose_body.py | 12 ++++++------ 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/python/pose_format/numpy/pose_body.py b/src/python/pose_format/numpy/pose_body.py index ccd0e14..60d60ea 100644 --- a/src/python/pose_format/numpy/pose_body.py +++ b/src/python/pose_format/numpy/pose_body.py @@ -128,11 +128,10 @@ def write(self, version: float, buffer: BinaryIO): buffer.write(np.array(self.data.data, dtype=np.float32).tobytes()) buffer.write(np.array(self.confidence, dtype=np.float32).tobytes()) - def copy(self) -> PoseBody: + def copy(self) -> 'NumPyPoseBody': return type(self)(fps=self.fps, data=self.data.copy(), - confidence=self.confidence.copy() - ) + confidence=self.confidence.copy()) @property def mask(self): @@ -187,8 +186,9 @@ def zero_filled(self): NumPyPoseBody changed pose body data. """ - self.data = ma.array(self.data.filled(0), mask=self.data.mask) - return self + copy = self.copy() + copy.data = ma.array(copy.data.filled(0), mask=copy.data.mask) + return copy def matmul(self, matrix: np.ndarray): """ diff --git a/src/python/pose_format/pose_body.py b/src/python/pose_format/pose_body.py index 8349b88..32d99a7 100644 --- a/src/python/pose_format/pose_body.py +++ b/src/python/pose_format/pose_body.py @@ -259,10 +259,9 @@ def write(self, version: float, buffer: BinaryIO): raise NotImplementedError("'write' not implemented on '%s'" % self.__class__) def copy(self)->"PoseBody": - return type(self)(fps=self.fps, + return self.__class__(fps=self.fps, data=self.data, - confidence=self.confidence - ) + confidence=self.confidence) def __getitem__(self, index): """ diff --git a/src/python/pose_format/tensorflow/pose_body.py b/src/python/pose_format/tensorflow/pose_body.py index 5c8691f..c7bd282 100644 --- a/src/python/pose_format/tensorflow/pose_body.py +++ b/src/python/pose_format/tensorflow/pose_body.py @@ -17,7 +17,7 @@ class TensorflowPoseBody(PoseBody): """ Representation of pose body data, optimized for TensorFlow operations. - * Inherites from PoseBody + * Inherits from PoseBody Parameters ---------- @@ -43,10 +43,11 @@ def __init__(self, fps: float, data: Union[MaskedTensor, tf.Tensor], confidence: super().__init__(fps, data, confidence) - def zero_filled(self): + def zero_filled(self) -> 'TensorflowPoseBody': """Return an instance with zero-filled data.""" - self.data = self.data.zero_filled() - return self + copy = self.copy() + copy.data = self.data.zero_filled() + return copy def select_frames(self, frame_indexes: List[int]): """ @@ -152,7 +153,7 @@ def points_perspective(self) -> MaskedTensor: """ return self.data.transpose(perm=POINTS_DIMS) - def copy(self) -> PoseBody: + def copy(self) -> 'TensorflowPoseBody': # Ensure copies are fully detached from the TF computation graph by round-trip through numpy detached_data = tf.convert_to_tensor(self.data.tensor.numpy()) detached_mask = tf.convert_to_tensor(self.data.mask.numpy()) @@ -161,8 +162,7 @@ def copy(self) -> PoseBody: return self.__class__( fps=self.fps, data=data_copy, - confidence=confidence_copy, - ) + confidence=confidence_copy) def get_points(self, indexes: List[int]): """ diff --git a/src/python/pose_format/torch/pose_body.py b/src/python/pose_format/torch/pose_body.py index f14ffa5..7038d8f 100644 --- a/src/python/pose_format/torch/pose_body.py +++ b/src/python/pose_format/torch/pose_body.py @@ -26,11 +26,11 @@ def __init__(self, fps: float, data: Union[MaskedTensor, torch.Tensor], confiden super().__init__(fps, data, confidence) def cuda(self): - """Move data and cofidence of tensors to GPU""" + """Move data and confidence of tensors to GPU""" self.data = self.data.cuda() self.confidence = self.confidence.cuda() - def copy(self) -> PoseBody: + def copy(self) -> 'TorchPoseBody': data_copy = MaskedTensor(tensor=self.data.tensor.detach().clone().to(self.data.tensor.device), mask=self.data.mask.detach().clone().to(self.data.mask.device), ) @@ -38,8 +38,7 @@ def copy(self) -> PoseBody: return self.__class__(fps=self.fps, data=data_copy, - confidence=confidence_copy - ) + confidence=confidence_copy) def zero_filled(self) -> 'TorchPoseBody': @@ -52,8 +51,9 @@ def zero_filled(self) -> 'TorchPoseBody': TorchPoseBody instance with masked data filled with zeros. """ - self.data.zero_filled() - return self + copy = self.copy() + copy.data = copy.data.zero_filled() + return copy def matmul(self, matrix: np.ndarray) -> 'TorchPoseBody': """ From e02fbdfdbc9af50c3faad91fe7740936509538f1 Mon Sep 17 00:00:00 2001 From: Colin Leong <122366389+cleong110@users.noreply.github.com> Date: Fri, 31 Jan 2025 09:35:16 -0500 Subject: [PATCH 8/9] uncomment pytests for torchposebody, rename MaskedTensor imports --- src/python/tests/pose_test.py | 39 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/python/tests/pose_test.py b/src/python/tests/pose_test.py index de6fa31..df65551 100644 --- a/src/python/tests/pose_test.py +++ b/src/python/tests/pose_test.py @@ -12,10 +12,11 @@ from pose_format.pose import Pose from pose_format.pose_header import (PoseHeader, PoseHeaderComponent, PoseHeaderDimensions) -from pose_format.tensorflow.masked.tensor import MaskedTensor as TF_MaskedTensor +from pose_format.tensorflow.masked.tensor import MaskedTensor as TensorflowMaskedTensor +from pose_format.torch.masked import MaskedTensor as TorchMaskedTensor from pose_format.tensorflow.pose_body import TensorflowPoseBody from pose_format.torch.pose_body import TorchPoseBody -from pose_format.torch.masked import MaskedTensor as PoseFormatTorchMaskedTensor + def _create_pose_header_component(name: str, num_keypoints: int) -> PoseHeaderComponent: @@ -247,7 +248,7 @@ def _get_random_pose_object_with_torch_posebody(num_keypoints: int, frames_min: frames_max=frames_max, num_keypoints=num_keypoints) - masked_tensor = PoseFormatTorchMaskedTensor(tensor=tensor, mask=mask) + masked_tensor = TorchMaskedTensor(tensor=tensor, mask=mask) body = TorchPoseBody(fps=10, data=masked_tensor, confidence=confidence) header = _create_pose_header(width=10, height=7, depth=0, num_components=3, num_keypoints=num_keypoints) @@ -277,7 +278,7 @@ def _get_random_pose_object_with_tf_posebody(num_keypoints: int, frames_min: int frames_max=frames_max, num_keypoints=num_keypoints) - masked_tensor = TF_MaskedTensor(tensor=tensor, mask=mask) + masked_tensor = TensorflowMaskedTensor(tensor=tensor, mask=mask) body = TensorflowPoseBody(fps=10, data=masked_tensor, confidence=confidence) header = _create_pose_header(width=10, height=7, depth=0, num_components=3, num_keypoints=num_keypoints) @@ -479,11 +480,11 @@ def create_pose_and_frame_dropout_uniform(example: tf.Tensor) -> tf.Tensor: def test_pose_tf_posebody_copy_creates_deepcopy(self): pose = _get_random_pose_object_with_tf_posebody(num_keypoints=5) self.assertIsInstance(pose.body, TensorflowPoseBody) - self.assertIsInstance(pose.body.data, TF_MaskedTensor) + self.assertIsInstance(pose.body.data, TensorflowMaskedTensor) pose_copy = pose.copy() self.assertIsInstance(pose_copy.body, TensorflowPoseBody) - self.assertIsInstance(pose_copy.body.data, TF_MaskedTensor) + self.assertIsInstance(pose_copy.body.data, TensorflowMaskedTensor) # Check that pose and pose_copy are not the same object self.assertNotEqual(pose, pose_copy, "Copy of pose should not be 'equal' to original") @@ -492,7 +493,7 @@ def test_pose_tf_posebody_copy_creates_deepcopy(self): self.assertTrue(tf.reduce_all(pose.body.data == pose_copy.body.data), "Copy's data should match original") # Modify original pose's data and check that copy remains unchanged - pose.body.data = TF_MaskedTensor(tf.zeros_like(pose.body.data.tensor), tf.zeros_like(pose.body.data.mask)) + pose.body.data = TensorflowMaskedTensor(tf.zeros_like(pose.body.data.tensor), tf.zeros_like(pose.body.data.mask)) self.assertFalse(tf.reduce_all(pose.body.data == pose_copy.body.data), "Copy's data should not match original after original is replaced") @@ -591,27 +592,29 @@ def test_pose_torch_posebody_copy_tensors_detached(self): def test_pose_torch_posebody_copy_creates_deepcopy(self): pose = _get_random_pose_object_with_torch_posebody(num_keypoints=5) self.assertIsInstance(pose.body, TorchPoseBody) - self.assertIsInstance(pose.body.data, PoseFormatTorchMaskedTensor) + self.assertIsInstance(pose.body.data, TorchMaskedTensor) pose_copy = pose.copy() self.assertIsInstance(pose_copy.body, TorchPoseBody) - self.assertIsInstance(pose_copy.body.data, PoseFormatTorchMaskedTensor) + self.assertIsInstance(pose_copy.body.data, TorchMaskedTensor) self.assertNotEqual(pose, pose_copy, "Copy of pose should not be 'equal' to original") self.assertTrue(pose.body.data.tensor.equal(pose_copy.body.data.tensor), "Copy's data should match original") - # self.assertTrue(pose.body.data.mask == pose_copy.body.data.mask, "Copy's mask should match original") + self.assertTrue(pose.body.data.mask.equal(pose_copy.body.data.mask), "Copy's mask should match original") + + pose.body.data = TorchMaskedTensor(tensor=torch.zeros_like(pose.body.data.tensor), + mask=torch.ones_like(pose.body.data.mask)) - # pose.body.data = torch.masked.MaskedTensor(data=torch.zeros_like(pose.body.data.tensor), - # mask=torch.ones_like(pose.body.data.mask)) - # self.assertFalse(pose.body.data.data == pose_copy.body.data.data, "Copy's data should not match original after original is replaced") - # self.assertFalse(pose.body.data.mask == pose_copy.body.data.mask, "Copy's mask should not match original after original is replaced") + self.assertFalse(pose.body.data.tensor.equal(pose_copy.body.data.tensor), "Copy's data should not match original after original is replaced") + self.assertFalse(pose.body.data.mask.equal(pose_copy.body.data.mask), "Copy's mask should not match original after original is replaced") - # pose = pose_copy.copy() + pose = pose_copy.copy() - # self.assertTrue(pose.body.data.data == pose_copy.body.data.data, "Copy's data should match original again") + self.assertTrue(pose.body.data.tensor.equal(pose_copy.body.data.tensor), "Copy's data should match original again") + self.assertTrue(pose.body.data.mask.equal(pose_copy.body.data.mask), "Copy's mask should match original again") - # pose_copy.body.data.fill_(3.14) + pose_copy.body.data.tensor.fill_(3.14) - # self.assertFalse(pose.body.data.data == pose_copy.body.data.data, "Copy's data should not match original after copy is modified") + self.assertFalse(pose.body.data.tensor.equal(pose_copy.body.data.tensor), "Copy's data should not match original after copy is modified") From e0b3ff78b50f06956da832b0b792fff80d489ea0 Mon Sep 17 00:00:00 2001 From: Colin Leong <122366389+cleong110@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:35:18 -0500 Subject: [PATCH 9/9] import numpy.ma instead of 'from numpy import ma' --- src/python/tests/pose_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/tests/pose_test.py b/src/python/tests/pose_test.py index df65551..897a0dc 100644 --- a/src/python/tests/pose_test.py +++ b/src/python/tests/pose_test.py @@ -4,7 +4,7 @@ from unittest import TestCase import numpy as np -from numpy import ma +import numpy.ma as ma import tensorflow as tf import torch