{
  "id": 428707,
  "title": "PyDicom issues with this dataset",
  "url": "/competitions/rsna-2023-abdominal-trauma-detection/discussion/428707",
  "author_name": "David Roberts",
  "post_date": "2023-08-02T13:58:07.568000",
  "votes": 6,
  "comment_count": 6,
  "views": 0,
  "content": "<p>I think PyDicom does not decode pixels properly when using RLE Lossless transfer syntax as this dataset does. </p>\n<p><a href=\"https://www.kaggle.com/huiminglin\" target=\"_blank\">@huiminglin</a> posted a signed/unsigned fix for images with PixelRepresentation = 1 in this post -&gt; <a href=\"https://www.kaggle.com/competitions/rsna-2023-abdominal-trauma-detection/discussion/427217\" target=\"_blank\">https://www.kaggle.com/competitions/rsna-2023-abdominal-trauma-detection/discussion/427217</a>, and it does fix some of them, but I think there's a bigger PyDicom (or underlying decoder) issue causing a second problem.</p>\n<p>We can extract and normalize pixels manually (kind of) .. but if we use pydicom's <code>apply_voi_lut()</code> or <code>apply_windowing()</code> functions, the results are bad. Likewise if we try to manually calculate and apply a VOI LUT, it doesn't work either.</p>\n<pre><code>image = pydicom.dcmread()\npixels = apply_voi_lut(image.pixel_array, image)\nfig, axes = plt.subplots(nrows=, ncols=,sharex=, sharey=, figsize=(, ))\nax = axes.ravel()\nax[].imshow(image.pixel_array, cmap=)\nax[].imshow(pixels, cmap=)\nplt.show()\n</code></pre>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4945934%2F593de121ad025929c23c7d2c01c7a655%2FCapture1.JPG?generation=1690985853617031&amp;alt=media\" alt=\"\"></p>\n<p>If we open CT images from a previous RSNA CT competition, these PyDicom functions work as expected (the images aren't in RLE TS).</p>\n<pre><code>image = pydicom.dcmread()\npixels = apply_voi_lut(image.pixel_array, image)\nfig, axes = plt.subplots(nrows=, ncols=,sharex=, sharey=, figsize=(, ))\nax = axes.ravel()\nax[].imshow(image.pixel_array, cmap=)\nax[].imshow(pixels, cmap=)\nplt.show()\n</code></pre>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4945934%2F901d3a0935766e0404b852c04da19e51%2FCapture2.JPG?generation=1690985889246200&amp;alt=media\" alt=\"\"></p>\n<p>Neither of these files have Modality or VOI LUT tags, so the default PyDicom behavior is to use the WindowWidth and WindowCenter tags to create a VOI LUT.</p>",
  "messages": [
    {
      "id": 2370580,
      "postDate": "2023-08-02T13:58:07.570Z",
      "content": "<p>I think PyDicom does not decode pixels properly when using RLE Lossless transfer syntax as this dataset does. </p>\n<p><a href=\"https://www.kaggle.com/huiminglin\" target=\"_blank\">@huiminglin</a> posted a signed/unsigned fix for images with PixelRepresentation = 1 in this post -&gt; <a href=\"https://www.kaggle.com/competitions/rsna-2023-abdominal-trauma-detection/discussion/427217\" target=\"_blank\">https://www.kaggle.com/competitions/rsna-2023-abdominal-trauma-detection/discussion/427217</a>, and it does fix some of them, but I think there's a bigger PyDicom (or underlying decoder) issue causing a second problem.</p>\n<p>We can extract and normalize pixels manually (kind of) .. but if we use pydicom's <code>apply_voi_lut()</code> or <code>apply_windowing()</code> functions, the results are bad. Likewise if we try to manually calculate and apply a VOI LUT, it doesn't work either.</p>\n<pre><code>image = pydicom.dcmread()\npixels = apply_voi_lut(image.pixel_array, image)\nfig, axes = plt.subplots(nrows=, ncols=,sharex=, sharey=, figsize=(, ))\nax = axes.ravel()\nax[].imshow(image.pixel_array, cmap=)\nax[].imshow(pixels, cmap=)\nplt.show()\n</code></pre>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4945934%2F593de121ad025929c23c7d2c01c7a655%2FCapture1.JPG?generation=1690985853617031&amp;alt=media\" alt=\"\"></p>\n<p>If we open CT images from a previous RSNA CT competition, these PyDicom functions work as expected (the images aren't in RLE TS).</p>\n<pre><code>image = pydicom.dcmread()\npixels = apply_voi_lut(image.pixel_array, image)\nfig, axes = plt.subplots(nrows=, ncols=,sharex=, sharey=, figsize=(, ))\nax = axes.ravel()\nax[].imshow(image.pixel_array, cmap=)\nax[].imshow(pixels, cmap=)\nplt.show()\n</code></pre>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4945934%2F901d3a0935766e0404b852c04da19e51%2FCapture2.JPG?generation=1690985889246200&amp;alt=media\" alt=\"\"></p>\n<p>Neither of these files have Modality or VOI LUT tags, so the default PyDicom behavior is to use the WindowWidth and WindowCenter tags to create a VOI LUT.</p>",
      "rawMarkdown": "I think PyDicom does not decode pixels properly when using RLE Lossless transfer syntax as this dataset does. \n\n@huiminglin posted a signed/unsigned fix for images with PixelRepresentation = 1 in this post -> https://www.kaggle.com/competitions/rsna-2023-abdominal-trauma-detection/discussion/427217, and it does fix some of them, but I think there's a bigger PyDicom (or underlying decoder) issue causing a second problem.\n\nWe can extract and normalize pixels manually (kind of) .. but if we use pydicom's `apply_voi_lut()` or `apply_windowing()` functions, the results are bad. Likewise if we try to manually calculate and apply a VOI LUT, it doesn't work either.\n\n```python\nimage = pydicom.dcmread('/kaggle/input/rsna-2023-abdominal-trauma-detection/train_images/10004/21057/1003.dcm')\npixels = apply_voi_lut(image.pixel_array, image)\nfig, axes = plt.subplots(nrows=1, ncols=2,sharex=True, sharey=True, figsize=(8, 8))\nax = axes.ravel()\nax[0].imshow(image.pixel_array, cmap='gray')\nax[1].imshow(pixels, cmap='gray')\nplt.show()\n```\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4945934%2F593de121ad025929c23c7d2c01c7a655%2FCapture1.JPG?generation=1690985853617031&alt=media)\n\nIf we open CT images from a previous RSNA CT competition, these PyDicom functions work as expected (the images aren't in RLE TS).\n\n```python\nimage = pydicom.dcmread('/kaggle/input/rsna-2022-cervical-spine-fracture-detection/train_images/1.2.826.0.1.3680043.10400/270.dcm')\npixels = apply_voi_lut(image.pixel_array, image)\nfig, axes = plt.subplots(nrows=1, ncols=2,sharex=True, sharey=True, figsize=(8, 8))\nax = axes.ravel()\nax[0].imshow(image.pixel_array, cmap='gray')\nax[1].imshow(pixels, cmap='gray')\nplt.show()\n```\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4945934%2F901d3a0935766e0404b852c04da19e51%2FCapture2.JPG?generation=1690985889246200&alt=media)\n\nNeither of these files have Modality or VOI LUT tags, so the default PyDicom behavior is to use the WindowWidth and WindowCenter tags to create a VOI LUT.",
      "votes": 6
    },
    {
      "id": 2409048,
      "postDate": "2023-08-26T03:34:37.797Z",
      "content": "<p><a href=\"https://www.kaggle.com/davidbroberts\" target=\"_blank\">@davidbroberts</a>  According to <a href=\"https://stackoverflow.com/questions/73581521/window-width-and-center-and-hounsfield-units:\" target=\"_blank\">https://stackoverflow.com/questions/73581521/window-width-and-center-and-hounsfield-units:</a><br>\nSo the Windowing always applies to mapped values (= the output of the Modality LUT) regardless of which units that refers to. <br>\nSo it seems we should first apply modality LUT and then apply VOI LUT.<br>\nResult at right plot.<br>\nCould it be it was not necessary  in previous datasets since the HU rescale/shift was identity?</p>",
      "rawMarkdown": "@davidbroberts  According to https://stackoverflow.com/questions/73581521/window-width-and-center-and-hounsfield-units:\nSo the Windowing always applies to mapped values (= the output of the Modality LUT) regardless of which units that refers to. \nSo it seems we should first apply modality LUT and then apply VOI LUT.\nResult at right plot.\nCould it be it was not necessary  in previous datasets since the HU rescale/shift was identity?",
      "votes": 1,
      "replies": [
        {
          "id": 2411465,
          "postDate": "2023-08-27T16:25:33.637Z",
          "content": "<p>In the DICOM grayscale rendering pipeline Modality LUT is always applied first, then other LUT's like VOI LUT are applied after. HU doesn't have anything to do with processing pixels for rendering though, it's just a method to calibrate pixel values to real world densities .. like water. </p>\n<p>Nobody really uses modality LUT functions/sequences outside of proprietary systems these days. Likewise we don't see VOI LUTs in DICOM files much either. If you check the CT LUT DICOM standard, you can see the default behavior is to use the specified WindowWidth and WindowCenter sequences to create a VOI LUT when there is not a modality LUT or VOI LUT in the file.</p>\n<p><a href=\"https://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.11.2.html#:~:text=The%20VOI%20LUT%20Function%20(0028,is%20defined%20in%20Section%20C\" target=\"_blank\">https://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.11.2.html#:~:text=The%20VOI%20LUT%20Function%20(0028,is%20defined%20in%20Section%20C</a>.</p>\n<p>This is how most image viewers and printers initially render CT images.</p>",
          "rawMarkdown": "In the DICOM grayscale rendering pipeline Modality LUT is always applied first, then other LUT's like VOI LUT are applied after. HU doesn't have anything to do with processing pixels for rendering though, it's just a method to calibrate pixel values to real world densities .. like water. \n\nNobody really uses modality LUT functions/sequences outside of proprietary systems these days. Likewise we don't see VOI LUTs in DICOM files much either. If you check the CT LUT DICOM standard, you can see the default behavior is to use the specified WindowWidth and WindowCenter sequences to create a VOI LUT when there is not a modality LUT or VOI LUT in the file.\n\nhttps://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.11.2.html#:~:text=The%20VOI%20LUT%20Function%20(0028,is%20defined%20in%20Section%20C.\n\nThis is how most image viewers and printers initially render CT images."
        }
      ]
    },
    {
      "id": 2371106,
      "postDate": "2023-08-02T21:47:45.857Z",
      "content": "<p>The <code>apply_voi_lut()</code> function does not account for RescaleSlope and RescaleIntercept. Rescaling the pixel_array is important when converting to Hounsfield Unit (HU). You could also look into <code>apply_modality_lut()</code>.</p>\n<p>Please see this link for more resources: <a href=\"https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.pixel_data_handlers.util.html\" target=\"_blank\">https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.pixel_data_handlers.util.html</a></p>",
      "rawMarkdown": "The `apply_voi_lut()` function does not account for RescaleSlope and RescaleIntercept. Rescaling the pixel_array is important when converting to Hounsfield Unit (HU). You could also look into `apply_modality_lut()`.\n\nPlease see this link for more resources: https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.pixel_data_handlers.util.html",
      "votes": 1,
      "replies": [
        {
          "id": 2371148,
          "postDate": "2023-08-02T23:59:52.317Z",
          "content": "<p>None of these images actually have Modality LUT or VOI LUT sequence tags. So the default PyDicom behavior for both <code>apply_voi_lut()</code> and <code>apply_modality_lut()</code> (and<code>apply_windowing()</code> as well) .. is to use the WindowWidth/WindowCenter tags to make a VOI LUT along with Slope and Intercept tags. I have seen both of these functions work properly on other CT datasets here on Kaggle, as in the screenshots above. I can usually make VOI LUT's manually with something similar to the code below, but it doesn't work on these images.</p>\n<pre><code> ():\n\n    min_pixel = (np.amin(pixels))\n    max_pixel = (np.amax(pixels))\n\n    \n    lut = [] * (max_pixel + )\n\n    \n    \n    invert = \n     p_i == :\n        invert = \n    :\n        center = (max_pixel - min_pixel) - center\n\n    \n     pix_value  (min_pixel, max_pixel):\n        lut_value = pix_value * slope + intercept\n        voi_value = (((lut_value - center) /  width + ) * )\n        clamped_value = ((voi_value, ), )\n         invert:\n            lut[pix_value] = ( - clamped_value)\n        :\n            lut[pix_value] = (clamped_value)\n     lut\n\n\n ():\n\n    pixels_in = pixels_in.flatten()\n    pixels_out = [] * (pixels_in)\n\n     i  (, (pixels_in)):\n        pixel = pixels_in[i]\n        pixels_out[i] = lut[(pixel)]\n\n     pixels_out \n</code></pre>\n<p>What am I missing here <a href=\"https://www.kaggle.com/huiminglin\" target=\"_blank\">@huiminglin</a> ? So far, I haven't seen anyone do anything outside of standard normalization (0-1) with these images.</p>",
          "rawMarkdown": "None of these images actually have Modality LUT or VOI LUT sequence tags. So the default PyDicom behavior for both `apply_voi_lut()` and `apply_modality_lut()` (and` apply_windowing()` as well) .. is to use the WindowWidth/WindowCenter tags to make a VOI LUT along with Slope and Intercept tags. I have seen both of these functions work properly on other CT datasets here on Kaggle, as in the screenshots above. I can usually make VOI LUT's manually with something similar to the code below, but it doesn't work on these images.\n\n```python\ndef make_lut(pixels, width, center, p_i, slope, intercept):\n    \n    min_pixel = int(np.amin(pixels))\n    max_pixel = int(np.amax(pixels))\n\n    # Make an empty array for the LUT the size of the pixel 'width' in the raw pixel data\n    lut = [0] * (max_pixel + 1)\n    \n    # Invert pixels and cent for MONOCHROME1. We invert the specified center so that \n    # increasing the center value makes the images brighter regardless of photometric intrepretation\n    invert = False\n    if p_i == \"MONOCHROME1\":\n        invert = True\n    else:\n        center = (max_pixel - min_pixel) - center\n        \n    # Loop through the pixels and calculate each LUT value\n    for pix_value in range(min_pixel, max_pixel):\n        lut_value = pix_value * slope + intercept\n        voi_value = (((lut_value - center) /  width + 0.5) * 255.0)\n        clamped_value = min(max(voi_value, 0), 255)\n        if invert:\n            lut[pix_value] = round(255 - clamped_value)\n        else:\n            lut[pix_value] = round(clamped_value)\n    return lut\n\n# Apply the LUT to a pixel array\ndef apply_lut(pixels_in, lut):\n    \n    pixels_in = pixels_in.flatten()\n    pixels_out = [0] * len(pixels_in)\n    \n    for i in range(0, len(pixels_in)):\n        pixel = pixels_in[i]\n        pixels_out[i] = lut[int(pixel)]\n        \n    return pixels_out \n```\n\nWhat am I missing here @huiminglin ? So far, I haven't seen anyone do anything outside of standard normalization (0-1) with these images.",
          "replies": [
            {
              "id": 2374586,
              "postDate": "2023-08-05T05:35:17.420Z",
              "content": "<p>Have you tried setting <code>prefer_lut=False</code> in the <code>apply_voi_lut</code> function?</p>",
              "rawMarkdown": "Have you tried setting `prefer_lut=False` in the `apply_voi_lut` function?",
              "votes": 1
            },
            {
              "id": 2376609,
              "postDate": "2023-08-06T13:55:38.987Z",
              "content": "<p>I have. The images don't have VOI LUT sequences, so the prefer_lut parameter doesn't do anything.</p>",
              "rawMarkdown": "I have. The images don't have VOI LUT sequences, so the prefer_lut parameter doesn't do anything."
            }
          ]
        }
      ]
    }
  ],
  "comments": [
    {
      "id": 2409048,
      "author_name": "WalkingMoose",
      "author_url": "",
      "post_date": "2023-08-26T03:34:37.797000",
      "content": "<p><a href=\"https://www.kaggle.com/davidbroberts\" target=\"_blank\">@davidbroberts</a>  According to <a href=\"https://stackoverflow.com/questions/73581521/window-width-and-center-and-hounsfield-units:\" target=\"_blank\">https://stackoverflow.com/questions/73581521/window-width-and-center-and-hounsfield-units:</a><br>\nSo the Windowing always applies to mapped values (= the output of the Modality LUT) regardless of which units that refers to. <br>\nSo it seems we should first apply modality LUT and then apply VOI LUT.<br>\nResult at right plot.<br>\nCould it be it was not necessary  in previous datasets since the HU rescale/shift was identity?</p>",
      "votes": 1,
      "replies": [
        {
          "id": 2411465,
          "author_name": "David Roberts",
          "author_url": "",
          "post_date": "2023-08-27T16:25:33.637000",
          "content": "<p>In the DICOM grayscale rendering pipeline Modality LUT is always applied first, then other LUT's like VOI LUT are applied after. HU doesn't have anything to do with processing pixels for rendering though, it's just a method to calibrate pixel values to real world densities .. like water. </p>\n<p>Nobody really uses modality LUT functions/sequences outside of proprietary systems these days. Likewise we don't see VOI LUTs in DICOM files much either. If you check the CT LUT DICOM standard, you can see the default behavior is to use the specified WindowWidth and WindowCenter sequences to create a VOI LUT when there is not a modality LUT or VOI LUT in the file.</p>\n<p><a href=\"https://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.11.2.html#:~:text=The%20VOI%20LUT%20Function%20(0028,is%20defined%20in%20Section%20C\" target=\"_blank\">https://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.11.2.html#:~:text=The%20VOI%20LUT%20Function%20(0028,is%20defined%20in%20Section%20C</a>.</p>\n<p>This is how most image viewers and printers initially render CT images.</p>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 2371106,
      "author_name": "Hui Ming Lin",
      "author_url": "",
      "post_date": "2023-08-02T21:47:45.857000",
      "content": "<p>The <code>apply_voi_lut()</code> function does not account for RescaleSlope and RescaleIntercept. Rescaling the pixel_array is important when converting to Hounsfield Unit (HU). You could also look into <code>apply_modality_lut()</code>.</p>\n<p>Please see this link for more resources: <a href=\"https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.pixel_data_handlers.util.html\" target=\"_blank\">https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.pixel_data_handlers.util.html</a></p>",
      "votes": 1,
      "replies": [
        {
          "id": 2371148,
          "author_name": "David Roberts",
          "author_url": "",
          "post_date": "2023-08-02T23:59:52.317000",
          "content": "<p>None of these images actually have Modality LUT or VOI LUT sequence tags. So the default PyDicom behavior for both <code>apply_voi_lut()</code> and <code>apply_modality_lut()</code> (and<code>apply_windowing()</code> as well) .. is to use the WindowWidth/WindowCenter tags to make a VOI LUT along with Slope and Intercept tags. I have seen both of these functions work properly on other CT datasets here on Kaggle, as in the screenshots above. I can usually make VOI LUT's manually with something similar to the code below, but it doesn't work on these images.</p>\n<pre><code> ():\n\n    min_pixel = (np.amin(pixels))\n    max_pixel = (np.amax(pixels))\n\n    \n    lut = [] * (max_pixel + )\n\n    \n    \n    invert = \n     p_i == :\n        invert = \n    :\n        center = (max_pixel - min_pixel) - center\n\n    \n     pix_value  (min_pixel, max_pixel):\n        lut_value = pix_value * slope + intercept\n        voi_value = (((lut_value - center) /  width + ) * )\n        clamped_value = ((voi_value, ), )\n         invert:\n            lut[pix_value] = ( - clamped_value)\n        :\n            lut[pix_value] = (clamped_value)\n     lut\n\n\n ():\n\n    pixels_in = pixels_in.flatten()\n    pixels_out = [] * (pixels_in)\n\n     i  (, (pixels_in)):\n        pixel = pixels_in[i]\n        pixels_out[i] = lut[(pixel)]\n\n     pixels_out \n</code></pre>\n<p>What am I missing here <a href=\"https://www.kaggle.com/huiminglin\" target=\"_blank\">@huiminglin</a> ? So far, I haven't seen anyone do anything outside of standard normalization (0-1) with these images.</p>",
          "votes": 0,
          "replies": [
            {
              "id": 2374586,
              "author_name": "Bardia Khosravi",
              "author_url": "",
              "post_date": "2023-08-05T05:35:17.420000",
              "content": "<p>Have you tried setting <code>prefer_lut=False</code> in the <code>apply_voi_lut</code> function?</p>",
              "votes": 1,
              "replies": []
            },
            {
              "id": 2376609,
              "author_name": "David Roberts",
              "author_url": "",
              "post_date": "2023-08-06T13:55:38.987000",
              "content": "<p>I have. The images don't have VOI LUT sequences, so the prefer_lut parameter doesn't do anything.</p>",
              "votes": 0,
              "replies": []
            }
          ]
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "2370580": "I think PyDicom does not decode pixels properly when using RLE Lossless transfer syntax as this dataset does. \n\n@huiminglin posted a signed/unsigned fix for images with PixelRepresentation = 1 in this post -> https://www.kaggle.com/competitions/rsna-2023-abdominal-trauma-detection/discussion/427217, and it does fix some of them, but I think there's a bigger PyDicom (or underlying decoder) issue causing a second problem.\n\nWe can extract and normalize pixels manually (kind of) .. but if we use pydicom's `apply_voi_lut()` or `apply_windowing()` functions, the results are bad. Likewise if we try to manually calculate and apply a VOI LUT, it doesn't work either.\n\n```python\nimage = pydicom.dcmread('/kaggle/input/rsna-2023-abdominal-trauma-detection/train_images/10004/21057/1003.dcm')\npixels = apply_voi_lut(image.pixel_array, image)\nfig, axes = plt.subplots(nrows=1, ncols=2,sharex=True, sharey=True, figsize=(8, 8))\nax = axes.ravel()\nax[0].imshow(image.pixel_array, cmap='gray')\nax[1].imshow(pixels, cmap='gray')\nplt.show()\n```\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4945934%2F593de121ad025929c23c7d2c01c7a655%2FCapture1.JPG?generation=1690985853617031&alt=media)\n\nIf we open CT images from a previous RSNA CT competition, these PyDicom functions work as expected (the images aren't in RLE TS).\n\n```python\nimage = pydicom.dcmread('/kaggle/input/rsna-2022-cervical-spine-fracture-detection/train_images/1.2.826.0.1.3680043.10400/270.dcm')\npixels = apply_voi_lut(image.pixel_array, image)\nfig, axes = plt.subplots(nrows=1, ncols=2,sharex=True, sharey=True, figsize=(8, 8))\nax = axes.ravel()\nax[0].imshow(image.pixel_array, cmap='gray')\nax[1].imshow(pixels, cmap='gray')\nplt.show()\n```\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F4945934%2F901d3a0935766e0404b852c04da19e51%2FCapture2.JPG?generation=1690985889246200&alt=media)\n\nNeither of these files have Modality or VOI LUT tags, so the default PyDicom behavior is to use the WindowWidth and WindowCenter tags to create a VOI LUT.",
    "2409048": "@davidbroberts  According to https://stackoverflow.com/questions/73581521/window-width-and-center-and-hounsfield-units:\nSo the Windowing always applies to mapped values (= the output of the Modality LUT) regardless of which units that refers to. \nSo it seems we should first apply modality LUT and then apply VOI LUT.\nResult at right plot.\nCould it be it was not necessary  in previous datasets since the HU rescale/shift was identity?",
    "2371106": "The `apply_voi_lut()` function does not account for RescaleSlope and RescaleIntercept. Rescaling the pixel_array is important when converting to Hounsfield Unit (HU). You could also look into `apply_modality_lut()`.\n\nPlease see this link for more resources: https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.pixel_data_handlers.util.html"
  }
}