{
  "id": 598083,
  "title": "For those having difficulty loading DICOM image data",
  "url": "/competitions/rsna-intracranial-aneurysm-detection/discussion/598083",
  "author_name": "Evan Calabrese",
  "post_date": "2025-08-08T15:32:31.337000",
  "votes": 16,
  "comment_count": 2,
  "views": 0,
  "content": "<p>I have seen a few comments on this discussion board about various difficulties in loading DICOM data into 3D image arrays. I wanted to provide my approach for this in case it is helpful for someone. However, important to acknowledge that there are many ways to do this, and mine is not necessarily the best.</p>\n<p>DICOM is a complex format with many different ways of structuring the underlying imaging data. In my opinion, its a terrible format for data analysis; however, it is the reality of current clinical practice, which is why we chose to provide DICOM data instead of a more data analysis friendly format.</p>\n<p>When I am trying to process DICOM image data, the first thing I do is convert to NIfTI format using <code>dcm2niix</code>. This is publicly available software (<a href=\"https://github.com/rordenlab/dcm2niix\" target=\"_blank\">https://github.com/rordenlab/dcm2niix</a>) that automates the process of DICOM to NIfTI conversion including graceful handling of a large majority of edge cases. The <code>dcm2niix</code> binary can be easily interfaced/pipelined with <code>nipype</code> see <a href=\"https://nipype.readthedocs.io/en/latest/api/generated/nipype.interfaces.dcm2nii.html#dcm2niix\" target=\"_blank\">docs here</a>. There is also a pip package available (<a href=\"https://pypi.org/project/dcm2niix/)\" target=\"_blank\">https://pypi.org/project/dcm2niix/)</a>, though I have limited experience with this. <br>\nWhen using <code>dcm2niix</code>, the key conversion flags that you will want to consider are:<br>\n<code>-b</code>, which allows you to output DICOM header variables as JSON alongside your images<br>\n<code>-i</code>, which allows you to ignore stacked derived/localizer images (see <a href=\"https://www.kaggle.com/competitions/rsna-intracranial-aneurysm-detection/discussion/596183\" target=\"_blank\">this thread</a>)<br>\n<code>-z</code>, which allows you gzip outputs to save considerable disk space<br>\nThe main benefit of <code>dcm2niix</code> is that it completely automates the otherwise complex process of extracting 3D <code>numpy</code> arrays from DICOM datasets. While it lacks the customizability of approaches using <code>pydicom</code>, it is much simpler, particularly for those without prior DICOM experience.</p>\n<p>NIfTI is a much simpler image format that can handle a large majority of cross-sectional medical imaging data. It is also extremely easy to load and manipulate using python and other image analysis softwares. <code>nibabel</code> is the essential python package for NIfTI file manipulation, and loading is a simple as <code>nibabel.load('/path/to/file.nii.gz')</code>.</p>",
  "messages": [
    {
      "id": 3266199,
      "postDate": "2025-08-08T15:32:31.337Z",
      "content": "<p>I have seen a few comments on this discussion board about various difficulties in loading DICOM data into 3D image arrays. I wanted to provide my approach for this in case it is helpful for someone. However, important to acknowledge that there are many ways to do this, and mine is not necessarily the best.</p>\n<p>DICOM is a complex format with many different ways of structuring the underlying imaging data. In my opinion, its a terrible format for data analysis; however, it is the reality of current clinical practice, which is why we chose to provide DICOM data instead of a more data analysis friendly format.</p>\n<p>When I am trying to process DICOM image data, the first thing I do is convert to NIfTI format using <code>dcm2niix</code>. This is publicly available software (<a href=\"https://github.com/rordenlab/dcm2niix\" target=\"_blank\">https://github.com/rordenlab/dcm2niix</a>) that automates the process of DICOM to NIfTI conversion including graceful handling of a large majority of edge cases. The <code>dcm2niix</code> binary can be easily interfaced/pipelined with <code>nipype</code> see <a href=\"https://nipype.readthedocs.io/en/latest/api/generated/nipype.interfaces.dcm2nii.html#dcm2niix\" target=\"_blank\">docs here</a>. There is also a pip package available (<a href=\"https://pypi.org/project/dcm2niix/)\" target=\"_blank\">https://pypi.org/project/dcm2niix/)</a>, though I have limited experience with this. <br>\nWhen using <code>dcm2niix</code>, the key conversion flags that you will want to consider are:<br>\n<code>-b</code>, which allows you to output DICOM header variables as JSON alongside your images<br>\n<code>-i</code>, which allows you to ignore stacked derived/localizer images (see <a href=\"https://www.kaggle.com/competitions/rsna-intracranial-aneurysm-detection/discussion/596183\" target=\"_blank\">this thread</a>)<br>\n<code>-z</code>, which allows you gzip outputs to save considerable disk space<br>\nThe main benefit of <code>dcm2niix</code> is that it completely automates the otherwise complex process of extracting 3D <code>numpy</code> arrays from DICOM datasets. While it lacks the customizability of approaches using <code>pydicom</code>, it is much simpler, particularly for those without prior DICOM experience.</p>\n<p>NIfTI is a much simpler image format that can handle a large majority of cross-sectional medical imaging data. It is also extremely easy to load and manipulate using python and other image analysis softwares. <code>nibabel</code> is the essential python package for NIfTI file manipulation, and loading is a simple as <code>nibabel.load('/path/to/file.nii.gz')</code>.</p>",
      "rawMarkdown": "I have seen a few comments on this discussion board about various difficulties in loading DICOM data into 3D image arrays. I wanted to provide my approach for this in case it is helpful for someone. However, important to acknowledge that there are many ways to do this, and mine is not necessarily the best.\n\nDICOM is a complex format with many different ways of structuring the underlying imaging data. In my opinion, its a terrible format for data analysis; however, it is the reality of current clinical practice, which is why we chose to provide DICOM data instead of a more data analysis friendly format.\n\nWhen I am trying to process DICOM image data, the first thing I do is convert to NIfTI format using ```dcm2niix```. This is publicly available software (https://github.com/rordenlab/dcm2niix) that automates the process of DICOM to NIfTI conversion including graceful handling of a large majority of edge cases. The ```dcm2niix``` binary can be easily interfaced/pipelined with ```nipype``` see [docs here](https://nipype.readthedocs.io/en/latest/api/generated/nipype.interfaces.dcm2nii.html#dcm2niix). There is also a pip package available (https://pypi.org/project/dcm2niix/), though I have limited experience with this. \nWhen using ```dcm2niix```, the key conversion flags that you will want to consider are:\n```-b```, which allows you to output DICOM header variables as JSON alongside your images\n```-i```, which allows you to ignore stacked derived/localizer images (see [this thread](https://www.kaggle.com/competitions/rsna-intracranial-aneurysm-detection/discussion/596183))\n```-z```, which allows you gzip outputs to save considerable disk space\nThe main benefit of ```dcm2niix``` is that it completely automates the otherwise complex process of extracting 3D ```numpy``` arrays from DICOM datasets. While it lacks the customizability of approaches using ```pydicom```, it is much simpler, particularly for those without prior DICOM experience.\n\nNIfTI is a much simpler image format that can handle a large majority of cross-sectional medical imaging data. It is also extremely easy to load and manipulate using python and other image analysis softwares. ```nibabel``` is the essential python package for NIfTI file manipulation, and loading is a simple as ```nibabel.load('/path/to/file.nii.gz')```.",
      "votes": 16
    },
    {
      "id": 3266814,
      "postDate": "2025-08-09T18:18:18.893Z",
      "content": "<h2>Experience with converting</h2>\n<p>Here’s a compact field-note I compiled from our work (joint with a ChatGPT-5 ); since this post is published under my name, a heads-up that most of the hands-on digging and write-up here is on my side. Hope it saves someone a few days. Wy fight we converting all dcms to NIfTI  is about 5 days.  But  still in progess.</p>\n<p>DICOM → NIfTI for brain aneurysm data: pitfalls &amp; fixes<br>\nBaseline tools: we convert with dcm2niix and keep the JSON sidecar (-b y), gzip outputs (-z y). When needed, we first “normalize” raw DICOM with gdcmconv --raw and then run dcm2niix.</p>\n<p>The traps we hit (and what worked)<br>\nMixed slice sizes / localizers in one folder<br>\nSymptoms: a series folder has scouts and the actual scan → different Rows/Cols.<br>\nFix: pre-audit and drop files whose matrix ≠ the mode; sort by InstanceNumber. Log the UID.</p>\n<p>Enhanced/multiframe DICOM &amp; hidden spacing<br>\nPixel spacing lives in FunctionalGroups (0028,9110); naïve pydicom reads miss it.<br>\nFix: prefer dcm2niix (handles this). If you must roll your own, parse functional groups.</p>\n<p>DERIVED / MPR vs “ignore derived”<br>\n-i y can throw away everything; -i n lets MPRs through.<br>\nFix: run with -i n, then filter post-hoc: keep isotropic, long volumes (e.g., Nz≥100, dz≤1.2 mm) even if DERIVED; send true slabs to a 2D/2.5D branch.</p>\n<p>RescaleSlope/Intercept after anonymization<br>\nBad slope/intercept → “grey rectangle” CTs.<br>\nFix: don’t pre-apply rescale yourself; let dcm2niix write scl_slope/inter into NIfTI. Apply HU on load only when slope/intercept ≠ identity; sanity-check with robust window percentiles.</p>\n<p>Ovals instead of circles (affine / spacing mix-ups)<br>\nFix: trust dcm2niix’s affine (from IOP/IPP). Verify dx≈dy and diagonal signs (LPS→RAS). If a head looks squashed, check pixdim first, not your viewer.</p>\n<p>RLE / uncommon transfer syntaxes<br>\nDecoder errors, “wraps”, corrupted offsets.<br>\nFix: gdcmconv --raw to a temp folder → then dcm2niix on the unpacked files.</p>\n<p>Throughput &amp; I/O pain (millions of files)<br>\nFix: parallelize (16–20 workers), write NIfTI with -z y, mount a tmpfs for intermediates, and write outputs to a separate fast disk.</p>\n<p>Quality control &amp; quarantine<br>\nFix: after conversion, compute per-volume: Nx,Ny,Nz,dx,dy,dz, intensity stats, and flags like derived_only, json_missing, int16_fullspan, slab. Keep a meta_summary.csv and suspects.csv; maintain a blacklist for truly broken cases.</p>\n<p>Brain masking (less junk, faster training)<br>\nFix: run SynthStrip; zero out outside the mask but dilate 3–5 vox and give 10–15 mm padding to avoid cutting cortex/foramen magnum. If SynthStrip fails, fallback = ellipse in XY ∪ largest head component (for CT via HU threshold). QC: brain_frac, brain_vol_ml, touch_border.</p>\n<p>Minimal flag cheat-sheet<br>\nDefault: dcm2niix -z y -b y -i n -o  -f %s </p>\n<p>If decoder complains: gdcmconv --raw /*.dcm // &amp;&amp; dcm2niix … //</p>\n<p>Takeaways<br>\ndcm2niix handles 90% of the weirdness; the rest is audit + simple rules.</p>\n<p>Always keep the JSON sidecar—it prevents guesswork later.</p>",
      "rawMarkdown": "##Experience with converting\n\nHere’s a compact field-note I compiled from our work (joint with a ChatGPT-5 ); since this post is published under my name, a heads-up that most of the hands-on digging and write-up here is on my side. Hope it saves someone a few days. Wy fight we converting all dcms to NIfTI  is about 5 days.  But  still in progess.\n\n\nDICOM → NIfTI for brain aneurysm data: pitfalls & fixes\nBaseline tools: we convert with dcm2niix and keep the JSON sidecar (-b y), gzip outputs (-z y). When needed, we first “normalize” raw DICOM with gdcmconv --raw and then run dcm2niix.\n\nThe traps we hit (and what worked)\nMixed slice sizes / localizers in one folder\nSymptoms: a series folder has scouts and the actual scan → different Rows/Cols.\nFix: pre-audit and drop files whose matrix ≠ the mode; sort by InstanceNumber. Log the UID.\n\nEnhanced/multiframe DICOM & hidden spacing\nPixel spacing lives in FunctionalGroups (0028,9110); naïve pydicom reads miss it.\nFix: prefer dcm2niix (handles this). If you must roll your own, parse functional groups.\n\nDERIVED / MPR vs “ignore derived”\n-i y can throw away everything; -i n lets MPRs through.\nFix: run with -i n, then filter post-hoc: keep isotropic, long volumes (e.g., Nz≥100, dz≤1.2 mm) even if DERIVED; send true slabs to a 2D/2.5D branch.\n\nRescaleSlope/Intercept after anonymization\nBad slope/intercept → “grey rectangle” CTs.\nFix: don’t pre-apply rescale yourself; let dcm2niix write scl_slope/inter into NIfTI. Apply HU on load only when slope/intercept ≠ identity; sanity-check with robust window percentiles.\n\nOvals instead of circles (affine / spacing mix-ups)\nFix: trust dcm2niix’s affine (from IOP/IPP). Verify dx≈dy and diagonal signs (LPS→RAS). If a head looks squashed, check pixdim first, not your viewer.\n\nRLE / uncommon transfer syntaxes\nDecoder errors, “wraps”, corrupted offsets.\nFix: gdcmconv --raw to a temp folder → then dcm2niix on the unpacked files.\n\nThroughput & I/O pain (millions of files)\nFix: parallelize (16–20 workers), write NIfTI with -z y, mount a tmpfs for intermediates, and write outputs to a separate fast disk.\n\nQuality control & quarantine\nFix: after conversion, compute per-volume: Nx,Ny,Nz,dx,dy,dz, intensity stats, and flags like derived_only, json_missing, int16_fullspan, slab. Keep a meta_summary.csv and suspects.csv; maintain a blacklist for truly broken cases.\n\nBrain masking (less junk, faster training)\nFix: run SynthStrip; zero out outside the mask but dilate 3–5 vox and give 10–15 mm padding to avoid cutting cortex/foramen magnum. If SynthStrip fails, fallback = ellipse in XY ∪ largest head component (for CT via HU threshold). QC: brain_frac, brain_vol_ml, touch_border.\n\nMinimal flag cheat-sheet\nDefault: dcm2niix -z y -b y -i n -o <out_dir> -f %s <series_dir>\n\nIf decoder complains: gdcmconv --raw <series>/*.dcm <tmp>/<uid>/ && dcm2niix ... <tmp>/<uid>/\n\nTakeaways\ndcm2niix handles 90% of the weirdness; the rest is audit + simple rules.\n\nAlways keep the JSON sidecar—it prevents guesswork later.\n\n\n\n\n",
      "votes": 6,
      "replies": [
        {
          "id": 3267313,
          "postDate": "2025-08-10T22:21:27.733Z",
          "content": "<p>Great advice!</p>",
          "rawMarkdown": "Great advice!"
        }
      ]
    }
  ],
  "comments": [
    {
      "id": 3266814,
      "author_name": "Anton Sibilev",
      "author_url": "",
      "post_date": "2025-08-09T18:18:18.893000",
      "content": "<h2>Experience with converting</h2>\n<p>Here’s a compact field-note I compiled from our work (joint with a ChatGPT-5 ); since this post is published under my name, a heads-up that most of the hands-on digging and write-up here is on my side. Hope it saves someone a few days. Wy fight we converting all dcms to NIfTI  is about 5 days.  But  still in progess.</p>\n<p>DICOM → NIfTI for brain aneurysm data: pitfalls &amp; fixes<br>\nBaseline tools: we convert with dcm2niix and keep the JSON sidecar (-b y), gzip outputs (-z y). When needed, we first “normalize” raw DICOM with gdcmconv --raw and then run dcm2niix.</p>\n<p>The traps we hit (and what worked)<br>\nMixed slice sizes / localizers in one folder<br>\nSymptoms: a series folder has scouts and the actual scan → different Rows/Cols.<br>\nFix: pre-audit and drop files whose matrix ≠ the mode; sort by InstanceNumber. Log the UID.</p>\n<p>Enhanced/multiframe DICOM &amp; hidden spacing<br>\nPixel spacing lives in FunctionalGroups (0028,9110); naïve pydicom reads miss it.<br>\nFix: prefer dcm2niix (handles this). If you must roll your own, parse functional groups.</p>\n<p>DERIVED / MPR vs “ignore derived”<br>\n-i y can throw away everything; -i n lets MPRs through.<br>\nFix: run with -i n, then filter post-hoc: keep isotropic, long volumes (e.g., Nz≥100, dz≤1.2 mm) even if DERIVED; send true slabs to a 2D/2.5D branch.</p>\n<p>RescaleSlope/Intercept after anonymization<br>\nBad slope/intercept → “grey rectangle” CTs.<br>\nFix: don’t pre-apply rescale yourself; let dcm2niix write scl_slope/inter into NIfTI. Apply HU on load only when slope/intercept ≠ identity; sanity-check with robust window percentiles.</p>\n<p>Ovals instead of circles (affine / spacing mix-ups)<br>\nFix: trust dcm2niix’s affine (from IOP/IPP). Verify dx≈dy and diagonal signs (LPS→RAS). If a head looks squashed, check pixdim first, not your viewer.</p>\n<p>RLE / uncommon transfer syntaxes<br>\nDecoder errors, “wraps”, corrupted offsets.<br>\nFix: gdcmconv --raw to a temp folder → then dcm2niix on the unpacked files.</p>\n<p>Throughput &amp; I/O pain (millions of files)<br>\nFix: parallelize (16–20 workers), write NIfTI with -z y, mount a tmpfs for intermediates, and write outputs to a separate fast disk.</p>\n<p>Quality control &amp; quarantine<br>\nFix: after conversion, compute per-volume: Nx,Ny,Nz,dx,dy,dz, intensity stats, and flags like derived_only, json_missing, int16_fullspan, slab. Keep a meta_summary.csv and suspects.csv; maintain a blacklist for truly broken cases.</p>\n<p>Brain masking (less junk, faster training)<br>\nFix: run SynthStrip; zero out outside the mask but dilate 3–5 vox and give 10–15 mm padding to avoid cutting cortex/foramen magnum. If SynthStrip fails, fallback = ellipse in XY ∪ largest head component (for CT via HU threshold). QC: brain_frac, brain_vol_ml, touch_border.</p>\n<p>Minimal flag cheat-sheet<br>\nDefault: dcm2niix -z y -b y -i n -o  -f %s </p>\n<p>If decoder complains: gdcmconv --raw /*.dcm // &amp;&amp; dcm2niix … //</p>\n<p>Takeaways<br>\ndcm2niix handles 90% of the weirdness; the rest is audit + simple rules.</p>\n<p>Always keep the JSON sidecar—it prevents guesswork later.</p>",
      "votes": 6,
      "replies": [
        {
          "id": 3267313,
          "author_name": "Evan Calabrese",
          "author_url": "",
          "post_date": "2025-08-10T22:21:27.733000",
          "content": "<p>Great advice!</p>",
          "votes": 0,
          "replies": []
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "3266199": "I have seen a few comments on this discussion board about various difficulties in loading DICOM data into 3D image arrays. I wanted to provide my approach for this in case it is helpful for someone. However, important to acknowledge that there are many ways to do this, and mine is not necessarily the best.\n\nDICOM is a complex format with many different ways of structuring the underlying imaging data. In my opinion, its a terrible format for data analysis; however, it is the reality of current clinical practice, which is why we chose to provide DICOM data instead of a more data analysis friendly format.\n\nWhen I am trying to process DICOM image data, the first thing I do is convert to NIfTI format using ```dcm2niix```. This is publicly available software (https://github.com/rordenlab/dcm2niix) that automates the process of DICOM to NIfTI conversion including graceful handling of a large majority of edge cases. The ```dcm2niix``` binary can be easily interfaced/pipelined with ```nipype``` see [docs here](https://nipype.readthedocs.io/en/latest/api/generated/nipype.interfaces.dcm2nii.html#dcm2niix). There is also a pip package available (https://pypi.org/project/dcm2niix/), though I have limited experience with this. \nWhen using ```dcm2niix```, the key conversion flags that you will want to consider are:\n```-b```, which allows you to output DICOM header variables as JSON alongside your images\n```-i```, which allows you to ignore stacked derived/localizer images (see [this thread](https://www.kaggle.com/competitions/rsna-intracranial-aneurysm-detection/discussion/596183))\n```-z```, which allows you gzip outputs to save considerable disk space\nThe main benefit of ```dcm2niix``` is that it completely automates the otherwise complex process of extracting 3D ```numpy``` arrays from DICOM datasets. While it lacks the customizability of approaches using ```pydicom```, it is much simpler, particularly for those without prior DICOM experience.\n\nNIfTI is a much simpler image format that can handle a large majority of cross-sectional medical imaging data. It is also extremely easy to load and manipulate using python and other image analysis softwares. ```nibabel``` is the essential python package for NIfTI file manipulation, and loading is a simple as ```nibabel.load('/path/to/file.nii.gz')```.",
    "3266814": "##Experience with converting\n\nHere’s a compact field-note I compiled from our work (joint with a ChatGPT-5 ); since this post is published under my name, a heads-up that most of the hands-on digging and write-up here is on my side. Hope it saves someone a few days. Wy fight we converting all dcms to NIfTI  is about 5 days.  But  still in progess.\n\n\nDICOM → NIfTI for brain aneurysm data: pitfalls & fixes\nBaseline tools: we convert with dcm2niix and keep the JSON sidecar (-b y), gzip outputs (-z y). When needed, we first “normalize” raw DICOM with gdcmconv --raw and then run dcm2niix.\n\nThe traps we hit (and what worked)\nMixed slice sizes / localizers in one folder\nSymptoms: a series folder has scouts and the actual scan → different Rows/Cols.\nFix: pre-audit and drop files whose matrix ≠ the mode; sort by InstanceNumber. Log the UID.\n\nEnhanced/multiframe DICOM & hidden spacing\nPixel spacing lives in FunctionalGroups (0028,9110); naïve pydicom reads miss it.\nFix: prefer dcm2niix (handles this). If you must roll your own, parse functional groups.\n\nDERIVED / MPR vs “ignore derived”\n-i y can throw away everything; -i n lets MPRs through.\nFix: run with -i n, then filter post-hoc: keep isotropic, long volumes (e.g., Nz≥100, dz≤1.2 mm) even if DERIVED; send true slabs to a 2D/2.5D branch.\n\nRescaleSlope/Intercept after anonymization\nBad slope/intercept → “grey rectangle” CTs.\nFix: don’t pre-apply rescale yourself; let dcm2niix write scl_slope/inter into NIfTI. Apply HU on load only when slope/intercept ≠ identity; sanity-check with robust window percentiles.\n\nOvals instead of circles (affine / spacing mix-ups)\nFix: trust dcm2niix’s affine (from IOP/IPP). Verify dx≈dy and diagonal signs (LPS→RAS). If a head looks squashed, check pixdim first, not your viewer.\n\nRLE / uncommon transfer syntaxes\nDecoder errors, “wraps”, corrupted offsets.\nFix: gdcmconv --raw to a temp folder → then dcm2niix on the unpacked files.\n\nThroughput & I/O pain (millions of files)\nFix: parallelize (16–20 workers), write NIfTI with -z y, mount a tmpfs for intermediates, and write outputs to a separate fast disk.\n\nQuality control & quarantine\nFix: after conversion, compute per-volume: Nx,Ny,Nz,dx,dy,dz, intensity stats, and flags like derived_only, json_missing, int16_fullspan, slab. Keep a meta_summary.csv and suspects.csv; maintain a blacklist for truly broken cases.\n\nBrain masking (less junk, faster training)\nFix: run SynthStrip; zero out outside the mask but dilate 3–5 vox and give 10–15 mm padding to avoid cutting cortex/foramen magnum. If SynthStrip fails, fallback = ellipse in XY ∪ largest head component (for CT via HU threshold). QC: brain_frac, brain_vol_ml, touch_border.\n\nMinimal flag cheat-sheet\nDefault: dcm2niix -z y -b y -i n -o <out_dir> -f %s <series_dir>\n\nIf decoder complains: gdcmconv --raw <series>/*.dcm <tmp>/<uid>/ && dcm2niix ... <tmp>/<uid>/\n\nTakeaways\ndcm2niix handles 90% of the weirdness; the rest is audit + simple rules.\n\nAlways keep the JSON sidecar—it prevents guesswork later.\n\n\n\n\n"
  }
}