{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12385021,"sourceType":"datasetVersion","datasetId":7797394}],"dockerImageVersionId":31041,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport polars as pl\nimport pandas as pd\nimport torch\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.utils.class_weight import compute_class_weight\n\n\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torch.optim as optim\nfrom torch.optim import AdamW\nfrom torch.optim.lr_scheduler import CosineAnnealingWarmRestarts\nfrom scipy.spatial.transform import Rotation as R\n\nimport os\nimport joblib\nimport random\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:17:00.723500Z","iopub.execute_input":"2025-07-06T01:17:00.723854Z","iopub.status.idle":"2025-07-06T01:17:00.729083Z","shell.execute_reply.started":"2025-07-06T01:17:00.723822Z","shell.execute_reply":"2025-07-06T01:17:00.728345Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMU_DIM = 42\nBATCH_SIZE = 64\nPAD_LEN = 151\nLR_INIT = 5e-4\nWD = 3e-3\nMIXUP_ALPHA = 0.4\nEPOCHS = 200\nPATIENCE = 40\nSEED = 3126\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:17:00.732238Z","iopub.execute_input":"2025-07-06T01:17:00.732632Z","iopub.status.idle":"2025-07-06T01:17:00.744728Z","shell.execute_reply.started":"2025-07-06T01:17:00.732616Z","shell.execute_reply":"2025-07-06T01:17:00.744209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def seed_all(seed=3126):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n\ndef worker_init_fn(worker_id):\n    np.random.seed(SEED + worker_id)\n    random.seed(SEED + worker_id)\n\nseed_all()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:17:00.745770Z","iopub.execute_input":"2025-07-06T01:17:00.746007Z","iopub.status.idle":"2025-07-06T01:17:00.759204Z","shell.execute_reply.started":"2025-07-06T01:17:00.745985Z","shell.execute_reply":"2025-07-06T01:17:00.758595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preprocessor = joblib.load(\"/kaggle/input/full-model-submission/preprocessor.joblib\")\nlabel_encoder = joblib.load(\"/kaggle/input/full-model-submission/label_encoder.joblib\")\nfeatures = joblib.load(\"/kaggle/input/full-model-submission/features.joblib\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:17:00.761396Z","iopub.execute_input":"2025-07-06T01:17:00.761618Z","iopub.status.idle":"2025-07-06T01:17:00.779882Z","shell.execute_reply.started":"2025-07-06T01:17:00.761596Z","shell.execute_reply":"2025-07-06T01:17:00.779230Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(features[\"full_features\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:17:00.781163Z","iopub.execute_input":"2025-07-06T01:17:00.781405Z","iopub.status.idle":"2025-07-06T01:17:00.785302Z","shell.execute_reply.started":"2025-07-06T01:17:00.781383Z","shell.execute_reply":"2025-07-06T01:17:00.784657Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CoordAttention(nn.Module):\n    \"\"\"\n    Coordinate Attention for Sequences.\n    Input Dimension: (B, T, C)\n    Output Dimension: (B, T, C)\n    \"\"\"\n    def __init__(self, channels, reduction=8):\n        super(CoordAttention, self).__init__()\n        self.mid_channels = max(8, channels // reduction)\n\n        self.compression = nn.Sequential(\n            nn.Conv1d(channels, self.mid_channels, kernel_size=1, bias=False),\n            nn.BatchNorm1d(self.mid_channels),\n            nn.SiLU(inplace=True)\n        )\n\n        # Attention branches\n        self.time_conv = nn.Conv1d(1, 1, kernel_size=5, padding=2, bias=False)  \n        self.channel_conv = nn.Conv1d(self.mid_channels, channels, kernel_size=1, bias=False)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        # x: (B, T, C)\n        x_p = x.permute(0, 2, 1)  # (B, C, T)\n        f   = self.compression(x_p)  # (B, rC, T)\n\n        ## Time Attention (B, 1, T)\n        f_t = f.mean(dim=1, keepdim=True)      \n        time_attn = self.sigmoid(self.time_conv(f_t))  \n\n        ## Channel Attention (B, C, 1)\n        f_c = f.mean(dim=2, keepdim=True)      \n        channel_attn = self.sigmoid(self.channel_conv(f_c)) \n\n        ## (B, T, C)\n        out = (x_p * time_attn * channel_attn).permute(0,2,1)\n        return out","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-07-06T01:17:00.785949Z","iopub.execute_input":"2025-07-06T01:17:00.786248Z","iopub.status.idle":"2025-07-06T01:17:00.797611Z","shell.execute_reply.started":"2025-07-06T01:17:00.786231Z","shell.execute_reply":"2025-07-06T01:17:00.796891Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ResCNNBlock(nn.Module):\n    \"\"\"\n    Residual CNN Block with Coordinate Attention. \n    Input Dimension: (B, T_in, C_in)\n    Output Dimension: (B, T_out, C_out)\n    \"\"\"\n    def __init__(self, in_channels, out_channels, kernel_size=3, pool_size=2, drop=0.2):\n        super(ResCNNBlock, self).__init__()\n\n        ## CNN model\n        self.cnn = 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            nn.Conv1d(out_channels, out_channels, kernel_size, padding='same', bias=False),\n            nn.BatchNorm1d(out_channels),\n            nn.ReLU(inplace=True)\n        )\n        ## Attention module\n        self.attention = CoordAttention(out_channels)\n        ## Projection path for skip connection\n        self.shortcut_proj = None\n        if in_channels != out_channels:\n            self.shortcut_proj = nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, kernel_size=1, padding='same', bias=False),\n                nn.BatchNorm1d(out_channels)\n            )\n        ## Final processing components\n        self.relu_final = nn.ReLU(inplace=True)\n        if pool_size is not None: \n            self.max_pool = nn.MaxPool1d(pool_size)\n        else: \n            self.max_pool = None\n        self.dropout = nn.Dropout(drop)\n\n    def forward(self, x):\n        # (B, C_in, T)\n        shortcut = x.permute(0,2,1)\n        x_p = x.permute(0,2,1)\n        \n        out = self.cnn(x_p)                         # (B, C_out, T)\n        out = self.attention(out.permute(0, 2, 1))  # (B, T, C_out)\n        out = out.permute(0, 2, 1)                  # (B, C_out, T)\n        \n        ## Handle shortcut connection -> (B, C_out, T)\n        if self.shortcut_proj:\n            shortcut = self.shortcut_proj(shortcut)\n\n        ## Residual connection\n        out += shortcut\n        out = self.relu_final(out)\n        if self.max_pool is not None:\n            ## (B, C_out, T) -> (B, C_out, T//pool_size)\n            out = self.max_pool(out)\n        out = self.dropout(out)\n        ## (B, T_out, C_out)\n        out = out.permute(0, 2, 1)\n        return out","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-07-06T01:17:00.799289Z","iopub.execute_input":"2025-07-06T01:17:00.799473Z","iopub.status.idle":"2025-07-06T01:17:00.817811Z","shell.execute_reply.started":"2025-07-06T01:17:00.799459Z","shell.execute_reply":"2025-07-06T01:17:00.817259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TempCNNBlock(nn.Module):\n    \"\"\"\n    Temporal CNN Block with Dilated Convolution, Attention, and Skip Connection.\n    Input Dimension: (B, T, C_in)\n    Output Dimension: (B, T, C_out)\n    \"\"\"\n    def __init__(self, in_channels, out_channels, kernel_size=3, dilation=1, drop=0.25):\n        super(TempCNNBlock, self).__init__()\n        ## Padding computation\n        padding = (kernel_size - 1) * dilation // 2\n        ## Temporal Convolution block\n        self.conv = nn.Sequential(\n            nn.Conv1d(in_channels, out_channels, kernel_size=kernel_size,\n                      dilation=dilation, padding=padding, bias=False),\n            nn.BatchNorm1d(out_channels),\n            nn.ReLU(inplace=True)\n        )\n        ## Attention module\n        self.attention = CoordAttention(out_channels)\n        ## Dropout\n        self.dropout = nn.Dropout(drop)\n        \n        ## Shortcut for skip connection\n        self.shortcut_proj = None\n        if in_channels != out_channels:\n            self.shortcut_proj = nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, kernel_size=1, padding='same', bias=False),\n                nn.BatchNorm1d(out_channels)\n            )\n        self.relu_final = nn.ReLU()\n        \n    def forward(self, x):\n        ## Processes skip connection\n        # (B, C_in, T)\n        x_p      = x.permute(0,2,1)\n        shortcut = x.permute(0,2,1)\n        # (B, C_out, T)\n        if self.shortcut_proj:\n            shortcut = self.shortcut_proj(shortcut)\n        ## Processes output\n        out = self.conv(x_p)                        # (B, C_out, T)\n        out = self.attention(out.permute(0,2,1))    # (B, T, C_out)\n        out = out.permute(0,2,1)                    # (B, C_out, T)\n        ## Skip connection\n        out = self.relu_final(out + shortcut) # (B, C_out, T)\n        out = self.dropout(out)\n        return out.permute(0,2,1)  # (B, T, C_out)","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-07-06T01:17:00.818678Z","iopub.execute_input":"2025-07-06T01:17:00.818942Z","iopub.status.idle":"2025-07-06T01:17:00.833514Z","shell.execute_reply.started":"2025-07-06T01:17:00.818921Z","shell.execute_reply":"2025-07-06T01:17:00.832796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TofCompressor(nn.Module):\n    \"\"\"\n    Module for compressing TOF sensors' information.\n    Input Dimension: (B, T, C_in)\n    Output Dimension: (B, T, C_out)\n    \"\"\"\n    def __init__(self, out_channels=16):\n        super(TofCompressor, self).__init__()\n        self.cnn = nn.Sequential(\n            nn.Conv2d(5, out_channels, kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2),\n            \n            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n            \n            nn.AdaptiveAvgPool2d((1, 1)) \n        )\n\n    def forward(self, x):\n        B, T, _ = x.shape\n        x = x.view(B*T, 5, 8, 8)\n        ## (B*T, C_out, 1, 1)\n        out = self.cnn(x) \n        ## (B, T, C_out)\n        out = out.view(B, T, -1)\n        return out","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-07-06T01:17:00.834258Z","iopub.execute_input":"2025-07-06T01:17:00.834475Z","iopub.status.idle":"2025-07-06T01:17:00.848640Z","shell.execute_reply.started":"2025-07-06T01:17:00.834452Z","shell.execute_reply":"2025-07-06T01:17:00.847888Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MLPAttention(nn.Module):\n    def __init__(self, feature_dim):\n        super(MLPAttention, self).__init__()\n        self.attn = nn.Sequential(\n            nn.Linear(feature_dim, feature_dim//8),\n            nn.SiLU(inplace=True),\n            nn.Linear(feature_dim//8, 1)\n        )\n\n    def forward(self, x):\n        # inputs shape: (B, T, C)\n        weights = self.attn(x)  # (B, T, 1)\n        weights = F.softmax(weights, dim=1)  # (B, T, 1)\n        context = (x * weights).sum(dim=1)  # (B, C)\n        return context","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-07-06T01:17:00.849554Z","iopub.execute_input":"2025-07-06T01:17:00.849754Z","iopub.status.idle":"2025-07-06T01:17:00.863840Z","shell.execute_reply.started":"2025-07-06T01:17:00.849738Z","shell.execute_reply":"2025-07-06T01:17:00.863159Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class GestureClassifier(nn.Module):\n    \"\"\"\n    Gesture Classifier using CNN blocks only.\n    \"\"\"\n    def __init__(self, imu_dim=45):\n        super(GestureClassifier, self).__init__()\n        self.imu_dim = imu_dim\n        \n        ## (B,151,45) --> (B, 151//4, 512) = (B, 37, 512)\n        self.imu_model =  nn.Sequential(\n            ResCNNBlock(imu_dim, 128, kernel_size=3, pool_size=2, drop=0.2),\n            ResCNNBlock(128, 256, kernel_size=3, pool_size=2, drop=0.2),\n            ResCNNBlock(256, 512, kernel_size=5, pool_size=None, drop=0.2),\n        )\n        \n        ## (B, 151, 24) --> (B, 151//4, 256) = (B, 37, 256)\n        self.tof_compressor = TofCompressor()\n        self.thm_tof_model = nn.Sequential(\n            nn.Conv1d(24, 128, kernel_size=3, padding='same', bias=False),\n            nn.BatchNorm1d(128),\n            nn.ReLU(inplace=True),\n            nn.MaxPool1d(2),\n            nn.Dropout(.2),\n            \n            nn.Conv1d(128, 128, kernel_size=3, padding='same', bias=False),\n            nn.BatchNorm1d(128),\n            nn.ReLU(inplace=True),\n            nn.MaxPool1d(2),\n            nn.Dropout(.2),\n            \n            nn.Conv1d(128, 256, kernel_size=3, padding='same', bias=False),\n            nn.BatchNorm1d(256),\n            nn.ReLU(inplace=True),\n            nn.Dropout(.2)\n        )\n\n        self.merged_attention = CoordAttention(768)\n        self.merged_dropout   = nn.Dropout(1/3)\n        self.temporalCNN      = nn.Sequential(\n            TempCNNBlock(768, 512, kernel_size=3, dilation=1, drop=0.2),\n            TempCNNBlock(512, 256, kernel_size=3, dilation=2, drop=0.2),\n            TempCNNBlock(256, 256, kernel_size=3, dilation=4, drop=0.2),\n            TempCNNBlock(256, 128, kernel_size=3, dilation=8, drop=0.2),\n        )\n\n        self.classifier_attention = MLPAttention(128)\n        self.classifier_dropout   = nn.Dropout(.2)\n        self.classifier = nn.Sequential(\n            nn.Linear(128, 32, bias=False),\n            nn.BatchNorm1d(32),\n            nn.Dropout(.2),\n            \n            nn.Linear(32, 18)\n        )\n        \n    def forward(self, x):\n        x_imu     = x[:, :, :self.imu_dim]                 # (B, 151, 45)\n        x_thm     = x[:, :, self.imu_dim : self.imu_dim+8] # (B, 151, 8)\n        x_tof     = x[:, :, self.imu_dim+8 : ]             # (B, 151, 320)\n\n        ## Extract compressed TOF features and merge features with thm data\n        x_tof        = self.tof_compressor(x_tof) # (B, 151, 16)\n        x_thm_tof    = torch.concat([x_thm, x_tof], dim=2) # (B, 151, 24)\n        x_thm_tof    = x_thm_tof.permute(0, 2, 1) # (B, 24, 151)\n        \n        ## Processes imu and thm-tof data\n        x_imu     = self.imu_model(x_imu)  # (B, 37, 512)\n        x_thm_tof = self.thm_tof_model(x_thm_tof).permute(0, 2, 1) # (B, 37, 256)\n        \n        ## Merge features and process them with temporal convolution\n        x_merged = torch.concat([x_imu, x_thm_tof], dim=2) # (B, 37, 768)\n        x_merged = self.merged_attention(x_merged)\n        x_merged = self.merged_dropout(x_merged)\n        out = self.temporalCNN(x_merged) # (B, 37, 128)\n\n        ## Provides output to classifier\n        out = self.classifier_attention(out) # (B, 128)\n        out = self.classifier_dropout(out)\n        out = self.classifier(out)           # (B, 18)\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:17:00.865415Z","iopub.execute_input":"2025-07-06T01:17:00.865607Z","iopub.status.idle":"2025-07-06T01:17:00.878078Z","shell.execute_reply.started":"2025-07-06T01:17:00.865593Z","shell.execute_reply":"2025-07-06T01:17:00.877498Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def pad_sequences(sequences, maxlen, padding='post', truncating='post', dtype=torch.float32):\n    \"\"\"\n    Pads a list of PyTorch tensors to a specified maxlen.\n    \"\"\"\n    if not sequences: return torch.empty(0, maxlen, 0, dtype=dtype) # Handle empty list of sequences\n\n    # Assume all sequences have the same feature dimension\n    feature_dim = sequences[0].shape[-1]\n    # Convert all sequences to tensor type\n    sequences = [torch.tensor(seq, dtype=dtype) if not isinstance(seq, torch.Tensor) else seq \n                 for seq in sequences]\n    \n    padded_sequences = []\n    for seq in sequences:\n        current_len = seq.shape[0]\n        \n        # Truncate if necessary\n        if current_len > maxlen:\n            if truncating == 'pre':\n                truncated_seq = seq[-maxlen:]\n            else: # 'post'\n                truncated_seq = seq[:maxlen]\n        else:\n            truncated_seq = seq\n\n        padding_needed = maxlen - truncated_seq.shape[0]\n        \n        # Pad if necessary\n        if padding_needed > 0:\n            if padding == 'pre':\n                # F.pad format: (pad_left, pad_right, pad_top, pad_bottom, ...)\n                # For (timesteps, features), we pad (0,0) for features, (padding_needed, 0) for timesteps\n                padded_seq = F.pad(truncated_seq, (0, 0, padding_needed, 0), 'constant', 0)\n            else: # 'post'\n                padded_seq = F.pad(truncated_seq, (0, 0, 0, padding_needed), 'constant', 0)\n        else:\n            padded_seq = truncated_seq\n        padded_sequences.append(padded_seq)\n            \n    return torch.stack(padded_sequences).to(dtype)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:17:00.878901Z","iopub.execute_input":"2025-07-06T01:17:00.879157Z","iopub.status.idle":"2025-07-06T01:17:00.892927Z","shell.execute_reply.started":"2025-07-06T01:17:00.879130Z","shell.execute_reply":"2025-07-06T01:17:00.892382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = GestureClassifier().to(DEVICE)\nmodel.load_state_dict(torch.load(\"/kaggle/input/full-model-submission/best_model.pth\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:17:00.893661Z","iopub.execute_input":"2025-07-06T01:17:00.893837Z","iopub.status.idle":"2025-07-06T01:17:01.025034Z","shell.execute_reply.started":"2025-07-06T01:17:00.893824Z","shell.execute_reply":"2025-07-06T01:17:01.024466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_features(data):\n    df = data.with_columns(  \n        ## Relative time linear feature\n        ((pl.col(\"sequence_counter\") / pl.col(\"sequence_counter\").max().over(\"sequence_id\")) + .1).alias(\"relative_time_imu\"),\n        ((pl.col(\"sequence_counter\") / pl.col(\"sequence_counter\").max().over(\"sequence_id\")) + .1).alias(\"relative_time_thmTof\")\n        \n    ).with_columns(\n        ## Relative time trig features\n        (pl.col(\"relative_time_imu\") * np.pi).sin().alias(\"relative_time_sin_imu\"),\n        (pl.col(\"relative_time_imu\") * np.pi).cos().alias(\"relative_time_cos_imu\"),\n        (pl.col(\"relative_time_thmTof\") * np.pi).sin().alias(\"relative_time_sin_thmTof\"),\n        (pl.col(\"relative_time_thmTof\") * np.pi).cos().alias(\"relative_time_cos_thmTof\"),\n        \n        ## Linear acceleration magnitude\n        pl.fold(\n            acc=pl.lit(0),\n            function=lambda acc, x: acc + x**2,\n            exprs=[pl.col(\"linear_acc_x\"), pl.col(\"linear_acc_y\"), pl.col(\"linear_acc_z\")]\n        ).sqrt().alias(\"linear_acc_mag\"),\n    \n        ## Angular velocity magnitude\n        pl.fold(\n            acc=pl.lit(0),\n            function=lambda acc, x: acc + x**2,\n            exprs=[pl.col(\"ang_vel_x\"), pl.col(\"ang_vel_y\"), pl.col(\"ang_vel_z\")]\n        ).sqrt().alias(\"ang_vel_mag\"),\n        \n        ## Rotational angle\n        (2*np.arccos(pl.col(\"rot_w\").clip(-1, 1))).alias(\"rot_angle\")\n    \n    ).with_columns(\n        ## Differential features of magnitude components\n        pl.col(\"linear_acc_mag\").diff().over(\"sequence_id\").fill_null(0).alias(\"linear_acc_mag_diff\"),\n        pl.col(\"ang_vel_mag\").diff().over(\"sequence_id\").fill_null(0).alias(\"ang_vel_mag_diff\"),\n        pl.col(\"rot_angle\").diff().over(\"sequence_id\").fill_null(0).alias(\"rot_angle_diff\"),\n        \n        ## Differential features for linear acceleration features\n        pl.col(\"linear_acc_x\").diff().over(\"sequence_id\").fill_null(0).alias(\"linear_acc_x_diff\"),\n        pl.col(\"linear_acc_y\").diff().over(\"sequence_id\").fill_null(0).alias(\"linear_acc_y_diff\"),\n        pl.col(\"linear_acc_z\").diff().over(\"sequence_id\").fill_null(0).alias(\"linear_acc_z_diff\"),\n\n        ## Differential features for angular velocity features\n        pl.col(\"ang_vel_x\").diff().over(\"sequence_id\").fill_null(0).alias(\"ang_vel_x_diff\"),\n        pl.col(\"ang_vel_y\").diff().over(\"sequence_id\").fill_null(0).alias(\"ang_vel_y_diff\"),\n        pl.col(\"ang_vel_z\").diff().over(\"sequence_id\").fill_null(0).alias(\"ang_vel_z_diff\"),\n    \n        ## Differential feature for angular distance\n        pl.col(\"ang_dist\").diff().over(\"sequence_id\").fill_null(0).alias(\"ang_dist_diff\"),\n    \n    ).with_columns(\n        ## Sign interaction features between linear acceleration components\n        (pl.col(\"linear_acc_x\")*pl.col(\"linear_acc_y\")).alias(\"linear_acc_xy\"),\n        (pl.col(\"linear_acc_x\")*pl.col(\"linear_acc_z\")).alias(\"linear_acc_xz\"),\n        (pl.col(\"linear_acc_y\")*pl.col(\"linear_acc_z\")).alias(\"linear_acc_yz\"),\n        (pl.col(\"linear_acc_x\")*pl.col(\"linear_acc_y\")*pl.col(\"linear_acc_z\")).alias(\"linear_acc_xyz\"),\n    \n        ## Sign interaction features between angular velocity components\n        (pl.col(\"ang_vel_x\")*pl.col(\"ang_vel_y\")).alias(\"ang_vel_xy\"),\n        (pl.col(\"ang_vel_x\")*pl.col(\"ang_vel_z\")).alias(\"ang_vel_xz\"),\n        (pl.col(\"ang_vel_y\")*pl.col(\"ang_vel_z\")).alias(\"ang_vel_yz\"),\n        (pl.col(\"ang_vel_x\")*pl.col(\"ang_vel_y\")*pl.col(\"ang_vel_z\")).alias(\"ang_vel_xyz\")\n    ).to_pandas()\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:17:01.025813Z","iopub.execute_input":"2025-07-06T01:17:01.026077Z","iopub.status.idle":"2025-07-06T01:17:01.036297Z","shell.execute_reply.started":"2025-07-06T01:17:01.026037Z","shell.execute_reply":"2025-07-06T01:17:01.035610Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def remove_gravity_from_acc(df):\n    \"\"\"\n    Function for removing gravivational component from each acceleration component.\n    \"\"\"\n    acc_values = df[['acc_x', 'acc_y', 'acc_z']].values\n    quat_values = df[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    \n    n_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(n_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    return linear_accel","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-07-06T01:17:01.036998Z","iopub.execute_input":"2025-07-06T01:17:01.037259Z","iopub.status.idle":"2025-07-06T01:17:01.049326Z","shell.execute_reply.started":"2025-07-06T01:17:01.037244Z","shell.execute_reply":"2025-07-06T01:17:01.048787Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def calculate_angular_velocity_from_quat(df, time_delta=1/200): # Assuming 200Hz sampling rate\n    \"\"\"\n    Calculates the 3D angular velocity vector.\n    \"\"\"\n    quat_values = df[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    n_samples   = quat_values.shape[0]\n    angular_vel = np.zeros((n_samples, 3))\n\n    for i in range(n_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","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-07-06T01:17:01.050176Z","iopub.execute_input":"2025-07-06T01:17:01.050468Z","iopub.status.idle":"2025-07-06T01:17:01.065196Z","shell.execute_reply.started":"2025-07-06T01:17:01.050417Z","shell.execute_reply":"2025-07-06T01:17:01.064490Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def calculate_angular_distance(df):\n    \"\"\"\n    Calculates a scalar angular distance for each sample.\n    \"\"\"\n    quat_values  = df[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    n_samples    = quat_values.shape[0]\n    angular_dist = np.zeros(n_samples)\n\n    for i in range(n_samples - 1):\n        q1, q2 = quat_values[i], 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 \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            pass\n            \n    return angular_dist","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-07-06T01:17:01.065922Z","iopub.execute_input":"2025-07-06T01:17:01.066225Z","iopub.status.idle":"2025-07-06T01:17:01.081857Z","shell.execute_reply.started":"2025-07-06T01:17:01.066196Z","shell.execute_reply":"2025-07-06T01:17:01.081229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_kinematic_features = [\n     'acc_x', 'acc_y', 'acc_z', \n     'rot_w', 'rot_x', 'rot_y', 'rot_z', \n     'linear_acc_x', 'linear_acc_y', 'linear_acc_z', \n     'ang_vel_x', 'ang_vel_y', 'ang_vel_z', \n     'ang_dist'\n ]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:23:01.183069Z","iopub.execute_input":"2025-07-06T01:23:01.183632Z","iopub.status.idle":"2025-07-06T01:23:01.187029Z","shell.execute_reply.started":"2025-07-06T01:23:01.183609Z","shell.execute_reply":"2025-07-06T01:23:01.186493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    ## Join dataframes and derive more kinematic features\n    merged_seq_df = sequence.join(demographics, on=\"subject\", how=\"left\").to_pandas()\n    linear_accs        = remove_gravity_from_acc(merged_seq_df)\n    angular_velocities = calculate_angular_velocity_from_quat(merged_seq_df)\n    angular_dist       = calculate_angular_distance(merged_seq_df)\n    merged_seq_df[[\"linear_acc_x\", \"linear_acc_y\", \"linear_acc_z\"]] = linear_accs \n    merged_seq_df[[\"ang_vel_x\", \"ang_vel_y\", \"ang_vel_z\"]] = angular_velocities \n    merged_seq_df[\"ang_dist\"] = angular_dist \n\n    ## Make more kinematic features\n    merged_seq_df[base_kinematic_features] = merged_seq_df[base_kinematic_features].ffill().bfill().fillna(0)\n    merged_seq_df = pl.from_pandas(merged_seq_df)\n    merged_seq_df = make_features(merged_seq_df)\n    X = merged_seq_df[features[\"full_features\"]]\n\n    ## Fill any NA values in kinematic features\n    X[features[\"full_features\"][:45]] = X[features[\"full_features\"][:45]].ffill().bfill().fillna(0)\n    ## Replace -1 in TOF pixels with NA\n    X[features[\"tof_features\"]] = X[features[\"tof_features\"]].replace(-1, np.nan)\n    \n    X = preprocessor.transform(X)\n\n    ## Strip prefixes from feature names\n    X.columns = X.columns.str.replace(r'^.*?__', '', regex=True)\n    ## Fills any NA values in thm signals\n    X[features[\"full_features\"][48:53]] = X[features[\"full_features\"][48:53]].ffill().bfill().fillna(0)\n    ## Fill NA values in TOF pixels with -1\n    X[features[\"tof_features\"]] = X[features[\"tof_features\"]].fillna(-1)\n    ## Reorder features\n    X = X[features[\"full_features\"]]\n\n    ## Converts to numpy array\n    X = X.values.astype('float32')\n    \n    X = pad_sequences([X], maxlen=PAD_LEN, padding='post', truncating='post')\n    \n    model.eval()\n    with torch.inference_mode():\n        X = torch.FloatTensor(X).to(DEVICE)\n        logits = model(X)\n        idx = logits.argmax(dim=1).item()\n        \n    return label_encoder.inverse_transform([idx])[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T01:25:53.828407Z","iopub.execute_input":"2025-07-06T01:25:53.828687Z","iopub.status.idle":"2025-07-06T01:25:53.836941Z","shell.execute_reply.started":"2025-07-06T01:25:53.828665Z","shell.execute_reply":"2025-07-06T01:25:53.836252Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import kaggle_evaluation.cmi_inference_server\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-07-06T01:25:55.976598Z","iopub.execute_input":"2025-07-06T01:25:55.976873Z","iopub.status.idle":"2025-07-06T01:25:59.099594Z","shell.execute_reply.started":"2025-07-06T01:25:55.976852Z","shell.execute_reply":"2025-07-06T01:25:59.098798Z"}},"outputs":[],"execution_count":null}]}