{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":52279,"databundleVersionId":5822112},{"sourceType":"datasetVersion","sourceId":16517466,"datasetId":10551832,"databundleVersionId":17521052},{"sourceType":"datasetVersion","sourceId":16466463,"datasetId":10551993,"databundleVersionId":17466423}],"dockerImageVersionId":31329,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import sys\nfrom unittest.mock import MagicMock\n\n# 1. 魔法：建立假的 thop 模組，騙過系統檢查 (推論時不需要算 FLOPs)\nsys.modules['thop'] = MagicMock()\n\n# 2. 直接加入你確認好的絕對路徑\nyolov12_path = '/kaggle/input/datasets/taisu0907/yolov12-source-code/yolov12'\nsys.path.append(yolov12_path)\nprint(f\"已將 {yolov12_path} 加入系統路徑\")\n\n# 3. 載入模型與相關套件\nimport torch\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom ultralytics import YOLO\n\nprint(f\"PyTorch Version: {torch.__version__}\")\nprint(\"🎉 YOLO 模組載入成功！\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-29T15:07:51.791335Z","iopub.execute_input":"2026-05-29T15:07:51.791583Z","iopub.status.idle":"2026-05-29T15:08:00.915639Z","shell.execute_reply.started":"2026-05-29T15:07:51.791560Z","shell.execute_reply":"2026-05-29T15:08:00.914780Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport base64\nimport zlib\nfrom pathlib import Path\nfrom pycocotools import _mask as coco_mask\nimport torch\nfrom ultralytics import YOLO\nfrom torchvision.ops import box_iou\n\n# 1. 環境設定\ntest_dir = Path(\"/kaggle/input/competitions/hubmap-hacking-the-human-vasculature/test\")\nsub_path = Path(\"/kaggle/input/competitions/hubmap-hacking-the-human-vasculature/sample_submission.csv\")\nweights_dir = Path(\"/kaggle/input/datasets/taisu0907/hubmap-yolov12-weights\")\n\n# 2. 載入模型\nprint(\"載入 5-Fold 模型中...\")\nmodels = []\nfor i in range(5):\n    pt_path = weights_dir / f\"hubmap_seg_5fold/fold_{i}/weights/best.pt\"\n    if pt_path.exists():\n        models.append(YOLO(str(pt_path)))\n\nif not models:\n    raise RuntimeError(\"找不到任何權重檔！請檢查路徑。\")\n\n# 3. 編碼工具\ndef encode_binary_mask(mask: np.ndarray) -> str:\n    mask_to_encode = mask.reshape(mask.shape[0], mask.shape[1], 1).astype(np.uint8)\n    mask_to_encode = np.asfortranarray(mask_to_encode)\n    encoded_mask = coco_mask.encode(mask_to_encode)[0][\"counts\"]\n    binary_str = zlib.compress(encoded_mask, zlib.Z_BEST_COMPRESSION)\n    return base64.b64encode(binary_str).decode('utf-8')\n\n# 4. 讀取官方 sample_submission 確保 Row 數量與 ID 絕對正確\nsub_df = pd.read_csv(sub_path)\nsubmission_data = []\n\nprint(f\"開始處理測試集，官方要求提交 {len(sub_df)} 張影像...\")\n\nfor img_id in sub_df['id']:\n    img_path = test_dir / f\"{img_id}.tif\"\n    if not img_path.exists():\n        continue\n        \n    img = cv2.imread(str(img_path))\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    \n    # 🌟 防爆點 1：動態抓取真實寬高，絕不寫死\n    h, w = img.shape[:2] \n    \n    all_boxes = []\n    all_scores = []\n    all_masks = []\n    \n    for model in models:\n        # --- 正常視角 ---\n        results_normal = model.predict(source=img, conf=0.01, imgsz=512, retina_masks=True, verbose=False)[0]\n        if results_normal.boxes is not None and len(results_normal.boxes) > 0:\n            for i, cls in enumerate(results_normal.boxes.cls.cpu().numpy()):\n                if int(cls) == 0:\n                    all_boxes.append(results_normal.boxes.xyxy[i].cpu().numpy())\n                    all_scores.append(results_normal.boxes.conf[i].cpu().item())\n                    all_masks.append(results_normal.masks.data[i].cpu().numpy())\n\n        # --- TTA 視角 ---\n        img_flipped = cv2.flip(img, 1)\n        results_flipped = model.predict(source=img_flipped, conf=0.01, imgsz=512, retina_masks=True, verbose=False)[0]\n        \n        if results_flipped.boxes is not None and len(results_flipped.boxes) > 0:\n            for i, cls in enumerate(results_flipped.boxes.cls.cpu().numpy()):\n                if int(cls) == 0:\n                    box = results_flipped.boxes.xyxy[i].cpu().numpy().copy()\n                    x1, y1, x2, y2 = box\n                    # 🌟 防爆點 2：使用動態寬度 w 進行座標翻轉\n                    box[0] = w - x2\n                    box[2] = w - x1\n                    all_boxes.append(box)\n                    all_scores.append(results_flipped.boxes.conf[i].cpu().item())\n                    \n                    mask = results_flipped.masks.data[i].cpu().numpy()\n                    mask = cv2.flip(mask, 1)\n                    all_masks.append(mask)\n\n    # 5. Mask-WBF 融合\n    prediction_strings = []\n    if len(all_boxes) > 0:\n        boxes_t = torch.tensor(np.array(all_boxes), dtype=torch.float32)\n        scores_t = torch.tensor(np.array(all_scores), dtype=torch.float32)\n        masks_t = torch.tensor(np.array(all_masks), dtype=torch.float32)\n        \n        sorted_idx = torch.argsort(scores_t, descending=True)\n        boxes_t = boxes_t[sorted_idx]\n        scores_t = scores_t[sorted_idx]\n        masks_t = masks_t[sorted_idx]\n        \n        keep_predictions = []\n        \n        while len(boxes_t) > 0:\n            best_box = boxes_t[0:1]\n            best_score = scores_t[0].item()\n            best_mask = masks_t[0]\n            \n            if len(boxes_t) == 1:\n                penalized_score = best_score * 0.1\n                if penalized_score > 0.02: \n                    keep_predictions.append((best_mask, penalized_score))\n                break\n                \n            ious = box_iou(best_box, boxes_t[1:])[0]\n            overlap_idxs = (ious > 0.5).nonzero(as_tuple=True)[0] + 1\n            \n            if len(overlap_idxs) > 0:\n                cluster_masks = torch.cat([best_mask.unsqueeze(0), masks_t[overlap_idxs]])\n                avg_mask = torch.mean(cluster_masks, dim=0)\n                \n                consensus_weight = (len(overlap_idxs) + 1) / 10.0\n                # 🌟 防爆點 3：致命 Bug 修復，強制把分數鎖在 0.9999 以下\n                final_score = min(0.9999, best_score * consensus_weight) \n                \n                keep_predictions.append((avg_mask, final_score))\n                \n                remove_idxs = torch.cat([torch.tensor([0]), overlap_idxs])\n                keep_mask_idx = torch.ones(len(boxes_t), dtype=torch.bool)\n                keep_mask_idx[remove_idxs] = False\n                \n                boxes_t = boxes_t[keep_mask_idx]\n                scores_t = scores_t[keep_mask_idx]\n                masks_t = masks_t[keep_mask_idx]\n            else:\n                penalized_score = best_score * 0.1\n                if penalized_score > 0.02:\n                    keep_predictions.append((best_mask, penalized_score))\n                    \n                boxes_t = boxes_t[1:]\n                scores_t = scores_t[1:]\n                masks_t = masks_t[1:]\n\n        for mask_t, conf in keep_predictions:\n            binary_mask = (mask_t.numpy() > 0.45).astype(np.uint8)\n            if binary_mask.sum() == 0: continue\n            \n            # 🌟 防爆點 4：確認 Mask 尺寸與原圖一致 (保險機制)\n            if binary_mask.shape != (h, w):\n                binary_mask = cv2.resize(binary_mask, (w, h), interpolation=cv2.INTER_NEAREST)\n                \n            encoded_str = encode_binary_mask(binary_mask)\n            prediction_strings.append(f\"0 {conf:.4f} {encoded_str}\")\n            \n    final_pred_str = \" \".join(prediction_strings)\n    \n    submission_data.append({\n        \"id\": img_id, \n        \"height\": h, \n        \"width\": w, \n        \"prediction_string\": final_pred_str\n    })\n\n# 6. 輸出\ndf_sub = pd.DataFrame(submission_data)\ndf_sub.to_csv(\"submission.csv\", index=False)\nprint(f\"✅ submission.csv 建立完成，成功處理 {len(df_sub)} 筆對齊官方資料！\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T15:08:00.917330Z","iopub.execute_input":"2026-05-29T15:08:00.917799Z","iopub.status.idle":"2026-05-29T15:08:21.516725Z","shell.execute_reply.started":"2026-05-29T15:08:00.917771Z","shell.execute_reply":"2026-05-29T15:08:21.516029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import cv2\n# import numpy as np\n# import pandas as pd\n# import base64\n# import zlib\n# from pathlib import Path\n# from ultralytics import YOLO\n# from pycocotools import _mask as coco_mask\n\n# # Kaggle 競賽隱藏測試集的路徑\n# test_dir = Path(\"/kaggle/input/competitions/hubmap-hacking-the-human-vasculature/test\")\n# # 你的權重路徑\n# model_path = \"/kaggle/input/datasets/taisu0907/hubmap-yolov12-weights/best_vessel.pt\"\n\n# # 載入模型\n# model = YOLO(model_path)\n\n# def encode_binary_mask(mask: np.ndarray) -> str:\n#     \"\"\"將 2D numpy array 轉換為 COCO RLE -> zlib -> Base64 格式\"\"\"\n#     # 轉換為 COCO API 預期的 Fortran array 格式\n#     mask_to_encode = mask.reshape(mask.shape[0], mask.shape[1], 1)\n#     mask_to_encode = mask_to_encode.astype(np.uint8)\n#     mask_to_encode = np.asfortranarray(mask_to_encode)\n    \n#     # 1. 取得 COCO RLE 編碼\n#     encoded_mask = coco_mask.encode(mask_to_encode)[0][\"counts\"]\n#     # 2. zlib 壓縮\n#     binary_str = zlib.compress(encoded_mask, zlib.Z_BEST_COMPRESSION)\n#     # 3. Base64 編碼\n#     base64_str = base64.b64encode(binary_str)\n    \n#     return base64_str.decode('utf-8')\n\n# submission_data = []\n\n# print(\"開始處理測試集...\")\n# for img_name in os.listdir(test_dir):\n#     if not img_name.endswith('.tif'):\n#         continue\n        \n#     img_id = img_name.split('.')[0]\n#     img_path = test_dir / img_name\n    \n#     # 執行預測\n#     results = model.predict(source=str(img_path), conf=0.01, imgsz=512, retina_masks=True, augment=True, verbose=False)\n    \n#     prediction_strings = []\n    \n#     for r in results:\n#         boxes = r.boxes\n#         if r.masks is not None and boxes is not None:\n#             masks = r.masks.data.cpu().numpy()\n#             confs = boxes.conf.cpu().numpy()\n#             clss = boxes.cls.cpu().numpy()  # 🌟 修正 1：抓取模型預測的類別 ID\n            \n#             for mask, conf, cls_id in zip(masks, confs, clss):\n#                 # 🌟 修正 2：只保留 blood_vessel (類別 0)，無情略過 glomerulus 和 unsure\n#                 if int(cls_id) != 0:\n#                     continue\n                    \n#                 # 還原為原圖大小並二值化\n#                 binary_mask = (mask > 0.5).astype(np.uint8)\n                \n#                 # 若為全空遮罩則跳過\n#                 if binary_mask.sum() == 0:\n#                     continue\n                \n#                 # 轉為比賽要求的 Base64 格式\n#                 encoded_str = encode_binary_mask(binary_mask)\n                \n#                 # 組合 prediction_string: \"0 {confidence} {EncodedMask}\"\n#                 prediction_strings.append(f\"0 {conf:.4f} {encoded_str}\")\n                \n#     # 將所有預測物件合併成單一字串，用空白隔開\n#     final_pred_str = \" \".join(prediction_strings)\n    \n#     # 嚴格遵守官方欄位：id, height, width, prediction_string\n#     submission_data.append({\n#         \"id\": img_id, \n#         \"height\": 512, \n#         \"width\": 512, \n#         \"prediction_string\": final_pred_str\n#     })\n\n# # 產生最終提交檔案\n# df_sub = pd.DataFrame(submission_data)\n# df_sub.to_csv(\"submission.csv\", index=False)\n# print(\"✅ submission.csv 建立完成，格式已校正！\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T15:08:21.517747Z","iopub.execute_input":"2026-05-29T15:08:21.518103Z","iopub.status.idle":"2026-05-29T15:08:21.523475Z","shell.execute_reply.started":"2026-05-29T15:08:21.518073Z","shell.execute_reply":"2026-05-29T15:08:21.522766Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}