{"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":11036126,"sourceType":"datasetVersion","datasetId":6873934},{"sourceId":11066309,"sourceType":"datasetVersion","datasetId":6895834},{"sourceId":227202990,"sourceType":"kernelVersion"}],"dockerImageVersionId":30919,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"colab":{"provenance":[]}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<h1 style=\"color: #6cb4e4;  text-align: center;  padding: 0.25em;  border-top: solid 2.5px #6cb4e4;  border-bottom: solid 2.5px #6cb4e4;  background: -webkit-repeating-linear-gradient(-45deg, #f0f8ff, #f0f8ff 3px,#e9f4ff 3px, #e9f4ff 7px);  background: repeating-linear-gradient(-45deg, #f0f8ff, #f0f8ff 3px,#e9f4ff 3px, #e9f4ff 7px);height:45px;\">\n<b>\nOnly Submission(LoadLocalTrainModels)\n</b></h1>","metadata":{"id":"ew34apO0SH42"}},{"cell_type":"markdown","source":"Note: this notebook coppied from \nhttps://www.kaggle.com/code/hideyukizushi/byu-onlyinf-yolov8l-map-0-933-f1-0-87-lb-634 [0.634]","metadata":{}},{"cell_type":"markdown","source":"### **ℹ️INFO**\n* First of all, I'm grateful to the host for sharing such a great bass line.\n* This notebook is an inference notebook that performed a unique LocalTrain based on the Train/Inference published by the host.\n    * https://www.kaggle.com/code/andrewjdarley/train-yolo\n    * https://www.kaggle.com/code/andrewjdarley/submission-notebook","metadata":{"id":"iEc9LBlkSH4-"}},{"cell_type":"markdown","source":"### **ℹ️2025/03/15 MY-Model(YOLOV11)**\n* **ExpNmae: BYU-model-YOLOv11**\n    * F1: 0.81\n    * mAP: 0.83\n    * Val Loss: 0.90\n    * ModelWeight: https://www.kaggle.com/datasets/engadamalmohammedi/byu-model-yolov11","metadata":{}},{"cell_type":"markdown","source":"### **ℹ️2025/03/10 YOLOV8-Model**\n* **ExpNmae: BYU-A-106-yolov8l-DAexp-MixUpExp**\n    * F1: 0.87\n    * mAP: 0.933\n    * Val Loss: 0.8292\n    * ModelWeight: https://www.kaggle.com/datasets/hideyukizushi/byu-a-106-yolov8l-daexp-mixupexp","metadata":{"id":"JIZV-8zrSH5E"}},{"cell_type":"code","source":"from PIL import Image\ndef get_concat_h_multi_resize(im_list, resample=Image.BICUBIC):\n    min_height = min(im.height for im in im_list)\n    im_list_resize = [im.resize((int(im.width * min_height / im.height), min_height),resample=resample)\n                      for im in im_list]\n    total_width = sum(im.width for im in im_list_resize)\n    dst = Image.new('RGB', (total_width, min_height))\n    pos_x = 0\n    for im in im_list_resize:\n        dst.paste(im, (pos_x, 0))\n        pos_x += im.width\n    return dst","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-17T22:33:43.401716Z","iopub.execute_input":"2025-03-17T22:33:43.402025Z","iopub.status.idle":"2025-03-17T22:33:43.407317Z","shell.execute_reply.started":"2025-03-17T22:33:43.402001Z","shell.execute_reply":"2025-03-17T22:33:43.406185Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# YOLOV11-Train-Results","metadata":{}},{"cell_type":"code","source":"im1 = Image.open('/kaggle/input/byu-model-yolov11/F1_curve.png')\nim2 = Image.open('/kaggle/input/byu-model-yolov11/PR_curve.png')\nim3 = Image.open('/kaggle/input/byu-model-yolov11/dfl_loss_curve.png')\nget_concat_h_multi_resize([im3, im2, im1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-17T22:33:43.813115Z","iopub.execute_input":"2025-03-17T22:33:43.813473Z","iopub.status.idle":"2025-03-17T22:33:44.221018Z","shell.execute_reply.started":"2025-03-17T22:33:43.813442Z","shell.execute_reply":"2025-03-17T22:33:44.220051Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# YOLOV8-Train-Results","metadata":{}},{"cell_type":"code","source":"im1 = Image.open('/kaggle/input/byu-a-106-yolov8l-daexp-mixupexp/F1_curve.png')\nim2 = Image.open('/kaggle/input/byu-a-106-yolov8l-daexp-mixupexp/PR_curve.png')\nim3 = Image.open('/kaggle/input/byu-a-106-yolov8l-daexp-mixupexp/dfl_loss_curve.png')\nget_concat_h_multi_resize([im3, im2, im1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-17T22:33:44.222024Z","iopub.execute_input":"2025-03-17T22:33:44.222271Z","iopub.status.idle":"2025-03-17T22:33:44.542704Z","shell.execute_reply.started":"2025-03-17T22:33:44.222226Z","shell.execute_reply":"2025-03-17T22:33:44.541767Z"},"_kg_hide-input":true,"id":"_FHu2NElSH5I"},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h1 style=\"color: #6cb4e4;  text-align: center;  padding: 0.25em;  border-top: solid 2.5px #6cb4e4;  border-bottom: solid 2.5px #6cb4e4;  background: -webkit-repeating-linear-gradient(-45deg, #f0f8ff, #f0f8ff 3px,#e9f4ff 3px, #e9f4ff 7px);  background: repeating-linear-gradient(-45deg, #f0f8ff, #f0f8ff 3px,#e9f4ff 3px, #e9f4ff 7px);height:45px;\">\n<b>\nInference Pipeline\n</b></h1>","metadata":{"id":"m_VgHcGtSH5Q"}},{"cell_type":"markdown","source":"## **》》》 [IMPORTANT] Env Params**","metadata":{"id":"5VXlYAfeSH5U"}},{"cell_type":"code","source":"\"\"\" [IMPORTANT]\n* This parameter has a significant impact on the value of LB since it is the threshold for the prediction score inferred by the model.\n* In my experiments, 0.5 to 0.55 is optimal for local CV, but when submitting, 0.35 to 0.45 seems to give better results, so there is a difference.\n\"\"\"\nCONFIDENCE_THRESHOLD = 0.43\n\nMAX_DETECTIONS_PER_TOMO = 3\nNMS_IOU_THRESHOLD = 0.1\nCONCENTRATION = 1\nBATCH_SIZE = 8","metadata":{"trusted":true,"id":"sPd0dKqsSH5Z","execution":{"iopub.status.busy":"2025-03-17T22:33:44.543867Z","iopub.execute_input":"2025-03-17T22:33:44.544089Z","iopub.status.idle":"2025-03-17T22:33:44.548304Z","shell.execute_reply.started":"2025-03-17T22:33:44.54407Z","shell.execute_reply":"2025-03-17T22:33:44.547391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\" Train Model \"\"\"\nmodel_yolov8 = \"/kaggle/input/byu-a-106-yolov8l-daexp-mixupexp/best.pt\"\nmodel_yolov11 = \"/kaggle/input/byu-model-yolov11/best.pt\"\nmodel_paths = [model_yolov8,model_yolov11]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-17T22:33:44.708107Z","iopub.execute_input":"2025-03-17T22:33:44.70853Z","iopub.status.idle":"2025-03-17T22:33:44.713426Z","shell.execute_reply.started":"2025-03-17T22:33:44.708487Z","shell.execute_reply":"2025-03-17T22:33:44.712347Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **》》》 Ultralytics Offline Install**(v8.3.88[2025/03/11 ReleaseVersion])","metadata":{"id":"bznGL6S3SH5c"}},{"cell_type":"code","source":"\"\"\"[INFO]\n* This notebookinstall Ultralytics v8.3.88(2025/03/11 ReleaseVersion)\n  Can use YOLO12 is latest family version.\n* If you need a newer version, you can make it available by running and attaching the notebook.\n  https://www.kaggle.com/code/hideyukizushi/ultralytics-offlineinstall-yolo12-weights\n\"\"\"\n!tar xfvz /kaggle/input/ultralytics-offlineinstall-yolo12-weights/archive.tar.gz\n!pip install --no-index --find-links=./packages ultralytics\n!rm -rf ./packages","metadata":{"trusted":true,"_kg_hide-input":false,"scrolled":true,"_kg_hide-output":true,"id":"x9UirWE-SH5d","execution":{"iopub.status.busy":"2025-03-17T22:33:45.056586Z","iopub.execute_input":"2025-03-17T22:33:45.057023Z","iopub.status.idle":"2025-03-17T22:34:11.336865Z","shell.execute_reply.started":"2025-03-17T22:33:45.056982Z","shell.execute_reply":"2025-03-17T22:34:11.335735Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **》》》 Import Libs**","metadata":{"id":"9RXLnjZrSH5e"}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nimport cv2\nfrom tqdm.notebook import tqdm\nfrom ultralytics import YOLO\nimport threading\nimport time\nfrom contextlib import nullcontext\nfrom concurrent.futures import ThreadPoolExecutor","metadata":{"trusted":true,"_kg_hide-input":false,"_kg_hide-output":true,"id":"uwjfGhvnSH5f","execution":{"iopub.status.busy":"2025-03-17T22:34:11.338218Z","iopub.execute_input":"2025-03-17T22:34:11.338552Z","iopub.status.idle":"2025-03-17T22:34:11.343224Z","shell.execute_reply.started":"2025-03-17T22:34:11.338511Z","shell.execute_reply":"2025-03-17T22:34:11.342617Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **》》》 Seed Fix**","metadata":{"id":"9PZcB58zSH5h"}},{"cell_type":"code","source":"np.random.seed(42)\ntorch.manual_seed(42)","metadata":{"trusted":true,"_kg_hide-input":false,"_kg_hide-output":true,"id":"SlTtERqoSH5i","execution":{"iopub.status.busy":"2025-03-17T22:34:11.344949Z","iopub.execute_input":"2025-03-17T22:34:11.345194Z","iopub.status.idle":"2025-03-17T22:34:12.222646Z","shell.execute_reply.started":"2025-03-17T22:34:11.345173Z","shell.execute_reply":"2025-03-17T22:34:12.221868Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **》》》 Inference&Submission**","metadata":{"id":"8iRqAdTmSH5j"}},{"cell_type":"markdown","source":"* Dataset","metadata":{"id":"zd7AOolsSH5l"}},{"cell_type":"code","source":"data_path = \"/kaggle/input/byu-locating-bacterial-flagellar-motors-2025/\"\ntest_dir = os.path.join(data_path, \"test\")\nsubmission_path = \"/kaggle/working/submission.csv\"","metadata":{"trusted":true,"id":"hgVCyl8hSH5m","execution":{"iopub.status.busy":"2025-03-17T22:34:12.223978Z","iopub.execute_input":"2025-03-17T22:34:12.224323Z","iopub.status.idle":"2025-03-17T22:34:12.234624Z","shell.execute_reply.started":"2025-03-17T22:34:12.22429Z","shell.execute_reply":"2025-03-17T22:34:12.233857Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"* GPU Init","metadata":{"id":"_rHfycJFSH5n"}},{"cell_type":"code","source":"class GPUProfiler:\n    def __init__(self, name):\n        self.name = name\n        self.start_time = None\n\n    def __enter__(self):\n        if torch.cuda.is_available():\n            torch.cuda.synchronize()\n        self.start_time = time.time()\n        return self\n\n    def __exit__(self, *args):\n        if torch.cuda.is_available():\n            torch.cuda.synchronize()\n        elapsed = time.time() - self.start_time\n        # print(f\"[PROFILE] {self.name}: {elapsed:.3f}s\")\n\n\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'\nif device.startswith('cuda'):\n    # Set CUDA optimization flags\n    torch.backends.cudnn.benchmark = True\n    torch.backends.cudnn.deterministic = False\n    torch.backends.cuda.matmul.allow_tf32 = True  # Allow TF32 on Ampere GPUs\n    torch.backends.cudnn.allow_tf32 = True\n\n    # Print GPU info\n    gpu_name = torch.cuda.get_device_name(0)\n    gpu_mem = torch.cuda.get_device_properties(0).total_memory / 1e9  # Convert to GB\n    print(f\"Using GPU: {gpu_name} with {gpu_mem:.2f} GB memory\")\n\n    # Get available GPU memory and set batch size accordingly\n    free_mem = gpu_mem - torch.cuda.memory_allocated(0) / 1e9\n    BATCH_SIZE = max(8, min(32, int(free_mem * 4)))  # 4 images per GB as rough estimate\n    print(f\"Dynamic batch size set to {BATCH_SIZE} based on {free_mem:.2f}GB free memory\")\nelse:\n    print(\"GPU not available, using CPU\")\n    BATCH_SIZE = 4  # Reduce batch size for CPU","metadata":{"trusted":true,"id":"wNHofLp3SH5p","execution":{"iopub.status.busy":"2025-03-17T22:34:12.23567Z","iopub.execute_input":"2025-03-17T22:34:12.236021Z","iopub.status.idle":"2025-03-17T22:34:12.249452Z","shell.execute_reply.started":"2025-03-17T22:34:12.235974Z","shell.execute_reply":"2025-03-17T22:34:12.248463Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"* Inference","metadata":{"id":"jIfZV6x0SH5s"}},{"cell_type":"code","source":"def normalize_slice(slice_data):\n    \"\"\"\n    Normalize slice data using 2nd and 98th percentiles for better contrast\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            # Try with PIL as fallback\n            img = np.array(Image.open(path))\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    # Sort by confidence (highest first)\n    detections = sorted(detections, key=lambda x: x['confidence'], reverse=True)\n\n    # List to store final detections after NMS\n    final_detections = []\n\n    # Define 3D distance function\n    def distance_3d(d1, d2):\n        return np.sqrt((d1['z'] - d2['z'])**2 +\n                       (d1['y'] - d2['y'])**2 +\n                       (d1['x'] - d2['x'])**2)\n\n    # Maximum distance threshold (based on box size and slice gap)\n    box_size = 24  # Same as annotation box size\n    distance_threshold = box_size * iou_threshold\n\n    # Process each detection\n    while detections:\n        # Take the detection with highest confidence\n        best_detection = detections.pop(0)\n        final_detections.append(best_detection)\n\n        # Filter out detections that are too close to the best detection\n        detections = [d for d in detections if distance_3d(d, best_detection) > distance_threshold]\n\n    return final_detections","metadata":{"trusted":true,"scrolled":true,"_kg_hide-input":true,"id":"8ZQ8WL8VSH5t","execution":{"iopub.status.busy":"2025-03-17T22:34:12.250369Z","iopub.execute_input":"2025-03-17T22:34:12.250683Z","iopub.status.idle":"2025-03-17T22:34:12.265033Z","shell.execute_reply.started":"2025-03-17T22:34:12.250651Z","shell.execute_reply":"2025-03-17T22:34:12.264119Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Ensemble Models (YOLOV8 + YOLOV11) ","metadata":{}},{"cell_type":"code","source":"def process_tomogram(tomo_id, models, index=0, total=1):\n    \"\"\"\n    Process a single tomogram using multiple YOLO models for ensemble inference.\n    Returns the most confident motor detection based on weighted averaging.\n    \"\"\"\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    selected_indices = np.linspace(0, len(slice_files)-1, int(len(slice_files) * CONCENTRATION))\n    selected_indices = np.round(selected_indices).astype(int)\n    slice_files = [slice_files[i] for i in selected_indices]\n\n    all_detections = []\n\n    if device.startswith('cuda'):\n        streams = [torch.cuda.Stream() for _ in range(min(4, BATCH_SIZE))]\n    else:\n        streams = [None]\n\n    next_batch_thread = None\n    next_batch_images = None\n\n    for batch_start in range(0, len(slice_files), BATCH_SIZE):\n        if next_batch_thread is not None:\n            next_batch_thread.join()\n            next_batch_images = None\n\n        batch_end = min(batch_start + BATCH_SIZE, len(slice_files))\n        batch_files = slice_files[batch_start:batch_end]\n\n        next_batch_start = batch_end\n        next_batch_end = min(next_batch_start + BATCH_SIZE, len(slice_files))\n        next_batch_files = slice_files[next_batch_start:next_batch_end] if next_batch_start < len(slice_files) else []\n\n        if next_batch_files:\n            next_batch_paths = [os.path.join(tomo_dir, f) for f in next_batch_files]\n            next_batch_thread = threading.Thread(target=preload_image_batch, args=(next_batch_paths,))\n            next_batch_thread.start()\n        else:\n            next_batch_thread = None\n\n        sub_batches = np.array_split(batch_files, len(streams))\n        sub_batch_results = []\n\n        for i, sub_batch in enumerate(sub_batches):\n            if len(sub_batch) == 0:\n                continue\n\n            stream = streams[i % len(streams)]\n            with torch.cuda.stream(stream) if stream and device.startswith('cuda') else nullcontext():\n                sub_batch_paths = [os.path.join(tomo_dir, slice_file) for slice_file in sub_batch]\n                sub_batch_slice_nums = [int(slice_file.split('_')[1].split('.')[0]) for slice_file in sub_batch]\n\n                ensemble_predictions = []\n\n                for model in models:\n                    with GPUProfiler(f\"Inference batch {i+1}/{len(sub_batches)}\"):\n                        sub_results = model(sub_batch_paths, verbose=False)\n\n                    for j, result in enumerate(sub_results):\n                        if len(result.boxes) > 0:\n                            boxes = result.boxes\n                            for box_idx, confidence in enumerate(boxes.conf):\n                                if confidence >= CONFIDENCE_THRESHOLD:\n                                    x1, y1, x2, y2 = boxes.xyxy[box_idx].cpu().numpy()\n                                    x_center = (x1 + x2) / 2\n                                    y_center = (y1 + y2) / 2\n\n                                    ensemble_predictions.append({\n                                        'z': round(sub_batch_slice_nums[j]),\n                                        'y': round(y_center),\n                                        'x': round(x_center),\n                                        'confidence': float(confidence)\n                                    })\n\n                # Fusion of ensemble predictions (Weighted Averaging)\n                fused_detections = fuse_ensemble_detections(ensemble_predictions)\n                all_detections.extend(fused_detections)\n\n        if device.startswith('cuda'):\n            torch.cuda.synchronize()\n\n    if next_batch_thread is not None:\n        next_batch_thread.join()\n\n    final_detections = perform_3d_nms(all_detections, NMS_IOU_THRESHOLD)\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\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    }\n\ndef fuse_ensemble_detections(detections):\n    \"\"\"\n    Perform weighted averaging on multiple model predictions.\n    \"\"\"\n    if not detections:\n        return []\n\n    # Sort detections by confidence\n    detections = sorted(detections, key=lambda x: x['confidence'], reverse=True)\n\n    grouped_detections = []\n    while detections:\n        base_det = detections.pop(0)\n        similar_detections = [base_det]\n\n        for det in detections[:]:\n            if np.linalg.norm(np.array([det['z'], det['y'], det['x']]) -\n                              np.array([base_det['z'], base_det['y'], base_det['x']])) < 10:\n                similar_detections.append(det)\n                detections.remove(det)\n\n        avg_z = np.mean([d['z'] for d in similar_detections])\n        avg_y = np.mean([d['y'] for d in similar_detections])\n        avg_x = np.mean([d['x'] for d in similar_detections])\n        avg_conf = np.mean([d['confidence'] for d in similar_detections])\n\n        grouped_detections.append({'z': round(avg_z), 'y': round(avg_y), 'x': round(avg_x), 'confidence': avg_conf})\n\n    return grouped_detections\n\ndef generate_submission():\n    \"\"\"\n    Generate submission file using ensemble models.\n    \"\"\"\n    test_tomos = sorted([d for d in os.listdir(test_dir) if os.path.isdir(os.path.join(test_dir, d))])\n    total_tomos = len(test_tomos)\n\n    if torch.cuda.is_available():\n        torch.cuda.empty_cache()\n    models = [YOLO(path).to(device) for path in model_paths]\n\n    if device.startswith('cuda'):\n        for model in models:\n            model.fuse()\n            if torch.cuda.get_device_capability(0)[0] >= 7:\n                model.model.half()\n\n    results = []\n    motors_found = 0\n\n    with ThreadPoolExecutor(max_workers=2) as executor:\n        future_to_tomo = {}\n\n        for i, tomo_id in enumerate(test_tomos, 1):\n            future = executor.submit(process_tomogram, tomo_id, models, i, total_tomos)\n            future_to_tomo[future] = tomo_id\n\n        for future in future_to_tomo:\n            tomo_id = future_to_tomo[future]\n            try:\n                if torch.cuda.is_available():\n                    torch.cuda.empty_cache()\n\n                result = future.result()\n                results.append(result)\n\n                if result['Motor axis 0'] != -1:\n                    motors_found += 1\n                    print(f\"Motor found in {tomo_id} at z={result['Motor axis 0']}, y={result['Motor axis 1']}, x={result['Motor axis 2']}\")\n                else:\n                    print(f\"No motor detected in {tomo_id}\")\n\n                print(f\"Detection rate: {motors_found}/{len(results)} ({motors_found/len(results)*100:.1f}%)\")\n\n            except Exception as e:\n                print(f\"Error processing {tomo_id}: {e}\")\n                results.append({'tomo_id': tomo_id, 'Motor axis 0': -1, 'Motor axis 1': -1, 'Motor axis 2': -1})\n\n    submission_df = pd.DataFrame(results)\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(\"= Submission Preview:\")\n    print(submission_df.head())\n\n    return submission_df","metadata":{"id":"4wQlmeHHUY7f","trusted":true,"execution":{"iopub.status.busy":"2025-03-17T22:34:12.266255Z","iopub.execute_input":"2025-03-17T22:34:12.266595Z","iopub.status.idle":"2025-03-17T22:34:12.2875Z","shell.execute_reply.started":"2025-03-17T22:34:12.266564Z","shell.execute_reply":"2025-03-17T22:34:12.286634Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Run the submission pipeline\nif __name__ == \"__main__\":\n    # Time entire process\n    start_time = time.time()\n    # Generate submission\n    submission = generate_submission()\n    print(submission.shape)\n    # Print total execution time\n    elapsed = time.time() - start_time\n    print(f\"\\nTotal execution time: {elapsed:.2f} seconds ({elapsed/60:.2f} minutes)\")","metadata":{"id":"vU3dMLWqeqtT","trusted":true,"execution":{"iopub.status.busy":"2025-03-17T22:34:12.288516Z","iopub.execute_input":"2025-03-17T22:34:12.288851Z","iopub.status.idle":"2025-03-17T22:36:07.531293Z","shell.execute_reply.started":"2025-03-17T22:34:12.28882Z","shell.execute_reply":"2025-03-17T22:36:07.530429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"submitted\")\nsubmission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-17T22:36:07.533462Z","iopub.execute_input":"2025-03-17T22:36:07.533712Z","iopub.status.idle":"2025-03-17T22:36:07.549075Z","shell.execute_reply.started":"2025-03-17T22:36:07.533693Z","shell.execute_reply":"2025-03-17T22:36:07.54821Z"}},"outputs":[],"execution_count":null}]}