Multimodal Synchronized Motion Capture, Force Plate, and Radar Dataset of the One-Legged Stand Test for Fall-Risk Assessment 1.0

File: <base>/Code/viz_OLST_attempt.ipynb (9,352 bytes)
{
 "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
}