{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","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":247017926,"sourceType":"kernelVersion"}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import kaggle_evaluation.cmi_inference_server\nfrom collections import Counter\nimport pandas as pd\nimport polars as pl\nimport numpy as np\nimport joblib\nimport os\n\nfinal_model = joblib.load('/kaggle/input/cmi-xgboost-train/model.pkl')\nprint('Loaded Model')\nrev_target_map = {\n    0:\"Above ear - pull hair\",\n    1:\"Cheek - pinch skin\",\n    2:\"Eyebrow - pull hair\",\n    3:\"Eyelash - pull hair\", \n    4:\"Forehead - pull hairline\",\n    5:\"Forehead - scratch\",\n    6:\"Neck - pinch skin\", \n    7:\"Neck - scratch\",\n    \n    8:\"Drink from bottle/cup\",\n    9:\"Feel around in tray and pull out an object\",\n    10:\"Glasses on/off\",\n    11:\"Pinch knee/leg skin\", \n    12:\"Pull air toward your face\",\n    13:\"Scratch knee/leg skin\",\n    14:\"Text on phone\",\n    15:\"Wave hello\",\n    16:\"Write name in air\",\n    17:\"Write name on leg\",\n}\n\ndef create_advanced_imu_features(df: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Generates advanced statistical and phase-approximated features \n    from IMU data ONLY, returning one row per sequence_id.\n    \"\"\"\n    # Identify IMU columns\n    imu_cols = [c for c in df.columns if c.startswith('acc_') or c.startswith('rot_')]\n\n    def extract_features(group: pd.DataFrame) -> pd.Series:\n        feats = {}\n        # Whole-sequence statistical features\n        for col in imu_cols:\n            data = group[col]\n            diffs = data.diff().fillna(0)\n\n            # Basic stats\n            feats[f'{col}_mean']       = data.mean()\n            feats[f'{col}_std']        = data.std(ddof=0)\n            feats[f'{col}_min']        = data.min()\n            feats[f'{col}_max']        = data.max()\n            feats[f'{col}_median']     = data.median()\n            feats[f'{col}_range']      = data.max() - data.min()\n            feats[f'{col}_q25']        = data.quantile(0.25)\n            feats[f'{col}_q75']        = data.quantile(0.75)\n            feats[f'{col}_iqr']        = data.quantile(0.75) - data.quantile(0.25)\n            feats[f'{col}_mad']        = (data - data.mean()).abs().mean() # mean absolute deviation\n            feats[f'{col}_mean_abs']   = data.abs().mean()\n            feats[f'{col}_rms']        = np.sqrt((data**2).mean())      # root mean square\n            feats[f'{col}_energy']     = (data**2).sum()                # signal energy\n\n            # Optional: skewness & kurtosis (requires pandas >= 1.1 or scipy)\n            feats[f'{col}_skew']       = data.skew()\n            feats[f'{col}_kurtosis']   = data.kurtosis()\n\n            # Difference‐based stats\n            feats[f'{col}_diff_mean']  = diffs.mean()\n            feats[f'{col}_diff_std']   = diffs.std(ddof=0)\n            feats[f'{col}_diff_median']= diffs.median()\n            feats[f'{col}_diff_q25']   = diffs.quantile(0.25)\n            feats[f'{col}_diff_q75']   = diffs.quantile(0.75)\n            feats[f'{col}_diff_iqr']   = diffs.quantile(0.75) - diffs.quantile(0.25)\n            feats[f'{col}_diff_rms']   = np.sqrt((diffs**2).mean())\n            feats[f'{col}_diff_mad']   = diffs.abs().mean()\n\n            # Zero‐crossing count (how many times signal changes sign)\n            zc = ((data.shift(1) * data) < 0).sum()\n            feats[f'{col}_zero_crossings'] = zc\n        # Phase-approximated features\n        seq      = group['sequence_counter']\n        max_seq  = seq.max()\n        first    = seq <  max_seq * 0.3\n        middle   = (seq >= max_seq * 0.3) & (seq < max_seq * 0.7)\n        last     = seq >= max_seq * 0.7\n\n        for col in imu_cols:\n            # precompute diffs for this column\n            diffs = group[col].diff().fillna(0)\n\n            for mask, name in [\n                (first,  'first_30pct'),\n                (middle, 'middle_40pct'),\n                (last,   'last_30pct')\n            ]:\n                sub       = group.loc[mask, col]\n\n                # basic stats\n                feats[f'{col}_mean_{name}']      = sub.mean() if not sub.empty else 0\n                feats[f'{col}_std_{name}']       = sub.std(ddof=0) if not sub.empty else 0\n\n        return pd.Series(feats)\n\n    # Apply to each sequence_id\n    feature_df = df.groupby('sequence_id').apply(extract_features).fillna(0)\n    df = df.merge(feature_df, on = 'sequence_id', how = 'left')\n    #diffs = df[imu_cols + ['subject']].groupby('subject').diff(periods = 2).fillna(0)\n    #diffs.columns = diffs.columns + '_diff'\n    #df = pd.concat([df, diffs], axis = 1)\n    # Fill any remaining NaNs (e.g. empty phases) with 0\n    return df.fillna(0)\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    Receives a single test sequence and predicts the gesture.\n    \"\"\"\n    # Create features from the input sequence using the advanced function\n    sequence = sequence.to_pandas()\n    demographics = demographics.to_pandas()\n    sequence = sequence.merge(demographics, on = 'subject', how = 'left')\n    sequence = create_advanced_imu_features(sequence)\n    sequence = sequence.drop(columns = ['row_id','sequence_id','sequence_counter','subject']).astype('float32')\n    pred = final_model.predict(sequence)\n\n    pred = [rev_target_map[p] for p in pred]\n    \n    return Counter(pred).most_common(1)[0][0]\n\n# --- 4. Start the Inference Server ---\nprint(\"Starting inference server...\")\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    # This branch runs when you submit the notebook\n    inference_server.serve()\nelse:\n    # This branch runs for local testing in the notebook editor\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    )","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-23T21:00:34.186271Z","iopub.execute_input":"2025-06-23T21:00:34.186577Z","iopub.status.idle":"2025-06-23T21:00:39.399694Z","shell.execute_reply.started":"2025-06-23T21:00:34.186546Z","shell.execute_reply":"2025-06-23T21:00:39.398314Z"}},"outputs":[],"execution_count":null}]}