{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":255504528,"sourceType":"kernelVersion"},{"sourceId":258551931,"sourceType":"kernelVersion"},{"sourceId":551275,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":414831,"modelId":432591}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import importlib.util\nimport sys\nimport os \nimport polars as pl\nimport numpy as np\nimport pandas as pd\nimport pathlib\nfrom pathlib import Path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:15.207665Z","iopub.execute_input":"2025-08-29T17:25:15.207999Z","iopub.status.idle":"2025-08-29T17:25:15.789656Z","shell.execute_reply.started":"2025-08-29T17:25:15.207976Z","shell.execute_reply":"2025-08-29T17:25:15.788493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm.notebook import tqdm\nimport polars.selectors as cs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:15.791265Z","iopub.execute_input":"2025-08-29T17:25:15.791810Z","iopub.status.idle":"2025-08-29T17:25:15.873970Z","shell.execute_reply.started":"2025-08-29T17:25:15.791781Z","shell.execute_reply":"2025-08-29T17:25:15.872873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import kagglehub\nfrom kagglehub import KaggleDatasetAdapter","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:15.874778Z","iopub.execute_input":"2025-08-29T17:25:15.875231Z","iopub.status.idle":"2025-08-29T17:25:16.041913Z","shell.execute_reply.started":"2025-08-29T17:25:15.875204Z","shell.execute_reply":"2025-08-29T17:25:16.041012Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load external module dynamically\ndef load_my_utils(filedir, module_name):\n    if module_name in sys.modules:\n        return sys.modules[module_name]\n    \n    spec = importlib.util.spec_from_file_location(module_name, filedir)\n    module = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(module)\n    sys.modules[module_name] = module\n    return module","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:16.043621Z","iopub.execute_input":"2025-08-29T17:25:16.043914Z","iopub.status.idle":"2025-08-29T17:25:16.050577Z","shell.execute_reply.started":"2025-08-29T17:25:16.043885Z","shell.execute_reply":"2025-08-29T17:25:16.049458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"filedir = '/kaggle/usr/lib/cmi_sensor_data_utility_functions/cmi_sensor_data_utility_functions.py'\nmodule_name = \"my_utils\"\nmy_utils = load_my_utils(filedir, module_name)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:16.051860Z","iopub.execute_input":"2025-08-29T17:25:16.052696Z","iopub.status.idle":"2025-08-29T17:25:16.781748Z","shell.execute_reply.started":"2025-08-29T17:25:16.052663Z","shell.execute_reply":"2025-08-29T17:25:16.780484Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"filedir = '/kaggle/input/cmi-gesture-classification-gru-models/pytorch/imu-data/15/gru_model_module.py'\nmodule_name = \"gru_utils\"\ngru_utils = load_my_utils(filedir, module_name)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:16.782864Z","iopub.execute_input":"2025-08-29T17:25:16.783569Z","iopub.status.idle":"2025-08-29T17:25:18.954043Z","shell.execute_reply.started":"2025-08-29T17:25:16.783532Z","shell.execute_reply":"2025-08-29T17:25:18.952850Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(my_utils.notebook_folder) \ndata_folders_dictionary = my_utils.data_folder(my_utils.notebook_folder)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:18.955716Z","iopub.execute_input":"2025-08-29T17:25:18.956303Z","iopub.status.idle":"2025-08-29T17:25:18.962797Z","shell.execute_reply.started":"2025-08-29T17:25:18.956271Z","shell.execute_reply":"2025-08-29T17:25:18.961587Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Test Submission models**","metadata":{}},{"cell_type":"code","source":"def move_column_to_end(df, col_name):\n    cols = [col for col in df.columns if col != col_name] + [col_name]\n    return df[cols]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:18.964526Z","iopub.execute_input":"2025-08-29T17:25:18.964882Z","iopub.status.idle":"2025-08-29T17:25:18.980643Z","shell.execute_reply.started":"2025-08-29T17:25:18.964853Z","shell.execute_reply":"2025-08-29T17:25:18.979501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from collections import defaultdict\ndef add_dicts_with_padding(dict1, dict2):\n    result = {}\n    all_keys = set(dict1.keys()).union(dict2.keys())\n\n    for key in all_keys:\n        list1 = dict1.get(key, [])\n        list2 = dict2.get(key, [])\n\n        max_len = max(len(list1), len(list2))\n        \n        # Pad shorter list with zeros\n        padded1 = list1 + [0] * (max_len - len(list1))\n        padded2 = list2 + [0] * (max_len - len(list2))\n\n        result[key] = [a + b for a, b in zip(padded1, padded2)]\n\n    return result","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:18.982029Z","iopub.execute_input":"2025-08-29T17:25:18.982436Z","iopub.status.idle":"2025-08-29T17:25:18.999263Z","shell.execute_reply.started":"2025-08-29T17:25:18.982401Z","shell.execute_reply":"2025-08-29T17:25:18.998119Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def check_quaternion_values(df: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Validates a quaternion DataFrame with columns ['rot_x', 'rot_y', 'rot_z', 'rot_w'].\n    - Returns the input quaternion if all values are non-null and norm > 0.\n    - Returns the default identity quaternion [0, 0, 0, 1] otherwise.\n\n    Args:\n        df (pd.DataFrame): Single-row DataFrame with quaternion components.\n\n    Returns:\n        pd.DataFrame: Validated quaternion as a single-row DataFrame.\n    \"\"\"\n\n    # Define identity quaternion [0, 0, 0, 1]\n    identity_q = pd.DataFrame({\n        'rot_x': [0.0],\n        'rot_y': [0.0],\n        'rot_z': [0.0],\n        'rot_w': [1.0]\n    })\n\n    # If any value is NaN, return identity quaternion\n    if df.isnull().values.any():\n        return identity_q\n\n    # Convert DataFrame to numpy array to check norm\n    q_values = df.to_numpy(dtype='float')\n    norm = np.linalg.norm(q_values)\n\n    # If norm is zero or negative (invalid quaternion), return identity\n    if norm > 0:\n        return df.copy()\n    else:\n        return identity_q   ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.003215Z","iopub.execute_input":"2025-08-29T17:25:19.003586Z","iopub.status.idle":"2025-08-29T17:25:19.017323Z","shell.execute_reply.started":"2025-08-29T17:25:19.003553Z","shell.execute_reply":"2025-08-29T17:25:19.016292Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Valid quaternion\ndf_valid = pd.DataFrame({'rot_x': [0.0], 'rot_y': [0.0], 'rot_z': [0.0], 'rot_w': [1.0]})\nprint(check_quaternion_values(df_valid))\n\n# Quaternion with NaN\ndf_nan = pd.DataFrame({'rot_x': [np.nan], 'rot_y': [0.0], 'rot_z': [0.0], 'rot_w': [1.0]})\nprint(check_quaternion_values(df_nan))\n\n# Zero quaternion\ndf_zero = pd.DataFrame({'rot_x': [0.0], 'rot_y': [0.0], 'rot_z': [0.0], 'rot_w': [0.0]})\nprint(check_quaternion_values(df_zero))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.018631Z","iopub.execute_input":"2025-08-29T17:25:19.019424Z","iopub.status.idle":"2025-08-29T17:25:19.049812Z","shell.execute_reply.started":"2025-08-29T17:25:19.019363Z","shell.execute_reply":"2025-08-29T17:25:19.048770Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**1. Function to find key by unique value**","metadata":{}},{"cell_type":"code","source":"# - Function to find key by unique value\ndef find_key_by_unique_value(my_dict, target_value):\n    \"\"\"\n    :param my_dict:\n    :param target_value:\n    :return:\n    \"\"\"\n\n    for key, value in my_dict.items():\n        if value == target_value:\n            return key\n    return None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.050746Z","iopub.execute_input":"2025-08-29T17:25:19.051047Z","iopub.status.idle":"2025-08-29T17:25:19.057018Z","shell.execute_reply.started":"2025-08-29T17:25:19.051018Z","shell.execute_reply":"2025-08-29T17:25:19.055907Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**2. Function to build full IMU dataset for prediction**","metadata":{}},{"cell_type":"code","source":"def build_imu_subset(sequence_cat_subset: pd.DataFrame,\n                     sequence_acc_subset: pd.DataFrame,\n                     sequence_rot_subset: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Processes IMU data by validating quaternion data and computing motion features:\n    world-frame acceleration, motion-frame acceleration, velocity, and position.\n\n    Parameters:\n        sequence_cat_subset (pd.DataFrame): Metadata including timestamps (e.g., 'sequence_counter')\n        sequence_acc_subset (pd.DataFrame): Accelerometer data (e.g., x, y, z)\n        sequence_rot_subset (pd.DataFrame): Quaternion rotation data (e.g., rot_x, rot_y, rot_z, rot_w)\n\n    Returns:\n        pd.DataFrame: Combined DataFrame with all raw and computed features.\n    \"\"\"\n    # Validate quaternion data row by row\n    checked_rot_subset = sequence_rot_subset.copy()\n    for i in tqdm(range(len(sequence_rot_subset)), desc=\"Checking quaternions\"):\n        rot_row = sequence_rot_subset.iloc[i]\n        checked_rot_subset.iloc[i] = check_quaternion_values(rot_row)\n\n    # Convert all components to NumPy arrays\n    tdata = sequence_cat_subset['sequence_counter'].to_numpy(dtype='float')\n    acc_data = sequence_acc_subset.to_numpy(dtype='float')\n    q = checked_rot_subset.to_numpy(dtype='float')\n\n    # Process IMU data using utility function (assumes pre-imported my_utils module)\n    acc_world, acc_motion, velocity, position = my_utils.process_imu_data(\n        acc_data=acc_data,\n        quaternions=q,\n        timestamps=tdata\n    )\n\n    # Convert computed outputs into DataFrames\n    acc_world_df = pd.DataFrame(acc_world, columns=['acc_world_x', 'acc_world_y', 'acc_world_z'])\n    acc_motion_df = pd.DataFrame(acc_motion, columns=['acc_motion_x', 'acc_motion_y', 'acc_motion_z'])\n    velocity_df = pd.DataFrame(velocity, columns=['velocity_x', 'velocity_y', 'velocity_z'])\n    position_df = pd.DataFrame(position, columns=['position_x', 'position_y', 'position_z'])\n\n    # Combine everything into a single IMU dataset\n    imu_merged_subset = pd.concat([\n        sequence_cat_subset[['sequence_counter']].reset_index(drop=True),\n        sequence_acc_subset.reset_index(drop=True),\n        checked_rot_subset.reset_index(drop=True),\n        acc_world_df,\n        acc_motion_df,\n        velocity_df,\n        position_df\n    ], axis=1)\n\n    # orientation invariant df\n    imu_merged_df = my_utils.compute_orientation_invariant_features(imu_merged_subset)\n\n    return imu_merged_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.058292Z","iopub.execute_input":"2025-08-29T17:25:19.059194Z","iopub.status.idle":"2025-08-29T17:25:19.073490Z","shell.execute_reply.started":"2025-08-29T17:25:19.059157Z","shell.execute_reply":"2025-08-29T17:25:19.072441Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Backup real my_utils\nreal_my_utils = my_utils\n# Create a mock wrapper that delegates everything else to the original\nclass MockMyUtils:\n    @staticmethod\n    def process_imu_data(acc_data, quaternions, timestamps):\n        # Mock behavior only for process_imu_data\n        return acc_data, acc_data * 0.5, acc_data * 0.1, acc_data * 0.01\n    \n    def __getattr__(self, name):\n        # Delegate all other attribute lookups to real my_utils\n        return getattr(real_my_utils, name)\n\n# Override my_utils for testing\nmy_utils = MockMyUtils()\n\n# Sample data\nsequence_cat_subset = pd.DataFrame({'sequence_counter': np.arange(5)})\nsequence_acc_subset = pd.DataFrame({\n    'acc_x': np.random.rand(5),\n    'acc_y': np.random.rand(5),\n    'acc_z': np.random.rand(5)\n})\nsequence_rot_subset = pd.DataFrame({\n    'rot_x': [0, 0, 0, 0, np.nan],\n    'rot_y': [0, 0, 0, 0, np.nan],\n    'rot_z': [0, 0, 0, 0, np.nan],\n    'rot_w': [1, 1, 1, 1, np.nan]\n})\n\n# Run your test\nresult = build_imu_subset(sequence_cat_subset, sequence_acc_subset, sequence_rot_subset)\nprint(result.head())\n\n# Restore original after test if needed\nmy_utils = real_my_utils","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.074704Z","iopub.execute_input":"2025-08-29T17:25:19.074982Z","iopub.status.idle":"2025-08-29T17:25:19.135173Z","shell.execute_reply.started":"2025-08-29T17:25:19.074962Z","shell.execute_reply":"2025-08-29T17:25:19.133942Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**3. Function to build full IMU+TEMP dataset for prediction**","metadata":{}},{"cell_type":"code","source":"def build_imu_temp_subset(sequence_temp_subset: pd.DataFrame,\n                          imu_merged_subset: pd.DataFrame) -> tuple[bool, pd.DataFrame]:\n    \"\"\"\n    Merges temperature sensor readings with IMU data,\n    handling missing values by substituting predefined defaults.\n\n    Parameters:\n        sequence_temp_subset (pd.DataFrame): Temperature sensor readings.\n        imu_merged_subset (pd.DataFrame): Processed IMU data.\n\n    Returns:\n        Tuple:\n            - all_values_null (bool): True if all rows have completely missing temperature data.\n            - imu_temp_merged_subset (pd.DataFrame): Combined IMU and temperature DataFrame.\n    \"\"\"\n\n    # Default substitution values for each thermometer sensor\n    missing_temp_substitution_values = {\n        'thm_1': 26.982324,\n        'thm_2': 26.354338,\n        'thm_3': 26.956276,\n        'thm_4': 27.742224,\n        'thm_5': 29.500000\n    }\n\n    # Define which columns to check for nulls\n    temp_cols = list(missing_temp_substitution_values.keys())\n    checked_temp_subset = sequence_temp_subset.copy()\n\n    # Determine if all values are NaN across all temperature columns\n    all_values_null = checked_temp_subset[temp_cols].isnull().all(axis=1).all()\n\n    # Only fill missing values in rows with at least one NaN\n    any_null_indices = checked_temp_subset[temp_cols].isnull().any(axis=1)\n    for col, default_val in missing_temp_substitution_values.items():\n        checked_temp_subset.loc[any_null_indices, col] = (\n            checked_temp_subset.loc[any_null_indices, col].fillna(default_val)\n        )\n\n    # Concatenate IMU data with cleaned temperature data\n    imu_temp_merged_subset = pd.concat([\n        imu_merged_subset.reset_index(drop=True),\n        checked_temp_subset.reset_index(drop=True)\n    ], axis=1)\n\n    return all_values_null, imu_temp_merged_subset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.136766Z","iopub.execute_input":"2025-08-29T17:25:19.137046Z","iopub.status.idle":"2025-08-29T17:25:19.144492Z","shell.execute_reply.started":"2025-08-29T17:25:19.137023Z","shell.execute_reply":"2025-08-29T17:25:19.143468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Mock IMU Data ---\nimu_merged_subset = pd.DataFrame({\n    'acc_x': [0.1, 0.2],\n    'acc_y': [0.3, 0.4],\n    'rot_w': [1.0, 1.0]\n})\n\n# --- Temperature Data with Missing Values ---\nsequence_temp_subset = pd.DataFrame({\n    'thm_1': [np.nan, 27.0],\n    'thm_2': [26.3, np.nan],\n    'thm_3': [np.nan, 26.9],\n    'thm_4': [np.nan, np.nan],\n    'thm_5': [29.5, np.nan]\n})\n\n# Call the function\nall_null, imu_temp_merged = build_imu_temp_subset(sequence_temp_subset, imu_merged_subset)\n\n# Show result\nprint(\"All values null in temperature input:\", all_null)\nprint(\"Merged DataFrame:\")\nprint(imu_temp_merged)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.145662Z","iopub.execute_input":"2025-08-29T17:25:19.145958Z","iopub.status.idle":"2025-08-29T17:25:19.177279Z","shell.execute_reply.started":"2025-08-29T17:25:19.145933Z","shell.execute_reply":"2025-08-29T17:25:19.176268Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**4. Function to predict with only IMU dataset**","metadata":{}},{"cell_type":"code","source":"#load imu only trained model: Gesture\nimport torch\nimport torch.nn as nn\n# ----------------------------\n# ✅ Load best training params\n# ----------------------------\n# Load the trained models from a pickle file located in the predictions folder\nfolder = '/kaggle/input/cmi-gesture-classification-gru-models/pytorch/imu-data/15/'\nimu_trained_models = my_utils.handle_pickle_dict(\n        folder=folder,\n        pickle_filename='imu_trained_models.pkl'\n    )\n\n# ----------------------------\n# ✅ Load best training params for gesture\n# ----------------------------\nbest_params = imu_trained_models['gesture']['best_params']\ninput_dim = imu_trained_models['gesture']['input_dim']\noutput_dim = imu_trained_models['gesture']['output_dim']\n\n# ----------------------------\n# ✅ Recreate model architecture for gesture\n# ----------------------------\ngesture_model = gru_utils.GRUMultiClassModel(\n    input_size=input_dim,\n    hidden_size=best_params['hidden_size'],\n    output_size=output_dim,\n    num_layers=best_params['num_layers'],\n    dropout=best_params['dropout'],\n    bidirectional=best_params['bidirectional']\n)\n\n# ----------------------------\n# 🔁 Load model weights safely (GPU/CPU compatible)\n# ----------------------------\ngesture_model_weights = Path(folder) / Path(imu_trained_models['gesture']['model_name']) \ngesture_adapter_state = Path(folder) / Path(imu_trained_models['gesture']['model_adapter']) \n\nassert gesture_model_weights.exists(), f\"❌ Model file not found: {gesture_model_weights}\"\nassert gesture_adapter_state.exists(), f\"❌ Model file not found: {gesture_adapter_state}\"\n# Detect device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"🖥️ Loading model on: {device}\")\n\n# Load with correct map_location\ngesture_model.load_state_dict(torch.load(gesture_model_weights, map_location=device))\nadapter_state = torch.load(gesture_adapter_state, map_location=device)\n\n# --- 2) Rebuild the adapter wrapper exactly as during adapter training ---\nmodel_adapter = gru_utils.make_adapter_from_pretrained(\n    pretrained_backbone=gesture_model,\n    num_classes=output_dim,   # same out_features as the original head\n    bottleneck_dim=32,\n    bn_momentum=0.05,\n    lora_rank=8,\n    lora_alpha=8.0,\n    p_drop=0.3\n)\n# --- 3) Load the adapter weights into the wrapper ---\nmodel_adapter.load_state_dict(adapter_state)  # loads SafeBN, Bottleneck, and LoRA weights","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.178468Z","iopub.execute_input":"2025-08-29T17:25:19.179425Z","iopub.status.idle":"2025-08-29T17:25:19.246873Z","shell.execute_reply.started":"2025-08-29T17:25:19.179357Z","shell.execute_reply":"2025-08-29T17:25:19.245716Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef gru_model_prediction(imu_merged_subset, imu_trained_models, model, key):\n    \n    # Scale features with training scaler\n    scaler = imu_trained_models[key]['scaler']\n    scaled_x_test = scaler.transform(imu_merged_subset.to_numpy())\n    # Sequence lengths per sequence\n    sequence_length = imu_merged_subset[\"sequence_counter\"].max() + 1\n    sequence_lengths = np.array([sequence_length]) \n    # ----------------------------\n    # 📦 Convert to tensors\n    # ----------------------------\n    x_tensor = gru_utils.reshape_to_padded_tensor(scaled_x_test, sequence_lengths) \n    lengths_tensor = torch.tensor(sequence_lengths, dtype=torch.long)\n    # ----------------------------\n    # 🔮 Make predictions\n    # ----------------------------\n    # ----- Gesture classification -----\n    # Set model to eval mode and move to device\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    model.to(device)\n    model.eval()\n\n    with torch.no_grad():\n\n        logits = model(x_tensor, lengths_tensor, return_logits=True) # shape (B, C)\n        probs = torch.softmax(logits, dim=1)\n        probs_list = probs.squeeze().tolist()\n        # shape (B,)\n        preds = torch.argmax(probs, dim=1)\n        \n    \n\n    if key == 'gesture':\n        # the gesture model was trained on zero based gesture indexes [0, 1, 2, ...]\n        return preds + 1, probs_list\n\n    else:\n        return preds, probs_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.247846Z","iopub.execute_input":"2025-08-29T17:25:19.248100Z","iopub.status.idle":"2025-08-29T17:25:19.256064Z","shell.execute_reply.started":"2025-08-29T17:25:19.248078Z","shell.execute_reply":"2025-08-29T17:25:19.254817Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef adapter_prediction(imu_merged_subset, imu_trained_models, model_adapter: nn.Module, key: str):\n    \"\"\"\n    Drop-in replacement for your gru_model_prediction() that calls the adapter model.\n    It reuses your scaler and reshaper exactly the same way.\n    \"\"\"\n    # 1) Scale with training scaler\n    scaler = imu_trained_models[key]['scaler']\n    scaled_x_test = scaler.transform(imu_merged_subset.to_numpy())\n\n    # 2) Sequence lengths\n    sequence_length = imu_merged_subset[\"sequence_counter\"].max() + 1\n    sequence_lengths = np.array([sequence_length])\n\n    # 3) Convert to tensors\n    x_tensor = gru_utils.reshape_to_padded_tensor(scaled_x_test, sequence_lengths)\n    lengths_tensor = torch.tensor(sequence_lengths, dtype=torch.long)\n\n    # 4) Predict\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    model_adapter.to(device).eval()\n\n    logits = model_adapter(x_tensor, lengths_tensor, return_logits=True)\n    probs = torch.softmax(logits, dim=1)\n    preds = torch.argmax(probs, dim=1)\n\n    probs_list = probs.squeeze().tolist()\n    return (preds + 1, probs_list) if key == 'gesture' else (preds, probs_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.257304Z","iopub.execute_input":"2025-08-29T17:25:19.257670Z","iopub.status.idle":"2025-08-29T17:25:19.281714Z","shell.execute_reply.started":"2025-08-29T17:25:19.257641Z","shell.execute_reply":"2025-08-29T17:25:19.280455Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def imu_only_prediction(\n    imu_merged_subset, \n):\n    \"\"\"\n    Performs gesture classification using IMU-only data and pre-trained models:\n    \n    Parameters:\n    - imu_merged_subset: pd.DataFrame containing preprocessed IMU features.\n\n    Returns:\n    - prediction: dict with keys ['gesture'] and their predicted labels.\n    \"\"\"\n    # Initialise the prediction dictionary\n    predictions = {\n        'gesture': [],\n    }\n\n    #load imu only trained model\n    global imu_trained_models\n    global gesture_model\n    global model_adapter\n\n    # ----------------------------\n    # 🔮 Make predictions\n    # ----------------------------\n    new_test_df = imu_merged_subset.copy()\n    # # ----- Gesture classification -----\n    # predictions['gesture'], gesture_probs = gru_model_prediction(new_test_df, imu_trained_models, model=gesture_model, key='gesture')\n    predictions['gesture'], gesture_probs = adapter_prediction(imu_merged_subset, imu_trained_models, model_adapter, key='gesture')\n\n    return predictions, gesture_probs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.282834Z","iopub.execute_input":"2025-08-29T17:25:19.283222Z","iopub.status.idle":"2025-08-29T17:25:19.305050Z","shell.execute_reply.started":"2025-08-29T17:25:19.283192Z","shell.execute_reply":"2025-08-29T17:25:19.303679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# load gesture types dictionaries:\nfolder = '/kaggle/usr/lib/cmi_detect_gesture_with_a_wrist_worm_device/competition-data/process-data'\nloaded_gesture_type_dictionary = my_utils.handle_pickle_dict(folder=folder, pickle_filename='gesture_type_dictionary.pkl')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.306349Z","iopub.execute_input":"2025-08-29T17:25:19.306678Z","iopub.status.idle":"2025-08-29T17:25:19.329017Z","shell.execute_reply.started":"2025-08-29T17:25:19.306650Z","shell.execute_reply":"2025-08-29T17:25:19.327799Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def map_probs_to_dataframe(gesture_type_dict: dict, probs: list) -> pd.DataFrame:\n    \"\"\"\n    Maps a list of probabilities to the corresponding gesture dictionary.\n\n    Parameters:\n        gesture_type_dict (dict): Dictionary containing gesture mapping.\n                                  e.g., loaded_gesture_type_dictionary['gesture_type']\n        probs (list): List of probabilities (same length and order as the values in the dictionary).\n\n    Returns:\n        pd.DataFrame: Sorted DataFrame with columns ['key', 'value', 'probability'].\n    \"\"\"\n\n    # Sanity check\n    \n    if len(gesture_type_dict) != len(probs):\n        raise ValueError(\"Length of probabilities list does not match dictionary length.\")\n\n    # Convert dict to list of tuples\n    keys = list(gesture_type_dict.keys())\n    values = list(gesture_type_dict.values())\n\n    # Create dataframe\n    df = pd.DataFrame({\n        'key': keys,\n        'value': values,\n        'probability': probs\n    })\n\n    # Sort by probability (ascending)\n    #df = df.sort_values(by='probability', ascending=True).reset_index(drop=True)\n\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.330446Z","iopub.execute_input":"2025-08-29T17:25:19.330803Z","iopub.status.idle":"2025-08-29T17:25:19.343994Z","shell.execute_reply.started":"2025-08-29T17:25:19.330773Z","shell.execute_reply":"2025-08-29T17:25:19.342699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def merge_probability_dataframes(df_base: pd.DataFrame, df_add: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Adds probability values from df_add to df_base based on matching 'key'.\n\n    Parameters:\n        df_base (pd.DataFrame): The base dataframe (always 18 rows) with columns ['key', 'value', 'probability'].\n        df_add (pd.DataFrame): The variable-length dataframe with same columns.\n\n    Returns:\n        pd.DataFrame: Updated df_base with summed probabilities.\n    \"\"\"\n\n    # Merge on 'key' to align rows\n    merged_df = df_base.merge(df_add[['key', 'probability']], on='key', how='left', suffixes=('', '_add'))\n\n    # Fill missing probabilities with 0 (in case df_add has fewer keys)\n    merged_df['probability_add'] = merged_df['probability_add'].fillna(0)\n\n    # Sum original and added probabilities\n    merged_df['probability'] = merged_df['probability'] + merged_df['probability_add']\n\n    # Drop the temporary column\n    merged_df = merged_df.drop(columns=['probability_add'])\n\n    return merged_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.345607Z","iopub.execute_input":"2025-08-29T17:25:19.345960Z","iopub.status.idle":"2025-08-29T17:25:19.366120Z","shell.execute_reply.started":"2025-08-29T17:25:19.345936Z","shell.execute_reply":"2025-08-29T17:25:19.364880Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def gru_eval_predictions(gesture_probs):\n    \"\"\"\n    Evaluates GRU output probabilities and generates mapped probability dataframes.\n\n    Parameters:\n        gesture_probs (list): List of 18 probabilities for the main gesture classifier.\n\n    Returns:\n        - If only `gesture_probs` is provided:\n            gesture_probs_df\n    \"\"\"\n\n    # Use global gesture type dictionaries\n    global loaded_gesture_type_dictionary\n\n    # === Case 1: Only gesture_probs provided ===\n    \n    gesture_probs_df = map_probs_to_dataframe(\n            loaded_gesture_type_dictionary['gesture_type'],\n            gesture_probs\n        )\n    \n    return gesture_probs_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.367205Z","iopub.execute_input":"2025-08-29T17:25:19.367569Z","iopub.status.idle":"2025-08-29T17:25:19.386680Z","shell.execute_reply.started":"2025-08-29T17:25:19.367543Z","shell.execute_reply":"2025-08-29T17:25:19.385468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def sequence_sampling_300(df_seq: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Reduce a long sequence to exactly 300 observations using a structured 3-part sampling strategy:\n\n    Args:\n        df_seq (pd.DataFrame): A mini dataframe for a single sequence_id\n\n    Returns:\n        pd.DataFrame: Sampled dataframe with exactly 300 rows and a reset `sequence_counter`\n    \"\"\"\n    # Step 1: Ensure data is sorted by time\n    df_seq = df_seq.sort_values(by=\"sequence_counter\").reset_index(drop=True)\n    seq_len = len(df_seq)\n    \n    # Step 2: Define key index boundaries\n    start_index = 0\n    last_index = 300\n\n    \n    if  seq_len < 300: \n        return df_seq\n    else:\n        return df_seq.iloc[start_index:last_index]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.387907Z","iopub.execute_input":"2025-08-29T17:25:19.388429Z","iopub.status.idle":"2025-08-29T17:25:19.402695Z","shell.execute_reply.started":"2025-08-29T17:25:19.388339Z","shell.execute_reply":"2025-08-29T17:25:19.401287Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Prediction with inference server**","metadata":{}},{"cell_type":"code","source":"import kaggle_evaluation.cmi_inference_server\n\ncounter = 0\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    Main prediction entry point for Kaggle evaluation.\n\n    Args:\n        sequence (pl.DataFrame): Sensor data for a single sample (acc, rot, temp, etc.)\n        demographics (pl.DataFrame): Subject metadata (not used currently)\n\n    Returns:\n        str: Predicted gesture label (for leaderboard submission interface)\n    \"\"\"\n    global counter\n\n    # Convert to pandas\n    sequence_pd = sequence.to_pandas()\n    # sequence_pd = sequence_sampling_300(sequence_pd)\n    demographics_pd = demographics.to_pandas()  # Currently unused\n    sequence_counter_length = len(sequence_pd)\n    # print(sequence_counter_length)\n      \n    # === Step 1: Subset sensor columns ===\n    CAT_COLUMNS = ['row_id', 'sequence_id', 'sequence_counter', 'subject']\n    IMU_ACC_COLUMNS = ['acc_x', 'acc_y', 'acc_z']\n    IMU_ROT_COLUMNS = ['rot_w', 'rot_x', 'rot_y', 'rot_z']\n    TEMP_COLUMNS = [f\"thm_{i}\" for i in range(1, 6)]\n\n    sequence_cat_subset = sequence_pd[CAT_COLUMNS]\n    sequence_acc_subset = sequence_pd[IMU_ACC_COLUMNS]\n    sequence_rot_subset = sequence_pd[IMU_ROT_COLUMNS]\n    sequence_rot_subset = move_column_to_end(sequence_rot_subset, 'rot_w')  # Ensure rot_w last\n    sequence_temp_subset = sequence_pd[TEMP_COLUMNS]\n\n    # === Step 2: Process IMU and temperature data ===\n    imu_merged_subset = build_imu_subset(sequence_cat_subset, sequence_acc_subset, sequence_rot_subset)\n    # invariant orientation:\n    _, imu_temp_merged_subset = build_imu_temp_subset(sequence_temp_subset, imu_merged_subset)\n    \n    \n    # # === Step 3: IMU-only predictions ===\n    for step in tqdm(range(1), desc=\"IMU Model Prediction\"):\n        \n        predictions, gesture_probs = imu_only_prediction(imu_merged_subset)\n        # Evaluate predictions:\n        gesture_df = gru_eval_predictions(gesture_probs)\n        # print(gesture_df)\n        gesture_most_probable = gesture_df.loc[gesture_df['probability'].idxmax(), 'key']\n\n    # === Step 5: Optional debug print every 50 calls ===\n    if counter % 50 == 0:\n        row_id = sequence_cat_subset['row_id'].iloc[0]\n        subject = sequence_cat_subset['subject'].iloc[0]\n        print(f\"\\nPREDICTION #{counter}\")\n        print(f\"📌 SUBJECT: {subject}   |   🆔 Row ID: {row_id}\")\n        print(\"──────────────────────────────────────\")\n        print(f\"Gesture Prediction:\\n  👉 Base Gesture Model = {gesture_most_probable}\")\n        print(\"──────────────────────────────────────\\n\")\n\n    counter += 1\n    # print(gesture_most_probable)\n    return gesture_most_probable   ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.404185Z","iopub.execute_input":"2025-08-29T17:25:19.404579Z","iopub.status.idle":"2025-08-29T17:25:19.500636Z","shell.execute_reply.started":"2025-08-29T17:25:19.404553Z","shell.execute_reply":"2025-08-29T17:25:19.499589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create mock sequence with minimal required structure\nmock_sequence = pl.DataFrame({\n    'row_id': [1],\n    'sequence_id': [100],\n    'sequence_counter': [0.0],\n    'subject': [42],\n    'acc_x': [0.1], 'acc_y': [0.2], 'acc_z': [0.3],\n    'rot_x': [0.0], 'rot_y': [0.0], 'rot_z': [0.0], 'rot_w': [1.0],\n    'thm_1': [np.nan], 'thm_2': [np.nan], 'thm_3': [np.nan], 'thm_4': [np.nan], 'thm_5': [np.nan]\n})\nmock_demo = pl.DataFrame({'subject': [42]})\n\n# Required helpers must be defined: move_column_to_end, build_imu_subset, build_imu_temp_subset,\n# imu_only_prediction, imu_temp_prediction\n\n# Call predict\ngesture_prediction = predict(mock_sequence, mock_demo)\n\nprint(\"Returned prediction:\", gesture_prediction)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.501610Z","iopub.execute_input":"2025-08-29T17:25:19.502056Z","iopub.status.idle":"2025-08-29T17:25:19.581886Z","shell.execute_reply.started":"2025-08-29T17:25:19.502034Z","shell.execute_reply":"2025-08-29T17:25:19.580898Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time \ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\n    \nelse:\n    \n    inference_server.run_local_gateway(\n        data_paths=(\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',\n            #data_folders_dictionary['process_data'] / Path('test.csv'),\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n            #data_folders_dictionary['process_data'] / Path('test_demographics.csv'),\n        ))\nprint()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T17:25:19.586739Z","iopub.execute_input":"2025-08-29T17:25:19.587096Z","iopub.status.idle":"2025-08-29T17:25:21.192144Z","shell.execute_reply.started":"2025-08-29T17:25:19.587070Z","shell.execute_reply":"2025-08-29T17:25:21.190551Z"}},"outputs":[],"execution_count":null}]}