{
  "id": 371534,
  "title": "17x dicom decode speedup on GPU for jpeg2000 encodings",
  "url": "/competitions/rsna-breast-cancer-detection/discussion/371534",
  "author_name": "David Austin",
  "post_date": "2022-12-10T17:09:33.126000",
  "votes": 85,
  "comment_count": 21,
  "views": 0,
  "content": "<p><strong>TLDR:</strong><br>\nApprox half the dicoms in the dataset contain jpeg2000 encoded images.  There's a way to hack the bitstream and extract/save the jp2 images which can then be decoded on GPU using <a href=\"https://docs.nvidia.com/deeplearning/dali/user-guide/docs/index.html\" target=\"_blank\">Nvidia DALI</a>.</p>\n<p>pydicom decode per image (parallel mode): <strong>0.88</strong> imgs/sec (32 imgs/36 sec)<br>\nDALI decode per image: <strong>15.16</strong> imgs/sec (32 imgs/2.03s)<br>\nSpeedup: <strong>17.7x</strong></p>\n<p>Check out the <a href=\"https://www.kaggle.com/code/tivfrvqhs5/decode-jpeg2000-dicom-with-dali?scriptVersionId=113466193\" target=\"_blank\">notebook</a> here which contains the minimal decode example.  Still working on a way to extract the lossless jpeg bitstream for the other half of dicoms which will then make this technique much more useful in an end-to-end solution.</p>\n<p>A little more:<br>\nWhile DALI doesn't support decoding dicom's directly, it does support jpeg2000 decode.  So the trick is extracting the jpeg2000 bitstream from the dicom container so DALI can ingest it.</p>\n<p>I'm still working on figuring out an analogous hack for the lossless jpeg format.  Any inputs welcome.</p>",
  "messages": [
    {
      "id": 2061084,
      "postDate": "2022-12-10T17:09:33.127Z",
      "content": "<p><strong>TLDR:</strong><br>\nApprox half the dicoms in the dataset contain jpeg2000 encoded images.  There's a way to hack the bitstream and extract/save the jp2 images which can then be decoded on GPU using <a href=\"https://docs.nvidia.com/deeplearning/dali/user-guide/docs/index.html\" target=\"_blank\">Nvidia DALI</a>.</p>\n<p>pydicom decode per image (parallel mode): <strong>0.88</strong> imgs/sec (32 imgs/36 sec)<br>\nDALI decode per image: <strong>15.16</strong> imgs/sec (32 imgs/2.03s)<br>\nSpeedup: <strong>17.7x</strong></p>\n<p>Check out the <a href=\"https://www.kaggle.com/code/tivfrvqhs5/decode-jpeg2000-dicom-with-dali?scriptVersionId=113466193\" target=\"_blank\">notebook</a> here which contains the minimal decode example.  Still working on a way to extract the lossless jpeg bitstream for the other half of dicoms which will then make this technique much more useful in an end-to-end solution.</p>\n<p>A little more:<br>\nWhile DALI doesn't support decoding dicom's directly, it does support jpeg2000 decode.  So the trick is extracting the jpeg2000 bitstream from the dicom container so DALI can ingest it.</p>\n<p>I'm still working on figuring out an analogous hack for the lossless jpeg format.  Any inputs welcome.</p>",
      "rawMarkdown": "**TLDR:**\nApprox half the dicoms in the dataset contain jpeg2000 encoded images.  There's a way to hack the bitstream and extract/save the jp2 images which can then be decoded on GPU using [Nvidia DALI](https://docs.nvidia.com/deeplearning/dali/user-guide/docs/index.html).\n\npydicom decode per image (parallel mode): **0.88** imgs/sec (32 imgs/36 sec)\nDALI decode per image: **15.16** imgs/sec (32 imgs/2.03s)\nSpeedup: **17.7x**\n\nCheck out the [notebook](https://www.kaggle.com/code/tivfrvqhs5/decode-jpeg2000-dicom-with-dali?scriptVersionId=113466193) here which contains the minimal decode example.  Still working on a way to extract the lossless jpeg bitstream for the other half of dicoms which will then make this technique much more useful in an end-to-end solution.\n\nA little more:\nWhile DALI doesn't support decoding dicom's directly, it does support jpeg2000 decode.  So the trick is extracting the jpeg2000 bitstream from the dicom container so DALI can ingest it.\n\nI'm still working on figuring out an analogous hack for the lossless jpeg format.  Any inputs welcome.\n\n\n\n\n\n",
      "votes": 85
    },
    {
      "id": 2063328,
      "postDate": "2022-12-12T20:06:12.180Z",
      "content": "<p>Update: The byte string to properly extract lossless jpg images can be done using this offset with the same code as before.</p>\n<p><code>\noffset = ds.PixelData.find(b\"\\xff\\xd8\\xff\\xe0\")\n</code><br>\nHowever, decode of lossless jpg is not currenly supported by nvjpeg on gpu.  For now the best known solution is to combine dali on jpeg2k images with dicomsdl for lossless jpg.  <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">@hengck23</a> just posted a notebook <a href=\"https://www.kaggle.com/code/hengck23/combine-dali-and-dicomsdl-for-reading-dicom-files\" target=\"_blank\">here</a> that combines the two methods beautifully.</p>",
      "rawMarkdown": "Update: The byte string to properly extract lossless jpg images can be done using this offset with the same code as before.\n\n`\noffset = ds.PixelData.find(b\"\\xff\\xd8\\xff\\xe0\")\n`\nHowever, decode of lossless jpg is not currenly supported by nvjpeg on gpu.  For now the best known solution is to combine dali on jpeg2k images with dicomsdl for lossless jpg.  @hengck23 just posted a notebook [here](https://www.kaggle.com/code/hengck23/combine-dali-and-dicomsdl-for-reading-dicom-files) that combines the two methods beautifully.",
      "votes": 7,
      "replies": [
        {
          "id": 2064525,
          "postDate": "2022-12-13T20:49:26.200Z",
          "content": "<p>The question isn't pydicom versus dicomsdl versus dali, but rather nvjpeg versus gdcm versus libjpeg-turbo versus..</p>",
          "rawMarkdown": "The question isn't pydicom versus dicomsdl versus dali, but rather nvjpeg versus gdcm versus libjpeg-turbo versus.."
        }
      ]
    },
    {
      "id": 2064128,
      "postDate": "2022-12-13T14:36:45.360Z",
      "content": "<p><img src=\"https://i.ibb.co/Gs3zFH1/Selection-169.png\" alt=\"https://i.ibb.co/Gs3zFH1/Selection-169.png\"></p>",
      "rawMarkdown": "![https://i.ibb.co/Gs3zFH1/Selection-169.png](https://i.ibb.co/Gs3zFH1/Selection-169.png)",
      "votes": 5
    },
    {
      "id": 2061228,
      "postDate": "2022-12-10T21:08:03.027Z",
      "content": "<p>  <br>\ni make a mistake. i should be using nvJPEG2K instead</p>\n<p>i think your pipeline can be simplified as:</p>\n<pre><code>#https://github.com/UsingNet/nvjpeg-python\nfrom nvjpeg import NvJpeg\nnj = NvJpeg()\n.....\n\n\n\n    dicom = pydicom.dcmread(f)\n    if dicom.file_meta.TransferSyntaxUID != '1.2.840.10008.1.2.4.90':\n        raise  NotImplementedError\n\n    byte_stream = dicom.PixelData\n    offset = byte_stream.find(b\"\\x00\\x00\\x00\\x0C\")\n    jpeg_stream = np.asarray(bytearray(byte_stream[offset:]), dtype='uint8')\n\n   #failed because out of memory ...\n    m1 = nj.decode(jpeg_stream) \n\n   #this works! meaning that i have get the correct jpeg byte string\n    m0 = cv2.imdecode(jpeg_stream, cv2.IMREAD_ANYDEPTH)\n    print(m0.shape)\n    plt.imshow(m0)\n</code></pre>",
      "rawMarkdown": "~~i have been trying to use nvjpeg directly, but failed.~~  \ni make a mistake. i should be using nvJPEG2K instead\n\ni think your pipeline can be simplified as:\n\n```\n#https://github.com/UsingNet/nvjpeg-python\nfrom nvjpeg import NvJpeg\nnj = NvJpeg()\n.....\n\n\n\n    dicom = pydicom.dcmread(f)\n    if dicom.file_meta.TransferSyntaxUID != '1.2.840.10008.1.2.4.90':\n        raise  NotImplementedError\n\n    byte_stream = dicom.PixelData\n    offset = byte_stream.find(b\"\\x00\\x00\\x00\\x0C\")\n    jpeg_stream = np.asarray(bytearray(byte_stream[offset:]), dtype='uint8')\n\n   #failed because out of memory ...\n    m1 = nj.decode(jpeg_stream) \n\n   #this works! meaning that i have get the correct jpeg byte string\n    m0 = cv2.imdecode(jpeg_stream, cv2.IMREAD_ANYDEPTH)\n    print(m0.shape)\n    plt.imshow(m0)\n\n\n```\n\n\n",
      "votes": 3,
      "replies": [
        {
          "id": 2061308,
          "postDate": "2022-12-11T00:34:13.773Z",
          "content": "<p>Interesting.  Does this actually work?  If so, it'd be great to benchmark it.  Augmenting off GPU would be better I think.  You're going to be quite constrained with the GPU if you're limited to dali augmentation.</p>",
          "rawMarkdown": "Interesting.  Does this actually work?  If so, it'd be great to benchmark it.  Augmenting off GPU would be better I think.  You're going to be quite constrained with the GPU if you're limited to dali augmentation."
        },
        {
          "id": 2061312,
          "postDate": "2022-12-11T01:16:06.217Z",
          "content": "<p><br>\n</p>\n<p></p>\n<p>i make a mistake. i should be using nvJPEG2K instead</p>",
          "rawMarkdown": "~~maybe torch vision already support nvjpeg ???~~\n~~https://pytorch.org/vision/main/generated/torchvision.io.decode_jpeg.html~~\n\n~~\"If a cuda device is specified, the image will be decoded with nvjpeg.\"~~\n\n i make a mistake. i should be using nvJPEG2K instead"
        },
        {
          "id": 2061313,
          "postDate": "2022-12-11T01:17:04.137Z",
          "content": "<p>Dali also uses nvjpeg, nvjpeg2k</p>",
          "rawMarkdown": "Dali also uses nvjpeg, nvjpeg2k"
        },
        {
          "id": 2061318,
          "postDate": "2022-12-11T01:26:48.660Z",
          "content": "<p></p>\n<p></p>\n<p><br>\n</p>\n<p>i make a mistake. i should be using nvJPEG2K instead<br>\n<a href=\"https://docs.nvidia.com/cuda/nvjpeg2000/userguide.html\" target=\"_blank\">https://docs.nvidia.com/cuda/nvjpeg2000/userguide.html</a><br>\nJPEG2000 Options:<br>\n    Up to 16 bits per component<br>\n(nvjpeg is 8bits per component)</p>",
          "rawMarkdown": "~~i want o confirm if DALI actually support 16bit decoding for jpeg2000?~~\n\n~~https://github.com/NVIDIA/DALI/issues/3275~~\n\n~~\"This feature would be greatly appreciated. I was hoping to use DALI for decoding 16-bit J2K images.\"~~\n~~\"The decoding itself is just part of the limitations.\"~~\n\ni make a mistake. i should be using nvJPEG2K instead\nhttps://docs.nvidia.com/cuda/nvjpeg2000/userguide.html\nJPEG2000 Options:\n    Up to 16 bits per component\n(nvjpeg is 8bits per component)"
        },
        {
          "id": 2061326,
          "postDate": "2022-12-11T01:51:23.977Z",
          "content": "<p>Someone should let them know, lol</p>",
          "rawMarkdown": "Someone should let them know, lol"
        },
        {
          "id": 2061359,
          "postDate": "2022-12-11T03:51:08.073Z",
          "content": "<p>Dali nightly whl supports 16bit, it uses nvjpeg2k under the hood</p>",
          "rawMarkdown": "Dali nightly whl supports 16bit, it uses nvjpeg2k under the hood"
        }
      ]
    },
    {
      "id": 2062485,
      "postDate": "2022-12-12T06:37:08.850Z",
      "content": "<p>to quickly identidy the type of jpeg encoding, one can use machine id:</p>\n<pre><code>train_df[['machine_id','TransferSyntaxUID','image_id']].groupby(['machine_id','TransferSyntaxUID']).count()\nOut[7]: \n                                   image_id\nmachine_id TransferSyntaxUID               \n21         1.2.840.10008.1.2.4.90      8221\n29         1.2.840.10008.1.2.4.90      8267\n48         1.2.840.10008.1.2.4.90      8699\n49         1.2.840.10008.1.2.4.70     23529\n93         1.2.840.10008.1.2.4.70      1915\n170        1.2.840.10008.1.2.4.70       923\n190        1.2.840.10008.1.2.4.70       145\n197        1.2.840.10008.1.2.4.70        29\n210        1.2.840.10008.1.2.4.70      1070\n216        1.2.840.10008.1.2.4.70      1908\n</code></pre>",
      "rawMarkdown": "to quickly identidy the type of jpeg encoding, one can use machine id:\n\n```\ntrain_df[['machine_id','TransferSyntaxUID','image_id']].groupby(['machine_id','TransferSyntaxUID']).count()\nOut[7]: \n                                   image_id\nmachine_id TransferSyntaxUID               \n21         1.2.840.10008.1.2.4.90      8221\n29         1.2.840.10008.1.2.4.90      8267\n48         1.2.840.10008.1.2.4.90      8699\n49         1.2.840.10008.1.2.4.70     23529\n93         1.2.840.10008.1.2.4.70      1915\n170        1.2.840.10008.1.2.4.70       923\n190        1.2.840.10008.1.2.4.70       145\n197        1.2.840.10008.1.2.4.70        29\n210        1.2.840.10008.1.2.4.70      1070\n216        1.2.840.10008.1.2.4.70      1908\n\n\n```",
      "votes": 1,
      "replies": [
        {
          "id": 2062489,
          "postDate": "2022-12-12T06:44:22.040Z",
          "content": "<p>Or just site id :)  Site_id 1 is .70, site_id 2 is .90 .. Need to check if it's the same on the leaderboard</p>",
          "rawMarkdown": "Or just site id :)  Site_id 1 is .70, site_id 2 is .90 .. Need to check if it's the same on the leaderboard",
          "votes": 1
        },
        {
          "id": 2062834,
          "postDate": "2022-12-12T13:17:56.317Z",
          "content": "<p>It makes sense that the dicom format produced would be consistent by machine_id</p>",
          "rawMarkdown": "It makes sense that the dicom format produced would be consistent by machine_id"
        }
      ]
    },
    {
      "id": 2061226,
      "postDate": "2022-12-10T21:04:13.317Z",
      "content": "<p>Wow. This is something great to know. Kudos.</p>",
      "rawMarkdown": "Wow. This is something great to know. Kudos.",
      "votes": 1
    },
    {
      "id": 2143523,
      "postDate": "2023-02-14T10:13:54.887Z",
      "content": "<p>Wow! very fast decoding. It must be really useful</p>",
      "rawMarkdown": "Wow! very fast decoding. It must be really useful\n"
    },
    {
      "id": 2139878,
      "postDate": "2023-02-11T08:53:52.587Z",
      "content": "<p>hi , i get this error when use notebook ;<br>\ncan help me?</p>\n<p>RuntimeError: Critical error in pipeline:<br>\nError when executing Mixed operator experimental__decoders__Image encountered:<br>\nError in thread 0: [/opt/dali/dali/imgcodec/image_decoder.cc:448] Cannot parse the image: <br>\nStacktrace (7 entries):<br>\n[frame 0]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_imgcodec.so(+0x94bcb) [0x7f8ef22d6bcb]<br>\n[frame 1]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_imgcodec.so(+0x6bb18) [0x7f8ef22adb18]<br>\n[frame 2]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_operators.so(+0x26ce484) [0x7f8ef60aa484]<br>\n[frame 3]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali.so(dali::ThreadPool::ThreadMain(int, int, bool, std::string const&amp;)+0x1e6) [0x7f8f23325896]<br>\n[frame 4]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali.so(+0x691150) [0x7f8f23824150]<br>\n[frame 5]: /lib/x86_64-linux-gnu/libpthread.so.0(+0x8609) [0x7f8f6a4fa609]<br>\n[frame 6]: /lib/x86_64-linux-gnu/libc.so.6(clone+0x43) [0x7f8f6a2b9133]</p>\n<p>Current pipeline object is no longer valid.</p>",
      "rawMarkdown": "hi , i get this error when use notebook ;\ncan help me?\n\nRuntimeError: Critical error in pipeline:\nError when executing Mixed operator experimental__decoders__Image encountered:\nError in thread 0: [/opt/dali/dali/imgcodec/image_decoder.cc:448] Cannot parse the image: \nStacktrace (7 entries):\n[frame 0]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_imgcodec.so(+0x94bcb) [0x7f8ef22d6bcb]\n[frame 1]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_imgcodec.so(+0x6bb18) [0x7f8ef22adb18]\n[frame 2]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_operators.so(+0x26ce484) [0x7f8ef60aa484]\n[frame 3]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali.so(dali::ThreadPool::ThreadMain(int, int, bool, std::string const&)+0x1e6) [0x7f8f23325896]\n[frame 4]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali.so(+0x691150) [0x7f8f23824150]\n[frame 5]: /lib/x86_64-linux-gnu/libpthread.so.0(+0x8609) [0x7f8f6a4fa609]\n[frame 6]: /lib/x86_64-linux-gnu/libc.so.6(clone+0x43) [0x7f8f6a2b9133]\n\nCurrent pipeline object is no longer valid."
    },
    {
      "id": 2062299,
      "postDate": "2022-12-12T01:19:52.183Z",
      "content": "<p>Some background on .70</p>\n<p><a href=\"https://crnl.readthedocs.io/jpeg_formats/index.html\" target=\"_blank\">https://crnl.readthedocs.io/jpeg_formats/index.html</a></p>\n<p>Compressed lossless JPEG</p>\n<blockquote>\n  <p>Common in DICOM. Transfer syntaxes 1.2.840.10008.1.2.4.57 and 1.2.840.10008.1.2.4.70 refer to a lossless JPEG format that is exceptionally rare outside of the medical domain (and completely different from both the lossless JPEG-LS and lossless JPEG-2000 encoding formats). While this was fully described in the JPEG ISO/IEC 10918-1:1994 T.81 (09/92), it did not gain traction outside of medical imaging (where GIF and PNG became the most popular lossless formats). This legacy lossless JPEG is a simple format, and only uses the Huffman encoding without the typical discrete cosine transforms (DCT). However, the fact that these images are typically saved with 16-bit precision means it is not supported by most libraries, and they generate an error saying they can not decode “SOF type 0xc3”. I have written my own library to support this format, though other tools (e.g. dcmtk) use custom-patched variations of the IJG library. This is not a very efficient compression method, and personally I would strongly recommend users investigate file-based compression (.zip, or disk driver enabled compression) over this arcane format. This is the default output of dcmcjpeg, probably explaining its widespread popularity, for example the command “./dcmcjpeg in.dcm out.dcm” generates a DICOM image with format 1.2.840.10008.1.2.4.70, while the command “./dcmcjpeg +el in.dcm out.dcm” generates an image with syntax 1.2.840.10008.1.2.4.57. You can also create these files withgdcmconv (e.g. ‘gdcmconv -J in.dcm out.dcm’).</p>\n</blockquote>",
      "rawMarkdown": "Some background on .70\n\nhttps://crnl.readthedocs.io/jpeg_formats/index.html\n\nCompressed lossless JPEG\n> Common in DICOM. Transfer syntaxes 1.2.840.10008.1.2.4.57 and 1.2.840.10008.1.2.4.70 refer to a lossless JPEG format that is exceptionally rare outside of the medical domain (and completely different from both the lossless JPEG-LS and lossless JPEG-2000 encoding formats). While this was fully described in the JPEG ISO/IEC 10918-1:1994 T.81 (09/92), it did not gain traction outside of medical imaging (where GIF and PNG became the most popular lossless formats). This legacy lossless JPEG is a simple format, and only uses the Huffman encoding without the typical discrete cosine transforms (DCT). However, the fact that these images are typically saved with 16-bit precision means it is not supported by most libraries, and they generate an error saying they can not decode “SOF type 0xc3”. I have written my own library to support this format, though other tools (e.g. dcmtk) use custom-patched variations of the IJG library. This is not a very efficient compression method, and personally I would strongly recommend users investigate file-based compression (.zip, or disk driver enabled compression) over this arcane format. This is the default output of dcmcjpeg, probably explaining its widespread popularity, for example the command “./dcmcjpeg in.dcm out.dcm” generates a DICOM image with format 1.2.840.10008.1.2.4.70, while the command “./dcmcjpeg +el in.dcm out.dcm” generates an image with syntax 1.2.840.10008.1.2.4.57. You can also create these files withgdcmconv (e.g. ‘gdcmconv -J in.dcm out.dcm’).\n",
      "replies": [
        {
          "id": 2062324,
          "postDate": "2022-12-12T02:06:55.160Z",
          "content": "<p>Thanks for sharing.  I'm working with dcmcjpeg right now to generate .70 format images and compare them to the compressed jpeg-ls images, the difference should yield insights on how to hack the bytesio.</p>",
          "rawMarkdown": "Thanks for sharing.  I'm working with dcmcjpeg right now to generate .70 format images and compare them to the compressed jpeg-ls images, the difference should yield insights on how to hack the bytesio."
        },
        {
          "id": 2063456,
          "postDate": "2022-12-13T00:06:03.773Z",
          "content": "<p>How does pydicom / gdcm extract the image for gdcm to decode it?</p>\n<p><a href=\"https://github.com/pydicom/pydicom/blob/master/pydicom/pixel_data_handlers/gdcm_handler.py\" target=\"_blank\">https://github.com/pydicom/pydicom/blob/master/pydicom/pixel_data_handlers/gdcm_handler.py</a></p>\n<pre><code>SUPPORTED_TRANSFER_SYNTAXES = [\n    pydicom.uid.JPEGBaseline8Bit,\n    pydicom.uid.JPEGExtended12Bit,\n    pydicom.uid.JPEGLosslessP14,\n    pydicom.uid.JPEGLosslessSV1,\n\n\nJPEGLosslessSV1 = UID()  \n</code></pre>\n<pre><code>\n    \n    \n    new = ds.group_dataset()\n    new[] = ds[]  \n    new.file_meta = ds.file_meta\n     NamedTemporaryFile(, delete=)  t:\n        new.save_as(t)\n</code></pre>\n<p>I guess gdcm does some stuff</p>",
          "rawMarkdown": "How does pydicom / gdcm extract the image for gdcm to decode it?\n\n\nhttps://github.com/pydicom/pydicom/blob/master/pydicom/pixel_data_handlers/gdcm_handler.py\n\n```python\nSUPPORTED_TRANSFER_SYNTAXES = [\n    pydicom.uid.JPEGBaseline8Bit,\n    pydicom.uid.JPEGExtended12Bit,\n    pydicom.uid.JPEGLosslessP14,\n    pydicom.uid.JPEGLosslessSV1,\n\n\nJPEGLosslessSV1 = UID(\"1.2.840.10008.1.2.4.70\")  # Old JPEGLossless\n```\n\n\n```python\n# Copy the relevant elements and write to a temporary file to avoid\n    #   having to deal with all the possible objects the dataset may\n    #   originate with\n    new = ds.group_dataset(0x0028)\n    new[\"PixelData\"] = ds[\"PixelData\"]  # avoid ambiguous VR\n    new.file_meta = ds.file_meta\n    with NamedTemporaryFile('wb', delete=False) as t:\n        new.save_as(t)\n```\n\nI guess gdcm does some stuff"
        }
      ]
    },
    {
      "id": 2061096,
      "postDate": "2022-12-10T17:26:22.970Z",
      "content": "<p>Nice! Great notebook and solution to learn new things. Looking inside notebook - thanks for contributing.</p>",
      "rawMarkdown": "Nice! Great notebook and solution to learn new things. Looking inside notebook - thanks for contributing."
    },
    {
      "id": 2062285,
      "postDate": "2022-12-12T00:08:35.660Z",
      "rawMarkdown": "",
      "isDeleted": true
    }
  ],
  "comments": [
    {
      "id": 2063328,
      "author_name": "David Austin",
      "author_url": "",
      "post_date": "2022-12-12T20:06:12.180000",
      "content": "<p>Update: The byte string to properly extract lossless jpg images can be done using this offset with the same code as before.</p>\n<p><code>\noffset = ds.PixelData.find(b\"\\xff\\xd8\\xff\\xe0\")\n</code><br>\nHowever, decode of lossless jpg is not currenly supported by nvjpeg on gpu.  For now the best known solution is to combine dali on jpeg2k images with dicomsdl for lossless jpg.  <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">@hengck23</a> just posted a notebook <a href=\"https://www.kaggle.com/code/hengck23/combine-dali-and-dicomsdl-for-reading-dicom-files\" target=\"_blank\">here</a> that combines the two methods beautifully.</p>",
      "votes": 7,
      "replies": [
        {
          "id": 2064525,
          "author_name": "@kaggleqrdl",
          "author_url": "",
          "post_date": "2022-12-13T20:49:26.200000",
          "content": "<p>The question isn't pydicom versus dicomsdl versus dali, but rather nvjpeg versus gdcm versus libjpeg-turbo versus..</p>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 2064128,
      "author_name": "hengck23",
      "author_url": "",
      "post_date": "2022-12-13T14:36:45.360000",
      "content": "<p><img src=\"https://i.ibb.co/Gs3zFH1/Selection-169.png\" alt=\"https://i.ibb.co/Gs3zFH1/Selection-169.png\"></p>",
      "votes": 5,
      "replies": []
    },
    {
      "id": 2061228,
      "author_name": "hengck23",
      "author_url": "",
      "post_date": "2022-12-10T21:08:03.027000",
      "content": "<p>  <br>\ni make a mistake. i should be using nvJPEG2K instead</p>\n<p>i think your pipeline can be simplified as:</p>\n<pre><code>#https://github.com/UsingNet/nvjpeg-python\nfrom nvjpeg import NvJpeg\nnj = NvJpeg()\n.....\n\n\n\n    dicom = pydicom.dcmread(f)\n    if dicom.file_meta.TransferSyntaxUID != '1.2.840.10008.1.2.4.90':\n        raise  NotImplementedError\n\n    byte_stream = dicom.PixelData\n    offset = byte_stream.find(b\"\\x00\\x00\\x00\\x0C\")\n    jpeg_stream = np.asarray(bytearray(byte_stream[offset:]), dtype='uint8')\n\n   #failed because out of memory ...\n    m1 = nj.decode(jpeg_stream) \n\n   #this works! meaning that i have get the correct jpeg byte string\n    m0 = cv2.imdecode(jpeg_stream, cv2.IMREAD_ANYDEPTH)\n    print(m0.shape)\n    plt.imshow(m0)\n</code></pre>",
      "votes": 3,
      "replies": [
        {
          "id": 2061308,
          "author_name": "@kaggleqrdl",
          "author_url": "",
          "post_date": "2022-12-11T00:34:13.773000",
          "content": "<p>Interesting.  Does this actually work?  If so, it'd be great to benchmark it.  Augmenting off GPU would be better I think.  You're going to be quite constrained with the GPU if you're limited to dali augmentation.</p>",
          "votes": 0,
          "replies": []
        },
        {
          "id": 2061312,
          "author_name": "hengck23",
          "author_url": "",
          "post_date": "2022-12-11T01:16:06.217000",
          "content": "<p><br>\n</p>\n<p></p>\n<p>i make a mistake. i should be using nvJPEG2K instead</p>",
          "votes": 0,
          "replies": []
        },
        {
          "id": 2061313,
          "author_name": "hengck23",
          "author_url": "",
          "post_date": "2022-12-11T01:17:04.137000",
          "content": "<p>Dali also uses nvjpeg, nvjpeg2k</p>",
          "votes": 0,
          "replies": []
        },
        {
          "id": 2061318,
          "author_name": "hengck23",
          "author_url": "",
          "post_date": "2022-12-11T01:26:48.660000",
          "content": "<p></p>\n<p></p>\n<p><br>\n</p>\n<p>i make a mistake. i should be using nvJPEG2K instead<br>\n<a href=\"https://docs.nvidia.com/cuda/nvjpeg2000/userguide.html\" target=\"_blank\">https://docs.nvidia.com/cuda/nvjpeg2000/userguide.html</a><br>\nJPEG2000 Options:<br>\n    Up to 16 bits per component<br>\n(nvjpeg is 8bits per component)</p>",
          "votes": 0,
          "replies": []
        },
        {
          "id": 2061326,
          "author_name": "@kaggleqrdl",
          "author_url": "",
          "post_date": "2022-12-11T01:51:23.977000",
          "content": "<p>Someone should let them know, lol</p>",
          "votes": 0,
          "replies": []
        },
        {
          "id": 2061359,
          "author_name": "David Austin",
          "author_url": "",
          "post_date": "2022-12-11T03:51:08.073000",
          "content": "<p>Dali nightly whl supports 16bit, it uses nvjpeg2k under the hood</p>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 2062485,
      "author_name": "hengck23",
      "author_url": "",
      "post_date": "2022-12-12T06:37:08.850000",
      "content": "<p>to quickly identidy the type of jpeg encoding, one can use machine id:</p>\n<pre><code>train_df[['machine_id','TransferSyntaxUID','image_id']].groupby(['machine_id','TransferSyntaxUID']).count()\nOut[7]: \n                                   image_id\nmachine_id TransferSyntaxUID               \n21         1.2.840.10008.1.2.4.90      8221\n29         1.2.840.10008.1.2.4.90      8267\n48         1.2.840.10008.1.2.4.90      8699\n49         1.2.840.10008.1.2.4.70     23529\n93         1.2.840.10008.1.2.4.70      1915\n170        1.2.840.10008.1.2.4.70       923\n190        1.2.840.10008.1.2.4.70       145\n197        1.2.840.10008.1.2.4.70        29\n210        1.2.840.10008.1.2.4.70      1070\n216        1.2.840.10008.1.2.4.70      1908\n</code></pre>",
      "votes": 1,
      "replies": [
        {
          "id": 2062489,
          "author_name": "@kaggleqrdl",
          "author_url": "",
          "post_date": "2022-12-12T06:44:22.040000",
          "content": "<p>Or just site id :)  Site_id 1 is .70, site_id 2 is .90 .. Need to check if it's the same on the leaderboard</p>",
          "votes": 1,
          "replies": []
        },
        {
          "id": 2062834,
          "author_name": "David Austin",
          "author_url": "",
          "post_date": "2022-12-12T13:17:56.317000",
          "content": "<p>It makes sense that the dicom format produced would be consistent by machine_id</p>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 2061226,
      "author_name": "Sandy",
      "author_url": "",
      "post_date": "2022-12-10T21:04:13.317000",
      "content": "<p>Wow. This is something great to know. Kudos.</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2143523,
      "author_name": "junseonglee11",
      "author_url": "",
      "post_date": "2023-02-14T10:13:54.887000",
      "content": "<p>Wow! very fast decoding. It must be really useful</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 2139878,
      "author_name": "Fatemeh Shahsavari",
      "author_url": "",
      "post_date": "2023-02-11T08:53:52.587000",
      "content": "<p>hi , i get this error when use notebook ;<br>\ncan help me?</p>\n<p>RuntimeError: Critical error in pipeline:<br>\nError when executing Mixed operator experimental__decoders__Image encountered:<br>\nError in thread 0: [/opt/dali/dali/imgcodec/image_decoder.cc:448] Cannot parse the image: <br>\nStacktrace (7 entries):<br>\n[frame 0]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_imgcodec.so(+0x94bcb) [0x7f8ef22d6bcb]<br>\n[frame 1]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_imgcodec.so(+0x6bb18) [0x7f8ef22adb18]<br>\n[frame 2]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_operators.so(+0x26ce484) [0x7f8ef60aa484]<br>\n[frame 3]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali.so(dali::ThreadPool::ThreadMain(int, int, bool, std::string const&amp;)+0x1e6) [0x7f8f23325896]<br>\n[frame 4]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali.so(+0x691150) [0x7f8f23824150]<br>\n[frame 5]: /lib/x86_64-linux-gnu/libpthread.so.0(+0x8609) [0x7f8f6a4fa609]<br>\n[frame 6]: /lib/x86_64-linux-gnu/libc.so.6(clone+0x43) [0x7f8f6a2b9133]</p>\n<p>Current pipeline object is no longer valid.</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 2062299,
      "author_name": "@kaggleqrdl",
      "author_url": "",
      "post_date": "2022-12-12T01:19:52.183000",
      "content": "<p>Some background on .70</p>\n<p><a href=\"https://crnl.readthedocs.io/jpeg_formats/index.html\" target=\"_blank\">https://crnl.readthedocs.io/jpeg_formats/index.html</a></p>\n<p>Compressed lossless JPEG</p>\n<blockquote>\n  <p>Common in DICOM. Transfer syntaxes 1.2.840.10008.1.2.4.57 and 1.2.840.10008.1.2.4.70 refer to a lossless JPEG format that is exceptionally rare outside of the medical domain (and completely different from both the lossless JPEG-LS and lossless JPEG-2000 encoding formats). While this was fully described in the JPEG ISO/IEC 10918-1:1994 T.81 (09/92), it did not gain traction outside of medical imaging (where GIF and PNG became the most popular lossless formats). This legacy lossless JPEG is a simple format, and only uses the Huffman encoding without the typical discrete cosine transforms (DCT). However, the fact that these images are typically saved with 16-bit precision means it is not supported by most libraries, and they generate an error saying they can not decode “SOF type 0xc3”. I have written my own library to support this format, though other tools (e.g. dcmtk) use custom-patched variations of the IJG library. This is not a very efficient compression method, and personally I would strongly recommend users investigate file-based compression (.zip, or disk driver enabled compression) over this arcane format. This is the default output of dcmcjpeg, probably explaining its widespread popularity, for example the command “./dcmcjpeg in.dcm out.dcm” generates a DICOM image with format 1.2.840.10008.1.2.4.70, while the command “./dcmcjpeg +el in.dcm out.dcm” generates an image with syntax 1.2.840.10008.1.2.4.57. You can also create these files withgdcmconv (e.g. ‘gdcmconv -J in.dcm out.dcm’).</p>\n</blockquote>",
      "votes": 0,
      "replies": [
        {
          "id": 2062324,
          "author_name": "David Austin",
          "author_url": "",
          "post_date": "2022-12-12T02:06:55.160000",
          "content": "<p>Thanks for sharing.  I'm working with dcmcjpeg right now to generate .70 format images and compare them to the compressed jpeg-ls images, the difference should yield insights on how to hack the bytesio.</p>",
          "votes": 0,
          "replies": []
        },
        {
          "id": 2063456,
          "author_name": "@kaggleqrdl",
          "author_url": "",
          "post_date": "2022-12-13T00:06:03.773000",
          "content": "<p>How does pydicom / gdcm extract the image for gdcm to decode it?</p>\n<p><a href=\"https://github.com/pydicom/pydicom/blob/master/pydicom/pixel_data_handlers/gdcm_handler.py\" target=\"_blank\">https://github.com/pydicom/pydicom/blob/master/pydicom/pixel_data_handlers/gdcm_handler.py</a></p>\n<pre><code>SUPPORTED_TRANSFER_SYNTAXES = [\n    pydicom.uid.JPEGBaseline8Bit,\n    pydicom.uid.JPEGExtended12Bit,\n    pydicom.uid.JPEGLosslessP14,\n    pydicom.uid.JPEGLosslessSV1,\n\n\nJPEGLosslessSV1 = UID()  \n</code></pre>\n<pre><code>\n    \n    \n    new = ds.group_dataset()\n    new[] = ds[]  \n    new.file_meta = ds.file_meta\n     NamedTemporaryFile(, delete=)  t:\n        new.save_as(t)\n</code></pre>\n<p>I guess gdcm does some stuff</p>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 2061096,
      "author_name": "Remek Kinas",
      "author_url": "",
      "post_date": "2022-12-10T17:26:22.970000",
      "content": "<p>Nice! Great notebook and solution to learn new things. Looking inside notebook - thanks for contributing.</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 2062285,
      "author_name": "",
      "author_url": "",
      "post_date": "2022-12-12T00:08:35.660000",
      "content": "",
      "votes": 0,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "2061084": "**TLDR:**\nApprox half the dicoms in the dataset contain jpeg2000 encoded images.  There's a way to hack the bitstream and extract/save the jp2 images which can then be decoded on GPU using [Nvidia DALI](https://docs.nvidia.com/deeplearning/dali/user-guide/docs/index.html).\n\npydicom decode per image (parallel mode): **0.88** imgs/sec (32 imgs/36 sec)\nDALI decode per image: **15.16** imgs/sec (32 imgs/2.03s)\nSpeedup: **17.7x**\n\nCheck out the [notebook](https://www.kaggle.com/code/tivfrvqhs5/decode-jpeg2000-dicom-with-dali?scriptVersionId=113466193) here which contains the minimal decode example.  Still working on a way to extract the lossless jpeg bitstream for the other half of dicoms which will then make this technique much more useful in an end-to-end solution.\n\nA little more:\nWhile DALI doesn't support decoding dicom's directly, it does support jpeg2000 decode.  So the trick is extracting the jpeg2000 bitstream from the dicom container so DALI can ingest it.\n\nI'm still working on figuring out an analogous hack for the lossless jpeg format.  Any inputs welcome.\n\n\n\n\n\n",
    "2063328": "Update: The byte string to properly extract lossless jpg images can be done using this offset with the same code as before.\n\n`\noffset = ds.PixelData.find(b\"\\xff\\xd8\\xff\\xe0\")\n`\nHowever, decode of lossless jpg is not currenly supported by nvjpeg on gpu.  For now the best known solution is to combine dali on jpeg2k images with dicomsdl for lossless jpg.  @hengck23 just posted a notebook [here](https://www.kaggle.com/code/hengck23/combine-dali-and-dicomsdl-for-reading-dicom-files) that combines the two methods beautifully.",
    "2064128": "![https://i.ibb.co/Gs3zFH1/Selection-169.png](https://i.ibb.co/Gs3zFH1/Selection-169.png)",
    "2061228": "~~i have been trying to use nvjpeg directly, but failed.~~  \ni make a mistake. i should be using nvJPEG2K instead\n\ni think your pipeline can be simplified as:\n\n```\n#https://github.com/UsingNet/nvjpeg-python\nfrom nvjpeg import NvJpeg\nnj = NvJpeg()\n.....\n\n\n\n    dicom = pydicom.dcmread(f)\n    if dicom.file_meta.TransferSyntaxUID != '1.2.840.10008.1.2.4.90':\n        raise  NotImplementedError\n\n    byte_stream = dicom.PixelData\n    offset = byte_stream.find(b\"\\x00\\x00\\x00\\x0C\")\n    jpeg_stream = np.asarray(bytearray(byte_stream[offset:]), dtype='uint8')\n\n   #failed because out of memory ...\n    m1 = nj.decode(jpeg_stream) \n\n   #this works! meaning that i have get the correct jpeg byte string\n    m0 = cv2.imdecode(jpeg_stream, cv2.IMREAD_ANYDEPTH)\n    print(m0.shape)\n    plt.imshow(m0)\n\n\n```\n\n\n",
    "2062485": "to quickly identidy the type of jpeg encoding, one can use machine id:\n\n```\ntrain_df[['machine_id','TransferSyntaxUID','image_id']].groupby(['machine_id','TransferSyntaxUID']).count()\nOut[7]: \n                                   image_id\nmachine_id TransferSyntaxUID               \n21         1.2.840.10008.1.2.4.90      8221\n29         1.2.840.10008.1.2.4.90      8267\n48         1.2.840.10008.1.2.4.90      8699\n49         1.2.840.10008.1.2.4.70     23529\n93         1.2.840.10008.1.2.4.70      1915\n170        1.2.840.10008.1.2.4.70       923\n190        1.2.840.10008.1.2.4.70       145\n197        1.2.840.10008.1.2.4.70        29\n210        1.2.840.10008.1.2.4.70      1070\n216        1.2.840.10008.1.2.4.70      1908\n\n\n```",
    "2061226": "Wow. This is something great to know. Kudos.",
    "2143523": "Wow! very fast decoding. It must be really useful\n",
    "2139878": "hi , i get this error when use notebook ;\ncan help me?\n\nRuntimeError: Critical error in pipeline:\nError when executing Mixed operator experimental__decoders__Image encountered:\nError in thread 0: [/opt/dali/dali/imgcodec/image_decoder.cc:448] Cannot parse the image: \nStacktrace (7 entries):\n[frame 0]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_imgcodec.so(+0x94bcb) [0x7f8ef22d6bcb]\n[frame 1]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_imgcodec.so(+0x6bb18) [0x7f8ef22adb18]\n[frame 2]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali_operators.so(+0x26ce484) [0x7f8ef60aa484]\n[frame 3]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali.so(dali::ThreadPool::ThreadMain(int, int, bool, std::string const&)+0x1e6) [0x7f8f23325896]\n[frame 4]: /opt/conda/lib/python3.7/site-packages/nvidia/dali/libdali.so(+0x691150) [0x7f8f23824150]\n[frame 5]: /lib/x86_64-linux-gnu/libpthread.so.0(+0x8609) [0x7f8f6a4fa609]\n[frame 6]: /lib/x86_64-linux-gnu/libc.so.6(clone+0x43) [0x7f8f6a2b9133]\n\nCurrent pipeline object is no longer valid.",
    "2062299": "Some background on .70\n\nhttps://crnl.readthedocs.io/jpeg_formats/index.html\n\nCompressed lossless JPEG\n> Common in DICOM. Transfer syntaxes 1.2.840.10008.1.2.4.57 and 1.2.840.10008.1.2.4.70 refer to a lossless JPEG format that is exceptionally rare outside of the medical domain (and completely different from both the lossless JPEG-LS and lossless JPEG-2000 encoding formats). While this was fully described in the JPEG ISO/IEC 10918-1:1994 T.81 (09/92), it did not gain traction outside of medical imaging (where GIF and PNG became the most popular lossless formats). This legacy lossless JPEG is a simple format, and only uses the Huffman encoding without the typical discrete cosine transforms (DCT). However, the fact that these images are typically saved with 16-bit precision means it is not supported by most libraries, and they generate an error saying they can not decode “SOF type 0xc3”. I have written my own library to support this format, though other tools (e.g. dcmtk) use custom-patched variations of the IJG library. This is not a very efficient compression method, and personally I would strongly recommend users investigate file-based compression (.zip, or disk driver enabled compression) over this arcane format. This is the default output of dcmcjpeg, probably explaining its widespread popularity, for example the command “./dcmcjpeg in.dcm out.dcm” generates a DICOM image with format 1.2.840.10008.1.2.4.70, while the command “./dcmcjpeg +el in.dcm out.dcm” generates an image with syntax 1.2.840.10008.1.2.4.57. You can also create these files withgdcmconv (e.g. ‘gdcmconv -J in.dcm out.dcm’).\n",
    "2061096": "Nice! Great notebook and solution to learn new things. Looking inside notebook - thanks for contributing.",
    "2062285": ""
  }
}