{"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":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":255504528,"sourceType":"kernelVersion"},{"sourceId":259076387,"sourceType":"kernelVersion"},{"sourceId":555000,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":422576,"modelId":432591}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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-09-01T15:42:28.795007Z","iopub.execute_input":"2025-09-01T15:42:28.795677Z","iopub.status.idle":"2025-09-01T15:42:29.153135Z","shell.execute_reply.started":"2025-09-01T15:42:28.795649Z","shell.execute_reply":"2025-09-01T15:42:29.152558Z"}},"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-09-01T15:42:29.154198Z","iopub.execute_input":"2025-09-01T15:42:29.154524Z","iopub.status.idle":"2025-09-01T15:42:29.214784Z","shell.execute_reply.started":"2025-09-01T15:42:29.154505Z","shell.execute_reply":"2025-09-01T15:42:29.214082Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import kagglehub\n# from kagglehub import KaggleDatasetAdapter","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:29.215513Z","iopub.execute_input":"2025-09-01T15:42:29.215798Z","iopub.status.idle":"2025-09-01T15:42:29.218877Z","shell.execute_reply.started":"2025-09-01T15:42:29.215782Z","shell.execute_reply":"2025-09-01T15:42:29.218120Z"}},"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-09-01T15:42:29.220191Z","iopub.execute_input":"2025-09-01T15:42:29.220409Z","iopub.status.idle":"2025-09-01T15:42:29.234816Z","shell.execute_reply.started":"2025-09-01T15:42:29.220394Z","shell.execute_reply":"2025-09-01T15:42:29.234187Z"}},"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-09-01T15:42:29.235544Z","iopub.execute_input":"2025-09-01T15:42:29.235789Z","iopub.status.idle":"2025-09-01T15:42:29.800475Z","shell.execute_reply.started":"2025-09-01T15:42:29.235766Z","shell.execute_reply":"2025-09-01T15:42:29.799939Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"filedir = '/kaggle/input/cmi-gesture-classification-gru-models/pytorch/imu-temp-tof-data/3/gru_model_module.py'\nmodule_name = \"gru_utils\"\ngru_utils = load_my_utils(filedir, module_name)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:29.801201Z","iopub.execute_input":"2025-09-01T15:42:29.801567Z","iopub.status.idle":"2025-09-01T15:42:31.513327Z","shell.execute_reply.started":"2025-09-01T15:42:29.801537Z","shell.execute_reply":"2025-09-01T15:42:31.512755Z"}},"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-09-01T15:42:31.514060Z","iopub.execute_input":"2025-09-01T15:42:31.514470Z","iopub.status.idle":"2025-09-01T15:42:31.519577Z","shell.execute_reply.started":"2025-09-01T15:42:31.514450Z","shell.execute_reply":"2025-09-01T15:42:31.518860Z"}},"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-09-01T15:42:31.520364Z","iopub.execute_input":"2025-09-01T15:42:31.520592Z","iopub.status.idle":"2025-09-01T15:42:31.534280Z","shell.execute_reply.started":"2025-09-01T15:42:31.520573Z","shell.execute_reply":"2025-09-01T15:42:31.533636Z"}},"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-09-01T15:42:31.534826Z","iopub.execute_input":"2025-09-01T15:42:31.535010Z","iopub.status.idle":"2025-09-01T15:42:31.550649Z","shell.execute_reply.started":"2025-09-01T15:42:31.534996Z","shell.execute_reply":"2025-09-01T15:42:31.549952Z"}},"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-09-01T15:42:31.553146Z","iopub.execute_input":"2025-09-01T15:42:31.553330Z","iopub.status.idle":"2025-09-01T15:42:31.564278Z","shell.execute_reply.started":"2025-09-01T15:42:31.553317Z","shell.execute_reply":"2025-09-01T15:42:31.563555Z"}},"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-09-01T15:42:31.565067Z","iopub.execute_input":"2025-09-01T15:42:31.565259Z","iopub.status.idle":"2025-09-01T15:42:31.591727Z","shell.execute_reply.started":"2025-09-01T15:42:31.565236Z","shell.execute_reply":"2025-09-01T15:42:31.591027Z"}},"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-09-01T15:42:31.592423Z","iopub.execute_input":"2025-09-01T15:42:31.592594Z","iopub.status.idle":"2025-09-01T15:42:31.596143Z","shell.execute_reply.started":"2025-09-01T15:42:31.592580Z","shell.execute_reply":"2025-09-01T15:42:31.595431Z"}},"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-09-01T15:42:31.596845Z","iopub.execute_input":"2025-09-01T15:42:31.597064Z","iopub.status.idle":"2025-09-01T15:42:31.610836Z","shell.execute_reply.started":"2025-09-01T15:42:31.597039Z","shell.execute_reply":"2025-09-01T15:42:31.610228Z"}},"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-09-01T15:42:31.611521Z","iopub.execute_input":"2025-09-01T15:42:31.611737Z","iopub.status.idle":"2025-09-01T15:42:31.657837Z","shell.execute_reply.started":"2025-09-01T15:42:31.611697Z","shell.execute_reply":"2025-09-01T15:42:31.656981Z"}},"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-09-01T15:42:31.658654Z","iopub.execute_input":"2025-09-01T15:42:31.659276Z","iopub.status.idle":"2025-09-01T15:42:31.665238Z","shell.execute_reply.started":"2025-09-01T15:42:31.659255Z","shell.execute_reply":"2025-09-01T15:42:31.664514Z"}},"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-09-01T15:42:31.665949Z","iopub.execute_input":"2025-09-01T15:42:31.666153Z","iopub.status.idle":"2025-09-01T15:42:31.691674Z","shell.execute_reply.started":"2025-09-01T15:42:31.666138Z","shell.execute_reply":"2025-09-01T15:42:31.690986Z"}},"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-temp-tof-data/3/'\n\nimu_trained_models = my_utils.handle_pickle_dict(\n        folder=folder,\n        pickle_filename='imu_trained_models.pkl'\n    )\n\nimu_temp_tof_trained_models = my_utils.handle_pickle_dict(\n        folder=folder,\n        pickle_filename='imu_temp_tof_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    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\n\n\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\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-09-01T15:42:31.692428Z","iopub.execute_input":"2025-09-01T15:42:31.692659Z","iopub.status.idle":"2025-09-01T15:42:31.896751Z","shell.execute_reply.started":"2025-09-01T15:42:31.692638Z","shell.execute_reply":"2025-09-01T15:42:31.896199Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------------\n# ✅ Load best training params for gesture\n# ----------------------------\nbest_params = imu_temp_tof_trained_models['gesture']['best_params']\ninput_dim = imu_temp_tof_trained_models['gesture']['input_dim']\noutput_dim = imu_temp_tof_trained_models['gesture']['output_dim']\n\n# ----------------------------\n# ✅ Recreate model architecture for gesture\n# ----------------------------\ngesture_model_temp = 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# + TEMP \ngesture_temp_model_weights = Path(folder) / Path(imu_temp_tof_trained_models['gesture']['model_name']) \ngesture_temp_adapter_state = Path(folder) / Path(imu_temp_tof_trained_models['gesture']['model_adapter']) \n\nassert gesture_temp_model_weights.exists(), f\"❌ Model file not found: {gesture_temp_model_weights}\"\nassert gesture_temp_adapter_state.exists(), f\"❌ Model file not found: {gesture_temp_adapter_state}\"\n\n# + TEMP \ngesture_model_temp.load_state_dict(torch.load(gesture_temp_model_weights, map_location=device))\nadapter_temp_state = torch.load(gesture_temp_adapter_state, map_location=device)\n\n# + TEMP \n# --- 2) Rebuild the adapter wrapper exactly as during adapter training ---\nmodel_temp_adapter = gru_utils.make_adapter_from_pretrained(\n    pretrained_backbone=gesture_model_temp,\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_temp_adapter.load_state_dict(adapter_temp_state)  # loads SafeBN, Bottleneck, and LoRA weights","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:31.897419Z","iopub.execute_input":"2025-09-01T15:42:31.897638Z","iopub.status.idle":"2025-09-01T15:42:31.961485Z","shell.execute_reply.started":"2025-09-01T15:42:31.897622Z","shell.execute_reply":"2025-09-01T15:42:31.961006Z"}},"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    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-09-01T15:42:31.962157Z","iopub.execute_input":"2025-09-01T15:42:31.962393Z","iopub.status.idle":"2025-09-01T15:42:31.968133Z","shell.execute_reply.started":"2025-09-01T15:42:31.962370Z","shell.execute_reply":"2025-09-01T15:42:31.967405Z"}},"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-09-01T15:42:31.968764Z","iopub.execute_input":"2025-09-01T15:42:31.968920Z","iopub.status.idle":"2025-09-01T15:42:31.987300Z","shell.execute_reply.started":"2025-09-01T15:42:31.968908Z","shell.execute_reply":"2025-09-01T15:42:31.986734Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Temperature scaling IMU validation dataset**","metadata":{}},{"cell_type":"code","source":"# calibration_100_670 = {\n#     'imu': {},\n#     'imu_temp_tof': {}\n# }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:31.987934Z","iopub.execute_input":"2025-09-01T15:42:31.988189Z","iopub.status.idle":"2025-09-01T15:42:32.007798Z","shell.execute_reply.started":"2025-09-01T15:42:31.988157Z","shell.execute_reply":"2025-09-01T15:42:32.007285Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_df = kagglehub.dataset_load(KaggleDatasetAdapter.PANDAS,\"pira245/cmi-dataset\",\"full_rot_imu_merged_v3_df.csv\",)\n# imu_df = imu_df.drop(['row_id.1' , 'sequence_counter.1'], axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.008456Z","iopub.execute_input":"2025-09-01T15:42:32.008635Z","iopub.status.idle":"2025-09-01T15:42:32.025302Z","shell.execute_reply.started":"2025-09-01T15:42:32.008621Z","shell.execute_reply":"2025-09-01T15:42:32.024758Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_columns = ['gesture']\n# n_splits = 5 # 80% train / 20% val\n# split_index  = 0\n# df = imu_df.drop(['row_id'], axis=1)\n# imu_balanced_datasets = my_utils.prepare_balanced_gesture_datasets(df, gesture_columns,n_splits, split_index)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.026251Z","iopub.execute_input":"2025-09-01T15:42:32.026497Z","iopub.status.idle":"2025-09-01T15:42:32.042045Z","shell.execute_reply.started":"2025-09-01T15:42:32.026477Z","shell.execute_reply":"2025-09-01T15:42:32.041319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = imu_balanced_datasets['gesture']['train']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.042677Z","iopub.execute_input":"2025-09-01T15:42:32.042943Z","iopub.status.idle":"2025-09-01T15:42:32.058196Z","shell.execute_reply.started":"2025-09-01T15:42:32.042921Z","shell.execute_reply":"2025-09-01T15:42:32.057468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # main_gesture_df = gesture_val.copy()\n# main_gesture_df = imu_df.drop(['row_id'], axis=1).copy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.058999Z","iopub.execute_input":"2025-09-01T15:42:32.059202Z","iopub.status.idle":"2025-09-01T15:42:32.071876Z","shell.execute_reply.started":"2025-09-01T15:42:32.059183Z","shell.execute_reply":"2025-09-01T15:42:32.071231Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # 🔧 Set the threshold for what you consider a \"long\" sequence\n# sequence_length_threshold = 100  # Default value (can be changed as needed)\n\n# # Step 1: Compute sequence-level statistics for 'sequence_counter'\n# seq_stats = main_gesture_df.groupby(\"sequence_id\")[\"sequence_counter\"].agg([\"min\", \"max\", \"count\"])\n\n# # Step 2: Identify sequence_ids where length exceeds the threshold\n# long_sequence_ids = seq_stats[seq_stats[\"count\"] > sequence_length_threshold].index\n\n# # Step 3: Filter the DataFrame to rows from long sequences\n# long_sequences_df = main_gesture_df[main_gesture_df[\"sequence_id\"].isin(long_sequence_ids)]\n\n# # Step 4: Count gestures associated with long sequences (based on first label per sequence)\n# long_gesture_counts = (\n#     long_sequences_df\n#     .groupby(\"sequence_id\")[\"gesture\"]\n#     .first()\n#     .value_counts()\n#     .reset_index(name=\"count\")\n#     .rename(columns={\"index\": \"gesture\"})\n# )\n\n# # Step 5: Print the result\n# print(f\"🎯 Gestures with sequences longer than {sequence_length_threshold} timesteps:\")\n# print(long_gesture_counts)\n\n# # Step 6: Store the long sequence_ids for future removal\n# long_sequence_ids_list = list(long_sequence_ids)\n# print(f\"\\n🧹 Sequence IDs to consider removing ({len(long_sequence_ids_list)} total):\")\n# print(long_sequence_ids_list[:10], \"...\")  # Show sample\n\n# # Step 7: Optionally remove long sequences from the original dataframe\n# main_gesture_filtered_df = main_gesture_df[~main_gesture_df[\"sequence_id\"].isin(long_sequence_ids)]\n# special_case_df = main_gesture_df[main_gesture_df[\"sequence_id\"].isin(long_sequence_ids)]\n\n# # Step 8: Confirm shape of the filtered dataset\n# print(f\"\\n✅ New DataFrame shape after removal: {main_gesture_filtered_df.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.072518Z","iopub.execute_input":"2025-09-01T15:42:32.072766Z","iopub.status.idle":"2025-09-01T15:42:32.087572Z","shell.execute_reply.started":"2025-09-01T15:42:32.072746Z","shell.execute_reply":"2025-09-01T15:42:32.086982Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Sequence statistics (min, max, length of each sequence)\n# seq_stats = main_gesture_filtered_df.groupby(\"sequence_id\")[\"sequence_counter\"].agg([\"min\", \"max\", \"count\"])\n# num_sequences = main_gesture_filtered_df[\"sequence_id\"].nunique()\n# num_sequences, seq_stats.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.088202Z","iopub.execute_input":"2025-09-01T15:42:32.088397Z","iopub.status.idle":"2025-09-01T15:42:32.106338Z","shell.execute_reply.started":"2025-09-01T15:42:32.088376Z","shell.execute_reply":"2025-09-01T15:42:32.105660Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Sequence statistics (min, max, length of each sequence)\n# seq_stats = special_case_df.groupby(\"sequence_id\")[\"sequence_counter\"].agg([\"min\", \"max\", \"count\"])\n# num_sequences = special_case_df[\"sequence_id\"].nunique()\n# num_sequences, seq_stats.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.111270Z","iopub.execute_input":"2025-09-01T15:42:32.111490Z","iopub.status.idle":"2025-09-01T15:42:32.120965Z","shell.execute_reply.started":"2025-09-01T15:42:32.111464Z","shell.execute_reply":"2025-09-01T15:42:32.120287Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = main_gesture_filtered_df.copy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.121629Z","iopub.execute_input":"2025-09-01T15:42:32.121834Z","iopub.status.idle":"2025-09-01T15:42:32.139737Z","shell.execute_reply.started":"2025-09-01T15:42:32.121820Z","shell.execute_reply":"2025-09-01T15:42:32.139196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from sklearn.metrics import classification_report, f1_score, confusion_matrix\n\n# # ----------------------------\n# # 📊 Prepare features\n# # ----------------------------\n# x_test_gesture = gesture_val.drop([\n#     'sequence_type', 'sequence_id', 'subject', 'orientation',\n#     'behavior', 'phase', 'gesture', 'bfrb_gesture', 'nbfrb_gesture'\n# ], axis=1).to_numpy()\n\n# # One label per sequence (likely 1-based: 1..18)\n# gesture_seq_labels = gesture_val.groupby(\"sequence_id\")[\"gesture\"].first().values\n# print(f\"🧩 # Test Sequences: {len(gesture_seq_labels)}\")\n# zero_based_labels = np.array(gesture_seq_labels) - 1\n\n\n# # Scale with training scaler\n# scaler = imu_trained_models['gesture']['scaler']\n# scaled_x_test_gesture = scaler.transform(x_test_gesture)\n\n# # Sequence lengths per sequence\n# sequence_lengths_df = gesture_val.groupby(\"sequence_id\")[\"sequence_counter\"].max() + 1\n# sequence_lengths_gesture = sequence_lengths_df.to_numpy()\n\n# # ----------------------------\n# # 📦 Convert to tensors\n# # ----------------------------\n# x_tensor = gru_utils.reshape_to_padded_tensor(scaled_x_test_gesture, sequence_lengths_gesture)  # (B, T, F)\n# lengths_tensor = torch.tensor(sequence_lengths_gesture, dtype=torch.long)\n# y_val_zero_based = torch.tensor(zero_based_labels, dtype=torch.long)\n\n# # Ensure same device for inputs and models\n# # device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# x_tensor = x_tensor.to(device)\n# lengths_tensor = lengths_tensor.to(device)\n# y_val_zero_based = y_val_zero_based.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.140378Z","iopub.execute_input":"2025-09-01T15:42:32.140569Z","iopub.status.idle":"2025-09-01T15:42:32.154766Z","shell.execute_reply.started":"2025-09-01T15:42:32.140554Z","shell.execute_reply":"2025-09-01T15:42:32.154241Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_base_T, imu_adpt_T, imu_p_base_val, imu_p_adpt_val = gru_utils.calibrate_two_models(\n#                         gesture_model,\n#                          model_adapter,\n#                          x_tensor,          # (N, T, F) CPU\n#                          lengths_tensor,        # (N,) CPU\n#                          y_val_zero_based,   # (N,) 0..C-1\n#                          device,\n#                          batch_size = 128,\n#                          amp = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.155508Z","iopub.execute_input":"2025-09-01T15:42:32.155686Z","iopub.status.idle":"2025-09-01T15:42:32.169320Z","shell.execute_reply.started":"2025-09-01T15:42:32.155673Z","shell.execute_reply":"2025-09-01T15:42:32.168756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# calibration_100_670['imu'].setdefault(100, {'model': [imu_base_T, imu_p_base_val], 'adapter': [imu_adpt_T, imu_p_adpt_val]})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.170018Z","iopub.execute_input":"2025-09-01T15:42:32.170287Z","iopub.status.idle":"2025-09-01T15:42:32.188883Z","shell.execute_reply.started":"2025-09-01T15:42:32.170266Z","shell.execute_reply":"2025-09-01T15:42:32.188270Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_w_base_history =  gru_utils.search_soft_voting_weight(\n#     calibration_100_670['imu'][100]['model'][1],               # np.ndarray or torch.Tensor, shape (N, C)\n#     calibration_100_670['imu'][100]['adapter'][1],             # same shape (N, C)\n#     y_val_zero_based.cpu(),         # array-like labels, shape (N,), 0..C-1\n#     weight_grid=None,          # defaults np.linspace(0,1,21)\n#     max_epochs=None,           # defaults len(weight_grid)\n#     verbose=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.189557Z","iopub.execute_input":"2025-09-01T15:42:32.189771Z","iopub.status.idle":"2025-09-01T15:42:32.204972Z","shell.execute_reply.started":"2025-09-01T15:42:32.189756Z","shell.execute_reply":"2025-09-01T15:42:32.204390Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_w_base_100 = imu_w_base_history['best_w']\n# calibration_100_670['imu'][100].setdefault('best_ens_weight', [imu_w_base_100])\n# print(f\"Best weight: {imu_w_base_100}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.205684Z","iopub.execute_input":"2025-09-01T15:42:32.205933Z","iopub.status.idle":"2025-09-01T15:42:32.218960Z","shell.execute_reply.started":"2025-09-01T15:42:32.205913Z","shell.execute_reply":"2025-09-01T15:42:32.218277Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = special_case_df.copy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.219612Z","iopub.execute_input":"2025-09-01T15:42:32.219847Z","iopub.status.idle":"2025-09-01T15:42:32.232851Z","shell.execute_reply.started":"2025-09-01T15:42:32.219828Z","shell.execute_reply":"2025-09-01T15:42:32.232221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # from sklearn.metrics import classification_report, f1_score, confusion_matrix\n\n# # ----------------------------\n# # 📊 Prepare features\n# # ----------------------------\n# x_test_gesture = gesture_val.drop([\n#     'sequence_type', 'sequence_id', 'subject', 'orientation',\n#     'behavior', 'phase', 'gesture', 'bfrb_gesture', 'nbfrb_gesture'\n# ], axis=1).to_numpy()\n\n# # One label per sequence (likely 1-based: 1..18)\n# gesture_seq_labels = gesture_val.groupby(\"sequence_id\")[\"gesture\"].first().values\n# print(f\"🧩 # Test Sequences: {len(gesture_seq_labels)}\")\n# zero_based_labels = np.array(gesture_seq_labels) - 1\n\n\n# # Scale with training scaler\n# scaler = imu_trained_models['gesture']['scaler']\n# scaled_x_test_gesture = scaler.transform(x_test_gesture)\n\n# # Sequence lengths per sequence\n# sequence_lengths_df = gesture_val.groupby(\"sequence_id\")[\"sequence_counter\"].max() + 1\n# sequence_lengths_gesture = sequence_lengths_df.to_numpy()\n\n# # ----------------------------\n# # 📦 Convert to tensors\n# # ----------------------------\n# x_tensor = gru_utils.reshape_to_padded_tensor(scaled_x_test_gesture, sequence_lengths_gesture)  # (B, T, F)\n# lengths_tensor = torch.tensor(sequence_lengths_gesture, dtype=torch.long)\n# y_val_zero_based = torch.tensor(zero_based_labels, dtype=torch.long)\n\n# # Ensure same device for inputs and models\n# # device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# x_tensor = x_tensor.to(device)\n# lengths_tensor = lengths_tensor.to(device)\n# y_val_zero_based = y_val_zero_based.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.233480Z","iopub.execute_input":"2025-09-01T15:42:32.233665Z","iopub.status.idle":"2025-09-01T15:42:32.246492Z","shell.execute_reply.started":"2025-09-01T15:42:32.233651Z","shell.execute_reply":"2025-09-01T15:42:32.245987Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_base_T, imu_adpt_T, imu_p_base_val, imu_p_adpt_val = gru_utils.calibrate_two_models(\n#                         gesture_model,\n#                          model_adapter,\n#                          x_tensor,          # (N, T, F) CPU\n#                          lengths_tensor,        # (N,) CPU\n#                          y_val_zero_based,   # (N,) 0..C-1\n#                          device,\n#                          batch_size = 128,\n#                          amp = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.247146Z","iopub.execute_input":"2025-09-01T15:42:32.247323Z","iopub.status.idle":"2025-09-01T15:42:32.264931Z","shell.execute_reply.started":"2025-09-01T15:42:32.247310Z","shell.execute_reply":"2025-09-01T15:42:32.264324Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# calibration_100_670['imu'].setdefault(670, {'model': [imu_base_T, imu_p_base_val], 'adapter': [imu_adpt_T, imu_p_adpt_val]})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.265495Z","iopub.execute_input":"2025-09-01T15:42:32.265646Z","iopub.status.idle":"2025-09-01T15:42:32.279518Z","shell.execute_reply.started":"2025-09-01T15:42:32.265634Z","shell.execute_reply":"2025-09-01T15:42:32.278900Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_w_base_history =  gru_utils.search_soft_voting_weight(\n#     calibration_100_670['imu'][670]['model'][1],               # np.ndarray or torch.Tensor, shape (N, C)\n#     calibration_100_670['imu'][670]['adapter'][1],             # same shape (N, C)\n#     y_val_zero_based.cpu(),         # array-like labels, shape (N,), 0..C-1\n#     weight_grid=None,          # defaults np.linspace(0,1,21)\n#     max_epochs=None,           # defaults len(weight_grid)\n#     verbose=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.280262Z","iopub.execute_input":"2025-09-01T15:42:32.280482Z","iopub.status.idle":"2025-09-01T15:42:32.298311Z","shell.execute_reply.started":"2025-09-01T15:42:32.280459Z","shell.execute_reply":"2025-09-01T15:42:32.297682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_w_base_670 = imu_w_base_history['best_w']\n# calibration_100_670['imu'][670].setdefault('best_ens_weight', [imu_w_base_670])\n# print(f\"Best weight: {imu_w_base_670}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.298852Z","iopub.execute_input":"2025-09-01T15:42:32.299023Z","iopub.status.idle":"2025-09-01T15:42:32.312631Z","shell.execute_reply.started":"2025-09-01T15:42:32.299010Z","shell.execute_reply":"2025-09-01T15:42:32.312115Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Temperature scaling IMU-TEMP-Tof validation dataset**","metadata":{}},{"cell_type":"code","source":"# imu_temp_tof_df = kagglehub.dataset_load(KaggleDatasetAdapter.PANDAS,\"pira245/cmi-dataset\",\"full_imu_temp_tof_merged_v2_df.csv\",)\n# imu_temp_tof_df = imu_temp_tof_df.drop(['row_id.1' , 'row_id.2', 'row_id.3', 'sequence_counter.1'], axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.313480Z","iopub.execute_input":"2025-09-01T15:42:32.313633Z","iopub.status.idle":"2025-09-01T15:42:32.326473Z","shell.execute_reply.started":"2025-09-01T15:42:32.313621Z","shell.execute_reply":"2025-09-01T15:42:32.325948Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_temp_tof_df.fillna(-1, inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.327135Z","iopub.execute_input":"2025-09-01T15:42:32.327336Z","iopub.status.idle":"2025-09-01T15:42:32.340038Z","shell.execute_reply.started":"2025-09-01T15:42:32.327321Z","shell.execute_reply":"2025-09-01T15:42:32.339368Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_columns = ['gesture']\n# n_splits = 5 # 80% train / 20% val\n# split_index  = 0\n# df = imu_temp_tof_df.drop(['row_id'], axis=1)\n# imu_balanced_datasets = my_utils.prepare_balanced_gesture_datasets(df, gesture_columns,n_splits, split_index)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.340684Z","iopub.execute_input":"2025-09-01T15:42:32.340948Z","iopub.status.idle":"2025-09-01T15:42:32.353498Z","shell.execute_reply.started":"2025-09-01T15:42:32.340923Z","shell.execute_reply":"2025-09-01T15:42:32.352955Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = imu_balanced_datasets['gesture']['train']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.354152Z","iopub.execute_input":"2025-09-01T15:42:32.354387Z","iopub.status.idle":"2025-09-01T15:42:32.367251Z","shell.execute_reply.started":"2025-09-01T15:42:32.354366Z","shell.execute_reply":"2025-09-01T15:42:32.366629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # main_gesture_df = gesture_val.copy()\n# main_gesture_df = imu_temp_tof_df.drop(['row_id'], axis=1).copy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.367847Z","iopub.execute_input":"2025-09-01T15:42:32.368093Z","iopub.status.idle":"2025-09-01T15:42:32.380068Z","shell.execute_reply.started":"2025-09-01T15:42:32.368071Z","shell.execute_reply":"2025-09-01T15:42:32.379540Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # 🔧 Set the threshold for what you consider a \"long\" sequence\n# sequence_length_threshold = 100  # Default value (can be changed as needed)\n\n# # Step 1: Compute sequence-level statistics for 'sequence_counter'\n# seq_stats = main_gesture_df.groupby(\"sequence_id\")[\"sequence_counter\"].agg([\"min\", \"max\", \"count\"])\n\n# # Step 2: Identify sequence_ids where length exceeds the threshold\n# long_sequence_ids = seq_stats[seq_stats[\"count\"] > sequence_length_threshold].index\n\n# # Step 3: Filter the DataFrame to rows from long sequences\n# long_sequences_df = main_gesture_df[main_gesture_df[\"sequence_id\"].isin(long_sequence_ids)]\n\n# # Step 4: Count gestures associated with long sequences (based on first label per sequence)\n# long_gesture_counts = (\n#     long_sequences_df\n#     .groupby(\"sequence_id\")[\"gesture\"]\n#     .first()\n#     .value_counts()\n#     .reset_index(name=\"count\")\n#     .rename(columns={\"index\": \"gesture\"})\n# )\n\n# # Step 5: Print the result\n# print(f\"🎯 Gestures with sequences longer than {sequence_length_threshold} timesteps:\")\n# print(long_gesture_counts)\n\n# # Step 6: Store the long sequence_ids for future removal\n# long_sequence_ids_list = list(long_sequence_ids)\n# print(f\"\\n🧹 Sequence IDs to consider removing ({len(long_sequence_ids_list)} total):\")\n# print(long_sequence_ids_list[:10], \"...\")  # Show sample\n\n# # Step 7: Optionally remove long sequences from the original dataframe\n# main_gesture_filtered_df = main_gesture_df[~main_gesture_df[\"sequence_id\"].isin(long_sequence_ids)]\n# special_case_df = main_gesture_df[main_gesture_df[\"sequence_id\"].isin(long_sequence_ids)]\n\n# # Step 8: Confirm shape of the filtered dataset\n# print(f\"\\n✅ New DataFrame shape after removal: {main_gesture_filtered_df.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.380755Z","iopub.execute_input":"2025-09-01T15:42:32.380984Z","iopub.status.idle":"2025-09-01T15:42:32.393976Z","shell.execute_reply.started":"2025-09-01T15:42:32.380962Z","shell.execute_reply":"2025-09-01T15:42:32.393458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Sequence statistics (min, max, length of each sequence)\n# seq_stats = main_gesture_filtered_df.groupby(\"sequence_id\")[\"sequence_counter\"].agg([\"min\", \"max\", \"count\"])\n# num_sequences = main_gesture_filtered_df[\"sequence_id\"].nunique()\n# num_sequences, seq_stats.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.394615Z","iopub.execute_input":"2025-09-01T15:42:32.394810Z","iopub.status.idle":"2025-09-01T15:42:32.411702Z","shell.execute_reply.started":"2025-09-01T15:42:32.394796Z","shell.execute_reply":"2025-09-01T15:42:32.411180Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Sequence statistics (min, max, length of each sequence)\n# seq_stats = special_case_df.groupby(\"sequence_id\")[\"sequence_counter\"].agg([\"min\", \"max\", \"count\"])\n# num_sequences = special_case_df[\"sequence_id\"].nunique()\n# num_sequences, seq_stats.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.412448Z","iopub.execute_input":"2025-09-01T15:42:32.412623Z","iopub.status.idle":"2025-09-01T15:42:32.426682Z","shell.execute_reply.started":"2025-09-01T15:42:32.412609Z","shell.execute_reply":"2025-09-01T15:42:32.426049Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = main_gesture_filtered_df.copy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.427220Z","iopub.execute_input":"2025-09-01T15:42:32.427434Z","iopub.status.idle":"2025-09-01T15:42:32.443291Z","shell.execute_reply.started":"2025-09-01T15:42:32.427413Z","shell.execute_reply":"2025-09-01T15:42:32.442615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # from sklearn.metrics import classification_report, f1_score, confusion_matrix\n\n# # ----------------------------\n# # 📊 Prepare features\n# # ----------------------------\n# x_test_gesture = gesture_val.drop([\n#     'sequence_type', 'sequence_id', 'subject', 'orientation',\n#     'behavior', 'phase', 'gesture', 'bfrb_gesture', 'nbfrb_gesture'\n# ], axis=1).to_numpy()\n\n# # One label per sequence (likely 1-based: 1..18)\n# gesture_seq_labels = gesture_val.groupby(\"sequence_id\")[\"gesture\"].first().values\n# print(f\"🧩 # Test Sequences: {len(gesture_seq_labels)}\")\n# zero_based_labels = np.array(gesture_seq_labels) - 1\n\n\n# # Scale with training scaler\n# scaler = imu_temp_tof_trained_models['gesture']['scaler']\n# scaled_x_test_gesture = scaler.transform(x_test_gesture)\n\n# # Sequence lengths per sequence\n# sequence_lengths_df = gesture_val.groupby(\"sequence_id\")[\"sequence_counter\"].max() + 1\n# sequence_lengths_gesture = sequence_lengths_df.to_numpy()\n\n# # ----------------------------\n# # 📦 Convert to tensors\n# # ----------------------------\n# x_tensor = gru_utils.reshape_to_padded_tensor(scaled_x_test_gesture, sequence_lengths_gesture)  # (B, T, F)\n# lengths_tensor = torch.tensor(sequence_lengths_gesture, dtype=torch.long)\n# y_val_zero_based = torch.tensor(zero_based_labels, dtype=torch.long)\n\n# # Ensure same device for inputs and models\n# # device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# x_tensor = x_tensor.to(device)\n# lengths_tensor = lengths_tensor.to(device)\n# y_val_zero_based = y_val_zero_based.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.444066Z","iopub.execute_input":"2025-09-01T15:42:32.444299Z","iopub.status.idle":"2025-09-01T15:42:32.455884Z","shell.execute_reply.started":"2025-09-01T15:42:32.444277Z","shell.execute_reply":"2025-09-01T15:42:32.455220Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_temp_base_T, imu_temp_adpt_T, imu_temp_p_base_val, imu_temp_p_adpt_val = gru_utils.calibrate_two_models(\n#                         gesture_model_temp,\n#                          model_temp_adapter,\n#                          x_tensor,          # (N, T, F) CPU\n#                          lengths_tensor,        # (N,) CPU\n#                          y_val_zero_based,   # (N,) 0..C-1\n#                          device,\n#                          batch_size = 128,\n#                          amp = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.456579Z","iopub.execute_input":"2025-09-01T15:42:32.457139Z","iopub.status.idle":"2025-09-01T15:42:32.468335Z","shell.execute_reply.started":"2025-09-01T15:42:32.457117Z","shell.execute_reply":"2025-09-01T15:42:32.467681Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# calibration_100_670['imu_temp_tof'].setdefault(100, {'model': [imu_temp_base_T, imu_temp_p_base_val], 'adapter': [imu_temp_adpt_T, imu_temp_p_adpt_val]})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.469029Z","iopub.execute_input":"2025-09-01T15:42:32.469242Z","iopub.status.idle":"2025-09-01T15:42:32.485464Z","shell.execute_reply.started":"2025-09-01T15:42:32.469228Z","shell.execute_reply":"2025-09-01T15:42:32.484841Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_temp_w_base_history =  gru_utils.search_soft_voting_weight(\n#     calibration_100_670['imu_temp_tof'][100]['model'][1],               # np.ndarray or torch.Tensor, shape (N, C)\n#     calibration_100_670['imu_temp_tof'][100]['adapter'][1],             # same shape (N, C)\n#     y_val_zero_based.cpu(),         # array-like labels, shape (N,), 0..C-1\n#     weight_grid=None,          # defaults np.linspace(0,1,21)\n#     max_epochs=None,           # defaults len(weight_grid)\n#     verbose=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.486068Z","iopub.execute_input":"2025-09-01T15:42:32.486257Z","iopub.status.idle":"2025-09-01T15:42:32.500038Z","shell.execute_reply.started":"2025-09-01T15:42:32.486242Z","shell.execute_reply":"2025-09-01T15:42:32.499341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_temp_w_base_100 = imu_temp_w_base_history['best_w']\n# calibration_100_670['imu_temp_tof'][100].setdefault('best_ens_weight', [imu_temp_w_base_100])\n# print(f\"Best weight: {imu_temp_w_base_100}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.500579Z","iopub.execute_input":"2025-09-01T15:42:32.500775Z","iopub.status.idle":"2025-09-01T15:42:32.512437Z","shell.execute_reply.started":"2025-09-01T15:42:32.500761Z","shell.execute_reply":"2025-09-01T15:42:32.511946Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = special_case_df.copy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.513116Z","iopub.execute_input":"2025-09-01T15:42:32.513292Z","iopub.status.idle":"2025-09-01T15:42:32.526198Z","shell.execute_reply.started":"2025-09-01T15:42:32.513279Z","shell.execute_reply":"2025-09-01T15:42:32.525572Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # from sklearn.metrics import classification_report, f1_score, confusion_matrix\n\n# # ----------------------------\n# # 📊 Prepare features\n# # ----------------------------\n# x_test_gesture = gesture_val.drop([\n#     'sequence_type', 'sequence_id', 'subject', 'orientation',\n#     'behavior', 'phase', 'gesture', 'bfrb_gesture', 'nbfrb_gesture'\n# ], axis=1).to_numpy()\n\n# # One label per sequence (likely 1-based: 1..18)\n# gesture_seq_labels = gesture_val.groupby(\"sequence_id\")[\"gesture\"].first().values\n# print(f\"🧩 # Test Sequences: {len(gesture_seq_labels)}\")\n# zero_based_labels = np.array(gesture_seq_labels) - 1\n\n\n# # Scale with training scaler\n# scaler = imu_temp_tof_trained_models['gesture']['scaler']\n# scaled_x_test_gesture = scaler.transform(x_test_gesture)\n\n# # Sequence lengths per sequence\n# sequence_lengths_df = gesture_val.groupby(\"sequence_id\")[\"sequence_counter\"].max() + 1\n# sequence_lengths_gesture = sequence_lengths_df.to_numpy()\n\n# # ----------------------------\n# # 📦 Convert to tensors\n# # ----------------------------\n# x_tensor = gru_utils.reshape_to_padded_tensor(scaled_x_test_gesture, sequence_lengths_gesture)  # (B, T, F)\n# lengths_tensor = torch.tensor(sequence_lengths_gesture, dtype=torch.long)\n# y_val_zero_based = torch.tensor(zero_based_labels, dtype=torch.long)\n\n# # Ensure same device for inputs and models\n# # device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# x_tensor = x_tensor.to(device)\n# lengths_tensor = lengths_tensor.to(device)\n# y_val_zero_based = y_val_zero_based.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.526867Z","iopub.execute_input":"2025-09-01T15:42:32.527062Z","iopub.status.idle":"2025-09-01T15:42:32.540140Z","shell.execute_reply.started":"2025-09-01T15:42:32.527048Z","shell.execute_reply":"2025-09-01T15:42:32.539560Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_temp_base_T, imu_temp_adpt_T, imu_temp_p_base_val, imu_temp_p_adpt_val = gru_utils.calibrate_two_models(\n#                         gesture_model_temp,\n#                          model_temp_adapter,\n#                          x_tensor,          # (N, T, F) CPU\n#                          lengths_tensor,        # (N,) CPU\n#                          y_val_zero_based,   # (N,) 0..C-1\n#                          device,\n#                          batch_size = 128,\n#                          amp = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.540764Z","iopub.execute_input":"2025-09-01T15:42:32.540998Z","iopub.status.idle":"2025-09-01T15:42:32.555156Z","shell.execute_reply.started":"2025-09-01T15:42:32.540978Z","shell.execute_reply":"2025-09-01T15:42:32.554627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# calibration_100_670['imu_temp_tof'].setdefault(670, {'model': [imu_temp_base_T, imu_temp_p_base_val], 'adapter': [imu_temp_adpt_T, imu_temp_p_adpt_val]})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.555911Z","iopub.execute_input":"2025-09-01T15:42:32.556120Z","iopub.status.idle":"2025-09-01T15:42:32.573212Z","shell.execute_reply.started":"2025-09-01T15:42:32.556102Z","shell.execute_reply":"2025-09-01T15:42:32.572509Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_temp_w_base_history =  gru_utils.search_soft_voting_weight(\n#     calibration_100_670['imu_temp_tof'][670]['model'][1],               # np.ndarray or torch.Tensor, shape (N, C)\n#     calibration_100_670['imu_temp_tof'][670]['adapter'][1],             # same shape (N, C)\n#     y_val_zero_based.cpu(),         # array-like labels, shape (N,), 0..C-1\n#     weight_grid=None,          # defaults np.linspace(0,1,21)\n#     max_epochs=None,           # defaults len(weight_grid)\n#     verbose=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.573764Z","iopub.execute_input":"2025-09-01T15:42:32.573951Z","iopub.status.idle":"2025-09-01T15:42:32.586276Z","shell.execute_reply.started":"2025-09-01T15:42:32.573937Z","shell.execute_reply":"2025-09-01T15:42:32.585564Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imu_temp_w_base_670 = imu_temp_w_base_history['best_w']\n# calibration_100_670['imu_temp_tof'][670].setdefault('best_ens_weight', [imu_temp_w_base_670])\n# print(f\"Best weight: {imu_temp_w_base_670}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.586901Z","iopub.execute_input":"2025-09-01T15:42:32.587102Z","iopub.status.idle":"2025-09-01T15:42:32.599857Z","shell.execute_reply.started":"2025-09-01T15:42:32.587088Z","shell.execute_reply":"2025-09-01T15:42:32.599128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # save calibration dictionary :\n# folder = data_folders_dictionary['predictions_data']\n# my_utils.handle_pickle_dict(folder=folder, pickle_filename ='calibration_100_670.pkl', data_dict=calibration_100_670)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.600471Z","iopub.execute_input":"2025-09-01T15:42:32.601016Z","iopub.status.idle":"2025-09-01T15:42:32.612436Z","shell.execute_reply.started":"2025-09-01T15:42:32.600995Z","shell.execute_reply":"2025-09-01T15:42:32.611734Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# load calibration dictionary :\nfolder = '/kaggle/input/cmi-gesture-classification-gru-models/pytorch/imu-temp-tof-data/3/'\nloaded_calibration_100_670 = my_utils.handle_pickle_dict(folder=folder, pickle_filename ='calibration_100_670.pkl')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.613167Z","iopub.execute_input":"2025-09-01T15:42:32.614027Z","iopub.status.idle":"2025-09-01T15:42:32.631053Z","shell.execute_reply.started":"2025-09-01T15:42:32.614010Z","shell.execute_reply":"2025-09-01T15:42:32.630362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def imu_only_prediction(\n    imu_merged_subset: pd.DataFrame,\n    task_key: str = 'gesture'\n):\n    \"\"\"\n    Performs prediction for a specified gesture-related task (e.g., 'gesture', 'bfrb_gesture')\n    using IMU-only features and a pre-trained GRU model.\n\n    Parameters:\n        imu_merged_subset (pd.DataFrame): The input DataFrame containing one full sequence.\n        task_key (str): The key indicating the task or output label, e.g., 'gesture', 'bfrb_gesture'.\n\n    Returns:\n        tuple: \n            - predictions (dict): {task_key: predicted label (1-based)}\n            - probs_list (list): List of predicted class probabilities.\n    \"\"\"\n\n    # ----------------------------\n    # 🔧 Setup\n    # ----------------------------\n\n    # Global models and configs (loaded externally)\n    global imu_trained_models\n    global model_adapter\n    global gesture_model\n    global loaded_calibration_100_670\n\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n    # Move models to the correct device\n    gesture_model.to(device)\n    model_adapter.to(device)\n\n    # Initialize the predictions dictionary for this task\n    predictions = {task_key: []}\n\n    # Make a copy of the input to avoid modifying it in-place\n    new_test_df = imu_merged_subset.copy()\n\n    # ----------------------------\n    # 1️⃣ Preprocessing\n    # ----------------------------\n\n    # Apply the saved scaler from training to standardize input features\n    scaler = imu_trained_models[task_key]['scaler']\n    scaled_x_test = scaler.transform(new_test_df.to_numpy())\n\n    # Compute the sequence length (assumes a single sequence per input)\n    sequence_length = new_test_df[\"sequence_counter\"].max() + 1\n    sequence_lengths = np.array([sequence_length])\n\n    # Convert scaled data to padded GRU-compatible tensor\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    # Move tensors to the same device as the model\n    x_tensor = x_tensor.to(device)\n    lengths_tensor = lengths_tensor.to(device)\n\n    # ----------------------------\n    # 2️⃣ Model Selection: Based on sequence length\n    # ----------------------------\n    if sequence_length <= 100:\n        model_key = 100\n    else:\n        model_key = 670\n\n    # Extract appropriate model, adapter, and ensemble weight\n    best_model = loaded_calibration_100_670['imu'][model_key]['model'][0]\n    best_adapter = loaded_calibration_100_670['imu'][model_key]['adapter'][0]\n    best_weight = loaded_calibration_100_670['imu'][model_key]['best_ens_weight'][0]\n\n    # ----------------------------\n    # 3️⃣ Make Predictions (with soft voting)\n    # ----------------------------\n\n    predicted_label, gesture_probs = gru_utils.soft_vote_predict(\n        gesture_model,\n        model_adapter,\n        best_model,\n        best_adapter,\n        x_tensor,\n        lengths_tensor,\n        device,\n        best_weight,\n        batch_size=128,\n        amp=True\n    )\n\n    # Convert to 1-based index as required by some pipelines\n    predictions[task_key] = predicted_label + 1\n\n    # Flatten probabilities to a Python list\n    probs_list = gesture_probs.squeeze().tolist()\n\n    return predictions, probs_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.631767Z","iopub.execute_input":"2025-09-01T15:42:32.631962Z","iopub.status.idle":"2025-09-01T15:42:32.639538Z","shell.execute_reply.started":"2025-09-01T15:42:32.631949Z","shell.execute_reply":"2025-09-01T15:42:32.638737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def imu_temp_prediction(\n    imu_temp_merged_subset: pd.DataFrame,\n    task_key: str = 'gesture'\n):\n    \"\"\"\n    Performs prediction for a specified gesture-related task (e.g., 'gesture', 'bfrb_gesture')\n    using IMU+Temperature features and a pre-trained GRU model.\n\n    Parameters:\n        imu_temp_merged_subset (pd.DataFrame): The input DataFrame containing one full sequence.\n        task_key (str): The key indicating the task or output label, e.g., 'gesture', 'bfrb_gesture'.\n\n    Returns:\n        tuple: \n            - predictions (dict): {task_key: predicted label (1-based)}\n            - probs_list (list): List of predicted class probabilities.\n    \"\"\"\n\n    # ----------------------------\n    # 🔧 Setup\n    # ----------------------------\n\n    # Global models and configs (loaded externally)\n    global imu_temp_tof_trained_models\n    global model_temp_adapter\n    global gesture_model_temp\n    global loaded_calibration_100_670\n\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n    # Move models to the correct device\n    gesture_model_temp.to(device)\n    model_temp_adapter.to(device)\n\n    # Initialize the predictions dictionary for this task\n    predictions = {task_key: []}\n\n    # Make a copy of the input to avoid modifying it in-place\n    new_test_df = imu_temp_merged_subset.copy()\n\n    # ----------------------------\n    # 1️⃣ Preprocessing\n    # ----------------------------\n\n    # Apply the saved scaler from training to standardize input features\n    scaler = imu_temp_tof_trained_models[task_key]['scaler']\n    scaled_x_test = scaler.transform(new_test_df.to_numpy())\n\n    # Compute the sequence length (assumes a single sequence per input)\n    sequence_length = new_test_df[\"sequence_counter\"].max() + 1\n    sequence_lengths = np.array([sequence_length])\n\n    # Convert scaled data to padded GRU-compatible tensor\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    # Move tensors to the same device as the model\n    x_tensor = x_tensor.to(device)\n    lengths_tensor = lengths_tensor.to(device)\n\n    # ----------------------------\n    # 2️⃣ Model Selection: Based on sequence length\n    # ----------------------------\n    if sequence_length <= 100:\n        model_key = 100\n    else:\n        model_key = 670\n\n    # Extract appropriate model, adapter, and ensemble weight\n    best_model = loaded_calibration_100_670['imu_temp_tof'][model_key]['model'][0]\n    best_adapter = loaded_calibration_100_670['imu_temp_tof'][model_key]['adapter'][0]\n    best_weight = loaded_calibration_100_670['imu_temp_tof'][model_key]['best_ens_weight'][0]\n\n    # ----------------------------\n    # 3️⃣ Make Predictions (with soft voting)\n    # ----------------------------\n\n    predicted_label, gesture_probs = gru_utils.soft_vote_predict(\n        gesture_model_temp,\n        model_temp_adapter,\n        best_model,\n        best_adapter,\n        x_tensor,\n        lengths_tensor,\n        device,\n        best_weight,\n        batch_size=128,\n        amp=True\n    )\n\n    # Convert to 1-based index as required by some pipelines\n    predictions[task_key] = predicted_label + 1\n\n    # Flatten probabilities to a Python list\n    probs_list = gesture_probs.squeeze().tolist()\n\n    return predictions, probs_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.640303Z","iopub.execute_input":"2025-09-01T15:42:32.640483Z","iopub.status.idle":"2025-09-01T15:42:32.659270Z","shell.execute_reply.started":"2025-09-01T15:42:32.640470Z","shell.execute_reply":"2025-09-01T15:42:32.658750Z"}},"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-09-01T15:42:32.660095Z","iopub.execute_input":"2025-09-01T15:42:32.660345Z","iopub.status.idle":"2025-09-01T15:42:32.680010Z","shell.execute_reply.started":"2025-09-01T15:42:32.660325Z","shell.execute_reply":"2025-09-01T15:42:32.679474Z"}},"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-09-01T15:42:32.680704Z","iopub.execute_input":"2025-09-01T15:42:32.680953Z","iopub.status.idle":"2025-09-01T15:42:32.689925Z","shell.execute_reply.started":"2025-09-01T15:42:32.680933Z","shell.execute_reply":"2025-09-01T15:42:32.689286Z"}},"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-09-01T15:42:32.690511Z","iopub.execute_input":"2025-09-01T15:42:32.690762Z","iopub.status.idle":"2025-09-01T15:42:32.708091Z","shell.execute_reply.started":"2025-09-01T15:42:32.690742Z","shell.execute_reply":"2025-09-01T15:42:32.707328Z"}},"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-09-01T15:42:32.708782Z","iopub.execute_input":"2025-09-01T15:42:32.709012Z","iopub.status.idle":"2025-09-01T15:42:32.726208Z","shell.execute_reply.started":"2025-09-01T15:42:32.708992Z","shell.execute_reply":"2025-09-01T15:42:32.725557Z"}},"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-09-01T15:42:32.727008Z","iopub.execute_input":"2025-09-01T15:42:32.727222Z","iopub.status.idle":"2025-09-01T15:42:32.740515Z","shell.execute_reply.started":"2025-09-01T15:42:32.727198Z","shell.execute_reply":"2025-09-01T15:42:32.739929Z"}},"outputs":[],"execution_count":null},{"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    Tof_COLUMNS = [*[f\"tof_{i}_v{j}\" for i in range(1, 6) for j in range(64)]]\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    sequence_tof_subset = sequence_pd[Tof_COLUMNS]\n    sequence_tof_subset.fillna(-1, inplace=True)\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    # # === Step 3: IMU+TEMP+Tof predictions ===\n    temp_cols = TEMP_COLUMNS\n    # Concatenate IMU+TEMP with Tof data\n    imu_temp_tof_merged_subset =  pd.concat([\n        imu_temp_merged_subset.reset_index(drop=True),\n        sequence_tof_subset.reset_index(drop=True)\n    ], axis=1)\n    \n    for step in tqdm(range(1), desc=\"IMU+Temp Model Prediction\"):\n        \n        if _ == False:\n            #print('imu+temp+tof prediction') \n            predictions, gesture_probs  = imu_temp_prediction(imu_temp_tof_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        else:\n            #print('only imu prediction')\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        result = gesture_most_probable\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 = {result}\")\n        print(\"──────────────────────────────────────\\n\")\n\n    counter += 1\n    # print(result)\n    return result","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.741204Z","iopub.execute_input":"2025-09-01T15:42:32.741883Z","iopub.status.idle":"2025-09-01T15:42:32.828092Z","shell.execute_reply.started":"2025-09-01T15:42:32.741861Z","shell.execute_reply":"2025-09-01T15:42:32.827436Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Create mock sequence with minimal required structure\n# mock_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# })\n# mock_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\n# gesture_prediction = predict(mock_sequence, mock_demo)\n# print(\"Returned prediction:\", gesture_prediction)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-01T15:42:32.828844Z","iopub.execute_input":"2025-09-01T15:42:32.829132Z","iopub.status.idle":"2025-09-01T15:42:32.832475Z","shell.execute_reply.started":"2025-09-01T15:42:32.829117Z","shell.execute_reply":"2025-09-01T15:42:32.831897Z"}},"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()\nelse:\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-09-01T15:42:32.833080Z","iopub.execute_input":"2025-09-01T15:42:32.833256Z","iopub.status.idle":"2025-09-01T15:42:34.242664Z","shell.execute_reply.started":"2025-09-01T15:42:32.833243Z","shell.execute_reply":"2025-09-01T15:42:34.242045Z"}},"outputs":[],"execution_count":null}]}