{"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":"gpu","dataSources":[{"sourceId":92399,"databundleVersionId":11038207,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom concurrent.futures import ThreadPoolExecutor\nfrom moviepy.editor import VideoFileClip\nfrom keras import layers, applications\nfrom datasets import load_dataset\nfrom tensorflow import keras\nfrom tqdm import tqdm\nimport torchvision.transforms.functional as F\nimport torch.nn.functional as FF\nimport tensorflow as tf\nimport pandas as pd\nimport numpy as np\nimport cv2, os, gc, torch, time","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:29:17.652301Z","iopub.execute_input":"2025-11-19T13:29:17.653275Z","iopub.status.idle":"2025-11-19T13:29:17.657891Z","shell.execute_reply.started":"2025-11-19T13:29:17.65325Z","shell.execute_reply":"2025-11-19T13:29:17.657014Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint(f'Using device: {device}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:29:21.889468Z","iopub.execute_input":"2025-11-19T13:29:21.889741Z","iopub.status.idle":"2025-11-19T13:29:21.957474Z","shell.execute_reply.started":"2025-11-19T13:29:21.889722Z","shell.execute_reply":"2025-11-19T13:29:21.956637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SIZE = 224\nMAX_FRAMES = 10\nDROPOUT_RATE = 0.3\nLAYERS_NUM = 256\nLR = 1e-4\nEPOCHS = 25\nBATCH_SIZE = 32","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:29:23.118025Z","iopub.execute_input":"2025-11-19T13:29:23.118775Z","iopub.status.idle":"2025-11-19T13:29:23.122449Z","shell.execute_reply.started":"2025-11-19T13:29:23.118746Z","shell.execute_reply":"2025-11-19T13:29:23.121742Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Preprocess:\n    def __init__(self, device, video_folder_path, csv_file_path):\n        self.device = device\n        self.max_frame = 10\n        self.frame_interval = 6\n        \n        self.video_matrix_output_dir = '/kaggle/working/video_matrices'\n        self.mask_matrix_output_dir = '/kaggle/working/masked_video_matrices'\n        self.video_folder_path = video_folder_path\n        self.csv_file_path = csv_file_path\n        \n        video_files = [f for f in os.listdir(self.video_folder_path)]\n        video_paths = {\n            os.path.splitext(f)[0]: os.path.join(self.video_folder_path, f)\n            for f in video_files\n        }\n        \n        num_digits = len(os.path.splitext(video_files[0])[0])\n        df = pd.read_csv(self.csv_file_path)\n        df['padded_id'] = df['id'].apply(lambda x: str(x).zfill(num_digits))\n        df['video_path'] = df['padded_id'].map(video_paths)\n        df = df.drop(columns='padded_id')\n        self.df = df\n    \n    def resize_on_gpu(self, frames):\n        frames_tensor = torch.from_numpy(frames).permute(0,3,1,2).float().to(self.device)\n        resized = F.resize(frames_tensor, [224,224])\n\n        return resized.permute(0,2,3,1).cpu().numpy()\n    \n    def pad_or_truncate(self, seq):\n        seq = np.array(seq)\n        if len(seq) > self.max_frame:\n            return seq[:self.max_frame]\n\n        if len(seq) < self.max_frame:\n            last = seq[-1]\n            padding = np.repeat(last[None, ...], self.max_frame - len(seq), axis=0)\n            return np.concatenate([seq, padding], axis=0)\n\n        return seq\n\n    def video_to_matrix(self, video_path, id_row):\n        os.makedirs(self.video_matrix_output_dir, exist_ok=True)\n\n        cap = cv2.VideoCapture(video_path)\n        fps = cap.get(cv2.CAP_PROP_FPS)\n        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n        duration = total_frames / fps\n\n        start_time = int(duration / 2)\n        end_time = start_time + 2\n\n        start_frame = int(start_time * fps)\n        end_frame = int(end_time * fps)\n\n        cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)\n\n        frames = []\n        frame_idx = start_frame\n\n        while frame_idx <= end_frame:\n            ok, frame = cap.read()\n            if not ok: break\n\n            if frame_idx % self.frame_interval == 0:\n                frames.append(frame)\n\n            frame_idx += 1\n\n        cap.release()\n\n        frames = np.stack(frames)\n        frames = self.resize_on_gpu(frames)\n        frames = self.pad_or_truncate(frames)\n\n        self.video_matrix_path = f'{self.video_matrix_output_dir}/video_{id_row}.npy'\n        np.save(self.video_matrix_path, frames)\n\n        return self.video_matrix_path\n\n    def masked_video_to_matrix(self, video_path, id_row):\n        os.makedirs(self.mask_matrix_output_dir, exist_ok=True)\n\n        frames = np.load(video_path)\n        frames_tensor = torch.from_numpy(frames).permute(0,3,1,2).float().to(self.device)/255.0\n\n        N = frames_tensor.shape[0]\n        gray = (0.2989*frames_tensor[:,0] + 0.5870*frames_tensor[:,1] + 0.1140*frames_tensor[:,2]).unsqueeze(1)\n\n        sobel_x = torch.tensor([[1,0,-1],[2,0,-2],[1,0,-1]], dtype=torch.float32, device=self.device).view(1,1,3,3)\n        sobel_y = torch.tensor([[1,2,1],[0,0,0],[-1,-2,-1]], dtype=torch.float32, device=self.device).view(1,1,3,3)\n        kernel = torch.ones((1,1,3,3), device=self.device)\n\n        mask_frames = []\n\n        for i in range(1, N):\n            prev = gray[i-1:i]\n            next = gray[i:i+1]\n\n            Ix = FF.conv2d(prev, sobel_x, padding=1)\n            Iy = FF.conv2d(prev, sobel_y, padding=1)\n            It = next - prev\n\n            Ixx = FF.conv2d(Ix*Ix, kernel, padding=1)\n            Iyy = FF.conv2d(Iy*Iy, kernel, padding=1)\n            Ixy = FF.conv2d(Ix*Iy, kernel, padding=1)\n            Ixt = FF.conv2d(Ix*It, kernel, padding=1)\n            Iyt = FF.conv2d(Iy*It, kernel, padding=1)\n\n            det = Ixx*Iyy - Ixy*Ixy + 1e-6\n            u = (Iyy*(-Ixt) - Ixy*(-Iyt)) / det\n            v = (-Ixy*(-Ixt) + Ixx*(-Iyt)) / det\n\n            u = u[0,0]\n            v = v[0,0]\n\n            magnitude = torch.sqrt(u*u + v*v)\n            magnitude = 255*(magnitude - magnitude.min())/(magnitude.max() - magnitude.min() + 1e-6)\n\n            angle = torch.atan2(v,u)*180/np.pi/2\n\n            dy_u, dx_u = torch.gradient(u)\n            dy_v, dx_v = torch.gradient(v)\n            divergence = dx_u + dy_v\n            divergence = ((torch.tanh(divergence)+1)/2)*255\n\n            stacked = torch.stack([magnitude, angle, divergence], dim=0).clamp(0,255).byte()\n            mask_frames.append(stacked.cpu())\n\n        mask_frames = torch.stack(mask_frames).numpy()\n        mask_frames = self.pad_or_truncate(mask_frames)\n\n        out_path = f'{self.mask_matrix_output_dir}/video_{id_row}.npy'\n        np.save(out_path, mask_frames)\n\n        return out_path\n\n    def add_matrix_path_col(self, col_name, id_row, idx) :\n        output_dir = self.mask_matrix_output_dir if col_name == 'masked_video_matrix_path' else self.video_matrix_output_dir\n        if idx == 0 :  \n            self.df[col_name] = None\n        \n        file_path = os.path.join(output_dir, f'video_{id_row}.npy')\n        if os.path.exists(file_path):\n            self.df.at[idx, col_name] = file_path\n            return file_path\n            \n        else:\n            self.df.at[idx, col_name] = None\n            return None\n            \n    def process(self):\n        for idx, row in self.df.iterrows():\n            id_row = row.id\n            video_path = row.video_path\n            \n            self.video_to_matrix(video_path, id_row)\n            matrix_path = self.add_matrix_path_col('video_matrix_path', id_row, idx)\n            self.masked_video_to_matrix(matrix_path, id_row)\n            self.add_matrix_path_col('masked_video_matrix_path', id_row, idx)\n        \n        return self.df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:29:26.325468Z","iopub.execute_input":"2025-11-19T13:29:26.325768Z","iopub.status.idle":"2025-11-19T13:29:26.345484Z","shell.execute_reply.started":"2025-11-19T13:29:26.325745Z","shell.execute_reply":"2025-11-19T13:29:26.344685Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_folder_path = '/kaggle/input/nexar-collision-prediction/train'\ntrain_csv_path = '/kaggle/input/nexar-collision-prediction/train.csv'\n\ntrain_pre = Preprocess(device, train_folder_path, train_csv_path)\ntrain_df = train_pre.process()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:29:31.538392Z","iopub.execute_input":"2025-11-19T13:29:31.539127Z","iopub.status.idle":"2025-11-19T13:42:31.322206Z","shell.execute_reply.started":"2025-11-19T13:29:31.539101Z","shell.execute_reply":"2025-11-19T13:42:31.32138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = train_df.drop(columns=['id', 'time_of_event', 'time_of_alert'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:42:31.323654Z","iopub.execute_input":"2025-11-19T13:42:31.324145Z","iopub.status.idle":"2025-11-19T13:42:31.329346Z","shell.execute_reply.started":"2025-11-19T13:42:31.324125Z","shell.execute_reply":"2025-11-19T13:42:31.328642Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class VideoMatrixSequence(keras.utils.Sequence):\n    def __init__(self, df, batch_size=BATCH_SIZE, target_size=(IMG_SIZE, IMG_SIZE), shuffle=True):\n        self.df = df.reset_index(drop=True)\n        self.batch_size = batch_size\n        self.target_size = target_size\n        self.shuffle = shuffle\n        self.indices = np.arange(len(self.df))\n        self.on_epoch_end()\n\n    def __len__(self):\n        return len(self.df) // self.batch_size\n\n    def __getitem__(self, idx):\n        batch_indices = self.indices[idx * self.batch_size : (idx + 1) * self.batch_size]\n\n        video_data = []\n        mask_data = []\n        labels = []\n\n        for i in batch_indices :\n            row = self.df.iloc[i]\n\n            video = np.load(row['video_matrix_path'])\n            frames_resized = np.stack(video)\n\n            mask = np.load(row['masked_video_matrix_path'])\n            mask_frames = np.stack(mask)\n\n            video_data.append(frames_resized)\n            mask_data.append(mask_frames)\n            labels.append(float(row['target']))\n\n        video_array = np.array(video_data, dtype=np.float32)\n        mask_array = np.array(mask_data, dtype=np.float32)\n        labels_array = np.array(labels, dtype=np.float32)\n        labels_array = np.expand_dims(labels_array, axis=-1)\n\n        output = (\n            {\n                'video': video_array,\n                'mask_flow': mask_array,\n            }, labels_array\n        )\n        return output\n\n    def on_epoch_end(self):\n        if self.shuffle:\n            np.random.shuffle(self.indices)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:42:31.330228Z","iopub.execute_input":"2025-11-19T13:42:31.330537Z","iopub.status.idle":"2025-11-19T13:42:31.398757Z","shell.execute_reply.started":"2025-11-19T13:42:31.330519Z","shell.execute_reply":"2025-11-19T13:42:31.398046Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df_split, val_df_split = train_test_split(train_df, test_size=0.25, random_state=1404)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:42:31.400373Z","iopub.execute_input":"2025-11-19T13:42:31.400625Z","iopub.status.idle":"2025-11-19T13:42:31.407911Z","shell.execute_reply.started":"2025-11-19T13:42:31.400603Z","shell.execute_reply":"2025-11-19T13:42:31.407379Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_seq = VideoMatrixSequence(train_df_split, batch_size=BATCH_SIZE, target_size=(IMG_SIZE, IMG_SIZE))\nval_seq = VideoMatrixSequence(val_df_split, batch_size=BATCH_SIZE, target_size=(IMG_SIZE, IMG_SIZE))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:42:31.408526Z","iopub.execute_input":"2025-11-19T13:42:31.408757Z","iopub.status.idle":"2025-11-19T13:42:31.421864Z","shell.execute_reply.started":"2025-11-19T13:42:31.408741Z","shell.execute_reply":"2025-11-19T13:42:31.421281Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_resnet(x):\n    return applications.resnet50.preprocess_input(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:42:31.4226Z","iopub.execute_input":"2025-11-19T13:42:31.422842Z","iopub.status.idle":"2025-11-19T13:42:31.436053Z","shell.execute_reply.started":"2025-11-19T13:42:31.422817Z","shell.execute_reply":"2025-11-19T13:42:31.435237Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def cnn_net(input_shape=(3, IMG_SIZE, IMG_SIZE)):\n    inputs = layers.Input(shape=input_shape)\n    if input_shape == (3, IMG_SIZE, IMG_SIZE) :\n        x = layers.Permute((2, 3, 1))(inputs)\n        x = layers.Lambda(preprocess_resnet)(x)\n\n    else :\n        x = layers.Lambda(preprocess_resnet)(inputs)\n\n    base = applications.ResNet50(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(IMG_SIZE, IMG_SIZE, 3),\n        pooling='avg'\n    )\n\n    base.trainable = False\n\n    x = base(x)\n\n    return keras.Model(inputs, x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:42:48.202158Z","iopub.execute_input":"2025-11-19T13:42:48.202905Z","iopub.status.idle":"2025-11-19T13:42:48.207624Z","shell.execute_reply.started":"2025-11-19T13:42:48.202883Z","shell.execute_reply":"2025-11-19T13:42:48.206841Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Inputs\nvideo_input = keras.Input(shape=(MAX_FRAMES, IMG_SIZE, IMG_SIZE, 3), name='video')\nmask_input = keras.Input(shape=(MAX_FRAMES, 3, IMG_SIZE, IMG_SIZE), name='mask_flow')\n\n# CNN\nbase_cnn_model = cnn_net((IMG_SIZE, IMG_SIZE, 3))\nmask_cnn_model = cnn_net() \n\nvideo_features = layers.TimeDistributed(base_cnn_model)(video_input)\nmask_features = layers.TimeDistributed(mask_cnn_model)(mask_input)\n\n# GRU\nvideo_gru_out = layers.Bidirectional(layers.GRU(LAYERS_NUM, return_sequences=False))(video_features)\nvideo_gru_out = layers.Dropout(DROPOUT_RATE)(video_gru_out)\n\nmask_gru_out = layers.Bidirectional(layers.GRU(LAYERS_NUM, return_sequences=False))(mask_features)\nmask_gru_out = layers.Dropout(DROPOUT_RATE)(mask_gru_out)\n\n# Merge video and mask\nfinal_merge = layers.Concatenate()([video_gru_out, mask_gru_out])\n\nfinal_merge = layers.BatchNormalization()(final_merge)\nfinal_merge = layers.Dropout(DROPOUT_RATE)(final_merge)\n\noutputs = layers.Dense(1, activation='sigmoid')(final_merge)\n\n# Final model\nmodel = keras.Model(\n    inputs=[video_input, mask_input],\n    outputs=outputs,\n)\n\nmodel.compile(\n    optimizer=keras.optimizers.Adam(learning_rate=LR),\n    loss='binary_crossentropy',\n    metrics=[\n        'accuracy',\n        keras.metrics.Precision(name='precision'),\n        keras.metrics.Recall(name='recall')\n    ]\n)\n\ncbs = [\n    keras.callbacks.EarlyStopping(\n        monitor='val_loss',\n        patience=10,\n        restore_best_weights=True,\n        verbose=1\n    ),\n    keras.callbacks.ModelCheckpoint(\n        filepath='model.keras',\n        monitor='val_accuracy',\n        save_best_only=True,\n        verbose=1\n    )\n]\n\nhistory = model.fit(train_seq, validation_data=val_seq, epochs=EPOCHS, callbacks=cbs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T13:42:48.639302Z","iopub.execute_input":"2025-11-19T13:42:48.640109Z","iopub.status.idle":"2025-11-19T13:59:59.405049Z","shell.execute_reply.started":"2025-11-19T13:42:48.640076Z","shell.execute_reply":"2025-11-19T13:59:59.404376Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!rm /kaggle/working/masked_video_matrices/*\n!rmdir ./masked_video_matrices\n!rm /kaggle/working/video_matrices/*\n!rmdir ./video_matrices","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T14:02:12.29849Z","iopub.execute_input":"2025-11-19T14:02:12.299279Z","iopub.status.idle":"2025-11-19T14:02:15.398982Z","shell.execute_reply.started":"2025-11-19T14:02:12.299252Z","shell.execute_reply":"2025-11-19T14:02:15.397674Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_model(model_path):\n    return keras.models.load_model(\n        model_path,\n        custom_objects={\n            'preprocess_resnet': preprocess_resnet\n        }\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T14:02:30.755834Z","iopub.execute_input":"2025-11-19T14:02:30.756576Z","iopub.status.idle":"2025-11-19T14:02:30.761054Z","shell.execute_reply.started":"2025-11-19T14:02:30.756542Z","shell.execute_reply":"2025-11-19T14:02:30.760389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_folder_path = '/kaggle/input/nexar-collision-prediction/test'\ntest_csv_path = '/kaggle/input/nexar-collision-prediction/test.csv'\n\ntest_pre = Preprocess(device, test_folder_path, test_csv_path)\ntest_df = test_pre.process()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T14:02:32.318584Z","iopub.execute_input":"2025-11-19T14:02:32.318871Z","iopub.status.idle":"2025-11-19T14:14:42.300729Z","shell.execute_reply.started":"2025-11-19T14:02:32.318849Z","shell.execute_reply":"2025-11-19T14:14:42.30006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = load_model('./model.keras')\n\ntest_df['score'] = None\n\nfor id_row in list(test_df.id) :\n    video_input = np.load(list(test_df.loc[test_df.id == id_row, 'video_matrix_path'])[0])\n    mask_input = np.load(list(test_df.loc[test_df.id == id_row, 'masked_video_matrix_path'])[0])\n    \n    video_input = np.expand_dims(video_input, axis=0).astype('float32')\n    mask_input = np.expand_dims(mask_input, axis=0).astype('float32')\n\n    pred = model.predict([video_input, mask_input])[0][0]\n\n    test_df.loc[test_df.id == id_row, 'score'] = pred","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T14:18:21.150404Z","iopub.execute_input":"2025-11-19T14:18:21.151157Z","iopub.status.idle":"2025-11-19T14:25:37.217676Z","shell.execute_reply.started":"2025-11-19T14:18:21.151132Z","shell.execute_reply":"2025-11-19T14:25:37.216899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = test_df.drop(columns=['video_path', 'video_matrix_path', 'masked_video_matrix_path'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T14:27:53.648665Z","iopub.execute_input":"2025-11-19T14:27:53.648969Z","iopub.status.idle":"2025-11-19T14:27:53.653729Z","shell.execute_reply.started":"2025-11-19T14:27:53.648944Z","shell.execute_reply":"2025-11-19T14:27:53.652917Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T14:29:30.427982Z","iopub.execute_input":"2025-11-19T14:29:30.428621Z","iopub.status.idle":"2025-11-19T14:29:30.442792Z","shell.execute_reply.started":"2025-11-19T14:29:30.428594Z","shell.execute_reply":"2025-11-19T14:29:30.442134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!rm /kaggle/working/masked_video_matrices/*\n!rmdir ./masked_video_matrices\n!rm /kaggle/working/video_matrices/*\n!rmdir ./video_matrices","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T14:30:07.861475Z","iopub.execute_input":"2025-11-19T14:30:07.861759Z","iopub.status.idle":"2025-11-19T14:30:10.678969Z","shell.execute_reply.started":"2025-11-19T14:30:07.861734Z","shell.execute_reply":"2025-11-19T14:30:10.678092Z"}},"outputs":[],"execution_count":null}]}