{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":30201,"databundleVersionId":2750748},{"sourceType":"datasetVersion","sourceId":2724590,"datasetId":1660631,"databundleVersionId":2769566},{"sourceType":"datasetVersion","sourceId":11960404,"datasetId":7453410,"databundleVersionId":12472655},{"sourceType":"datasetVersion","sourceId":11965549,"datasetId":7453202,"databundleVersionId":12478486},{"sourceType":"datasetVersion","sourceId":11965992,"datasetId":7524375,"databundleVersionId":12478968},{"sourceType":"datasetVersion","sourceId":11958591,"datasetId":7519117,"databundleVersionId":12470629},{"sourceType":"datasetVersion","sourceId":11908664,"datasetId":7451568,"databundleVersionId":12414896},{"sourceType":"datasetVersion","sourceId":11965282,"datasetId":7451518,"databundleVersionId":12478189}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from IPython.display import FileLink\n# 複製整個 detectron2 專案到 working 資料夾（這裡可以寫入）\n!cp -r /kaggle/input/detectron2 /kaggle/working/\n\n# 切換目錄\n!pip install --no-index --find-links /kaggle/input/detectron2-whls/wheels --no-deps yacs portalocker fvcore\n\n# 切換目錄\n%cd /kaggle/working/detectron2\n\n!pip install -e .","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-26T15:45:34.643028Z","iopub.execute_input":"2025-05-26T15:45:34.643275Z","iopub.status.idle":"2025-05-26T15:47:45.318221Z","shell.execute_reply.started":"2025-05-26T15:45:34.643253Z","shell.execute_reply":"2025-05-26T15:47:45.317269Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nif not os.path.exists(\"/kaggle/working/RESULTS\"): \n    os.makedirs(\"/kaggle/working/RESULTS\", exist_ok=True)\n    \n    !cp -r /kaggle/input/detectron2-models-shsy5y/events.out.tfevents.1748275474.d698ef096d4d.18.0 /kaggle/working/RESULTS\n    !cp -r /kaggle/input/detectron2-models-shsy5y/last_checkpoint /kaggle/working/RESULTS\n    !cp -r /kaggle/input/detectron2-models-shsy5y/metrics.json /kaggle/working/RESULTS\n    !cp -r /kaggle/input/detectron2-models-shsy5y/model_0011999.pth /kaggle/working/RESULTS","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T06:23:25.818371Z","iopub.execute_input":"2025-05-27T06:23:25.818890Z","iopub.status.idle":"2025-05-27T06:23:34.470962Z","shell.execute_reply.started":"2025-05-27T06:23:25.818869Z","shell.execute_reply":"2025-05-27T06:23:34.470199Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.environ[\"TORCHDYNAMO_DISABLE\"] = \"1\"\n\nimport pandas as pd\nimport numpy as np\nimport pandas as pd \nfrom tqdm import tqdm\nfrom tqdm import tqdm_notebook as tqdm # progress bar\nfrom datetime import datetime\nimport time\nimport matplotlib.pyplot as plt\nfrom pycocotools.coco import COCO\nimport os, json, cv2, random\nimport skimage.io as io\nimport copy\nfrom pathlib import Path\nfrom typing import Optional\n\n\n\nfrom tqdm import tqdm\nimport itertools\n\nimport torch\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\n\nfrom glob import glob\nimport numba\nfrom numba import jit\n\nimport warnings\nwarnings.filterwarnings('ignore') #Ignore \"future\" warnings and Data-Frame-Slicing warnings.\n\n\n# detectron2\nfrom detectron2.structures import BoxMode\nfrom detectron2 import model_zoo\nfrom detectron2.config import get_cfg\nfrom detectron2.data import DatasetCatalog, MetadataCatalog\nfrom detectron2.engine import DefaultPredictor, DefaultTrainer, launch\nfrom detectron2.evaluation import COCOEvaluator\nfrom detectron2.structures import BoxMode\nfrom detectron2.utils.visualizer import ColorMode\nfrom detectron2.utils.logger import setup_logger\nfrom detectron2.utils.visualizer import Visualizer\n\nfrom detectron2.data import DatasetCatalog, MetadataCatalog, build_detection_test_loader, build_detection_train_loader\nfrom detectron2.data import detection_utils as utils\n\n\nfrom detectron2.data import DatasetCatalog, MetadataCatalog, build_detection_test_loader, build_detection_train_loader\nfrom detectron2.data import detection_utils as utils\nimport detectron2.data.transforms as T\nfrom detectron2.evaluation import COCOEvaluator, inference_on_dataset\n\nsetup_logger()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-26T15:55:19.335851Z","iopub.execute_input":"2025-05-26T15:55:19.336554Z","iopub.status.idle":"2025-05-26T15:55:19.345609Z","shell.execute_reply.started":"2025-05-26T15:55:19.336527Z","shell.execute_reply":"2025-05-26T15:55:19.344929Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom detectron2.data import DatasetCatalog, MetadataCatalog\nfrom detectron2.data.datasets.coco import load_coco_json\n\ndef force_register_coco_instance(name, json_file, image_root, class_names):\n    # ✅ 若已存在，先移除 Dataset 與 Metadata 註冊\n    if name in DatasetCatalog.list():\n        DatasetCatalog._REGISTERED.pop(name, None)\n    if name in MetadataCatalog._NAME_TO_META:\n        MetadataCatalog._NAME_TO_META.pop(name, None)\n\n    # ✅ 註冊 COCO 資料集\n    DatasetCatalog.register(name, lambda: load_coco_json(json_file, image_root, name))\n    MetadataCatalog.get(name).set(\n        thing_classes=class_names,\n        evaluator_type=\"coco\",\n        json_file=json_file,\n        image_root=image_root\n    )\n\ndef register_sartorius_dataset(base_path=\"/kaggle/input/annotations/\"):\n    train_json = os.path.join(base_path, \"annotations_train_fixed_shsy5y_only.json\")\n    val_json = os.path.join(base_path, \"annotations_val_fixed_shsy5y_only.json\")\n    image_root = \"/kaggle/input/sartorius-cell-instance-segmentation\"\n    class_names = [\"shsy5y\"]\n\n    # 註冊\n    force_register_coco_instance(\"sartorius_shsy5y_train\", train_json, image_root, class_names)\n    force_register_coco_instance(\"sartorius_shsy5y_val\", val_json, image_root, class_names)\n\n    print(\"✅ Sartorius dataset registered (previous entries removed).\")\n\nregister_sartorius_dataset()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-26T15:55:22.741459Z","iopub.execute_input":"2025-05-26T15:55:22.742182Z","iopub.status.idle":"2025-05-26T15:55:22.748623Z","shell.execute_reply.started":"2025-05-26T15:55:22.742153Z","shell.execute_reply":"2025-05-26T15:55:22.747793Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"metadata = MetadataCatalog.get(\"sartorius_shsy5y_train\")\ndataset_train = DatasetCatalog.get(\"sartorius_shsy5y_train\")\ndataset_valid = DatasetCatalog.get(\"sartorius_shsy5y_val\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-26T15:55:26.541554Z","iopub.execute_input":"2025-05-26T15:55:26.541839Z","iopub.status.idle":"2025-05-26T15:55:27.377870Z","shell.execute_reply.started":"2025-05-26T15:55:26.541818Z","shell.execute_reply":"2025-05-26T15:55:27.377062Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize =(18,11))\nd=dataset_valid[0] \nimg = cv2.imread(d[\"file_name\"])\nprint(img.shape)\nv = Visualizer(img[:, :, ::-1],\n                metadata=metadata, \n                scale=1,\n                instance_mode=ColorMode.IMAGE_BW   # remove the colors of unsegmented pixels. This option is only available for segmentation models\n    )\nout = v.draw_dataset_dict(d)\nax.grid(False)\nax.axis('off')\nax.imshow(out.get_image()[:, :, ::-1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-26T15:55:31.122976Z","iopub.execute_input":"2025-05-26T15:55:31.123604Z","iopub.status.idle":"2025-05-26T15:55:33.389979Z","shell.execute_reply.started":"2025-05-26T15:55:31.123575Z","shell.execute_reply":"2025-05-26T15:55:33.388990Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from detectron2.structures import (\n    BitMasks,\n    Boxes,\n    BoxMode,\n    Instances,\n    Keypoints,\n    PolygonMasks,\n    RotatedBoxes,\n    polygons_to_bitmask,\n)\nimport torch\nimport numpy as np # 確保 numpy 有被導入，因為您使用了 np.ascontiguousarray\n\n_DEBUG_INSTANCES_PRINT_COUNT = 0\n_MAX_DEBUG_INSTANCES_PRINTS = 10 \n\ndef _my_annotations_to_instances(annos, image_size):\n    global _DEBUG_INSTANCES_PRINT_COUNT\n    \n    target = Instances(image_size)\n\n    # 過濾掉無效的標註：確保 bbox 存在且寬高都大於 0\n    valid_annos = []\n    for obj in annos:\n        if \"bbox\" in obj and obj[\"bbox_mode\"] is not None:\n            # 將 bbox 轉換為 XYXY_ABS 格式以方便檢查寬高\n            x1, y1, w, h = obj[\"bbox\"]\n            if obj[\"bbox_mode\"] == BoxMode.XYWH_ABS: # 假設常見的 COCO 格式是 XYWH_ABS\n                if w > 0 and h > 0: # 檢查寬高是否大於 0\n                    valid_annos.append(obj)\n            else: # 對於其他 bbox_mode，我們暫時不做精確檢查，但最好也處理\n                # 對於 XYXY_ABS 或其他模式，需要在轉換後再檢查\n                valid_annos.append(obj) # 先保留，待會轉換後再檢查\n\n    # 確保 gt_boxes 總是存在且是 Boxes 物件\n    boxes = [BoxMode.convert(obj[\"bbox\"], obj[\"bbox_mode\"], BoxMode.XYXY_ABS) for obj in valid_annos]\n    \n    # 再次過濾，確保轉換後的邊界框仍然有效（寬度 > 0 且高度 > 0）\n    final_boxes = []\n    final_valid_annos = [] # 儲存對應的有效 annos\n    for i, box in enumerate(boxes):\n        x1, y1, x2, y2 = box\n        if (x2 - x1) > 0 and (y2 - y1) > 0:\n            final_boxes.append(box)\n            final_valid_annos.append(valid_annos[i]) # 儲存有效的 annos\n    \n    target.gt_boxes = Boxes(final_boxes) # 使用過濾後的邊界框\n\n    if target.gt_boxes is None or len(target.gt_boxes) == 0:\n        target.gt_boxes = Boxes([])\n\n    # 只有當 gt_boxes 是一個 Boxes 實例時才嘗試 clip\n    if isinstance(target.gt_boxes, Boxes):\n        target.gt_boxes.clip(image_size)\n        \n    # 後續的類別、掩碼和關鍵點也應該使用過濾後的 valid_annos\n    classes = [obj[\"category_id\"] for obj in final_valid_annos]\n    target.gt_classes = torch.tensor(classes, dtype=torch.int64)\n\n    if len(final_valid_annos) and \"segmentation\" in final_valid_annos[0]:\n        segms = [obj[\"segmentation\"] for obj in final_valid_annos]\n        # 確保 segms 轉換為 BitMasks 之前是非空的\n        if segms: # 只有當 segms 列表非空時才執行\n             masks = BitMasks(\n                torch.stack([torch.from_numpy(np.ascontiguousarray(x)) for x in segms])\n             )\n             target.gt_masks = masks\n        else: # 如果 segms 為空，則初始化為空的 BitMasks\n            target.gt_masks = BitMasks(torch.empty(0, *image_size, dtype=torch.bool))\n\n\n    if len(final_valid_annos) and \"keypoints\" in final_valid_annos[0]:\n        kpts = [obj.get(\"keypoints\", []) for obj in final_valid_annos]\n        if kpts: # 只有當 kpts 列表非空時才執行\n            target.gt_keypoints = Keypoints(kpts)\n        else: # 如果 kpts 為空，則初始化為空的 Keypoints\n            target.gt_keypoints = Keypoints(torch.empty(0, 0, 3, dtype=torch.float32))\n\n\n    return target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-26T15:55:40.109598Z","iopub.execute_input":"2025-05-26T15:55:40.110172Z","iopub.status.idle":"2025-05-26T15:55:40.121739Z","shell.execute_reply.started":"2025-05-26T15:55:40.110149Z","shell.execute_reply":"2025-05-26T15:55:40.120922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 全局變數來控制警告訊息的數量\n_RLE_COUNTS_LIST_WARNING_COUNT = 0\n_MAX_RLE_COUNTS_LIST_WARNINGS = 10 \n\n_EMPTY_POLYGON_WARNING_COUNT = 0 \n_MAX_EMPTY_POLYGON_WARNINGS = 10\n\n# 新增偵錯打印計數器\n_DEBUG_SEGMENTATION_TYPE_PRINT_COUNT = 0\n_MAX_DEBUG_SEGMENTATION_TYPE_PRINTS = 20 # 最多打印前 20 個實例的分割類型\n\ndef custom_mapper(dataset_dict):\n    \"\"\"\n    Detectron2 DataLoader 的自定義映射函數。\n    它執行數據增強，並確保所有分割數據最終都轉換為二進制掩碼 (Bitmask, np.ndarray)。\n    \"\"\"\n    global _RLE_COUNTS_LIST_WARNING_COUNT\n    global _EMPTY_POLYGON_WARNING_COUNT\n    global _DEBUG_SEGMENTATION_TYPE_PRINT_COUNT # 引入新的全局變數\n\n    dataset_dict = copy.deepcopy(dataset_dict)\n    image = utils.read_image(dataset_dict[\"file_name\"], format=\"BGR\")\n        \n    transform_list = [\n        T.RandomBrightness(0.9, 1.1),\n        T.RandomContrast(0.9, 1.1),\n        T.RandomSaturation(0.9, 1.1),\n        T.RandomLighting(0.9),\n        T.RandomFlip(prob=0.5, horizontal=False, vertical=True),\n        T.RandomFlip(prob=0.5, horizontal=True, vertical=False),\n    ]\n    image, transforms = T.apply_transform_gens(transform_list, image)\n    dataset_dict[\"image\"] = torch.as_tensor(image.transpose(2, 0, 1).astype(\"float32\"))\n\n    annos = []\n    original_h, original_w = dataset_dict[\"height\"], dataset_dict[\"width\"] \n\n    for obj in dataset_dict.pop(\"annotations\"):\n        anno_id = obj.get('id', 'unknown')\n        image_id = dataset_dict.get('image_id', 'unknown')\n\n        if obj.get(\"iscrowd\", 0) == 0:\n            if \"segmentation\" not in obj:\n                if _RLE_COUNTS_LIST_WARNING_COUNT < _MAX_RLE_COUNTS_LIST_WARNINGS:\n                    print(f\"Warning: 註釋 {anno_id} (圖像 {image_id}) 沒有 'segmentation' 鍵。跳過。\")\n                    _RLE_COUNTS_LIST_WARNING_COUNT += 1\n                continue\n\n            segm_data_original = obj[\"segmentation\"] \n\n            if isinstance(segm_data_original, list):\n                if not segm_data_original: \n                    if _EMPTY_POLYGON_WARNING_COUNT < _MAX_EMPTY_POLYGON_WARNINGS:\n                        print(f\"Warning: 註釋 {anno_id} (圖像 {image_id}) 包含空的 Polygon。跳過。\")\n                        _EMPTY_POLYGON_WARNING_COUNT += 1\n                    continue\n                \n            elif isinstance(segm_data_original, dict) and \"counts\" in segm_data_original and \"size\" in segm_data_original:\n                if isinstance(segm_data_original[\"counts\"], list): \n                    if _RLE_COUNTS_LIST_WARNING_COUNT < _MAX_RLE_COUNTS_LIST_WARNINGS:\n                        print(f\"Warning: 註釋 {anno_id} (圖像 {image_id}) 的 RLE 'counts' 是一個列表 (未被外部腳本修復)，預期為 str 或 bytes。跳過。\")\n                        _RLE_COUNTS_LIST_WARNING_COUNT += 1\n                    if _RLE_COUNTS_LIST_WARNING_COUNT == _MAX_RLE_COUNTS_LIST_WARNINGS:\n                         print(f\"--- Warning: 已達到 RLE 'counts' 列表警告的最大數量 ({_MAX_RLE_COUNTS_LIST_WARNINGS})。後續類似警告將不會打印。 ---\")\n                    continue\n                elif not isinstance(segm_data_original[\"counts\"], (str, bytes)): \n                    if _RLE_COUNTS_LIST_WARNING_COUNT < _MAX_RLE_COUNTS_LIST_WARNINGS:\n                        print(f\"Warning: 註釋 {anno_id} (圖像 {image_id}) 的 RLE 'counts' 類型不符合預期: {type(segm_data_original['counts'])}。預期為 str 或 bytes。跳過。\")\n                        _RLE_COUNTS_LIST_WARNING_COUNT += 1\n                    continue\n            else: \n                if _RLE_COUNTS_LIST_WARNING_COUNT < _MAX_RLE_COUNTS_LIST_WARNINGS:\n                    print(f\"Warning: 註釋 {anno_id} (圖像 {image_id}) 的分割類型不符合預期: {type(segm_data_original)}。預期為 list (多邊形) 或 dict (RLE)。跳過。\")\n                    _RLE_COUNTS_LIST_WARNING_COUNT += 1\n                continue\n\n            # 讓 transform_instance_annotations 處理初始變換\n            anno = utils.transform_instance_annotations(obj, transforms, image.shape[:2])\n\n            # 確保所有分割數據最終都轉換為二進制掩碼 (np.ndarray)\n            if \"segmentation\" in anno: \n                current_segm = anno[\"segmentation\"]\n                \n                if isinstance(current_segm, list):\n                    if not current_segm: \n                        if _EMPTY_POLYGON_WARNING_COUNT < _MAX_EMPTY_POLYGON_WARNINGS:\n                            print(f\"Warning: 註釋 {anno_id} (圖像 {image_id}) 經變換後產生空的 Polygon。跳過。\")\n                            _EMPTY_POLYGON_WARNING_COUNT += 1\n                        continue\n                    try:\n                        h_transformed, w_transformed = image.shape[:2]\n                        \n                        rles = mask_util.frPyObjects(current_segm, h_transformed, w_transformed)\n                        bitmask_3d = mask_util.decode(rles)\n                        processed_bitmask = (bitmask_3d.sum(axis=2) > 0).astype(np.uint8)\n                        anno[\"segmentation\"] = np.asfortranarray(processed_bitmask)\n                    except Exception as e:\n                        if _EMPTY_POLYGON_WARNING_COUNT < _MAX_EMPTY_POLYGON_WARNINGS:\n                            print(f\"Warning: 轉換變換後的多邊形到 bitmask 時出錯 註釋 {anno_id} (圖像 {image_id}): {e}. 跳過。\")\n                            _EMPTY_POLYGON_WARNING_COUNT += 1\n                        continue\n                elif isinstance(current_segm, np.ndarray):\n                    anno[\"segmentation\"] = np.asfortranarray(current_segm)\n                else: \n                    if _RLE_COUNTS_LIST_WARNING_COUNT < _MAX_RLE_COUNTS_LIST_WARNINGS: \n                        print(f\"Warning: 註釋 {anno_id} (圖像 {image_id}) 經變換後產生非預期分割類型: {type(current_segm)}。跳過。\")\n                        _RLE_COUNTS_LIST_WARNING_COUNT += 1\n                    continue\n            else: \n                if _RLE_COUNTS_LIST_WARNING_COUNT < _MAX_RLE_COUNTS_LIST_WARNINGS: \n                    print(f\"Warning: 註釋 {anno_id} (圖像 {image_id}) 在變換後失去 'segmentation' 鍵。跳過。\")\n                    _RLE_COUNTS_LIST_WARNING_COUNT += 1\n                continue\n\n            annos.append(anno)\n        else:\n            pass \n\n    instances = _my_annotations_to_instances(annos, image.shape[:2])\n    dataset_dict[\"instances\"] = utils.filter_empty_instances(instances)\n    return dataset_dict\n\nclass AugTrainer(DefaultTrainer):\n    def run_step(self):\n        torch.autograd.set_detect_anomaly(True)\n        super().run_step()\n    @classmethod\n    def build_train_loader(cls, cfg):\n        return build_detection_train_loader(cfg, mapper=custom_mapper)\n\n    @classmethod\n    def build_evaluator(cls, cfg, dataset_name, output_folder=None):\n        if output_folder is None:\n            output_folder = os.path.join(cfg.OUTPUT_DIR, \"inference\")\n            os.makedirs(output_folder, exist_ok=True)\n        return MAPIOUEvaluator(dataset_name, output_folder)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-26T15:55:46.218955Z","iopub.execute_input":"2025-05-26T15:55:46.219227Z","iopub.status.idle":"2025-05-26T15:55:46.234724Z","shell.execute_reply.started":"2025-05-26T15:55:46.219207Z","shell.execute_reply":"2025-05-26T15:55:46.233943Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Taken from https://www.kaggle.com/theoviel/competition-metric-map-iou\nfrom detectron2.evaluation.evaluator import DatasetEvaluator\nimport pycocotools.mask as mask_util\nfrom collections import OrderedDict \ndef precision_at(threshold, iou):\n    matches = iou > threshold\n    true_positives = np.sum(matches, axis=1) == 1  # Correct objects\n    false_positives = np.sum(matches, axis=0) == 0  # Missed objects\n    false_negatives = np.sum(matches, axis=1) == 0  # Extra objects\n    return np.sum(true_positives), np.sum(false_positives), np.sum(false_negatives)\n\ndef score(pred, targ):\n    pred_masks = pred['instances'].pred_masks.cpu().numpy()\n    enc_preds = [mask_util.encode(np.asarray(p, order='F')) for p in pred_masks]\n    enc_targs = list(map(lambda x:x['segmentation'], targ))\n    ious = mask_util.iou(enc_preds, enc_targs, [0]*len(enc_targs))\n    prec = []\n    for t in np.arange(0.5, 1.0, 0.05):\n        tp, fp, fn = precision_at(t, ious)\n        p = tp / (tp + fp + fn)\n        prec.append(p)\n    return np.mean(prec)\n\nclass MAPIOUEvaluator(DatasetEvaluator):\n    # 【關鍵修改點】：將 __init__ 方法的簽名改為接受 output_folder 參數\n    def __init__(self, dataset_name, output_folder=None):\n        dataset_dicts = DatasetCatalog.get(dataset_name)\n        self.annotations_cache = {item['image_id']: item['annotations'] for item in dataset_dicts}\n        # 如果您以後需要在評估器中保存結果，可以將 output_folder 賦值給實例變數，例如 self.output_folder = output_folder\n\n    def reset(self):\n        self.scores = []\n\n    def process(self, inputs, outputs):\n        for inp, out in zip(inputs, outputs):\n            if len(out['instances']) == 0:\n                self.scores.append(0)\n            else:\n                targ = self.annotations_cache[inp['image_id']]\n                self.scores.append(score(out, targ)) # 確保 score 函數在類別定義前或在同一文件中\n\n    def evaluate(self):\n        mean_iou_score = np.mean(self.scores)\n\n        # 【請務必確保這裡的結構是這樣子的！】\n        # 返回一個符合 Detectron2 期望的巢狀 OrderedDict 結構\n        # 最外層的鍵是任務類型 (例如 \"segm\" 表示分割任務)\n        # 內層是該任務的實際指標名稱和值\n        return OrderedDict({\n            \"segm\": OrderedDict({ # 使用 \"segm\" 作為任務名稱，這是 Detectron2 常用的任務鍵\n                \"MaP IoU\": float(mean_iou_score) # 確保將 numpy.float64 轉換為標準 float\n            })\n        })\n\nclass Trainer(DefaultTrainer):\n    @classmethod\n    def build_evaluator(cls, cfg, dataset_name, output_folder=None):\n        return MAPIOUEvaluator(dataset_name)\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-26T15:55:55.680823Z","iopub.execute_input":"2025-05-26T15:55:55.681391Z","iopub.status.idle":"2025-05-26T15:55:55.690992Z","shell.execute_reply.started":"2025-05-26T15:55:55.681370Z","shell.execute_reply":"2025-05-26T15:55:55.690229Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"import torch\nimport gc\n\n# 假設你有一些張量或模型在 GPU 上\n# model = MyModel().cuda()\n# x = torch.randn(1000, 1000).cuda()\n\n# 清理所有不再使用的變數\n# 如果你不再需要你的模型或其他大型張量，先 del 掉它們\n# del model\n# del x\n\n# 觸發 Python 垃圾回收\ngc.collect()\n\n# 清空 PyTorch 的 CUDA 記憶體快取\ntorch.cuda.empty_cache()\n\nprint(f\"CUDA 記憶體已清空，當前分配記憶體: {torch.cuda.memory_allocated() / (1024**3):.2f} GB\")\nprint(f\"CUDA 記憶體快取大小: {torch.cuda.memory_reserved() / (1024**3):.2f} GB\")","metadata":{"execution":{"iopub.status.busy":"2025-05-24T19:42:00.920771Z","iopub.execute_input":"2025-05-24T19:42:00.921058Z","iopub.status.idle":"2025-05-24T19:42:01.788279Z","shell.execute_reply.started":"2025-05-24T19:42:00.921042Z","shell.execute_reply":"2025-05-24T19:42:01.787678Z"}}},{"cell_type":"code","source":"cfg = get_cfg()\nconfig_name = \"/kaggle/input/myconfig/livecell_config.yaml\" \ncfg.merge_from_file(config_name)\ncfg.DATASETS.TRAIN = (\"sartorius_train\",)\ncfg.DATASETS.TEST = (\"sartorius_val\",)\n\ncfg.MODEL.WEIGHTS =\"/kaggle/input/models/LIVECell_anchor_based_model.pth\"\ncfg.MODEL.BACKBONE.FREEZE_AT = 0\n\n#cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url(config_name)\n\ncfg.DATALOADER.NUM_WORKERS = 2\ncfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 128  # 64 is slower but more accurate (128 faster but less accurate)\ncfg.MODEL.ROI_HEADS.NUM_CLASSES = 1 \ncfg.SOLVER.IMS_PER_BATCH = 2 #(2 is per defaults)\ncfg.INPUT.MASK_FORMAT='bitmask'\n\ncfg.DATASETS.TRAIN = (\"sartorius_shsy5y_train\",)\ncfg.DATASETS.TEST = (\"sartorius_shsy5y_val\",)\n\ncfg.SOLVER.BASE_LR = 0.00025 #(quite high base learning rate but should drop)\n#cfg.SOLVER.MOMENTUM = 0.9\n#cfg.SOLVER.WEIGHT_DECAY = 0.0005\n#cfg.SOLVER.GAMMA = 0.1\n\n    \ncfg.SOLVER.WARMUP_ITERS = 1000 #How many iterations to go from 0 to reach base LR\ncfg.SOLVER.MAX_ITER = 20000 #Maximum of iterations 1\ncfg.SOLVER.STEPS = (16000, 18000) #At which point to change the LR 0.25,0.5\ncfg.TEST.EVAL_PERIOD = 1000\ncfg.SOLVER.CHECKPOINT_PERIOD= 2000\n\nos.makedirs(cfg.OUTPUT_DIR, exist_ok=True)\ntrainer = AugTrainer(cfg) # with  data augmentation \n\n#trainer = Trainer(cfg)  # without data augmentation\ntrainer.resume_or_load(resume=True)\ntrainer.train()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-26T15:56:02.504354Z","iopub.execute_input":"2025-05-26T15:56:02.504676Z","iopub.status.idle":"2025-05-26T16:01:25.138595Z","shell.execute_reply.started":"2025-05-26T15:56:02.504655Z","shell.execute_reply":"2025-05-26T16:01:25.137158Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"evaluator = COCOEvaluator(Data_Resister_valid, cfg, False, output_dir=\"./output/\")\ncfg.MODEL.WEIGHTS=\"../input/detectron2cell/output/model_final.pth\"\n#cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.2   # set a custom testing threshold\n#cfg.INPUT.MASK_FORMAT='polygon'\nval_loader = build_detection_test_loader(cfg, Data_Resister_valid)\ninference_on_dataset(trainer.model, val_loader, evaluator)","metadata":{"execution":{"iopub.status.busy":"2025-05-26T15:40:38.369275Z","iopub.status.idle":"2025-05-26T15:40:38.369555Z","shell.execute_reply.started":"2025-05-26T15:40:38.369438Z","shell.execute_reply":"2025-05-26T15:40:38.369450Z"}}},{"cell_type":"markdown","source":"\nimport json\nimport pycocotools.mask as mask_util\nimport numpy as np\n\ndef rle_list_to_binary_mask(counts_list, height, width):\n    \"\"\"\n    將原始的 Run-Length 編碼列表 (例如 [run1, run2, ...]) 轉換為二進制掩碼。\n    此列表代表著交替的 0 和 1 的連續像素數量，通常從 0 (背景) 開始。\n    \"\"\"\n    # 創建一個平坦的 NumPy 數組來構建掩碼\n    mask = np.zeros(height * width, dtype=np.uint8)\n    current_pixel = 0\n    current_value = 0 # RLE 通常以背景 (0) 的運行開始\n\n    for count in counts_list:\n        # 確保 count 是有效的正數\n        if count < 0:\n            print(f\"Warning: 在 RLE 列表中遇到負數計數: {count}。跳過此運行。\")\n            continue\n        \n        # 填充當前段落，確保不超出掩碼邊界\n        segment_end = current_pixel + count\n        if segment_end > len(mask):\n            segment_end = len(mask)\n            print(f\"Warning: RLE 運行超出掩碼大小。已截斷。\")\n\n        mask[current_pixel : segment_end] = current_value\n        current_pixel = segment_end\n        \n        # 交替值 (0 變為 1，1 變為 0)\n        current_value = 1 - current_value\n        \n        # 如果已填滿整個掩碼，則停止\n        if current_pixel >= len(mask):\n            break\n            \n    # 如果處理所有計數後，掩碼未完全填充，則可能表示數據格式錯誤\n    if current_pixel < len(mask):\n        print(f\"Warning: RLE 計數總和 ({current_pixel}) 小於總掩碼大小 ({height * width})。掩碼可能不完整。\")\n\n    # 將 1D 掩碼重塑為 2D (height, width) 並確保 Fortran 連續順序\n    # Fortran 連續順序 ('F') 對於 pycocotools.mask.encode 性能至關重要\n    return mask.reshape((height, width), order='F')\n\ndef fix_rle_counts(input_json_path, output_json_path):\n    \"\"\"\n    讀取 COCO 格式的 JSON 文件，修復其中 'counts' 為列表的 'segmentation'，\n    將其轉換為正確的 RLE 字串格式，並保存修改後的數據。\n    \"\"\"\n    with open(input_json_path, 'r', encoding='utf-8') as f:\n        data = json.load(f)\n\n    fixed_annotations_count = 0\n    total_annotations = len(data.get('annotations', []))\n\n    if 'annotations' in data:\n        for i, annotation in enumerate(data['annotations']):\n            anno_id = annotation.get('id', 'unknown')\n            image_id = annotation.get('image_id', 'unknown')\n\n            if 'segmentation' in annotation:\n                segm = annotation['segmentation']\n                \n                # 檢查是否為 'counts' 為列表的 RLE 字典\n                if isinstance(segm, dict) and 'counts' in segm and 'size' in segm:\n                    if isinstance(segm['counts'], list):\n                        h, w = segm['size']\n                        counts_list = segm['counts']\n                        \n                        try:\n                            # 將原始的 Run-Length 列表轉換為二進制掩碼\n                            binary_mask = rle_list_to_binary_mask(counts_list, h, w)\n                            \n                            # 將二進制掩碼編碼為 COCO RLE 格式 (帶有 bytes 類型的 counts)\n                            encoded_rle = mask_util.encode(np.asfortranarray(binary_mask)) \n                            \n                            # 將 bytes 類型的 counts 解碼為字串，以便 JSON 序列化\n                            segm['counts'] = encoded_rle['counts'].decode('utf-8')\n                            fixed_annotations_count += 1\n                        except Exception as e:\n                            print(f\"Error fixing annotation {anno_id} (image {image_id}): {e}. 跳過此註釋的修復。\")\n                            pass\n                # 如果是多邊形格式，則不進行處理 (因為此修復腳本專為 RLE 'counts' 列表問題設計)\n                elif isinstance(segm, list): \n                    pass \n                # 處理其他意外的分割格式\n                elif not (isinstance(segm, dict) and 'counts' in segm and 'size' in segm):\n                    print(f\"Warning: 註釋 {anno_id} (圖像 {image_id}) 的分割格式不符合預期: {type(segm)}。預期為 RLE 字典或多邊形列表。跳過。\")\n\n    print(f\"\\n已處理 {total_annotations} 個註釋。已修復 {fixed_annotations_count} 個註釋。\")\n\n    with open(output_json_path, 'w', encoding='utf-8') as f:\n        json.dump(data, f, indent=4, ensure_ascii=False) \n\n    print(f\"已將修復後的註釋保存到: {output_json_path}\")\n\n\n# --- 使用方法 ---\n# 請確保這些路徑在你的環境中是正確的\ninput_file_val = '/kaggle/input/sartorius-cell-instance-segmentation-coco/annotations_val.json'\noutput_file_val = '/kaggle/working/annotations_val_fixed.json'\n\ninput_file_train = '/kaggle/input/sartorius-cell-instance-segmentation-coco/annotations_train.json'\noutput_file_train = '/kaggle/working/annotations_train_fixed.json'\n\nprint(\"正在嘗試修復訓練集註釋...\")\nfix_rle_counts(input_json_path=input_file_train, output_json_path=output_file_train)\n\nprint(\"\\n正在嘗試修復驗證集註釋...\")\nfix_rle_counts(input_json_path=input_file_val, output_json_path=output_file_val)","metadata":{"execution":{"iopub.status.busy":"2025-05-26T15:18:47.745325Z","iopub.execute_input":"2025-05-26T15:18:47.745577Z","iopub.status.idle":"2025-05-26T15:19:08.085163Z","shell.execute_reply.started":"2025-05-26T15:18:47.745559Z","shell.execute_reply":"2025-05-26T15:19:08.084534Z"}}},{"cell_type":"markdown","source":"def fix_rle_counts_shsy5y_only(input_json_path, output_json_path):\n    import json\n    import numpy as np\n    import pycocotools.mask as mask_util\n\n    def rle_list_to_binary_mask(counts_list, height, width):\n        mask = np.zeros(height * width, dtype=np.uint8)\n        current_pixel = 0\n        current_value = 0\n        for count in counts_list:\n            segment_end = current_pixel + count\n            if segment_end > len(mask):\n                segment_end = len(mask)\n            mask[current_pixel : segment_end] = current_value\n            current_pixel = segment_end\n            current_value = 1 - current_value\n        return mask.reshape((height, width), order='F')\n\n    with open(input_json_path, 'r', encoding='utf-8') as f:\n        data = json.load(f)\n\n    # 找出 shsy5y 的 category_id\n    shsy5y_category = next((cat for cat in data['categories'] if cat['name'].lower() == 'shsy5y'), None)\n    if shsy5y_category is None:\n        print(\"❌ 找不到 'shsy5y' 類別。\")\n        return\n\n    shsy5y_category_id = shsy5y_category['id']\n\n    # 過濾 annotations，只保留 shsy5y\n    fixed_annotations = []\n    valid_image_ids = set()\n    for anno in data['annotations']:\n        if anno['category_id'] != shsy5y_category_id:\n            continue\n\n        segm = anno.get('segmentation', {})\n        if isinstance(segm, dict) and isinstance(segm.get('counts'), list):\n            try:\n                h, w = segm['size']\n                binary_mask = rle_list_to_binary_mask(segm['counts'], h, w)\n                encoded_rle = mask_util.encode(np.asfortranarray(binary_mask))\n                segm['counts'] = encoded_rle['counts'].decode('utf-8')\n                anno['segmentation'] = segm\n            except Exception as e:\n                print(f\"⚠️ 修復失敗，略過：id={anno.get('id')} err={e}\")\n                continue\n\n        fixed_annotations.append(anno)\n        valid_image_ids.add(anno['image_id'])\n\n    # 過濾 images\n    filtered_images = [img for img in data['images'] if img['id'] in valid_image_ids]\n\n    # 更新資料集\n    data['annotations'] = fixed_annotations\n    data['images'] = filtered_images\n    data['categories'] = [shsy5y_category]\n\n    with open(output_json_path, 'w', encoding='utf-8') as f:\n        json.dump(data, f, indent=4, ensure_ascii=False)\n\n    print(f\"✅ 已輸出過濾後的訓練集：{output_json_path}\")\n    print(f\"📊 保留 annotations 數量: {len(fixed_annotations)}，對應 images: {len(filtered_images)}\")\n\nfix_rle_counts_shsy5y_only(\n    input_json_path='/kaggle/input/sartorius-cell-instance-segmentation-coco/annotations_train.json',\n    output_json_path='/kaggle/working/annotations_train_fixed_shsy5y_only.json'\n)\n\nfix_rle_counts_shsy5y_only(\n    input_json_path='/kaggle/input/sartorius-cell-instance-segmentation-coco/annotations_val.json',\n    output_json_path='/kaggle/working/annotations_val_fixed_shsy5y_only.json'\n)","metadata":{"execution":{"iopub.status.busy":"2025-05-26T15:51:20.714679Z","iopub.execute_input":"2025-05-26T15:51:20.715392Z","iopub.status.idle":"2025-05-26T15:51:35.209492Z","shell.execute_reply.started":"2025-05-26T15:51:20.715365Z","shell.execute_reply":"2025-05-26T15:51:35.208786Z"}}}]}