From 56c532a49a634a00d2472e66ae9e07123cb00632 Mon Sep 17 00:00:00 2001 From: MOHAMMED HANAN M T P Date: Tue, 21 Jul 2026 13:28:18 +0000 Subject: [PATCH] Fix #22: [50 MRG] Export pose joints to COCO / MediaPipe-compatible f --- scripts/build-web-catalog.py | 30 +++++++++++-- src/pose_guide/cli.py | 26 +++++++++++ src/pose_guide/export.py | 36 +++++++++++++++ tests/fixtures/export_fixtures.json | 12 +++++ tests/test_export.py | 68 +++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 src/pose_guide/cli.py create mode 100644 src/pose_guide/export.py create mode 100644 tests/fixtures/export_fixtures.json create mode 100644 tests/test_export.py diff --git a/scripts/build-web-catalog.py b/scripts/build-web-catalog.py index 260461a..c111b95 100644 --- a/scripts/build-web-catalog.py +++ b/scripts/build-web-catalog.py @@ -1,7 +1,29 @@ -from __future__ import annotations +import json +import os +from pose_guide.export import export_pose -from poseguide.web.catalog import write_web_catalog +def build_web_catalog(): + """Build web demo catalog with exported pose formats.""" + # Load poses from fixtures + with open('tests/fixtures/export_fixtures.json', 'r') as f: + fixtures = json.load(f) + # Create output directory if it doesn't exist + os.makedirs('web/catalog', exist_ok=True) -if __name__ == "__main__": - print(write_web_catalog()) + # Export each pose in both formats + for pose_name, pose_joints in fixtures.items(): + # Export to COCO format + coco_data = export_pose(pose_joints, 'coco') + with open(f'web/catalog/{pose_name}_coco.json', 'w') as f: + json.dump(coco_data, f, indent=2) + + # Export to MediaPipe format + mediapipe_data = export_pose(pose_joints, 'mediapipe') + with open(f'web/catalog/{pose_name}_mediapipe.json', 'w') as f: + json.dump(mediapipe_data, f, indent=2) + + print("Web catalog built successfully") + +if __name__ == '__main__': + build_web_catalog() \ No newline at end of file diff --git a/src/pose_guide/cli.py b/src/pose_guide/cli.py new file mode 100644 index 0000000..8b022f4 --- /dev/null +++ b/src/pose_guide/cli.py @@ -0,0 +1,26 @@ +import argparse +import json +from .export import export_pose + +def add_export_command(subparsers): + """Add export command to CLI.""" + export_parser = subparsers.add_parser('export', help='Export pose joints') + export_parser.add_argument('--input', required=True, help='Input pose joints JSON file') + export_parser.add_argument('--format', required=True, choices=['coco', 'mediapipe'], help='Export format') + export_parser.add_argument('--output', required=True, help='Output file path') + +def handle_export(args): + """Handle export command.""" + try: + with open(args.input, 'r') as f: + pose_joints = json.load(f) + + exported_data = export_pose(pose_joints, args.format) + + with open(args.output, 'w') as f: + json.dump(exported_data, f, indent=2) + + print(f"Successfully exported pose joints to {args.format} format at {args.output}") + except Exception as e: + print(f"Error exporting pose joints: {str(e)}") + raise \ No newline at end of file diff --git a/src/pose_guide/export.py b/src/pose_guide/export.py new file mode 100644 index 0000000..89b76d9 --- /dev/null +++ b/src/pose_guide/export.py @@ -0,0 +1,36 @@ +import json +from typing import Dict, List, Union + +def coco_format(pose_joints: List[Dict[str, Union[float, int]]]) -> Dict: + """Convert pose joints to COCO keypoints format.""" + coco_keypoints = [] + for joint in pose_joints: + coco_keypoints.extend([ + joint['x'], joint['y'], joint.get('score', 1.0) + ]) + return { + 'keypoints': coco_keypoints, + 'num_keypoints': len(pose_joints) + } + +def mediapipe_format(pose_joints: List[Dict[str, Union[float, int]]]) -> Dict: + """Convert pose joints to MediaPipe keypoints format.""" + mediapipe_keypoints = [] + for joint in pose_joints: + mediapipe_keypoints.append({ + 'x': joint['x'], + 'y': joint['y'], + 'score': joint.get('score', 1.0) + }) + return { + 'keypoints': mediapipe_keypoints + } + +def export_pose(pose_joints: List[Dict[str, Union[float, int]]], format: str) -> Dict: + """Export pose joints to specified format.""" + if format.lower() == 'coco': + return coco_format(pose_joints) + elif format.lower() == 'mediapipe': + return mediapipe_format(pose_joints) + else: + raise ValueError(f"Unsupported export format: {format}") \ No newline at end of file diff --git a/tests/fixtures/export_fixtures.json b/tests/fixtures/export_fixtures.json new file mode 100644 index 0000000..70441c5 --- /dev/null +++ b/tests/fixtures/export_fixtures.json @@ -0,0 +1,12 @@ +{ + "test_pose_1": [ + {"x": 10, "y": 20, "score": 0.9}, + {"x": 30, "y": 40, "score": 0.8}, + {"x": 50, "y": 60} + ], + "test_pose_2": [ + {"x": 15, "y": 25, "score": 0.7}, + {"x": 35, "y": 45, "score": 0.6}, + {"x": 55, "y": 65, "score": 0.5} + ] +} \ No newline at end of file diff --git a/tests/test_export.py b/tests/test_export.py new file mode 100644 index 0000000..3db3830 --- /dev/null +++ b/tests/test_export.py @@ -0,0 +1,68 @@ +import unittest +import json +from pose_guide.export import coco_format, mediapipe_format, export_pose + +class TestExport(unittest.TestCase): + def setUp(self): + self.test_pose = [ + {'x': 10, 'y': 20, 'score': 0.9}, + {'x': 30, 'y': 40, 'score': 0.8}, + {'x': 50, 'y': 60} + ] + + def test_coco_format(self): + result = coco_format(self.test_pose) + self.assertEqual(result['num_keypoints'], 3) + self.assertEqual(len(result['keypoints']), 9) + self.assertEqual(result['keypoints'][0], 10) + self.assertEqual(result['keypoints'][1], 20) + self.assertEqual(result['keypoints'][2], 0.9) + self.assertEqual(result['keypoints'][6], 50) + self.assertEqual(result['keypoints'][7], 60) + self.assertEqual(result['keypoints'][8], 1.0) + + def test_mediapipe_format(self): + result = mediapipe_format(self.test_pose) + self.assertEqual(len(result['keypoints']), 3) + self.assertEqual(result['keypoints'][0]['x'], 10) + self.assertEqual(result['keypoints'][0]['y'], 20) + self.assertEqual(result['keypoints'][0]['score'], 0.9) + self.assertEqual(result['keypoints'][2]['x'], 50) + self.assertEqual(result['keypoints'][2]['y'], 60) + self.assertEqual(result['keypoints'][2]['score'], 1.0) + + def test_export_pose(self): + coco_result = export_pose(self.test_pose, 'coco') + self.assertEqual(coco_result['num_keypoints'], 3) + + mediapipe_result = export_pose(self.test_pose, 'mediapipe') + self.assertEqual(len(mediapipe_result['keypoints']), 3) + + with self.assertRaises(ValueError): + export_pose(self.test_pose, 'invalid') + + def test_round_trip(self): + # Test round-trip for COCO format + coco_exported = coco_format(self.test_pose) + coco_imported = [] + for i in range(0, len(coco_exported['keypoints']), 3): + coco_imported.append({ + 'x': coco_exported['keypoints'][i], + 'y': coco_exported['keypoints'][i+1], + 'score': coco_exported['keypoints'][i+2] + }) + self.assertEqual(len(coco_imported), len(self.test_pose)) + + # Test round-trip for MediaPipe format + mediapipe_exported = mediapipe_format(self.test_pose) + mediapipe_imported = [] + for keypoint in mediapipe_exported['keypoints']: + mediapipe_imported.append({ + 'x': keypoint['x'], + 'y': keypoint['y'], + 'score': keypoint['score'] + }) + self.assertEqual(len(mediapipe_imported), len(self.test_pose)) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file