From 6416217820679d4dc84c473f5bfb6464c39f7a2d Mon Sep 17 00:00:00 2001 From: AmitMY Date: Sat, 21 Feb 2026 08:40:56 +0200 Subject: [PATCH] fix: preserve float precision for fps in all code paths Remove int() casts and fix type annotations that truncated non-integer framerates (e.g. 29.97, 23.976 fps) to integers, causing audio/video sync drift. Fixes #201 Co-Authored-By: Claude Opus 4.6 --- src/python/pose_format/numpy/pose_body.py | 4 ++-- src/python/pose_format/tensorflow/pose_body.py | 6 +++--- src/python/pose_format/utils/generic.py | 4 ++-- src/python/pose_format/utils/holistic.py | 2 +- src/python/pose_format/utils/openpose.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/python/pose_format/numpy/pose_body.py b/src/python/pose_format/numpy/pose_body.py index ab3bff4..8ac9ac3 100644 --- a/src/python/pose_format/numpy/pose_body.py +++ b/src/python/pose_format/numpy/pose_body.py @@ -298,13 +298,13 @@ def bbox(self, header: PoseHeader): return NumPyPoseBody(self.fps, new_data, confidence) - def interpolate(self, new_fps: int = None, kind='cubic'): + def interpolate(self, new_fps: float = None, kind='cubic'): """ Interpolates the pose data to match a new frame rate. Parameters ---------- - new_fps : int, optional + new_fps : float, optional The desired frame rate for interpolation. kind : str, optional The type of interpolation. Options include: "linear", "quadratic", and "cubic". diff --git a/src/python/pose_format/tensorflow/pose_body.py b/src/python/pose_format/tensorflow/pose_body.py index c7bd282..df3566f 100644 --- a/src/python/pose_format/tensorflow/pose_body.py +++ b/src/python/pose_format/tensorflow/pose_body.py @@ -7,7 +7,7 @@ from .masked.tensor import MaskedTensor TF_POSE_RECORD_DESCRIPTION = { - 'fps': tf.io.FixedLenFeature([], tf.int64, default_value=0), + 'fps': tf.io.FixedLenFeature([], tf.float32, default_value=0.0), 'pose_data': tf.io.FixedLenFeature([], tf.string), 'pose_confidence': tf.io.FixedLenFeature([], tf.string), } @@ -218,7 +218,7 @@ def as_tfrecord(self): confidence = tf.io.serialize_tensor(self.confidence).numpy() return { - 'fps': tf.train.Feature(int64_list=tf.train.Int64List(value=[self.fps])), + 'fps': tf.train.Feature(float_list=tf.train.FloatList(value=[self.fps])), 'pose_data': tf.train.Feature(bytes_list=tf.train.BytesList(value=[data])), 'pose_confidence': tf.train.Feature(bytes_list=tf.train.BytesList(value=[confidence])) } @@ -238,7 +238,7 @@ def from_tfrecord(cls, tfrecord_dict: dict): TensorflowPoseBody An instance constructed from given TensorFlow record data """ - fps = tf.cast(tfrecord_dict['fps'], dtype=tf.float32) + fps = tfrecord_dict['fps'] data = tf.io.parse_tensor(tfrecord_dict['pose_data'], out_type=tf.float32) confidence = tf.io.parse_tensor(tfrecord_dict['pose_confidence'], out_type=tf.float32) return cls(fps=fps, data=data, confidence=confidence) diff --git a/src/python/pose_format/utils/generic.py b/src/python/pose_format/utils/generic.py index 653d9ae..b36d01f 100644 --- a/src/python/pose_format/utils/generic.py +++ b/src/python/pose_format/utils/generic.py @@ -213,7 +213,7 @@ def get_standard_components_for_known_format(known_pose_format: KnownPoseFormat) raise NotImplementedError(f"Unsupported pose header schema {known_pose_format}") -def fake_pose(num_frames: int, fps: int=25, components: Union[List[PoseHeaderComponent],None]=None)->Pose: +def fake_pose(num_frames: int, fps: float=25.0, components: Union[List[PoseHeaderComponent],None]=None)->Pose: if components is None: components = copy.deepcopy(OpenPose_Components) # fixes W0102, dangerous default value @@ -230,7 +230,7 @@ def fake_pose(num_frames: int, fps: int=25, components: Union[List[PoseHeaderCom confidence = np.random.randn(num_frames, 1, total_points) masked_data = ma.masked_array(data) - body = NumPyPoseBody(fps=int(fps), data=masked_data, confidence=confidence) + body = NumPyPoseBody(fps=fps, data=masked_data, confidence=confidence) return Pose(header, body) diff --git a/src/python/pose_format/utils/holistic.py b/src/python/pose_format/utils/holistic.py index c7f4a40..6320cba 100644 --- a/src/python/pose_format/utils/holistic.py +++ b/src/python/pose_format/utils/holistic.py @@ -367,7 +367,7 @@ def formatted_holistic_pose(width: int, height: int, additional_face_points: int {"FACE_LANDMARKS": FACEMESH_CONTOURS_POINTS}) -def load_mediapipe_directory(directory: str, fps: int, width: int, height: int, num_face_points: int = 128) -> Pose: +def load_mediapipe_directory(directory: str, fps: float, width: int, height: int, num_face_points: int = 128) -> Pose: """ Load pose data from a directory of MediaPipe diff --git a/src/python/pose_format/utils/openpose.py b/src/python/pose_format/utils/openpose.py index 36daed2..4a4801e 100644 --- a/src/python/pose_format/utils/openpose.py +++ b/src/python/pose_format/utils/openpose.py @@ -279,7 +279,7 @@ def load_openpose(frames: OpenPoseFrames, stacked_confidence = np.stack([mask, mask], axis=3) masked_data = ma.masked_array(data, mask=stacked_confidence) - body = NumPyPoseBody(fps=int(fps), data=masked_data, confidence=confidence) + body = NumPyPoseBody(fps=fps, data=masked_data, confidence=confidence) return Pose(header, body)