{"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":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12892600,"sourceType":"datasetVersion","datasetId":7578000}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# The training notebook is here:\nhttps://www.kaggle.com/code/farisalahmdi/lgbm-starter","metadata":{}},{"cell_type":"code","source":"import pickle\nimport kaggle_evaluation.cmi_inference_server\nimport polars as pl\nimport lightgbm as lgbm\nimport numpy as np\nfrom scipy.spatial.transform import Rotation as R\nimport pandas as pd","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-28T05:36:07.069603Z","iopub.execute_input":"2025-08-28T05:36:07.070516Z","iopub.status.idle":"2025-08-28T05:36:07.074721Z","shell.execute_reply.started":"2025-08-28T05:36:07.070469Z","shell.execute_reply":"2025-08-28T05:36:07.073907Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def remove_gravity_from_acc(acc_data, rot_data):\n\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    \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\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             \n    return linear_accel\n\ndef calculate_angular_velocity_from_quat(rot_data, time_delta=1/200): # Assuming 200Hz sampling rate\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\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\n        try:\n            rot_t = R.from_quat(q_t)\n            rot_t_plus_dt = R.from_quat(q_t_plus_dt)\n\n            # Calculate the relative rotation\n            delta_rot = rot_t.inv() * rot_t_plus_dt\n            \n            # Convert delta rotation to angular velocity vector\n            # The rotation vector (Euler axis * angle) scaled by 1/dt\n            # is a good approximation for small delta_rot\n            angular_vel[i, :] = delta_rot.as_rotvec() / time_delta\n        except ValueError:\n            # If quaternion is invalid, angular velocity remains zero\n            pass\n            \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\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\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 # Или np.nan, в зависимости от желаемого поведения\n            continue\n        try:\n            # Преобразование кватернионов в объекты Rotation\n            r1 = R.from_quat(q1)\n            r2 = R.from_quat(q2)\n\n            # Вычисление углового расстояния: 2 * arccos(|real(p * q*)|)\n            # где p* - сопряженный кватернион q\n            # В scipy.spatial.transform.Rotation, r1.inv() * r2 дает относительное вращение.\n            # Угол этого относительного вращения - это и есть угловое расстояние.\n            relative_rotation = r1.inv() * r2\n            \n            # Угол rotation vector соответствует угловому расстоянию\n            # Норма rotation vector - это угол в радианах\n            angle = np.linalg.norm(relative_rotation.as_rotvec())\n            angular_dist[i] = angle\n        except ValueError:\n            angular_dist[i] = 0 # В случае недействительных кватернионов\n            pass\n            \n    return angular_dist","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T05:36:07.075909Z","iopub.execute_input":"2025-08-28T05:36:07.076195Z","iopub.status.idle":"2025-08-28T05:36:07.095143Z","shell.execute_reply.started":"2025-08-28T05:36:07.076177Z","shell.execute_reply":"2025-08-28T05:36:07.094243Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"version = 2.9\nwith open(f\"/kaggle/input/cmi-gesture-models/lgbm_gesture_model_{version}.pkl\", \"rb\") as f:\n    model = pickle.load(f)\n#/kaggle/input/cmi-gesture-models/gesture_label_encoder_0.1.pkl\nwith open(f\"/kaggle/input/cmi-gesture-models/gesture_label_encoder_{version}.pkl\", \"rb\") as f:\n    le = pickle.load(f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T05:36:07.096038Z","iopub.execute_input":"2025-08-28T05:36:07.096284Z","iopub.status.idle":"2025-08-28T05:36:07.336578Z","shell.execute_reply.started":"2025-08-28T05:36:07.096259Z","shell.execute_reply":"2025-08-28T05:36:07.335990Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def feature_engineering_inf(data:pl.DataFrame):\n    demographic_cols = [\n    \"adult_child\", \"age\", \"sex\", \"handedness\",\n    \"height_cm\", \"shoulder_to_wrist_cm\", \"elbow_to_wrist_cm\"\n    ]\n    \n    # All numeric sensor columns (everything except id, demo, target)\n    stat_cols = [\n        c for c in data.columns\n        if c not in demographic_cols + [\"sequence_id\", \"row_id\",\"subject\"]\n    ]\n    \n    agg_exprs = []\n    \n    # full-stats bundle for sensor columns\n    for c in stat_cols:\n        agg_exprs += [\n            pl.col(c).mean().alias(f\"{c}_mean\"),\n            pl.col(c).std().alias(f\"{c}_std\"),\n            pl.col(c).var().alias(f\"{c}_var\"),\n            # pl.col(c).mode().list.first().alias(f\"{c}_mode\"),\n            pl.col(c).quantile(0.25).alias(f\"{c}_q25\"),\n            pl.col(c).median().alias(f\"{c}_q50\"),\n            pl.col(c).quantile(0.75).alias(f\"{c}_q75\"),\n            pl.col(c).max().alias(f\"{c}_max\"),\n            pl.col(c).min().alias(f\"{c}_min\"),\n            # pl.col(c).first().alias(f\"{c}_first\"),\n            # pl.col(c).last().alias(f\"{c}_last\"),\n            # pl.col(c).quantile(0.25, \"nearest\").alias(f\"{c}_t25\"),\n            # pl.col(c).quantile(0.75, \"nearest\").alias(f\"{c}_t75\"),\n            # (pl.col(c).last() - pl.col(c).first()).alias(f\"{c}_delta\"),\n            # pl.corr(\"sequence_counter\", c).alias(f\"{c}_corr_time\"),\n            # pl.col(c).diff().mean().alias(f\"{c}_diff_mean\"),\n            # pl.col(c).diff().std().alias(f\"{c}_diff_std\"),\n            # pl.col(c).skew().alias(f\"{c}_skew\"),\n            # pl.col(c).kurtosis().alias(f\"{c}_kurt\"),\n            # pl.col(c).diff().abs().gt(0).sum().alias(f\"{c}_n_changes\"),\n            # (pl.col(c) - pl.col(c).median()).abs().median().alias(f\"{c}_mad\"),\n            # pl.col(c).pow(2).mean().sqrt().alias(f\"{c}_rms\"),\n            # (pl.col(c).max() - pl.col(c).min()).alias(f\"{c}_ptp\"),\n        ]\n        agg_exprs += [\n            pl.when(pl.col(\"sequence_counter\") < 0.25 * pl.max(\"sequence_counter\"))\n              .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg1_mean\"),\n            pl.when((pl.col(\"sequence_counter\") > 0.3 * pl.max(\"sequence_counter\")) & (pl.col(\"sequence_counter\") < 0.5 * pl.max(\"sequence_counter\")))\n              .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg2_mean\"),\n        pl.when((pl.col(\"sequence_counter\") > 0.5 * pl.max(\"sequence_counter\")) & (pl.col(\"sequence_counter\") < 0.75 * pl.max(\"sequence_counter\")))\n              .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg3_mean\"),\n            pl.when(pl.col(\"sequence_counter\") > 0.75 * pl.max(\"sequence_counter\"))\n              .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg4_mean\"),\n        ]\n    for a, b in [(\"acc_x\", \"acc_y\"), (\"acc_x\", \"acc_z\"),\n             (\"acc_y\", \"acc_z\"), (\"angular_vel_x\", \"angular_vel_y\"),\n             (\"angular_vel_x\", \"angular_vel_z\"), (\"angular_vel_y\", \"angular_vel_z\")]:\n        agg_exprs += [\n            pl.corr(a, b).alias(f\"{a}_{b}_corr\"),\n            (pl.col(a) * pl.col(b)).mean().alias(f\"{a}_{b}_cov\")\n        ]\n    \n    # first() for demographics and target\n    # agg_exprs += [\n    #     pl.col(c).first().alias(c) for c in demographic_cols\n    # ]\n    agg_exprs += [pl.col(\"sequence_counter\").max().alias(c)]\n    # Group-by and aggregate\n    cleaned_data = (\n        data\n        .group_by(\"sequence_id\", maintain_order=True)\n        .agg(agg_exprs)\n    )\n    return cleaned_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T05:36:07.338372Z","iopub.execute_input":"2025-08-28T05:36:07.338606Z","iopub.status.idle":"2025-08-28T05:36:07.348685Z","shell.execute_reply.started":"2025-08-28T05:36:07.338586Z","shell.execute_reply":"2025-08-28T05:36:07.347919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:   \n    data = sequence.join(demographics,on=\"subject\",how=\"left\")\n    data = data.to_pandas()\n    \n    data['acc_mag'] = np.sqrt(data['acc_x']**2 + data['acc_y']**2 + data['acc_z']**2)\n    data['rot_angle'] = 2 * np.arccos(data['rot_w'].clip(-1, 1))\n    data['acc_mag_jerk'] = data.groupby('sequence_id')['acc_mag'].diff().fillna(0)\n    data['rot_angle_vel'] = data.groupby('sequence_id')['rot_angle'].diff().fillna(0)\n\n    linear_accel_list = []\n    for _, group in data.groupby('sequence_id'):\n        acc_data_group = group[['acc_x', 'acc_y', 'acc_z']]\n        rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n        linear_accel_group = remove_gravity_from_acc(acc_data_group, rot_data_group)\n        linear_accel_list.append(pd.DataFrame(linear_accel_group, columns=['linear_acc_x', 'linear_acc_y', 'linear_acc_z'], index=group.index))\n    \n    df_linear_accel = pd.concat(linear_accel_list)\n    data = pd.concat([data, df_linear_accel], axis=1)\n    data['linear_acc_mag'] = np.sqrt(data['linear_acc_x']**2 + data['linear_acc_y']**2 + data['linear_acc_z']**2)\n    data['linear_acc_mag_jerk'] = data.groupby('sequence_id')['linear_acc_mag'].diff().fillna(0)\n    angular_vel_list = []\n    for _, group in data.groupby('sequence_id'):\n        rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n        angular_vel_group = calculate_angular_velocity_from_quat(rot_data_group)\n        angular_vel_list.append(pd.DataFrame(angular_vel_group, columns=['angular_vel_x', 'angular_vel_y', 'angular_vel_z'], index=group.index))\n    \n    df_angular_vel = pd.concat(angular_vel_list)\n    data = pd.concat([data, df_angular_vel], axis=1)\n    \n    print(\"  Calculating angular distance between successive quaternions...\")\n    angular_distance_list = []\n    for _, group in data.groupby('sequence_id'):\n        rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n        angular_dist_group = calculate_angular_distance(rot_data_group)\n        angular_distance_list.append(pd.DataFrame(angular_dist_group, columns=['angular_distance'], index=group.index))\n    \n    df_angular_distance = pd.concat(angular_distance_list)\n    data = pd.concat([data, df_angular_distance], axis=1)\n    data = pl.from_pandas(data)\n    cleaned_data = feature_engineering_inf(data)\n    pdf = cleaned_data.to_pandas().drop(columns=[\"sequence_id\"])\n    predictions = model.predict(pdf)\n    predictions = le.inverse_transform(predictions)\n    return predictions[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T05:36:07.349440Z","iopub.execute_input":"2025-08-28T05:36:07.349715Z","iopub.status.idle":"2025-08-28T05:36:07.369821Z","shell.execute_reply.started":"2025-08-28T05:36:07.349691Z","shell.execute_reply":"2025-08-28T05:36:07.369220Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\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            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T05:36:07.370646Z","iopub.execute_input":"2025-08-28T05:36:07.370924Z","iopub.status.idle":"2025-08-28T05:36:08.919361Z","shell.execute_reply.started":"2025-08-28T05:36:07.370896Z","shell.execute_reply":"2025-08-28T05:36:08.918212Z"}},"outputs":[],"execution_count":null}]}