{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.9","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":23823,"databundleVersionId":1920183,"sourceType":"competition"},{"sourceId":1911681,"sourceType":"datasetVersion","datasetId":1128406},{"sourceId":1934626,"sourceType":"datasetVersion","datasetId":1128710}],"dockerImageVersionId":30056,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"This is simple mmdetection infrence script as a base line.\nTraining part can be foud [here](https://www.kaggle.com/its7171/mmdetection-for-segmentation-training).","metadata":{"papermill":{"duration":0.009854,"end_time":"2021-02-02T02:49:13.549001","exception":false,"start_time":"2021-02-02T02:49:13.539147","status":"completed"},"tags":[]}},{"cell_type":"code","source":"!rsync -a ../input/mmdetection-v280/mmdetection ../\n!pip install ../input/mmdetection-v280/src/mmdet-2.8.0/mmdet-2.8.0/\n!pip install ../input/mmdetection-v280/src/mmpycocotools-12.0.3/mmpycocotools-12.0.3/\n!pip install ../input/mmdetection-v280/src/addict-2.4.0-py3-none-any.whl\n!pip install ../input/mmdetection-v280/src/yapf-0.30.0-py2.py3-none-any.whl\n!pip install ../input/mmdetection-v280/src/mmcv_full-1.2.6-cp37-cp37m-manylinux1_x86_64.whl","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2025-06-08T04:31:17.785475Z","iopub.execute_input":"2025-06-08T04:31:17.785772Z","iopub.status.idle":"2025-06-08T04:34:34.873019Z","shell.execute_reply.started":"2025-06-08T04:31:17.785747Z","shell.execute_reply":"2025-06-08T04:34:34.872262Z"},"papermill":{"duration":151.034626,"end_time":"2021-02-02T02:51:44.592784","exception":false,"start_time":"2021-02-02T02:49:13.558158","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 这里改了","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport cv2\nfrom tqdm import tqdm\nimport pickle\nfrom itertools import groupby\nfrom pycocotools import mask as mutils\nfrom pycocotools import _mask as coco_mask\nimport matplotlib.pyplot as plt\nimport os\nimport base64\nimport typing as t\nimport zlib\nimport random\nrandom.seed(0)\n\nexp_name = \"v4\"\nconf_name = \"mask_rcnn_s101_fpn_syncbn-backbone+head_mstrain_1x_coco\"\nmodel_name = 'mask_rcnn_resnest101_v5_ep9'\nROOT = '../input/hpa-single-cell-image-classification/'\ntrain_or_test = 'train'  # ⚠️ 改为 train\n\n# ✅ 改为读取 train.csv 并取前 20%\ndf = pd.read_csv(os.path.join(ROOT, 'train.csv'))\nnum_total = len(df)\ndf = df.iloc[:int(num_total * 0.2)].copy()\n\nprint(f\"处理 train.csv 前 20%：共 {len(df)} 条记录\")","metadata":{"execution":{"iopub.status.busy":"2025-06-08T04:34:34.875223Z","iopub.execute_input":"2025-06-08T04:34:34.875453Z","iopub.status.idle":"2025-06-08T04:34:34.903095Z","shell.execute_reply.started":"2025-06-08T04:34:34.87543Z","shell.execute_reply":"2025-06-08T04:34:34.902342Z"},"papermill":{"duration":0.216599,"end_time":"2021-02-02T02:51:44.826551","exception":false,"start_time":"2021-02-02T02:51:44.609952","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# helper funcs","metadata":{"papermill":{"duration":0.016707,"end_time":"2021-02-02T02:51:44.860458","exception":false,"start_time":"2021-02-02T02:51:44.843751","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"copy from raw notebook","metadata":{}},{"cell_type":"code","source":"def encode_binary_mask(mask: np.ndarray) -> t.Text:\n  \"\"\"Converts a binary mask into OID challenge encoding ascii text.\"\"\"\n\n  # check input mask --\n  if mask.dtype != np.bool:\n    raise ValueError(\n        \"encode_binary_mask expects a binary mask, received dtype == %s\" %\n        mask.dtype)\n\n  mask = np.squeeze(mask)\n  if len(mask.shape) != 2:\n    raise ValueError(\n        \"encode_binary_mask expects a 2d mask, received shape == %s\" %\n        mask.shape)\n\n  # convert input mask to expected COCO API input --\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  # RLE encode mask --\n  encoded_mask = coco_mask.encode(mask_to_encode)[0][\"counts\"]\n\n  # compress and base64 encoding --\n  binary_str = zlib.compress(encoded_mask, zlib.Z_BEST_COMPRESSION)\n  base64_str = base64.b64encode(binary_str)\n  return base64_str.decode()\n\ndef read_img(image_id, color, train_or_test='train', image_size=None):\n    filename = f'{ROOT}/{train_or_test}/{image_id}_{color}.png'\n    assert os.path.exists(filename), f'not found {filename}'\n    img = cv2.imread(filename, cv2.IMREAD_UNCHANGED)\n    if image_size is not None:\n        img = cv2.resize(img, (image_size, image_size))\n    if img.dtype == 'uint16':\n        img = (img/256).astype('uint8')\n    return img\n\ndef load_RGBY_image(image_id, train_or_test='train', image_size=None):\n    red = read_img(image_id, \"red\", train_or_test, image_size)\n    green = read_img(image_id, \"green\", train_or_test, image_size)\n    blue = read_img(image_id, \"blue\", train_or_test, image_size)\n    # using rgb only here\n    #yellow = read_img(image_id, \"yellow\", train_or_test, image_size)\n    stacked_images = np.transpose(np.array([red, green, blue]), (1,2,0))\n    return stacked_images\n\ndef print_masked_img(image_id, mask):\n    img = load_RGBY_image(image_id, train_or_test)\n    \n    plt.figure(figsize=(15, 15))\n    plt.subplot(1, 3, 1)\n    plt.imshow(img)\n    plt.title('Image')\n    plt.axis('off')\n    \n    plt.subplot(1, 3, 2)\n    plt.imshow(mask)\n    plt.title('Mask')\n    plt.axis('off')\n    \n    plt.subplot(1, 3, 3)\n    plt.imshow(img)\n    plt.imshow(mask, alpha=0.6)\n    plt.title('Image + Mask')\n    plt.axis('off')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2025-06-08T04:34:34.904568Z","iopub.execute_input":"2025-06-08T04:34:34.904892Z","iopub.status.idle":"2025-06-08T04:34:35.007823Z","shell.execute_reply.started":"2025-06-08T04:34:34.904858Z","shell.execute_reply":"2025-06-08T04:34:35.00686Z"},"papermill":{"duration":0.033138,"end_time":"2021-02-02T02:51:44.910682","exception":false,"start_time":"2021-02-02T02:51:44.877544","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# generate files for mmdetection","metadata":{"papermill":{"duration":0.016528,"end_time":"2021-02-02T02:51:44.94507","exception":false,"start_time":"2021-02-02T02:51:44.928542","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# 输出目录也改为对应的 train\nout_image_dir = f'../work/mmdet_{exp_name}_{train_or_test}_20percent/'\nos.makedirs(out_image_dir, exist_ok=True)\n# 重新构造推理 annotation 列表\nannos = []\nfor idx in tqdm(range(len(df))):\n    image_id = df.iloc[idx].ID\n    img = load_RGBY_image(image_id, train_or_test)\n    cv2.imwrite(f'{out_image_dir}/{image_id}.jpg', img)\n    ann = {\n        'filename': f'{image_id}.jpg',\n        'width': img.shape[1],\n        'height': img.shape[0],\n        # 不要添加 ann 字段 from gpt\n    }\n    annos.append(ann)\n\n# 保存为 PKL\nwith open(f'../work/mmdet_{exp_name}_train20_percent.pkl', 'wb') as f:\n    pickle.dump(annos, f)\n\nprint(f\"成功保存 {len(annos)} 条记录至 ../work/mmdet_{exp_name}_train20_percent.pkl\")","metadata":{"execution":{"iopub.status.busy":"2025-06-08T04:34:35.008917Z","iopub.execute_input":"2025-06-08T04:34:35.009182Z","iopub.status.idle":"2025-06-08T04:34:51.368627Z","shell.execute_reply.started":"2025-06-08T04:34:35.009155Z","shell.execute_reply":"2025-06-08T04:34:51.367914Z"},"lines_to_next_cell":2,"papermill":{"duration":2.240857,"end_time":"2021-02-02T02:51:47.202634","exception":false,"start_time":"2021-02-02T02:51:44.961777","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# check what trian20_percent has\nimport pickle\n\n# 路径根据前面保存的位置调整\npkl_path = '../work/mmdet_v4_train20_percent.pkl'\n\nwith open(pkl_path, 'rb') as f:\n    data = pickle.load(f)\n\n# 显示前3个样本结构\nfor i, item in enumerate(data[:3]):\n    print(f\"\\n🔎 第 {i+1} 个样本:\")\n    for k, v in item.items():\n        print(f\"  {k}: {v}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:34:51.371097Z","iopub.execute_input":"2025-06-08T04:34:51.371386Z","iopub.status.idle":"2025-06-08T04:34:51.377073Z","shell.execute_reply.started":"2025-06-08T04:34:51.37136Z","shell.execute_reply":"2025-06-08T04:34:51.376359Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 保存完后复制文件\nimport shutil\n\nsrc_path = '../work/mmdet_v4_train20_percent.pkl'\ndst_path = '/kaggle/working/mmdet_v4_train20_percent.pkl'\n\nshutil.copy(src_path, dst_path)\nprint(\"已将结果复制到 Output 区域\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:34:51.379424Z","iopub.execute_input":"2025-06-08T04:34:51.379647Z","iopub.status.idle":"2025-06-08T04:34:51.405282Z","shell.execute_reply.started":"2025-06-08T04:34:51.379625Z","shell.execute_reply":"2025-06-08T04:34:51.404511Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 这里改了","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport cv2\nimport pickle\nfrom tqdm import tqdm\n\n# 参数设置\nexp_name = \"v4\"\ntrain_or_test = \"train\"\nROOT = '../input/hpa-single-cell-image-classification/'\nout_image_dir = f'../work/mmdet_{exp_name}_{train_or_test}_20percent'\npkl_path = f'../work/mmdet_{exp_name}_train20_percent.pkl'\n\n# 读取 train.csv 并截取前 20%\ndf = pd.read_csv(os.path.join(ROOT, 'train.csv'))\ndf = df.iloc[:int(len(df) * 0.2)].copy()\nprint(f\"✅ 当前处理前 20% 的图像，共 {len(df)} 张\")\n\n# 创建输出目录\nos.makedirs(out_image_dir, exist_ok=True)\n\n# 图像读取函数（RGB）\ndef load_RGBY_image(image_id, split='train'):\n    def load_channel(color):\n        return cv2.imread(f\"{ROOT}/{split}/{image_id}_{color}.png\", cv2.IMREAD_GRAYSCALE)\n    r = load_channel(\"red\")\n    g = load_channel(\"green\")\n    b = load_channel(\"blue\")\n    img = cv2.merge([r, g, b])\n    return img\n\n# 推理准备 & 写入\nannos = []\nfor _, row in tqdm(df.iterrows(), total=len(df)):\n    image_id = row[\"ID\"]\n    img = load_RGBY_image(image_id, train_or_test)\n\n    # 保存图像\n    image_path = os.path.join(out_image_dir, f\"{image_id}.jpg\")\n    cv2.imwrite(image_path, img)\n\n    # 添加 annotation\n    annos.append({\n        'filename': image_path,\n        'width': img.shape[1],\n        'height': img.shape[0],\n        'ann': {\n            'bboxes': None,\n            'labels': None,\n            'masks': None\n        }\n    })\n\n# 保存为 .pkl\nwith open(pkl_path, 'wb') as f:\n    pickle.dump(annos, f)\n\nprint(f\"🎉 成功保存 {len(annos)} 条记录至: {pkl_path}\")\n\n# 可选：复制到 working 区域用于下载\nimport shutil\nshutil.copy(pkl_path, '/kaggle/working/')\nprint(\"📁 已复制 pkl 文件到 /kaggle/working 目录\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:34:51.406892Z","iopub.execute_input":"2025-06-08T04:34:51.407222Z","iopub.status.idle":"2025-06-08T04:35:05.63483Z","shell.execute_reply.started":"2025-06-08T04:34:51.40719Z","shell.execute_reply":"2025-06-08T04:35:05.633682Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# inference","metadata":{"papermill":{"duration":0.018048,"end_time":"2021-02-02T02:51:47.239699","exception":false,"start_time":"2021-02-02T02:51:47.221651","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"copy from raw but change a little bit to fit the model","metadata":{}},{"cell_type":"code","source":"exp_name = \"v4\"\nconf_name = \"mask_rcnn_s101_fpn_syncbn-backbone+head_mstrain_1x_coco\"\nmodel_name = \"mask_rcnn_resnest101_v5_ep9\"\n\nconfig = f'configs/hpa_{exp_name}/{conf_name}.py'\nmodel_file = f'../input/hpa-models/{model_name}.pth'\nann_file = f'../work/mmdet_{exp_name}_train20_percent.pkl'\nresult_pkl = f'../work/{model_name}_train20.pkl'\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:35:05.63618Z","iopub.execute_input":"2025-06-08T04:35:05.636522Z","iopub.status.idle":"2025-06-08T04:35:05.641344Z","shell.execute_reply.started":"2025-06-08T04:35:05.636485Z","shell.execute_reply":"2025-06-08T04:35:05.640479Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"config = f'configs/hpa_{exp_name}/{conf_name}.py'\nmodel_file = f'../input/hpa-models/{model_name}.pth'\nresult_pkl = f'../work/{model_name}_train20.pkl'\n\nadditional_conf = '--cfg-options '\nadditional_conf += (\n    f'data.test.ann_file=../work/mmdet_v4_train20_percent.pkl '\n    f'data.test.img_prefix=\"\" '\n    'test_cfg.rcnn.score_thr=0.001'\n)\n\ncmd = f'python tools/test.py {config} {model_file} --out {result_pkl} {additional_conf}'\n!cd ../mmdetection; {cmd}\n\nimport shutil\nshutil.copy(result_pkl, f'/kaggle/working/{model_name}_train20.pkl')\nprint(\"已复制推理结果到 /kaggle/working，供后续使用或下载\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:35:05.643112Z","iopub.execute_input":"2025-06-08T04:35:05.643457Z","iopub.status.idle":"2025-06-08T04:39:55.38323Z","shell.execute_reply.started":"2025-06-08T04:35:05.643426Z","shell.execute_reply":"2025-06-08T04:39:55.38229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pickle\nimport numpy as np\n\n# 加载推理结果（结构为 tuple: (bboxes, segms)）\nwith open('/kaggle/working/mask_rcnn_resnest101_v5_ep9_train20.pkl', 'rb') as f:\n    results = pickle.load(f)\n\ntotal_cells = 0\ncells_above_001 = 0\ncells_above_02 = 0\n\nfor img_result in results:\n    bboxes, _ = img_result  # 只关注 bbox 的部分\n    for class_result in bboxes:\n        if class_result is None or len(class_result) == 0:\n            continue\n        total_cells += len(class_result)\n        cells_above_001 += np.sum(class_result[:, 4] >= 0.001)\n        cells_above_02 += np.sum(class_result[:, 4] >= 0.5)\n\nprint(f\"所有预测的细胞总数：{total_cells}\")\nprint(f\"score ≥ 0.001 的细胞数：{cells_above_001}（{cells_above_001 / total_cells:.2%}）\")\nprint(f\"score ≥ 0.5 的细胞数：{cells_above_02}（{cells_above_02 / total_cells:.2%}）\")\nprint(f\"若使用阈值 0.5，将丢弃 {(cells_above_001 - cells_above_02)} 个预测细胞\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:39:55.384819Z","iopub.execute_input":"2025-06-08T04:39:55.385072Z","iopub.status.idle":"2025-06-08T04:39:55.432561Z","shell.execute_reply.started":"2025-06-08T04:39:55.385046Z","shell.execute_reply":"2025-06-08T04:39:55.431616Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pickle\nimport numpy as np\n\n# 读取原始推理结果\nwith open('/kaggle/working/mask_rcnn_resnest101_v5_ep9_train20.pkl', 'rb') as f:\n    results = pickle.load(f)\n\nfiltered_results = []\n\nfor img_result in results:\n    bboxes, segms = img_result  # 解包为两个列表\n    filtered_bboxes = []\n    filtered_segms = []\n\n    for bbox_list, segm_list in zip(bboxes, segms):\n        new_bbox_list = []\n        new_segm_list = []\n\n        for bbox, segm in zip(bbox_list, segm_list):\n            if isinstance(bbox, (list, np.ndarray)) and len(bbox) == 5 and bbox[4] >= 0.5:\n                new_bbox_list.append(bbox)\n                new_segm_list.append(segm)\n\n        filtered_bboxes.append(np.array(new_bbox_list) if new_bbox_list else np.empty((0, 5)))\n        filtered_segms.append(new_segm_list)\n\n    filtered_results.append((filtered_bboxes, filtered_segms))\n\n# 保存为新 pkl 文件\nfiltered_pkl_path = '/kaggle/working/mask_rcnn_resnest101_v5_ep9_train20_thr05.pkl'\nwith open(filtered_pkl_path, 'wb') as f:\n    pickle.dump(filtered_results, f)\n\nprint(f\"已保存过滤后推理结果（score ≥ 0.5）至: {filtered_pkl_path}\")\n\nimport shutil\nshutil.copy('/kaggle/working/mask_rcnn_resnest101_v5_ep9_train20_thr05.pkl',\n            '/kaggle/working/mask_rcnn_resnest101_v5_ep9_train20.pkl')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:39:55.433505Z","iopub.execute_input":"2025-06-08T04:39:55.433715Z","iopub.status.idle":"2025-06-08T04:39:55.499766Z","shell.execute_reply.started":"2025-06-08T04:39:55.433694Z","shell.execute_reply":"2025-06-08T04:39:55.499096Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"for example 假设模型学会了：\n\nGolgi 体的区域通常是绿色强信号靠近核周边；\n\n细胞质一般分布较广，强度不均；它就可以在你没有 pixel-level 标签的前提下，尝试在图中检测出可能的“候选细胞”，并对每个区域打分。这些就是你在 .pkl 文件中看到的预测框 + score 值\n\n是模型对“这个框属于某类细胞”的估计值；","metadata":{}},{"cell_type":"markdown","source":"# check amount","metadata":{}},{"cell_type":"code","source":"import pickle\nimport numpy as np\n\npkl_path = '/kaggle/working/mask_rcnn_resnest101_v5_ep9_train20.pkl'\n\n# 加载推理结果\nwith open(pkl_path, 'rb') as f:\n    results = pickle.load(f)\n\n# 初始化计数器\ntarget_class_ids = [0, 1, 2, 3,16,13,18]\nclass_counts = {cls_id: 0 for cls_id in target_class_ids}\n\n# 遍历每张图像\nfor res in results:\n    if isinstance(res, tuple) and len(res) == 2:\n        bboxes, segms = res  # bboxes 是 list，每个元素是该类的所有 box\n        for cls_id in target_class_ids:\n            if cls_id < len(bboxes):\n                class_counts[cls_id] += len(bboxes[cls_id])  # 该类别的 box 数量 == mask 数量\n\n# 输出统计结果\nprint(\"✅ 每个目标类别的 mask 数量如下：\")\nfor cls_id in target_class_ids:\n    print(f\"  └─ 类别 {cls_id}：{class_counts[cls_id]} 个 mask\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:39:55.5009Z","iopub.execute_input":"2025-06-08T04:39:55.501166Z","iopub.status.idle":"2025-06-08T04:39:55.515973Z","shell.execute_reply.started":"2025-06-08T04:39:55.501111Z","shell.execute_reply":"2025-06-08T04:39:55.515318Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pickle\nimport numpy as np\n\npkl_path = '/kaggle/working/mask_rcnn_resnest101_v5_ep9_train20.pkl' \n\n# 读取 pkl 文件\nwith open(pkl_path, 'rb') as f:\n    results = pickle.load(f)\n\nprint(f\"一共包含 {len(results)} 个图像的推理结果\")\n\n# 查看前几个图像的推理结果结构\nfor i, res in enumerate(results[:3]):\n    print(f\"\\n 图像 {i+1} 推理结果类型：{type(res)}\")\n\n    if isinstance(res, tuple) and len(res) == 2:\n        bboxes, segms = res\n        print(f\"  ├─ bbox 数量: {len(bboxes)}\")\n        print(f\"  └─ segm 数量: {len(segms)}\")\n        \n        # 查看第一个 bbox 的 shape\n        if len(bboxes) > 0 and isinstance(bboxes[0], np.ndarray):\n            print(f\"     └─ bboxes[0].shape: {bboxes[0].shape}\")\n        \n        # 查看一个 segm 的结构\n        if len(segms) > 0 and isinstance(segms[0], list):\n            print(f\"     └─ segms[0] 中包含 {len(segms[0])} 个 mask（按类别）\")\n            if len(segms[0]) > 0:\n                print(f\"        └─ segms[0][0] 是 {type(segms[0][0])}，可能是 RLE 格式的字典\")\n    else:\n        print(\" 未识别的结果格式\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:39:55.51712Z","iopub.execute_input":"2025-06-08T04:39:55.517366Z","iopub.status.idle":"2025-06-08T04:39:55.531675Z","shell.execute_reply.started":"2025-06-08T04:39:55.517343Z","shell.execute_reply":"2025-06-08T04:39:55.530949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pickle\n\n# 读取 pkl 文件\nwith open('/kaggle/working/mmdet_v4_train20_percent.pkl', 'rb') as f:\n    records = pickle.load(f)\n\n# 确认有足够的记录\nif len(records) >= 4:\n    rec = records[3]  # 第 4 张（索引从 0 开始）\n    print(\"✅ 第4张图像的内容如下：\")\n    print(\"文件名：\", rec['filename'])\n    print(\"尺寸：\", rec['width'], \"x\", rec['height'])\n    print(\"注释信息：\", rec['ann'])\nelse:\n    print(f\"❌ 文件中只有 {len(records)} 条记录，不足 4 张。\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:39:55.532815Z","iopub.execute_input":"2025-06-08T04:39:55.533165Z","iopub.status.idle":"2025-06-08T04:39:55.540852Z","shell.execute_reply.started":"2025-06-08T04:39:55.533112Z","shell.execute_reply":"2025-06-08T04:39:55.540185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pickle\n\nresult = pickle.load(open('/kaggle/working/mask_rcnn_resnest101_v5_ep9_train20.pkl', 'rb'))\n\n# 第 4 张图像的推理结果（注意索引从 0 开始）\nidx = 2\nif len(result) > idx:\n    masks = result[idx][0]  # [0]: mask, [1]: labels, [2]: scores，如果有\n    print(f\"✅ 第 {idx+1} 张图像的 mask 数量：{len(masks)}\")\nelse:\n    print(f\"❌ 推理结果中仅包含 {len(result)} 条图像记录，无法查看第 {idx+1} 张\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:39:55.541637Z","iopub.execute_input":"2025-06-08T04:39:55.541884Z","iopub.status.idle":"2025-06-08T04:39:55.561055Z","shell.execute_reply.started":"2025-06-08T04:39:55.541862Z","shell.execute_reply":"2025-06-08T04:39:55.560212Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# inference result","metadata":{}},{"cell_type":"code","source":"import os\nimport pickle\nimport numpy as np\nimport cv2\nfrom tqdm import tqdm\nfrom pycocotools import mask as mask_utils\n\n# ====== 路径设置 ======\npkl_result_path = '/kaggle/working/mask_rcnn_resnest101_v5_ep9_train20.pkl'  # ✅ 使用主文件（0.5阈值）\npkl_info_path = '/kaggle/working/mmdet_v4_train20_percent.pkl'  # 包含 image_id 映射信息\nsave_root = '/kaggle/working/masks_thr05'  # ✅ 与当前使用的阈值保持一致\nos.makedirs(save_root, exist_ok=True)\n\n# ====== 加载 image_id 列表 ======\nwith open(pkl_info_path, 'rb') as f:\n    image_infos = pickle.load(f)\n\nimage_ids = [os.path.splitext(os.path.basename(info['filename']))[0] for info in image_infos]\n\n# ====== 加载过滤后模型推理结果 ======\nwith open(pkl_result_path, 'rb') as f:\n    results = pickle.load(f)\n\nprint(f\"共 {len(results)} 张图像结果，将保存至 {save_root}\")\n\n# ====== 开始逐张图像保存掩膜 ======\nfor idx, (bboxes, segms) in tqdm(enumerate(results), total=len(results)):\n    image_id = image_ids[idx]\n    save_dir = os.path.join(save_root, image_id)\n    os.makedirs(save_dir, exist_ok=True)\n\n    for class_id, masks_in_class in enumerate(segms):\n        if not masks_in_class:\n            continue\n        for i, segm in enumerate(masks_in_class):\n            if not isinstance(segm, dict) or 'counts' not in segm or 'size' not in segm:\n                continue\n            try:\n                mask = mask_utils.decode(segm).astype(np.uint8)\n                if mask.sum() < 10:  # 过滤掉极小区域（可选）\n                    continue\n                mask_path = os.path.join(save_dir, f'class{class_id}_cell{i}.png')\n                cv2.imwrite(mask_path, mask * 255)\n            except Exception as e:\n                print(f\"⚠️ {image_id} 图像中 class {class_id} 的 cell{i} 解码失败: {e}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:39:55.562077Z","iopub.execute_input":"2025-06-08T04:39:55.56235Z","iopub.status.idle":"2025-06-08T04:40:47.423276Z","shell.execute_reply.started":"2025-06-08T04:39:55.562328Z","shell.execute_reply":"2025-06-08T04:40:47.422532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom collections import defaultdict\n\nmask_root = '/kaggle/working/masks_thr05'  # 掩膜目录\nimage_class_dict = defaultdict(list)\n\n# 遍历每张图像目录，提取所有 class_id\nfor image_id in sorted(os.listdir(mask_root)):\n    image_dir = os.path.join(mask_root, image_id)\n    if not os.path.isdir(image_dir):\n        continue\n    for fname in os.listdir(image_dir):\n        if fname.startswith('class') and fname.endswith('.png'):\n            class_part = fname.split('_')[0]\n            class_id = int(class_part.replace('class', ''))\n            image_class_dict[image_id].append(class_id)\n\n# 打印前五张图像的类别\nfor i, (image_id, class_ids) in enumerate(image_class_dict.items()):\n    print(f'{image_id}: {sorted(set(class_ids))}')\n    if i >= 10:\n        break\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:40:47.424376Z","iopub.execute_input":"2025-06-08T04:40:47.424627Z","iopub.status.idle":"2025-06-08T04:40:47.43538Z","shell.execute_reply.started":"2025-06-08T04:40:47.424602Z","shell.execute_reply":"2025-06-08T04:40:47.434723Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nclass_count_per_image = [len(set(class_ids)) for class_ids in image_class_dict.values()]\n\nplt.hist(class_count_per_image, bins=range(1, 20), edgecolor='black')\nplt.xlabel('Number of Classes per Image')\nplt.ylabel('Number of Images')\nplt.title('Class Count Distribution (Threshold=0.5)')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:40:47.436422Z","iopub.execute_input":"2025-06-08T04:40:47.43663Z","iopub.status.idle":"2025-06-08T04:40:47.57841Z","shell.execute_reply.started":"2025-06-08T04:40:47.436608Z","shell.execute_reply":"2025-06-08T04:40:47.577662Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 打包整个 masks 文件夹（包括所有子目录）\n!cd /kaggle/working && zip -r masks_thr05.zip masks_thr05\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:42:37.029328Z","iopub.execute_input":"2025-06-08T04:42:37.029674Z","iopub.status.idle":"2025-06-08T04:42:38.335034Z","shell.execute_reply.started":"2025-06-08T04:42:37.029643Z","shell.execute_reply":"2025-06-08T04:42:38.333978Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls -lh /kaggle/working/masks_thr05.zip\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T04:42:50.252464Z","iopub.execute_input":"2025-06-08T04:42:50.252769Z","iopub.status.idle":"2025-06-08T04:42:51.261064Z","shell.execute_reply.started":"2025-06-08T04:42:50.252744Z","shell.execute_reply":"2025-06-08T04:42:51.260194Z"}},"outputs":[],"execution_count":null}]}