{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# CMI Competition - Submission Notebook\n# This notebook loads pre-trained models from dataset and runs inference\n# MUST BE UNDER 900 SECONDS\n\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nimport tensorflow as tf\nfrom tensorflow.keras.models import load_model\nfrom tensorflow.keras.layers import Layer\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom scipy.spatial.transform import Rotation as R\nimport kaggle_evaluation.cmi_inference_server\nimport os\nimport pickle\nimport joblib\n\ndef seed_everything(seed):\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n\nseed_everything(42)\n\n# Competition setup\ntarget_gestures = ['Above ear - pull hair','Forehead - scratch','Forehead - pull hairline',\n                   'Eyebrow - pull hair','Eyelash - pull hair','Neck - pinch skin', \n                   'Cheek - pinch skin', 'Neck - scratch']\n\n# ===== FEATURE ENGINEERING =====\n\ndef remove_gravity_from_acc(acc_data, rot_data):\n    if isinstance(acc_data, pd.DataFrame):\n        acc_values = acc_data[['acc_x', 'acc_y', 'acc_z']].values\n    else:\n        acc_values = acc_data\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n\n    num_samples = acc_values.shape[0]\n    linear_accel = np.zeros_like(acc_values)\n    gravity_world = np.array([0, 0, 9.81])\n\n    for i in range(num_samples):\n        if np.all(np.isnan(quat_values[i])) or np.all(np.isclose(quat_values[i], 0)):\n            linear_accel[i, :] = acc_values[i, :]\n            continue\n        try:\n            rotation = R.from_quat(quat_values[i])\n            gravity_sensor_frame = rotation.apply(gravity_world, inverse=True)\n            linear_accel[i, :] = acc_values[i, :] - gravity_sensor_frame\n        except ValueError:\n             linear_accel[i, :] = acc_values[i, :]\n    return linear_accel\n\ndef calculate_angular_velocity_from_quat(rot_data, time_delta=1/200):\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n    num_samples = quat_values.shape[0]\n    angular_vel = np.zeros((num_samples, 3))\n    for i in range(num_samples - 1):\n        q_t = quat_values[i]\n        q_t_plus_dt = quat_values[i+1]\n        if np.all(np.isnan(q_t)) or np.all(np.isclose(q_t, 0)) or \\\n           np.all(np.isnan(q_t_plus_dt)) or np.all(np.isclose(q_t_plus_dt, 0)):\n            continue\n        try:\n            rot_t = R.from_quat(q_t)\n            rot_t_plus_dt = R.from_quat(q_t_plus_dt)\n            delta_rot = rot_t.inv() * rot_t_plus_dt\n            angular_vel[i, :] = delta_rot.as_rotvec() / time_delta\n        except ValueError:\n            pass\n    return angular_vel\n\ndef calculate_angular_distance(rot_data):\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n    num_samples = quat_values.shape[0]\n    angular_dist = np.zeros(num_samples)\n    for i in range(num_samples - 1):\n        q1 = quat_values[i]\n        q2 = quat_values[i+1]\n        if np.all(np.isnan(q1)) or np.all(np.isclose(q1, 0)) or \\\n           np.all(np.isnan(q2)) or np.all(np.isclose(q2, 0)):\n            angular_dist[i] = 0\n            continue\n        try:\n            r1 = R.from_quat(q1)\n            r2 = R.from_quat(q2)\n            relative_rotation = r1.inv() * r2\n            angle = np.linalg.norm(relative_rotation.as_rotvec())\n            angular_dist[i] = angle\n        except ValueError:\n            angular_dist[i] = 0\n    return angular_dist\n\ndef process_sequence_for_model(seq_df):\n    # Ensure all required columns exist\n    required_base_cols = ['acc_x', 'acc_y', 'acc_z', 'rot_x', 'rot_y', 'rot_z', 'rot_w']\n    for col in required_base_cols:\n        if col not in seq_df.columns:\n            seq_df[col] = 0\n    \n    # Ensure thermal columns exist\n    for i in range(1, 6):\n        col = f'thm_{i}'\n        if col not in seq_df.columns:\n            seq_df[col] = 0\n    \n    # Add features\n    linear_accel = remove_gravity_from_acc(seq_df, seq_df)\n    seq_df['linear_acc_x'] = linear_accel[:, 0]\n    seq_df['linear_acc_y'] = linear_accel[:, 1] \n    seq_df['linear_acc_z'] = linear_accel[:, 2]\n    seq_df['linear_acc_mag'] = np.sqrt(seq_df['linear_acc_x']**2 + seq_df['linear_acc_y']**2 + seq_df['linear_acc_z']**2)\n    seq_df['linear_acc_mag_jerk'] = seq_df['linear_acc_mag'].diff().fillna(0)\n    \n    angular_vel = calculate_angular_velocity_from_quat(seq_df)\n    seq_df['angular_vel_x'] = angular_vel[:, 0]\n    seq_df['angular_vel_y'] = angular_vel[:, 1]\n    seq_df['angular_vel_z'] = angular_vel[:, 2]\n    seq_df['angular_distance'] = calculate_angular_distance(seq_df)\n\n    # ToF features\n    for i in range(1, 6):\n        pixel_cols = [f\"tof_{i}_v{p}\" for p in range(64)]\n        available_cols = [col for col in pixel_cols if col in seq_df.columns]\n        \n        if available_cols:\n            tof_data = seq_df[available_cols].replace(-1, np.nan)\n            seq_df[f'tof_{i}_mean'] = tof_data.mean(axis=1)\n            seq_df[f'tof_{i}_std'] = tof_data.std(axis=1)\n            seq_df[f'tof_{i}_min'] = tof_data.min(axis=1)\n            seq_df[f'tof_{i}_max'] = tof_data.max(axis=1)\n        else:\n            seq_df[f'tof_{i}_mean'] = 0\n            seq_df[f'tof_{i}_std'] = 0\n            seq_df[f'tof_{i}_min'] = 0\n            seq_df[f'tof_{i}_max'] = 0\n\n    # Final feature set\n    final_feature_cols = (['acc_x', 'acc_y', 'acc_z', 'rot_x', 'rot_y', 'rot_z', 'rot_w'] +\n                          [f'thm_{i}' for i in range(1, 6)] +\n                          ['linear_acc_x', 'linear_acc_y', 'linear_acc_z', 'linear_acc_mag', 'linear_acc_mag_jerk',\n                           'angular_vel_x', 'angular_vel_y', 'angular_vel_z', 'angular_distance'] +\n                          [f'tof_{i}_{stat}' for i in range(1, 6) for stat in ['mean', 'std', 'min', 'max']])\n    \n    return seq_df[final_feature_cols].ffill().bfill().fillna(0).values.astype('float32')\n\n# ===== CUSTOM LAYERS AND FUNCTIONS =====\n\ndef squeeze_last_axis(x):\n    return tf.squeeze(x, axis=-1)\n\ndef expand_last_axis(x):\n    return tf.expand_dims(x, axis=-1)\n\ndef time_sum(x):\n    return tf.reduce_sum(x, axis=1)\n\nclass SplitLayer(Layer):\n    def __init__(self, start_idx, end_idx, **kwargs):\n        super(SplitLayer, self).__init__(**kwargs)\n        self.start_idx = start_idx\n        self.end_idx = end_idx\n    \n    def call(self, inputs):\n        return inputs[:, :, self.start_idx:self.end_idx]\n    \n    def get_config(self):\n        config = super(SplitLayer, self).get_config()\n        config.update({'start_idx': self.start_idx, 'end_idx': self.end_idx})\n        return config\n\nclass GlobalSumPooling1D(Layer):\n    def __init__(self, **kwargs):\n        super(GlobalSumPooling1D, self).__init__(**kwargs)\n    \n    def call(self, inputs):\n        return tf.reduce_sum(inputs, axis=1)\n    \n    def get_config(self):\n        return super(GlobalSumPooling1D, self).get_config()\n\n# ===== LOAD PRE-TRAINED MODELS FROM DATASET =====\n\nMODEL_DATASET_PATH = \"/kaggle/input/cmi-best-trained-models/cmi_test_models/\"\n\nprint(\"Loading pre-trained models from dataset...\")\n\n# Load model parameters\nwith open(f\"{MODEL_DATASET_PATH}model_params.pkl\", 'rb') as f:\n    params = pickle.load(f)\n    pad_len = params['pad_len']\n    n_features = params['n_features']\n    imu_dim = params['imu_dim']\n    tof_dim = params['tof_dim']\n    gesture_classes = params['gesture_classes']\n\nprint(f\"Model parameters loaded: pad_len={pad_len}, n_features={n_features}\")\n\n# Load scaler and label encoder\nscaler = joblib.load(f\"{MODEL_DATASET_PATH}scaler.pkl\")\nle = joblib.load(f\"{MODEL_DATASET_PATH}label_encoder.pkl\")\n\nprint(\"Scaler and label encoder loaded\")\n\n# Load models with custom objects\ntrained_models = []\ncustom_objects = {\n    'SplitLayer': SplitLayer,\n    'GlobalSumPooling1D': GlobalSumPooling1D,\n    'squeeze_last_axis': squeeze_last_axis,\n    'expand_last_axis': expand_last_axis,\n    'time_sum': time_sum\n}\n\n# Load all 10 fold models\nfor fold in range(10):\n    print(f\"Loading model_fold_{fold}.h5...\")\n    model = load_model(f\"{MODEL_DATASET_PATH}model_fold_{fold}.h5\", custom_objects=custom_objects)\n    trained_models.append(model)\n\nprint(f\"Successfully loaded {len(trained_models)} pre-trained models!\")\n\n# ===== PREDICTION FUNCTION  =====\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"Ensemble prediction using all 10 fold models\"\"\"\n    try:\n        # Convert to pandas\n        df_seq = sequence.to_pandas()\n        \n        # Process sequence\n        seq_matrix = process_sequence_for_model(df_seq)\n        \n        # Handle feature mismatch\n        if seq_matrix.shape[1] != n_features:\n            if seq_matrix.shape[1] < n_features:\n                padding = np.zeros((seq_matrix.shape[0], n_features - seq_matrix.shape[1]))\n                seq_matrix = np.hstack([seq_matrix, padding])\n            else:\n                seq_matrix = seq_matrix[:, :n_features]\n        \n        # Pad sequence\n        pad_input = np.zeros((1, pad_len, n_features), dtype='float32')\n        seq_len = min(len(seq_matrix), pad_len)\n        pad_input[0, :seq_len] = seq_matrix[:seq_len]\n        \n        # Scale\n        pad_input_reshaped = pad_input.reshape(-1, n_features)\n        pad_input_scaled = scaler.transform(pad_input_reshaped).reshape(pad_input.shape)\n        \n        # Ensemble prediction using all 10 models\n        ensemble_pred = np.zeros((1, len(gesture_classes)))\n        for model in trained_models:\n            pred = model.predict(pad_input_scaled, verbose=0)\n            ensemble_pred += pred\n        \n        ensemble_pred /= len(trained_models)  # Average predictions\n        predicted_class = le.inverse_transform([np.argmax(ensemble_pred[0])])[0]\n        \n        return str(predicted_class)\n        \n    except Exception as e:\n        return 'Wave hello'\n\n# ===== KAGGLE SUBMISSION =====\n\nprint(\"\\n=== Setting up Kaggle submission ===\")\n\n# Start server\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    print(\"Competition rerun - serving...\")\n    inference_server.serve()\nelse:\n    print(\"Local testing - running gateway...\")\n    inference_server.run_local_gateway(\n        data_paths=(\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null}]}