{"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":12687388,"sourceType":"datasetVersion","datasetId":7888789}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## CMI - DBwSD - Inference","metadata":{}},{"cell_type":"markdown","source":"## 1. Setup","metadata":{}},{"cell_type":"code","source":"import os\nimport yaml\nfrom tqdm import tqdm\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nfrom scipy.spatial.transform import Rotation as R\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nimport kaggle_evaluation.cmi_inference_server","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:26:26.513572Z","iopub.execute_input":"2025-08-06T06:26:26.514238Z","iopub.status.idle":"2025-08-06T06:26:31.605181Z","shell.execute_reply.started":"2025-08-06T06:26:26.514214Z","shell.execute_reply":"2025-08-06T06:26:31.604565Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"competition_dataset_directory = Path('/kaggle/input/cmi-detect-behavior-with-sensor-data')\nexternal_dataset_directory = Path('/kaggle/input/cmi-dbwsd-dataset')\n\npd.set_option('display.max_rows', 1000)\npd.set_option('display.max_columns', 1000)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:26:31.606228Z","iopub.execute_input":"2025-08-06T06:26:31.606588Z","iopub.status.idle":"2025-08-06T06:26:31.610673Z","shell.execute_reply.started":"2025-08-06T06:26:31.606561Z","shell.execute_reply":"2025-08-06T06:26:31.609996Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_test = pd.read_csv(competition_dataset_directory / 'test.csv')\nprint(f'Test Set Shape {df_test.shape}')\n\nis_submission = df_test.shape[0] != 107\nprint(f'Submission: {is_submission}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:26:31.611244Z","iopub.execute_input":"2025-08-06T06:26:31.611433Z","iopub.status.idle":"2025-08-06T06:26:31.651521Z","shell.execute_reply.started":"2025-08-06T06:26:31.611419Z","shell.execute_reply":"2025-08-06T06:26:31.650773Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Models","metadata":{}},{"cell_type":"code","source":"def load_torch_model(model_directory, device):\n\n    config_path = model_directory / 'config.yaml'\n    config = yaml.load(open(config_path), Loader=yaml.FullLoader)\n\n    models = {}\n\n    for model_path in tqdm(sorted(list(model_directory.glob('model*')))):\n        model = eval(config['model']['model_class'])(**config['model']['model_args'])\n        model_path = str(model_path)\n        model.load_state_dict(torch.load(model_path, map_location=device))\n        model.eval()\n        model.to(device)\n        model_file_name = model_path.split('/')[-1].split('.')[0]\n        models[model_file_name] = model\n        print(f'Loaded {model.__class__.__name__} Model from {model_path}')\n\n    return config, models\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:26:37.306472Z","iopub.execute_input":"2025-08-06T06:26:37.307023Z","iopub.status.idle":"2025-08-06T06:26:37.312287Z","shell.execute_reply.started":"2025-08-06T06:26:37.307000Z","shell.execute_reply":"2025-08-06T06:26:37.311490Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def drop_path(x, drop_prob: float = 0., training: bool = False, scale_by_keep: bool = True):\n\n    if drop_prob == 0. or not training:\n        return x\n    keep_prob = 1 - drop_prob\n    shape = (x.shape[0],) + (1,) * (x.ndim - 1)\n    random_tensor = x.new_empty(shape).bernoulli_(keep_prob)\n    if keep_prob > 0.0 and scale_by_keep:\n        random_tensor.div_(keep_prob)\n    return x * random_tensor\n\nclass DropPath(nn.Module):\n\n    def __init__(self, drop_prob=None, scale_by_keep=True):\n        super(DropPath, self).__init__()\n        self.drop_prob = drop_prob\n        self.scale_by_keep = scale_by_keep\n\n    def forward(self, x):\n        return drop_path(x, self.drop_prob, self.training, self.scale_by_keep)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:27:21.748386Z","iopub.execute_input":"2025-08-06T06:27:21.748953Z","iopub.status.idle":"2025-08-06T06:27:21.754653Z","shell.execute_reply.started":"2025-08-06T06:27:21.748931Z","shell.execute_reply":"2025-08-06T06:27:21.753789Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EnhancedSEBlock(nn.Module):\n\n    def __init__(self, channels, reduction=8):\n\n        super(EnhancedSEBlock, self).__init__()\n\n        self.avg_pool = nn.AdaptiveAvgPool1d(1)\n        self.max_pool = nn.AdaptiveMaxPool1d(1)\n        self.excitation = nn.Sequential(\n            nn.Linear(channels * 2, channels // reduction, bias=False),\n            nn.SiLU(inplace=True),\n            nn.Linear(channels // reduction, channels, bias=False),\n            nn.Sigmoid()\n        )\n\n    def forward(self, x):\n\n        b, c, _ = x.size()\n        avg_y = self.avg_pool(x).view(b, c)\n        max_y = self.max_pool(x).view(b, c)\n        y = torch.cat([avg_y, max_y], dim=1)\n        y = self.excitation(y).view(b, c, 1)\n\n        return x * y.expand_as(x)\n\n\nclass MultiScaleConv1d(nn.Module):\n\n    def __init__(self, in_channels, out_channels, kernel_sizes):\n\n        super(MultiScaleConv1d, self).__init__()\n\n        self.conv_blocks = nn.ModuleList()\n        for kernel_size in kernel_sizes:\n            self.conv_blocks.append(nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, kernel_size, padding='same', bias=False),\n                nn.BatchNorm1d(out_channels),\n                nn.ReLU(inplace=True)\n            ))\n\n    def forward(self, x):\n        conv_block_outputs = []\n        for conv_block in self.conv_blocks:\n            conv_block_outputs.append(conv_block(x))\n        conv_block_outputs = torch.cat(conv_block_outputs, dim=1)\n        return conv_block_outputs\n\n\nclass ResidualSEBlock(nn.Module):\n\n    def __init__(self, in_channels, out_channels, kernel_size, pool_size=2, dropout_probability=0., drop_path_probability=0.):\n\n        super(ResidualSEBlock, self).__init__()\n\n        self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size, padding='same', bias=False)\n        self.bn1 = nn.BatchNorm1d(out_channels)\n        self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size, padding='same', bias=False)\n        self.bn2 = nn.BatchNorm1d(out_channels)\n        self.se = EnhancedSEBlock(out_channels, reduction=8)\n\n        self.shortcut = nn.Sequential()\n        if in_channels != out_channels:\n            self.shortcut = nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, 1, bias=False),\n                nn.BatchNorm1d(out_channels)\n            )\n\n        self.pool = nn.MaxPool1d(pool_size)\n        self.dropout = nn.Dropout(dropout_probability)\n        self.drop_path = DropPath(drop_prob=drop_path_probability)\n\n    def forward(self, x):\n\n        shortcut = self.shortcut(x)\n\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n\n        out = self.se(out)\n        out = self.drop_path(out)\n        out += shortcut\n        out = F.relu(out)\n\n        out = self.pool(out)\n        out = self.dropout(out)\n\n        return out\n\n\nclass TemporalAttention(nn.Module):\n\n    def __init__(self, hidden_dim):\n\n        super(TemporalAttention, self).__init__()\n\n        self.attention = nn.Linear(hidden_dim, 1)\n\n    def forward(self, x):\n\n        scores = torch.tanh(self.attention(x))\n        weights = F.softmax(scores.squeeze(-1), dim=1)\n        context = torch.sum(x * weights.unsqueeze(-1), dim=1)\n\n        return context\n\n\nclass TOFTHMEncoder(nn.Module):\n\n    def __init__(self, in_channels=10):\n\n        super(TOFTHMEncoder, self).__init__()\n\n        self.encoder = nn.Sequential(\n            nn.Conv2d(in_channels, 32, kernel_size=3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.Conv2d(32, 64, kernel_size=3, padding=1),\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n            nn.Conv2d(64, 128, kernel_size=3, padding=1),\n            nn.BatchNorm2d(128),\n            nn.ReLU(),\n            nn.AdaptiveAvgPool2d(1),\n        )\n\n    def forward(self, x):\n\n        batch_size, sequence_length, channel, height, width = x.shape\n        x = x.view(batch_size * sequence_length, channel, height, width)\n        x = self.encoder(x).view(batch_size, sequence_length, -1)\n        x = F.avg_pool1d(x.transpose(1, 2), kernel_size=4, stride=4).transpose(1, 2)\n\n        return x\n\n\nclass IMUHybridConvRNN(nn.Module):\n\n    def __init__(self, imu_input_channels, rnn_dimensions, n_classes):\n\n        super(IMUHybridConvRNN, self).__init__()\n\n        self.imu_branches = nn.ModuleList(\n            [\n                nn.Sequential(\n                    MultiScaleConv1d(1, 12, kernel_sizes=[3, 5, 7, 9]),\n                    ResidualSEBlock(48, 64, 3, dropout_probability=0.1, drop_path_probability=0.1),\n                    ResidualSEBlock(64, 48, 3, dropout_probability=0.1, drop_path_probability=0.1),\n                )\n                for _ in range(imu_input_channels)\n            ]\n        )\n        self.gru = nn.GRU(\n            input_size=48 * imu_input_channels,\n            hidden_size=rnn_dimensions,\n            num_layers=2,\n            batch_first=True,\n            bidirectional=True,\n            dropout=0.,\n        )\n\n        self.gesture_head = nn.Sequential(\n            nn.Linear(rnn_dimensions * 2, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Linear(512, n_classes),\n        )\n        self.phase_head = nn.Conv1d(in_channels=rnn_dimensions * 2, out_channels=1, kernel_size=(1,), padding='same')\n\n    def forward(self, imu, metadata):\n\n        batch_size, sequence_length, = imu.shape[:2]\n\n        imu_branch_outputs = []\n        for i in range(imu.shape[2]):\n            channel_input = imu[:, :, i].unsqueeze(1)\n            processed = self.imu_branches[i](channel_input)\n            imu_branch_outputs.append(processed.transpose(1, 2))\n\n        imu_branch_outputs = torch.cat(imu_branch_outputs, dim=2)\n\n        gru_output, _ = self.gru(imu_branch_outputs)\n        gru_output_sequence_length = gru_output.size(1)\n        pooled_features = gru_output.mean(dim=1)\n\n        phase_output = F.interpolate(\n            gru_output.permute(0, 2, 1),\n            scale_factor=sequence_length / gru_output_sequence_length,\n            mode='nearest'\n        )\n        phase_output = self.phase_head(phase_output).squeeze(dim=1)\n        gesture_output = self.gesture_head(pooled_features)\n\n        return gesture_output, phase_output\n\n\nclass IMUTHMTOFHybridConvRNN(nn.Module):\n\n    def __init__(self, imu_input_channels, rnn_dimensions, n_classes):\n\n        super(IMUTHMTOFHybridConvRNN, self).__init__()\n\n        self.imu_branches = nn.ModuleList(\n            [\n                nn.Sequential(\n                    MultiScaleConv1d(1, 12, kernel_sizes=[3, 5, 7, 9]),\n                    ResidualSEBlock(48, 64, 3, dropout_probability=0.1, drop_path_probability=0.1),\n                    ResidualSEBlock(64, 48, 3, dropout_probability=0.1, drop_path_probability=0.1),\n                )\n                for _ in range(imu_input_channels)\n            ]\n        )\n        self.tof_thm_encoder = TOFTHMEncoder(in_channels=10)\n        self.gru = nn.GRU(\n            input_size=48 * imu_input_channels + 128,\n            hidden_size=rnn_dimensions,\n            num_layers=2,\n            batch_first=True,\n            bidirectional=True,\n            dropout=0.,\n        )\n\n        self.gesture_head = nn.Sequential(\n            nn.Linear(rnn_dimensions * 2, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Linear(512, n_classes),\n        )\n        self.phase_head = nn.Conv1d(in_channels=rnn_dimensions * 2, out_channels=1, kernel_size=(1,), padding='same')\n\n    def forward(self, imu, thm, tof, metadata):\n\n        batch_size, sequence_length, = imu.shape[:2]\n\n        imu_branch_outputs = []\n        for i in range(imu.shape[2]):\n            channel_input = imu[:, :, i].unsqueeze(1)\n            processed = self.imu_branches[i](channel_input)\n            imu_branch_outputs.append(processed.transpose(1, 2))\n\n        imu_branch_outputs = torch.cat(imu_branch_outputs, dim=2)\n\n        thm = thm.unsqueeze(-1).unsqueeze(-1).expand(-1, -1, -1, 8, 8)\n        tof = tof.view(batch_size, sequence_length, 5, 8, 8)\n        tof_thm = torch.cat([tof, thm], dim=2)\n        tof_thm_features = self.tof_thm_encoder(tof_thm)\n        gru_input = torch.cat([imu_branch_outputs, tof_thm_features], dim=-1)\n        gru_output, _ = self.gru(gru_input)\n        gru_output_sequence_length = gru_output.size(1)\n\n        phase_output = F.interpolate(\n            gru_output.permute(0, 2, 1),\n            scale_factor=sequence_length / gru_output_sequence_length,\n            mode='nearest'\n        )\n        phase_output = self.phase_head(phase_output).squeeze(dim=1)\n\n        pooled_output = gru_output.mean(dim=1)\n        gesture_output = self.gesture_head(pooled_output)\n\n        return gesture_output, phase_output\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:27:22.189865Z","iopub.execute_input":"2025-08-06T06:27:22.190634Z","iopub.status.idle":"2025-08-06T06:27:22.215769Z","shell.execute_reply.started":"2025-08-06T06:27:22.190605Z","shell.execute_reply":"2025-08-06T06:27:22.214868Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device('cuda')\n\nimu_model_config, imu_models = load_torch_model(external_dataset_directory / 'imu_model', device=device)\nimu_thm_tof_model_config, imu_thm_tof_models = load_torch_model(external_dataset_directory / 'imu_thm_tof_model', device=device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:28:17.025182Z","iopub.execute_input":"2025-08-06T06:28:17.025442Z","iopub.status.idle":"2025-08-06T06:28:25.942841Z","shell.execute_reply.started":"2025-08-06T06:28:17.025425Z","shell.execute_reply":"2025-08-06T06:28:25.942076Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Preprocessing","metadata":{}},{"cell_type":"code","source":"def create_imu_features(df):\n\n    df['acc_mag'] = np.sqrt(df['acc_x'] ** 2 + df['acc_y'] ** 2 + df['acc_z'] ** 2)\n    df['rot_mag'] = np.sqrt(df['rot_w'] ** 2 + df['rot_x'] ** 2 + df['rot_y'] ** 2 + df['rot_z'] ** 2)\n    df['rot_angle'] = 2 * np.arccos(df['rot_w'].clip(-1, 1))\n\n    acceleration = df[['acc_x', 'acc_y', 'acc_z']].values\n    quaternion = df[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    quaternion_shift = df[['rot_x', 'rot_y', 'rot_z', 'rot_w']].shift(1).bfill().values\n\n    linear_acceleration = np.zeros((df.shape[0], 3))\n    angular_velocity = np.zeros((df.shape[0], 3))\n    \n    mask = np.all(np.isnan(quaternion), axis=1) | np.all(np.isclose(quaternion, 0), axis=1)\n    mask_shift = np.all(np.isnan(quaternion_shift), axis=1) | np.all(np.isclose(quaternion_shift, 0), axis=1)\n    mask = mask & mask_shift\n\n    if np.sum(~mask) > 0:\n        \n        rotation = R.from_quat(quaternion[~mask])\n        rotation_shift = R.from_quat(quaternion_shift[~mask])\n        \n        delta_rotation = (rotation.inv() * rotation_shift).as_rotvec()\n        angular_velocity[~mask] = delta_rotation\n        df[['angular_vel_x', 'angular_vel_y', 'angular_vel_z']] = angular_velocity\n        df['angular_vel_mag'] = np.sqrt(df['angular_vel_x'] ** 2 + df['angular_vel_y'] ** 2 + df['angular_vel_z'] ** 2)\n        \n        linear_acceleration[mask] = acceleration[mask]\n        gravity_world = np.array([0, 0, 9.81])\n        gravity_sensor_frame = rotation.apply(gravity_world, inverse=True)\n        linear_acceleration[~mask] = gravity_sensor_frame\n        df[['linear_acc_x', 'linear_acc_y', 'linear_acc_z']] = linear_acceleration\n        df['linear_acc_mag'] = np.sqrt(df['linear_acc_x'] ** 2 + df['linear_acc_y'] ** 2 + df['linear_acc_z'] ** 2)\n\n    else:\n        df[['angular_vel_x', 'angular_vel_y', 'angular_vel_z', 'angular_vel_mag']] = 0.\n        df[['linear_acc_x', 'linear_acc_y', 'linear_acc_z', 'linear_acc_mag']] = 0.\n\n    df['acc_x_diff_1'] = df['acc_x'].diff(1).fillna(0)\n    df['acc_y_diff_1'] = df['acc_y'].diff(1).fillna(0)\n    df['acc_z_diff_1'] = df['acc_z'].diff(1).fillna(0)\n    df['rot_x_diff_1'] = df['rot_x'].diff(1).fillna(0)\n    df['rot_y_diff_1'] = df['rot_y'].diff(1).fillna(0)\n    df['rot_z_diff_1'] = df['rot_z'].diff(1).fillna(0)\n    df['rot_w_diff_1'] = df['rot_w'].diff(1).fillna(0)\n    df['acc_mag_diff_1'] = df['acc_mag'].diff(1).fillna(0)\n    df['rot_mag_diff_1'] = df['rot_mag'].diff(1).fillna(0)\n    df['linear_acc_x_diff_1'] = df['linear_acc_x'].diff(1).fillna(0)\n    df['linear_acc_y_diff_1'] = df['linear_acc_y'].diff(1).fillna(0)\n    df['linear_acc_z_diff_1'] = df['linear_acc_z'].diff(1).fillna(0)\n    df['linear_acc_mag_diff_1'] = df['linear_acc_mag'].diff(1).fillna(0)\n    df['angular_vel_x_diff_1'] = df['angular_vel_x'].diff(1).fillna(0)\n    df['angular_vel_y_diff_1'] = df['angular_vel_y'].diff(1).fillna(0)\n    df['angular_vel_z_diff_1'] = df['angular_vel_z'].diff(1).fillna(0)\n    df['angular_vel_mag_diff_1'] = df['angular_vel_mag'].diff(1).fillna(0)\n\n    return df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:31:26.508082Z","iopub.execute_input":"2025-08-06T06:31:26.508428Z","iopub.status.idle":"2025-08-06T06:31:26.520269Z","shell.execute_reply.started":"2025-08-06T06:31:26.508405Z","shell.execute_reply":"2025-08-06T06:31:26.519619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def crop_and_pad(\n    imu, thm, tof,\n    sequence_length,\n    min_gesture_crop_ratio=0., max_gesture_crop_ratio=0.,\n    min_transition_crop_ratio=0., max_transition_crop_ratio=0.,\n    random_padding=False\n):\n    \n    current_sequence_length = imu.shape[0]\n\n    indices = np.arange(current_sequence_length)\n    transition_indices = indices[:len(indices) // 2]\n    transition_length = len(transition_indices)\n    transition_ratio = 0.5\n    gesture_indices = indices[len(indices) // 2:]\n    gesture_length = len(gesture_indices)\n    gesture_ratio = 0.5    \n\n    # Estimate minimum required crop to match desired sequence length\n    min_required_crop = np.maximum(0, current_sequence_length - sequence_length)\n\n    crop_start = 0\n    crop_end = current_sequence_length\n    gesture_crop_deficit = 0\n\n    if min_required_crop >= transition_length:\n        crop_start = imu.shape[0] - sequence_length\n        crop_end = imu.shape[0]\n    else:\n        if gesture_ratio > 0:\n    \n            # Compute minimum and maximum gesture crop values based on configuration ratios\n            min_config_gesture_crop = int(gesture_length * min_gesture_crop_ratio)\n            max_config_gesture_crop = int(gesture_length * max_gesture_crop_ratio)\n            # Estimate the minimum gesture crop required to meet the total crop demand proportionally\n            min_required_gesture_crop = int(np.floor(min_required_crop * gesture_ratio))\n    \n            # Final gesture crop range is constrained between:\n            #   - At least the greater of config minimum or required crop\n            #   - At most the smaller of config maximum and actual gesture length\n            min_gesture_crop = np.maximum(min_config_gesture_crop, min_required_gesture_crop)\n            min_gesture_crop = np.minimum(min_gesture_crop, max_config_gesture_crop)\n            max_gesture_crop = np.minimum(max_config_gesture_crop, len(gesture_indices) - 1)\n    \n            gesture_crop_offset = np.random.randint(min_gesture_crop, max_gesture_crop + 1)\n            gesture_crop_deficit = np.maximum(0, (min_required_gesture_crop - gesture_crop_offset))\n            if gesture_crop_offset > 0:\n                crop_end = gesture_indices[len(gesture_indices) - gesture_crop_offset]\n    \n        if transition_ratio > 0:\n    \n            # Compute minimum and maximum transition crop values based on configuration ratios.\n            min_config_transition_crop = int(transition_length * min_transition_crop_ratio)\n            max_config_transition_crop = int(transition_length * max_transition_crop_ratio)\n            # Estimate the minimum transition crop required, adjusted by any gesture crop deficit\n            min_required_transition_crop = int(np.ceil(min_required_crop * transition_ratio)) + gesture_crop_deficit\n    \n            # Final transition crop range is constrained to:\n            #   - At least the greater of config minimum or required crop plus gesture deficit\n            #   - At most the maximum allowed by config, ensuring valid sampling range\n            min_transition_crop = np.maximum(min_config_transition_crop, min_required_transition_crop)\n            max_transition_crop = np.maximum(min_transition_crop, max_config_transition_crop)\n    \n            transition_crop_offset = np.random.randint(min_transition_crop, max_transition_crop + 1)\n            if transition_crop_offset > 0:\n                crop_start = transition_indices[transition_crop_offset]\n\n    imu = imu[crop_start:crop_end, :]\n    thm = thm[crop_start:crop_end, :]\n    tof = tof[crop_start:crop_end, :]\n\n    total_pad = sequence_length - imu.shape[0]\n    \n    if total_pad > 0:\n        \n        if random_padding:\n            transition_pad = np.random.randint(0, total_pad + 1)\n            gesture_pad = total_pad - transition_pad\n        else:\n            transition_pad = total_pad\n            gesture_pad = 0\n\n        imu = np.vstack((\n            np.zeros((transition_pad, imu.shape[1])),\n            imu,\n            np.zeros((gesture_pad, imu.shape[1])),\n        ))\n\n        thm = np.vstack((\n            np.zeros((transition_pad, thm.shape[1])),\n            thm,\n            np.zeros((gesture_pad, thm.shape[1])),\n        ))\n\n        tof = np.vstack((\n            np.zeros((transition_pad, tof.shape[1])),\n            tof,\n            np.zeros((gesture_pad, tof.shape[1])),\n        ))\n\n    return imu, thm, tof\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:34:18.173730Z","iopub.execute_input":"2025-08-06T06:34:18.174009Z","iopub.status.idle":"2025-08-06T06:34:18.184613Z","shell.execute_reply.started":"2025-08-06T06:34:18.173989Z","shell.execute_reply":"2025-08-06T06:34:18.183878Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def nn_predict(imu, thm, tof, models, verbose=False):\n\n    if thm is not None and tof is not None:\n        inputs = {'imu': imu, 'thm': thm, 'tof': tof, 'metadata': None}\n    else:\n        inputs = {'imu': imu, 'metadata': None}\n    \n    predictions = torch.zeros(1, 18, device=device)\n\n    for model_file_name, model in models.items():\n        \n        with torch.no_grad():\n            gesture_outputs, phase_outputs = model(**inputs)\n\n        predictions += gesture_outputs / len(models)\n\n    predictions = torch.softmax(predictions, dim=-1).squeeze(dim=0).cpu().numpy()\n    \n    return predictions\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:52:15.290503Z","iopub.execute_input":"2025-08-06T06:52:15.290753Z","iopub.status.idle":"2025-08-06T06:52:15.296138Z","shell.execute_reply.started":"2025-08-06T06:52:15.290736Z","shell.execute_reply":"2025-08-06T06:52:15.295419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence, demographics):\n\n    df_sequence = sequence.to_pandas()\n\n    raw_imu_columns = [\n        'acc_x', 'acc_y', 'acc_z',\n        'rot_x', 'rot_y', 'rot_z', 'rot_w'\n    ]\n    thm_columns = [column for column in df_sequence.columns if column.startswith('thm')]\n    tof_columns = [column for column in df_sequence.columns if column.startswith('tof')]\n    \n    df_sequence = df_sequence.loc[:, raw_imu_columns + thm_columns + tof_columns]\n    df_sequence = df_sequence.interpolate(method='linear', limit_area='inside').fillna(0.)\n    \n    df_sequence = create_imu_features(df_sequence)\n\n    imu_columns = [\n        'acc_x', 'acc_y', 'acc_z',\n        'rot_x', 'rot_y', 'rot_z', 'rot_w',\n\n        'acc_mag', 'rot_mag', 'rot_angle',\n\n        'linear_acc_x', 'linear_acc_y', 'linear_acc_z', 'linear_acc_mag',\n\n        'acc_x_diff_1', 'acc_y_diff_1', 'acc_z_diff_1',\n        'rot_x_diff_1', 'rot_y_diff_1', 'rot_z_diff_1', 'rot_w_diff_1',\n\n        'acc_mag_diff_1', 'rot_mag_diff_1',\n\n        'linear_acc_x_diff_1', 'linear_acc_y_diff_1', 'linear_acc_z_diff_1', 'linear_acc_mag_diff_1',\n\n        'angular_vel_x', 'angular_vel_y', 'angular_vel_z',\n        'angular_vel_mag',\n\n        'angular_vel_x_diff_1', 'angular_vel_y_diff_1', 'angular_vel_z_diff_1',\n        'angular_vel_mag_diff_1'\n    ]\n    \n    imu = df_sequence.loc[:, imu_columns].values\n    thm = df_sequence.loc[:, thm_columns].values\n    tof = df_sequence.loc[:, tof_columns].values\n    \n    imu, thm, tof = crop_and_pad(imu=imu, thm=thm, tof=tof, sequence_length=96)\n    print(f'IMU {imu.shape} - THM {thm.shape} - TOF {tof.shape}')\n\n    imu = torch.as_tensor(imu.copy(), dtype=torch.float)\n    imu = torch.unsqueeze(imu, dim=0)\n    imu = imu.to(device)\n\n    thm = torch.as_tensor(thm.copy(), dtype=torch.float)\n    thm = torch.unsqueeze(thm, dim=0)\n    thm = thm.to(device)\n\n    tof = torch.as_tensor(tof.copy(), dtype=torch.float)\n    tof = torch.unsqueeze(tof, dim=0)\n    tof = tof.to(device)\n    print(f'IMU {imu.shape} - THM {thm.shape} - TOF {tof.shape}')\n\n    if torch.sum(thm) == 0 and torch.sum(tof) == 0:\n        print('predict with 1')\n        model_predictions = nn_predict(imu=imu, thm=None, tof=None, models=imu_models)\n    else:\n        print('predict with 2')\n        model_predictions = nn_predict(imu=imu, thm=thm, tof=tof, models=imu_thm_tof_models)\n    \n    model_predictions = int(np.argmax(model_predictions))\n    \n    print(model_predictions)\n\n    gesture_mapping = {\n        # Target gestures\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        # Non-target gestures\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\n    class_prediction = gesture_mapping[model_predictions]\n    print(class_prediction)\n    \n    return class_prediction","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:55:53.986359Z","iopub.execute_input":"2025-08-06T06:55:53.987052Z","iopub.status.idle":"2025-08-06T06:55:54.001826Z","shell.execute_reply.started":"2025-08-06T06:55:53.987025Z","shell.execute_reply":"2025-08-06T06:55:54.001240Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"inference_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    )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:55:54.283757Z","iopub.execute_input":"2025-08-06T06:55:54.284404Z","iopub.status.idle":"2025-08-06T06:55:56.023699Z","shell.execute_reply.started":"2025-08-06T06:55:54.284383Z","shell.execute_reply":"2025-08-06T06:55:56.023129Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}