| """Visualize mocap skeleton frames, IMU waveforms, EMG waveforms.""" |
| import os, numpy as np, pandas as pd, matplotlib.pyplot as plt |
| from mpl_toolkits.mplot3d import Axes3D |
|
|
| REC = "${PULSE_ROOT}/dataset/v1/s1" |
| OUT = "${PULSE_ROOT}/paper/figures" |
| os.makedirs(OUT, exist_ok=True) |
|
|
| |
| BONES = [ |
| |
| ("HeadTop","HeadFront"),("HeadL","HeadR"),("HeadFront","SpineTop"), |
| ("SpineTop","Chest"),("Chest","WaistLFront"),("Chest","WaistRFront"), |
| ("WaistLFront","WaistLBack"),("WaistRFront","WaistRBack"), |
| ("WaistLBack","BackL"),("WaistRBack","BackR"),("BackL","BackR"), |
| ("SpineTop","LShoulderTop"),("SpineTop","RShoulderTop"), |
| ("LShoulderTop","LShoulderBack"),("RShoulderTop","RShoulderBack"), |
| |
| ("LShoulderTop","LArm"),("LArm","LElbowOut"),("LElbowOut","LElbowBack"), |
| ("LElbowOut","LForearmRoll"),("LForearmRoll","LWristOut"), |
| ("LWristOut","LWristIn"),("LWristOut","LHandOut"),("LWristIn","LHandIn"), |
| ("LHandOut","LIndex2"),("LIndex2","LIndexTip"), |
| ("LHandOut","LMiddle2"),("LMiddle2","LMiddleTip"), |
| ("LHandIn","LRing2"),("LRing2","LRingTip"), |
| ("LHandIn","LPinky2"),("LPinky2","LPinkyTip"), |
| ("LWristIn","LThumb1"),("LThumb1","LThumbTip"), |
| |
| ("RShoulderTop","RArm"),("RArm","RElbowOut"),("RElbowOut","RElbowBack"), |
| ("RElbowOut","RForearmRoll"),("RForearmRoll","RWristOut"), |
| ("RWristOut","RWristIn"),("RWristOut","RHandOut"),("RWristIn","RHandIn"), |
| ("RHandOut","RIndex2"),("RIndex2","RIndexTip"), |
| ("RHandOut","RMiddle2"),("RMiddle2","RMiddleTip"), |
| ("RHandIn","RRing2"),("RRing2","RRingTip"), |
| ("RHandIn","RPinky2"),("RPinky2","RPinkyTip"), |
| ("RWristIn","RThumb1"),("RThumb1","RThumbTip"), |
| ] |
|
|
|
|
| def load_mocap(path): |
| df = pd.read_csv(path) |
| |
| markers = {} |
| for col in df.columns: |
| if col.startswith("Q_") and col.endswith(" X"): |
| name = col[2:-2] |
| xs = df[f"Q_{name} X"].to_numpy() |
| ys = df[f"Q_{name} Y"].to_numpy() |
| zs = df[f"Q_{name} Z"].to_numpy() |
| markers[name] = np.stack([xs, ys, zs], axis=-1) |
| return df["Time"].to_numpy(), markers |
|
|
|
|
| def plot_skeletons(): |
| t, mk = load_mocap(os.path.join(REC, "aligned_mocap_100hz.csv")) |
| N = len(t) |
| |
| candidate = np.linspace(int(0.1*N), int(0.9*N), 4).astype(int) |
|
|
| fig = plt.figure(figsize=(12, 3.2)) |
| for i, fr in enumerate(candidate): |
| ax = fig.add_subplot(1, 4, i+1, projection='3d') |
| |
| pts = np.array([mk[n][fr] for n in mk]) |
| pts = pts[~np.isnan(pts).any(axis=1)] |
| if len(pts) == 0: |
| continue |
| |
| for a, b in BONES: |
| if a in mk and b in mk: |
| pa, pb = mk[a][fr], mk[b][fr] |
| if np.isnan(pa).any() or np.isnan(pb).any(): |
| continue |
| ax.plot([pa[0], pb[0]], [pa[1], pb[1]], [pa[2], pb[2]], |
| color='#2266aa', lw=1.2) |
| ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], s=4, c='#cc3333', alpha=0.8) |
| |
| c = pts.mean(0) |
| r = np.ptp(pts, axis=0).max() / 2 |
| ax.set_xlim(c[0]-r, c[0]+r); ax.set_ylim(c[1]-r, c[1]+r); ax.set_zlim(c[2]-r, c[2]+r) |
| ax.set_xticks([]); ax.set_yticks([]); ax.set_zticks([]) |
| ax.set_title(f"t={t[fr]:.1f}s", fontsize=9) |
| ax.view_init(elev=12, azim=-75) |
| fig.suptitle("MoCap skeleton frames (56-marker Qualisys, v1/s1)", fontsize=11) |
| fig.tight_layout() |
| out = os.path.join(OUT, "mocap_skeleton.pdf") |
| fig.savefig(out, bbox_inches='tight'); fig.savefig(out.replace('.pdf', '.png'), dpi=150, bbox_inches='tight') |
| plt.close(fig) |
| print("Saved", out) |
|
|
|
|
| def plot_imu(): |
| df = pd.read_csv(os.path.join(REC, "aligned_imu_100hz.csv")) |
| t = df["time"].to_numpy(); t = t - t[0] |
| |
| sites = [("WT0", "Wrist R"), ("WT2", "Forearm R"), |
| ("WT4", "Upper arm R"), ("WT6", "Shin R"), ("WT9", "Torso")] |
| fig, axes = plt.subplots(len(sites), 1, figsize=(9, 6), sharex=True) |
| |
| mid = len(t)//2 |
| sl = slice(max(0, mid-1000), min(len(t), mid+1000)) |
| for ax, (sid, lbl) in zip(axes, sites): |
| for comp, col in zip(["x", "y", "z"], ["#d62728", "#2ca02c", "#1f77b4"]): |
| ax.plot(t[sl], df[f"{sid}_acc_{comp}"].to_numpy()[sl], color=col, lw=0.8, label=f"acc_{comp}") |
| ax.set_ylabel(lbl, fontsize=9) |
| ax.grid(alpha=0.3) |
| axes[0].legend(loc="upper right", ncol=3, fontsize=8) |
| axes[-1].set_xlabel("Time (s)") |
| fig.suptitle("IMU 3-axis acceleration across 5 body sites (v1/s1, 20s window)", fontsize=11) |
| fig.tight_layout() |
| out = os.path.join(OUT, "imu_waveforms.pdf") |
| fig.savefig(out, bbox_inches='tight'); fig.savefig(out.replace('.pdf', '.png'), dpi=150, bbox_inches='tight') |
| plt.close(fig) |
| print("Saved", out) |
|
|
|
|
| def plot_emg(): |
| df = pd.read_csv(os.path.join(REC, "aligned_emg_100hz.csv")) |
| t = df["time"].to_numpy(); t = t - t[0] |
| ch = [f"emg_{i}" for i in range(1, 9)] |
| |
| mid = len(t)//2 |
| sl = slice(max(0, mid-1000), min(len(t), mid+1000)) |
| fig, axes = plt.subplots(8, 1, figsize=(9, 7), sharex=True) |
| for ax, c in zip(axes, ch): |
| sig = df[c].to_numpy()[sl] |
| ax.plot(t[sl], sig, color="#555", lw=0.5) |
| |
| env = pd.Series(np.abs(sig)).rolling(20, min_periods=1).mean().to_numpy() |
| ax.plot(t[sl], env, color="#d62728", lw=0.9) |
| ax.set_ylabel(c, fontsize=8) |
| ax.grid(alpha=0.3) |
| axes[-1].set_xlabel("Time (s)") |
| fig.suptitle("Surface EMG 8-channel raw (grey) with rectified envelope (red), v1/s1, 20s window", |
| fontsize=11) |
| fig.tight_layout() |
| out = os.path.join(OUT, "emg_waveforms.pdf") |
| fig.savefig(out, bbox_inches='tight'); fig.savefig(out.replace('.pdf', '.png'), dpi=150, bbox_inches='tight') |
| plt.close(fig) |
| print("Saved", out) |
|
|
|
|
| if __name__ == "__main__": |
| plot_skeletons() |
| plot_imu() |
| plot_emg() |
|
|