{"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,"sourceType":"competition"},{"sourceId":255504528,"sourceType":"kernelVersion"},{"sourceId":259076387,"sourceType":"kernelVersion"},{"sourceId":554621,"sourceType":"modelInstanceVersion","modelInstanceId":414832,"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm.notebook import tqdm\nimport polars.selectors as cs","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import kagglehub\n# from kagglehub import KaggleDatasetAdapter","metadata":{"trusted":true},"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},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"filedir = '/kaggle/input/cmi-gesture-classification-gru-models/pytorch/imu-temp-data/9/gru_model_module.py'\nmodule_name = \"gru_utils\"\ngru_utils = load_my_utils(filedir, module_name)","metadata":{"trusted":true},"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},"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},"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},"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},"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},"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},"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},"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},"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},"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},"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-data/9/'\n\nimu_trained_models = my_utils.handle_pickle_dict(\n        folder=folder,\n        pickle_filename='imu_trained_models.pkl'\n    )\n\nimu_temp_trained_models = my_utils.handle_pickle_dict(\n        folder=folder,\n        pickle_filename='imu_temp_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\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------------\n# ✅ Load best training params for gesture\n# ----------------------------\nbest_params = imu_temp_trained_models['gesture']['best_params']\ninput_dim = imu_temp_trained_models['gesture']['input_dim']\noutput_dim = imu_temp_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_trained_models['gesture']['model_name']) \ngesture_temp_adapter_state = Path(folder) / Path(imu_temp_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},"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},"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},"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': {}\n# }","metadata":{"trusted":true},"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},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = imu_balanced_datasets['gesture']['train']","metadata":{"trusted":true},"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},"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},"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},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = main_gesture_filtered_df.copy()","metadata":{"trusted":true},"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},"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},"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},"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},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = special_case_df.copy()","metadata":{"trusted":true},"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},"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},"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},"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},"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},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Temperature scaling IMU-TEMP validation dataset**","metadata":{}},{"cell_type":"code","source":"# imu_temp_df = kagglehub.dataset_load(KaggleDatasetAdapter.PANDAS,\"pira245/cmi-dataset\",\"full_rot_imu_temp_merged_v3_df.csv\",)\n# imu_temp_df = imu_temp_df.drop(['row_id.1' , 'row_id.2', 'sequence_counter.1'], axis=1)","metadata":{"trusted":true},"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_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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = imu_balanced_datasets['gesture']['train']","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # main_gesture_df = gesture_val.copy()\n# main_gesture_df = imu_temp_df.drop(['row_id'], axis=1).copy()","metadata":{"trusted":true},"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},"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},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = main_gesture_filtered_df.copy()","metadata":{"trusted":true},"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_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},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# calibration_100_670['imu_temp'].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},"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'][100]['model'][1],               # np.ndarray or torch.Tensor, shape (N, C)\n#     calibration_100_670['imu_temp'][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},"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'][100].setdefault('best_ens_weight', [imu_temp_w_base_100])\n# print(f\"Best weight: {imu_temp_w_base_100}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# gesture_val = special_case_df.copy()","metadata":{"trusted":true},"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_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},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# calibration_100_670['imu_temp'].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},"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'][670]['model'][1],               # np.ndarray or torch.Tensor, shape (N, C)\n#     calibration_100_670['imu_temp'][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},"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'][670].setdefault('best_ens_weight', [imu_temp_w_base_670])\n# print(f\"Best weight: {imu_temp_w_base_670}\")","metadata":{"trusted":true},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# load calibration dictionary :\nfolder = '/kaggle/input/cmi-gesture-classification-gru-models/pytorch/imu-temp-data/9/'\nloaded_calibration_100_670 = my_utils.handle_pickle_dict(folder=folder, pickle_filename ='calibration_100_670_v2.pkl')","metadata":{"trusted":true},"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},"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_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_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'][model_key]['model'][0]\n    best_adapter = loaded_calibration_100_670['imu_temp'][model_key]['adapter'][0]\n    best_weight = loaded_calibration_100_670['imu_temp'][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},"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},"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},"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},"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},"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},"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\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    # # === Step 3: IMU+TEMP predictions ===\n    temp_cols = TEMP_COLUMNS\n    \n    for step in tqdm(range(1), desc=\"IMU+Temp Model Prediction\"):\n        \n        if _ == False:\n            #print('imu+temp prediction') \n            predictions, gesture_probs  = imu_temp_prediction(imu_temp_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},"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)\nprint(\"Returned prediction:\", gesture_prediction)","metadata":{"trusted":true},"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},"outputs":[],"execution_count":null}]}