Load Data into DataFrames#
This guide goes through the process of using OPynSim to load various data files
in the OpenSim ecosystem (e.g. .trc, .sto) into Pandas dataframes so
that your Python code can easily manipulate and visualize the data.
The utility of dataframes is that they are standard across the Python ecosystem: many libraries (e.g. plotters, statistical, machine learning) accept/emit dataframes. Having a pathway between the data files emitted by platforms like OpenSim/OPynSim and data frames increases the utility of that data.
Setup Example Data#
If you want to run the Python code in this guide, you must first set up the Example Data.
Read Data into an OPynSim DataFrame#
The opynsim module provides a variety of file readers. All are prefixed
with read_. Here are some useful ones:
opynsim.read_osim: Read an OpenSim model file (.osim) into anopynsim.ModelSpecification.opynsim.read_sto: Read an OpenSim storage file (.sto) into anopynsim.DataFrame.opynsim.read_trc: Read a track-row-column OpenSim file (.trc) into anopynsim.DataFrame.opynsim.read_vtp: Read a VTP mesh file (.vtp) into anopynsim.Mesh.
For example, say you wanted to read the .mot file that we used in the
previous Load States From a File guide. You could do that with
opynsim.read_mot, which returns an opynsim.DataFrame:
import opynsim as opyn
# `df` is an `opynsim.DataFrame`
df = opyn.read_mot("pragmatic_resources/gait2354/OutputReference/subject01_walk1_ik.mot")
Convert to Pandas DataFrame#
OPynSim’s DataFrame class is designed to be converted to/from DataFrames
from the wider Python ecosystem using optimized conversion functions, which
lets you choose which data frame library you want to use in your
scripts. These conversion functions convert to common DataFrame
implementations:
df.to_pandas(): Convertsdfinto a pandas.DataFrame (requires thepyarrowPython package to also be installed).df.to_polars(): Convertsdfinto a polars.DataFrame.
For example, here is how you would load a .mot file into a pandas.DataFrame:
import opynsim
# An `opynsim.DataFrame`.
odf = opyn.read_mot("pragmatic_resources/gait2354/OutputReference/subject01_walk1_ik.mot")
# A `pandas.DataFrame`.
pdf = opynsim_df.to_pandas()
Manipulate/Plot Pandas DataFrame#
Once you have a DataFrame in a data frame library of your choice, you can
then manipulate it, plot it, or export it to a different data format much
more easily.
For example, you could use matplotlib to plot columns in a pandas.DataFrame:
# Reads a `.mot` file and plots a timeseries of some of its columns
import opynsim
import matplotlib.pyplot as plt
# Using built-in Pandas plotting (usually easiest)
pdf = opyn.read_mot("pragmatic_resources/gait2354/OutputReference/subject01_walk1_ik.mot").to_pandas()
pdf.plot(x="time", y=["knee_angle_l", "knee_angle_r"], kind="line")
plt.show()
# Using matplotlib directly (more customizable)
plt.plot(pdf["time"], pdf["knee_angle_l"], label="knee_angle_l")
plt.plot(pdf["time"], pdf["knee_angle_r"], label="knee_angle_r")
plt.xlabel("time")
plt.legend()
plt.show()
Or scaling things:
# Reads a `.mot` file and scales `pelvis_tx` by 100x.
import opynsim
pdf = opyn.read_mot("pragmatic_resources/gait2354/OutputReference/subject01_walk1_ik.mot").to_pandas()
pdf["pelvis_tx"] *= 100.0 # e.g. m -> cm
Or resampling the data to a fixed rate, which can be useful for plotting, animating, signal processing, etc.:
# Reads a `.mot` file and resamples it to 120 Hz with linear interpolation.
import numpy as np
import opynsim as opyn
import pandas as pd
pdf = opyn.read_mot("pragmatic_resources/gait2354/OutputReference/subject01_walk1_ik.mot").to_pandas()
pdf = pdf.set_index("time")
t_120hz = np.arange(pdf.index.min(), pdf.index.max(), 1/120.0)
pdf_120hz = (
pdf.reindex(pdf.index.union(t_120hz))
.interpolate(method="index")
.loc[t_120hz]
)
# Reads a `.mot` file and resamples it to 120 Hz with cubic splines.
#
# Requires installing `scipy` (e.g. `pip install scipy`).
from scipy.interpolate import CubicSpline
import pandas as pd
import numpy as np
import opynsim as opyn
pdf = opyn.read_mot("pragmatic_resources/gait2354/OutputReference/subject01_walk1_ik.mot").to_pandas()
pdf = pdf.set_index("time")
t_120hz = np.arange(pdf.index.min(), pdf.index.max(), 1/120.0)
pdf_120hz = pd.DataFrame(
data=CubicSpline(pdf.index, pdf.to_numpy(), axis=0)(t_120hz),
index=t_120hz,
columns=pdf.columns
)
Although beware: in this example’s case we’re actually upsampling with interpolation (the original data is <120 Hz). Python can make things easier, not sensible 🤡.
Convert to OPynSim DataFrame#
Sometimes you’ll want to get data from a third-party DataFrame to an
opynsim.DataFrame. Examples include loading cleaned-up DataFrames
into opynsim.ModelStates, loading motion data from an alternative file
formats (e.g. .csv, .parquet),
into OPynSim, or writing OpenSim-compatible data files from OPynSim’s DataFrame.
OPynSim’s DataFrame class can be constructed from third-party DataFrames - provided
they expose an Arrow PyCapsule Interface (many do):
import opynsim as opyn
# A `pandas.DataFrame`.
pdf = opyn.read_mot("pragmatic_resources/gait2354/OutputReference/subject01_walk1_ik.mot").to_pandas()
# An `opynsim.DataFrame`.
df = opyn.DataFrame(pdf)
State Data Handling and Examples#
Once you have the ability to load data, convert it, manipulate it, and push it back into OPynSim, you can then combine those techniques to do interesting things with models.
For example, say you have some states from OpenSim’s IK dynamic solver. You would like to extract outputs from those states to plot alongside experimental data. One problem is that OpenSim’s forward dynamic solver may have a variable step size, but your experimental data has a fixed step size.
TODO write up concrete example
Warning
State files from OpenSim may encode angular columns in either degrees or radians. The
header/metadata will have an inDegrees=yes entry when all applicable columns are
in degrees. Otherwise, they’re in radians. It’s recommended to always normalize
angular columns to radians, so that you don’t have to constantly track the
inDegrees flag.
Unfortunately, state files (e.g. .sto, .mot) do not express which columns
are angular. Therefore, both OPynSim and OpenSim must first load the associated model
to figure that out; for example, with:
model.convert_data_frame_to_radians(df)model.getSimbodyEngine().convertDegreesToRadians(table)