import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axisartist import ParasiteAxes
from mpl_toolkits.axisartist.parasite_axes import HostAxes
from .imu import push_imu
from .utils import lowpass_butter
[docs]
def plot_pushes(data, pushes, var="torque", start=True, stop=True, peak=True, ax=None):
"""
Plot pushes from measurement wheel or ergometer data.
Parameters
----------
data : pd.DataFrame
measurement wheel dataframe or the selected side from the ergometer dict, left, right or mean
pushes : pd.DataFrame
measurement wheel push by push dataframe or the selected side from the ergometer dict, left, right or mean
var : str
variable to plot, default is torque
start : bool
plot push starts, default is True
stop : bool
plot push stops, default is True
peak : bool
plot push peaks, default is True
ax : axis object
Axis to plot on, you can add your own, or it will make a new one.
Returns
-------
ax : axis object
"""
with plt.style.context("seaborn-v0_8-white"):
if not ax:
_, ax = plt.subplots(1, 1)
ax.plot(data["time"], data[var])
if start:
ax.plot(data["time"][pushes["start"]], data[var][pushes["start"]], "C1o")
if stop:
ax.plot(data["time"][pushes["stop"]], data[var][pushes["stop"]], "C1o")
if peak:
ax.plot(data["time"][pushes["peak"]], data[var][pushes["peak"]], "C2o")
ax.set_xlabel("time")
ax.set_ylabel(var)
ax.set_title(f"{var} over time")
return ax
[docs]
def plot_pushes_ergo(data, pushes, title=None, var="power", start=True, stop=True, peak=True):
"""
Plot left, right and mean side ergometer push data
Parameters
----------
data : dict
processed ergometer data dictionary with dataframes
pushes : dict
processed push_by_push ergometer data dictionary with dataframes
title : str
title of the plot, optional
var : str
variable to plot, default is power
start : bool
plot push starts, default is True
stop : bool
plot push stops, default is True
peak : bool
plot push peaks, default is True
Returns
-------
axes : np.array
an array containing an axis for the left, right and mean side
"""
_, axes = plt.subplots(3, 1, sharex="all", sharey="all")
if title:
plt.suptitle("Push detection: " + str(title))
if not title:
plt.suptitle("Push detection")
for idx, side in enumerate(data):
axes[idx] = plot_pushes(data[side], pushes[side], var=var, start=start, stop=stop, peak=peak, ax=axes[idx])
axes[idx].set_title(str(side) + " " + str(var))
plt.tight_layout()
return axes
[docs]
def bland_altman_plot(data1, data2, ax=None, condition=None):
"""
Make a Bland-Altman plot.
A Bland–Altman plot (Difference plot) is a method of data plotting used in analyzing the agreement between two
different assays.
Parameters
----------
data1 : np.array, pd.Series
First variable
data2 : np.array, pd.Series
Second variable
ax : axis object, optional
Axis to plot on, you can add your own, or it will make a new one.
condition : str, optional
add labels to the plot
Returns
-------
ax : axis object
"""
if not ax:
fig, ax = plt.subplots(1, 1)
data1 = np.asarray(data1)
data2 = np.asarray(data2)
mean = np.mean([data1, data2], axis=0)
diff = data1 - data2 # Difference between data1 and data2
md = np.mean(diff) # Mean of the difference
sd = np.std(diff, axis=0) # Standard deviation of the difference
with plt.style.context("seaborn-v0_8-white"):
ax.scatter(mean, diff)
ax.axhline(0, color="dimgray", linestyle="-")
ax.axhline(md, color="darkgray", linestyle="--")
ax.axhline(md + 1.96 * sd, color="lightcoral", linestyle="--")
ax.axhline(md - 1.96 * sd, color="lightcoral", linestyle="--")
ax.set_ylim([md - 3 * sd, md + 3 * sd])
if condition:
ax.set_xlabel(f"Mean of {condition}")
ax.set_ylabel(f"Difference between {condition}")
ax.set_title(f"Bland-Altman plot: {condition} ")
return ax
[docs]
def vel_plot(time, vel, name=""):
"""
Plot velocity versus time
Parameters
----------
time : np.array, pd.Series
time structure
vel : np.array, pd.Series
velocity structure
name : str
name of a session
Returns
-------
ax: axis object
"""
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax = plt.subplots(1, 1, figsize=[10, 6])
ax.plot(time, vel, "r")
ax.set_xlabel("Time [s]", fontsize=12)
ax.set_ylabel("Velocity [m/s]", fontsize=12)
ax.tick_params(axis="y", colors="r", labelsize=12)
ax.tick_params(axis="x", labelsize=12)
ax.yaxis.label.set_color("r")
ax.set_title(f"{name} Velocity")
ax.set_ylim(0, np.max(vel) + 0.5)
ax.autoscale(axis="x", tight=True)
return ax
[docs]
def vel_peak_plot(time, vel, name=""):
"""
Plot velocity versus time, with vel_peak
Parameters
----------
time : np.array, pd.Series
time structure
vel : np.array, pd.Series
velocity structure
name : str
name of a session
Returns
-------
ax: axis object
"""
# Calculate vel_peak and position of vel_peak
y_max_vel = vel.idxmax()
y_max_vel_value = np.max(vel)
# Create time vs. velocity figure with vel_peak
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax = plt.subplots(1, 1, figsize=[10, 6])
ax.plot(time, vel, "r")
ax.plot(time[y_max_vel], vel[y_max_vel], "ko", label="Vel$_{peak}$: " + str(round(y_max_vel_value, 2)) + " m/s")
ax.set_xlabel("Time [s]", fontsize=12)
ax.set_ylabel("Velocity [m/s]", fontsize=12)
ax.tick_params(axis="y", colors="r", labelsize=12)
ax.tick_params(axis="x", labelsize=12)
ax.yaxis.label.set_color("r")
ax.set_title(f"{name} Velocity with vel_peak")
ax.legend(loc="lower right", prop={"size": 12})
ax.set_ylim(0, y_max_vel_value + 0.5)
ax.autoscale(axis="x", tight=True)
return ax
[docs]
def vel_peak_dist_plot(time, vel, dist, name=""):
"""
Plot velocity and distance against time
Parameters
----------
time : np.array, pd.Series
time structure
vel : np.array, pd.Series
velocity structure
dist : np.array, pd.Series
distance structure
name : str
name of a session
Returns
-------
ax: axis object
"""
# Calculate vel_peak and position of vel_peak
y_max_vel = vel.idxmax()
y_max_vel_value = np.max(vel)
# Create time vs. velocity figure with vel_peak
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax1 = plt.subplots(1, 1, figsize=[10, 6])
ax1.set_ylim(0, y_max_vel_value + 0.5)
ax1.plot(time, vel, "r")
ax1.plot(time[y_max_vel], vel[y_max_vel], "ko", label="Vel$_{peak}$: " + str(round(y_max_vel_value, 2)) + " m/s")
ax1.set_xlabel("Time [s]", fontsize=12)
ax1.set_ylabel("Velocity [m/s]", fontsize=12)
ax1.yaxis.label.set_color("r")
ax1.tick_params(axis="y", colors="r", labelsize=12)
ax1.tick_params(axis="x", labelsize=12)
ax1.set_title(f"{name} Velocity and distance with vel_peak")
ax1.legend(loc="lower right", prop={"size": 12})
ax1.autoscale(axis="x", tight=True)
# Create time vs. distance figure
ax2 = ax1.twinx()
ax2.set_ylim(0, max(dist) + 1)
ax2.plot(time, dist)
ax2.plot(time[y_max_vel], dist[y_max_vel], "ko")
ax2.set_ylabel("Distance [m]", fontsize=12)
ax2.tick_params(axis="y", colors="b", labelsize=12)
ax2.yaxis.label.set_color("b")
ax2.autoscale(axis="x", tight=True)
return ax1, ax2
[docs]
def acc_plot(time, acc, name=""):
"""
Plot acceleration versus time
Parameters
----------
time : np.array, pd.Series
time structure
acc : np.array, pd.Series
acceleration structure
name : str
name of a session
Returns
-------
ax: axis object
"""
# Create time vs. acceleration figure
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax = plt.subplots(1, 1, figsize=[10, 6])
ax.plot(time, acc, "g")
ax.set_xlabel("Time [s]", fontsize=12)
ax.set_ylabel("Acceleration [m/$s^2$]", fontsize=12)
ax.tick_params(axis="y", colors="g", labelsize=12)
ax.tick_params(axis="x", labelsize=12)
ax.yaxis.label.set_color("g")
ax.set_title(f"{name} Acceleration")
ax.set_ylim(np.min(acc) - 1, np.max(acc) + 1)
ax.autoscale(axis="x", tight=True)
return ax
[docs]
def acc_peak_plot(time, acc, name=""):
"""
Plot acceleration versus time, with acc_peak
Parameters
----------
time : np.array, pd.Series
time structure
acc : np.array, pd.Series
acceleration structure
name : str
name of a session
Returns
-------
ax: axis object
"""
# Calculate acc_peak and position of acc_peak
y_max_acc_value = np.max(acc)
y_max_acc = acc.idxmax()
# Create time vs. acceleration figure with acc_peak
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax = plt.subplots(1, 1, figsize=[10, 6])
ax.plot(time, acc, "g")
ax.plot(time[y_max_acc], acc[y_max_acc], "k.", label="Acc$_{peak}$: " + str(round(y_max_acc_value, 2)) + " m/$s^2$")
ax.set_xlabel("Time [s]", fontsize=12)
ax.set_ylabel("Acceleration [m/$s^2$]", fontsize=12)
ax.tick_params(axis="y", colors="g", labelsize=12)
ax.tick_params(axis="x", labelsize=12)
ax.yaxis.label.set_color("g")
ax.set_title(f"{name} Acceleration with acc_peak")
ax.legend(loc="lower center", prop={"size": 12})
ax.set_ylim(np.min(acc) - 1, y_max_acc_value + 1)
ax.autoscale(axis="x", tight=True)
return ax
[docs]
def acc_peak_dist_plot(time, acc, dist, name=""):
"""
Plot acceleration and distance versus time, with acc_peak
Parameters
----------
time : np.array, pd.Series
time structure
acc : np.array, pd.Series
acceleration structure
dist : np.array, pd.Series
distance structure
name : str
name of a session
Returns
-------
ax: axis object
"""
# Calculate acc_peak and position of acc_peak
y_max_acc_value = np.max(acc)
y_max_acc = acc.idxmax()
# Create time vs. acceleration figure with acc_peak
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax1 = plt.subplots(1, 1, figsize=[10, 6])
ax1.set_ylim(np.min(acc) - 1, y_max_acc_value + 1)
ax1.plot(time, acc, "g")
ax1.plot(
time[y_max_acc], acc[y_max_acc], "k.", label="Acc$_{peak}$: " + str(round(y_max_acc_value, 2)) + " m/$s^2$"
)
ax1.set_xlabel("Time [s]", fontsize=12)
ax1.set_ylabel("Acceleration [m/$s^2$]", fontsize=12)
ax1.tick_params(axis="y", colors="g", labelsize=12)
ax1.tick_params(axis="x", labelsize=12)
ax1.yaxis.label.set_color("g")
ax1.legend(loc="lower center", prop={"size": 12})
ax1.set_title(f"{name} Acceleration and distance with acc_peak")
ax1.autoscale(axis="x", tight=True)
# Create time vs. distance figure
ax2 = ax1.twinx()
ax2.set_ylim(0, max(dist) + 1)
ax2.plot(time, dist)
ax2.plot(time[y_max_acc], dist[y_max_acc], "k.")
ax2.set_ylabel("Distance [m]", fontsize=12)
ax2.tick_params(axis="y", colors="b", labelsize=12)
ax2.yaxis.label.set_color("b")
ax2.autoscale(axis="x", tight=True)
return ax1, ax2
[docs]
def rot_vel_plot(time, rot_vel, name=""):
"""
Plot rotational velocity versus time
Parameters
----------
time : np.array, pd.Series
time structure
rot_vel : np.array, pd.Series
rotational velocity structure
name : str
name of a session
Returns
-------
ax: axis object
"""
# Create time vs. rotational velocity figure
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax = plt.subplots(1, 1, figsize=[10, 6])
ax.plot(time, rot_vel, "b")
ax.set_xlabel("Time [s]", fontsize=12)
ax.set_ylabel("Rotational velocity [deg/s]", fontsize=12)
ax.tick_params(axis="y", colors="b", labelsize=12)
ax.tick_params(axis="x", labelsize=12)
ax.yaxis.label.set_color("b")
ax.set_title(f"{name} Rotational velocity")
ax.set_ylim(np.min(rot_vel) - 10, np.max(rot_vel) + 10)
ax.autoscale(axis="x", tight=True)
return ax
[docs]
def set_axes_equal_3d(axes):
"""
Set 3D plot axes to equal scale and size
Parameters
----------
axes : matplotlib.axes._subplots.Axes3DSubplot
axes containing 3D plotted data
"""
axes.set_box_aspect([1, 1, 1])
limits = np.array([axes.get_xlim3d(), axes.get_ylim3d(), axes.get_zlim3d()])
origin = np.mean(limits, axis=1)
radius = 0.5 * np.max(np.abs(limits[:, 1] - limits[:, 0]))
x, y, z = origin
axes.set_xlim3d([x - radius, x + radius])
axes.set_ylim3d([y - radius, y + radius])
axes.set_zlim3d([z - radius, z + radius])
[docs]
def imu_push_plot(sessiondata, acc_frame=True, name='', dec=False):
"""
Plot push detection with IMUs
Parameters
----------
sessiondata : dict or pd.Series
processed sessiondata structure or pd.Series with frame data
acc_frame: boolean
set to True if frame acceleration is used, if False the wheel is used
name : str
name of a session
dec : boolean
set to True if main deceleration should be found
Returns
-------
ax: axis object
"""
if type(sessiondata) is dict:
sessiondata = sessiondata["frame"]
sfreq = int(1 / sessiondata['time'].diff().mean())
if acc_frame is True:
acc = sessiondata['accelerometer_x']
else:
acc = lowpass_butter(np.gradient(sessiondata['vel']) * sfreq,
sfreq=sfreq, cutoff=10)
# Changes signal if the main deceleration values should be found
if dec is True:
acc -= 1
push_idx, acc_filt, n_pushes, cycle_time, push_freq = push_imu(acc, sfreq)
# Create time vs. velocity with push detection figure
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax1 = plt.subplots(1, 1, figsize=[10, 6])
ax1.set_ylim(-2, np.max(sessiondata['vel']) + 0.5)
ax1.plot(sessiondata['time'], sessiondata['vel'], 'r')
ax1.plot(sessiondata['time'][push_idx],
sessiondata['vel'][push_idx], 'k.', markersize=10)
ax1.set_xlabel("Time [s]", fontsize=12)
ax1.set_ylabel("Velocity [m/s]", fontsize=12)
ax1.tick_params(axis='y', colors='r', labelsize=12)
ax1.tick_params(axis='x', labelsize=12)
ax1.yaxis.label.set_color('r')
ax1.set_title(f"{name} Push detection Sprint test")
# Create time vs. acceleration with push detection figure
ax2 = ax1.twinx()
ax2.set_ylim(-30, 30)
ax2.plot(sessiondata['time'], acc, 'C7', alpha=0.5)
ax2.plot(sessiondata['time'], acc_filt, 'b')
ax2.plot(sessiondata['time'][push_idx], acc_filt[push_idx], 'k.',
markersize=10, label="Detected push")
ax2.set_ylabel("Acceleration [m/$s^2$]", fontsize=12)
ax2.tick_params(axis='y', colors='b', labelsize=12)
ax2.yaxis.label.set_color('b')
ax2.legend(frameon=True, loc='lower right')
return ax1, ax2
[docs]
def plot_power_speed_dist(data, title="", ylim_power=None, ylim_speed=None, ylim_distance=None):
"""
Plot power, speed and distance versus time for left (solid line) and
right (dotted line) separately
Figure scales automatically, unless you specify it manually with the ylim_* arguments
Parameters
----------
data: dict
processed ergometer data dictionary with dataframes
title: str
a title for the plot
ylim_power: list [min, max] of float or int, optional
list of the minimal and maximal ylim for power in W
ylim_speed: list [min, max] of floats or int, optional
list of the minimal and maximal ylim for speed in km/h
ylim_distance: list [min, max] of floats or int, optional
list of the minimal and maximal ylim for distance in m
Returns
-------
fig: matplotlib.figure.Figure
axes: tuple
the three axes objects
"""
plt.style.use("seaborn-v0_8-ticks")
fig = plt.figure()
# Generate three axes
host = HostAxes(fig, [0.15, 0.1, 0.65, 0.8])
par1 = ParasiteAxes(host, sharex=host)
par2 = ParasiteAxes(host, sharex=host)
host.parasites.append(par1)
host.parasites.append(par2)
# Edit axis visibility
host.axis["right"].set_visible(False)
par1.axis["right"].set_visible(True)
par1.axis["right"].major_ticklabels.set_visible(True)
par1.axis["right"].label.set_visible(True)
# Define third ax
new_axisline = par2.get_grid_helper().new_fixed_axis
par2.axis["right2"] = new_axisline(loc="right", axes=par2, offset=(60, 0))
fig.add_axes(host)
# Plot data
host.plot(data["left"]["time"], data["left"]["power"], "forestgreen", label="Power left")
host.plot(data["right"]["time"], data["right"]["power"], "forestgreen", linestyle="dotted", label="Power right")
par1.plot(data["left"]["time"], data["left"]["speed"], "firebrick", label="Speed left", alpha=0.7)
par1.plot(
data["right"]["time"], data["right"]["speed"], "firebrick", linestyle="dotted", label="Speed right", alpha=0.7
)
par2.plot(data["left"]["time"], data["left"]["dist"], "y", label="Distance left", alpha=0.5)
par2.plot(data["right"]["time"], data["right"]["dist"], "y", linestyle="dotted", label="Distance right", alpha=0.5)
host.autoscale(tight=True, axis="x")
# Scale figure (manually or automatically)
if ylim_power:
host.set_ylim(ylim_power[0], ylim_power[1])
if not ylim_power:
host.set_ylim(0.0, max(max(data["left"]["power"]), max(data["right"]["power"])) * 1.5)
if ylim_speed:
par1.set_ylim(ylim_speed[0], ylim_speed[1])
if not ylim_speed:
par1.set_ylim(0.0, max(max(data["left"]["speed"]), max(data["right"]["speed"])) * 1.1)
if ylim_distance:
par2.set_ylim(ylim_distance[0], ylim_distance[1])
if not ylim_distance:
par2.set_ylim(0.0, max(max(data["left"]["dist"]), max(data["right"]["dist"])) * 1.1)
host.set_title(title)
host.set_xlabel("Time [s]")
host.set_ylabel("Power [W]")
par1.set_ylabel("Speed [km/h]")
par2.set_ylabel("Distance [m]")
host.legend(loc="upper left", frameon=True) # make the legend
host.axis["left"].label.set_color("forestgreen")
par1.axis["right"].label.set_color("firebrick")
par2.axis["right2"].label.set_color("y")
return fig, (host, par1, par2)