[Python] Feet and Hip Do Not Rotate During Circular Walking Motion - OpenSim RajaGopal2016

Discussão em 'Python' iniciado por Stack, Junho 6, 2025.

  1. Stack

    Stack Membro Participativo

    I am currently working with motion capture data from the CMU MoCap dataset and analyzing it using the RajaGopal2016 OpenSim model in the GUI.

    To prepare the data, I did the following:

    1. Converted the .c3d files into .trc format using ezc3d in Python.
    2. Applied coordinate transformation if the data was in a Z-up system.
    3. Mapped CMU marker labels to OpenSim model marker names using a custom mapping.
    4. Exported the .trc file and successfully ran Inverse Kinematics (IK) in the OpenSim GUI.

    The results look good for straight-line walking. However, in circular or turning motion trials, the feet and hips do not rotate or follow the arc of motion. The model appears to walk in place rather than along a curve, even though the upper body follows the correct motion path.

    I have attached two frames below showing this issue: image

    My questions are:

    1. Has anyone faced this issue with circular or turning motions?
    2. Could this be caused by missing or misaligned foot or hip markers?
    3. Are there known limitations of the RajaGopal2016 model with rotational lower-body motion?
    4. Do IK settings, such as marker weights, need adjustment for turning trials?

    Any suggestions or guidance would be greatly appreciated.

    I have shared below the Python code that creates .trc from .c3d files.

    import ezc3d
    import numpy as np
    from scipy.spatial.transform import Rotation as R

    # Load the C3D file
    c3d = ezc3d.c3d("/Downloads/0005_Walking001.c3d")

    # Extract marker data
    points = c3d['data']['points'][:3] # (X, Y, Z)
    # Detect if Z-up (vertical movement is mostly in the Z axis)
    spread_y = np.ptp(points[1, :, :]) # Y range
    spread_z = np.ptp(points[2, :, :]) # Z range

    if spread_z > spread_y:
    print("↪ Detected Z-up coordinate system. Rotating to Y-up for OpenSim...")
    rot = R.from_euler('x', -90, degrees=True).as_matrix()
    points = np.einsum('ij,jkl->ikl', rot, points)
    else:
    print("Coordinate system appears Y-up — no rotation applied.")


    raw_labels = c3d['parameters']['POINT']['LABELS']['value']
    labels = [l.split(':')[-1] for l in raw_labels] # Remove prefix like 'liu:'

    # OpenSim model marker list (from Rajagopal2016.osim)
    opensim_markers = [
    "RACR", "LACR", "C7", "CLAV", "RASH", "RPSH", "LASH", "LPSH", "RSJC",
    "RUA1", "RUA2", "RUA3", "RLEL", "RMEL", "RFAsuperior", "RFAradius", "RFAulna",
    "LSJC", "LUA1", "LUA2", "LUA3", "LLEL", "LMEL", "LFAsuperior", "LFAradius", "LFAulna",
    "RASI", "LASI", "RPSI", "LPSI", "LHJC", "RHJC", "RTH1", "RTH2", "RTH3",
    "RLFC", "RMFC", "RKJC", "RTB1", "RTB2", "RTB3", "RLMAL", "RMMAL", "RAJC",
    "RCAL", "RTOE", "RMT5", "LTH1", "LTH2", "LTH3", "LLFC", "LMFC", "LKJC",
    "LTB1", "LTB2", "LTB3", "LLMAL", "LMMAL", "LAJC", "LCAL", "LTOE", "LMT5",
    "REJC", "LEJC", "R_tibial_plateau", "L_tibial_plateau"
    ]

    # Map TRC labels to OpenSim model names
    rename_map = {
    'LFWT': 'LASI', 'RFWT': 'RASI', 'LBWT': 'LPSI', 'RBWT': 'RPSI',
    'LSHO': 'LASH', 'RSHO': 'RASH', 'LELB': 'LMEL', 'RELB': 'RMEL',
    'LWRB': 'LFAradius', 'RWRB': 'RFAradius', 'LWRA': 'LFAradius', 'RWRA': 'RFAradius',
    'LFRM': 'LFAsuperior', 'RFRM': 'RFAsuperior', 'LTHI': 'LTH1', 'RTHI': 'RTH1',
    'LSHN': 'LTH3', 'RSHN': 'RTH3', 'LANK': 'LLMAL', 'RANK': 'RLMAL',
    'LHEE': 'LCAL', 'RHEE': 'RCAL', 'LKNE': 'LKJC', 'RKNE': 'RKJC',
    'LTOE': 'LTOE', 'RTOE': 'RTOE', 'LMT5': 'LMT5', 'RMT5': 'RMT5',
    'STRN': 'CLAV', 'CLAV': 'CLAV', 'C7': 'C7'
    }

    # Reverse map for writing: only keep labels matching OpenSim markers
    matched_indices = []
    mapped_labels = []
    used_names = set()

    for i, label in enumerate(labels):
    new_label = rename_map.get(label)
    if new_label in opensim_markers and new_label not in used_names:
    matched_indices.append(i)
    mapped_labels.append(new_label)
    used_names.add(new_label)


    # Filter points
    points_filtered = points[:, matched_indices, :]

    # Prepare TRC content
    n_frames = points.shape[2]
    rate = c3d['header']['points']['frame_rate']
    time = np.arange(n_frames) / rate

    # Write TRC
    trc_path = "0005_Walking001.trc"
    with open(trc_path, 'w') as f:
    f.write(f"PathFileType\t4\t(X/Y/Z)\t{trc_path}\n")
    f.write("DataRate\tCameraRate\tNumFrames\tNumMarkers\tUnits\tOrigDataRate\tOrigDataStartFrame\tOrigNumFrames\n")
    f.write(f"{rate:.2f}\t{rate:.2f}\t{n_frames}\t{len(mapped_labels)}\tmm\t{rate:.2f}\t1\t{n_frames}\n")
    f.write("Frame#\tTime\t" + "\t\t".join(mapped_labels) + "\n")
    f.write("\t" + "\t".join([f"{axis}{i+1}" for i in range(len(mapped_labels)) for axis in ["X", "Y", "Z"]]) + "\n")

    for i in range(n_frames):
    row = [str(i+1), f"{time:.5f}"]
    for m in range(len(mapped_labels)):
    x, y, z = points_filtered[:, m, i]
    row.extend([f"{x:.5f}", f"{y:.5f}", f"{z:.5f}"])
    f.write("\t".join(row) + "\n")

    print(f"TRC file written to: {trc_path}")

    Continue reading...

Compartilhe esta Página