From 27270180ba3577bc5ac8e6becb98b3f32d84ea1b Mon Sep 17 00:00:00 2001 From: bricksdont Date: Tue, 23 Jun 2026 10:45:59 +0200 Subject: [PATCH 1/9] Add MMPose wholebody estimator loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds mmposewholebody.py wrapping MMPoseInferencer('wholebody') to load a video into a Pose using the canonical COCO Wholebody 133 header from cocowholebody133_header.py. MMPose is an optional dependency; importing the module without it installed raises ImportError immediately (same pattern as holistic.py / mediapipe). Frames where no person is detected are not skipped — a zeroed, fully-masked row is inserted instead so the output frame count stays aligned with the video. Downstream code can distinguish "no detection" from a real keypoint via the mask. Tests cover: header/components (no MMPose required), output shape and metadata, empty-frame masking, all-empty video, and version default. MMPoseInferencer is mocked via sys.modules stubs so the test suite runs without MMPose installed. Co-Authored-By: catherine-o-brien Co-Authored-By: GerrySant Co-Authored-By: Claude Sonnet 4.6 --- .../pose_format/utils/mmposewholebody.py | 112 +++++++++++++++++ .../pose_format/utils/mmposewholebody_test.py | 118 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 src/python/pose_format/utils/mmposewholebody.py create mode 100644 src/python/pose_format/utils/mmposewholebody_test.py diff --git a/src/python/pose_format/utils/mmposewholebody.py b/src/python/pose_format/utils/mmposewholebody.py new file mode 100644 index 0000000..5ac8e0a --- /dev/null +++ b/src/python/pose_format/utils/mmposewholebody.py @@ -0,0 +1,112 @@ +import numpy as np +import numpy.ma as ma + +try: + from mmpose.apis import MMPoseInferencer +except ImportError: + raise ImportError( + "Please install MMPose and its dependencies with:\n" + " pip install mmpose mmcv mmengine mmdet" + ) + +from ..numpy.pose_body import NumPyPoseBody +from ..pose import Pose +from ..pose_header import PoseHeader, PoseHeaderDimensions +from .cocowholebody133_header import cocowholebody_components + +NUM_KEYPOINTS = 133 + + +def load_mmposewholebody(input_path: str, + version: float = 0.2, + fps: float = 24, + width: int = 1000, + height: int = 1000, + depth: int = 0) -> Pose: + """ + Run MMPose wholebody inference on a video and return a Pose object. + + Parameters + ---------- + input_path : str + Path to the input video file. + version : float + Pose format version written to the header. + fps : float + Frames per second stored in the pose body. + width : int + Frame width in pixels stored in the header dimensions. + height : int + Frame height in pixels stored in the header dimensions. + depth : int + Depth dimension size (0 for 2D poses). + + Returns + ------- + Pose + Loaded pose with header and body. + """ + header = PoseHeader( + version=version, + dimensions=PoseHeaderDimensions(width=width, height=height, depth=depth), + components=cocowholebody_components(), + ) + body = _process_video(input_path, fps) + return Pose(header, body) + + +def _process_video(input_path: str, fps: float, use_cpu: bool = False) -> NumPyPoseBody: + """ + Run MMPose wholebody inference and convert frame results to NumPyPoseBody. + + Parameters + ---------- + input_path : str + Path to the input video file. + fps : float + Frames per second to store in the pose body. + use_cpu : bool + If True, run inference on CPU (slow; useful when no GPU is available). + + Returns + ------- + NumPyPoseBody + """ + device = 'cpu' if use_cpu else None + inferencer_kwargs = {'wholebody': True} + if device is not None: + inferencer_kwargs['device'] = device + + inferencer = MMPoseInferencer('wholebody', **({'device': device} if device else {})) + result_generator = inferencer(input_path, show=False, return_vis=False) + + frames_xy = [] + frames_conf = [] + frames_mask = [] # True = valid, False = masked out (no detection) + + for result in result_generator: + predictions_by_frame = result['predictions'] # list of per-person dicts for this frame + + if len(predictions_by_frame) == 0 or len(predictions_by_frame[0]) == 0: + # No person detected in this frame. Insert a zeroed, fully-masked row so + # the frame count stays aligned with the video. Callers can distinguish + # "no detection" from a real zero-coordinate keypoint via the mask. + frames_xy.append(np.zeros((1, NUM_KEYPOINTS, 2), dtype=np.float32)) + frames_conf.append(np.zeros((1, NUM_KEYPOINTS), dtype=np.float32)) + frames_mask.append(True) # True = mask this frame entirely + else: + person = predictions_by_frame[0][0] + frames_xy.append(np.array(person['keypoints'], dtype=np.float32)[None]) # (1, 133, 2) + frames_conf.append(np.array(person['keypoint_scores'], dtype=np.float32)[None]) # (1, 133) + frames_mask.append(False) # False = keep (not masked) + + xy_data = np.concatenate(frames_xy, axis=0)[:, None, :, :] # (T, 1, 133, 2) + conf_data = np.concatenate(frames_conf, axis=0)[:, None, :] # (T, 1, 133) + + # Build the masked array: mask=True on empty frames so downstream code + # can treat them as missing rather than as detected-at-origin. + mask = np.array(frames_mask) # (T,) + xy_mask = mask[:, None, None, None] * np.ones_like(xy_data, dtype=bool) + masked_xy = ma.array(xy_data, mask=xy_mask) + + return NumPyPoseBody(fps=fps, data=masked_xy, confidence=conf_data) diff --git a/src/python/pose_format/utils/mmposewholebody_test.py b/src/python/pose_format/utils/mmposewholebody_test.py new file mode 100644 index 0000000..3df2ad0 --- /dev/null +++ b/src/python/pose_format/utils/mmposewholebody_test.py @@ -0,0 +1,118 @@ +import sys +from unittest.mock import MagicMock + +import numpy as np +import numpy.ma as ma +import pytest + +# Stub the MMPose package and its dependencies before our module is imported. +# mmposewholebody.py does `from mmpose.apis import MMPoseInferencer` at module level, +# so sys.modules must be populated before the first import of that module. +for _mod in ["mmpose", "mmpose.apis", "mmcv", "mmengine", "mmdet"]: + sys.modules.setdefault(_mod, MagicMock()) + +from pose_format.utils import mmposewholebody # noqa: E402 — must come after the stubs above +from pose_format.utils.mmposewholebody import load_mmposewholebody # noqa: E402 +from pose_format.utils.cocowholebody133_header import cocowholebody_components # noqa: E402 + +NUM_KEYPOINTS = 133 + + +# --------------------------------------------------------------------------- +# Header / components tests — no MMPose installation required +# --------------------------------------------------------------------------- + +def test_components_total_keypoints(): + assert sum(len(c.points) for c in cocowholebody_components()) == NUM_KEYPOINTS + + +def test_components_names(): + names = [c.name for c in cocowholebody_components()] + assert names == ["BODY", "FACE", "LEFT_HAND", "RIGHT_HAND"] + + +def test_components_point_format(): + for c in cocowholebody_components(): + assert c.format == "XYC" + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _fake_result(num_keypoints: int = NUM_KEYPOINTS): + """Single-frame MMPose result with one detected person.""" + return { + "predictions": [[{ + "keypoints": np.random.rand(num_keypoints, 2).tolist(), + "keypoint_scores": np.random.rand(num_keypoints).tolist(), + }]] + } + + +def _empty_result(): + """Single-frame MMPose result with no detected person.""" + return {"predictions": []} + + +def _make_inferencer(results): + """Return a patched MMPoseInferencer class whose instance yields `results`.""" + fake_instance = MagicMock() + fake_instance.return_value = iter(results) + return MagicMock(return_value=fake_instance) + + +# --------------------------------------------------------------------------- +# Loader tests (MMPoseInferencer is mocked) +# --------------------------------------------------------------------------- + +def test_load_shape(monkeypatch, tmp_path): + """Output Pose has the right frame/keypoint shape.""" + monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", + _make_inferencer([_fake_result(), _fake_result(), _fake_result()])) + pose = load_mmposewholebody(str(tmp_path / "video.mp4"), fps=25.0, width=1280, height=720) + + assert pose.body.data.shape == (3, 1, NUM_KEYPOINTS, 2) + assert pose.body.fps == 25.0 + assert pose.header.dimensions.width == 1280 + assert pose.header.dimensions.height == 720 + + +def test_load_component_names(monkeypatch, tmp_path): + monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", + _make_inferencer([_fake_result()])) + pose = load_mmposewholebody(str(tmp_path / "video.mp4")) + + assert [c.name for c in pose.header.components] == ["BODY", "FACE", "LEFT_HAND", "RIGHT_HAND"] + + +def test_empty_frame_is_masked(monkeypatch, tmp_path): + """Frames with no detection are present in the output but fully masked.""" + results = [_fake_result(), _empty_result(), _fake_result()] + monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", _make_inferencer(results)) + pose = load_mmposewholebody(str(tmp_path / "video.mp4")) + + # All three frames must be present so frame count matches the video. + assert pose.body.data.shape[0] == 3 + + # Frame 1 (index 1) must be fully masked; frames 0 and 2 must not be. + assert pose.body.data[1].mask.all(), "empty frame should be fully masked" + assert not pose.body.data[0].mask.all(), "detected frame should not be fully masked" + assert not pose.body.data[2].mask.all(), "detected frame should not be fully masked" + + +def test_all_empty_frames(monkeypatch, tmp_path): + """A video where no person is ever detected produces a fully masked Pose.""" + results = [_empty_result(), _empty_result()] + monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", _make_inferencer(results)) + pose = load_mmposewholebody(str(tmp_path / "video.mp4")) + + assert pose.body.data.shape[0] == 2 + assert pose.body.data.mask.all() + + +def test_version_default(monkeypatch, tmp_path): + monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", + _make_inferencer([_fake_result()])) + pose = load_mmposewholebody(str(tmp_path / "video.mp4")) + assert pose.header.version == 0.2 From 573a68b135ebe07a29c902ac80f67e6119ee2a5a Mon Sep 17 00:00:00 2001 From: bricksdont Date: Tue, 23 Jun 2026 10:49:42 +0200 Subject: [PATCH 2/9] docs: update README section 6 to distinguish core vs experimental estimators Restructures "Integration with External Data Sources" to label OpenPose support as core and AlphaPose / MMPose as experimental. Adds MMPose wholebody loader example with install instructions. Co-Authored-By: catherine-o-brien Co-Authored-By: GerrySant Co-Authored-By: Claude Sonnet 4.6 --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e27bebe..6beb647 100644 --- a/README.md +++ b/README.md @@ -228,11 +228,11 @@ Normalizing the pose before creating the mp4 output file (`--normalize`) is opt #### 6. Integration with External Data Sources: -If you have pose data in OpenPose (usually folders of JSON files, one per frame) format, you can easily import it. +Loaders are available for several pose estimators. **OpenPose** support is core and well-tested. Support for additional estimators is **experimental** — APIs may evolve and edge cases may not yet be fully handled. -##### Loading OpenPose Data +##### Core: OpenPose -To load an OpenPose `directory`, use the `load_openpose_directory` utility: +To load an OpenPose `directory` (one JSON file per frame): * For the 137-keypoint OpenPose model: @@ -252,7 +252,7 @@ directory = "/path/to/openpose_135/directory" pose = load_openpose_135_directory(directory, fps=24, width=1000, height=1000) ``` -##### Loading AlphaPose WholeBody JSON +##### Experimental: AlphaPose WholeBody ```python from pose_format.utils.alphapose import load_alphapose_wholebody_from_json @@ -268,6 +268,16 @@ from pose_format.utils.alphapose_133 import load_alphapose_wholebody_from_json a pose = load_alphapose_133("alphapose.json") ``` +##### Experimental: MMPose WholeBody + +Requires MMPose and its dependencies (`pip install mmpose mmcv mmengine mmdet`). Runs inference directly on a video file: + +```python +from pose_format.utils.mmposewholebody import load_mmposewholebody + +pose = load_mmposewholebody("video.mp4", fps=25, width=1920, height=1080) +``` + #### 7. Generating Fake Pose Data for Testing Purposes: ```python From bcebe9e9fcb11f265fcf1ce591a0419de5854a4c Mon Sep 17 00:00:00 2001 From: bricksdont Date: Tue, 23 Jun 2026 10:53:30 +0200 Subject: [PATCH 3/9] build: add mmpose optional install target to pyproject.toml Adds a [project.optional-dependencies] mmpose group listing mmcv, mmengine, mmdet, and mmpose (all >=their first stable 1.x/2.x releases), installable via pip install pose_format[mmpose]. Co-Authored-By: catherine-o-brien Co-Authored-By: GerrySant Co-Authored-By: Claude Sonnet 4.6 --- src/python/pyproject.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/python/pyproject.toml b/src/python/pyproject.toml index 5bcd451..9a8a998 100644 --- a/src/python/pyproject.toml +++ b/src/python/pyproject.toml @@ -33,6 +33,13 @@ mediapipe = [ "mediapipe<0.10.30", ] +mmpose = [ + "mmcv>=2.0.0", + "mmengine>=0.7.0", + "mmdet>=3.0.0", + "mmpose>=1.0.0", +] + [tool.setuptools] packages = [ "pose_format", From 24019715436655334ffef20cfa823cb4c5f26cba Mon Sep 17 00:00:00 2001 From: bricksdont Date: Tue, 23 Jun 2026 11:04:46 +0200 Subject: [PATCH 4/9] Pin mmpose optional deps to tested versions, fix mmcv install guidance Version floors tightened to match a known-good OpenMMLab 2.x combination (mmcv 2.1.0 / mmengine 0.10.7 / mmdet 3.3.0 / mmpose 1.3.2) verified by ZurichNLP's install script. Also clarifies that mmcv for GPU requires the OpenMMLab CUDA-specific index, not plain pip install. Co-Authored-By: catherine-o-brien Co-Authored-By: GerrySant Co-Authored-By: Claude Sonnet 4.6 --- README.md | 8 +++++++- src/python/pose_format/utils/mmposewholebody.py | 5 +++-- src/python/pyproject.toml | 8 ++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6beb647..acb7a53 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,13 @@ pose = load_alphapose_133("alphapose.json") ##### Experimental: MMPose WholeBody -Requires MMPose and its dependencies (`pip install mmpose mmcv mmengine mmdet`). Runs inference directly on a video file: +Requires MMPose and its dependencies. For GPU use, `mmcv` must be installed from the OpenMMLab CUDA-specific index — plain `pip install mmcv` installs a CPU-only build. See the [mmcv installation guide](https://mmcv.readthedocs.io/en/latest/get_started/installation.html) for the right command for your CUDA and PyTorch versions. The remaining packages can be installed with pip: + +```bash +pip install mmpose>=1.3.2 mmengine>=0.10.7 mmdet>=3.3.0 +``` + +Then run inference directly on a video file: ```python from pose_format.utils.mmposewholebody import load_mmposewholebody diff --git a/src/python/pose_format/utils/mmposewholebody.py b/src/python/pose_format/utils/mmposewholebody.py index 5ac8e0a..e9be30d 100644 --- a/src/python/pose_format/utils/mmposewholebody.py +++ b/src/python/pose_format/utils/mmposewholebody.py @@ -5,8 +5,9 @@ from mmpose.apis import MMPoseInferencer except ImportError: raise ImportError( - "Please install MMPose and its dependencies with:\n" - " pip install mmpose mmcv mmengine mmdet" + "Please install MMPose and its dependencies. For GPU support, mmcv must be installed\n" + "from the OpenMMLab CUDA-specific index (see https://mmcv.readthedocs.io/en/latest/get_started/installation.html).\n" + "The remaining packages: pip install 'mmpose>=1.3.2' 'mmengine>=0.10.7' 'mmdet>=3.3.0'" ) from ..numpy.pose_body import NumPyPoseBody diff --git a/src/python/pyproject.toml b/src/python/pyproject.toml index 9a8a998..b308e37 100644 --- a/src/python/pyproject.toml +++ b/src/python/pyproject.toml @@ -34,10 +34,10 @@ mediapipe = [ ] mmpose = [ - "mmcv>=2.0.0", - "mmengine>=0.7.0", - "mmdet>=3.0.0", - "mmpose>=1.0.0", + "mmcv>=2.1.0", + "mmengine>=0.10.7", + "mmdet>=3.3.0", + "mmpose>=1.3.2", ] [tool.setuptools] From 9036ad3e66ab9943bbaae735dd503c17b901037d Mon Sep 17 00:00:00 2001 From: bricksdont Date: Tue, 23 Jun 2026 11:05:50 +0200 Subject: [PATCH 5/9] Replace core/experimental prose with summary table in section 6 Co-Authored-By: Claude Sonnet 4.6 --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index acb7a53..4271878 100644 --- a/README.md +++ b/README.md @@ -228,9 +228,15 @@ Normalizing the pose before creating the mp4 output file (`--normalize`) is opt #### 6. Integration with External Data Sources: -Loaders are available for several pose estimators. **OpenPose** support is core and well-tested. Support for additional estimators is **experimental** — APIs may evolve and edge cases may not yet be fully handled. +Loaders are available for several pose estimators: -##### Core: OpenPose +| Estimator | Support | +|---|---| +| OpenPose | core | +| AlphaPose WholeBody | experimental | +| MMPose WholeBody | experimental | + +##### OpenPose To load an OpenPose `directory` (one JSON file per frame): @@ -252,7 +258,7 @@ directory = "/path/to/openpose_135/directory" pose = load_openpose_135_directory(directory, fps=24, width=1000, height=1000) ``` -##### Experimental: AlphaPose WholeBody +##### AlphaPose WholeBody ```python from pose_format.utils.alphapose import load_alphapose_wholebody_from_json @@ -268,7 +274,7 @@ from pose_format.utils.alphapose_133 import load_alphapose_wholebody_from_json a pose = load_alphapose_133("alphapose.json") ``` -##### Experimental: MMPose WholeBody +##### MMPose WholeBody Requires MMPose and its dependencies. For GPU use, `mmcv` must be installed from the OpenMMLab CUDA-specific index — plain `pip install mmcv` installs a CPU-only build. See the [mmcv installation guide](https://mmcv.readthedocs.io/en/latest/get_started/installation.html) for the right command for your CUDA and PyTorch versions. The remaining packages can be installed with pip: From 68ade8ffbcc60f45ad305c67a9b72a353472895b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20M=C3=BCller?= Date: Tue, 23 Jun 2026 11:11:23 +0200 Subject: [PATCH 6/9] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4271878..94cfc3e 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,7 @@ Loaders are available for several pose estimators: | Estimator | Support | |---|---| +| Mediapipe Holistic | core | | OpenPose | core | | AlphaPose WholeBody | experimental | | MMPose WholeBody | experimental | @@ -276,14 +277,12 @@ pose = load_alphapose_133("alphapose.json") ##### MMPose WholeBody -Requires MMPose and its dependencies. For GPU use, `mmcv` must be installed from the OpenMMLab CUDA-specific index — plain `pip install mmcv` installs a CPU-only build. See the [mmcv installation guide](https://mmcv.readthedocs.io/en/latest/get_started/installation.html) for the right command for your CUDA and PyTorch versions. The remaining packages can be installed with pip: +Requires MMPose and its dependencies, install the `pose` library with an additional target: ```bash -pip install mmpose>=1.3.2 mmengine>=0.10.7 mmdet>=3.3.0 +pip install pose-format[mmpose] ``` -Then run inference directly on a video file: - ```python from pose_format.utils.mmposewholebody import load_mmposewholebody From 59e89184e04bf721064f56fef49a79b7265fa1a5 Mon Sep 17 00:00:00 2001 From: bricksdont Date: Tue, 23 Jun 2026 11:25:24 +0200 Subject: [PATCH 7/9] Remove MMPose and Mediapipe Holistic from External Data Sources section MMPose runs inference on raw video rather than loading a pre-existing keypoint format, so it does not belong in this section. Mediapipe Holistic is handled via the CLI (section 2) and also does not fit here. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/README.md b/README.md index 94cfc3e..5cc3463 100644 --- a/README.md +++ b/README.md @@ -232,10 +232,8 @@ Loaders are available for several pose estimators: | Estimator | Support | |---|---| -| Mediapipe Holistic | core | | OpenPose | core | | AlphaPose WholeBody | experimental | -| MMPose WholeBody | experimental | ##### OpenPose @@ -275,20 +273,6 @@ from pose_format.utils.alphapose_133 import load_alphapose_wholebody_from_json a pose = load_alphapose_133("alphapose.json") ``` -##### MMPose WholeBody - -Requires MMPose and its dependencies, install the `pose` library with an additional target: - -```bash -pip install pose-format[mmpose] -``` - -```python -from pose_format.utils.mmposewholebody import load_mmposewholebody - -pose = load_mmposewholebody("video.mp4", fps=25, width=1920, height=1080) -``` - #### 7. Generating Fake Pose Data for Testing Purposes: ```python From e107292406a1d2eb5e435807d572d63f01ed9566 Mon Sep 17 00:00:00 2001 From: bricksdont Date: Tue, 23 Jun 2026 11:27:20 +0200 Subject: [PATCH 8/9] Rename load_mmposewholebody -> estimate_mmpose_wholebody "load" implies ingesting a pre-existing keypoint file (like OpenPose/AlphaPose loaders); this function runs inference on raw video, so "estimate" is more accurate. Co-Authored-By: Claude Sonnet 4.6 --- src/python/pose_format/utils/mmposewholebody.py | 2 +- src/python/pose_format/utils/mmposewholebody_test.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/python/pose_format/utils/mmposewholebody.py b/src/python/pose_format/utils/mmposewholebody.py index e9be30d..1a883b1 100644 --- a/src/python/pose_format/utils/mmposewholebody.py +++ b/src/python/pose_format/utils/mmposewholebody.py @@ -18,7 +18,7 @@ NUM_KEYPOINTS = 133 -def load_mmposewholebody(input_path: str, +def estimate_mmpose_wholebody(input_path: str, version: float = 0.2, fps: float = 24, width: int = 1000, diff --git a/src/python/pose_format/utils/mmposewholebody_test.py b/src/python/pose_format/utils/mmposewholebody_test.py index 3df2ad0..d172a03 100644 --- a/src/python/pose_format/utils/mmposewholebody_test.py +++ b/src/python/pose_format/utils/mmposewholebody_test.py @@ -12,7 +12,7 @@ sys.modules.setdefault(_mod, MagicMock()) from pose_format.utils import mmposewholebody # noqa: E402 — must come after the stubs above -from pose_format.utils.mmposewholebody import load_mmposewholebody # noqa: E402 +from pose_format.utils.mmposewholebody import estimate_mmpose_wholebody # noqa: E402 from pose_format.utils.cocowholebody133_header import cocowholebody_components # noqa: E402 NUM_KEYPOINTS = 133 @@ -70,7 +70,7 @@ def test_load_shape(monkeypatch, tmp_path): """Output Pose has the right frame/keypoint shape.""" monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", _make_inferencer([_fake_result(), _fake_result(), _fake_result()])) - pose = load_mmposewholebody(str(tmp_path / "video.mp4"), fps=25.0, width=1280, height=720) + pose = estimate_mmpose_wholebody(str(tmp_path / "video.mp4"), fps=25.0, width=1280, height=720) assert pose.body.data.shape == (3, 1, NUM_KEYPOINTS, 2) assert pose.body.fps == 25.0 @@ -81,7 +81,7 @@ def test_load_shape(monkeypatch, tmp_path): def test_load_component_names(monkeypatch, tmp_path): monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", _make_inferencer([_fake_result()])) - pose = load_mmposewholebody(str(tmp_path / "video.mp4")) + pose = estimate_mmpose_wholebody(str(tmp_path / "video.mp4")) assert [c.name for c in pose.header.components] == ["BODY", "FACE", "LEFT_HAND", "RIGHT_HAND"] @@ -90,7 +90,7 @@ def test_empty_frame_is_masked(monkeypatch, tmp_path): """Frames with no detection are present in the output but fully masked.""" results = [_fake_result(), _empty_result(), _fake_result()] monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", _make_inferencer(results)) - pose = load_mmposewholebody(str(tmp_path / "video.mp4")) + pose = estimate_mmpose_wholebody(str(tmp_path / "video.mp4")) # All three frames must be present so frame count matches the video. assert pose.body.data.shape[0] == 3 @@ -105,7 +105,7 @@ def test_all_empty_frames(monkeypatch, tmp_path): """A video where no person is ever detected produces a fully masked Pose.""" results = [_empty_result(), _empty_result()] monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", _make_inferencer(results)) - pose = load_mmposewholebody(str(tmp_path / "video.mp4")) + pose = estimate_mmpose_wholebody(str(tmp_path / "video.mp4")) assert pose.body.data.shape[0] == 2 assert pose.body.data.mask.all() @@ -114,5 +114,5 @@ def test_all_empty_frames(monkeypatch, tmp_path): def test_version_default(monkeypatch, tmp_path): monkeypatch.setattr(mmposewholebody, "MMPoseInferencer", _make_inferencer([_fake_result()])) - pose = load_mmposewholebody(str(tmp_path / "video.mp4")) + pose = estimate_mmpose_wholebody(str(tmp_path / "video.mp4")) assert pose.header.version == 0.2 From bcf9e764bb457365a39f54f1f017e68ae2cc841c Mon Sep 17 00:00:00 2001 From: bricksdont Date: Tue, 23 Jun 2026 11:29:14 +0200 Subject: [PATCH 9/9] Revert all README changes from this branch Co-Authored-By: Claude Sonnet 4.6 --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5cc3463..e27bebe 100644 --- a/README.md +++ b/README.md @@ -228,16 +228,11 @@ Normalizing the pose before creating the mp4 output file (`--normalize`) is opt #### 6. Integration with External Data Sources: -Loaders are available for several pose estimators: +If you have pose data in OpenPose (usually folders of JSON files, one per frame) format, you can easily import it. -| Estimator | Support | -|---|---| -| OpenPose | core | -| AlphaPose WholeBody | experimental | +##### Loading OpenPose Data -##### OpenPose - -To load an OpenPose `directory` (one JSON file per frame): +To load an OpenPose `directory`, use the `load_openpose_directory` utility: * For the 137-keypoint OpenPose model: @@ -257,7 +252,7 @@ directory = "/path/to/openpose_135/directory" pose = load_openpose_135_directory(directory, fps=24, width=1000, height=1000) ``` -##### AlphaPose WholeBody +##### Loading AlphaPose WholeBody JSON ```python from pose_format.utils.alphapose import load_alphapose_wholebody_from_json