{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12920904,"sourceType":"datasetVersion","datasetId":8170087}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import backend as K\nfrom tensorflow.keras.layers import Layer\nregister = tf.keras.utils.register_keras_serializable\n\n@register(package=\"custom_layers\")\nclass SqueezeLastAxis(Layer):\n    def call(self, x):\n        return tf.squeeze(x, axis=-1)\n\n@register(package=\"custom_layers\")\nclass ExpandLastAxis(Layer):\n    def call(self, x):\n        return tf.expand_dims(x, axis=-1)\n\n@register(package=\"custom_layers\")\nclass TimeSum(Layer):\n    def call(self, x):\n        return tf.reduce_sum(x, axis=1)\n\n@register(package=\"custom_layers\")\nclass SliceLayer(Layer):\n    def __init__(self, start, end=None, **kwargs):\n        super().__init__(**kwargs)\n        self.start = int(start)\n        self.end = None if end is None else int(end)\n\n    def get_config(self):\n        cfg = super().get_config()\n        cfg.update({'start': self.start, 'end': self.end})\n        return cfg\n\n    def call(self, inputs):\n        if self.end is None:\n            return inputs[:, :, self.start:]\n        return inputs[:, :, self.start:self.end]\n\n@register(package=\"custom_layers\")\nclass SEBlock(Layer):\n    def __init__(self, reduction=8, **kwargs):\n        super().__init__(**kwargs)\n        self.reduction = reduction\n\n    def build(self, input_shape):\n        ch = int(input_shape[-1])\n        from tensorflow.keras.layers import Dense\n        self.dense1 = Dense(ch // self.reduction, activation='relu')\n        self.dense2 = Dense(ch, activation='sigmoid')\n        super().build(input_shape)\n\n    def call(self, x):\n        from tensorflow.keras.layers import Reshape, Multiply\n        se = tf.reduce_mean(x, axis=1, keepdims=False)   # GlobalAveragePooling1D\n        se = self.dense1(se)\n        se = self.dense2(se)\n        se = Reshape((1, tf.shape(se)[-1]))(se)\n        return Multiply()([x, se])\n\n    def get_config(self):\n        cfg = super().get_config()\n        cfg.update({'reduction': self.reduction})\n        return cfg\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 K.sum(x, axis=1)\n\ndef se_block(x, reduction=8):\n    from tensorflow.keras.layers import GlobalAveragePooling1D, Dense, Reshape, Multiply\n    ch = x.shape[-1]\n    se = GlobalAveragePooling1D()(x)\n    se = Dense(ch // reduction, activation='relu')(se)\n    se = Dense(ch, activation='sigmoid')(se)\n    se = Reshape((1, ch))(se)\n    return Multiply()([x, se])\n\ncustom_objects = {\n    'SqueezeLastAxis': SqueezeLastAxis,\n    'ExpandLastAxis': ExpandLastAxis,\n    'TimeSum': TimeSum,\n    'SliceLayer': SliceLayer,\n    'SEBlock': SEBlock,\n    'squeeze_last_axis': squeeze_last_axis,\n    'expand_last_axis': expand_last_axis,\n    'time_sum': time_sum,\n    'se_block': se_block,\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-31T11:14:13.178976Z","iopub.execute_input":"2025-08-31T11:14:13.179290Z","iopub.status.idle":"2025-08-31T11:14:13.197135Z","shell.execute_reply.started":"2025-08-31T11:14:13.179269Z","shell.execute_reply":"2025-08-31T11:14:13.196250Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport os\nimport pandas as pd\nimport polars as pl\nimport numpy as np\nimport joblib\nimport tensorflow as tf\nfrom tensorflow.keras.models import load_model\nfrom tensorflow.keras.utils import pad_sequences\nfrom tensorflow.keras import backend as K\nfrom scipy.spatial.transform import Rotation as R\nimport gc\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\nos.environ['CUDA_VISIBLE_DEVICES'] = '-1'  \ntf.config.set_visible_devices([], 'GPU')  # Disable GPU\n\ntf.config.threading.set_inter_op_parallelism_threads(1)\ntf.config.threading.set_intra_op_parallelism_threads(1)\n\ndef time_sum(x):\n    return K.sum(x, axis=1)\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 se_block(x, reduction=8):\n    \"\"\"Squeeze-and-Excitation block\"\"\"\n    from tensorflow.keras.layers import GlobalAveragePooling1D, Dense, Reshape, Multiply\n    ch = x.shape[-1]\n    se = GlobalAveragePooling1D()(x)\n    se = Dense(ch // reduction, activation='relu')(se)\n    se = Dense(ch, activation='sigmoid')(se)\n    se = Reshape((1, ch))(se)\n    return Multiply()([x, se])\n\n\n# Load preprocessing objects\nscaler = joblib.load('/kaggle/input/cmi-final-model-artifacts/scaler.pkl')\nle = joblib.load('/kaggle/input/cmi-final-model-artifacts/label_encoder.pkl')\ngesture_classes = le.classes_\n\nmodels = []\nprint(\"Loading models...\")\n\nfor i in range(1, 6):\n    path = f\"/kaggle/input/cmi-final-model-artifacts/gesture_model_fold_{i}.h5\"\n    try:\n        m = load_model(path, custom_objects=custom_objects, compile=False)\n        models.append(m)\n        print(f\"Loaded model {i}\")\n    except Exception as e:\n        print(f\"Could not load model {i}: {e}\")\n\nif len(models) == 0:\n    print(\"Trying to load only the best model...\")\n    try:\n        best_model = load_model('/kaggle/input/cmi-final-model-artifacts/best_model_after_training.h5',\n                        custom_objects=custom_objects,\n                        compile=False)\n\n        models = [best_model]\n        print(\"Successfully loaded best model\")\n    except Exception as e:\n        print(f\"Could not load best model: {e}\")\n\nif len(models) == 0:\n    print(\"Error: No models could be loaded!\")\n    # Fallback: create a dummy prediction function\n    def fallback_predict():\n        return 'Text on phone'\nelse:\n    print(f\"Successfully loaded {len(models)} models\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-31T11:14:14.102081Z","iopub.execute_input":"2025-08-31T11:14:14.102387Z","iopub.status.idle":"2025-08-31T11:14:20.626449Z","shell.execute_reply.started":"2025-08-31T11:14:14.102364Z","shell.execute_reply":"2025-08-31T11:14:20.625316Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pad_len = 127  # Use the same pad_len from training\n\ndef remove_gravity_from_acc(acc_data, rot_data):\n    \"\"\"Remove gravity from acceleration 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\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    \"\"\"Calculate angular velocity from quaternions\"\"\"\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 = quat_values.shape[0]\n    angular_vel = np.zeros((num_samples, 3))\n\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    \"\"\"Calculate angular distance between consecutive rotations\"\"\"\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 = quat_values.shape[0]\n    angular_dist = np.zeros(num_samples)\n\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 create_advanced_features(df_seq):\n    \"\"\"Create advanced features from raw sensor data\"\"\"\n    # Gravity removal\n    linear_accel = remove_gravity_from_acc(df_seq, df_seq)\n    df_seq['linear_acc_x'] = linear_accel[:, 0]\n    df_seq['linear_acc_y'] = linear_accel[:, 1]\n    df_seq['linear_acc_z'] = linear_accel[:, 2]\n    df_seq['linear_acc_mag'] = np.sqrt(df_seq['linear_acc_x']**2 +\n                                      df_seq['linear_acc_y']**2 +\n                                      df_seq['linear_acc_z']**2)\n    df_seq['linear_acc_mag_jerk'] = df_seq['linear_acc_mag'].diff().fillna(0)\n\n    # Angular velocity features\n    angular_vel = calculate_angular_velocity_from_quat(df_seq)\n    df_seq['angular_vel_x'] = angular_vel[:, 0]\n    df_seq['angular_vel_y'] = angular_vel[:, 1]\n    df_seq['angular_vel_z'] = angular_vel[:, 2]\n    df_seq['angular_vel_mag'] = np.sqrt(angular_vel[:, 0]**2 +\n                                       angular_vel[:, 1]**2 +\n                                       angular_vel[:, 2]**2)\n    df_seq['angular_distance'] = calculate_angular_distance(df_seq)\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        if all(col in df_seq.columns for col in pixel_cols):\n            tof_data = df_seq[pixel_cols].replace(-1, np.nan)\n\n            # Statistical features\n            df_seq[f'tof_{i}_mean'] = tof_data.mean(axis=1)\n            df_seq[f'tof_{i}_std'] = tof_data.std(axis=1)\n            df_seq[f'tof_{i}_min'] = tof_data.min(axis=1)\n            df_seq[f'tof_{i}_max'] = tof_data.max(axis=1)\n            df_seq[f'tof_{i}_median'] = tof_data.median(axis=1)\n            df_seq[f'tof_{i}_range'] = df_seq[f'tof_{i}_max'] - df_seq[f'tof_{i}_min']\n\n            # Spatial features\n            try:\n                tof_reshaped = tof_data.values.reshape(-1, 8, 8)\n                df_seq[f'tof_{i}_center'] = np.nanmean(tof_reshaped[:, 3:5, 3:5], axis=(1,2))\n                df_seq[f'tof_{i}_edge'] = np.nanmean(np.concatenate([\n                    tof_reshaped[:, 0, :].reshape(-1, 8),\n                    tof_reshaped[:, -1, :].reshape(-1, 8),\n                    tof_reshaped[:, 1:-1, 0].reshape(-1, 6),\n                    tof_reshaped[:, 1:-1, -1].reshape(-1, 6)\n                ], axis=1), axis=1)\n            except:\n                df_seq[f'tof_{i}_center'] = 0\n                df_seq[f'tof_{i}_edge'] = 0\n\n    for i in range(1, 6):\n        if f'thm_{i}' in df_seq.columns:\n            df_seq[f'thm_{i}_diff'] = df_seq[f'thm_{i}'].diff().fillna(0)\n            df_seq[f'thm_{i}_rolling_mean'] = df_seq[f'thm_{i}'].rolling(\n                window=5, center=True).mean().fillna(df_seq[f'thm_{i}'])\n\n    return df_seq\n    \ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"Predict gesture for a single sequence using ensemble of trained models\"\"\"\n    try:\n        df_seq = sequence.to_pandas()\n\n        # Feature engineering\n        df_seq = create_advanced_features(df_seq)\n\n        # Define features\n        imu_features = [\n            'acc_x','acc_y','acc_z','rot_w','rot_x','rot_y','rot_z',\n            'linear_acc_x','linear_acc_y','linear_acc_z','linear_acc_mag',\n            'linear_acc_mag_jerk','angular_vel_x','angular_vel_y','angular_vel_z',\n            'angular_vel_mag','angular_distance'\n        ]\n        tof_features = [f'tof_{i}_{feat}' for i in range(1,6) for feat in \n                        ['mean','std','min','max','median','range','center','edge']]\n        thm_features = [f for i in range(1,6) for f in \n                        [f'thm_{i}',f'thm_{i}_diff',f'thm_{i}_rolling_mean']]\n        FEATURE_COLS = imu_features + tof_features + thm_features\n\n        # Ensure all features exist\n        for col in FEATURE_COLS:\n            if col not in df_seq.columns:\n                df_seq[col] = 0.0\n        df_seq[FEATURE_COLS] = df_seq[FEATURE_COLS].ffill().bfill().fillna(0)\n\n        # Scale + pad\n        mat_unscaled = df_seq[FEATURE_COLS].values.astype('float32')\n        mat_scaled = scaler.transform(mat_unscaled)\n        pad_input = pad_sequences([mat_scaled], maxlen=pad_len,\n                                  padding='post', truncating='post', dtype='float32')\n\n        # Ensemble prediction\n        if len(models) > 0:\n            all_preds = [m.predict(pad_input, verbose=0)[0] for m in models]\n            ensemble_pred = np.mean(all_preds, axis=0)\n            predicted_idx = np.argmax(ensemble_pred)\n            return gesture_classes[predicted_idx]\n        else:\n            return 'Text on phone'  # fallback\n    except Exception as e:\n        print(f\"Prediction error: {str(e)}\")\n        return 'Text on phone'\n\n        \n\n# For Kaggle submission\nif __name__ == \"__main__\":\n    print(\"Setting up inference server...\")\n    \n    # Import kaggle evaluation\n    import kaggle_evaluation.cmi_inference_server\n    \n    # Create inference server\n    inference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n    \n    # Check if running in competition environment\n    if os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n        print(\"Running in competition mode...\")\n        inference_server.serve()\n    else:\n        print(\"Running in local test mode...\")\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    \n    print(\"Submission complete!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-31T11:14:37.727254Z","iopub.execute_input":"2025-08-31T11:14:37.727582Z","iopub.status.idle":"2025-08-31T11:14:53.151626Z","shell.execute_reply.started":"2025-08-31T11:14:37.727560Z","shell.execute_reply":"2025-08-31T11:14:53.150815Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import polars as pl\nimport pandas as pd\nfrom tqdm.auto import tqdm\nimport os\n\n# File paths\nTEST_CSV = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv\"\nDEM_CSV  = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv\"\nOUTPUT_CSV = \"/kaggle/working/predictions.csv\"\nLIMIT_SEQS = None\n\ndef find_seq_col(df):\n    candidates = ['sequence_id', 'seq_id', 'group', 'id', 'session_id', 'sequence', 'group_id']\n    cols = set(df.columns)\n    for c in candidates:\n        if c in cols:\n            return c\n    for c in df.columns:\n        nunique = df[c].nunique()\n        if 2 < nunique < 10000 and nunique < len(df) // 2:\n            return c\n    raise ValueError(\"No sequence id column detected. Set SEQ_COL manually.\")\n\nprint(\"Loading test CSV...\")\ntest_pl = pl.read_csv(TEST_CSV)\nprint(\"Loading demographics CSV...\")\ndem_pl = pl.read_csv(DEM_CSV)\n\ntry:\n    SEQ_COL = find_seq_col(test_pl.to_pandas())\n    print(f\"Detected sequence id column: '{SEQ_COL}'\")\nexcept Exception as e:\n    print(\"Detection failed:\", e)\n    raise\n\nseq_ids = test_pl.select(SEQ_COL).unique().to_series().to_list()\nif LIMIT_SEQS:\n    seq_ids = seq_ids[:LIMIT_SEQS]\n\npreds = []\nerrors = []\n\nprint(f\"Running predictions for {len(seq_ids)} sequences (limit={LIMIT_SEQS})...\")\nfor sid in tqdm(seq_ids):\n    try:\n        seq_df = test_pl.filter(pl.col(SEQ_COL) == sid)\n        dem_row = pl.DataFrame({})\n\n        if 'subject' in dem_pl.columns:\n            if 'subject' in seq_df.columns:\n                subj_vals = seq_df.select('subject').unique().to_series().to_list()\n                if len(subj_vals) > 0:\n                    dem_row = dem_pl.filter(pl.col('subject') == subj_vals[0])\n\n        if dem_row.height == 0:\n            common = list(set(dem_pl.columns).intersection(set(seq_df.columns)))\n            fallback_keys = ['participant_id', 'person_id', 'session_id', 'id', 'subject']\n            for key in fallback_keys:\n                if key in common:\n                    val_list = seq_df.select(key).unique().to_series().to_list()\n                    if len(val_list) > 0:\n                        dem_row = dem_pl.filter(pl.col(key) == val_list[0])\n                        break\n\n        if dem_row.height == 0:\n            dem_row = pl.DataFrame({})\n\n        prediction = predict(seq_df, dem_row)\n        preds.append((sid, prediction))\n\n    except Exception as e:\n        errors.append((sid, str(e)))\n        preds.append((sid, \"ERROR\"))\n        print(f\"Error predicting for seq {sid}: {e}\")\n\npred_df = pd.DataFrame(preds, columns=[SEQ_COL, 'predicted_gesture'])\npred_df.to_csv(OUTPUT_CSV, index=False)\nprint(f\"Saved predictions to {OUTPUT_CSV}\")\n\nprint(\"\\nSample predictions (first 10):\")\nprint(pred_df.head(10))\n\nprint(\"\\nPrediction counts:\")\nprint(pred_df['predicted_gesture'].value_counts().head(20))\n\nif errors:\n    print(f\"\\nThere were {len(errors)} prediction errors. Sample:\")\n    print(errors[:5])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-31T11:15:03.236376Z","iopub.execute_input":"2025-08-31T11:15:03.237098Z","iopub.status.idle":"2025-08-31T11:15:04.541327Z","shell.execute_reply.started":"2025-08-31T11:15:03.237071Z","shell.execute_reply":"2025-08-31T11:15:04.540506Z"}},"outputs":[],"execution_count":null}]}