{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":91249,"databundleVersionId":11294684,"sourceType":"competition"},{"sourceId":11607921,"sourceType":"datasetVersion","datasetId":7280849},{"sourceId":11608019,"sourceType":"datasetVersion","datasetId":7280896},{"sourceId":11608122,"sourceType":"datasetVersion","datasetId":7280982},{"sourceId":11608731,"sourceType":"datasetVersion","datasetId":7281380},{"sourceId":11608941,"sourceType":"datasetVersion","datasetId":7281519},{"sourceId":11635348,"sourceType":"datasetVersion","datasetId":7300346},{"sourceId":11635730,"sourceType":"datasetVersion","datasetId":7300650},{"sourceId":11635881,"sourceType":"datasetVersion","datasetId":7300755},{"sourceId":11635946,"sourceType":"datasetVersion","datasetId":7300806},{"sourceId":11638635,"sourceType":"datasetVersion","datasetId":7302785},{"sourceId":11640761,"sourceType":"datasetVersion","datasetId":7304368},{"sourceId":11640929,"sourceType":"datasetVersion","datasetId":7304489},{"sourceId":11706434,"sourceType":"datasetVersion","datasetId":7347937},{"sourceId":11777898,"sourceType":"datasetVersion","datasetId":7394350},{"sourceId":11791368,"sourceType":"datasetVersion","datasetId":7403837},{"sourceId":11795625,"sourceType":"datasetVersion","datasetId":7407053},{"sourceId":11798097,"sourceType":"datasetVersion","datasetId":7408832},{"sourceId":11801003,"sourceType":"datasetVersion","datasetId":7410928},{"sourceId":11988326,"sourceType":"datasetVersion","datasetId":7540316},{"sourceId":226864880,"sourceType":"kernelVersion"}],"dockerImageVersionId":30919,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!tar xfvz /kaggle/input/ultralytics-for-offline-install/archive.tar.gz\n!pip install --no-index --find-links=./packages ultralytics\n!rm -rf ./packages","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T23:36:32.672402Z","iopub.execute_input":"2025-05-27T23:36:32.672792Z","iopub.status.idle":"2025-05-27T23:37:27.688951Z","shell.execute_reply.started":"2025-05-27T23:36:32.672764Z","shell.execute_reply":"2025-05-27T23:37:27.687897Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import plotly.express as px\nfrom PIL import Image, ImageDraw\nimport random\nimport seaborn as sns\nfrom matplotlib.patches import Rectangle\nfrom ultralytics import YOLO\nimport yaml\nimport json\nimport os\nimport glob\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom tqdm.notebook import tqdm\nfrom sklearn.model_selection import train_test_split\nimport torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import torch.optim as optim\n# from torch.utils.data import Dataset, DataLoader\n# from torch.optim.lr_scheduler import ReduceLROnPlateau\nimport cv2\nimport threading\nimport time\nfrom contextlib import nullcontext\nfrom concurrent.futures import ThreadPoolExecutor\nfrom concurrent.futures import ProcessPoolExecutor\nimport math","metadata":{"trusted":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-05-27T23:37:27.690357Z","iopub.execute_input":"2025-05-27T23:37:27.690632Z","iopub.status.idle":"2025-05-27T23:37:35.996202Z","shell.execute_reply.started":"2025-05-27T23:37:27.690609Z","shell.execute_reply":"2025-05-27T23:37:35.995522Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1.1) Global Constants and Environment Setup\n\nWe define paths, create necessary directories, set up the computing device (GPU if available), and fix random seeds for reproducibility.\n","metadata":{}},{"cell_type":"code","source":"# Set device: Use GPU if available; otherwise, fall back to CPU\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# print(f\"Using device: {DEVICE}\")\n\n# Set random seeds for reproducibility\nRANDOM_SEED = 42\nrandom.seed(RANDOM_SEED)\nnp.random.seed(RANDOM_SEED)\n# torch.manual_seed(RANDOM_SEED)\n# if torch.cuda.is_available():\n#     torch.cuda.manual_seed(RANDOM_SEED)\n#     torch.backends.cudnn.deterministic = True\n\nassert torch.cuda.device_count() == 2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T23:37:35.997889Z","iopub.execute_input":"2025-05-27T23:37:35.998493Z","iopub.status.idle":"2025-05-27T23:37:36.029518Z","shell.execute_reply.started":"2025-05-27T23:37:35.998434Z","shell.execute_reply":"2025-05-27T23:37:36.02871Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4.1) Setting Up Inference Libraries\n\nWe import additional libraries and set up some GPU and batch parameters for efficient inference.","metadata":{}},{"cell_type":"code","source":"# Set random seed for reproducibility\nnp.random.seed(42)\n# torch.manual_seed(42)\n\n# Define paths for the test data and submission\ndata_path = \"/kaggle/input/byu-locating-bacterial-flagellar-motors-2025/\"\ntest_dir = os.path.join(data_path, \"test\")\nsubmission_path = \"/kaggle/working/submission.csv\"\n\n# Path to the best trained model (adjust if necessary)\nmodel_path = \"/kaggle/input/yolov8l-960-bs32-tr8vd4-ext2-lr-f1f2f3f4/yolo_weights14/best_dfl/epoch12_optimized.pt\"\nmodel_path2 = \"/kaggle/input/yolov8l-960-bs32-tr8vd4-ext2-lr-f1f2f3f4/yolo_weights24/best_dfl/epoch9_optimized.pt\"\nmodel_path3 = \"/kaggle/input/yolov8l-960-bs32-tr8vd4-ext2-lr-f1f2f3f4/yolo_weights34/best_dfl/epoch12_optimized.pt\"\nmodel_path4 = \"/kaggle/input/yolov8l-960-bs32-tr8vd4-ext2-lr-f1f2f3f4/yolo_weights44/best_dfl/epoch5_optimized.pt\"\n\nmodel_path5 = \"/kaggle/input/yolo11l-960-bs32-t8augv4aug-fold1-ext2/yolo_weights/best_dfl/epoch3_optimized.pt\"\nmodel_path6 = \"/kaggle/input/yolo11l-960-bs32-t8augv4aug-fold2-ext2/yolo_weights/best_dfl/epoch6_optimized.pt\"\nmodel_path7 = \"/kaggle/input/yolo11l-960-bs32-t8augv4aug-fold3-ext2/yolo_weights/best_dfl/epoch13_optimized.pt\"\nmodel_path8 = \"/kaggle/input/yolo11l-960-bs32-t8augv4aug-fold4-ext2/yolo_weights/best_dfl/best.pt\"\n\n# model_path5 = \"/kaggle/input/yolo11l-960-bs32-t8v4aug-hold09-ext2/yolo_weights/best_dfl/best.pt\"\n\n# model_path5 = \"/kaggle/input/yolov8l-tr8vd4-t2woi-dflloss-f1/yolo_weights/best_dfl/epoch8_optimized.pt\"\n# model_path6 = \"/kaggle/input/yolov8l-tr8vd4-t2woi-dflloss-f2/yolo_weights/best_dfl/epoch12_optimized.pt\"\n# model_path7 = \"/kaggle/input/yolov8l-tr8vd4-t2woi-dflloss-f3/yolo_weights/best_dfl/epoch5_optimized.pt\"\n# model_path8 = \"/kaggle/input/yolov8l-tr8vd4-t2woi-dflloss-f4/yolo_weights/best_dlf/best.pt\"\n# model_path9 = \"/kaggle/input/yolov8l-tr8vd4-t2iwoi-dflloss-f5/yolo_weights/best_dfl/best.pt\"\n\n# model_path10 = \"/kaggle/input/yolo11l-960-bs32-t8v4aug-hold09-ext2/yolo_weights/best_dfl/best.pt\"\n# model_path11 = \"/kaggle/input/yolov10l-960-bs32-tr8vd4-fold1/yolo_weights/best_dfl/epoch20_optimized.pt\"\n# model_path12 = \"/kaggle/input/yolov8l-960-bs32-t8v4-ext2-fold33/yolo_weights/best_dfl/epoch14_optimized.pt\"\n\n# Define detection and processing parameters\nCONFIDENCE_THRESHOLD = 0.45\n# MAX_DETECTIONS_PER_TOMO = 3\nNMS_IOU_THRESHOLD = 60\nCONCENTRATION = 1  # Process a fraction of slices for fast submission\n\n#子プロセスがCUDAを使用しようとすると、親から継承したCUDAコンテキストが壊れた状態になっています\n#ので、ここでmodelを定義する。子プロセスで定義するとエラーになる\nprint(f\"Loading YOLO model from {model_path}\")\nmodel = YOLO(model_path)\nmodel2 = YOLO(model_path2)\nmodel3 = YOLO(model_path3)\nmodel4 = YOLO(model_path4)\nmodel5 = YOLO(model_path5)\nmodel6 = YOLO(model_path6)\nmodel7 = YOLO(model_path7)\nmodel8 = YOLO(model_path8)\n# model9 = YOLO(model_path9)\n# model10 = YOLO(model_path10)\n# model11 = YOLO(model_path11)\n# model12 = YOLO(model_path12)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T23:37:36.030919Z","iopub.execute_input":"2025-05-27T23:37:36.031147Z","iopub.status.idle":"2025-05-27T23:37:41.715643Z","shell.execute_reply.started":"2025-05-27T23:37:36.031129Z","shell.execute_reply":"2025-05-27T23:37:41.714552Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4.2) Helper Functions for Inference\n\nThe functions below handle image normalization, preloading batches, processing each tomogram (with 3D NMS), and debugging image loading.","metadata":{}},{"cell_type":"code","source":"def normalize_slice(slice_data):\n    \"\"\"\n    Normalize slice data using the 2nd and 98th percentiles.\n    \"\"\"\n    p2 = np.percentile(slice_data, 2)\n    p98 = np.percentile(slice_data, 98)\n    clipped_data = np.clip(slice_data, p2, p98)\n    normalized = 255 * (clipped_data - p2) / (p98 - p2)\n    return np.uint8(normalized)\n\ndef preload_image_batch(file_paths):\n    \"\"\"Preload a batch of images to CPU memory.\"\"\"\n    images = []\n    for path in file_paths:\n        img = cv2.imread(path)\n        if img is None:\n            img = np.array(Image.open(path))\n        # img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n        images.append(img)\n    return images\n\ndef perform_3d_nms(detections, iou_threshold):\n    \"\"\"\n    Perform 3D Non-Maximum Suppression on detections to merge nearby motors.\n    \"\"\"\n    if not detections:\n        return []\n\n    #confidenceの大きい順に並べる→一番大きいconfidenceを中心点として判定\n    detections = sorted(detections, key=lambda x: x['confidence'], reverse=True)\n\n    final_detections = []\n    def distance_3d(d1, d2):\n        return np.sqrt((d1['z'] - d2['z'])**2 + (d1['y'] - d2['y'])**2 + (d1['x'] - d2['x'])**2)\n    \n    box_size = 24\n    distance_threshold = box_size * iou_threshold\n    \n    while detections:\n        #残ったdetectionの中で最大のconfidenceを持つdetectionを追加\n        best_detection = detections.pop(0)\n        final_detections.append(best_detection)\n        #best_detectionからdistance_threshold 以上離れた点を抽出（他のモーターと判定）\n        detections = [d for d in detections if distance_3d(d, best_detection) > distance_threshold]\n    \n    return final_detections\n\nclass perform_3d_nms2:\n    def __init__(self, detections, iou_threshold):\n        self.detections = detections\n        self.iou_threshold = iou_threshold\n\n    # depth first search.\n    # aggregate the coordinates and confidence scores of connected graphs.\n    def dfs(self, v):\n        self.passed[v] = True\n        self.conf_sum += self.detections[v]['confidence']\n        self.cx += self.detections[v]['x']\n        self.cy += self.detections[v]['y']\n        self.cz += self.detections[v]['z']\n        self.nv += 1\n        for next_v in self.adjacency_list[v]:\n            if (self.passed[next_v]): continue\n            self.dfs(next_v)\n\n    def make_detection(self):\n        if not self.detections:\n            return []\n\n        # define the graph\n        self.adjacency_list = [[] for _ in range(len(self.detections))]\n        # which already passed in dfs\n        self.passed = [False for _ in range(len(self.detections))]\n\n        # print(f'detections={len(self.detections)}')\n        # for i in range(len(self.detections)):\n        #     x1 = self.detections[i]['x']\n        #     y1 = self.detections[i]['y']\n        #     z1 = self.detections[i]['z']\n        #     conf = self.detections[i]['confidence']\n        #     print(f'x={x1} y={y1} z={z1} conf={conf}')\n            \n\n        # box_size = 24\n        # distance_threshold = box_size * self.iou_threshold\n        distance_threshold = self.iou_threshold\n        # distance_threshold = box_size\n        self.detections = sorted(self.detections, key=lambda x: x['z'], reverse=True)\n\n         # Connect two points when they are close enough\n        for i in range(len(self.detections)):\n            x1 = self.detections[i]['x']\n            y1 = self.detections[i]['y']\n            z1 = self.detections[i]['z']\n            for j in range(i+1, len(self.detections), 1):\n                x2 = self.detections[j]['x']\n                y2 = self.detections[j]['y']\n                z2 = self.detections[j]['z']\n                # if abs(z1-z2)>4:\n                #     break\n                dist_p2 = (x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2\n                if dist_p2 < distance_threshold:\n                    self.adjacency_list[i].append(j)\n                    self.adjacency_list[j].append(i)\n\n        # for i in range(len(self.adjacency_list)):\n        #     print(f'adjacency_list[{i}]={self.adjacency_list[i]}')\n    \n        final_detections = []\n        \n        # print(f'adjacency_list={len(self.adjacency_list)}')\n        # Perform DFS on all points and find the center of the sphere from the average of the coordinates\n        for i in range(len(self.detections)):\n            self.conf_sum = 0.0\n            self.nv = 0\n            self.cx = 0\n            self.cy = 0\n            self.cz = 0\n            if not self.passed[i]:\n                self.dfs(i)\n\n            # Different confidence for different particle types\n            if self.nv>=10:\n                # print(f'nv:{self.nv}')\n                final_detections.append({\n                    'z': round(self.cz / self.nv),\n                    'y': round(self.cy / self.nv),\n                    'x': round(self.cx / self.nv),\n                    'confidence': float(self.conf_sum)\n                })\n    \n        return final_detections\n\ndef load_image(i, file_path):\n    img = cv2.imread(file_path)\n    return i, img\n\ndef process_tomogram(tomo_id, device_no):\n    \"\"\"\n    Process a single tomogram and return the most confident motor detection.\n    \"\"\"\n    print(f\"Processing tomogram {tomo_id}\")\n    tomo_dir = os.path.join(test_dir, tomo_id)\n    slice_files = sorted([f for f in os.listdir(tomo_dir) if f.endswith('.jpg')])\n\n    random.seed(RANDOM_SEED)\n    ### skip slice\n    slen = len(slice_files)\n    selected_indices = [random.choice(list(range(i, min(i+4, slen)))) for i in range(0, slen, 4)]\n    selected_indices2 = [random.choice(list(range(i, min(i+4, slen)))) for i in range(0, slen, 4)]\n    selected_indices3 = [random.choice(list(range(i, min(i+4, slen)))) for i in range(0, slen, 4)]\n    selected_indices4 = [random.choice(list(range(i, min(i+4, slen)))) for i in range(0, slen, 4)]\n    # print(f'selected_indices={selected_indices}')\n    # print(f'selected_indices2={selected_indices2}')\n    # print(f'selected_indices3={selected_indices3}')\n    # print(f'selected_indices4={selected_indices4}')\n    ###\n\n    all_detections = []\n\n    #slice1&ensemble\n    with ThreadPoolExecutor(max_workers=1) as executor:\n        futures = []\n        for i in selected_indices:\n            # if i < 10:\n            #     print(f'even i={i}')\n            file_path = os.path.join(tomo_dir, slice_files[i])\n            future = executor.submit(load_image, i, file_path)\n            futures.append(future)\n    \n        for future in futures:\n            i, img = future.result()\n            res = model.predict(img, save=False, device=device_no, batch=1, verbose=False, half=True)\n            for j, result in enumerate(res):\n                if len(result.boxes) > 0:\n                    for box_idx, confidence in enumerate(result.boxes.conf):\n                        if confidence >= CONFIDENCE_THRESHOLD:\n                            x1, y1, x2, y2 = result.boxes.xyxy[box_idx].cpu().numpy()\n                            x_center = (x1 + x2) / 2\n                            y_center = (y1 + y2) / 2\n                            # print(f'z:{i}, y:{round(y_center)}, x:{round(x_center)}, confidence:{float(confidence)}')\n                            all_detections.append({\n                                'z': i,\n                                'y': round(y_center),\n                                'x': round(x_center),\n                                'confidence': float(confidence)\n                            })\n            #ensemble\n            res = model5.predict(img, save=False, device=device_no, batch=1, verbose=False, half=True)\n            for j, result in enumerate(res):\n                if len(result.boxes) > 0:\n                    for box_idx, confidence in enumerate(result.boxes.conf):\n                        if confidence >= CONFIDENCE_THRESHOLD:\n                            x1, y1, x2, y2 = result.boxes.xyxy[box_idx].cpu().numpy()\n                            x_center = (x1 + x2) / 2\n                            y_center = (y1 + y2) / 2\n                            # print(f'z:{i}, y:{round(y_center)}, x:{round(x_center)}, confidence:{float(confidence)}')\n                            all_detections.append({\n                                'z': i,\n                                'y': round(y_center),\n                                'x': round(x_center),\n                                'confidence': float(confidence)\n                            })\n    #slice2&ensemble\n    with ThreadPoolExecutor(max_workers=1) as executor:\n        futures = []\n        for i in selected_indices2:\n            # if i < 10:\n            #     print(f'even i={i}')\n            file_path = os.path.join(tomo_dir, slice_files[i])\n            future = executor.submit(load_image, i, file_path)\n            futures.append(future)\n    \n        for future in futures:\n            i, img = future.result()\n            res = model2.predict(img, save=False, device=device_no, batch=1, verbose=False, half=True)\n            for j, result in enumerate(res):\n                if len(result.boxes) > 0:\n                    for box_idx, confidence in enumerate(result.boxes.conf):\n                        if confidence >= CONFIDENCE_THRESHOLD:\n                            x1, y1, x2, y2 = result.boxes.xyxy[box_idx].cpu().numpy()\n                            x_center = (x1 + x2) / 2\n                            y_center = (y1 + y2) / 2\n                            # print(f'z:{i}, y:{round(y_center)}, x:{round(x_center)}, confidence:{float(confidence)}')\n                            all_detections.append({\n                                'z': i,\n                                'y': round(y_center),\n                                'x': round(x_center),\n                                'confidence': float(confidence)\n                            })\n            #ensemble\n            res = model6.predict(img, save=False, device=device_no, batch=1, verbose=False, half=True)\n            for j, result in enumerate(res):\n                if len(result.boxes) > 0:\n                    for box_idx, confidence in enumerate(result.boxes.conf):\n                        if confidence >= CONFIDENCE_THRESHOLD:\n                            x1, y1, x2, y2 = result.boxes.xyxy[box_idx].cpu().numpy()\n                            x_center = (x1 + x2) / 2\n                            y_center = (y1 + y2) / 2\n                            # print(f'z:{i}, y:{round(y_center)}, x:{round(x_center)}, confidence:{float(confidence)}')\n                            all_detections.append({\n                                'z': i,\n                                'y': round(y_center),\n                                'x': round(x_center),\n                                'confidence': float(confidence)\n                            })\n    #slice3&ensemble                          \n    with ThreadPoolExecutor(max_workers=1) as executor:\n        futures = []\n        for i in selected_indices3:\n            # if i < 10:\n            #     print(f'odd i={i}')\n            file_path = os.path.join(tomo_dir, slice_files[i])\n            future = executor.submit(load_image, i, file_path)\n            futures.append(future)\n    \n        for future in futures:\n            i, img = future.result()\n            res = model3.predict(img, save=False, device=device_no, batch=1, verbose=False, half=True)\n            for j, result in enumerate(res):\n                if len(result.boxes) > 0:\n                    for box_idx, confidence in enumerate(result.boxes.conf):\n                        if confidence >= CONFIDENCE_THRESHOLD:\n                            x1, y1, x2, y2 = result.boxes.xyxy[box_idx].cpu().numpy()\n                            x_center = (x1 + x2) / 2\n                            y_center = (y1 + y2) / 2\n                            # print(f'z:{i}, y:{round(y_center)}, x:{round(x_center)}, confidence:{float(confidence)}')\n                            all_detections.append({\n                                'z': i,\n                                'y': round(y_center),\n                                'x': round(x_center),\n                                'confidence': float(confidence)\n                            })\n            #ensemble\n            res = model7.predict(img, save=False, device=device_no, batch=1, verbose=False, half=True)\n            for j, result in enumerate(res):\n                if len(result.boxes) > 0:\n                    for box_idx, confidence in enumerate(result.boxes.conf):\n                        if confidence >= CONFIDENCE_THRESHOLD:\n                            x1, y1, x2, y2 = result.boxes.xyxy[box_idx].cpu().numpy()\n                            x_center = (x1 + x2) / 2\n                            y_center = (y1 + y2) / 2\n                            # print(f'z:{i}, y:{round(y_center)}, x:{round(x_center)}, confidence:{float(confidence)}')\n                            all_detections.append({\n                                'z': i,\n                                'y': round(y_center),\n                                'x': round(x_center),\n                                'confidence': float(confidence)\n                            })\n    #slice4&ensemble                          \n    with ThreadPoolExecutor(max_workers=1) as executor:\n        futures = []\n        for i in selected_indices4:\n            # if i < 10:\n            #     print(f'odd i={i}')\n            file_path = os.path.join(tomo_dir, slice_files[i])\n            future = executor.submit(load_image, i, file_path)\n            futures.append(future)\n    \n        for future in futures:\n            i, img = future.result()\n            res = model4.predict(img, save=False, device=device_no, batch=1, verbose=False, half=True)\n            for j, result in enumerate(res):\n                if len(result.boxes) > 0:\n                    for box_idx, confidence in enumerate(result.boxes.conf):\n                        if confidence >= CONFIDENCE_THRESHOLD:\n                            x1, y1, x2, y2 = result.boxes.xyxy[box_idx].cpu().numpy()\n                            x_center = (x1 + x2) / 2\n                            y_center = (y1 + y2) / 2\n                            # print(f'z:{i}, y:{round(y_center)}, x:{round(x_center)}, confidence:{float(confidence)}')\n                            all_detections.append({\n                                'z': i,\n                                'y': round(y_center),\n                                'x': round(x_center),\n                                'confidence': float(confidence)\n                            })\n            #ensemble\n            res = model8.predict(img, save=False, device=device_no, batch=1, verbose=False, half=True)\n            for j, result in enumerate(res):\n                if len(result.boxes) > 0:\n                    for box_idx, confidence in enumerate(result.boxes.conf):\n                        if confidence >= CONFIDENCE_THRESHOLD:\n                            x1, y1, x2, y2 = result.boxes.xyxy[box_idx].cpu().numpy()\n                            x_center = (x1 + x2) / 2\n                            y_center = (y1 + y2) / 2\n                            # print(f'z:{i}, y:{round(y_center)}, x:{round(x_center)}, confidence:{float(confidence)}')\n                            all_detections.append({\n                                'z': i,\n                                'y': round(y_center),\n                                'x': round(x_center),\n                                'confidence': float(confidence)\n                            })\n\n    # all_detectionsをzの昇順でソート（DFSがはやくなるかも）\n    all_detections.sort(key=lambda item: item['z'])\n\n    d_nms = perform_3d_nms2(detections=all_detections, iou_threshold=NMS_IOU_THRESHOLD)\n    final_detections = d_nms.make_detection()\n    final_detections.sort(key=lambda x: x['confidence'], reverse=True)\n    \n    if not final_detections:\n        return {'tomo_id': tomo_id, 'Motor axis 0': -1, 'Motor axis 1': -1, 'Motor axis 2': -1}\n    \n    best_detection = final_detections[0]\n    # for i, detect in enumerate(final_detections):\n    #     print(f\"z:{detect['z']}\")\n    #     print(f\"y:{detect['y']}\")\n    #     print(f\"x:{detect['x']}\")\n    #     print(f\"confidence:{detect['confidence']}\")\n    return {\n        'tomo_id': tomo_id,\n        'Motor axis 0': round(best_detection['z']),\n        'Motor axis 1': round(best_detection['y']),\n        'Motor axis 2': round(best_detection['x'])\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T23:47:55.086852Z","iopub.execute_input":"2025-05-27T23:47:55.087224Z","iopub.status.idle":"2025-05-27T23:47:55.128302Z","shell.execute_reply.started":"2025-05-27T23:47:55.087194Z","shell.execute_reply":"2025-05-27T23:47:55.127381Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4.3) Submission Generation\n\nThe function below processes each tomogram in the test directory using parallel processing, applies 3D NMS to merge detections, and then creates a CSV submission file with the predicted motor coordinates.","metadata":{}},{"cell_type":"code","source":"def inference(tomos, device_no):\n    results = []\n    # print(f\"(tomo_len={len(tomos)}\")\n    for i, tomo_id in tqdm(enumerate(tomos), total=len(tomos), desc=f\"device{device_no}\"):\n        result = process_tomogram(tomo_id, device_no)\n        results.append(result)\n    \n    return results #dictのlist","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T23:47:55.129475Z","iopub.execute_input":"2025-05-27T23:47:55.129805Z","iopub.status.idle":"2025-05-27T23:47:55.143925Z","shell.execute_reply.started":"2025-05-27T23:47:55.129782Z","shell.execute_reply":"2025-05-27T23:47:55.143136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_submission():\n    \"\"\"\n    Main function to generate the submission file.\n    \"\"\"\n    test_tomos = sorted([d for d in os.listdir(test_dir) if os.path.isdir(os.path.join(test_dir, d))])\n    # test_tomos20 = test_tomos + test_tomos + test_tomos + test_tomos + test_tomos + test_tomos + test_tomos[:2]\n    # test_tomos = test_tomos20\n    total_tomos = len(test_tomos)\n    sp = total_tomos//4\n    if sp == 0: sp = 1\n    test_tomos0 = test_tomos[:sp]\n    test_tomos1 = test_tomos[sp:sp*2]\n    test_tomos2 = test_tomos[sp*2:sp*3]\n    test_tomos3 = test_tomos[sp*3:]\n    \n    print(f\"Found {total_tomos} tomograms in test directory\")\n\n    with ProcessPoolExecutor(max_workers=4) as executor:\n        results = list(executor.map(inference, (test_tomos0, test_tomos1, test_tomos2, test_tomos3),\n                                    (\"0\", \"0\", \"1\", \"1\"))) #generatorをlist化\n\n    submission0 = pd.DataFrame()\n    submission1 = pd.DataFrame()\n    submission2 = pd.DataFrame()\n    submission3 = pd.DataFrame()\n    if len(results[0]) > 0: submission0 = pd.DataFrame(results[0])\n    if len(results[1]) > 0: submission1 = pd.DataFrame(results[1])\n    if len(results[2]) > 0: submission2 = pd.DataFrame(results[2])\n    if len(results[3]) > 0: submission3 = pd.DataFrame(results[3])\n    \n    submission_df = pd.concat([submission0, submission1, submission2, submission3]).reset_index(drop=True)\n    submission_df = submission_df[['tomo_id', 'Motor axis 0', 'Motor axis 1', 'Motor axis 2']]\n    submission_df.to_csv(submission_path, index=False)\n    \n    print(f\"\\nSubmission complete!\")\n    # print(f\"Motors detected: {(motors_found)}/{total_tomos} ({(motors_found)/total_tomos*100:.1f}%)\")\n    print(f\"Submission saved to: {submission_path}\")\n    print(\"\\nSubmission preview:\")\n    print(submission_df.head())\n    return submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T23:47:55.145492Z","iopub.execute_input":"2025-05-27T23:47:55.14572Z","iopub.status.idle":"2025-05-27T23:47:55.159131Z","shell.execute_reply.started":"2025-05-27T23:47:55.1457Z","shell.execute_reply":"2025-05-27T23:47:55.158203Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4.4) Run the Submission Pipeline\n\nFinally, we time the entire inference process and generate the submission file.","metadata":{}},{"cell_type":"code","source":"# if __name__ == \"__main__\":\nstart_time = time.time()\nsubmission = generate_submission()\nelapsed = time.time() - start_time\nprint(f\"\\nTotal execution time: {elapsed:.2f} seconds ({elapsed/60:.2f} minutes)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T23:47:55.16024Z","iopub.execute_input":"2025-05-27T23:47:55.160527Z"}},"outputs":[],"execution_count":null}]}