{
  "id": 631311,
  "title": "a feature for discussion, \"sheetness\"",
  "url": "/competitions/vesuvius-challenge-surface-detection/discussion/631311",
  "author_name": "",
  "post_date": "2025-11-18T20:55:52.537581Z",
  "votes": 9,
  "comment_count": 2,
  "views": 0,
  "content": "<p>I'm not a serious contender for this, just a hobbyist who wanted to learn how to train a 3-D model end-to-end and submit something for scoring. But while experimenting with preprocessing and visualizing the data, hit on a small idea which was helpful for me and wanted to share in case helpful for others.</p>\n<p>Imagine a thin flat sheet.  Voxels on the surface of the sheet should tend to be very similar to their neighboring voxels in 2 dimensions (X and Y) and very different from their neighbors in (at least) one direction on Z dimension (air above the sheet). Even though the papyrus is rolled into a scroll (not to mention crushed and burnt to a crisp), this would still be more true than not true for surface voxels.</p>\n<p>I vibecoded a way to calculate the \"sheetness\" of a voxel (how well it matches this platonic ideal of a surface voxel's relationship with its neighbors) over a few different scales of \"neighborhood.\" </p>\n<ol>\n<li>compute local gradients</li>\n<li>compute the structure tensor over a small window</li>\n<li>take the eigenvalues λ1 ≥ λ2 ≥ λ3</li>\n<li>a sheet-like structure has λ1 ≫ λ2 and λ2 ≫ λ3</li>\n<li>convert that pattern into a normalized “sheetness” value</li>\n</ol>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F30604449%2Fe8f165731e4afa093f0cf5a96d59f067%2Fsheetness.png?generation=1763498803894104&amp;alt=media\" alt=\"\"></p>\n<p>example function for implementation below -</p>\n<p>`import numpy as np\nfrom scipy.ndimage import gaussian_filter, sobel</p>\n<p>def compute_sheetness(volume, sigma=1.0, eps=1e-6):\n    \"\"\"\n    volume: 3D numpy array (float32)\n    returns: 3D numpy array of sheetness values in [0, 1]\n    \"\"\"\n    # Gradients\n    gx = sobel(volume, axis=0)\n    gy = sobel(volume, axis=1)\n    gz = sobel(volume, axis=2)</p>\n<pre><code># Structure tensor \nJxx = gaussian_filter(gx*gx, sigma)\nJyy = gaussian_filter(gy*gy, sigma)\nJzz = gaussian_filter(gz*gz, sigma)\nJxy = gaussian_filter(gx*gy, sigma)\nJxz = gaussian_filter(gx*gz, sigma)\nJyz = gaussian_filter(gy*gz, sigma)\n\nsheetness = .zeros_like(volume, dtype=.float32)\n\n# Compute   each voxel\n idx  .ndindex(volume.shape):\n    J = .([\n        [Jxx[idx], Jxy[idx], Jxz[idx]],\n        [Jxy[idx], Jyy[idx], Jyz[idx]],\n        [Jxz[idx], Jyz[idx], Jzz[idx]]\n    ])\n    w = .linalg.eigvalsh(J)\n    w1, w2, w3 = sorted(w, =True)\n\n    # Simple sheetness metric\n    S = (.(w2) / (.(w1) + eps)) * (.(w3) / (.(w2) + eps))\n    sheetness[idx] = .clip(S, , )\n\n sheetness\n</code></pre>\n<p>`</p>\n<p>I did some testing and including this as a channel produced a small but meaningful improvement in validation Dice. Someone else might be able to take this and do more with it than I'm able to (happy to chat / brainstorm further). I want to see those scrolls unrolled - good luck, all! </p>",
  "messages": [
    {
      "id": "3337125",
      "postDate": "11/18/2025 20:55:52",
      "content": "<p>I'm not a serious contender for this, just a hobbyist who wanted to learn how to train a 3-D model end-to-end and submit something for scoring. But while experimenting with preprocessing and visualizing the data, hit on a small idea which was helpful for me and wanted to share in case helpful for others.</p>\n<p>Imagine a thin flat sheet.  Voxels on the surface of the sheet should tend to be very similar to their neighboring voxels in 2 dimensions (X and Y) and very different from their neighbors in (at least) one direction on Z dimension (air above the sheet). Even though the papyrus is rolled into a scroll (not to mention crushed and burnt to a crisp), this would still be more true than not true for surface voxels.</p>\n<p>I vibecoded a way to calculate the \"sheetness\" of a voxel (how well it matches this platonic ideal of a surface voxel's relationship with its neighbors) over a few different scales of \"neighborhood.\" </p>\n<ol>\n<li>compute local gradients</li>\n<li>compute the structure tensor over a small window</li>\n<li>take the eigenvalues λ1 ≥ λ2 ≥ λ3</li>\n<li>a sheet-like structure has λ1 ≫ λ2 and λ2 ≫ λ3</li>\n<li>convert that pattern into a normalized “sheetness” value</li>\n</ol>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F30604449%2Fe8f165731e4afa093f0cf5a96d59f067%2Fsheetness.png?generation=1763498803894104&amp;alt=media\" alt=\"\"></p>\n<p>example function for implementation below -</p>\n<p>`import numpy as np\nfrom scipy.ndimage import gaussian_filter, sobel</p>\n<p>def compute_sheetness(volume, sigma=1.0, eps=1e-6):\n    \"\"\"\n    volume: 3D numpy array (float32)\n    returns: 3D numpy array of sheetness values in [0, 1]\n    \"\"\"\n    # Gradients\n    gx = sobel(volume, axis=0)\n    gy = sobel(volume, axis=1)\n    gz = sobel(volume, axis=2)</p>\n<pre><code># Structure tensor \nJxx = gaussian_filter(gx*gx, sigma)\nJyy = gaussian_filter(gy*gy, sigma)\nJzz = gaussian_filter(gz*gz, sigma)\nJxy = gaussian_filter(gx*gy, sigma)\nJxz = gaussian_filter(gx*gz, sigma)\nJyz = gaussian_filter(gy*gz, sigma)\n\nsheetness = .zeros_like(volume, dtype=.float32)\n\n# Compute   each voxel\n idx  .ndindex(volume.shape):\n    J = .([\n        [Jxx[idx], Jxy[idx], Jxz[idx]],\n        [Jxy[idx], Jyy[idx], Jyz[idx]],\n        [Jxz[idx], Jyz[idx], Jzz[idx]]\n    ])\n    w = .linalg.eigvalsh(J)\n    w1, w2, w3 = sorted(w, =True)\n\n    # Simple sheetness metric\n    S = (.(w2) / (.(w1) + eps)) * (.(w3) / (.(w2) + eps))\n    sheetness[idx] = .clip(S, , )\n\n sheetness\n</code></pre>\n<p>`</p>\n<p>I did some testing and including this as a channel produced a small but meaningful improvement in validation Dice. Someone else might be able to take this and do more with it than I'm able to (happy to chat / brainstorm further). I want to see those scrolls unrolled - good luck, all! </p>",
      "rawMarkdown": "I'm not a serious contender for this, just a hobbyist who wanted to learn how to train a 3-D model end-to-end and submit something for scoring. But while experimenting with preprocessing and visualizing the data, hit on a small idea which was helpful for me and wanted to share in case helpful for others.\n\nImagine a thin flat sheet.  Voxels on the surface of the sheet should tend to be very similar to their neighboring voxels in 2 dimensions (X and Y) and very different from their neighbors in (at least) one direction on Z dimension (air above the sheet). Even though the papyrus is rolled into a scroll (not to mention crushed and burnt to a crisp), this would still be more true than not true for surface voxels.\n\nI vibecoded a way to calculate the \"sheetness\" of a voxel (how well it matches this platonic ideal of a surface voxel's relationship with its neighbors) over a few different scales of \"neighborhood.\" \n\n1. compute local gradients\n2. compute the structure tensor over a small window\n3. take the eigenvalues λ1 ≥ λ2 ≥ λ3\n4. a sheet-like structure has λ1 ≫ λ2 and λ2 ≫ λ3\n5. convert that pattern into a normalized “sheetness” value\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F30604449%2Fe8f165731e4afa093f0cf5a96d59f067%2Fsheetness.png?generation=1763498803894104&alt=media)\n\nexample function for implementation below -\n\n`import numpy as np\nfrom scipy.ndimage import gaussian_filter, sobel\n\ndef compute_sheetness(volume, sigma=1.0, eps=1e-6):\n    \"\"\"\n    volume: 3D numpy array (float32)\n    returns: 3D numpy array of sheetness values in [0, 1]\n    \"\"\"\n    # Gradients\n    gx = sobel(volume, axis=0)\n    gy = sobel(volume, axis=1)\n    gz = sobel(volume, axis=2)\n\n    # Structure tensor components\n    Jxx = gaussian_filter(gx*gx, sigma)\n    Jyy = gaussian_filter(gy*gy, sigma)\n    Jzz = gaussian_filter(gz*gz, sigma)\n    Jxy = gaussian_filter(gx*gy, sigma)\n    Jxz = gaussian_filter(gx*gz, sigma)\n    Jyz = gaussian_filter(gy*gz, sigma)\n\n    sheetness = np.zeros_like(volume, dtype=np.float32)\n\n    # Compute eigenvalues at each voxel\n    for idx in np.ndindex(volume.shape):\n        J = np.array([\n            [Jxx[idx], Jxy[idx], Jxz[idx]],\n            [Jxy[idx], Jyy[idx], Jyz[idx]],\n            [Jxz[idx], Jyz[idx], Jzz[idx]]\n        ])\n        w = np.linalg.eigvalsh(J)\n        w1, w2, w3 = sorted(w, reverse=True)\n\n        # Simple sheetness metric\n        S = (np.abs(w2) / (np.abs(w1) + eps)) * (np.abs(w3) / (np.abs(w2) + eps))\n        sheetness[idx] = np.clip(S, 0, 1)\n\n    return sheetness\n`\n\nI did some testing and including this as a channel produced a small but meaningful improvement in validation Dice. Someone else might be able to take this and do more with it than I'm able to (happy to chat / brainstorm further). I want to see those scrolls unrolled - good luck, all!",
      "votes": null
    },
    {
      "id": "3341186",
      "postDate": "11/20/2025 03:14:45",
      "content": "<p>Interesting post! This remembers me of the Frangi filter which is used to enhance vesselness.</p>",
      "rawMarkdown": "Interesting post! This remembers me of the Frangi filter which is used to enhance vesselness.",
      "votes": null
    },
    {
      "id": "3351430",
      "postDate": "11/28/2025 12:03:35",
      "content": "<p>Interesting idea, would it have a strong bias for the axis along you would apply it? Also would work as post-processing filter? </p>",
      "rawMarkdown": "Interesting idea, would it have a strong bias for the axis along you would apply it? Also would work as post-processing filter?",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 3341186,
      "author_name": "giorgioangelotti",
      "author_url": "",
      "post_date": "11/20/2025 03:14:45",
      "content": "<p>Interesting post! This remembers me of the Frangi filter which is used to enhance vesselness.</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3351430,
      "author_name": "jirkaborovec",
      "author_url": "",
      "post_date": "11/28/2025 12:03:35",
      "content": "<p>Interesting idea, would it have a strong bias for the axis along you would apply it? Also would work as post-processing filter? </p>",
      "votes": null,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "3337125": "I'm not a serious contender for this, just a hobbyist who wanted to learn how to train a 3-D model end-to-end and submit something for scoring. But while experimenting with preprocessing and visualizing the data, hit on a small idea which was helpful for me and wanted to share in case helpful for others.\n\nImagine a thin flat sheet.  Voxels on the surface of the sheet should tend to be very similar to their neighboring voxels in 2 dimensions (X and Y) and very different from their neighbors in (at least) one direction on Z dimension (air above the sheet). Even though the papyrus is rolled into a scroll (not to mention crushed and burnt to a crisp), this would still be more true than not true for surface voxels.\n\nI vibecoded a way to calculate the \"sheetness\" of a voxel (how well it matches this platonic ideal of a surface voxel's relationship with its neighbors) over a few different scales of \"neighborhood.\" \n\n1. compute local gradients\n2. compute the structure tensor over a small window\n3. take the eigenvalues λ1 ≥ λ2 ≥ λ3\n4. a sheet-like structure has λ1 ≫ λ2 and λ2 ≫ λ3\n5. convert that pattern into a normalized “sheetness” value\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F30604449%2Fe8f165731e4afa093f0cf5a96d59f067%2Fsheetness.png?generation=1763498803894104&alt=media)\n\nexample function for implementation below -\n\n`import numpy as np\nfrom scipy.ndimage import gaussian_filter, sobel\n\ndef compute_sheetness(volume, sigma=1.0, eps=1e-6):\n    \"\"\"\n    volume: 3D numpy array (float32)\n    returns: 3D numpy array of sheetness values in [0, 1]\n    \"\"\"\n    # Gradients\n    gx = sobel(volume, axis=0)\n    gy = sobel(volume, axis=1)\n    gz = sobel(volume, axis=2)\n\n    # Structure tensor components\n    Jxx = gaussian_filter(gx*gx, sigma)\n    Jyy = gaussian_filter(gy*gy, sigma)\n    Jzz = gaussian_filter(gz*gz, sigma)\n    Jxy = gaussian_filter(gx*gy, sigma)\n    Jxz = gaussian_filter(gx*gz, sigma)\n    Jyz = gaussian_filter(gy*gz, sigma)\n\n    sheetness = np.zeros_like(volume, dtype=np.float32)\n\n    # Compute eigenvalues at each voxel\n    for idx in np.ndindex(volume.shape):\n        J = np.array([\n            [Jxx[idx], Jxy[idx], Jxz[idx]],\n            [Jxy[idx], Jyy[idx], Jyz[idx]],\n            [Jxz[idx], Jyz[idx], Jzz[idx]]\n        ])\n        w = np.linalg.eigvalsh(J)\n        w1, w2, w3 = sorted(w, reverse=True)\n\n        # Simple sheetness metric\n        S = (np.abs(w2) / (np.abs(w1) + eps)) * (np.abs(w3) / (np.abs(w2) + eps))\n        sheetness[idx] = np.clip(S, 0, 1)\n\n    return sheetness\n`\n\nI did some testing and including this as a channel produced a small but meaningful improvement in validation Dice. Someone else might be able to take this and do more with it than I'm able to (happy to chat / brainstorm further). I want to see those scrolls unrolled - good luck, all!",
    "3341186": "Interesting post! This remembers me of the Frangi filter which is used to enhance vesselness.",
    "3351430": "Interesting idea, would it have a strong bias for the axis along you would apply it? Also would work as post-processing filter?"
  },
  "source": "meta"
}