{
"cells": [
{
"cell_type": "markdown",
"id": "a3c65a72",
"metadata": {},
"source": [
"# 📦 Setup"
]
},
{
"cell_type": "code",
"execution_count": 149,
"id": "c8ba79b5",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from matplotlib import animation\n",
"from scipy.io import loadmat\n",
"from IPython.display import HTML\n",
"import imageio"
]
},
{
"cell_type": "markdown",
"id": "b163b96e",
"metadata": {},
"source": [
"# 📑 Load metadata"
]
},
{
"cell_type": "code",
"execution_count": 150,
"id": "35e5984f",
"metadata": {},
"outputs": [],
"source": [
"metadata_path = '../Metadata/OLST_Attempts.csv'\n",
"attempts_df = pd.read_csv(metadata_path)"
]
},
{
"cell_type": "markdown",
"id": "5cb051e6",
"metadata": {},
"source": [
"# 🔢 USER INPUT: Enter OLST_attempt_id"
]
},
{
"cell_type": "code",
"execution_count": 151,
"id": "109654f1",
"metadata": {},
"outputs": [],
"source": [
"attempt_id = \"36_TRLGL_RR_V4_an2\" # ENTER Example attempt ID\n",
"participant_id = attempt_id.split('_')[0]"
]
},
{
"cell_type": "markdown",
"id": "2d1e0e86",
"metadata": {},
"source": [
"# � Define base directory paths"
]
},
{
"cell_type": "code",
"execution_count": 152,
"id": "c3b4f3ab",
"metadata": {},
"outputs": [],
"source": [
"mocap_base = f'../Raw/mocap/{participant_id}'\n",
"fp_base = f'../Raw/ForcePlate/{participant_id}'\n",
"radar_base = f'../Processed/Radar_RDMs/{participant_id}'"
]
},
{
"cell_type": "markdown",
"id": "f19397ce",
"metadata": {},
"source": [
"# � Lookup metadata row"
]
},
{
"cell_type": "code",
"execution_count": 153,
"id": "6a9ef760",
"metadata": {},
"outputs": [],
"source": [
"row = attempts_df[attempts_df['OLST_attempt_id'] == attempt_id].iloc[0]\n",
"radar_id = row['RADAR_capture']\n",
"an_number = row['an']"
]
},
{
"cell_type": "markdown",
"id": "ede232ab",
"metadata": {},
"source": [
"# 🧠Inferred filenames"
]
},
{
"cell_type": "code",
"execution_count": 154,
"id": "0d2da7e5",
"metadata": {},
"outputs": [],
"source": [
"mocap_file = f\"{radar_id.replace('_RR_', '_MC_')}_pos.csv\"\n",
"foot_side = 'left' if any(tag in radar_id for tag in ['MNTRL', 'TRLG']) else 'right'\n",
"fp_file = f\"{radar_id.replace('_RR_', '_FP_')}_{foot_side}.csv\"\n",
"radar_file = f\"{radar_id}.mat\"\n",
"\n",
"mocap_path = os.path.join(mocap_base, mocap_file)\n",
"fp_path = os.path.join(fp_base, fp_file)\n",
"radar_path = os.path.join(radar_base, radar_file)"
]
},
{
"cell_type": "markdown",
"id": "ba158ad3",
"metadata": {},
"source": [
"# 📈 Load sensor data"
]
},
{
"cell_type": "code",
"execution_count": 155,
"id": "4c82d5b4",
"metadata": {},
"outputs": [],
"source": [
"df_mocap = pd.read_csv(mocap_path)\n",
"df_fp = pd.read_csv(fp_path)\n",
"mat_data = loadmat(radar_path)\n",
"rdm_cube = mat_data['zoomed_RDM_cube'] # (range, doppler, frames)"
]
},
{
"cell_type": "markdown",
"id": "15853735",
"metadata": {},
"source": [
"# 🧠Sync info"
]
},
{
"cell_type": "code",
"execution_count": 156,
"id": "8b190f58",
"metadata": {},
"outputs": [],
"source": [
"# --- Time sync constants ---\n",
"t_start = row['t_foot_up']\n",
"t_stop = row['t_end']\n",
"seconds_per_frame = row['Seconds_per_Frame']\n",
"num_frames = int(np.floor((t_stop - t_start) / seconds_per_frame))\n",
"\n",
"# --- Create target time vector (radar-aligned) ---\n",
"target_times = t_start + np.arange(num_frames) * seconds_per_frame\n",
"\n",
"# --- Resample MOCAP ---\n",
"df_mocap['time'] = pd.to_numeric(df_mocap['time'], errors='coerce')\n",
"df_mocap_interp = df_mocap.set_index('time').interpolate(method='linear', axis=0)\n",
"df_mocap_sync = df_mocap_interp.reindex(target_times, method='nearest').reset_index(drop=True)\n",
"\n",
"# --- Resample FP ---\n",
"df_fp['time'] = pd.to_numeric(df_fp['time'], errors='coerce')\n",
"df_fp_interp = df_fp.set_index('time').interpolate(method='linear', axis=0)\n",
"df_fp_sync = df_fp_interp.reindex(target_times, method='nearest').reset_index(drop=True)\n",
"\n",
"# --- Radar frame indices ---\n",
"radar_frame_indices = list(range(int(row['frame_foot_up']), int(row['frame_foot_up']) + num_frames))"
]
},
{
"cell_type": "markdown",
"id": "4f4bf292",
"metadata": {},
"source": [
"# ⚖� Normalize force plate CoP for plotting"
]
},
{
"cell_type": "code",
"execution_count": 157,
"id": "65442c2c",
"metadata": {},
"outputs": [],
"source": [
"cop_x = df_fp_sync['COP_X'].values\n",
"cop_y = df_fp_sync['COP_Y'].values\n",
"cop_x = (cop_x - np.min(cop_x)) / (np.max(cop_x) - np.min(cop_x))\n",
"cop_y = (cop_y - np.min(cop_y)) / (np.max(cop_y) - np.min(cop_y))"
]
},
{
"cell_type": "markdown",
"id": "d85f6955",
"metadata": {},
"source": [
"# � Set up figure and animation"
]
},
{
"cell_type": "code",
"execution_count": 158,
"id": "15aee345",
"metadata": {},
"outputs": [],
"source": [
"fig, axes = plt.subplots(1, 3, figsize=(15, 5))\n",
"fig.tight_layout(rect=[0, 0, 1, 0.87]) # make room for a longer suptitle\n",
"\n",
"# Precompute time and frame ranges for the title\n",
"time_start = target_times[0]\n",
"time_end = target_times[-1]\n",
"frame_start = radar_frame_indices[0]\n",
"frame_end = radar_frame_indices[-1]\n",
"\n",
"def update(frame_idx):\n",
" f_radar = radar_frame_indices[frame_idx]\n",
" f_fp = frame_idx\n",
" f_mocap = frame_idx\n",
" current_time = target_times[frame_idx]\n",
"\n",
" for ax in axes:\n",
" ax.clear()\n",
"\n",
" # --- Radar RDM Frame ---\n",
" axes[0].imshow(rdm_cube[:, :, f_radar], aspect='auto', origin='lower', cmap='jet')\n",
" axes[0].set_title(f'Radar RDM\\nFrame {f_radar}')\n",
" axes[0].axis('off')\n",
"\n",
" # --- Force Plate CoP ---\n",
" axes[1].plot(cop_x[:f_fp+1], cop_y[:f_fp+1], color='blue', alpha=0.7)\n",
" axes[1].scatter(cop_x[f_fp], cop_y[f_fp], color='red')\n",
" axes[1].set_xlim(0, 1)\n",
" axes[1].set_ylim(0, 1)\n",
" axes[1].set_title(f'Force Plate CoP\\nTime {current_time:.2f} s')\n",
" axes[1].axis('off')\n",
"\n",
" # --- MOCAP Point Cloud Plot (Z-Y projection, excluding actuator markers) ---\n",
" pos_cols = [col for col in df_mocap_sync.columns if '_pos_' in col]\n",
" marker_names = sorted(set(col.rsplit('_pos_', 1)[0] for col in pos_cols))\n",
" marker_names = [name for name in marker_names if 'Actuator' not in name]\n",
"\n",
" points = []\n",
" for marker in marker_names:\n",
" x_col = f\"{marker}_pos_X\"\n",
" y_col = f\"{marker}_pos_Y\"\n",
" z_col = f\"{marker}_pos_Z\"\n",
" if x_col in df_mocap_sync.columns and y_col in df_mocap_sync.columns and z_col in df_mocap_sync.columns:\n",
" x = df_mocap_sync.at[f_mocap, x_col]\n",
" y = df_mocap_sync.at[f_mocap, y_col]\n",
" z = df_mocap_sync.at[f_mocap, z_col]\n",
" points.append((x, y, z))\n",
"\n",
" points = np.array(points)\n",
"\n",
" if points.shape[0] > 0:\n",
" axes[2].scatter(points[:, 1], points[:, 2], color='purple', s=20) # Z-Y projection\n",
" axes[2].set_title(f'MOCAP Point Cloud\\nZ-Y Projection\\nTime {current_time:.2f} s')\n",
" axes[2].axis('equal')\n",
" axes[2].axis('off')\n",
"\n",
" # --- Super title with full info ---\n",
" fig.suptitle(\n",
" f\"{attempt_id} | Time {time_start:.2f}–{time_end:.2f} s | Frames {frame_start}–{frame_end}\",\n",
" fontsize=16\n",
" )\n",
"\n",
" return axes\n",
"\n",
"ani = animation.FuncAnimation(fig, update, frames=num_frames, interval=1000 * seconds_per_frame)\n",
"plt.close()"
]
},
{
"cell_type": "markdown",
"id": "4ec60598",
"metadata": {},
"source": [
"# 💾 Save GIF"
]
},
{
"cell_type": "code",
"execution_count": 159,
"id": "48220f01",
"metadata": {},
"outputs": [],
"source": [
"gif_filename = f'viz_{attempt_id}.gif'\n",
"ani.save(gif_filename, writer='pillow', dpi=100)\n",
"plt.close()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "radartreepose_env",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.15"
}
},
"nbformat": 4,
"nbformat_minor": 5
}