Skip to content

Commit 3dbfbae

Browse files
author
ipuch
committed
feat(trc reader)
1 parent 3b601e7 commit 3dbfbae

File tree

7 files changed

+1254
-0
lines changed

7 files changed

+1254
-0
lines changed

environment.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ dependencies:
1414
- imageio
1515
- imageio-ffmpeg
1616
- opensim
17+
# from pip
18+
# pip install trc-data-reader
1719

examples/osim/ABD01.trc

Lines changed: 1096 additions & 0 deletions
Large diffs are not rendered by default.

examples/osim/trc_reader.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import pyorerun as prr
2+
3+
prr.trc(
4+
"ABD01.trc",
5+
)

pyorerun/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@
3838
from .pyoemg import PyoMuscles
3939
from .rrbiomod import rr_biorbd as animate
4040
from .rrc3d import rrc3d as c3d
41+
from .rrtrc import rrtrc as trc
4142
from .xp_components.timeseries_q import OsimTimeSeries
4243
from .xp_components.persistent_marker_options import PersistentMarkerOptions

pyorerun/pyomarkers.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Optional, List
66

77
import ezc3d
8+
from trc import TRCData
89
import numpy as np
910

1011

@@ -200,6 +201,58 @@ def from_c3d(
200201

201202
return cls(data=points, time=time, marker_names=marker_names, show_labels=show_labels, attrs=attrs)
202203

204+
@classmethod
205+
def from_trc(cls, filename: str, show_labels: bool = True) -> "PyoMarkers":
206+
"""
207+
Create PyoMarkers from a TRC file.
208+
209+
Parameters
210+
----------
211+
filename : str
212+
Path to the TRC file
213+
show_labels : bool, default True
214+
Whether to show marker labels
215+
216+
Returns
217+
-------
218+
PyoMarkers
219+
A new PyoMarkers instance
220+
"""
221+
trc_data = TRCData()
222+
trc_data.load(filename)
223+
224+
# Get marker names
225+
marker_names = trc_data["Markers"]
226+
n_markers = len(marker_names)
227+
n_frames = int(trc_data["NumFrames"])
228+
229+
# Initialize data array (3, n_markers, n_frames)
230+
data = np.zeros((3, n_markers, n_frames))
231+
232+
# Fill data array with marker coordinates
233+
for frame_idx, frame_num in enumerate(trc_data["Frame#"]):
234+
_, frame_data = trc_data[frame_num]
235+
for marker_idx, marker_coords in enumerate(frame_data):
236+
# marker_coords is [X, Y, Z]
237+
data[:, marker_idx, frame_idx] = marker_coords
238+
239+
# Get time vector
240+
time = np.array(trc_data["Time"])
241+
242+
# Get units and rate
243+
units = trc_data.get("Units", "mm")
244+
rate = trc_data.get("DataRate", None)
245+
246+
attrs = {
247+
"units": units,
248+
"rate": rate,
249+
"filename": filename,
250+
"first_frame": trc_data["Frame#"][0] if trc_data["Frame#"] else 0,
251+
"last_frame": trc_data["Frame#"][-1] if trc_data["Frame#"] else n_frames - 1,
252+
}
253+
254+
return cls(data=data, time=time, marker_names=marker_names, show_labels=show_labels, attrs=attrs)
255+
203256

204257
class MockChannel:
205258
"""

pyorerun/rrtrc.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from pathlib import Path
2+
from typing import Any
3+
4+
import numpy as np
5+
import rerun as rr
6+
7+
from .phase_rerun import PhaseRerun
8+
from .pyomarkers import PyoMarkers
9+
from .multi_frame_rate_phase_rerun import MultiFrameRatePhaseRerun
10+
11+
12+
def rrtrc(
13+
trc_filename: str,
14+
marker_trajectories: bool = False,
15+
show_floor: bool = True,
16+
notebook: bool = False,
17+
) -> None:
18+
"""
19+
Display a c3d file in rerun.
20+
21+
Parameters
22+
----------
23+
trc_filename: str
24+
The path to the trc file.
25+
marker_trajectories: bool
26+
If True, show the marker trajectories.
27+
show_floor: bool
28+
If True, show the floor.
29+
notebook: bool
30+
If True, display the animation in the notebook.
31+
"""
32+
33+
# Load a c3d file
34+
pyomarkers = PyoMarkers.from_trc(trc_filename)
35+
units = pyomarkers.units
36+
pyomarkers = adjust_position_unit_to_meters(pyomarkers, pyomarkers.units)
37+
pyomarkers.show_labels = False
38+
39+
t_span = pyomarkers.time
40+
filename = Path(trc_filename).name
41+
42+
phase_reruns = []
43+
phase_rerun = PhaseRerun(t_span)
44+
phase_reruns.append(phase_rerun)
45+
phase_rerun.add_xp_markers(filename, pyomarkers)
46+
47+
if show_floor:
48+
square_width = max_xy_coordinate_span_by_markers(pyomarkers)
49+
lowest_corner = 0
50+
phase_rerun.add_floor(square_width, height_offset=lowest_corner - 0.0005)
51+
52+
multi_phase_rerun = MultiFrameRatePhaseRerun(phase_reruns)
53+
multi_phase_rerun.rerun(filename, notebook=notebook)
54+
55+
if marker_trajectories:
56+
# # todo: find a better way to display curves but hacky way ok for now
57+
marker_names = phase_rerun.xp_data.xp_data[0].marker_names
58+
for m in marker_names:
59+
for j, axis in enumerate(["X", "Y", "Z"]):
60+
rr.send_columns(
61+
f"markers_graphs/{m}/{axis}",
62+
indexes=[rr.TimeColumn("stable_time", duration=t_span)],
63+
columns=[
64+
*rr.Scalars.columns(
65+
scalars=phase_rerun.xp_data.xp_data[0].markers_numpy[j, marker_names.index(m), :]
66+
)
67+
],
68+
)
69+
70+
71+
def max_xy_coordinate_span_by_markers(pyomarkers: PyoMarkers) -> float:
72+
"""Return the max span of the x and y coordinates of the markers."""
73+
min_pyomarkers = np.nanmin(np.nanmin(pyomarkers.to_numpy(), axis=2), axis=1)
74+
max_pyomarkers = np.nanmax(np.nanmax(pyomarkers.to_numpy(), axis=2), axis=1)
75+
x_absolute_max = np.nanmax(np.abs([min_pyomarkers[0], max_pyomarkers[0]]))
76+
y_absolute_max = np.nanmax(np.abs([min_pyomarkers[1], max_pyomarkers[1]]))
77+
78+
return np.max([x_absolute_max, y_absolute_max])
79+
80+
81+
def adjust_pyomarkers_unit_to_meters(pyomarkers: PyoMarkers, unit: str) -> PyoMarkers:
82+
"""Adjust the positions to meters for displaying purposes."""
83+
pyomarkers = adjust_position_unit_to_meters(pyomarkers, unit)
84+
pyomarkers.attrs["units"] = "m"
85+
return pyomarkers
86+
87+
88+
def adjust_position_unit_to_meters(array: Any, unit: str) -> Any:
89+
conversion_factors = {"mm": 1000, "cm": 100, "m": 1}
90+
for u, factor in conversion_factors.items():
91+
if u in unit:
92+
array /= factor
93+
break
94+
else:
95+
raise ValueError("The unit of the c3d file is not in meters, mm or cm.")
96+
return array

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies = [
2828
"imageio",
2929
"imageio-ffmpeg",
3030
"matplotlib",
31+
"trc-data-reader"
3132
# "opensim", # Not yet available on pypi, use `conda install opensim-org opensim=4.5.1`
3233
# "biorbd" # Not yet available on pypi, use `conda install -c conda-forge biorbd`
3334
]

0 commit comments

Comments
 (0)