{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":91249,"databundleVersionId":11294684,"sourceType":"competition"},{"sourceId":11309849,"sourceType":"datasetVersion","datasetId":6996891}],"dockerImageVersionId":30919,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# DEIM Single Model Inference Notebook","metadata":{}},{"cell_type":"markdown","source":"- Training Data  \nonly official image data with num_motors>0 used (no external data, no negative sampling).  \n75% training, 25% validation  \n- Image Size  \n(384, 384, 3) (both training and inference)  \n- Model weight and DEIM code (including training config) are not public.  \n","metadata":{}},{"cell_type":"markdown","source":"```\n Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.798\n Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.958\n Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.927\n Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000\n Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.798\n Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.000\n Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.785\n Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.854\n Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.883\n Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000\n Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.883\n Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.000\n Average Recall     (AR) @[ IoU=0.50      | area=   all | maxDets=100 ] = 1.000\n Average Recall     (AR) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.971\n```","metadata":{}},{"cell_type":"code","source":"!pip install -q /kaggle/input/byu-private-dataset/faster_coco_eval-1.6.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\n!pip install -q /kaggle/input/byu-private-dataset/calflops-0.3.2-py3-none-any.whl","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:02:48.359758Z","iopub.execute_input":"2025-04-07T18:02:48.359947Z","iopub.status.idle":"2025-04-07T18:02:56.249495Z","shell.execute_reply.started":"2025-04-07T18:02:48.359928Z","shell.execute_reply":"2025-04-07T18:02:56.24855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from glob import glob\nimport sys\nimport os\n\nimport torch\nimport torch.nn as nn\nimport torchvision\nimport torchvision.transforms as T\nimport numpy as np\nfrom PIL import Image, ImageDraw\nimport pandas as pd\nimport cv2 \nfrom fastprogress import progress_bar as pb\nfrom tqdm import tqdm\nfrom scipy.spatial import distance\nfrom scipy.optimize import linear_sum_assignment\nimport networkx as nx\ntqdm.pandas()\n\nsys.path.append('/kaggle/input/byu-private-dataset/BYU_DEIM_code_exp101_2/DEIM')\nfrom engine.core import YAMLConfig","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:11.388964Z","iopub.execute_input":"2025-04-07T18:03:11.389248Z","iopub.status.idle":"2025-04-07T18:03:35.089302Z","shell.execute_reply.started":"2025-04-07T18:03:11.389226Z","shell.execute_reply":"2025-04-07T18:03:35.088406Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 0. Config","metadata":{}},{"cell_type":"code","source":"device = 'cuda' if torch.cuda.is_available() else 'cpu'\nDEIM_CONFIG_FILEPATH = '/kaggle/input/byu-private-dataset/BYU_DEIM_code_exp101_2/DEIM/configs/deim_exp101_2.yml'\nDEIM_MODEL_FILEPATH = '/kaggle/input/byu-private-dataset/BYU_DEIM_exp101_2_best_stg1.pth'\nIMAGE_SIZE = (384, 384)\nSCORE_TH_PRE = SCORE_TH_AGG = 0.8\nGROUP_DIST_TH = 20.0\nMIN_DET_PER_GROUP = 1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:35.090296Z","iopub.execute_input":"2025-04-07T18:03:35.090697Z","iopub.status.idle":"2025-04-07T18:03:35.145452Z","shell.execute_reply.started":"2025-04-07T18:03:35.090652Z","shell.execute_reply":"2025-04-07T18:03:35.144415Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Data Preparation","metadata":{}},{"cell_type":"code","source":"BASE_IMAGE_DIR = \"/kaggle/input/byu-locating-bacterial-flagellar-motors-2025/\"\nTEST_IMAGE_DIR = os.path.join(BASE_IMAGE_DIR, \"test\")\ntest_tomo_dir_list = glob(f'{TEST_IMAGE_DIR}/*')\ntest_tomo_id_list = [d.split('/')[-1] for d in test_tomo_dir_list]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:35.147939Z","iopub.execute_input":"2025-04-07T18:03:35.148325Z","iopub.status.idle":"2025-04-07T18:03:35.175906Z","shell.execute_reply.started":"2025-04-07T18:03:35.148287Z","shell.execute_reply":"2025-04-07T18:03:35.175024Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_images(tomo_id, train_or_test='test', resize_size=IMAGE_SIZE, loader='torchvision'):\n    assert loader in ['pil', 'torchvision']\n    image_dir = f'{BASE_IMAGE_DIR}/{train_or_test}/{tomo_id}'\n    image_files = sorted(glob(f'{image_dir}/*.*'))\n    df_image_files = pd.DataFrame({'filepath': image_files})\n    df_image_files['no'] = df_image_files['filepath'].map(lambda x: int(x.split('_')[-1].split('.')[0]))\n    df_image_files = df_image_files.sort_values(by='no', ascending=True)\n    # None : pil/torchvision resize results in slightly different values.\n    if loader == 'pil':\n        images = [Image.open(f).convert('L') for f in df_image_files['filepath']]\n        org_image_size = images[0].size  # (w, h)\n        if resize_size is not None:\n            images = [image.resize(resize_size) for image in images]\n        images = np.stack([np.asarray(image) for image in images])  # (n_frames, h, w)\n    elif loader == 'torchvision':\n        trainsforms = T.Resize(resize_size) if resize_size is not None else T.Compose([])\n        images = [torchvision.io.read_image(f) for f in df_image_files['filepath']]\n        org_image_size = (images[0].shape[2], images[0].shape[1])  # (w, h)\n        images = [trainsforms(image) for image in images]\n        images = torch.concatenate(images, dim=0)  # (n_frames, h, w)\n        images = images.numpy()\n    return images, df_image_files, org_image_size","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:35.177143Z","iopub.execute_input":"2025-04-07T18:03:35.177417Z","iopub.status.idle":"2025-04-07T18:03:35.185648Z","shell.execute_reply.started":"2025-04-07T18:03:35.177387Z","shell.execute_reply":"2025-04-07T18:03:35.184848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# %%time\n# images, df_image_files, org_image_size = load_images(tomo_id='tomo_003acc', loader='torchvision', resize_size=IMAGE_SIZE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:35.186467Z","iopub.execute_input":"2025-04-07T18:03:35.186787Z","iopub.status.idle":"2025-04-07T18:03:35.204971Z","shell.execute_reply.started":"2025-04-07T18:03:35.186759Z","shell.execute_reply":"2025-04-07T18:03:35.204098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# %%time\n# images2, df_image_files2, org_image_size2 = load_images(tomo_id='tomo_003acc', loader='pil', resize_size=IMAGE_SIZE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:35.205835Z","iopub.execute_input":"2025-04-07T18:03:35.206168Z","iopub.status.idle":"2025-04-07T18:03:35.220251Z","shell.execute_reply.started":"2025-04-07T18:03:35.206121Z","shell.execute_reply":"2025-04-07T18:03:35.219311Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Prepare DEIM Model","metadata":{}},{"cell_type":"code","source":"class Model(nn.Module):\n    def __init__(self, cfg):\n        super().__init__()\n        self.model = cfg.model.deploy()\n        self.postprocessor = cfg.postprocessor.deploy()\n\n    def forward(self, images, orig_target_sizes):\n        outputs = self.model(images)\n        outputs = self.postprocessor(outputs, orig_target_sizes)\n        return outputs\n\n\ndef prepare_deim_model(cfg_filepath: str, weight_filepath: str, device=device):\n    cfg = YAMLConfig(cfg_filepath, resume=weight_filepath)\n    checkpoint = torch.load(weight_filepath, map_location=device)\n    if 'ema' in checkpoint:\n        state = checkpoint['ema']['module']\n    else:\n        state = checkpoint['model']\n    # Load train mode state and convert to deploy mode\n    cfg.model.load_state_dict(state)\n    model = Model(cfg).to(device)\n    return model.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:35.221082Z","iopub.execute_input":"2025-04-07T18:03:35.221318Z","iopub.status.idle":"2025-04-07T18:03:35.237596Z","shell.execute_reply.started":"2025-04-07T18:03:35.221288Z","shell.execute_reply":"2025-04-07T18:03:35.236749Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"det_model = prepare_deim_model(\n    cfg_filepath=DEIM_CONFIG_FILEPATH,\n    weight_filepath=DEIM_MODEL_FILEPATH,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:35.239287Z","iopub.execute_input":"2025-04-07T18:03:35.239482Z","iopub.status.idle":"2025-04-07T18:03:52.567871Z","shell.execute_reply.started":"2025-04-07T18:03:35.239466Z","shell.execute_reply":"2025-04-07T18:03:52.567104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Run Detection","metadata":{}},{"cell_type":"code","source":"@torch.inference_mode()\ndef inference_np_batch(model, np_image: np.ndarray, resize_size=IMAGE_SIZE):\n    if np_image.dtype == np.uint8:\n        np_image = np_image.astype(float) / 255\n    tensor_image = torch.tensor(np_image).permute(0, 3, 1, 2).float()  # (bs, h, w, ch) => (bs, ch, h, w)\n\n    transforms = T.Compose([\n        T.Resize(IMAGE_SIZE),\n    ])\n    im_data = transforms(tensor_image).to(device)\n    bs, ch, h, w = im_data.shape\n    orig_size = torch.tensor([[w, h]]).to(device)\n\n    output = model(im_data, orig_size)\n    labels, boxes, scores = output\n    return labels, boxes, scores\n\n\ndef filter_detection(labels, boxes, scores, thrh=0.4):\n    n_query1, =  labels.shape\n    n_query2, bbox_dim =  boxes.shape\n    n_query3, =  scores.shape\n    assert n_query1 == n_query2 == n_query3, (n_query1, n_query2, n_query3)\n    assert bbox_dim == 4\n    lab = labels[scores > thrh]\n    box = boxes[scores > thrh]\n    scrs = scores[scores > thrh]\n    return lab, box, scrs\n\n\n@torch.inference_mode()\ndef inference_tomo(model, tomo_id: str, batch_size: int = 4, th: float = 0.4) -> pd.DataFrame:\n    # 1. Load images for target tomo_id\n    images, df_image_files, org_image_size = load_images(tomo_id=tomo_id, loader='pil', resize_size=IMAGE_SIZE)\n    images = images.transpose(1, 2, 0)  # (n_frames, h, w) => (h, w, n_frames)\n    z_max = images.shape[-1] - 1\n    w_org, h_org = org_image_size\n\n    # 2. Run detection on sliced 3ch images along z axis.\n    image_sliced_list = []\n    df_detection_list = []\n    z_center_list = list(range(1, z_max-1))\n    for z in pb(z_center_list):\n        image_sliced = images[:, :, z-1:z+1+1]  # (h, w, 3)\n        image_sliced_list.append(image_sliced)\n        if (len(image_sliced_list) >= batch_size) or (z == z_center_list[-1]):\n            image_sliced_batch = np.stack(image_sliced_list)  # (bs, h, w, 3)\n            labels, boxes, scores = inference_np_batch(model, image_sliced_batch)\n            for i in range(labels.shape[0]):\n                lab, box, scrs = filter_detection(labels[i], boxes[i], scores[i], th)\n                if len(lab) > 0:\n                    df_det = pd.DataFrame(data=box.cpu().numpy(), columns=['x1', 'y1', 'x2', 'y2'])\n                    df_det['z'] = z\n                    df_det['x_384'] = 0.5 * (df_det['x1'] + df_det['x2'])\n                    df_det['y_384'] = 0.5 * (df_det['y1'] + df_det['y2'])\n                    df_det['x_normed'] = df_det['x_384'] / 384\n                    df_det['y_normed'] = df_det['y_384'] / 384\n                    df_det['x'] = w_org * df_det['x_normed']\n                    df_det['y'] = h_org * df_det['y_normed']\n                    df_det['label'] = lab.cpu().tolist()\n                    df_det['score'] = scrs.cpu().tolist()\n                    df_det['tomo_id'] = tomo_id\n                    df_detection_list.append(df_det)\n            image_sliced_list = []\n    return pd.concat(df_detection_list) if len(df_detection_list) > 0 else pd.DataFrame([])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:52.570745Z","iopub.execute_input":"2025-04-07T18:03:52.570971Z","iopub.status.idle":"2025-04-07T18:03:52.581782Z","shell.execute_reply.started":"2025-04-07T18:03:52.570952Z","shell.execute_reply":"2025-04-07T18:03:52.58095Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Run detection for each tomo_id\ndf_det_list = []\n\nfor tomo_id in pb(test_tomo_id_list):\n    df_det_list.append(inference_tomo(det_model, tomo_id, th=SCORE_TH_PRE))\n\ndf_det_all = pd.concat(df_det_list)\ndf_det_all = df_det_all.reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:03:52.58333Z","iopub.execute_input":"2025-04-07T18:03:52.583559Z","iopub.status.idle":"2025-04-07T18:05:09.508805Z","shell.execute_reply.started":"2025-04-07T18:03:52.58354Z","shell.execute_reply":"2025-04-07T18:05:09.50794Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_det_all","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:05:09.510146Z","iopub.execute_input":"2025-04-07T18:05:09.510405Z","iopub.status.idle":"2025-04-07T18:05:09.539169Z","shell.execute_reply.started":"2025-04-07T18:05:09.510383Z","shell.execute_reply":"2025-04-07T18:05:09.538535Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Aggregate Detections","metadata":{}},{"cell_type":"code","source":"def aggregate_detection(\n    df_det: pd.DataFrame,\n    score_th: float,\n    group_dist_th: float = 5.0,\n    min_det_per_group: int = None,\n) -> pd.DataFrame:\n    # pre-filter by score threshold\n    df_det = df_det[df_det['score'] >= score_th]\n    df_agg_det_tomo_list = []\n    for tomo_id, df_det_tomo in df_det.groupby('tomo_id'):\n        # calculate euclidean distance matrix (in voxel space) between each detections in this tomo_id\n        dist_mat = distance.cdist(df_det_tomo[['x', 'y', 'z']], df_det_tomo[['x', 'y', 'z']], metric='euclidean')\n        # calculate adjacency matrix based on distance matrix and threshold distance\n        adj_mat = (dist_mat <= group_dist_th).astype(int)\n        np.fill_diagonal(adj_mat, 0)\n        # group detections into connected graphs based on adjacency matrix\n        G = nx.from_numpy_array(adj_mat)\n        connected_components = list(nx.connected_components(G))\n        agg_det_dict_list = []\n        # Aggregate detections in each connected groups\n        for group_idx_set in connected_components:\n            df_det_grp = df_det_tomo.iloc[list(group_idx_set)]  # detections belonging to this group\n            z = (df_det_grp['z'] * df_det_grp['score']).sum() / df_det_grp['score'].sum()  # score weighted mean\n            y = (df_det_grp['y'] * df_det_grp['score']).sum() / df_det_grp['score'].sum()  # score weighted mean\n            x = (df_det_grp['x'] * df_det_grp['score']).sum() / df_det_grp['score'].sum()  # score weighted mean\n            agg_det = {\n                'tomo_id': tomo_id,\n                'x': x,\n                'y': y,\n                'z': z,\n                'score_mean': df_det_grp['score'].mean(),\n                'group_det_count': len(group_idx_set),  # detection count in this group\n            }\n            agg_det_dict_list.append(agg_det)\n        df_agg_det_tomo = pd.DataFrame(agg_det_dict_list)\n        if min_det_per_group is not None:\n            # delete the detections belonging to the groups that has detection count less than min_det_per_group\n            df_agg_det_tomo = df_agg_det_tomo[df_agg_det_tomo['group_det_count'] >= min_det_per_group]\n        # select highest (group_det_count, score_mean) group's aggregated detection as final detection for this tomo_id\n        df_agg_det_tomo = df_agg_det_tomo.sort_values(by=['group_det_count', 'score_mean'], ascending=False).iloc[:1]\n        df_agg_det_tomo_list.append(df_agg_det_tomo)\n    return pd.concat(df_agg_det_tomo_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:05:09.539842Z","iopub.execute_input":"2025-04-07T18:05:09.540027Z","iopub.status.idle":"2025-04-07T18:05:09.547044Z","shell.execute_reply.started":"2025-04-07T18:05:09.540011Z","shell.execute_reply":"2025-04-07T18:05:09.546174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_det_agg = aggregate_detection(df_det_all, score_th=SCORE_TH_AGG, group_dist_th=GROUP_DIST_TH, min_det_per_group=MIN_DET_PER_GROUP)\nassert not df_det_agg['tomo_id'].duplicated().any()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:05:09.549089Z","iopub.execute_input":"2025-04-07T18:05:09.549283Z","iopub.status.idle":"2025-04-07T18:05:09.586073Z","shell.execute_reply.started":"2025-04-07T18:05:09.549265Z","shell.execute_reply":"2025-04-07T18:05:09.585351Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_det_agg","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:05:09.586871Z","iopub.execute_input":"2025-04-07T18:05:09.587116Z","iopub.status.idle":"2025-04-07T18:05:09.594912Z","shell.execute_reply.started":"2025-04-07T18:05:09.587092Z","shell.execute_reply":"2025-04-07T18:05:09.594245Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Submit","metadata":{}},{"cell_type":"code","source":"# no motor detected tomo_id list \nno_motor_tomo_id_list = list(set(test_tomo_id_list) - set(df_det_agg['tomo_id']))\nlen(no_motor_tomo_id_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:05:09.5958Z","iopub.execute_input":"2025-04-07T18:05:09.596073Z","iopub.status.idle":"2025-04-07T18:05:09.610718Z","shell.execute_reply.started":"2025-04-07T18:05:09.596048Z","shell.execute_reply":"2025-04-07T18:05:09.610085Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# no motor detected predictions\ndf_det_no_motor = pd.DataFrame({\n    'tomo_id': no_motor_tomo_id_list,\n    'Motor axis 0': [-1] * len(no_motor_tomo_id_list),\n    'Motor axis 1': [-1] * len(no_motor_tomo_id_list),\n    'Motor axis 2': [-1] * len(no_motor_tomo_id_list),\n})\n# motor detected predictions\ndf_det_agg = df_det_agg.rename(\n    columns={'z': 'Motor axis 0', 'y': 'Motor axis 1', 'x': 'Motor axis 2'}\n)[['tomo_id', 'Motor axis 0', 'Motor axis 1', 'Motor axis 2']]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:05:09.611492Z","iopub.execute_input":"2025-04-07T18:05:09.611808Z","iopub.status.idle":"2025-04-07T18:05:09.625807Z","shell.execute_reply.started":"2025-04-07T18:05:09.61178Z","shell.execute_reply":"2025-04-07T18:05:09.625113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"display(df_det_no_motor)\ndisplay(df_det_agg)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:05:09.626528Z","iopub.execute_input":"2025-04-07T18:05:09.626745Z","iopub.status.idle":"2025-04-07T18:05:09.647777Z","shell.execute_reply.started":"2025-04-07T18:05:09.626728Z","shell.execute_reply":"2025-04-07T18:05:09.647141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_submission = pd.concat([df_det_agg, df_det_no_motor])\nassert set(df_submission['tomo_id']) == set(test_tomo_id_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:05:09.64846Z","iopub.execute_input":"2025-04-07T18:05:09.648704Z","iopub.status.idle":"2025-04-07T18:05:09.660391Z","shell.execute_reply.started":"2025-04-07T18:05:09.648686Z","shell.execute_reply":"2025-04-07T18:05:09.659538Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_submission.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T18:05:09.661184Z","iopub.execute_input":"2025-04-07T18:05:09.661469Z","iopub.status.idle":"2025-04-07T18:05:09.681388Z","shell.execute_reply.started":"2025-04-07T18:05:09.661444Z","shell.execute_reply":"2025-04-07T18:05:09.680565Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}