{
  "id": 664320,
  "title": "Marching ANTS is all you need !!!",
  "url": "/competitions/vesuvius-challenge-surface-detection/discussion/664320",
  "author_name": "",
  "post_date": "2025-12-24T03:17:50.105379600Z",
  "votes": 23,
  "comment_count": 27,
  "views": 0,
  "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F5d2caa533f48da6fda04f1d01046aa49%2FSelection_1831.png?generation=1766546235865360&amp;alt=media\" alt=\"\">\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F175d90e857b58c0bac1da18f10fbbf5b%2FSelection_1832.png?generation=1766546250857133&amp;alt=media\" alt=\"\">\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F31084f09670960e1c6a0d11b46ee1758%2FSelection_1833.png?generation=1766546267917679&amp;alt=media\" alt=\"\"></p>\n<hr>\n<p>Before this final proposed solution, Gemini, Chatgpt and I have been trying many methods like watershed, graphcut, dynamic programming, detecting ouliers normals/density … etc. We wrote codes and tried about 10 methods, and all of them failed for the following reasons:</p>\n<ol>\n<li>The \"Smooth C-Curve\" vs. T-Junctions</li>\n<li>The Bridge is \"Structural,\" Not \"Visual\"</li>\n<li>Proximity to Holes (The \"Tear-and-Join\" Phenomenon)</li>\n</ol>\n<p>Because \"touching voxels\" forms a smooth C-curve, the surface normals of Sheet 1 gradually \"melt\" into the normals of Sheet 2. To a normal-based filter, the gradient remains consistent. The \"bridge\" is essentially invisible to local geometry because it perfectly mimics the curvature of the papyrus itself.</p>\n<p>While googling for topology, i came across an image of ants crawling on mobius strip. If AI agent wants to be \"intelligent\", he needs to relate observation (and maybe seemly unreated obseobservation\") to a possible solution to the current task/problem. I wonder how to train An AI agnet to do that … to let him generate \"the stroke of a genius\"</p>",
  "messages": [
    {
      "id": "3381212",
      "postDate": "12/24/2025 03:17:50",
      "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F5d2caa533f48da6fda04f1d01046aa49%2FSelection_1831.png?generation=1766546235865360&amp;alt=media\" alt=\"\">\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F175d90e857b58c0bac1da18f10fbbf5b%2FSelection_1832.png?generation=1766546250857133&amp;alt=media\" alt=\"\">\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F31084f09670960e1c6a0d11b46ee1758%2FSelection_1833.png?generation=1766546267917679&amp;alt=media\" alt=\"\"></p>\n<hr>\n<p>Before this final proposed solution, Gemini, Chatgpt and I have been trying many methods like watershed, graphcut, dynamic programming, detecting ouliers normals/density … etc. We wrote codes and tried about 10 methods, and all of them failed for the following reasons:</p>\n<ol>\n<li>The \"Smooth C-Curve\" vs. T-Junctions</li>\n<li>The Bridge is \"Structural,\" Not \"Visual\"</li>\n<li>Proximity to Holes (The \"Tear-and-Join\" Phenomenon)</li>\n</ol>\n<p>Because \"touching voxels\" forms a smooth C-curve, the surface normals of Sheet 1 gradually \"melt\" into the normals of Sheet 2. To a normal-based filter, the gradient remains consistent. The \"bridge\" is essentially invisible to local geometry because it perfectly mimics the curvature of the papyrus itself.</p>\n<p>While googling for topology, i came across an image of ants crawling on mobius strip. If AI agent wants to be \"intelligent\", he needs to relate observation (and maybe seemly unreated obseobservation\") to a possible solution to the current task/problem. I wonder how to train An AI agnet to do that … to let him generate \"the stroke of a genius\"</p>",
      "rawMarkdown": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F5d2caa533f48da6fda04f1d01046aa49%2FSelection_1831.png?generation=1766546235865360&alt=media)\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F175d90e857b58c0bac1da18f10fbbf5b%2FSelection_1832.png?generation=1766546250857133&alt=media)\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F31084f09670960e1c6a0d11b46ee1758%2FSelection_1833.png?generation=1766546267917679&alt=media)\n\n---\n\n\nBefore this final proposed solution, Gemini, Chatgpt and I have been trying many methods like watershed, graphcut, dynamic programming, detecting ouliers normals/density ... etc. We wrote codes and tried about 10 methods, and all of them failed for the following reasons:\n\n1. The \"Smooth C-Curve\" vs. T-Junctions\n2. The Bridge is \"Structural,\" Not \"Visual\"\n3. Proximity to Holes (The \"Tear-and-Join\" Phenomenon)\n\n\nBecause \"touching voxels\" forms a smooth C-curve, the surface normals of Sheet 1 gradually \"melt\" into the normals of Sheet 2. To a normal-based filter, the gradient remains consistent. The \"bridge\" is essentially invisible to local geometry because it perfectly mimics the curvature of the papyrus itself.\n\nWhile googling for topology, i came across an image of ants crawling on mobius strip. If AI agent wants to be \"intelligent\", he needs to relate observation (and maybe seemly unreated obseobservation\") to a possible solution to the current task/problem. I wonder how to train An AI agnet to do that ... to let him generate \"the stroke of a genius\"",
      "votes": null
    },
    {
      "id": "3381213",
      "postDate": "12/24/2025 03:18:26",
      "content": "<p><a href=\"https://github.com/seung-lab/dijkstra3d\" target=\"_blank\">https://github.com/seung-lab/dijkstra3d</a><br>\nDijkstra's Shortest Path variants for 6, 18, and 26-connected 3D Image Volumes or 4 and 8-connected 2D images.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F306d4c1614264b669c039ccfda42dbab%2FSelection_1834.png?generation=1766546304697280&amp;alt=media\" alt=\"\"></p>",
      "rawMarkdown": "https://github.com/seung-lab/dijkstra3d  \nDijkstra's Shortest Path variants for 6, 18, and 26-connected 3D Image Volumes or 4 and 8-connected 2D images.\n\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F306d4c1614264b669c039ccfda42dbab%2FSelection_1834.png?generation=1766546304697280&alt=media)",
      "votes": null
    },
    {
      "id": "3381272",
      "postDate": "12/24/2025 06:12:29",
      "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F360e9f03a725f68fa606cd198e28c82b%2FSelection_1845.png?generation=1766556987884686&amp;alt=media\" alt=\"\"></p>\n<p>\n\nuse  \"geodesic Voronoi\" above instead</p>",
      "rawMarkdown": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F360e9f03a725f68fa606cd198e28c82b%2FSelection_1845.png?generation=1766556987884686&alt=media)\n\n~~code notebook coming soon!~~\n~~i see if i can show you some results on the non-labelled region of the train data (label==2)~~\nuse  \"geodesic Voronoi\" above instead",
      "votes": null
    },
    {
      "id": "3381486",
      "postDate": "12/24/2025 18:07:15",
      "content": "<p>hmm, very simply method to estimate number of sheets. I had thought about it but didn't get an appropriate opportunity to try it out, but i feel knowing number of sheets can be very helpful. After that we can assume that many 2-4 voxel thick continuous sheets and bend them via some method to fit them according to the input, I feel this is the only way to use all the three important priors we have (continuous sheets, no merging, and nearly constant thickness) .</p>",
      "rawMarkdown": "hmm, very simply method to estimate number of sheets. I had thought about it but didn't get an appropriate opportunity to try it out, but i feel knowing number of sheets can be very helpful. After that we can assume that many 2-4 voxel thick continuous sheets and bend them via some method to fit them according to the input, I feel this is the only way to use all the three important priors we have (continuous sheets, no merging, and nearly constant thickness) .",
      "votes": null
    },
    {
      "id": "3381626",
      "postDate": "12/25/2025 04:27:04",
      "content": "<p>very good news!</p>\n<p>there is even a faster method usig the concept \"geodesic Voronoi\"  </p>\n<ul>\n<li>sepeate sheets in less than few sec on cpu</li>\n<li>can run in gpu</li>\n<li>given a 3d connected component, use ray test to identify different sheet. select one pixel on each sheet as seed. ( or strategically select a set of seeds)</li>\n<li>then run \"geodesic Voronoi\"  using 2-pass chamfer / raster-scan geodesic distance transform</li>\n<li>if you still want the bridge (eg for training), look for Voronoi boundary</li>\n</ul>\n<p>example results<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Ffd068ac494af3c6ba0158713cc5d0eb5%2FSelection_1849.png?generation=1766636462105065&amp;alt=media\" alt=\"\"></p>\n<p>here is the code:\nexample input \"one_cc.npz\" is in the attachmnet of the post  </p>\n<p>note: there is a differentiable pytorch version, by i cannot get it to work (cuda compile error)<br>\n<a href=\"https://github.com/masadcv/FastGeodis\" target=\"_blank\">https://github.com/masadcv/FastGeodis</a>  </p>\n<p>if you can get it to work, it means that</p>\n<ul>\n<li>your unet takes in seed as input</li>\n<li>and produce fg logit (probability) such that  there is a path (26-connectivity) from fg voxel to the respective seed in 3d, backproagate by \"Voronoi loss (aka Reachability loss) - use softmax or max diffusion or learn geodesic distance transform\"</li>\n<li><strong>it is training dijkstra3d for free</strong></li>\n</ul>\n<pre><code>#geodesic Voronoi\n\nimport numpy as np\nimport math\n\n\nimport pyvista as pv\nfrom matplotlib.colors import ListedColormap\n\ndef show_in_3d(data, is_label=True,  cmap=\"tab20\", title=None):\n    vol = data.astype(np.float32)  # image  #mask  predict overlay_3d\n    vol = vol / vol.max()  # scale to [0, 1]\n    #vol = vol ** 1.15\n\n    # Wrap as a pyvista UniformGrid\n    grid = pv.wrap(vol)\n\n    # ----------------------------------------------------------\n    # 3. Wrap volume as PyVista grid and attach label scalars\n    if is_label:\n        labels = data.astype(np.int32)\n        grid[\"labels\"] = labels.flatten(order=\"F\")  # PyVista expects Fortran order\n    else:\n        pass\n\n    if cmap=='my_color':\n        my_color = np.full((256,4),fill_value=64,dtype=np.uint8)\n        my_color[0]   = [  0,   0,   0, 255]\n        my_color[1]   = [255, 255,   0, 255]\n        my_color[2]   = [255,   0, 255, 255]\n        my_color[3]   = [  0, 255, 255, 255]\n        my_color[4]   = [  0,   0, 255, 255]\n        my_color[5]   = [  0, 255,   0, 255]\n        my_color[6]   = [  0,   0,   0, 255]\n        my_color[254] = [  0,   0, 255, 255]\n        my_color[255] = [255,   0,   0, 255]\n        cmap = ListedColormap(my_color/255)\n\n    opacity = np.ones(255) * 0.5\n    opacity[0] = 0.0\n    opacity[254] = 1.0\n\n    # surface = grid.extract_surface()  # marching cubes surface\n    # surface[\"label\"] = surface.point_data[\"values\"]\n\n    # ----------------------------------------------------------\n    plotter = pv.Plotter(title=title)\n    if is_label:\n        plotter.add_volume(\n            grid,\n            shade=True,\n            opacity=opacity,\n            scalars=\"labels\",\n            cmap=cmap,  # good for categorical colors\n        )\n    else:\n        #not implemented\n        pass\n    plotter.show()\n\n\n#############################################################\ndef chamfer_geodesic_voronoi_3d(mask, seeds):\n    \"\"\"\n    mask: 3D bool array, True = allowed/foreground, False = blocked\n    seeds: list of (z,y,x) integer coords (must lie in mask)\n\n    Returns:\n      dist: float32 array, inf outside mask\n      lab : int32 array, -1 outside mask, otherwise index of nearest seed\n    \"\"\"\n    D, H, W = mask.shape\n    INF = np.float32(1e9)\n\n    dist = np.full((D, H, W), INF, dtype=np.float32)\n    lab  = np.full((D, H, W), -1,  dtype=np.int32)\n\n    # init seeds\n    for i, (z, y, x) in enumerate(seeds):\n        if not mask[z, y, x]:\n            raise ValueError(f\"Seed {i} at {(z,y,x)} is outside mask.\")\n        dist[z, y, x] = 0.0\n        lab[z, y, x] = i\n\n    # 26-neighborhood offsets + chamfer costs\n    # axial: 1, face-diagonal: sqrt(2), body-diagonal: sqrt(3)\n    nbrs = []\n    for dz in (-1, 0, 1):\n        for dy in (-1, 0, 1):\n            for dx in (-1, 0, 1):\n                if dz == 0 and dy == 0 and dx == 0:\n                    continue\n                k = abs(dz) + abs(dy) + abs(dx)\n                if k == 1:\n                    w = 1.0\n                elif k == 2:\n                    w = math.sqrt(2.0)\n                else:\n                    w = math.sqrt(3.0)\n                nbrs.append((dz, dy, dx, np.float32(w)))\n\n    # Split into forward/backward neighbor sets based on scan order.\n    # Forward scan order: z increasing, then y, then x.\n    # So \"already visited\" neighbors are those lexicographically smaller:\n    # (dz &lt; 0) or (dz==0 and dy &lt; 0) or (dz==0 and dy==0 and dx &lt; 0)\n    fwd = [(dz,dy,dx,w) for (dz,dy,dx,w) in nbrs\n           if (dz &lt; 0) or (dz == 0 and dy &lt; 0) or (dz == 0 and dy == 0 and dx &lt; 0)]\n    bwd = [(dz,dy,dx,w) for (dz,dy,dx,w) in nbrs\n           if (dz &gt; 0) or (dz == 0 and dy &gt; 0) or (dz == 0 and dy == 0 and dx &gt; 0)]\n\n    # One relaxation pass\n    def relax(scan_z, scan_y, scan_x, neigh_list):\n        for z in scan_z:\n            for y in scan_y:\n                for x in scan_x:\n                    if not mask[z, y, x]:\n                        continue\n                    best_d = dist[z, y, x]\n                    best_l = lab[z, y, x]\n\n                    for dz, dy, dx, w in neigh_list:\n                        zz, yy, xx = z + dz, y + dy, x + dx\n                        if 0 &lt;= zz &lt; D and 0 &lt;= yy &lt; H and 0 &lt;= xx &lt; W and mask[zz, yy, xx]:\n                            cand = dist[zz, yy, xx] + w\n                            if cand &lt; best_d:\n                                best_d = cand\n                                best_l = lab[zz, yy, xx]\n\n                    dist[z, y, x] = best_d\n                    lab[z, y, x]  = best_l\n\n    # Two-pass chamfer propagation\n    relax(range(D), range(H), range(W), fwd)                 # forward pass\n    relax(range(D-1, -1, -1), range(H-1, -1, -1), range(W-1, -1, -1), bwd)  # backward pass\n\n    return dist, lab\n\n\n# --- tiny demo ---\nif __name__ == \"__main__\":\n\n    #dummy for test\n    #mask = np.zeros((160, 160, 160), dtype=bool)\n    #mask[:, 1:5, 1:6] = True  # simple block component\n    #seeds = [(0, 1, 1), (4, 4, 5)]\n\n    mask =np.load('one_cc.npz')['arr_0']\n    #show_in_3d(mask, title='mask', cmap='my_color')\n\n    sz, sy, sx = 0, 4, 46\n    ez, ey, ex = 0, 22, 53\n    seeds =[(sz, sy, sx),(ez, ey, ex)]\n    #check\n    print (mask[seeds[0]])\n    print (mask[seeds[1]])\n\n    dist, lab = chamfer_geodesic_voronoi_3d(mask, seeds)\n    print(np.unique(lab, return_counts=True))\n    lab = lab+1\n\n    show_in_3d(lab, title='lab', cmap='my_color')\n</code></pre>",
      "rawMarkdown": "very good news!\n\nthere is even a faster method usig the concept \"geodesic Voronoi\"  \n- sepeate sheets in less than few sec on cpu\n- can run in gpu\n- given a 3d connected component, use ray test to identify different sheet. select one pixel on each sheet as seed. ( or strategically select a set of seeds)\n- then run \"geodesic Voronoi\"  using 2-pass chamfer / raster-scan geodesic distance transform\n- if you still want the bridge (eg for training), look for Voronoi boundary\n\nexample results![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Ffd068ac494af3c6ba0158713cc5d0eb5%2FSelection_1849.png?generation=1766636462105065&alt=media)\n\nhere is the code:\nexample input \"one_cc.npz\" is in the attachmnet of the post  \n\nnote: there is a differentiable pytorch version, by i cannot get it to work (cuda compile error)  \nhttps://github.com/masadcv/FastGeodis  \n\nif you can get it to work, it means that\n- your unet takes in seed as input\n- and produce fg logit (probability) such that  there is a path (26-connectivity) from fg voxel to the respective seed in 3d, backproagate by \"Voronoi loss (aka Reachability loss) - use softmax or max diffusion or learn geodesic distance transform\"\n- **it is training dijkstra3d for free**\n\n\n```\n#geodesic Voronoi\n\nimport numpy as np\nimport math\n\n\nimport pyvista as pv\nfrom matplotlib.colors import ListedColormap\n\ndef show_in_3d(data, is_label=True,  cmap=\"tab20\", title=None):\n    vol = data.astype(np.float32)  # image  #mask  predict overlay_3d\n    vol = vol / vol.max()  # scale to [0, 1]\n    #vol = vol ** 1.15\n\n    # Wrap as a pyvista UniformGrid\n    grid = pv.wrap(vol)\n\n    # ----------------------------------------------------------\n    # 3. Wrap volume as PyVista grid and attach label scalars\n    if is_label:\n        labels = data.astype(np.int32)\n        grid[\"labels\"] = labels.flatten(order=\"F\")  # PyVista expects Fortran order\n    else:\n        pass\n\n    if cmap=='my_color':\n        my_color = np.full((256,4),fill_value=64,dtype=np.uint8)\n        my_color[0]   = [  0,   0,   0, 255]\n        my_color[1]   = [255, 255,   0, 255]\n        my_color[2]   = [255,   0, 255, 255]\n        my_color[3]   = [  0, 255, 255, 255]\n        my_color[4]   = [  0,   0, 255, 255]\n        my_color[5]   = [  0, 255,   0, 255]\n        my_color[6]   = [  0,   0,   0, 255]\n        my_color[254] = [  0,   0, 255, 255]\n        my_color[255] = [255,   0,   0, 255]\n        cmap = ListedColormap(my_color/255)\n\n    opacity = np.ones(255) * 0.5\n    opacity[0] = 0.0\n    opacity[254] = 1.0\n\n    # surface = grid.extract_surface()  # marching cubes surface\n    # surface[\"label\"] = surface.point_data[\"values\"]\n\n    # ----------------------------------------------------------\n    plotter = pv.Plotter(title=title)\n    if is_label:\n        plotter.add_volume(\n            grid,\n            shade=True,\n            opacity=opacity,\n            scalars=\"labels\",\n            cmap=cmap,  # good for categorical colors\n        )\n    else:\n        #not implemented\n        pass\n    plotter.show()\n\n\n#############################################################\ndef chamfer_geodesic_voronoi_3d(mask, seeds):\n    \"\"\"\n    mask: 3D bool array, True = allowed/foreground, False = blocked\n    seeds: list of (z,y,x) integer coords (must lie in mask)\n\n    Returns:\n      dist: float32 array, inf outside mask\n      lab : int32 array, -1 outside mask, otherwise index of nearest seed\n    \"\"\"\n    D, H, W = mask.shape\n    INF = np.float32(1e9)\n\n    dist = np.full((D, H, W), INF, dtype=np.float32)\n    lab  = np.full((D, H, W), -1,  dtype=np.int32)\n\n    # init seeds\n    for i, (z, y, x) in enumerate(seeds):\n        if not mask[z, y, x]:\n            raise ValueError(f\"Seed {i} at {(z,y,x)} is outside mask.\")\n        dist[z, y, x] = 0.0\n        lab[z, y, x] = i\n\n    # 26-neighborhood offsets + chamfer costs\n    # axial: 1, face-diagonal: sqrt(2), body-diagonal: sqrt(3)\n    nbrs = []\n    for dz in (-1, 0, 1):\n        for dy in (-1, 0, 1):\n            for dx in (-1, 0, 1):\n                if dz == 0 and dy == 0 and dx == 0:\n                    continue\n                k = abs(dz) + abs(dy) + abs(dx)\n                if k == 1:\n                    w = 1.0\n                elif k == 2:\n                    w = math.sqrt(2.0)\n                else:\n                    w = math.sqrt(3.0)\n                nbrs.append((dz, dy, dx, np.float32(w)))\n\n    # Split into forward/backward neighbor sets based on scan order.\n    # Forward scan order: z increasing, then y, then x.\n    # So \"already visited\" neighbors are those lexicographically smaller:\n    # (dz < 0) or (dz==0 and dy < 0) or (dz==0 and dy==0 and dx < 0)\n    fwd = [(dz,dy,dx,w) for (dz,dy,dx,w) in nbrs\n           if (dz < 0) or (dz == 0 and dy < 0) or (dz == 0 and dy == 0 and dx < 0)]\n    bwd = [(dz,dy,dx,w) for (dz,dy,dx,w) in nbrs\n           if (dz > 0) or (dz == 0 and dy > 0) or (dz == 0 and dy == 0 and dx > 0)]\n\n    # One relaxation pass\n    def relax(scan_z, scan_y, scan_x, neigh_list):\n        for z in scan_z:\n            for y in scan_y:\n                for x in scan_x:\n                    if not mask[z, y, x]:\n                        continue\n                    best_d = dist[z, y, x]\n                    best_l = lab[z, y, x]\n\n                    for dz, dy, dx, w in neigh_list:\n                        zz, yy, xx = z + dz, y + dy, x + dx\n                        if 0 <= zz < D and 0 <= yy < H and 0 <= xx < W and mask[zz, yy, xx]:\n                            cand = dist[zz, yy, xx] + w\n                            if cand < best_d:\n                                best_d = cand\n                                best_l = lab[zz, yy, xx]\n\n                    dist[z, y, x] = best_d\n                    lab[z, y, x]  = best_l\n\n    # Two-pass chamfer propagation\n    relax(range(D), range(H), range(W), fwd)                 # forward pass\n    relax(range(D-1, -1, -1), range(H-1, -1, -1), range(W-1, -1, -1), bwd)  # backward pass\n\n    return dist, lab\n\n\n# --- tiny demo ---\nif __name__ == \"__main__\":\n\n    #dummy for test\n    #mask = np.zeros((160, 160, 160), dtype=bool)\n    #mask[:, 1:5, 1:6] = True  # simple block component\n    #seeds = [(0, 1, 1), (4, 4, 5)]\n\n    mask =np.load('one_cc.npz')['arr_0']\n    #show_in_3d(mask, title='mask', cmap='my_color')\n\n    sz, sy, sx = 0, 4, 46\n    ez, ey, ex = 0, 22, 53\n    seeds =[(sz, sy, sx),(ez, ey, ex)]\n    #check\n    print (mask[seeds[0]])\n    print (mask[seeds[1]])\n\n    dist, lab = chamfer_geodesic_voronoi_3d(mask, seeds)\n    print(np.unique(lab, return_counts=True))\n    lab = lab+1\n\n    show_in_3d(lab, title='lab', cmap='my_color')\n\n\n```",
      "votes": null
    },
    {
      "id": "3381632",
      "postDate": "12/25/2025 05:14:15",
      "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F817bdf72795f4d0b83472a9f4b9f5215%2FSelection_1855.png?generation=1766639653589660&amp;alt=media\" alt=\"\"></p>",
      "rawMarkdown": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F817bdf72795f4d0b83472a9f4b9f5215%2FSelection_1855.png?generation=1766639653589660&alt=media)",
      "votes": null
    },
    {
      "id": "3382733",
      "postDate": "12/28/2025 11:57:35",
      "content": "<p>Thanks, this made me think a bit.</p>\n<p>If you have constant thickness, no merging, and a known size of the artifact…maybe there is something to that.  You could estimate the number of turns of the scroll within some margin of error…yes?</p>\n<p>Yes.  If you know the <strong>outer radius</strong>, the <strong>thickness of the sheet of papyrus</strong>, and <strong>the innermost (core) radius</strong>, you can get the number of '<strong>turns of the papyrus around the core</strong>'.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F601679%2Fedc32b376efb3659a24304e07d574efa%2Fwrap_1.jpg?generation=1766939561741672&amp;alt=media\" alt=\"\"></p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F601679%2Ff3197cb8df94da647d3b2da9dcfa9830%2FChatGPT%20Image%20Dec%2028%202025%2005_32_07%20PM.png?generation=1766939574422300&amp;alt=media\" alt=\"\"></p>\n<p>Personally, I don't know any of the real variables yet for any of the scrolls, but maybe we can crowdsource the info?</p>",
      "rawMarkdown": "Thanks, this made me think a bit.\n\nIf you have constant thickness, no merging, and a known size of the artifact...maybe there is something to that.  You could estimate the number of turns of the scroll within some margin of error...yes?\n\nYes.  If you know the **outer radius**, the **thickness of the sheet of papyrus**, and **the innermost (core) radius**, you can get the number of '**turns of the papyrus around the core**'.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F601679%2Fedc32b376efb3659a24304e07d574efa%2Fwrap_1.jpg?generation=1766939561741672&alt=media)\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F601679%2Ff3197cb8df94da647d3b2da9dcfa9830%2FChatGPT%20Image%20Dec%2028%202025%2005_32_07%20PM.png?generation=1766939574422300&alt=media)\n\nPersonally, I don't know any of the real variables yet for any of the scrolls, but maybe we can crowdsource the info?",
      "votes": null
    },
    {
      "id": "3382737",
      "postDate": "12/28/2025 12:14:51",
      "content": "<p>Actually in this kaggle competition, we don't have to segment the complete papyrus scan. We just have to predict for a small cutout, so for that cube, There will be numerous sheets.</p>",
      "rawMarkdown": "Actually in this kaggle competition, we don't have to segment the complete papyrus scan. We just have to predict for a small cutout, so for that cube, There will be numerous sheets.",
      "votes": null
    },
    {
      "id": "3383226",
      "postDate": "12/29/2025 16:51:31",
      "content": "<p>How do you find all this? You haven't created an information retrieval course yet?</p>",
      "rawMarkdown": "How do you find all this? You haven't created an information retrieval course yet?",
      "votes": null
    },
    {
      "id": "3383278",
      "postDate": "12/29/2025 18:29:24",
      "content": "<p>True; so in that case, did you determine a way to estimate the sheets in each voxel?  It seems random to me, at least in isolation.</p>",
      "rawMarkdown": "True; so in that case, did you determine a way to estimate the sheets in each voxel?  It seems random to me, at least in isolation.",
      "votes": null
    },
    {
      "id": "3383292",
      "postDate": "12/29/2025 19:38:53",
      "content": "<p>I think you mean in each volume, There are multiple ways. One of them being analyze number of components in different slices across z axis, and choose via them. Or we can send rays perpendicular to the flow of sheets, which can be easily estimated via taking a slice and taking normal to flow in that slice, We then see how many sheets does the rays intersect (this method gives pretty accurate results, in worst case +-1 ). The result from both depends on the quality of your basic segmentation results.</p>",
      "rawMarkdown": "I think you mean in each volume, There are multiple ways. One of them being analyze number of components in different slices across z axis, and choose via them. Or we can send rays perpendicular to the flow of sheets, which can be easily estimated via taking a slice and taking normal to flow in that slice, We then see how many sheets does the rays intersect (this method gives pretty accurate results, in worst case +-1 ). The result from both depends on the quality of your basic segmentation results.",
      "votes": null
    },
    {
      "id": "3383465",
      "postDate": "12/30/2025 08:05:20",
      "content": "<p>Thank you for the geodesic Voronoi <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">@hengck23</a> </p>",
      "rawMarkdown": "Thank you for the geodesic Voronoi @hengck23",
      "votes": null
    },
    {
      "id": "3383497",
      "postDate": "12/30/2025 09:38:39",
      "content": "<p>Thank you for the geodesic Vorono</p>",
      "rawMarkdown": "Thank you for the geodesic Vorono",
      "votes": null
    },
    {
      "id": "3383921",
      "postDate": "12/31/2025 05:40:01",
      "content": "<p>There are many shortcuts. Eg deeply supervised to estimate fg/bg at many scales. At each scale you can add aux labels like is voxel close to neighbouring sheet or shortest distance to neighbouring sheet. This is helpful for post processing </p>",
      "rawMarkdown": "There are many shortcuts. Eg deeply supervised to estimate fg/bg at many scales. At each scale you can add aux labels like is voxel close to neighbouring sheet or shortest distance to neighbouring sheet. This is helpful for post processing",
      "votes": null
    },
    {
      "id": "3384364",
      "postDate": "01/01/2026 04:31:21",
      "content": "<p>To estimate number of sheets maybe another way is to compute the distance transform gradients for the sheets, ignore vectors with components in a particular direction and run a 3D connected components algorithm. This is however very cpu intensive. Radius of curvature of the scrolls also seem to be large enough for this to work.</p>",
      "rawMarkdown": "To estimate number of sheets maybe another way is to compute the distance transform gradients for the sheets, ignore vectors with components in a particular direction and run a 3D connected components algorithm. This is however very cpu intensive. Radius of curvature of the scrolls also seem to be large enough for this to work.",
      "votes": null
    },
    {
      "id": "3384365",
      "postDate": "01/01/2026 04:41:19",
      "content": "<p>no need to be so complicated:</p>\n<pre><code>prob = unnet(vol)\ncc = connected_componet_3d(prob&gt;0.3)\nfor label in range(1, max cc labe):\n   one_cc = cc==label #binary\n   for each slice: \n       y_edge = one_cc[z,:-1]- one_cc[z,1:]\n       #cast y rays  \n       y_edge_sum = y_edge.sum( ... sum vertical colums)\n\n    if single surface :  y_edge_sum is only zero or one\n    if two sheet:  y_edge_sum has value =2\n\n    do for all slices and build an occurrence histogram and check median count etc ...\n     cast ray in x direction too\n</code></pre>",
      "rawMarkdown": "no need to be so complicated:\n\n```\nprob = unnet(vol)\ncc = connected_componet_3d(prob>0.3)\nfor label in range(1, max cc labe):\n   one_cc = cc==label #binary\n   for each slice: \n       y_edge = one_cc[z,:-1]- one_cc[z,1:]\n       #cast y rays  \n       y_edge_sum = y_edge.sum( ... sum vertical colums)\n\n    if single surface :  y_edge_sum is only zero or one\n    if two sheet:  y_edge_sum has value =2\n   \n    do for all slices and build an occurrence histogram and check median count etc ...\n     cast ray in x direction too\n\n```",
      "votes": null
    },
    {
      "id": "3384995",
      "postDate": "01/02/2026 11:58:19",
      "content": "<p>faster version:</p>\n<pre><code>while 1:\n     find seeds from different sheets using ray casting.\n     compute geodesic paths\n     All paths jump into the air?  (i.e., because sheets are not connected,  the path runs through the air)\n     YES: break and exit\n     NO:  remove the most common path of the paths and repeat\n</code></pre>",
      "rawMarkdown": "faster version:\n```\n\n\nwhile 1:\n     find seeds from different sheets using ray casting.\n     compute geodesic paths\n     All paths jump into the air?  (i.e., because sheets are not connected,  the path runs through the air)\n     YES: break and exit\n     NO:  remove the most common path of the paths and repeat\n\n```",
      "votes": null
    },
    {
      "id": "3387271",
      "postDate": "01/06/2026 16:44:19",
      "content": "<p>Geodesic voronoi's result depends heavily on the seed selection, logically I deduced that taking seeds normal to the flow of direction of flow of sheets in some component is best way to take seeds. It still doesn't give relaible results. Example-&gt; I took random slice, and random seeds which follow the condition I said above. and following is the result</p>\n<p>SEED and COMPONENT</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2F6f4411edd891029d4b48f1fb2e8d4a74%2FScreenshot%202026-01-06%20220354.png?generation=1767717655700861&amp;alt=media\" alt=\"\"></p>\n<p>RESULT OF GEODESIC VERONOI</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2Fbfc6aece9431ef6d6c06bbb9bfa5481c%2FScreenshot%202026-01-06%20220404.png?generation=1767717711123312&amp;alt=media\" alt=\"\"></p>\n<p>This happens because seed initialization is not the only parameter here, where the sheets touch and where the holes are also an issue, because they affect the distance calculations.</p>",
      "rawMarkdown": "Geodesic voronoi's result depends heavily on the seed selection, logically I deduced that taking seeds normal to the flow of direction of flow of sheets in some component is best way to take seeds. It still doesn't give relaible results. Example-> I took random slice, and random seeds which follow the condition I said above. and following is the result\n\nSEED and COMPONENT\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2F6f4411edd891029d4b48f1fb2e8d4a74%2FScreenshot%202026-01-06%20220354.png?generation=1767717655700861&alt=media)\n\nRESULT OF GEODESIC VERONOI\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2Fbfc6aece9431ef6d6c06bbb9bfa5481c%2FScreenshot%202026-01-06%20220404.png?generation=1767717711123312&alt=media)\n\n\nThis happens because seed initialization is not the only parameter here, where the sheets touch and where the holes are also an issue, because they affect the distance calculations.",
      "votes": null
    },
    {
      "id": "3387342",
      "postDate": "01/06/2026 18:55:05",
      "content": "<p>Hi curious about how you are computing the \"flow\" direction of the sheets. Could you share?</p>",
      "rawMarkdown": "Hi curious about how you are computing the \"flow\" direction of the sheets. Could you share?",
      "votes": null
    },
    {
      "id": "3387438",
      "postDate": "01/07/2026 02:07:28",
      "content": "<p>to do heuristic, follow the steps:<br>\n1) make a way to verify results is correct or not\n(you can easily train a single curve vs multiple curves, or even count number of curve by training a slice classifier or 2.5D volume classifiere using ground truth)<br>\n2) if you are using  Geodesic voronoi's, randomly just select seeds. e.g. just 2 random seeds from 2 curves (from connected component). then use the automtic verifier above to record results. we are interested in, e.g. if we have 1000 random trials, how many % or the trials are recorded correctly by the verifier.  </p>\n<p>hence you can work out the computation cost of trial and error and guarantee success.<br>\nfor the failure case, you would hvae to restore to finding bridge using dijkstra3d.  </p>\n<p>the trick is chop your prediction into overlaps of say 64 slices for Geodesic voronoi to speed up. the touching part isminor. so most of the \"chops\" will pass the test easily</p>",
      "rawMarkdown": "to do heuristic, follow the steps:  \n1) make a way to verify results is correct or not\n(you can easily train a single curve vs multiple curves, or even count number of curve by training a slice classifier or 2.5D volume classifiere using ground truth)  \n2) if you are using  Geodesic voronoi's, randomly just select seeds. e.g. just 2 random seeds from 2 curves (from connected component). then use the automtic verifier above to record results. we are interested in, e.g. if we have 1000 random trials, how many % or the trials are recorded correctly by the verifier.  \n\nhence you can work out the computation cost of trial and error and guarantee success.  \nfor the failure case, you would hvae to restore to finding bridge using dijkstra3d.  \n\nthe trick is chop your prediction into overlaps of say 64 slices for Geodesic voronoi to speed up. the touching part isminor. so most of the \"chops\" will pass the test easily",
      "votes": null
    },
    {
      "id": "3387447",
      "postDate": "01/07/2026 03:11:24",
      "content": "<p>This is just a sample, obviously we won't mask with and all during actual inference.\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2F25a392be7138792e0539b926ae30fcd7%2FScreenshot%202026-01-07%20081326.png?generation=1767755436996236&amp;alt=media\" alt=\"\"></p>",
      "rawMarkdown": "This is just a sample, obviously we won't mask with and all during actual inference.\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2F25a392be7138792e0539b926ae30fcd7%2FScreenshot%202026-01-07%20081326.png?generation=1767755436996236&alt=media)",
      "votes": null
    },
    {
      "id": "3387448",
      "postDate": "01/07/2026 03:13:59",
      "content": "<p>Thanks, I'll think in these directions.</p>",
      "rawMarkdown": "Thanks, I'll think in these directions.",
      "votes": null
    },
    {
      "id": "3387464",
      "postDate": "01/07/2026 04:25:42",
      "content": "<p><a href=\"https://www.kaggle.com/choudharymanas\" target=\"_blank\">@choudharymanas</a> </p>\n<p>simple removal:<br>\nbefore: 3 sheets touching<br>\nafter: one sheet is removed from given points from 2 sheets   </p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Fa616532e95907c2af7e6c5ea31afd77c%2FSelection_2118.png?generation=1767759874757429&amp;alt=media\" alt=\"\"></p>\n<p>ask chatgpt for speedup code or explain the code</p>\n<pre><code>#helper\n\ndef compute_iou_1d(\n    x1, x2\n):\n    x1 = x1!=0\n    x2 = x2!=0\n    inter = (x1 &amp; x2).sum()\n    union = (x1 | x2).sum()\n    return inter / (union + 1e-6)\n\n# ray casting test\ndef do_ray_casting(\n    point1,  #yx format: Nx2\n    point2,\n    h,w\n):\n    # for y\n    by1 = np.bincount(point1[:, 0], minlength=h)\n    by2 = np.bincount(point2[:, 0], minlength=h)\n    #iouy = compute_iou_2d(by1, by2)\n    #print('iouy', iouy)\n\n    # for x\n    bx1 = np.bincount(point1[:, 1], minlength=w)\n    bx2 = np.bincount(point2[:, 1], minlength=w)\n    #ioux = compute_iou_2d(bx1, bx2)\n    #print('iou', ioux)\n\n    #we do joint test\n    iou = compute_iou_1d(\n        np.concatenate([by1, bx1]),\n        np.concatenate([by2, bx2]),\n    )\n    return iou\n\n#--------------------------------\nd,h,w = problem.shape\nz = d//2\n\nccz =  cc3d.connected_components(problem[z])\n#point yx\npoint=[None] #bg placeholder\nfor i in range(1,ccz.max()+1):\n    point.append(\n        np.stack(np.where(ccz == i)).T\n    )\niou = do_ray_casting(point[1], point[2],h,w)\nprint('ray_cast: iou', iou)\n# if iou &gt;0.1 then point[1], point[2] are on different sheet\n\n#different surface\ni1, i2 = 1,2\n\npoint1 = point[i1]\npoint2 = point[i2]\nshow_in_3d(problem, cmap='my_color', title='problem') #problem is binary cc3d\n\n#remove until shortest path is in the \"air\"\nfor trial in range(100):\n\n    path =[]\n\n    k1 = np.arange(len(point1))\n    np.random.shuffle(k1)\n    startpoint = point1[k1[:8]]\n    for (sy, sx) in startpoint:\n        parent = dijkstra3d.parental_field(\n            np.where(problem, 1.0, 1e6).astype(np.float32), source=(z, sy, sx), connectivity=26)\n\n        k2 = np.arange(len(point2))\n        np.random.shuffle(k2)\n        endppoint =  point2[k2[:8]]\n        path.extend([\n            dijkstra3d.path_from_parents(parent, (z, ey, ex))\n                     for (ey, ex) in endppoint\n        ])\n    path_flat = np.concatenate(path)\n    jump = np.any(~problem[path_flat[:,0],path_flat[:,1],path_flat[:,2]]) #brige in \"air\"/bg\n\n    if jump:\n\n        #for debug\n        if 1:\n            overlay3d =  cc3d.connected_components(problem)\n            show_in_3d(overlay3d, cmap='my_color',title='remove overlay3d')\n\n\n\n        break\n    else:\n        #remove common path and tr\n\n        uniq, cnt = np.unique(path_flat, axis=0, return_counts=True)\n        order = np.argsort(-cnt)  # descending by count\n        uniq = uniq[order]\n        cnt = cnt[order]\n        print('cnt', cnt)\n\n        #for debug\n        if trial==0:\n            overlay3d = problem.astype(np.uint8)\n            overlay3d[uniq[:,0],uniq[:,1],uniq[:,2]]= 255#cnt+1\n            show_in_3d(overlay3d, cmap='my_color',title='overlay3d')\n\n\n        #remove\n        threshold =0.8*(8*8)\n        u = uniq[cnt&gt;threshold]\n        problem[u[:, 0], u[:, 1], u[:, 2]] = False  # you can dilate for speedup\n</code></pre>",
      "rawMarkdown": "choudharymanas \n\nsimple removal:  \nbefore: 3 sheets touching     \nafter: one sheet is removed from given points from 2 sheets   \n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Fa616532e95907c2af7e6c5ea31afd77c%2FSelection_2118.png?generation=1767759874757429&alt=media)\n\n\nask chatgpt for speedup code or explain the code\n\n```\n\n#helper\n\ndef compute_iou_1d(\n    x1, x2\n):\n    x1 = x1!=0\n    x2 = x2!=0\n    inter = (x1 & x2).sum()\n    union = (x1 | x2).sum()\n    return inter / (union + 1e-6)\n\n# ray casting test\ndef do_ray_casting(\n    point1,  #yx format: Nx2\n    point2,\n    h,w\n):\n    # for y\n    by1 = np.bincount(point1[:, 0], minlength=h)\n    by2 = np.bincount(point2[:, 0], minlength=h)\n    #iouy = compute_iou_2d(by1, by2)\n    #print('iouy', iouy)\n\n    # for x\n    bx1 = np.bincount(point1[:, 1], minlength=w)\n    bx2 = np.bincount(point2[:, 1], minlength=w)\n    #ioux = compute_iou_2d(bx1, bx2)\n    #print('iou', ioux)\n\n    #we do joint test\n    iou = compute_iou_1d(\n        np.concatenate([by1, bx1]),\n        np.concatenate([by2, bx2]),\n    )\n    return iou\n\n#--------------------------------\nd,h,w = problem.shape\nz = d//2\n\nccz =  cc3d.connected_components(problem[z])\n#point yx\npoint=[None] #bg placeholder\nfor i in range(1,ccz.max()+1):\n    point.append(\n        np.stack(np.where(ccz == i)).T\n    )\niou = do_ray_casting(point[1], point[2],h,w)\nprint('ray_cast: iou', iou)\n# if iou >0.1 then point[1], point[2] are on different sheet\n\n#different surface\ni1, i2 = 1,2\n\npoint1 = point[i1]\npoint2 = point[i2]\nshow_in_3d(problem, cmap='my_color', title='problem') #problem is binary cc3d\n\n#remove until shortest path is in the \"air\"\nfor trial in range(100):\n\n    path =[]\n\n    k1 = np.arange(len(point1))\n    np.random.shuffle(k1)\n    startpoint = point1[k1[:8]]\n    for (sy, sx) in startpoint:\n        parent = dijkstra3d.parental_field(\n            np.where(problem, 1.0, 1e6).astype(np.float32), source=(z, sy, sx), connectivity=26)\n\n        k2 = np.arange(len(point2))\n        np.random.shuffle(k2)\n        endppoint =  point2[k2[:8]]\n        path.extend([\n            dijkstra3d.path_from_parents(parent, (z, ey, ex))\n                     for (ey, ex) in endppoint\n        ])\n    path_flat = np.concatenate(path)\n    jump = np.any(~problem[path_flat[:,0],path_flat[:,1],path_flat[:,2]]) #brige in \"air\"/bg\n\n    if jump:\n\n        #for debug\n        if 1:\n            overlay3d =  cc3d.connected_components(problem)\n            show_in_3d(overlay3d, cmap='my_color',title='remove overlay3d')\n\n\n\n        break\n    else:\n        #remove common path and tr\n  \n        uniq, cnt = np.unique(path_flat, axis=0, return_counts=True)\n        order = np.argsort(-cnt)  # descending by count\n        uniq = uniq[order]\n        cnt = cnt[order]\n        print('cnt', cnt)\n\n        #for debug\n        if trial==0:\n            overlay3d = problem.astype(np.uint8)\n            overlay3d[uniq[:,0],uniq[:,1],uniq[:,2]]= 255#cnt+1\n            show_in_3d(overlay3d, cmap='my_color',title='overlay3d')\n\n\n        #remove\n        threshold =0.8*(8*8)\n        u = uniq[cnt>threshold]\n        problem[u[:, 0], u[:, 1], u[:, 2]] = False  # you can dilate for speedup\n\n\n```",
      "votes": null
    },
    {
      "id": "3387621",
      "postDate": "01/07/2026 11:35:43",
      "content": "<p>CODE is up!!!!!\n<a href=\"https://www.kaggle.com/code/hengck23/placeholder-killer-ant-post-processing\" target=\"_blank\">https://www.kaggle.com/code/hengck23/placeholder-killer-ant-post-processing</a></p>",
      "rawMarkdown": "CODE is up!!!!!\nhttps://www.kaggle.com/code/hengck23/placeholder-killer-ant-post-processing",
      "votes": null
    },
    {
      "id": "3387935",
      "postDate": "01/07/2026 20:28:14",
      "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F068c706d8fa95717d2beb128173636d4%2FSelection_2157.png?generation=1767817693048292&amp;alt=media\" alt=\"\"></p>\n<p>there is a trick one can try<br>\nA) given a set of ground truth labels<br>\nB) given another set of predicted labels (use different threshold)  </p>\n<p>note there are a few things you can do:<br>\n1) train a classifier to classify A and B. then the class activation map CAM may tell you the holes and bridges<br>\n2) train an auto encoder on A only. if you apply to B maybe you can remove the bridges and filled the holes<br>\n3) train a probablistic  denoise diffusion model DPPM : noise= (B) with different probaility  threshold, clean= A. then maybe you can clean up as post processing.<br>\n4) paired translator: a unet to mapp any thresholded labels to the best thresholded ones.(or best results assembed  from different thresholds as holes are filled and bridges disappears) </p>",
      "rawMarkdown": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F068c706d8fa95717d2beb128173636d4%2FSelection_2157.png?generation=1767817693048292&alt=media)\n\nthere is a trick one can try  \nA) given a set of ground truth labels  \nB) given another set of predicted labels (use different threshold)  \n\nnote there are a few things you can do:  \n1) train a classifier to classify A and B. then the class activation map CAM may tell you the holes and bridges  \n2) train an auto encoder on A only. if you apply to B maybe you can remove the bridges and filled the holes  \n3) train a probablistic  denoise diffusion model DPPM : noise= (B) with different probaility  threshold, clean= A. then maybe you can clean up as post processing.  \n4) paired translator: a unet to mapp any thresholded labels to the best thresholded ones.(or best results assembed  from different thresholds as holes are filled and bridges disappears)",
      "votes": null
    },
    {
      "id": "3387942",
      "postDate": "01/07/2026 20:42:58",
      "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Fe216f3103b5081adaae63a8823dda965%2FSelection_2160.png?generation=1767818576903970&amp;alt=media\" alt=\"\"></p>",
      "rawMarkdown": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Fe216f3103b5081adaae63a8823dda965%2FSelection_2160.png?generation=1767818576903970&alt=media)",
      "votes": null
    },
    {
      "id": "3387949",
      "postDate": "01/07/2026 21:26:20",
      "content": "<p>i really like the autoencoder idea, its something i've thought about for a bit. i tried a few similar things to your notes here . but never explored them past an initial \"huh this is kinda cool\" stage, to verify whether they worked or not </p>\n<ul>\n<li>i trained a classifier on \"missing sheets\" and also \"merges/holes\" , and used a grad cam , which showed some relatively interesting results as far as what the model was seeing</li>\n<li>i started to (but never finished) training an autoencoder to compress + reconstruct from this a GT label, to create an \"embedding\" model , which you could then use in a loss function, essentially saying embedding at pred and embedding at GT should be the same -- this seemed interesting in that you get all these topo metrics \"for free\"</li>\n</ul>",
      "rawMarkdown": "i really like the autoencoder idea, its something i've thought about for a bit. i tried a few similar things to your notes here . but never explored them past an initial \"huh this is kinda cool\" stage, to verify whether they worked or not \n\n- i trained a classifier on \"missing sheets\" and also \"merges/holes\" , and used a grad cam , which showed some relatively interesting results as far as what the model was seeing\n- i started to (but never finished) training an autoencoder to compress + reconstruct from this a GT label, to create an \"embedding\" model , which you could then use in a loss function, essentially saying embedding at pred and embedding at GT should be the same -- this seemed interesting in that you get all these topo metrics \"for free\"",
      "votes": null
    },
    {
      "id": "3388054",
      "postDate": "01/08/2026 04:25:41",
      "content": "<p><a href=\"https://www.kaggle.com/seanjohnsonsp\" target=\"_blank\">@seanjohnsonsp</a> This can be viewed as a reconstruction-based one-class anomaly detection approach. You basically don't need to label holes or missing part, just can guide everything from reconstruction. Below, I list several works applied to industrial images, and I strongly believe these approaches could be extended to this problem. Unfortunately, I am unable to work on it due to the limited time remaining before the deadline.</p>\n<ul>\n<li><a href=\"https://www.sciencedirect.com/science/article/abs/pii/S0893608021004810\" target=\"_blank\">UTRAD: Anomaly detection and localization with U-Transformer</a></li>\n<li><a href=\"https://openaccess.thecvf.com/content/CVPR2022/papers/Deng_Anomaly_Detection_via_Reverse_Distillation_From_One-Class_Embedding_CVPR_2022_paper.pdf\" target=\"_blank\">Anomaly Detection via Reverse Distillation from One-Class Embedding</a></li>\n<li><a href=\"https://openaccess.thecvf.com/content/CVPR2023/html/Tien_Revisiting_Reverse_Distillation_for_Anomaly_Detection_CVPR_2023_paper.html\" target=\"_blank\">Revisiting Reverse Distillation for Anomaly Detection</a></li>\n</ul>",
      "rawMarkdown": "seanjohnsonsp This can be viewed as a reconstruction-based one-class anomaly detection approach. You basically don't need to label holes or missing part, just can guide everything from reconstruction. Below, I list several works applied to industrial images, and I strongly believe these approaches could be extended to this problem. Unfortunately, I am unable to work on it due to the limited time remaining before the deadline.\n\n* [UTRAD: Anomaly detection and localization with U-Transformer](https://www.sciencedirect.com/science/article/abs/pii/S0893608021004810)\n* [Anomaly Detection via Reverse Distillation from One-Class Embedding](https://openaccess.thecvf.com/content/CVPR2022/papers/Deng_Anomaly_Detection_via_Reverse_Distillation_From_One-Class_Embedding_CVPR_2022_paper.pdf)\n* [Revisiting Reverse Distillation for Anomaly Detection](https://openaccess.thecvf.com/content/CVPR2023/html/Tien_Revisiting_Reverse_Distillation_for_Anomaly_Detection_CVPR_2023_paper.html)",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 3381213,
      "author_name": "hengck23",
      "author_url": "",
      "post_date": "12/24/2025 03:18:26",
      "content": "<p><a href=\"https://github.com/seung-lab/dijkstra3d\" target=\"_blank\">https://github.com/seung-lab/dijkstra3d</a><br>\nDijkstra's Shortest Path variants for 6, 18, and 26-connected 3D Image Volumes or 4 and 8-connected 2D images.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F306d4c1614264b669c039ccfda42dbab%2FSelection_1834.png?generation=1766546304697280&amp;alt=media\" alt=\"\"></p>",
      "votes": null,
      "replies": [
        {
          "id": 3381272,
          "author_name": "hengck23",
          "author_url": "",
          "post_date": "12/24/2025 06:12:29",
          "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F360e9f03a725f68fa606cd198e28c82b%2FSelection_1845.png?generation=1766556987884686&amp;alt=media\" alt=\"\"></p>\n<p>\n\nuse  \"geodesic Voronoi\" above instead</p>",
          "votes": null,
          "replies": [
            {
              "id": 3384995,
              "author_name": "hengck23",
              "author_url": "",
              "post_date": "01/02/2026 11:58:19",
              "content": "<p>faster version:</p>\n<pre><code>while 1:\n     find seeds from different sheets using ray casting.\n     compute geodesic paths\n     All paths jump into the air?  (i.e., because sheets are not connected,  the path runs through the air)\n     YES: break and exit\n     NO:  remove the most common path of the paths and repeat\n</code></pre>",
              "votes": null,
              "replies": []
            }
          ]
        }
      ]
    },
    {
      "id": 3381486,
      "author_name": "choudharymanas",
      "author_url": "",
      "post_date": "12/24/2025 18:07:15",
      "content": "<p>hmm, very simply method to estimate number of sheets. I had thought about it but didn't get an appropriate opportunity to try it out, but i feel knowing number of sheets can be very helpful. After that we can assume that many 2-4 voxel thick continuous sheets and bend them via some method to fit them according to the input, I feel this is the only way to use all the three important priors we have (continuous sheets, no merging, and nearly constant thickness) .</p>",
      "votes": null,
      "replies": [
        {
          "id": 3382733,
          "author_name": "travis422",
          "author_url": "",
          "post_date": "12/28/2025 11:57:35",
          "content": "<p>Thanks, this made me think a bit.</p>\n<p>If you have constant thickness, no merging, and a known size of the artifact…maybe there is something to that.  You could estimate the number of turns of the scroll within some margin of error…yes?</p>\n<p>Yes.  If you know the <strong>outer radius</strong>, the <strong>thickness of the sheet of papyrus</strong>, and <strong>the innermost (core) radius</strong>, you can get the number of '<strong>turns of the papyrus around the core</strong>'.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F601679%2Fedc32b376efb3659a24304e07d574efa%2Fwrap_1.jpg?generation=1766939561741672&amp;alt=media\" alt=\"\"></p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F601679%2Ff3197cb8df94da647d3b2da9dcfa9830%2FChatGPT%20Image%20Dec%2028%202025%2005_32_07%20PM.png?generation=1766939574422300&amp;alt=media\" alt=\"\"></p>\n<p>Personally, I don't know any of the real variables yet for any of the scrolls, but maybe we can crowdsource the info?</p>",
          "votes": null,
          "replies": [
            {
              "id": 3382737,
              "author_name": "choudharymanas",
              "author_url": "",
              "post_date": "12/28/2025 12:14:51",
              "content": "<p>Actually in this kaggle competition, we don't have to segment the complete papyrus scan. We just have to predict for a small cutout, so for that cube, There will be numerous sheets.</p>",
              "votes": null,
              "replies": [
                {
                  "id": 3383278,
                  "author_name": "travis422",
                  "author_url": "",
                  "post_date": "12/29/2025 18:29:24",
                  "content": "<p>True; so in that case, did you determine a way to estimate the sheets in each voxel?  It seems random to me, at least in isolation.</p>",
                  "votes": null,
                  "replies": [
                    {
                      "id": 3383292,
                      "author_name": "choudharymanas",
                      "author_url": "",
                      "post_date": "12/29/2025 19:38:53",
                      "content": "<p>I think you mean in each volume, There are multiple ways. One of them being analyze number of components in different slices across z axis, and choose via them. Or we can send rays perpendicular to the flow of sheets, which can be easily estimated via taking a slice and taking normal to flow in that slice, We then see how many sheets does the rays intersect (this method gives pretty accurate results, in worst case +-1 ). The result from both depends on the quality of your basic segmentation results.</p>",
                      "votes": null,
                      "replies": []
                    }
                  ]
                }
              ]
            }
          ]
        },
        {
          "id": 3384364,
          "author_name": "arjunashokbhandary",
          "author_url": "",
          "post_date": "01/01/2026 04:31:21",
          "content": "<p>To estimate number of sheets maybe another way is to compute the distance transform gradients for the sheets, ignore vectors with components in a particular direction and run a 3D connected components algorithm. This is however very cpu intensive. Radius of curvature of the scrolls also seem to be large enough for this to work.</p>",
          "votes": null,
          "replies": [
            {
              "id": 3384365,
              "author_name": "hengck23",
              "author_url": "",
              "post_date": "01/01/2026 04:41:19",
              "content": "<p>no need to be so complicated:</p>\n<pre><code>prob = unnet(vol)\ncc = connected_componet_3d(prob&gt;0.3)\nfor label in range(1, max cc labe):\n   one_cc = cc==label #binary\n   for each slice: \n       y_edge = one_cc[z,:-1]- one_cc[z,1:]\n       #cast y rays  \n       y_edge_sum = y_edge.sum( ... sum vertical colums)\n\n    if single surface :  y_edge_sum is only zero or one\n    if two sheet:  y_edge_sum has value =2\n\n    do for all slices and build an occurrence histogram and check median count etc ...\n     cast ray in x direction too\n</code></pre>",
              "votes": null,
              "replies": []
            }
          ]
        }
      ]
    },
    {
      "id": 3381626,
      "author_name": "hengck23",
      "author_url": "",
      "post_date": "12/25/2025 04:27:04",
      "content": "<p>very good news!</p>\n<p>there is even a faster method usig the concept \"geodesic Voronoi\"  </p>\n<ul>\n<li>sepeate sheets in less than few sec on cpu</li>\n<li>can run in gpu</li>\n<li>given a 3d connected component, use ray test to identify different sheet. select one pixel on each sheet as seed. ( or strategically select a set of seeds)</li>\n<li>then run \"geodesic Voronoi\"  using 2-pass chamfer / raster-scan geodesic distance transform</li>\n<li>if you still want the bridge (eg for training), look for Voronoi boundary</li>\n</ul>\n<p>example results<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Ffd068ac494af3c6ba0158713cc5d0eb5%2FSelection_1849.png?generation=1766636462105065&amp;alt=media\" alt=\"\"></p>\n<p>here is the code:\nexample input \"one_cc.npz\" is in the attachmnet of the post  </p>\n<p>note: there is a differentiable pytorch version, by i cannot get it to work (cuda compile error)<br>\n<a href=\"https://github.com/masadcv/FastGeodis\" target=\"_blank\">https://github.com/masadcv/FastGeodis</a>  </p>\n<p>if you can get it to work, it means that</p>\n<ul>\n<li>your unet takes in seed as input</li>\n<li>and produce fg logit (probability) such that  there is a path (26-connectivity) from fg voxel to the respective seed in 3d, backproagate by \"Voronoi loss (aka Reachability loss) - use softmax or max diffusion or learn geodesic distance transform\"</li>\n<li><strong>it is training dijkstra3d for free</strong></li>\n</ul>\n<pre><code>#geodesic Voronoi\n\nimport numpy as np\nimport math\n\n\nimport pyvista as pv\nfrom matplotlib.colors import ListedColormap\n\ndef show_in_3d(data, is_label=True,  cmap=\"tab20\", title=None):\n    vol = data.astype(np.float32)  # image  #mask  predict overlay_3d\n    vol = vol / vol.max()  # scale to [0, 1]\n    #vol = vol ** 1.15\n\n    # Wrap as a pyvista UniformGrid\n    grid = pv.wrap(vol)\n\n    # ----------------------------------------------------------\n    # 3. Wrap volume as PyVista grid and attach label scalars\n    if is_label:\n        labels = data.astype(np.int32)\n        grid[\"labels\"] = labels.flatten(order=\"F\")  # PyVista expects Fortran order\n    else:\n        pass\n\n    if cmap=='my_color':\n        my_color = np.full((256,4),fill_value=64,dtype=np.uint8)\n        my_color[0]   = [  0,   0,   0, 255]\n        my_color[1]   = [255, 255,   0, 255]\n        my_color[2]   = [255,   0, 255, 255]\n        my_color[3]   = [  0, 255, 255, 255]\n        my_color[4]   = [  0,   0, 255, 255]\n        my_color[5]   = [  0, 255,   0, 255]\n        my_color[6]   = [  0,   0,   0, 255]\n        my_color[254] = [  0,   0, 255, 255]\n        my_color[255] = [255,   0,   0, 255]\n        cmap = ListedColormap(my_color/255)\n\n    opacity = np.ones(255) * 0.5\n    opacity[0] = 0.0\n    opacity[254] = 1.0\n\n    # surface = grid.extract_surface()  # marching cubes surface\n    # surface[\"label\"] = surface.point_data[\"values\"]\n\n    # ----------------------------------------------------------\n    plotter = pv.Plotter(title=title)\n    if is_label:\n        plotter.add_volume(\n            grid,\n            shade=True,\n            opacity=opacity,\n            scalars=\"labels\",\n            cmap=cmap,  # good for categorical colors\n        )\n    else:\n        #not implemented\n        pass\n    plotter.show()\n\n\n#############################################################\ndef chamfer_geodesic_voronoi_3d(mask, seeds):\n    \"\"\"\n    mask: 3D bool array, True = allowed/foreground, False = blocked\n    seeds: list of (z,y,x) integer coords (must lie in mask)\n\n    Returns:\n      dist: float32 array, inf outside mask\n      lab : int32 array, -1 outside mask, otherwise index of nearest seed\n    \"\"\"\n    D, H, W = mask.shape\n    INF = np.float32(1e9)\n\n    dist = np.full((D, H, W), INF, dtype=np.float32)\n    lab  = np.full((D, H, W), -1,  dtype=np.int32)\n\n    # init seeds\n    for i, (z, y, x) in enumerate(seeds):\n        if not mask[z, y, x]:\n            raise ValueError(f\"Seed {i} at {(z,y,x)} is outside mask.\")\n        dist[z, y, x] = 0.0\n        lab[z, y, x] = i\n\n    # 26-neighborhood offsets + chamfer costs\n    # axial: 1, face-diagonal: sqrt(2), body-diagonal: sqrt(3)\n    nbrs = []\n    for dz in (-1, 0, 1):\n        for dy in (-1, 0, 1):\n            for dx in (-1, 0, 1):\n                if dz == 0 and dy == 0 and dx == 0:\n                    continue\n                k = abs(dz) + abs(dy) + abs(dx)\n                if k == 1:\n                    w = 1.0\n                elif k == 2:\n                    w = math.sqrt(2.0)\n                else:\n                    w = math.sqrt(3.0)\n                nbrs.append((dz, dy, dx, np.float32(w)))\n\n    # Split into forward/backward neighbor sets based on scan order.\n    # Forward scan order: z increasing, then y, then x.\n    # So \"already visited\" neighbors are those lexicographically smaller:\n    # (dz &lt; 0) or (dz==0 and dy &lt; 0) or (dz==0 and dy==0 and dx &lt; 0)\n    fwd = [(dz,dy,dx,w) for (dz,dy,dx,w) in nbrs\n           if (dz &lt; 0) or (dz == 0 and dy &lt; 0) or (dz == 0 and dy == 0 and dx &lt; 0)]\n    bwd = [(dz,dy,dx,w) for (dz,dy,dx,w) in nbrs\n           if (dz &gt; 0) or (dz == 0 and dy &gt; 0) or (dz == 0 and dy == 0 and dx &gt; 0)]\n\n    # One relaxation pass\n    def relax(scan_z, scan_y, scan_x, neigh_list):\n        for z in scan_z:\n            for y in scan_y:\n                for x in scan_x:\n                    if not mask[z, y, x]:\n                        continue\n                    best_d = dist[z, y, x]\n                    best_l = lab[z, y, x]\n\n                    for dz, dy, dx, w in neigh_list:\n                        zz, yy, xx = z + dz, y + dy, x + dx\n                        if 0 &lt;= zz &lt; D and 0 &lt;= yy &lt; H and 0 &lt;= xx &lt; W and mask[zz, yy, xx]:\n                            cand = dist[zz, yy, xx] + w\n                            if cand &lt; best_d:\n                                best_d = cand\n                                best_l = lab[zz, yy, xx]\n\n                    dist[z, y, x] = best_d\n                    lab[z, y, x]  = best_l\n\n    # Two-pass chamfer propagation\n    relax(range(D), range(H), range(W), fwd)                 # forward pass\n    relax(range(D-1, -1, -1), range(H-1, -1, -1), range(W-1, -1, -1), bwd)  # backward pass\n\n    return dist, lab\n\n\n# --- tiny demo ---\nif __name__ == \"__main__\":\n\n    #dummy for test\n    #mask = np.zeros((160, 160, 160), dtype=bool)\n    #mask[:, 1:5, 1:6] = True  # simple block component\n    #seeds = [(0, 1, 1), (4, 4, 5)]\n\n    mask =np.load('one_cc.npz')['arr_0']\n    #show_in_3d(mask, title='mask', cmap='my_color')\n\n    sz, sy, sx = 0, 4, 46\n    ez, ey, ex = 0, 22, 53\n    seeds =[(sz, sy, sx),(ez, ey, ex)]\n    #check\n    print (mask[seeds[0]])\n    print (mask[seeds[1]])\n\n    dist, lab = chamfer_geodesic_voronoi_3d(mask, seeds)\n    print(np.unique(lab, return_counts=True))\n    lab = lab+1\n\n    show_in_3d(lab, title='lab', cmap='my_color')\n</code></pre>",
      "votes": null,
      "replies": [
        {
          "id": 3381632,
          "author_name": "hengck23",
          "author_url": "",
          "post_date": "12/25/2025 05:14:15",
          "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F817bdf72795f4d0b83472a9f4b9f5215%2FSelection_1855.png?generation=1766639653589660&amp;alt=media\" alt=\"\"></p>",
          "votes": null,
          "replies": []
        },
        {
          "id": 3387271,
          "author_name": "choudharymanas",
          "author_url": "",
          "post_date": "01/06/2026 16:44:19",
          "content": "<p>Geodesic voronoi's result depends heavily on the seed selection, logically I deduced that taking seeds normal to the flow of direction of flow of sheets in some component is best way to take seeds. It still doesn't give relaible results. Example-&gt; I took random slice, and random seeds which follow the condition I said above. and following is the result</p>\n<p>SEED and COMPONENT</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2F6f4411edd891029d4b48f1fb2e8d4a74%2FScreenshot%202026-01-06%20220354.png?generation=1767717655700861&amp;alt=media\" alt=\"\"></p>\n<p>RESULT OF GEODESIC VERONOI</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2Fbfc6aece9431ef6d6c06bbb9bfa5481c%2FScreenshot%202026-01-06%20220404.png?generation=1767717711123312&amp;alt=media\" alt=\"\"></p>\n<p>This happens because seed initialization is not the only parameter here, where the sheets touch and where the holes are also an issue, because they affect the distance calculations.</p>",
          "votes": null,
          "replies": [
            {
              "id": 3387342,
              "author_name": "arjunashokbhandary",
              "author_url": "",
              "post_date": "01/06/2026 18:55:05",
              "content": "<p>Hi curious about how you are computing the \"flow\" direction of the sheets. Could you share?</p>",
              "votes": null,
              "replies": [
                {
                  "id": 3387447,
                  "author_name": "choudharymanas",
                  "author_url": "",
                  "post_date": "01/07/2026 03:11:24",
                  "content": "<p>This is just a sample, obviously we won't mask with and all during actual inference.\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2F25a392be7138792e0539b926ae30fcd7%2FScreenshot%202026-01-07%20081326.png?generation=1767755436996236&amp;alt=media\" alt=\"\"></p>",
                  "votes": null,
                  "replies": []
                }
              ]
            },
            {
              "id": 3387438,
              "author_name": "hengck23",
              "author_url": "",
              "post_date": "01/07/2026 02:07:28",
              "content": "<p>to do heuristic, follow the steps:<br>\n1) make a way to verify results is correct or not\n(you can easily train a single curve vs multiple curves, or even count number of curve by training a slice classifier or 2.5D volume classifiere using ground truth)<br>\n2) if you are using  Geodesic voronoi's, randomly just select seeds. e.g. just 2 random seeds from 2 curves (from connected component). then use the automtic verifier above to record results. we are interested in, e.g. if we have 1000 random trials, how many % or the trials are recorded correctly by the verifier.  </p>\n<p>hence you can work out the computation cost of trial and error and guarantee success.<br>\nfor the failure case, you would hvae to restore to finding bridge using dijkstra3d.  </p>\n<p>the trick is chop your prediction into overlaps of say 64 slices for Geodesic voronoi to speed up. the touching part isminor. so most of the \"chops\" will pass the test easily</p>",
              "votes": null,
              "replies": [
                {
                  "id": 3387448,
                  "author_name": "choudharymanas",
                  "author_url": "",
                  "post_date": "01/07/2026 03:13:59",
                  "content": "<p>Thanks, I'll think in these directions.</p>",
                  "votes": null,
                  "replies": []
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "id": 3383226,
      "author_name": "zaakciiru",
      "author_url": "",
      "post_date": "12/29/2025 16:51:31",
      "content": "<p>How do you find all this? You haven't created an information retrieval course yet?</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3383465,
      "author_name": "navneetbende",
      "author_url": "",
      "post_date": "12/30/2025 08:05:20",
      "content": "<p>Thank you for the geodesic Voronoi <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">@hengck23</a> </p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3383497,
      "author_name": "khushikyad001",
      "author_url": "",
      "post_date": "12/30/2025 09:38:39",
      "content": "<p>Thank you for the geodesic Vorono</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3383921,
      "author_name": "hengck23",
      "author_url": "",
      "post_date": "12/31/2025 05:40:01",
      "content": "<p>There are many shortcuts. Eg deeply supervised to estimate fg/bg at many scales. At each scale you can add aux labels like is voxel close to neighbouring sheet or shortest distance to neighbouring sheet. This is helpful for post processing </p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3387464,
      "author_name": "hengck23",
      "author_url": "",
      "post_date": "01/07/2026 04:25:42",
      "content": "<p><a href=\"https://www.kaggle.com/choudharymanas\" target=\"_blank\">@choudharymanas</a> </p>\n<p>simple removal:<br>\nbefore: 3 sheets touching<br>\nafter: one sheet is removed from given points from 2 sheets   </p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Fa616532e95907c2af7e6c5ea31afd77c%2FSelection_2118.png?generation=1767759874757429&amp;alt=media\" alt=\"\"></p>\n<p>ask chatgpt for speedup code or explain the code</p>\n<pre><code>#helper\n\ndef compute_iou_1d(\n    x1, x2\n):\n    x1 = x1!=0\n    x2 = x2!=0\n    inter = (x1 &amp; x2).sum()\n    union = (x1 | x2).sum()\n    return inter / (union + 1e-6)\n\n# ray casting test\ndef do_ray_casting(\n    point1,  #yx format: Nx2\n    point2,\n    h,w\n):\n    # for y\n    by1 = np.bincount(point1[:, 0], minlength=h)\n    by2 = np.bincount(point2[:, 0], minlength=h)\n    #iouy = compute_iou_2d(by1, by2)\n    #print('iouy', iouy)\n\n    # for x\n    bx1 = np.bincount(point1[:, 1], minlength=w)\n    bx2 = np.bincount(point2[:, 1], minlength=w)\n    #ioux = compute_iou_2d(bx1, bx2)\n    #print('iou', ioux)\n\n    #we do joint test\n    iou = compute_iou_1d(\n        np.concatenate([by1, bx1]),\n        np.concatenate([by2, bx2]),\n    )\n    return iou\n\n#--------------------------------\nd,h,w = problem.shape\nz = d//2\n\nccz =  cc3d.connected_components(problem[z])\n#point yx\npoint=[None] #bg placeholder\nfor i in range(1,ccz.max()+1):\n    point.append(\n        np.stack(np.where(ccz == i)).T\n    )\niou = do_ray_casting(point[1], point[2],h,w)\nprint('ray_cast: iou', iou)\n# if iou &gt;0.1 then point[1], point[2] are on different sheet\n\n#different surface\ni1, i2 = 1,2\n\npoint1 = point[i1]\npoint2 = point[i2]\nshow_in_3d(problem, cmap='my_color', title='problem') #problem is binary cc3d\n\n#remove until shortest path is in the \"air\"\nfor trial in range(100):\n\n    path =[]\n\n    k1 = np.arange(len(point1))\n    np.random.shuffle(k1)\n    startpoint = point1[k1[:8]]\n    for (sy, sx) in startpoint:\n        parent = dijkstra3d.parental_field(\n            np.where(problem, 1.0, 1e6).astype(np.float32), source=(z, sy, sx), connectivity=26)\n\n        k2 = np.arange(len(point2))\n        np.random.shuffle(k2)\n        endppoint =  point2[k2[:8]]\n        path.extend([\n            dijkstra3d.path_from_parents(parent, (z, ey, ex))\n                     for (ey, ex) in endppoint\n        ])\n    path_flat = np.concatenate(path)\n    jump = np.any(~problem[path_flat[:,0],path_flat[:,1],path_flat[:,2]]) #brige in \"air\"/bg\n\n    if jump:\n\n        #for debug\n        if 1:\n            overlay3d =  cc3d.connected_components(problem)\n            show_in_3d(overlay3d, cmap='my_color',title='remove overlay3d')\n\n\n\n        break\n    else:\n        #remove common path and tr\n\n        uniq, cnt = np.unique(path_flat, axis=0, return_counts=True)\n        order = np.argsort(-cnt)  # descending by count\n        uniq = uniq[order]\n        cnt = cnt[order]\n        print('cnt', cnt)\n\n        #for debug\n        if trial==0:\n            overlay3d = problem.astype(np.uint8)\n            overlay3d[uniq[:,0],uniq[:,1],uniq[:,2]]= 255#cnt+1\n            show_in_3d(overlay3d, cmap='my_color',title='overlay3d')\n\n\n        #remove\n        threshold =0.8*(8*8)\n        u = uniq[cnt&gt;threshold]\n        problem[u[:, 0], u[:, 1], u[:, 2]] = False  # you can dilate for speedup\n</code></pre>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3387621,
      "author_name": "hengck23",
      "author_url": "",
      "post_date": "01/07/2026 11:35:43",
      "content": "<p>CODE is up!!!!!\n<a href=\"https://www.kaggle.com/code/hengck23/placeholder-killer-ant-post-processing\" target=\"_blank\">https://www.kaggle.com/code/hengck23/placeholder-killer-ant-post-processing</a></p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3387935,
      "author_name": "hengck23",
      "author_url": "",
      "post_date": "01/07/2026 20:28:14",
      "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F068c706d8fa95717d2beb128173636d4%2FSelection_2157.png?generation=1767817693048292&amp;alt=media\" alt=\"\"></p>\n<p>there is a trick one can try<br>\nA) given a set of ground truth labels<br>\nB) given another set of predicted labels (use different threshold)  </p>\n<p>note there are a few things you can do:<br>\n1) train a classifier to classify A and B. then the class activation map CAM may tell you the holes and bridges<br>\n2) train an auto encoder on A only. if you apply to B maybe you can remove the bridges and filled the holes<br>\n3) train a probablistic  denoise diffusion model DPPM : noise= (B) with different probaility  threshold, clean= A. then maybe you can clean up as post processing.<br>\n4) paired translator: a unet to mapp any thresholded labels to the best thresholded ones.(or best results assembed  from different thresholds as holes are filled and bridges disappears) </p>",
      "votes": null,
      "replies": [
        {
          "id": 3387942,
          "author_name": "hengck23",
          "author_url": "",
          "post_date": "01/07/2026 20:42:58",
          "content": "<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Fe216f3103b5081adaae63a8823dda965%2FSelection_2160.png?generation=1767818576903970&amp;alt=media\" alt=\"\"></p>",
          "votes": null,
          "replies": []
        },
        {
          "id": 3387949,
          "author_name": "seanjohnsonsp",
          "author_url": "",
          "post_date": "01/07/2026 21:26:20",
          "content": "<p>i really like the autoencoder idea, its something i've thought about for a bit. i tried a few similar things to your notes here . but never explored them past an initial \"huh this is kinda cool\" stage, to verify whether they worked or not </p>\n<ul>\n<li>i trained a classifier on \"missing sheets\" and also \"merges/holes\" , and used a grad cam , which showed some relatively interesting results as far as what the model was seeing</li>\n<li>i started to (but never finished) training an autoencoder to compress + reconstruct from this a GT label, to create an \"embedding\" model , which you could then use in a loss function, essentially saying embedding at pred and embedding at GT should be the same -- this seemed interesting in that you get all these topo metrics \"for free\"</li>\n</ul>",
          "votes": null,
          "replies": [
            {
              "id": 3388054,
              "author_name": "tom99763",
              "author_url": "",
              "post_date": "01/08/2026 04:25:41",
              "content": "<p><a href=\"https://www.kaggle.com/seanjohnsonsp\" target=\"_blank\">@seanjohnsonsp</a> This can be viewed as a reconstruction-based one-class anomaly detection approach. You basically don't need to label holes or missing part, just can guide everything from reconstruction. Below, I list several works applied to industrial images, and I strongly believe these approaches could be extended to this problem. Unfortunately, I am unable to work on it due to the limited time remaining before the deadline.</p>\n<ul>\n<li><a href=\"https://www.sciencedirect.com/science/article/abs/pii/S0893608021004810\" target=\"_blank\">UTRAD: Anomaly detection and localization with U-Transformer</a></li>\n<li><a href=\"https://openaccess.thecvf.com/content/CVPR2022/papers/Deng_Anomaly_Detection_via_Reverse_Distillation_From_One-Class_Embedding_CVPR_2022_paper.pdf\" target=\"_blank\">Anomaly Detection via Reverse Distillation from One-Class Embedding</a></li>\n<li><a href=\"https://openaccess.thecvf.com/content/CVPR2023/html/Tien_Revisiting_Reverse_Distillation_for_Anomaly_Detection_CVPR_2023_paper.html\" target=\"_blank\">Revisiting Reverse Distillation for Anomaly Detection</a></li>\n</ul>",
              "votes": null,
              "replies": []
            }
          ]
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "3381212": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F5d2caa533f48da6fda04f1d01046aa49%2FSelection_1831.png?generation=1766546235865360&alt=media)\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F175d90e857b58c0bac1da18f10fbbf5b%2FSelection_1832.png?generation=1766546250857133&alt=media)\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F31084f09670960e1c6a0d11b46ee1758%2FSelection_1833.png?generation=1766546267917679&alt=media)\n\n---\n\n\nBefore this final proposed solution, Gemini, Chatgpt and I have been trying many methods like watershed, graphcut, dynamic programming, detecting ouliers normals/density ... etc. We wrote codes and tried about 10 methods, and all of them failed for the following reasons:\n\n1. The \"Smooth C-Curve\" vs. T-Junctions\n2. The Bridge is \"Structural,\" Not \"Visual\"\n3. Proximity to Holes (The \"Tear-and-Join\" Phenomenon)\n\n\nBecause \"touching voxels\" forms a smooth C-curve, the surface normals of Sheet 1 gradually \"melt\" into the normals of Sheet 2. To a normal-based filter, the gradient remains consistent. The \"bridge\" is essentially invisible to local geometry because it perfectly mimics the curvature of the papyrus itself.\n\nWhile googling for topology, i came across an image of ants crawling on mobius strip. If AI agent wants to be \"intelligent\", he needs to relate observation (and maybe seemly unreated obseobservation\") to a possible solution to the current task/problem. I wonder how to train An AI agnet to do that ... to let him generate \"the stroke of a genius\"",
    "3381213": "https://github.com/seung-lab/dijkstra3d  \nDijkstra's Shortest Path variants for 6, 18, and 26-connected 3D Image Volumes or 4 and 8-connected 2D images.\n\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F306d4c1614264b669c039ccfda42dbab%2FSelection_1834.png?generation=1766546304697280&alt=media)",
    "3381272": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F360e9f03a725f68fa606cd198e28c82b%2FSelection_1845.png?generation=1766556987884686&alt=media)\n\n~~code notebook coming soon!~~\n~~i see if i can show you some results on the non-labelled region of the train data (label==2)~~\nuse  \"geodesic Voronoi\" above instead",
    "3381486": "hmm, very simply method to estimate number of sheets. I had thought about it but didn't get an appropriate opportunity to try it out, but i feel knowing number of sheets can be very helpful. After that we can assume that many 2-4 voxel thick continuous sheets and bend them via some method to fit them according to the input, I feel this is the only way to use all the three important priors we have (continuous sheets, no merging, and nearly constant thickness) .",
    "3381626": "very good news!\n\nthere is even a faster method usig the concept \"geodesic Voronoi\"  \n- sepeate sheets in less than few sec on cpu\n- can run in gpu\n- given a 3d connected component, use ray test to identify different sheet. select one pixel on each sheet as seed. ( or strategically select a set of seeds)\n- then run \"geodesic Voronoi\"  using 2-pass chamfer / raster-scan geodesic distance transform\n- if you still want the bridge (eg for training), look for Voronoi boundary\n\nexample results![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Ffd068ac494af3c6ba0158713cc5d0eb5%2FSelection_1849.png?generation=1766636462105065&alt=media)\n\nhere is the code:\nexample input \"one_cc.npz\" is in the attachmnet of the post  \n\nnote: there is a differentiable pytorch version, by i cannot get it to work (cuda compile error)  \nhttps://github.com/masadcv/FastGeodis  \n\nif you can get it to work, it means that\n- your unet takes in seed as input\n- and produce fg logit (probability) such that  there is a path (26-connectivity) from fg voxel to the respective seed in 3d, backproagate by \"Voronoi loss (aka Reachability loss) - use softmax or max diffusion or learn geodesic distance transform\"\n- **it is training dijkstra3d for free**\n\n\n```\n#geodesic Voronoi\n\nimport numpy as np\nimport math\n\n\nimport pyvista as pv\nfrom matplotlib.colors import ListedColormap\n\ndef show_in_3d(data, is_label=True,  cmap=\"tab20\", title=None):\n    vol = data.astype(np.float32)  # image  #mask  predict overlay_3d\n    vol = vol / vol.max()  # scale to [0, 1]\n    #vol = vol ** 1.15\n\n    # Wrap as a pyvista UniformGrid\n    grid = pv.wrap(vol)\n\n    # ----------------------------------------------------------\n    # 3. Wrap volume as PyVista grid and attach label scalars\n    if is_label:\n        labels = data.astype(np.int32)\n        grid[\"labels\"] = labels.flatten(order=\"F\")  # PyVista expects Fortran order\n    else:\n        pass\n\n    if cmap=='my_color':\n        my_color = np.full((256,4),fill_value=64,dtype=np.uint8)\n        my_color[0]   = [  0,   0,   0, 255]\n        my_color[1]   = [255, 255,   0, 255]\n        my_color[2]   = [255,   0, 255, 255]\n        my_color[3]   = [  0, 255, 255, 255]\n        my_color[4]   = [  0,   0, 255, 255]\n        my_color[5]   = [  0, 255,   0, 255]\n        my_color[6]   = [  0,   0,   0, 255]\n        my_color[254] = [  0,   0, 255, 255]\n        my_color[255] = [255,   0,   0, 255]\n        cmap = ListedColormap(my_color/255)\n\n    opacity = np.ones(255) * 0.5\n    opacity[0] = 0.0\n    opacity[254] = 1.0\n\n    # surface = grid.extract_surface()  # marching cubes surface\n    # surface[\"label\"] = surface.point_data[\"values\"]\n\n    # ----------------------------------------------------------\n    plotter = pv.Plotter(title=title)\n    if is_label:\n        plotter.add_volume(\n            grid,\n            shade=True,\n            opacity=opacity,\n            scalars=\"labels\",\n            cmap=cmap,  # good for categorical colors\n        )\n    else:\n        #not implemented\n        pass\n    plotter.show()\n\n\n#############################################################\ndef chamfer_geodesic_voronoi_3d(mask, seeds):\n    \"\"\"\n    mask: 3D bool array, True = allowed/foreground, False = blocked\n    seeds: list of (z,y,x) integer coords (must lie in mask)\n\n    Returns:\n      dist: float32 array, inf outside mask\n      lab : int32 array, -1 outside mask, otherwise index of nearest seed\n    \"\"\"\n    D, H, W = mask.shape\n    INF = np.float32(1e9)\n\n    dist = np.full((D, H, W), INF, dtype=np.float32)\n    lab  = np.full((D, H, W), -1,  dtype=np.int32)\n\n    # init seeds\n    for i, (z, y, x) in enumerate(seeds):\n        if not mask[z, y, x]:\n            raise ValueError(f\"Seed {i} at {(z,y,x)} is outside mask.\")\n        dist[z, y, x] = 0.0\n        lab[z, y, x] = i\n\n    # 26-neighborhood offsets + chamfer costs\n    # axial: 1, face-diagonal: sqrt(2), body-diagonal: sqrt(3)\n    nbrs = []\n    for dz in (-1, 0, 1):\n        for dy in (-1, 0, 1):\n            for dx in (-1, 0, 1):\n                if dz == 0 and dy == 0 and dx == 0:\n                    continue\n                k = abs(dz) + abs(dy) + abs(dx)\n                if k == 1:\n                    w = 1.0\n                elif k == 2:\n                    w = math.sqrt(2.0)\n                else:\n                    w = math.sqrt(3.0)\n                nbrs.append((dz, dy, dx, np.float32(w)))\n\n    # Split into forward/backward neighbor sets based on scan order.\n    # Forward scan order: z increasing, then y, then x.\n    # So \"already visited\" neighbors are those lexicographically smaller:\n    # (dz < 0) or (dz==0 and dy < 0) or (dz==0 and dy==0 and dx < 0)\n    fwd = [(dz,dy,dx,w) for (dz,dy,dx,w) in nbrs\n           if (dz < 0) or (dz == 0 and dy < 0) or (dz == 0 and dy == 0 and dx < 0)]\n    bwd = [(dz,dy,dx,w) for (dz,dy,dx,w) in nbrs\n           if (dz > 0) or (dz == 0 and dy > 0) or (dz == 0 and dy == 0 and dx > 0)]\n\n    # One relaxation pass\n    def relax(scan_z, scan_y, scan_x, neigh_list):\n        for z in scan_z:\n            for y in scan_y:\n                for x in scan_x:\n                    if not mask[z, y, x]:\n                        continue\n                    best_d = dist[z, y, x]\n                    best_l = lab[z, y, x]\n\n                    for dz, dy, dx, w in neigh_list:\n                        zz, yy, xx = z + dz, y + dy, x + dx\n                        if 0 <= zz < D and 0 <= yy < H and 0 <= xx < W and mask[zz, yy, xx]:\n                            cand = dist[zz, yy, xx] + w\n                            if cand < best_d:\n                                best_d = cand\n                                best_l = lab[zz, yy, xx]\n\n                    dist[z, y, x] = best_d\n                    lab[z, y, x]  = best_l\n\n    # Two-pass chamfer propagation\n    relax(range(D), range(H), range(W), fwd)                 # forward pass\n    relax(range(D-1, -1, -1), range(H-1, -1, -1), range(W-1, -1, -1), bwd)  # backward pass\n\n    return dist, lab\n\n\n# --- tiny demo ---\nif __name__ == \"__main__\":\n\n    #dummy for test\n    #mask = np.zeros((160, 160, 160), dtype=bool)\n    #mask[:, 1:5, 1:6] = True  # simple block component\n    #seeds = [(0, 1, 1), (4, 4, 5)]\n\n    mask =np.load('one_cc.npz')['arr_0']\n    #show_in_3d(mask, title='mask', cmap='my_color')\n\n    sz, sy, sx = 0, 4, 46\n    ez, ey, ex = 0, 22, 53\n    seeds =[(sz, sy, sx),(ez, ey, ex)]\n    #check\n    print (mask[seeds[0]])\n    print (mask[seeds[1]])\n\n    dist, lab = chamfer_geodesic_voronoi_3d(mask, seeds)\n    print(np.unique(lab, return_counts=True))\n    lab = lab+1\n\n    show_in_3d(lab, title='lab', cmap='my_color')\n\n\n```",
    "3381632": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F817bdf72795f4d0b83472a9f4b9f5215%2FSelection_1855.png?generation=1766639653589660&alt=media)",
    "3382733": "Thanks, this made me think a bit.\n\nIf you have constant thickness, no merging, and a known size of the artifact...maybe there is something to that.  You could estimate the number of turns of the scroll within some margin of error...yes?\n\nYes.  If you know the **outer radius**, the **thickness of the sheet of papyrus**, and **the innermost (core) radius**, you can get the number of '**turns of the papyrus around the core**'.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F601679%2Fedc32b376efb3659a24304e07d574efa%2Fwrap_1.jpg?generation=1766939561741672&alt=media)\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F601679%2Ff3197cb8df94da647d3b2da9dcfa9830%2FChatGPT%20Image%20Dec%2028%202025%2005_32_07%20PM.png?generation=1766939574422300&alt=media)\n\nPersonally, I don't know any of the real variables yet for any of the scrolls, but maybe we can crowdsource the info?",
    "3382737": "Actually in this kaggle competition, we don't have to segment the complete papyrus scan. We just have to predict for a small cutout, so for that cube, There will be numerous sheets.",
    "3383226": "How do you find all this? You haven't created an information retrieval course yet?",
    "3383278": "True; so in that case, did you determine a way to estimate the sheets in each voxel?  It seems random to me, at least in isolation.",
    "3383292": "I think you mean in each volume, There are multiple ways. One of them being analyze number of components in different slices across z axis, and choose via them. Or we can send rays perpendicular to the flow of sheets, which can be easily estimated via taking a slice and taking normal to flow in that slice, We then see how many sheets does the rays intersect (this method gives pretty accurate results, in worst case +-1 ). The result from both depends on the quality of your basic segmentation results.",
    "3383465": "Thank you for the geodesic Voronoi @hengck23",
    "3383497": "Thank you for the geodesic Vorono",
    "3383921": "There are many shortcuts. Eg deeply supervised to estimate fg/bg at many scales. At each scale you can add aux labels like is voxel close to neighbouring sheet or shortest distance to neighbouring sheet. This is helpful for post processing",
    "3384364": "To estimate number of sheets maybe another way is to compute the distance transform gradients for the sheets, ignore vectors with components in a particular direction and run a 3D connected components algorithm. This is however very cpu intensive. Radius of curvature of the scrolls also seem to be large enough for this to work.",
    "3384365": "no need to be so complicated:\n\n```\nprob = unnet(vol)\ncc = connected_componet_3d(prob>0.3)\nfor label in range(1, max cc labe):\n   one_cc = cc==label #binary\n   for each slice: \n       y_edge = one_cc[z,:-1]- one_cc[z,1:]\n       #cast y rays  \n       y_edge_sum = y_edge.sum( ... sum vertical colums)\n\n    if single surface :  y_edge_sum is only zero or one\n    if two sheet:  y_edge_sum has value =2\n   \n    do for all slices and build an occurrence histogram and check median count etc ...\n     cast ray in x direction too\n\n```",
    "3384995": "faster version:\n```\n\n\nwhile 1:\n     find seeds from different sheets using ray casting.\n     compute geodesic paths\n     All paths jump into the air?  (i.e., because sheets are not connected,  the path runs through the air)\n     YES: break and exit\n     NO:  remove the most common path of the paths and repeat\n\n```",
    "3387271": "Geodesic voronoi's result depends heavily on the seed selection, logically I deduced that taking seeds normal to the flow of direction of flow of sheets in some component is best way to take seeds. It still doesn't give relaible results. Example-> I took random slice, and random seeds which follow the condition I said above. and following is the result\n\nSEED and COMPONENT\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2F6f4411edd891029d4b48f1fb2e8d4a74%2FScreenshot%202026-01-06%20220354.png?generation=1767717655700861&alt=media)\n\nRESULT OF GEODESIC VERONOI\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2Fbfc6aece9431ef6d6c06bbb9bfa5481c%2FScreenshot%202026-01-06%20220404.png?generation=1767717711123312&alt=media)\n\n\nThis happens because seed initialization is not the only parameter here, where the sheets touch and where the holes are also an issue, because they affect the distance calculations.",
    "3387342": "Hi curious about how you are computing the \"flow\" direction of the sheets. Could you share?",
    "3387438": "to do heuristic, follow the steps:  \n1) make a way to verify results is correct or not\n(you can easily train a single curve vs multiple curves, or even count number of curve by training a slice classifier or 2.5D volume classifiere using ground truth)  \n2) if you are using  Geodesic voronoi's, randomly just select seeds. e.g. just 2 random seeds from 2 curves (from connected component). then use the automtic verifier above to record results. we are interested in, e.g. if we have 1000 random trials, how many % or the trials are recorded correctly by the verifier.  \n\nhence you can work out the computation cost of trial and error and guarantee success.  \nfor the failure case, you would hvae to restore to finding bridge using dijkstra3d.  \n\nthe trick is chop your prediction into overlaps of say 64 slices for Geodesic voronoi to speed up. the touching part isminor. so most of the \"chops\" will pass the test easily",
    "3387447": "This is just a sample, obviously we won't mask with and all during actual inference.\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F26230365%2F25a392be7138792e0539b926ae30fcd7%2FScreenshot%202026-01-07%20081326.png?generation=1767755436996236&alt=media)",
    "3387448": "Thanks, I'll think in these directions.",
    "3387464": "choudharymanas \n\nsimple removal:  \nbefore: 3 sheets touching     \nafter: one sheet is removed from given points from 2 sheets   \n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Fa616532e95907c2af7e6c5ea31afd77c%2FSelection_2118.png?generation=1767759874757429&alt=media)\n\n\nask chatgpt for speedup code or explain the code\n\n```\n\n#helper\n\ndef compute_iou_1d(\n    x1, x2\n):\n    x1 = x1!=0\n    x2 = x2!=0\n    inter = (x1 & x2).sum()\n    union = (x1 | x2).sum()\n    return inter / (union + 1e-6)\n\n# ray casting test\ndef do_ray_casting(\n    point1,  #yx format: Nx2\n    point2,\n    h,w\n):\n    # for y\n    by1 = np.bincount(point1[:, 0], minlength=h)\n    by2 = np.bincount(point2[:, 0], minlength=h)\n    #iouy = compute_iou_2d(by1, by2)\n    #print('iouy', iouy)\n\n    # for x\n    bx1 = np.bincount(point1[:, 1], minlength=w)\n    bx2 = np.bincount(point2[:, 1], minlength=w)\n    #ioux = compute_iou_2d(bx1, bx2)\n    #print('iou', ioux)\n\n    #we do joint test\n    iou = compute_iou_1d(\n        np.concatenate([by1, bx1]),\n        np.concatenate([by2, bx2]),\n    )\n    return iou\n\n#--------------------------------\nd,h,w = problem.shape\nz = d//2\n\nccz =  cc3d.connected_components(problem[z])\n#point yx\npoint=[None] #bg placeholder\nfor i in range(1,ccz.max()+1):\n    point.append(\n        np.stack(np.where(ccz == i)).T\n    )\niou = do_ray_casting(point[1], point[2],h,w)\nprint('ray_cast: iou', iou)\n# if iou >0.1 then point[1], point[2] are on different sheet\n\n#different surface\ni1, i2 = 1,2\n\npoint1 = point[i1]\npoint2 = point[i2]\nshow_in_3d(problem, cmap='my_color', title='problem') #problem is binary cc3d\n\n#remove until shortest path is in the \"air\"\nfor trial in range(100):\n\n    path =[]\n\n    k1 = np.arange(len(point1))\n    np.random.shuffle(k1)\n    startpoint = point1[k1[:8]]\n    for (sy, sx) in startpoint:\n        parent = dijkstra3d.parental_field(\n            np.where(problem, 1.0, 1e6).astype(np.float32), source=(z, sy, sx), connectivity=26)\n\n        k2 = np.arange(len(point2))\n        np.random.shuffle(k2)\n        endppoint =  point2[k2[:8]]\n        path.extend([\n            dijkstra3d.path_from_parents(parent, (z, ey, ex))\n                     for (ey, ex) in endppoint\n        ])\n    path_flat = np.concatenate(path)\n    jump = np.any(~problem[path_flat[:,0],path_flat[:,1],path_flat[:,2]]) #brige in \"air\"/bg\n\n    if jump:\n\n        #for debug\n        if 1:\n            overlay3d =  cc3d.connected_components(problem)\n            show_in_3d(overlay3d, cmap='my_color',title='remove overlay3d')\n\n\n\n        break\n    else:\n        #remove common path and tr\n  \n        uniq, cnt = np.unique(path_flat, axis=0, return_counts=True)\n        order = np.argsort(-cnt)  # descending by count\n        uniq = uniq[order]\n        cnt = cnt[order]\n        print('cnt', cnt)\n\n        #for debug\n        if trial==0:\n            overlay3d = problem.astype(np.uint8)\n            overlay3d[uniq[:,0],uniq[:,1],uniq[:,2]]= 255#cnt+1\n            show_in_3d(overlay3d, cmap='my_color',title='overlay3d')\n\n\n        #remove\n        threshold =0.8*(8*8)\n        u = uniq[cnt>threshold]\n        problem[u[:, 0], u[:, 1], u[:, 2]] = False  # you can dilate for speedup\n\n\n```",
    "3387621": "CODE is up!!!!!\nhttps://www.kaggle.com/code/hengck23/placeholder-killer-ant-post-processing",
    "3387935": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F068c706d8fa95717d2beb128173636d4%2FSelection_2157.png?generation=1767817693048292&alt=media)\n\nthere is a trick one can try  \nA) given a set of ground truth labels  \nB) given another set of predicted labels (use different threshold)  \n\nnote there are a few things you can do:  \n1) train a classifier to classify A and B. then the class activation map CAM may tell you the holes and bridges  \n2) train an auto encoder on A only. if you apply to B maybe you can remove the bridges and filled the holes  \n3) train a probablistic  denoise diffusion model DPPM : noise= (B) with different probaility  threshold, clean= A. then maybe you can clean up as post processing.  \n4) paired translator: a unet to mapp any thresholded labels to the best thresholded ones.(or best results assembed  from different thresholds as holes are filled and bridges disappears)",
    "3387942": "![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2Fe216f3103b5081adaae63a8823dda965%2FSelection_2160.png?generation=1767818576903970&alt=media)",
    "3387949": "i really like the autoencoder idea, its something i've thought about for a bit. i tried a few similar things to your notes here . but never explored them past an initial \"huh this is kinda cool\" stage, to verify whether they worked or not \n\n- i trained a classifier on \"missing sheets\" and also \"merges/holes\" , and used a grad cam , which showed some relatively interesting results as far as what the model was seeing\n- i started to (but never finished) training an autoencoder to compress + reconstruct from this a GT label, to create an \"embedding\" model , which you could then use in a loss function, essentially saying embedding at pred and embedding at GT should be the same -- this seemed interesting in that you get all these topo metrics \"for free\"",
    "3388054": "seanjohnsonsp This can be viewed as a reconstruction-based one-class anomaly detection approach. You basically don't need to label holes or missing part, just can guide everything from reconstruction. Below, I list several works applied to industrial images, and I strongly believe these approaches could be extended to this problem. Unfortunately, I am unable to work on it due to the limited time remaining before the deadline.\n\n* [UTRAD: Anomaly detection and localization with U-Transformer](https://www.sciencedirect.com/science/article/abs/pii/S0893608021004810)\n* [Anomaly Detection via Reverse Distillation from One-Class Embedding](https://openaccess.thecvf.com/content/CVPR2022/papers/Deng_Anomaly_Detection_via_Reverse_Distillation_From_One-Class_Embedding_CVPR_2022_paper.pdf)\n* [Revisiting Reverse Distillation for Anomaly Detection](https://openaccess.thecvf.com/content/CVPR2023/html/Tien_Revisiting_Reverse_Distillation_for_Anomaly_Detection_CVPR_2023_paper.html)"
  },
  "source": "meta"
}