{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"%load_ext autoreload\n%autoreload 2","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:43:57.971878Z","iopub.execute_input":"2025-06-08T10:43:57.972150Z","iopub.status.idle":"2025-06-08T10:43:57.999115Z","shell.execute_reply.started":"2025-06-08T10:43:57.972128Z","shell.execute_reply":"2025-06-08T10:43:57.998332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport polars as pl\nfrom matplotlib import cm\nfrom matplotlib.colors import Normalize\nfrom mpl_toolkits.mplot3d.art3d import Line3DCollection\nfrom scipy.spatial.transform import Rotation as R\nfrom scipy.spatial.transform import Slerp\n\n\ndef moving_average(data, window_size=5, pad_mode=\"reflect\"):\n    \"\"\"\n    Moving average with customizable padding (e.g., 'edge', 'reflect', 'symmetric').\n\n    Parameters:\n        data : ndarray (1D or 2D)\n        window_size : int\n        pad_mode : str, padding mode for np.pad (e.g., 'edge', 'reflect', 'symmetric')\n\n    Returns:\n        Smoothed data, same shape as input.\n    \"\"\"\n    data = np.asarray(data)\n    pad = window_size // 2\n\n    if data.ndim == 1:\n        padded = np.pad(data, pad_width=pad, mode=pad_mode)  # type: ignore\n        kernel = np.ones(window_size) / window_size\n        return np.convolve(padded, kernel, mode=\"valid\")\n\n    elif data.ndim == 2:\n        padded = np.pad(data, ((pad, pad), (0, 0)), mode=pad_mode)  # type: ignore\n        kernel = np.ones(window_size) / window_size\n        return np.apply_along_axis(\n            lambda col: np.convolve(col, kernel, mode=\"valid\"), axis=0, arr=padded\n        )\n    else:\n        raise ValueError(\"data must be 1D or 2D\")\n\n\ndef plot_in_3d(x, title=\"3D Acceleration Trajectory\", unit=\"m/s²\"):\n    n_samples = x.shape[0]\n\n    # 線分生成（2点ごとのつなぎ）\n    points = x.reshape(-1, 1, 3)\n    segments = np.concatenate([points[:-1], points[1:]], axis=1)\n\n    # カラーマップ用の正規化\n    norm = Normalize(vmin=0, vmax=n_samples - 1)\n    colors = cm.viridis(norm(np.arange(n_samples - 1)))  # type: ignore\n\n    # 3D線分に色をつける\n    lc = Line3DCollection(segments, colors=colors, linewidth=1)\n\n    # 描画\n    fig = plt.figure(figsize=(4, 3))\n    ax = fig.add_subplot(111, projection=\"3d\")\n    ax.add_collection3d(lc)  # type: ignore\n    # 軸設定\n    ax.set_xlim(np.nanmin(x[:, 0]), np.nanmax(x[:, 0]))\n    ax.set_ylim(np.nanmin(x[:, 1]), np.nanmax(x[:, 1]))\n    ax.set_zlim(np.nanmin(x[:, 2]), np.nanmax(x[:, 2]))  # type: ignore\n    ax.set_xlabel(f\"X ({unit})\")\n    ax.set_ylabel(f\"Y ({unit})\")\n    ax.set_zlabel(f\"Z ({unit}\")  # type: ignore\n    ax.set_aspect(\"equal\")\n    ax.set_title(title)\n\n    # カラーバー追加\n    sm = plt.cm.ScalarMappable(cmap=cm.viridis, norm=norm)  # type: ignore\n    sm.set_array([])  # ダミーデータ（matplotlib仕様）\n    cbar = fig.colorbar(sm, ax=ax, pad=0.1)\n    cbar.set_label(\"Time step index\")\n\n    plt.show()\n\n\ndef correct_quaternion_sign_consistency(q):\n    \"\"\"\n    q: (N, 4) ndarray of quaternions [x, y, z, w]\n    \"\"\"\n    q_corrected = np.copy(q)\n    for i in range(1, len(q)):\n        if np.dot(q_corrected[i], q_corrected[i - 1]) < 0:\n            q_corrected[i] *= -1\n    return q_corrected\n\n\ndef plot_feature(feature, names, hand_at_target_index, gestures_start_index):\n    _, ax = plt.subplots(figsize=(5, 3))\n    for i in range(feature.shape[1]):\n        ax.plot(feature[:, i], label=names[i])\n    ax.axvspan(\n        0,\n        hand_at_target_index,\n        color=\"red\",\n        alpha=0.1,\n        label=\"Transition Phase\",\n    )\n    ax.axvspan(\n        hand_at_target_index,\n        gestures_start_index,\n        color=\"green\",\n        alpha=0.1,\n        label=\"Initial Phase\",\n    )\n    ax.axvspan(\n        gestures_start_index,\n        len(feature),\n        color=\"blue\",\n        alpha=0.1,\n        label=\"Gesture Phase\",\n    )\n    ax.set(\n        title=\"Feature Visualization\",\n        xlabel=\"Time step\",\n        ylabel=\"Feature Value\",\n    )\n    ax.grid()\n    ax.legend(bbox_to_anchor=(1.05, 1), loc=\"upper left\")\n\n\ndef visualize_sequence(\n    train_df, sequence_id, dt=0.1, window_size=21, zero_reset=\"none\"\n):\n    single_sequence = train_df.filter(pl.col(\"sequence_id\") == sequence_id)\n\n    print(\n        single_sequence.select(\n            [\"sequence_id\", \"subject\", \"orientation\", \"gesture\", \"sequence_type\"]\n        )\n        .head(1)\n        .to_pandas()\n        .T\n    )\n\n    tof1 = single_sequence.select(f\"tof_1_v{i}\" for i in range(64)).to_numpy()\n    tof1 = tof1.reshape(-1, 8, 8)\n    tof2 = single_sequence.select(f\"tof_2_v{i}\" for i in range(64)).to_numpy()\n    tof2 = tof2.reshape(-1, 8, 8)\n    tof3 = single_sequence.select(f\"tof_3_v{i}\" for i in range(64)).to_numpy()\n    tof3 = tof3.reshape(-1, 8, 8)\n    tof4 = single_sequence.select(f\"tof_4_v{i}\" for i in range(64)).to_numpy()\n    tof4 = tof4.reshape(-1, 8, 8)\n    tof5 = single_sequence.select(f\"tof_5_v{i}\" for i in range(64)).to_numpy()\n    tof5 = tof5.reshape(-1, 8, 8)\n    acc = single_sequence.select(\"acc_x\", \"acc_y\", \"acc_z\").to_numpy()\n    quat = single_sequence.select(\"rot_x\", \"rot_y\", \"rot_z\", \"rot_w\").to_numpy()\n\n    quat = correct_quaternion_sign_consistency(quat)\n\n    try:\n        # convert sensor coord to world coord\n        rot = R.from_quat(quat)\n        acc_world = rot.apply(acc)\n    except Exception as e:\n        print(f\"Error converting sensor coordinates to world coordinates: {e}\")\n        acc_world = np.zeros_like(acc)\n\n    x_gesture = (single_sequence[\"phase\"] == \"Gesture\").to_numpy().astype(np.float32)\n    gestures_start_index = np.where(x_gesture == 1)[0][0]\n    hand_at_target_index = np.where(\n        single_sequence[\"behavior\"] == \"Hand at target location\"\n    )[0][0]\n\n    print(f\"Hand at target index: {hand_at_target_index}\")\n    print(f\"Gesture start index: {gestures_start_index}\")\n\n    gravity = np.array([0, 0, 9.81])\n    acc_linear = acc_world - gravity\n\n    acc_abs = np.linalg.norm(acc_linear, axis=1, keepdims=True)\n\n    if zero_reset == \"target_location\":\n        # Reset to zero at the target location\n        acc_linear[: hand_at_target_index + 1] = 0\n        acc_abs[: hand_at_target_index + 1] = 0\n    elif zero_reset == \"gesture_start\":\n        # Reset to zero at the gesture start\n        acc_linear[: gestures_start_index + 1] = 0\n        acc_abs[: gestures_start_index + 1] = 0\n    elif zero_reset == \"none\":\n        # Do not reset to zero\n        pass\n    else:\n        raise ValueError(\n            \"zero_reset must be 'target_location', 'gesture_start', or 'none'\"\n        )\n\n    velocity_linear = np.cumsum(acc_linear * dt, axis=0)\n    velocity_ma = moving_average(velocity_linear, window_size=window_size)\n    velocity_abs = np.linalg.norm(velocity_linear, axis=1, keepdims=True)\n\n    position_linear = np.cumsum(velocity_linear * dt, axis=0)\n    position_ma = moving_average(position_linear, window_size=window_size)\n    position_abs = np.linalg.norm(position_linear, axis=1, keepdims=True)\n\n    plot_feature(\n        np.concatenate([acc_linear, acc_abs], axis=1),\n        (\"$a_x$\", \"$a_y$\", \"$a_z$\", \"abs $a$\"),\n        hand_at_target_index,\n        gestures_start_index,\n    )\n    # plot velocity\n    plot_feature(\n        np.concatenate([velocity_linear, velocity_abs, velocity_ma], axis=1),\n        (\"$v_x$\", \"$v_y$\", \"$v_z$\", \"abs $v$\", \"MA $v_x$\", \"MA $v_y$\", \"MA $v_z$\"),\n        hand_at_target_index,\n        gestures_start_index,\n    )\n    plot_feature(\n        np.concatenate([position_linear, position_abs, position_ma], axis=1),\n        (\"$p_x$\", \"$p_y$\", \"$p_z$\", \"abs $p$\", \"MA $p_x$\", \"MA $p_y$\", \"MA $p_z$\"),\n        hand_at_target_index,\n        gestures_start_index,\n    )\n\n    plot_in_3d(acc_linear, title=\"acc_linear\", unit=\"m/s²\")\n    plot_in_3d(velocity_linear, title=\"velocity_linear\", unit=\"m/s\")\n    plot_in_3d(position_linear, title=\"position_linear\", unit=\"m\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:44:05.638320Z","iopub.execute_input":"2025-06-08T10:44:05.638600Z","iopub.status.idle":"2025-06-08T10:44:06.598302Z","shell.execute_reply.started":"2025-06-08T10:44:05.638578Z","shell.execute_reply":"2025-06-08T10:44:06.597550Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport polars as pl","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:46:40.341340Z","iopub.execute_input":"2025-06-08T10:46:40.341621Z","iopub.status.idle":"2025-06-08T10:46:40.358429Z","shell.execute_reply.started":"2025-06-08T10:46:40.341601Z","shell.execute_reply":"2025-06-08T10:46:40.357353Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_dir = Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:46:40.789191Z","iopub.execute_input":"2025-06-08T10:46:40.789540Z","iopub.status.idle":"2025-06-08T10:46:40.806478Z","shell.execute_reply.started":"2025-06-08T10:46:40.789517Z","shell.execute_reply":"2025-06-08T10:46:40.805605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUMERIC_COLUMNS = [\n    *[f\"thm_{i}\" for i in range(1, 6)],\n    *[f\"tof_{i}_v{j}\" for i in range(1, 6) for j in range(64)],\n]\ntrain_df = pl.read_csv(data_dir / \"train.csv\").with_columns(\n    pl.col(c).replace(-1, None) for c in NUMERIC_COLUMNS if c.startswith(\"tof_\")\n)\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:46:42.599868Z","iopub.execute_input":"2025-06-08T10:46:42.600167Z","iopub.status.idle":"2025-06-08T10:46:54.205746Z","shell.execute_reply.started":"2025-06-08T10:46:42.600143Z","shell.execute_reply":"2025-06-08T10:46:54.204982Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def sanitize_col_name(s):\n    return s.lower().replace(\" \", \"_\")\n\n\nsequence_meta_df = train_df.group_by(\"sequence_id\", maintain_order=True).agg(\n    *[\n        pl.col(c).first()\n        for c in [\n            \"sequence_type\",\n            \"subject\",\n            \"orientation\",\n            \"gesture\",\n        ]\n    ]\n)\nsequence_meta_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:47:10.848287Z","iopub.execute_input":"2025-06-08T10:47:10.848579Z","iopub.status.idle":"2025-06-08T10:47:11.854070Z","shell.execute_reply.started":"2025-06-08T10:47:10.848555Z","shell.execute_reply":"2025-06-08T10:47:11.853214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gesture_to_type = {\n    d[\"gesture\"]: d[\"sequence_type\"]\n    for d in train_df.group_by(\"gesture\")\n    .agg(pl.col(\"sequence_type\").eq(\"Target\").first())\n    .sort(\"sequence_type\", \"gesture\")\n    .to_dicts()\n}\n\nfor i, (k, v) in enumerate(gesture_to_type.items()):\n    tag = \"🚨\" if v else \"✅\"\n    print(f\"{tag} {i}: {k}: {v}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:47:23.528975Z","iopub.execute_input":"2025-06-08T10:47:23.529545Z","iopub.status.idle":"2025-06-08T10:47:23.626076Z","shell.execute_reply.started":"2025-06-08T10:47:23.529518Z","shell.execute_reply":"2025-06-08T10:47:23.625053Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import Markdown\n\nfor gesture in gesture_to_type.keys():\n    sequence_id = sequence_meta_df.filter(pl.col(\"gesture\") == gesture).sample(\n        1, seed=2, with_replacement=False\n    )[\"sequence_id\"][0]\n    display(Markdown(f\"### 📊 {gesture=}, {sequence_id=}\"))\n    visualize_sequence(\n        train_df, sequence_id, window_size=7, zero_reset=\"none\"\n    )\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:47:54.970029Z","iopub.execute_input":"2025-06-08T10:47:54.970408Z","iopub.status.idle":"2025-06-08T10:47:57.030590Z","shell.execute_reply.started":"2025-06-08T10:47:54.970384Z","shell.execute_reply":"2025-06-08T10:47:57.029788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import Markdown\n\nfor gesture in gesture_to_type.keys():\n    sequence_id = sequence_meta_df.filter(pl.col(\"gesture\") == gesture).sample(\n        1, seed=2, with_replacement=False\n    )[\"sequence_id\"][0]\n    display(Markdown(f\"### 📊 {gesture=}, {sequence_id=}\"))\n    visualize_sequence(\n        train_df, sequence_id, window_size=7, zero_reset=\"target_location\"\n    )\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:48:17.559784Z","iopub.execute_input":"2025-06-08T10:48:17.560714Z","iopub.status.idle":"2025-06-08T10:48:18.874064Z","shell.execute_reply.started":"2025-06-08T10:48:17.560684Z","shell.execute_reply":"2025-06-08T10:48:18.872676Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import Markdown\n\nfor gesture in gesture_to_type.keys():\n    sequence_id = sequence_meta_df.filter(pl.col(\"gesture\") == gesture).sample(\n        1, seed=2, with_replacement=False\n    )[\"sequence_id\"][0]\n    display(Markdown(f\"### 📊 {gesture=}, {sequence_id=}\"))\n    visualize_sequence(\n        train_df, sequence_id, window_size=7, zero_reset=\"gesture_start\"\n    )\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T10:48:44.090752Z","iopub.execute_input":"2025-06-08T10:48:44.091059Z","iopub.status.idle":"2025-06-08T10:48:45.554712Z","shell.execute_reply.started":"2025-06-08T10:48:44.091035Z","shell.execute_reply":"2025-06-08T10:48:45.553795Z"}},"outputs":[],"execution_count":null}]}