{
  "id": 600485,
  "title": "how to read a dicom folder with multi series?",
  "url": "/competitions/rsna-intracranial-aneurysm-detection/discussion/600485",
  "author_name": "hengck23",
  "post_date": "2025-08-23T03:56:48.542000",
  "votes": 7,
  "comment_count": 4,
  "views": 0,
  "content": "<p>take for example:<br>\nStudy Instance UID = 1.2.826.0.1.3680043.8.498.11292405526057262764682810976119257084<br>\nitk-snap shows:<br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F428e84747f13708cfad416e647a6ac75%2FSelection_544.png?generation=1755921041558083&amp;alt=media\" alt=\"\"></p>\n<p>there are 3 series. according chatgpt,<br>\n(28 slices → real axial CTA)<br>\n(2 slices → likely scout/localizer)<br>\n(1 slice-&gt; likely  MIP — a Maximum Intensity Projection.)</p>\n<p>although cahtgpt gives some clustering code, it does not work.<br>\ni cannot remove the scout/localizer.  I wonder how itk-snap works?</p>\n<p>here are some meta data i extracted for clustering:  <br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F3752f48c81be9d8913e7932aacce4327%2FSelection_547.png?generation=1755921309508874&amp;alt=media\" alt=\"\"></p>",
  "messages": [
    {
      "id": 3273630,
      "postDate": "2025-08-23T03:56:48.543Z",
      "content": "<p>take for example:<br>\nStudy Instance UID = 1.2.826.0.1.3680043.8.498.11292405526057262764682810976119257084<br>\nitk-snap shows:<br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F428e84747f13708cfad416e647a6ac75%2FSelection_544.png?generation=1755921041558083&amp;alt=media\" alt=\"\"></p>\n<p>there are 3 series. according chatgpt,<br>\n(28 slices → real axial CTA)<br>\n(2 slices → likely scout/localizer)<br>\n(1 slice-&gt; likely  MIP — a Maximum Intensity Projection.)</p>\n<p>although cahtgpt gives some clustering code, it does not work.<br>\ni cannot remove the scout/localizer.  I wonder how itk-snap works?</p>\n<p>here are some meta data i extracted for clustering:  <br>\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F3752f48c81be9d8913e7932aacce4327%2FSelection_547.png?generation=1755921309508874&amp;alt=media\" alt=\"\"></p>",
      "rawMarkdown": "take for example:\nStudy Instance UID = 1.2.826.0.1.3680043.8.498.11292405526057262764682810976119257084\nitk-snap shows:\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F428e84747f13708cfad416e647a6ac75%2FSelection_544.png?generation=1755921041558083&alt=media)\n\nthere are 3 series. according chatgpt,\n(28 slices → real axial CTA)\n(2 slices → likely scout/localizer)\n(1 slice-> likely  MIP — a Maximum Intensity Projection.)\n\nalthough cahtgpt gives some clustering code, it does not work.\ni cannot remove the scout/localizer.  I wonder how itk-snap works?\n\nhere are some meta data i extracted for clustering:  \n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F3752f48c81be9d8913e7932aacce4327%2FSelection_547.png?generation=1755921309508874&alt=media)",
      "votes": 7
    },
    {
      "id": 3274671,
      "postDate": "2025-08-25T08:52:31.817Z",
      "content": "<p>ITK sucks with multi-series dicom. pydicom is much better at handling those. </p>\n<p>Here's how to wrap the DICOM series selection logic into a command-line interface (CLI) using the Click library. This CLI will allow users to:</p>\n<ol>\n<li>Specify a DICOM folder.</li>\n<li>List all series with their descriptions and other relevant tags.</li>\n<li>Select a series by its description or UID to perform an action on its files (e.g., print the file list). =&gt; You can extend the code from here. </li>\n</ol>\n<h1>CLI Script (dicom_cli.py)</h1>\n<p>Create a Python file named dicom_cli.py. The script uses two main commands: list-series to show available series and select-series to choose one based on criteria.</p>\n<pre><code> os\n pydicom\n click\n\n\n\n ():\n    \n    series_map = {}\n     root, _, files  os.walk(dicom_folder):\n         file_name  files:\n            file_path = os.path.join(root, file_name)\n            :\n                ds = pydicom.dcmread(file_path, stop_before_pixels=)\n                series_uid = ds.SeriesInstanceUID\n\n                 series_uid   series_map:\n                    series_map[series_uid] = []\n                series_map[series_uid].append(file_path)\n             pydicom.errors.InvalidDicomError:\n                \n     series_map\n\n ():\n    \n    metadata_map = {}\n     series_uid, files  series_map.items():\n         files:\n            first_file = pydicom.dcmread(files[], stop_before_pixels=)\n            metadata_map[series_uid] = {\n                : first_file.get(, ),\n                : first_file.get(, ),\n                : first_file.get(, ),\n                : (files)\n            }\n     metadata_map\n\n\n\n\n ():\n    \n    \n\n\n\n ():\n    \n    click.echo()\n    series_map = get_series_info(dicom_folder)\n    metadata = get_series_metadata(series_map)\n\n      metadata:\n        click.echo()\n        \n\n     uid, data  metadata.items():\n        click.echo()\n        click.echo()\n        click.echo()\n        click.echo()\n        click.echo()\n        click.echo()\n        click.echo()\n\n\n\n\n\n\n ():\n    \n    series_map = get_series_info(dicom_folder)\n\n    selected_files = []\n\n    \n     uid:\n        selected_files = series_map.get(uid, [])\n    :\n        \n         series_uid, file_list  series_map.items():\n              file_list:\n                \n\n            first_file = pydicom.dcmread(file_list[], stop_before_pixels=)\n\n             = \n             description  first_file.get() != description:\n                 = \n             modality  first_file.get() != modality:\n                 = \n\n             :\n                selected_files = (file_list, key= f: pydicom.dcmread(f).InstanceNumber)\n                  \n\n     selected_files:\n        click.echo()\n         file  selected_files:\n            click.echo(file)\n    :\n        click.echo()\n\n __name__ == :\n    cli()\n</code></pre>\n<h1>Usage</h1>\n<p>Save the script as dicom_cli.py and run it from your terminal.</p>\n<h2>Listing Series</h2>\n<p>To see all the series in a folder, use the list-series command.</p>\n<p>Bash</p>\n<pre><code> .  /////\n</code></pre>\n<p>This will print a formatted list of all series found, including their UID, description, and modality.</p>\n<h2>Selecting a Series</h2>\n<p>To select a specific series, use the select-series command with the appropriate options.</p>\n<p>Example 1: Select by description</p>\n<p>Bash</p>\n<pre><code>python dicom_cli -series ///your/dicom/folder  \"Axial T2\"\n</code></pre>\n<p>Example 2: Select by Modality</p>\n<p>Bash</p>\n<pre><code>python dicom_cli -series ///your/dicom/folder  CT\n</code></pre>\n<p>Example 3: Select by UID (most specific)</p>\n<p>Bash</p>\n<pre><code>python dicom_cli -series ///your/dicom/folder  ...\n</code></pre>\n<p>This command will print the file paths of all DICOM files belonging to the selected series, sorted by instance number. You can then pipe this output to another script or application for further processing.</p>",
      "rawMarkdown": "ITK sucks with multi-series dicom. pydicom is much better at handling those. \n\nHere's how to wrap the DICOM series selection logic into a command-line interface (CLI) using the Click library. This CLI will allow users to:\n\n1. Specify a DICOM folder.\n2. List all series with their descriptions and other relevant tags.\n3. Select a series by its description or UID to perform an action on its files (e.g., print the file list). => You can extend the code from here. \n\n#  CLI Script (dicom_cli.py)\n\nCreate a Python file named dicom_cli.py. The script uses two main commands: list-series to show available series and select-series to choose one based on criteria.\n\n```\nimport os\nimport pydicom\nimport click\n\n\n\ndef get_series_info(dicom_folder):\n    \"\"\"Identifies and groups DICOM files by their Series Instance UID.\"\"\"\n    series_map = {}\n    for root, _, files in os.walk(dicom_folder):\n        for file_name in files:\n            file_path = os.path.join(root, file_name)\n            try:\n                ds = pydicom.dcmread(file_path, stop_before_pixels=True)\n                series_uid = ds.SeriesInstanceUID\n                \n                if series_uid not in series_map:\n                    series_map[series_uid] = []\n                series_map[series_uid].append(file_path)\n            except pydicom.errors.InvalidDicomError:\n                continue\n    return series_map\n\ndef get_series_metadata(series_map):\n    \"\"\"\n    Extracts key metadata for each series for display purposes.\n    Returns a dictionary of series UIDs mapped to their metadata.\n    \"\"\"\n    metadata_map = {}\n    for series_uid, files in series_map.items():\n        if files:\n            first_file = pydicom.dcmread(files[0], stop_before_pixels=True)\n            metadata_map[series_uid] = {\n                'description': first_file.get('SeriesDescription', 'N/A'),\n                'modality': first_file.get('Modality', 'N/A'),\n                'series_number': first_file.get('SeriesNumber', 'N/A'),\n                'num_files': len(files)\n            }\n    return metadata_map\n\n# --- Click CLI Definition ---\n\n@click.group()\ndef cli():\n    \"\"\"A CLI tool for navigating and selecting DICOM series.\"\"\"\n    pass\n\n@cli.command('list-series')\n@click.argument('dicom_folder', type=click.Path(exists=True, file_okay=False, dir_okay=True))\ndef list_series_command(dicom_folder):\n    \"\"\"Lists all available DICOM series in the specified folder.\"\"\"\n    click.echo(f\"Scanning folder: {dicom_folder}\\n\")\n    series_map = get_series_info(dicom_folder)\n    metadata = get_series_metadata(series_map)\n\n    if not metadata:\n        click.echo(\"No DICOM series found.\")\n        return\n\n    for uid, data in metadata.items():\n        click.echo(f\"---------------------------------------------------\")\n        click.echo(f\"Series UID:      {uid}\")\n        click.echo(f\"Series Number:   {data['series_number']}\")\n        click.echo(f\"Description:     {data['description']}\")\n        click.echo(f\"Modality:        {data['modality']}\")\n        click.echo(f\"Number of Files: {data['num_files']}\")\n        click.echo(f\"---------------------------------------------------\\n\")\n\n@cli.command('select-series')\n@click.argument('dicom_folder', type=click.Path(exists=True, file_okay=False, dir_okay=True))\n@click.option('--description', '-d', help=\"Filter by SeriesDescription.\")\n@click.option('--modality', '-m', help=\"Filter by Modality.\")\n@click.option('--uid', '-u', help=\"Filter by SeriesInstanceUID.\")\ndef select_series_command(dicom_folder, description, modality, uid):\n    \"\"\"\n    Selects and prints the file paths for a specific series.\n    Use --description, --modality, or --uid to filter.\n    \"\"\"\n    series_map = get_series_info(dicom_folder)\n    \n    selected_files = []\n    \n    # Prioritize UID for a unique match\n    if uid:\n        selected_files = series_map.get(uid, [])\n    else:\n        # Fallback to description/modality search\n        for series_uid, file_list in series_map.items():\n            if not file_list:\n                continue\n            \n            first_file = pydicom.dcmread(file_list[0], stop_before_pixels=True)\n            \n            match = True\n            if description and first_file.get('SeriesDescription') != description:\n                match = False\n            if modality and first_file.get('Modality') != modality:\n                match = False\n            \n            if match:\n                selected_files = sorted(file_list, key=lambda f: pydicom.dcmread(f).InstanceNumber)\n                break  # Found the first matching series, so we can stop.\n\n    if selected_files:\n        click.echo(\"Selected the following files:\")\n        for file in selected_files:\n            click.echo(file)\n    else:\n        click.echo(\"No matching series found.\")\n\nif __name__ == '__main__':\n    cli()\n\n```\n\n#  Usage\nSave the script as dicom_cli.py and run it from your terminal.\n\n## Listing Series\nTo see all the series in a folder, use the list-series command.\n\nBash\n```\npython dicom_cli.py list-series /path/to/your/dicom/folder\n```\nThis will print a formatted list of all series found, including their UID, description, and modality.\n\n## Selecting a Series\nTo select a specific series, use the select-series command with the appropriate options.\n\nExample 1: Select by description\n\nBash\n```\npython dicom_cli.py select-series /path/to/your/dicom/folder --description \"Axial T2\"\n```\nExample 2: Select by Modality\n\nBash\n```\npython dicom_cli.py select-series /path/to/your/dicom/folder --modality CT\n```\nExample 3: Select by UID (most specific)\n\nBash\n```\npython dicom_cli.py select-series /path/to/your/dicom/folder --uid 1.2.840.113619.2.222.12345.67890\n```\nThis command will print the file paths of all DICOM files belonging to the selected series, sorted by instance number. You can then pipe this output to another script or application for further processing.",
      "votes": 3
    },
    {
      "id": 3273728,
      "postDate": "2025-08-23T09:25:16.850Z",
      "content": "<p>According to <a href=\"https://www.kaggle.com/competitions/rsna-intracranial-aneurysm-detection/discussion/596183\" target=\"_blank\">this</a> those cases are single volumes with \"scouts\".</p>",
      "rawMarkdown": "According to [this](https://www.kaggle.com/competitions/rsna-intracranial-aneurysm-detection/discussion/596183) those cases are single volumes with \"scouts\".",
      "votes": 2
    },
    {
      "id": 3273637,
      "postDate": "2025-08-23T04:29:09.733Z",
      "content": "<pre><code> ():\n    reader = sitk.ImageSeriesReader()\n\n    \n    series_ids = reader.GetGDCMSeriesIDs(series_path)\n      series_ids:\n         RuntimeError()\n\n    \n    series_id = (series_ids[]  series_id    series_id)\n\n    \n    all_files = reader.GetGDCMSeriesFileNames(series_path, series_id)\n\n    \n     ():\n        ds = pydicom.dcmread(f, stop_before_pixels=)\n         ((ds.Rows), (ds.Columns)), f\n\n     ThreadPoolExecutor(max_workers=max_workers)  ex:\n        sizes = (ex.(get_size, all_files))\n\n    \n    most_common_size = Counter(s[]  s  sizes).most_common()[][]\n    files = [f  (sz, f)  sizes  sz == most_common_size]\n\n    \n    reader.SetFileNames(files)\n    image = reader.Execute()\n\n    \n    spacing = (image.GetSpacing())\n     spacing[] == :\n        spacing[] = default_thickness\n        image.SetSpacing(spacing)\n\n    \n     resample  (spacing[] - spacing[]) &gt; spacing_tolerance:\n        new_spacing = [spacing[], spacing[], spacing[]]\n        new_size = [\n            ((image.GetSize()[] * spacing[] / new_spacing[])),\n            ((image.GetSize()[] * spacing[] / new_spacing[])),\n            ((image.GetSize()[] * spacing[] / new_spacing[]))\n        ]\n        resampler = sitk.ResampleImageFilter()\n        resampler.SetOutputSpacing(new_spacing)\n        resampler.SetSize(new_size)\n        resampler.SetOutputDirection(image.GetDirection())\n        resampler.SetOutputOrigin(image.GetOrigin())\n        resampler.SetInterpolator(sitk.sitkLinear)\n        image = resampler.Execute(image)\n\n    \n    volume = sitk.GetArrayFromImage(image)\n     volume\n</code></pre>",
      "rawMarkdown": "```python\ndef load_series2vol(series_path, series_id=None, spacing_tolerance=1e-3, resample=False, default_thickness=1.0, max_workers=20):\n    reader = sitk.ImageSeriesReader()\n\n    # Get all series IDs\n    series_ids = reader.GetGDCMSeriesIDs(series_path)\n    if not series_ids:\n        raise RuntimeError(f\"No DICOM series found in {series_path}\")\n\n    # Pick first if not specified\n    series_id = str(series_ids[0] if series_id is None else series_id)\n\n    # Get file names for the series\n    all_files = reader.GetGDCMSeriesFileNames(series_path, series_id)\n\n    # --- Parallel metadata read (fast size check) ---\n    def get_size(f):\n        ds = pydicom.dcmread(f, stop_before_pixels=True)\n        return (int(ds.Rows), int(ds.Columns)), f\n\n    with ThreadPoolExecutor(max_workers=max_workers) as ex:\n        sizes = list(ex.map(get_size, all_files))\n\n    # Pick the most common size\n    most_common_size = Counter(s[0] for s in sizes).most_common(1)[0][0]\n    files = [f for (sz, f) in sizes if sz == most_common_size]\n\n    # --- Now read the actual image series ---\n    reader.SetFileNames(files)\n    image = reader.Execute()\n\n    # --- Fix zero thickness ---\n    spacing = list(image.GetSpacing())\n    if spacing[2] == 0:\n        spacing[2] = default_thickness\n        image.SetSpacing(spacing)\n\n    # --- Optional resample ---\n    if resample and abs(spacing[2] - spacing[0]) > spacing_tolerance:\n        new_spacing = [spacing[0], spacing[1], spacing[0]]\n        new_size = [\n            int(round(image.GetSize()[0] * spacing[0] / new_spacing[0])),\n            int(round(image.GetSize()[1] * spacing[1] / new_spacing[1])),\n            int(round(image.GetSize()[2] * spacing[2] / new_spacing[2]))\n        ]\n        resampler = sitk.ResampleImageFilter()\n        resampler.SetOutputSpacing(new_spacing)\n        resampler.SetSize(new_size)\n        resampler.SetOutputDirection(image.GetDirection())\n        resampler.SetOutputOrigin(image.GetOrigin())\n        resampler.SetInterpolator(sitk.sitkLinear)\n        image = resampler.Execute(image)\n\n    # Convert to numpy array\n    volume = sitk.GetArrayFromImage(image)\n    return volume\n```\n",
      "votes": 2
    },
    {
      "id": 3290589,
      "postDate": "2025-09-18T03:30:27.233Z",
      "content": "<p>Was the multi-series data removed? I don’t see the example one anymore.</p>",
      "rawMarkdown": "Was the multi-series data removed? I don’t see the example one anymore."
    }
  ],
  "comments": [
    {
      "id": 3274671,
      "author_name": "navneeth subramanian",
      "author_url": "",
      "post_date": "2025-08-25T08:52:31.817000",
      "content": "<p>ITK sucks with multi-series dicom. pydicom is much better at handling those. </p>\n<p>Here's how to wrap the DICOM series selection logic into a command-line interface (CLI) using the Click library. This CLI will allow users to:</p>\n<ol>\n<li>Specify a DICOM folder.</li>\n<li>List all series with their descriptions and other relevant tags.</li>\n<li>Select a series by its description or UID to perform an action on its files (e.g., print the file list). =&gt; You can extend the code from here. </li>\n</ol>\n<h1>CLI Script (dicom_cli.py)</h1>\n<p>Create a Python file named dicom_cli.py. The script uses two main commands: list-series to show available series and select-series to choose one based on criteria.</p>\n<pre><code> os\n pydicom\n click\n\n\n\n ():\n    \n    series_map = {}\n     root, _, files  os.walk(dicom_folder):\n         file_name  files:\n            file_path = os.path.join(root, file_name)\n            :\n                ds = pydicom.dcmread(file_path, stop_before_pixels=)\n                series_uid = ds.SeriesInstanceUID\n\n                 series_uid   series_map:\n                    series_map[series_uid] = []\n                series_map[series_uid].append(file_path)\n             pydicom.errors.InvalidDicomError:\n                \n     series_map\n\n ():\n    \n    metadata_map = {}\n     series_uid, files  series_map.items():\n         files:\n            first_file = pydicom.dcmread(files[], stop_before_pixels=)\n            metadata_map[series_uid] = {\n                : first_file.get(, ),\n                : first_file.get(, ),\n                : first_file.get(, ),\n                : (files)\n            }\n     metadata_map\n\n\n\n\n ():\n    \n    \n\n\n\n ():\n    \n    click.echo()\n    series_map = get_series_info(dicom_folder)\n    metadata = get_series_metadata(series_map)\n\n      metadata:\n        click.echo()\n        \n\n     uid, data  metadata.items():\n        click.echo()\n        click.echo()\n        click.echo()\n        click.echo()\n        click.echo()\n        click.echo()\n        click.echo()\n\n\n\n\n\n\n ():\n    \n    series_map = get_series_info(dicom_folder)\n\n    selected_files = []\n\n    \n     uid:\n        selected_files = series_map.get(uid, [])\n    :\n        \n         series_uid, file_list  series_map.items():\n              file_list:\n                \n\n            first_file = pydicom.dcmread(file_list[], stop_before_pixels=)\n\n             = \n             description  first_file.get() != description:\n                 = \n             modality  first_file.get() != modality:\n                 = \n\n             :\n                selected_files = (file_list, key= f: pydicom.dcmread(f).InstanceNumber)\n                  \n\n     selected_files:\n        click.echo()\n         file  selected_files:\n            click.echo(file)\n    :\n        click.echo()\n\n __name__ == :\n    cli()\n</code></pre>\n<h1>Usage</h1>\n<p>Save the script as dicom_cli.py and run it from your terminal.</p>\n<h2>Listing Series</h2>\n<p>To see all the series in a folder, use the list-series command.</p>\n<p>Bash</p>\n<pre><code> .  /////\n</code></pre>\n<p>This will print a formatted list of all series found, including their UID, description, and modality.</p>\n<h2>Selecting a Series</h2>\n<p>To select a specific series, use the select-series command with the appropriate options.</p>\n<p>Example 1: Select by description</p>\n<p>Bash</p>\n<pre><code>python dicom_cli -series ///your/dicom/folder  \"Axial T2\"\n</code></pre>\n<p>Example 2: Select by Modality</p>\n<p>Bash</p>\n<pre><code>python dicom_cli -series ///your/dicom/folder  CT\n</code></pre>\n<p>Example 3: Select by UID (most specific)</p>\n<p>Bash</p>\n<pre><code>python dicom_cli -series ///your/dicom/folder  ...\n</code></pre>\n<p>This command will print the file paths of all DICOM files belonging to the selected series, sorted by instance number. You can then pipe this output to another script or application for further processing.</p>",
      "votes": 3,
      "replies": []
    },
    {
      "id": 3273728,
      "author_name": "Ángel Jacinto Sánchez Ruiz",
      "author_url": "",
      "post_date": "2025-08-23T09:25:16.850000",
      "content": "<p>According to <a href=\"https://www.kaggle.com/competitions/rsna-intracranial-aneurysm-detection/discussion/596183\" target=\"_blank\">this</a> those cases are single volumes with \"scouts\".</p>",
      "votes": 2,
      "replies": []
    },
    {
      "id": 3273637,
      "author_name": "Tom",
      "author_url": "",
      "post_date": "2025-08-23T04:29:09.733000",
      "content": "<pre><code> ():\n    reader = sitk.ImageSeriesReader()\n\n    \n    series_ids = reader.GetGDCMSeriesIDs(series_path)\n      series_ids:\n         RuntimeError()\n\n    \n    series_id = (series_ids[]  series_id    series_id)\n\n    \n    all_files = reader.GetGDCMSeriesFileNames(series_path, series_id)\n\n    \n     ():\n        ds = pydicom.dcmread(f, stop_before_pixels=)\n         ((ds.Rows), (ds.Columns)), f\n\n     ThreadPoolExecutor(max_workers=max_workers)  ex:\n        sizes = (ex.(get_size, all_files))\n\n    \n    most_common_size = Counter(s[]  s  sizes).most_common()[][]\n    files = [f  (sz, f)  sizes  sz == most_common_size]\n\n    \n    reader.SetFileNames(files)\n    image = reader.Execute()\n\n    \n    spacing = (image.GetSpacing())\n     spacing[] == :\n        spacing[] = default_thickness\n        image.SetSpacing(spacing)\n\n    \n     resample  (spacing[] - spacing[]) &gt; spacing_tolerance:\n        new_spacing = [spacing[], spacing[], spacing[]]\n        new_size = [\n            ((image.GetSize()[] * spacing[] / new_spacing[])),\n            ((image.GetSize()[] * spacing[] / new_spacing[])),\n            ((image.GetSize()[] * spacing[] / new_spacing[]))\n        ]\n        resampler = sitk.ResampleImageFilter()\n        resampler.SetOutputSpacing(new_spacing)\n        resampler.SetSize(new_size)\n        resampler.SetOutputDirection(image.GetDirection())\n        resampler.SetOutputOrigin(image.GetOrigin())\n        resampler.SetInterpolator(sitk.sitkLinear)\n        image = resampler.Execute(image)\n\n    \n    volume = sitk.GetArrayFromImage(image)\n     volume\n</code></pre>",
      "votes": 2,
      "replies": []
    },
    {
      "id": 3290589,
      "author_name": "Luca",
      "author_url": "",
      "post_date": "2025-09-18T03:30:27.233000",
      "content": "<p>Was the multi-series data removed? I don’t see the example one anymore.</p>",
      "votes": 0,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "3273630": "take for example:\nStudy Instance UID = 1.2.826.0.1.3680043.8.498.11292405526057262764682810976119257084\nitk-snap shows:\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F428e84747f13708cfad416e647a6ac75%2FSelection_544.png?generation=1755921041558083&alt=media)\n\nthere are 3 series. according chatgpt,\n(28 slices → real axial CTA)\n(2 slices → likely scout/localizer)\n(1 slice-> likely  MIP — a Maximum Intensity Projection.)\n\nalthough cahtgpt gives some clustering code, it does not work.\ni cannot remove the scout/localizer.  I wonder how itk-snap works?\n\nhere are some meta data i extracted for clustering:  \n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F113660%2F3752f48c81be9d8913e7932aacce4327%2FSelection_547.png?generation=1755921309508874&alt=media)",
    "3274671": "ITK sucks with multi-series dicom. pydicom is much better at handling those. \n\nHere's how to wrap the DICOM series selection logic into a command-line interface (CLI) using the Click library. This CLI will allow users to:\n\n1. Specify a DICOM folder.\n2. List all series with their descriptions and other relevant tags.\n3. Select a series by its description or UID to perform an action on its files (e.g., print the file list). => You can extend the code from here. \n\n#  CLI Script (dicom_cli.py)\n\nCreate a Python file named dicom_cli.py. The script uses two main commands: list-series to show available series and select-series to choose one based on criteria.\n\n```\nimport os\nimport pydicom\nimport click\n\n\n\ndef get_series_info(dicom_folder):\n    \"\"\"Identifies and groups DICOM files by their Series Instance UID.\"\"\"\n    series_map = {}\n    for root, _, files in os.walk(dicom_folder):\n        for file_name in files:\n            file_path = os.path.join(root, file_name)\n            try:\n                ds = pydicom.dcmread(file_path, stop_before_pixels=True)\n                series_uid = ds.SeriesInstanceUID\n                \n                if series_uid not in series_map:\n                    series_map[series_uid] = []\n                series_map[series_uid].append(file_path)\n            except pydicom.errors.InvalidDicomError:\n                continue\n    return series_map\n\ndef get_series_metadata(series_map):\n    \"\"\"\n    Extracts key metadata for each series for display purposes.\n    Returns a dictionary of series UIDs mapped to their metadata.\n    \"\"\"\n    metadata_map = {}\n    for series_uid, files in series_map.items():\n        if files:\n            first_file = pydicom.dcmread(files[0], stop_before_pixels=True)\n            metadata_map[series_uid] = {\n                'description': first_file.get('SeriesDescription', 'N/A'),\n                'modality': first_file.get('Modality', 'N/A'),\n                'series_number': first_file.get('SeriesNumber', 'N/A'),\n                'num_files': len(files)\n            }\n    return metadata_map\n\n# --- Click CLI Definition ---\n\n@click.group()\ndef cli():\n    \"\"\"A CLI tool for navigating and selecting DICOM series.\"\"\"\n    pass\n\n@cli.command('list-series')\n@click.argument('dicom_folder', type=click.Path(exists=True, file_okay=False, dir_okay=True))\ndef list_series_command(dicom_folder):\n    \"\"\"Lists all available DICOM series in the specified folder.\"\"\"\n    click.echo(f\"Scanning folder: {dicom_folder}\\n\")\n    series_map = get_series_info(dicom_folder)\n    metadata = get_series_metadata(series_map)\n\n    if not metadata:\n        click.echo(\"No DICOM series found.\")\n        return\n\n    for uid, data in metadata.items():\n        click.echo(f\"---------------------------------------------------\")\n        click.echo(f\"Series UID:      {uid}\")\n        click.echo(f\"Series Number:   {data['series_number']}\")\n        click.echo(f\"Description:     {data['description']}\")\n        click.echo(f\"Modality:        {data['modality']}\")\n        click.echo(f\"Number of Files: {data['num_files']}\")\n        click.echo(f\"---------------------------------------------------\\n\")\n\n@cli.command('select-series')\n@click.argument('dicom_folder', type=click.Path(exists=True, file_okay=False, dir_okay=True))\n@click.option('--description', '-d', help=\"Filter by SeriesDescription.\")\n@click.option('--modality', '-m', help=\"Filter by Modality.\")\n@click.option('--uid', '-u', help=\"Filter by SeriesInstanceUID.\")\ndef select_series_command(dicom_folder, description, modality, uid):\n    \"\"\"\n    Selects and prints the file paths for a specific series.\n    Use --description, --modality, or --uid to filter.\n    \"\"\"\n    series_map = get_series_info(dicom_folder)\n    \n    selected_files = []\n    \n    # Prioritize UID for a unique match\n    if uid:\n        selected_files = series_map.get(uid, [])\n    else:\n        # Fallback to description/modality search\n        for series_uid, file_list in series_map.items():\n            if not file_list:\n                continue\n            \n            first_file = pydicom.dcmread(file_list[0], stop_before_pixels=True)\n            \n            match = True\n            if description and first_file.get('SeriesDescription') != description:\n                match = False\n            if modality and first_file.get('Modality') != modality:\n                match = False\n            \n            if match:\n                selected_files = sorted(file_list, key=lambda f: pydicom.dcmread(f).InstanceNumber)\n                break  # Found the first matching series, so we can stop.\n\n    if selected_files:\n        click.echo(\"Selected the following files:\")\n        for file in selected_files:\n            click.echo(file)\n    else:\n        click.echo(\"No matching series found.\")\n\nif __name__ == '__main__':\n    cli()\n\n```\n\n#  Usage\nSave the script as dicom_cli.py and run it from your terminal.\n\n## Listing Series\nTo see all the series in a folder, use the list-series command.\n\nBash\n```\npython dicom_cli.py list-series /path/to/your/dicom/folder\n```\nThis will print a formatted list of all series found, including their UID, description, and modality.\n\n## Selecting a Series\nTo select a specific series, use the select-series command with the appropriate options.\n\nExample 1: Select by description\n\nBash\n```\npython dicom_cli.py select-series /path/to/your/dicom/folder --description \"Axial T2\"\n```\nExample 2: Select by Modality\n\nBash\n```\npython dicom_cli.py select-series /path/to/your/dicom/folder --modality CT\n```\nExample 3: Select by UID (most specific)\n\nBash\n```\npython dicom_cli.py select-series /path/to/your/dicom/folder --uid 1.2.840.113619.2.222.12345.67890\n```\nThis command will print the file paths of all DICOM files belonging to the selected series, sorted by instance number. You can then pipe this output to another script or application for further processing.",
    "3273728": "According to [this](https://www.kaggle.com/competitions/rsna-intracranial-aneurysm-detection/discussion/596183) those cases are single volumes with \"scouts\".",
    "3273637": "```python\ndef load_series2vol(series_path, series_id=None, spacing_tolerance=1e-3, resample=False, default_thickness=1.0, max_workers=20):\n    reader = sitk.ImageSeriesReader()\n\n    # Get all series IDs\n    series_ids = reader.GetGDCMSeriesIDs(series_path)\n    if not series_ids:\n        raise RuntimeError(f\"No DICOM series found in {series_path}\")\n\n    # Pick first if not specified\n    series_id = str(series_ids[0] if series_id is None else series_id)\n\n    # Get file names for the series\n    all_files = reader.GetGDCMSeriesFileNames(series_path, series_id)\n\n    # --- Parallel metadata read (fast size check) ---\n    def get_size(f):\n        ds = pydicom.dcmread(f, stop_before_pixels=True)\n        return (int(ds.Rows), int(ds.Columns)), f\n\n    with ThreadPoolExecutor(max_workers=max_workers) as ex:\n        sizes = list(ex.map(get_size, all_files))\n\n    # Pick the most common size\n    most_common_size = Counter(s[0] for s in sizes).most_common(1)[0][0]\n    files = [f for (sz, f) in sizes if sz == most_common_size]\n\n    # --- Now read the actual image series ---\n    reader.SetFileNames(files)\n    image = reader.Execute()\n\n    # --- Fix zero thickness ---\n    spacing = list(image.GetSpacing())\n    if spacing[2] == 0:\n        spacing[2] = default_thickness\n        image.SetSpacing(spacing)\n\n    # --- Optional resample ---\n    if resample and abs(spacing[2] - spacing[0]) > spacing_tolerance:\n        new_spacing = [spacing[0], spacing[1], spacing[0]]\n        new_size = [\n            int(round(image.GetSize()[0] * spacing[0] / new_spacing[0])),\n            int(round(image.GetSize()[1] * spacing[1] / new_spacing[1])),\n            int(round(image.GetSize()[2] * spacing[2] / new_spacing[2]))\n        ]\n        resampler = sitk.ResampleImageFilter()\n        resampler.SetOutputSpacing(new_spacing)\n        resampler.SetSize(new_size)\n        resampler.SetOutputDirection(image.GetDirection())\n        resampler.SetOutputOrigin(image.GetOrigin())\n        resampler.SetInterpolator(sitk.sitkLinear)\n        image = resampler.Execute(image)\n\n    # Convert to numpy array\n    volume = sitk.GetArrayFromImage(image)\n    return volume\n```\n",
    "3290589": "Was the multi-series data removed? I don’t see the example one anymore."
  }
}