{"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-15T11:40:20.296723Z","iopub.execute_input":"2025-04-15T11:40:20.296983Z","iopub.status.idle":"2025-04-15T11:40:28.365030Z","shell.execute_reply.started":"2025-04-15T11:40:20.296952Z","shell.execute_reply":"2025-04-15T11:40:28.363942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from glob import glob\nimport sys\nimport os\nfrom typing import Literal\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-15T11:43:15.608969Z","iopub.execute_input":"2025-04-15T11:43:15.609313Z","iopub.status.idle":"2025-04-15T11:43:36.951465Z","shell.execute_reply.started":"2025-04-15T11:43:15.609282Z","shell.execute_reply":"2025-04-15T11:43:36.950781Z"}},"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.825\nGROUP_DIST_TH = 20.0\nMIN_DET_PER_GROUP = 1\n# AGG_METHOD = 'score_highest'\nAGG_METHOD = 'score_weighted_mean'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T11:43:36.952536Z","iopub.execute_input":"2025-04-15T11:43:36.952827Z","iopub.status.idle":"2025-04-15T11:43:37.006612Z","shell.execute_reply.started":"2025-04-15T11:43:36.952799Z","shell.execute_reply":"2025-04-15T11:43:37.005830Z"}},"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-15T11:43:37.008267Z","iopub.execute_input":"2025-04-15T11:43:37.008555Z","iopub.status.idle":"2025-04-15T11:43:37.026960Z","shell.execute_reply.started":"2025-04-15T11:43:37.008534Z","shell.execute_reply":"2025-04-15T11:43:37.026168Z"}},"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-15T11:43:37.027958Z","iopub.execute_input":"2025-04-15T11:43:37.028236Z","iopub.status.idle":"2025-04-15T11:43:37.035864Z","shell.execute_reply.started":"2025-04-15T11:43:37.028207Z","shell.execute_reply":"2025-04-15T11:43:37.035186Z"}},"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-14T05:03:28.232547Z","iopub.execute_input":"2025-04-14T05:03:28.232851Z","iopub.status.idle":"2025-04-14T05:03:28.248060Z","shell.execute_reply.started":"2025-04-14T05:03:28.232821Z","shell.execute_reply":"2025-04-14T05:03:28.247471Z"}},"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-14T05:03:28.248845Z","iopub.execute_input":"2025-04-14T05:03:28.249147Z","iopub.status.idle":"2025-04-14T05:03:28.263041Z","shell.execute_reply.started":"2025-04-14T05:03:28.249120Z","shell.execute_reply":"2025-04-14T05:03:28.262298Z"}},"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-15T11:43:41.586350Z","iopub.execute_input":"2025-04-15T11:43:41.586715Z","iopub.status.idle":"2025-04-15T11:43:41.592943Z","shell.execute_reply.started":"2025-04-15T11:43:41.586690Z","shell.execute_reply":"2025-04-15T11:43:41.591804Z"}},"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-15T11:43:58.323834Z","iopub.execute_input":"2025-04-15T11:43:58.324114Z","iopub.status.idle":"2025-04-15T11:44:17.656477Z","shell.execute_reply.started":"2025-04-15T11:43:58.324092Z","shell.execute_reply":"2025-04-15T11:44:17.655540Z"}},"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":"def rolling_mean_image(image: torch.Tensor, dim: int, window: int) -> torch.Tensor:\n    assert window % 2 == 1, \"Window size must be odd\"\n    n_dim = image.ndim\n\n    if dim != (n_dim - 1):\n        image = image.transpose(n_dim - 1, dim)  # move target dim to last\n\n    n_padding = (window - 1) // 2\n    pad_image_head = image[..., [0]].repeat([1] * (n_dim - 1) + [n_padding]).to(image)\n    pad_image_tail = image[..., [-1]].repeat([1] * (n_dim - 1) + [n_padding]).to(image)\n    image_padded = torch.cat([pad_image_head, image, pad_image_tail], dim=-1)\n\n    image_rolling_mean = image_padded.unfold(dimension=-1, size=window, step=1).mean(dim=-1)\n\n    if dim != (n_dim - 1):\n        image_rolling_mean = image_rolling_mean.transpose(n_dim - 1, dim)  # revert to original shape\n\n    assert image.shape == image_rolling_mean.shape\n    return image_rolling_mean","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T11:44:35.317117Z","iopub.execute_input":"2025-04-15T11:44:35.317431Z","iopub.status.idle":"2025-04-15T11:44:35.323401Z","shell.execute_reply.started":"2025-04-15T11:44:35.317408Z","shell.execute_reply":"2025-04-15T11:44:35.322339Z"}},"outputs":[],"execution_count":null},{"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    experimental = False\n    if experimental:\n        # calculate rolling mean along z-axis\n        images = rolling_mean_image(torch.tensor(images).float(), dim=2, window=21).numpy() / 255\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-15T11:54:59.410843Z","iopub.execute_input":"2025-04-15T11:54:59.411149Z","iopub.status.idle":"2025-04-15T11:54:59.422736Z","shell.execute_reply.started":"2025-04-15T11:54:59.411126Z","shell.execute_reply":"2025-04-15T11:54:59.421840Z"}},"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-15T11:55:01.630164Z","iopub.execute_input":"2025-04-15T11:55:01.630467Z","iopub.status.idle":"2025-04-15T11:56:14.588932Z","shell.execute_reply.started":"2025-04-15T11:55:01.630421Z","shell.execute_reply":"2025-04-15T11:56:14.588295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_det_all","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T11:56:17.823027Z","iopub.execute_input":"2025-04-15T11:56:17.823304Z","iopub.status.idle":"2025-04-15T11:56:17.838053Z","shell.execute_reply.started":"2025-04-15T11:56:17.823284Z","shell.execute_reply":"2025-04-15T11:56:17.837333Z"}},"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    agg_method: Literal['score_weighted_mean', 'score_highest'] = 'score_weighted_mean',\n) -> pd.DataFrame:\n    assert agg_method in ['score_weighted_mean', 'score_highest']\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            if agg_method == 'score_weighted_mean':\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                score_mean = df_det_grp['score'].mean()\n            elif agg_method == 'score_highest':\n                z = df_det_grp.sort_values(by='score', ascending=False).iloc[0].z\n                y = df_det_grp.sort_values(by='score', ascending=False).iloc[0].y\n                x = df_det_grp.sort_values(by='score', ascending=False).iloc[0].x\n                score_mean = df_det_grp.sort_values(by='score', ascending=False).iloc[0].score\n            else:\n                raise ValueError(agg_method)\n            agg_det = {\n                'tomo_id': tomo_id,\n                'x': x,\n                'y': y,\n                'z': z,\n                'score_mean': 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        if agg_method == 'score_weighted_mean':\n            order_by = ['group_det_count', 'score_mean']\n        elif agg_method == 'score_highest':\n            order_by = ['score_mean', 'group_det_count']\n        else:\n            raise ValueError(agg_method)\n        df_agg_det_tomo = df_agg_det_tomo.sort_values(by=order_by, 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-15T11:56:20.075597Z","iopub.execute_input":"2025-04-15T11:56:20.075889Z","iopub.status.idle":"2025-04-15T11:56:20.085331Z","shell.execute_reply.started":"2025-04-15T11:56:20.075869Z","shell.execute_reply":"2025-04-15T11:56:20.084494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_det_agg = aggregate_detection(\n    df_det_all,\n    score_th=SCORE_TH_AGG,\n    group_dist_th=GROUP_DIST_TH,\n    min_det_per_group=MIN_DET_PER_GROUP,\n    agg_method=AGG_METHOD,\n)\nassert not df_det_agg['tomo_id'].duplicated().any()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T11:56:22.323577Z","iopub.execute_input":"2025-04-15T11:56:22.323898Z","iopub.status.idle":"2025-04-15T11:56:22.341621Z","shell.execute_reply.started":"2025-04-15T11:56:22.323872Z","shell.execute_reply":"2025-04-15T11:56:22.341019Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_det_agg","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T11:56:22.575678Z","iopub.execute_input":"2025-04-15T11:56:22.575945Z","iopub.status.idle":"2025-04-15T11:56:22.584854Z","shell.execute_reply.started":"2025-04-15T11:56:22.575925Z","shell.execute_reply":"2025-04-15T11:56:22.584108Z"}},"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-15T11:56:25.152104Z","iopub.execute_input":"2025-04-15T11:56:25.152408Z","iopub.status.idle":"2025-04-15T11:56:25.157641Z","shell.execute_reply.started":"2025-04-15T11:56:25.152383Z","shell.execute_reply":"2025-04-15T11:56:25.156936Z"}},"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-15T11:56:25.731414Z","iopub.execute_input":"2025-04-15T11:56:25.731723Z","iopub.status.idle":"2025-04-15T11:56:25.737478Z","shell.execute_reply.started":"2025-04-15T11:56:25.731700Z","shell.execute_reply":"2025-04-15T11:56:25.736711Z"}},"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-15T11:56:29.771960Z","iopub.execute_input":"2025-04-15T11:56:29.772241Z","iopub.status.idle":"2025-04-15T11:56:29.784106Z","shell.execute_reply.started":"2025-04-15T11:56:29.772219Z","shell.execute_reply":"2025-04-15T11:56:29.783204Z"}},"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-15T11:56:40.191663Z","iopub.execute_input":"2025-04-15T11:56:40.191951Z","iopub.status.idle":"2025-04-15T11:56:40.196555Z","shell.execute_reply.started":"2025-04-15T11:56:40.191931Z","shell.execute_reply":"2025-04-15T11:56:40.195688Z"}},"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-15T11:56:40.331331Z","iopub.execute_input":"2025-04-15T11:56:40.331652Z","iopub.status.idle":"2025-04-15T11:56:40.343241Z","shell.execute_reply.started":"2025-04-15T11:56:40.331628Z","shell.execute_reply":"2025-04-15T11:56:40.342391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}