{"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":117682,"databundleVersionId":15062069},{"sourceType":"datasetVersion","sourceId":14785421,"datasetId":9115475,"databundleVersionId":15638973},{"sourceType":"datasetVersion","sourceId":13444908,"datasetId":8534135,"databundleVersionId":14160985},{"sourceType":"datasetVersion","sourceId":14981137,"datasetId":9113883,"databundleVersionId":15854371},{"sourceType":"datasetVersion","sourceId":14261875,"datasetId":8670484,"databundleVersionId":15061995},{"sourceType":"modelInstanceVersion","sourceId":778818,"databundleVersionId":15984392,"modelInstanceId":594290,"modelId":606557},{"sourceType":"kernelVersion","sourceId":296754894}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!mkdir /kaggle/working/nnunetv2-whl/\n!cp -r /kaggle/input/nnunet-auto-dependency/* /kaggle/working/nnunetv2-whl/\n!cp -r /kaggle/input/vcsd-nnunet-whl/* /kaggle/working/nnunetv2-whl/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:32:19.507386Z","iopub.execute_input":"2026-02-19T17:32:19.507689Z","iopub.status.idle":"2026-02-19T17:33:31.022938Z","shell.execute_reply.started":"2026-02-19T17:32:19.507663Z","shell.execute_reply":"2026-02-19T17:33:31.021915Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install batchgenerators nnunetv2 --no-index --find-links=/kaggle/working/nnunetv2-whl/\n!rm -r /kaggle/working/nnunetv2-whl/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:33:31.024419Z","iopub.execute_input":"2026-02-19T17:33:31.024729Z","iopub.status.idle":"2026-02-19T17:35:55.988159Z","shell.execute_reply.started":"2026-02-19T17:33:31.024684Z","shell.execute_reply":"2026-02-19T17:35:55.986792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cp -r /kaggle/input/vcsd-models/nnUNet_trainer/training/ /usr/local/lib/python3.12/dist-packages/nnunetv2/\n!cp /kaggle/input/vcsd-models/nnUNet_trainer/batchgeneratorsv2/extranoisetransforms.py /usr/local/lib/python3.12/dist-packages/batchgeneratorsv2/transforms/noise/\n!cp /kaggle/input/vcsd-models/nnUNet_trainer/batchgeneratorsv2/illumination.py /usr/local/lib/python3.12/dist-packages/batchgeneratorsv2/transforms/intensity/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:35:55.991021Z","iopub.execute_input":"2026-02-19T17:35:55.991299Z","iopub.status.idle":"2026-02-19T17:35:56.590996Z","shell.execute_reply.started":"2026-02-19T17:35:55.991269Z","shell.execute_reply":"2026-02-19T17:35:56.590087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import glob\nimport importlib\nimport os\nimport subprocess\nimport sys\nimport numpy as np\nimport pandas as pd\n\nclass ParticipantVisibleError(Exception):\n    pass\n\n\nclass HostVisibleError(Exception):\n    pass\n\ndef install_dependencies():\n    \"\"\"On Kaggle, the topometrics library must be installed during the run. This function handles the entire process.\"\"\"\n    try:\n        import topometrics.leaderboard\n\n        return None\n    # The broad exception is necessary as the initial import can fail for multiple reasons.\n    except:\n        pass\n\n    resources_dir = '/kaggle/input/vesuvius-metric-resources'\n    install_dir = '/kaggle/working/topological-metrics-kaggle'\n\n    try:\n        subprocess.run(f'pip install \"pybind11[global]\" --no-index --find-links={resources_dir}/wheels', shell=True, check=True)\n        subprocess.run(f'cd /kaggle/working && cp -r {resources_dir}/topological-metrics-kaggle .', shell=True, check=True)\n        subprocess.run(\n            f'cd {install_dir} && chmod +x scripts/setup_submodules.sh scripts/build_betti.sh && make build-betti',\n            shell=True,\n            check=True,\n        )\n        sys.path.append('/kaggle/working/topological-metrics-kaggle/src')\n        importlib.invalidate_caches()\n\n    except Exception as err:\n        raise HostVisibleError(f'Failed to install topometrics library: {err}')\ninstall_dependencies()\nimport sys\nbuild_path = \"/kaggle/working/topological-metrics-kaggle/external/Betti-Matching-3D/build\"\nif build_path not in sys.path:\n    sys.path.insert(0, build_path)\nimport betti_matching as bm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:35:56.592286Z","iopub.execute_input":"2026-02-19T17:35:56.592680Z","iopub.status.idle":"2026-02-19T17:37:01.083129Z","shell.execute_reply.started":"2026-02-19T17:35:56.592619Z","shell.execute_reply":"2026-02-19T17:37:01.082318Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"code = r'''\nimport sys\nbuild_path = \"/kaggle/working/topological-metrics-kaggle/external/Betti-Matching-3D/build\"\nif build_path not in sys.path:\n    sys.path.insert(0, build_path)\nimport betti_matching as bm\nimport os\n\nos.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"expandable_segments:True\"\nos.environ['nnUNet_compile'] = 'True'\nos.environ['torch.backends.cudnn.benchmark'] = 'True'\n\nimport tifffile\nimport numpy as np\nimport tifffile\nimport scipy.ndimage as ndi\nfrom skimage.morphology import remove_small_objects\nimport cc3d\n\nfrom typing import List, Tuple\n\nfrom skimage.measure import label as sk_label\nimport cv2\nfrom sklearn.decomposition import PCA\nimport SimpleITK as sitk\n\ndef compute_betti_num(image):\n    topo_pred = 1 - image\n    split_ = (2, 2, 2)\n    slices = _octant_slices(topo_pred.shape, split_)\n    topo_preds = []\n    topo_results = []\n    for zs, ys, xs in slices:\n        topo_preds.append(topo_pred[zs, ys, xs])\n    for idx in range(0,len(topo_preds),1):\n        topo_results += bm.compute_barcode(topo_preds[idx:idx+1])\n    b0_error = np.sum([item.num_pairs[0] for item in topo_results])\n    b1_error = np.sum([item.num_pairs[1] for item in topo_results])\n    b2_error = np.sum([item.num_pairs[2] for item in topo_results])\n    return b0_error, b1_error, b2_error\n\n\ndef fill_betti2_holes(volume, connectivity=3):\n    \"\"\"\n    volume: 3D binary ndarray (1=foreground, 0=background)\n    返回：填充 Betti-2 hole 后的 volume\n    \"\"\"\n\n    vol = volume.astype(bool)\n\n    # background\n    bg = ~vol\n\n    # 连通结构（26 邻域）\n    struct = ndi.generate_binary_structure(3, connectivity)\n\n    # label background components\n    bg_labels, num = ndi.label(bg, structure=struct)\n\n    if num == 0:\n        return volume.copy()\n\n    # 找接触边界的 background label\n    touching_boundary = set()\n\n    z_max, y_max, x_max = np.array(bg.shape) - 1\n\n    boundary_slices = [\n        (0, slice(None), slice(None)),\n        (z_max, slice(None), slice(None)),\n        (slice(None), 0, slice(None)),\n        (slice(None), y_max, slice(None)),\n        (slice(None), slice(None), 0),\n        (slice(None), slice(None), x_max),\n    ]\n\n    for slc in boundary_slices:\n        touching_boundary |= set(np.unique(bg_labels[slc]))\n\n    touching_boundary.discard(0)  # 0 是 background 外\n\n    # 所有 background labels\n    all_labels = set(range(1, num + 1))\n\n    # 真正的 Betti-2 holes\n    enclosed_labels = all_labels - touching_boundary\n\n    # 填充这些 hole\n    filled = vol.copy()\n    for lab in enclosed_labels:\n        filled[bg_labels == lab] = True\n\n    return filled.astype(np.uint8)\n\ndef largest_connected_component(vol):\n    labels, n = ndi.label(vol)\n    if n == 0:\n        return vol, None\n\n    sizes = ndi.sum(vol, labels, index=range(1, n+1))\n    largest = np.argmax(sizes) + 1\n    return (labels == largest), largest\n\ndef find_fully_enclosed_voxels(surface):\n    \"\"\"\n    返回所有 26 邻域完全为 1 的 voxel 坐标\n    \"\"\"\n    struct = np.ones((3,3,3), dtype=np.uint8)\n\n    # 计算每个 voxel 的邻域 1 的数量\n    neighbor_count = ndi.convolve(\n        surface.astype(np.uint8),\n        struct,\n        mode='constant',\n        cval=0\n    )\n\n    # 3x3x3 = 27，包括自己\n    enclosed = (surface == 1) & (neighbor_count == 27)\n    return np.argwhere(enclosed)\n\ndef punch_single_voxel_cavity(vol, seed=None):\n    if seed is not None:\n        np.random.seed(seed)\n\n    vol = vol.copy().astype(np.uint8)\n\n    surface, _ = largest_connected_component(vol)\n    candidates = find_fully_enclosed_voxels(surface)\n\n    if len(candidates) == 0:\n        raise RuntimeError(\"No fully enclosed voxel found.\")\n\n    z, y, x = candidates[np.random.randint(len(candidates))]\n    vol[z, y, x] = 0\n\n    return vol, (z, y, x)\n\ndef detect_isolated_regions(mask_2d: np.ndarray):\n    \n    labeled_array = sk_label(1-mask_2d, connectivity=1)\n    isolated_regions = []\n    for region_id in range(1, labeled_array.max() + 1):\n        region_mask = (labeled_array == region_id)\n        # 检查区域是否接触边界\n        touches_boundary = np.any(region_mask[0, :]) or np.any(region_mask[-1, :]) or \\\n                            np.any(region_mask[:, 0]) or np.any(region_mask[:, -1])\n        if not touches_boundary:\n            isolated_regions.append(region_id)\n    isolated_mask = np.isin(labeled_array, isolated_regions)\n    return isolated_mask\n\ndef projection_betti1_finding(vol: np.ndarray):\n    image_ccs = cc3d.connected_components(vol, connectivity=26)\n    num_image_ccs = image_ccs.max()\n\n    projection1_image_list = []\n    projection2_image_list = []\n    projection3_image_list = []\n    zero_3d = np.zeros_like(vol, dtype=np.uint8)\n    cnt = 1\n    for lab in range(1, num_image_ccs+1):\n        component = (image_ccs == lab).astype(np.uint8)\n        proj = (component.sum(axis=0)>0).astype(np.uint8)\n        projection1_image_list.append(proj)\n        proj = (component.sum(axis=1)>0).astype(np.uint8)\n        projection2_image_list.append(proj)\n        proj = (component.sum(axis=2)>0).astype(np.uint8)\n        projection3_image_list.append(proj)\n\n    for idx,(proj1, proj2, proj3) in enumerate(zip(projection1_image_list, projection2_image_list, projection3_image_list)):\n        # 检测proj1中是否包含不接触边界的孤立区域\n        # labeled_array1 = sk_label(1-proj1, connectivity=1)\n        # use the detect_isolated_regions function\n        isolated_mask1 = detect_isolated_regions(proj1)\n        \n        # labeled_array2 = sk_label(1-proj2, connectivity=1)\n        isolated_mask2 = detect_isolated_regions(proj2)\n        \n        # labeled_array3 = sk_label(1-proj3, connectivity=1)\n        isolated_mask3 = detect_isolated_regions(proj3)\n        \n        # 选择mask中最大的区域进行膨胀操作，然后映射回3D\n        if np.any(isolated_mask1) or np.any(isolated_mask2) or np.any(isolated_mask3):\n            max_idx = np.argmax(np.array((isolated_mask1.sum(), isolated_mask2.sum(), isolated_mask3.sum())))\n            if max_idx == 0:\n                isolated_mask = isolated_mask1\n            elif max_idx == 1:\n                isolated_mask = isolated_mask2\n            else:  \n                isolated_mask = isolated_mask3\n\n            isolated_mask = cv2.dilate(isolated_mask.astype(np.uint8), np.ones((8,8),np.uint8), iterations=1).astype(bool)\n            isolated_mask_ccs,num_labels_im = ndi.label(isolated_mask)\n            # 根据max_idx映射回3D\n            for lab in range(1, num_labels_im+1):\n                mask_3d = np.zeros_like(vol, dtype=bool)\n                this_isolated_mask = (isolated_mask_ccs == lab)\n                if max_idx == 0:\n                    mask_3d[:, this_isolated_mask] = True\n                elif max_idx == 1:\n                    mask_3d = mask_3d.transpose(1,0,2)\n                    mask_3d[:, this_isolated_mask] = True\n                    mask_3d = mask_3d.transpose(1,0,2)\n                else:\n                    mask_3d[this_isolated_mask,:] = True\n                    \n                mask_3d = mask_3d & (image_ccs == (idx + 1))\n                # if np.diff(mask_3d,axis=max_idx).astype(np.uint8).sum(max_idx).max() > 2:\n                #     continue\n                mask_3d = mask_3d.astype(np.uint8) * cnt\n                cnt += 1\n                zero_3d = zero_3d | mask_3d.astype(np.uint8)\n    return zero_3d\n\n\ndef pca_hole_filling(out, return_surface=False, finding_betti1_surface_func=projection_betti1_finding):\n    surface_around_out_ccs = finding_betti1_surface_func(out)\n    fill_holes = np.zeros_like(out, dtype=np.uint8)\n    # surface_around_out_ccs = cc3d.connected_components(surface_around_out)\n    for i in range(1, surface_around_out_ccs.max()+1):\n        try:\n            this_ccs = (surface_around_out_ccs==i).astype(np.uint8)\n            # points: (N, 3)\n            # points = np.argwhere(out_ccs)\n            # points = np.array(get_lowest_surface_coordinates(this_ccs)).T\n            \n            points = np.argwhere(this_ccs)\n            pca = PCA(n_components=2,)\n            points_2d = pca.fit_transform(points)\n            if np.sum(pca.explained_variance_ratio_) < 0.8:\n                # print(f\"The {i}-th ccs surface isnot approximately planar. skip.\")\n                continue\n\n            pts = points_2d\n\n            # --- rasterize to a binary image ---\n            xmin, ymin = pts.min(axis=0)\n            xmax, ymax = pts.max(axis=0)\n\n            pad = 5.0\n            xmin -= pad; ymin -= pad\n            xmax += pad; ymax += pad\n\n            # choose resolution (pixels per unit); increase to get finer mask\n            scale = 1.\n\n            W = int(np.ceil((xmax - xmin) * scale)) + 1\n            H = int(np.ceil((ymax - ymin) * scale)) + 1\n            W = max(W, 16)\n            H = max(H, 16)\n\n            # map points to pixel coords\n            px = np.round((pts[:, 0] - xmin) * scale).astype(np.int32)\n            py = np.round((pts[:, 1] - ymin) * scale).astype(np.int32)\n            px = np.clip(px, 0, W - 1)\n            py = np.clip(py, 0, H - 1)\n\n            mask = np.zeros((H, W), dtype=np.uint8)\n            mask[py, px] = 1\n\n            # connect sparse points into a closed boundary (dilate a bit)\n            mask = cv2.dilate(mask, np.ones((3, 3), np.uint8), iterations=1)\n\n            # --- fill holes in 2D ---\n            filled_mask = ndi.binary_fill_holes(mask > 0)\n\n            yy, xx = np.where(filled_mask)  # row=y, col=x\n\n            # pixel -> continuous 2D coords（与之前 rasterize 时的映射一致）\n            pts_filled_2d = np.stack([xx / scale + xmin, yy / scale + ymin], axis=1).astype(np.float32)\n\n            fillholes = pca.inverse_transform(pts_filled_2d)\n\n            fillholes = np.round(fillholes).astype(np.int16)\n            fillholes[fillholes >= out.shape[0]] = out.shape[0] - 1\n\n            # # remove the duplicate coords\n            fillholes = np.unique(fillholes, axis=0)\n            fill_holes[fillholes[:,0], fillholes[:,1], fillholes[:,2]] = 1\n        except Exception as e:\n            print(f\"Error processing the {i}-th ccs surface: {e}. skip.\")\n            continue\n    structure = np.ones((4,4,4),np.uint8)\n    structure[0,...] = 0\n    structure[-1,...] = 0\n    fill_holes = ndi.binary_dilation(fill_holes.astype(np.uint8), structure, iterations=1).astype(np.uint8)\n    if return_surface:\n        return ((fill_holes | out )>0).astype(np.uint8), surface_around_out_ccs\n    else:\n        return ((fill_holes | out)>0).astype(np.uint8)\n\n\ndef _axis_cuts(n: int, parts: int) -> List[Tuple[int, int]]:\n    \"\"\"\n    Split [0, n) into 'parts' contiguous chunks, sizes differ by at most 1.\n    Clamps parts to avoid zero-sized slices when n < parts.\n    \"\"\"\n    parts = max(1, min(parts, n if n > 0 else 1))\n    base = n // parts\n    extra = n % parts\n    cuts: List[Tuple[int, int]] = []\n    start = 0\n    for i in range(parts):\n        size = base + (1 if i < extra else 0)\n        end = start + size\n        cuts.append((start, end))\n        start = end\n    return cuts\n\ndef _octant_slices(shape, splits):\n    zcuts = _axis_cuts(shape[0], splits[0])\n    ycuts = _axis_cuts(shape[1], splits[1])\n    xcuts = _axis_cuts(shape[2], splits[2])\n    out: List[Tuple[slice, slice, slice]] = []\n    for z0, z1 in zcuts:\n        for y0, y1 in ycuts:\n            for x0, x1 in xcuts:\n                if (z1 - z0) > 0 and (y1 - y0) > 0 and (x1 - x0) > 0:\n                    out.append((slice(z0, z1), slice(y0, y1), slice(x0, x1)))\n    return out\n\ndef build_anisotropic_struct(z_radius: int, xy_radius: int):\n    z, r = z_radius, xy_radius\n\n    if z == 0 and r == 0:\n        return None\n\n    if z == 0 and r > 0:\n        size = 2 * r + 1\n        struct = np.zeros((1, size, size), dtype=bool)\n        cy, cx = r, r\n        for dy in range(-r, r + 1):\n            for dx in range(-r, r + 1):\n                if dy * dy + dx * dx <= r * r:\n                    struct[0, cy + dy, cx + dx] = True\n        return struct\n\n    if z > 0 and r == 0:\n        struct = np.zeros((2 * z + 1, 1, 1), dtype=bool)\n        struct[:, 0, 0] = True\n        return struct\n\n    depth = 2 * z + 1\n    size = 2 * r + 1\n    struct = np.zeros((depth, size, size), dtype=bool)\n    cz, cy, cx = z, r, r\n    for dz in range(-z, z + 1):\n        for dy in range(-r, r + 1):\n            for dx in range(-r, r + 1):\n                if dy * dy + dx * dx <= r * r:\n                    struct[cz + dz, cy + dy, cx + dx] = True\n    return struct\n\ndef topo_postprocess(\n    mask,         \n    low_thred=0.2,\n    high_thred=0.8,\n    z_radius=1,\n    xy_radius=0,\n    dust_min_size=1000,\n    probs = True,\n):  \n    if probs:        \n        strong = (mask >= high_thred).astype(np.uint8)\n        weak = (mask >= low_thred).astype(np.uint8)\n        struct_hyst = ndi.generate_binary_structure(3, 3)\n        mask = ndi.binary_propagation(strong, mask=weak, structure=struct_hyst)\n    else:\n        mask = mask.astype(np.uint8)\n    \n    #mask = remove_components_not_touching_two_boundaries(mask, tolerance=5, connectivity=3)\n        \n    # --- Step 2: 3D Anisotropic Closing ---\n    if z_radius > 0 or xy_radius > 0:\n        struct_close = build_anisotropic_struct(z_radius, xy_radius)\n        if struct_close is not None:\n            mask = ndi.binary_closing(mask, structure=struct_close)\n\n    # --- Step 3: Dust Removal ---\n    if dust_min_size > 0:\n        mask = remove_small_objects(mask.astype(bool), min_size=dust_min_size)\n\n    return mask.astype(np.uint8)\n\ndef bmbarcode_betti1_finding(vol: np.ndarray, split_size=(16,16,16)):\n    topo_pred = 1 - vol.copy()\n    slices = _octant_slices(topo_pred.shape, split_size)\n    topo_preds = []\n    for zs, ys, xs in slices:\n        topo_preds.append(topo_pred[zs, ys, xs])\n    topo_results = bm.compute_barcode(topo_preds)\n    \n    betti1 = np.array([item.num_pairs[1] for item in topo_results])\n    \n    betti1_idxs = np.where(betti1>0)[0]\n    small_betti_surface = np.zeros_like(vol, dtype=np.int32)\n    \n    for idx1, idx in enumerate(betti1_idxs):\n        zs, ys, xs = slices[idx]\n        keep_vol = np.zeros_like(topo_preds[idx], dtype=np.int32)\n        this_vol = vol[zs, ys, xs]\n        cc3d_labels = cc3d.connected_components(this_vol, connectivity=26)\n        for lab in range(1, cc3d_labels.max()+1):\n            this_ccs = (cc3d_labels==lab).astype(np.int32)\n            this_topo = bm.compute_barcode(1 - this_ccs)\n            if this_topo.num_pairs[1]>0:\n                keep_vol = keep_vol | this_ccs\n        small_betti_surface[zs, ys, xs] = keep_vol*idx1\n    return small_betti_surface\n\n\ndef main(gpu_id, image_paths):\n    \n    os.environ[\"CUDA_VISIBLE_DEVICES\"] = str(gpu_id)\n\n    import torch\n    from nnunetv2.inference.predict_from_raw_data import nnUNetPredictor\n\n    device = torch.device(\"cuda:0\")\n\n    ckpt_dir = \"/kaggle/input/vcsd-models/nnUNetTrainer__nnUNetResEncUNetMPlansbs8ps192s7__3d_fullres_pretrain_1500ft/\"\n\n    predictor = nnUNetPredictor(\n        tile_step_size=0.25,\n        use_gaussian=True,\n        use_mirroring=True,\n        perform_everything_on_device=True,\n        device=device,\n        verbose=False,\n        verbose_preprocessing=False,\n        allow_tqdm=True\n    )\n\n    predictor.initialize_from_trained_model_folder(\n        ckpt_dir,\n        ['all'],\n        checkpoint_name='checkpoint_final.pth'\n    )\n\n    props = {\n        'sitk_stuff': {\n            'spacing': (1.0, 1.0, 1.0),\n            'origin': (0.0, 0.0, 0.0),\n            'direction': (\n                1.0, 0.0, 0.0,\n                0.0, 1.0, 0.0,\n                0.0, 0.0, 1.0\n            )\n        },\n        'spacing': [1.0, 1.0, 1.0]\n    }\n\n    for image_path in image_paths:\n        image_name = image_path.split('/')[-1]\n        image = tifffile.imread(image_path)\n\n        _, ret_probs = predictor.predict_single_npy_array(\n            image[None, ...],\n            props,\n            None,\n            None,\n            True\n        )\n\n        pred_hard = topo_postprocess(ret_probs[1],low_thred=0.2,high_thred=0.8, probs=True)\n        pred_hard = pca_hole_filling(pred_hard,finding_betti1_surface_func=projection_betti1_finding).astype(np.uint8)\n        pred_hard = pca_hole_filling(pred_hard,finding_betti1_surface_func=bmbarcode_betti1_finding)\n        pred_hard = topo_postprocess(pred_hard,probs=False).astype(np.uint8)\n        b0_error, b1_error, b2_error = compute_betti_num(pred_hard)\n        \n        if b2_error > 0:\n            pred_hard = fill_betti2_holes(pred_hard)\n        \n        if b0_error > 100:\n            pred_hard, _ = punch_single_voxel_cavity(pred_hard)\n        elif b1_error > 3:\n            pred_hard, _ = punch_single_voxel_cavity(pred_hard)\n\n        tifffile.imwrite(image_name, pred_hard)\n'''\n\nwith open(\"worker_infer.py\", \"w\") as f:\n    f.write(code)\n\nprint(\"Saved as worker_infer.py\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:37:01.084609Z","iopub.execute_input":"2026-02-19T17:37:01.085194Z","iopub.status.idle":"2026-02-19T17:37:01.099219Z","shell.execute_reply.started":"2026-02-19T17:37:01.085171Z","shell.execute_reply":"2026-02-19T17:37:01.098543Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import torch\n# torch.tensor([1]).cuda()\n# !fuser -v /dev/nvidia*","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:37:01.100217Z","iopub.execute_input":"2026-02-19T17:37:01.100546Z","iopub.status.idle":"2026-02-19T17:37:01.117773Z","shell.execute_reply.started":"2026-02-19T17:37:01.100521Z","shell.execute_reply":"2026-02-19T17:37:01.116910Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !kill -9 55","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:37:01.118763Z","iopub.execute_input":"2026-02-19T17:37:01.119587Z","iopub.status.idle":"2026-02-19T17:37:01.131161Z","shell.execute_reply.started":"2026-02-19T17:37:01.119562Z","shell.execute_reply":"2026-02-19T17:37:01.130447Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from multiprocessing import Process\nfrom glob import glob\nfrom worker_infer import main\nimport pandas as pd  \n\ntest_csv_path = \"/kaggle/input/vesuvius-challenge-surface-detection/test.csv\"\ntest_images_dir = \"/kaggle/input/vesuvius-challenge-surface-detection/test_images\"\n\ntest_df = pd.read_csv(test_csv_path)\ntest_ids = test_df['id'].tolist()\n\nimage_paths = [f'/kaggle/input/vesuvius-challenge-surface-detection/test_images/{item}.tif' for item in test_ids]\n# image_paths = glob('/kaggle/input/vesuvius-challenge-surface-detection/train_images/*.tif')\n# image_paths = image_paths[:20]\n\nif len(image_paths) > 4:\n    half_len = int(len(image_paths)//2)\n    image_paths1 = image_paths[0:half_len]\n    image_paths2 = image_paths[half_len:]\n    \n    p1 = Process(target=main, args=(0, image_paths1))\n    p2 = Process(target=main, args=(1, image_paths2))\n    \n    p1.start()\n    p2.start()\n    \n    p1.join()\n    p2.join()\nelse:\n    main(0, image_paths)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:37:01.132101Z","iopub.execute_input":"2026-02-19T17:37:01.132329Z","iopub.status.idle":"2026-02-19T17:43:10.477672Z","shell.execute_reply.started":"2026-02-19T17:37:01.132308Z","shell.execute_reply":"2026-02-19T17:43:10.476718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!rm -r /kaggle/working/worker_infer.py","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:43:10.480069Z","iopub.execute_input":"2026-02-19T17:43:10.480814Z","iopub.status.idle":"2026-02-19T17:43:10.710676Z","shell.execute_reply.started":"2026-02-19T17:43:10.480785Z","shell.execute_reply":"2026-02-19T17:43:10.709496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile\n\nwith zipfile.ZipFile('submission.zip', 'w') as zipf:\n    for test_id in test_ids:\n        zipf.write(f\"{test_id}.tif\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-19T17:43:10.712411Z","iopub.execute_input":"2026-02-19T17:43:10.712874Z","iopub.status.idle":"2026-02-19T17:43:10.796843Z","shell.execute_reply.started":"2026-02-19T17:43:10.712840Z","shell.execute_reply":"2026-02-19T17:43:10.796001Z"}},"outputs":[],"execution_count":null}]}