Guides

Tutorial

Complete walkthrough of motion retargeting from C3D data to joint angles.

This tutorial walks you through the complete workflow of taking a C3D motion capture file and retargeting it onto a 3D character using the MyoSapiens Python SDK.

Prerequisites

Before starting, make sure you have:

If you're new to MyoSapiens, read the Introduction first to understand key concepts like assets and jobs.

Step 1: Initialize the Client

Start by importing the SDK and initializing the client. Make sure you have your API key set as an environment variable (see Installation Guide):

Python
import os
from myosdk import Client

# Initialize the client
client = Client(api_key=os.getenv("MYOSDK_API_KEY"))

Upload Your Input Files

Upload your C3D motion capture file and markerset configuration:

Python
# Upload C3D motion capture file
c3d_asset = client.assets.upload_file("path/to/your/motion.c3d")
print(f"C3D uploaded: {c3d_asset}")

# Upload markerset XML file
markerset_asset = client.assets.upload_file("path/to/your/markerset.xml")
print(f"Markerset uploaded: {markerset_asset}")

The SDK handles the upload process and returns asset objects with IDs you'll use in the next step.

Run the Retarget Job

Now create a retarget job to map the motion onto your chosen character:

Python
# Option 1: Use default character (simplest)
job = client.jobs.start_retarget(
    c3d_asset_id=c3d_asset["asset_id"],
    markerset_asset_id=markerset_asset["asset_id"],
)

print(f"Job started: {job['job_id']}")
print(f"Status: {job['status']}")
print(f"Estimated wait time: {job.get('estimated_wait_time_seconds', 'unknown')} seconds")

See Retarget Parameters for all available configuration options.

Wait for Completion

The retarget job runs on our cloud infrastructure. Use the wait() method to poll until it completes:

Python
# Wait for the job to finish (with timeout)
try:
    result = client.jobs.wait(job["job_id"], timeout=600)  # 10 minutes

    # Check job status
    if result["status"] == "SUCCEEDED":
        retarget_output_asset_id = result["output"]["retarget_output_asset_id"]
        print(f"Job completed successfully!")
        print(f"Output asset ID: {retarget_output_asset_id}")
    elif result["status"] == "FAILED":
        error = result.get("error", {})
        print(f"Job failed: {error.get('message', 'Unknown error')}")
        # Handle failure appropriately
    else:
        print(f"Job ended with status: {result['status']}")

except TimeoutError:
    print("Job did not complete within timeout period")
    # You can still check status later with client.jobs.get(job["job_id"])
The wait() method automatically polls the job status until completion. For long-running jobs, this may take several minutes depending on the complexity of your motion data. Always set a reasonable timeout and handle failures appropriately.

Download the Result

Finally, download the generated animation file:

Python
output_path = "output/animation.npz"

print(f"Downloading result to {output_path}...")
client.assets.download(retarget_output_asset_id, output_path)
print("Download complete!")

Use the Output

The output is a .npz file (NumPy compressed archive) containing joint angles and joint names:

Python
import numpy as np

# Load the output
data = np.load(output_path)
joint_angles = data["joint_angles"]  # Shape: (frames, joints, 3)
joint_names = data["joint_names"]    # Array of joint name strings

print(f"Frames: {joint_angles.shape[0]}")
print(f"Joints: {joint_angles.shape[1]}")
print(f"Joint names: {joint_names}")

# Example: Access first frame
first_frame = joint_angles[0]
print(f"First frame shape: {first_frame.shape}")

See File Formats for complete details on the output format structure and usage. ::

Complete Example

Here's the full script putting it all together with error handling:

Python
import os
import numpy as np
from myosdk import Client, APIError, NotFoundError

# Initialize client
client = Client(api_key=os.getenv("MYOSDK_API_KEY"))

try:
    # Upload input files
    print("Uploading C3D file...")
    c3d_asset = client.assets.upload_file("motion.c3d")
    print(f"C3D uploaded: {c3d_asset['asset_id']}")

    print("Uploading markerset file...")
    markerset_asset = client.assets.upload_file("markerset.xml")
    print(f"Markerset uploaded: {markerset_asset['asset_id']}")

    # Start retarget job
    print("Starting retarget job...")
    job = client.jobs.start_retarget(
        c3d_asset_id=c3d_asset["asset_id"],
        markerset_asset_id=markerset_asset["asset_id"],
    )
    print(f"Job started: {job['job_id']}")

    # Wait for completion
    print("Waiting for job to complete...")
    result = client.jobs.wait(job["job_id"], timeout=600)

    if result["status"] == "SUCCEEDED":
        retarget_output_asset_id = result["output"]["retarget_output_asset_id"]

        # Download result
        print("Downloading result...")
        client.assets.download(retarget_output_asset_id, "joint_angles.npz")
        print("Done! Animation saved to joint_angles.npz")

        # Verify output
        data = np.load("joint_angles.npz")
        print(f"Output contains {data['joint_angles'].shape[0]} frames")
        print(f"Joints: {list(data['joint_names'])}")
    else:
        print(f"Job failed with status: {result['status']}")
        if "error" in result:
            print(f"Error: {result['error']}")

except NotFoundError as e:
    print(f"Resource not found: {e.message}")
except APIError as e:
    print(f"API error: {e.message}")
finally:
    client.close()

Next Steps

Now that you've completed your first retargeting:

  • Experiment with different characters from the character library
  • Try different motion capture files to see how well they retarget
  • Learn about retarget parameters for advanced configuration
  • Check the SDK Reference for complete API documentation
  • Review error handling for production applications
  • See the API Reference for REST API documentation
  • Learn more about Why Myo? and our approach to unified motion processing