Skip to content

Commit c5d0658

Browse files
fedorovclaude
andcommitted
fix: bring idc_dataset notebook into compliance with contribution guidelines
- Fix license header: use MONAI Consortium copyright, correct format with trailing double spaces and &nbsp; indentation, moved to top of first cell - Move all imports (os, sys, itkwasm_dicom) into Setup imports cell; simplify Setup environment cell to pip install only - Add README.md entry for idc_dataset under Modules section - Add idc_dataset to doesnt_contain_max_epochs and skip_run_papermill in runner.sh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Andrey Fedorov <andrey.fedorov@gmail.com>
1 parent 4b01df9 commit c5d0658

3 files changed

Lines changed: 11 additions & 209 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ Illustrate reading NIfTI files and iterating over image patches of the volumes l
331331
This tutorial illustrates the flexible network APIs and utilities.
332332
##### [postprocessing_transforms](./modules/postprocessing_transforms.ipynb)
333333
This notebook shows the usage of several postprocessing transforms based on the model output of spleen segmentation task.
334+
##### [idc_dataset](./modules/idc_dataset.ipynb)
335+
This notebook shows how to query and download public cancer imaging data from NCI Imaging Data Commons (IDC) using `idc-index`, and how to load DICOM images and DICOM-SEG segmentations into MONAI for AI/ML preprocessing.
334336
##### [public_datasets](./modules/public_datasets.ipynb)
335337
This notebook shows how to quickly set up training workflow based on `MedNISTDataset` and `DecathlonDataset`, and how to create a new dataset.
336338
##### [tcia_csv_processing](./modules/tcia_csv_processing.ipynb)
@@ -386,4 +388,4 @@ Example shows the use cases of using MONAI to evaluate the performance of a gene
386388

387389
#### [VISTA2D](./vista_2d)
388390
This tutorial demonstrates how to train a cell segmentation model using the [MONAI](https://monai.io/) framework and the [Segment Anything Model (SAM)](https://github.com/facebookresearch/segment-anything) on the [Cellpose dataset](https://www.cellpose.org/).
389-
ECHO°¡ ¼³Á¤µÇ¾î ÀÖ½À´Ï´Ù.
391+
ECHO�� �����Ǿ� �ֽ��ϴ�.

modules/idc_dataset.ipynb

Lines changed: 6 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,14 @@
55
"metadata": {
66
"id": "eFLP44iEFCpB"
77
},
8-
"source": [
9-
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ImagingDataCommons/idc-monai/blob/main/monai_contribution/idc_dataset.ipynb)\n",
10-
"\n",
11-
"# Using NCI Imaging Data Commons with MONAI\n",
12-
"\n",
13-
"Copyright 2026 Imaging Data Commons\n",
14-
"\n",
15-
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
16-
"you may not use this file except in compliance with the License.\n",
17-
"You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n",
18-
"\n",
19-
"---\n",
20-
"\n",
21-
"## What is IDC?\n",
22-
"\n",
23-
"[NCI Imaging Data Commons (IDC)](https://portal.imaging.datacommons.cancer.gov/) is a free, cloud-hosted repository of publicly available cancer imaging data maintained by the National Cancer Institute (NCI). It provides:\n",
24-
"\n",
25-
"- **~100 TB** of radiology (CT, MR, PET) and pathology images across 160+ cancer collections\n",
26-
"- **No sign-up or authentication required** — data is openly accessible\n",
27-
"- **Expert and AI-generated annotations** (e.g., organ segmentations) paired with images\n",
28-
"- **Standardized format** — all data uses DICOM, the medical imaging industry standard\n",
29-
"- **Cloud-native storage** — data lives in Google Cloud Storage (GCS) buckets, so downloads are fast\n",
30-
"- **Accompanying tools** - you can search, visualize, and subset the data\n",
31-
"\n",
32-
"## What is `idc-index`?\n",
33-
"\n",
34-
"[`idc-index`](https://github.com/ImagingDataCommons/idc-index) is a lightweight Python package that lets you search and download IDC data without any cloud account or special credentials. It ships with a local metadata index — a set of DuckDB tables describing every image series in IDC — so you can run SQL queries locally to find exactly the data you need before downloading anything.\n",
35-
"\n",
36-
"## What this tutorial covers\n",
37-
"\n",
38-
"This tutorial shows how to:\n",
39-
"1. Query IDC metadata with SQL to find cancer imaging data\n",
40-
"2. Download DICOM images and segmentations with one function call\n",
41-
"3. Load the data into MONAI for AI/ML preprocessing\n",
42-
"4. Work with DICOM Segmentation (DICOM-SEG) objects and their rich metadata\n",
43-
"\n",
44-
"> **Tip**: This tutorial was created using [idc-claude-skill](https://github.com/ImagingDataCommons/idc-claude-skill) — an AI assistant skill for navigating IDC data and the `idc-index` API."
45-
]
8+
"source": "Copyright (c) MONAI Consortium \nLicensed under the Apache License, Version 2.0 (the \"License\"); \nyou may not use this file except in compliance with the License. \nYou may obtain a copy of the License at \n&nbsp;&nbsp;&nbsp;&nbsp;http://www.apache.org/licenses/LICENSE-2.0 \nUnless required by applicable law or agreed to in writing, software \ndistributed under the License is distributed on an \"AS IS\" BASIS, \nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \nSee the License for the specific language governing permissions and \nlimitations under the License.\n\n[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ImagingDataCommons/idc-monai/blob/main/monai_contribution/idc_dataset.ipynb)\n\n# Using NCI Imaging Data Commons with MONAI\n\n## What is IDC?\n\n[NCI Imaging Data Commons (IDC)](https://portal.imaging.datacommons.cancer.gov/) is a free, cloud-hosted repository of publicly available cancer imaging data maintained by the National Cancer Institute (NCI). It provides:\n\n- **~100 TB** of radiology (CT, MR, PET) and pathology images across 160+ cancer collections\n- **No sign-up or authentication required** — data is openly accessible\n- **Expert and AI-generated annotations** (e.g., organ segmentations) paired with images\n- **Standardized format** — all data uses DICOM, the medical imaging industry standard\n- **Cloud-native storage** — data lives in Google Cloud Storage (GCS) buckets, so downloads are fast\n- **Accompanying tools** - you can search, visualize, and subset the data\n\n## What is `idc-index`?\n\n[`idc-index`](https://github.com/ImagingDataCommons/idc-index) is a lightweight Python package that lets you search and download IDC data without any cloud account or special credentials. It ships with a local metadata index — a set of DuckDB tables describing every image series in IDC — so you can run SQL queries locally to find exactly the data you need before downloading anything.\n\n## What this tutorial covers\n\nThis tutorial shows how to:\n1. Query IDC metadata with SQL to find cancer imaging data\n2. Download DICOM images and segmentations with one function call\n3. Load the data into MONAI for AI/ML preprocessing\n4. Work with DICOM Segmentation (DICOM-SEG) objects and their rich metadata\n\n> **Tip**: This tutorial was created using [idc-claude-skill](https://github.com/ImagingDataCommons/idc-claude-skill) — an AI assistant skill for navigating IDC data and the `idc-index` API."
469
},
4710
{
4811
"cell_type": "markdown",
4912
"metadata": {
5013
"id": "iudwt5GoFCpC"
5114
},
52-
"source": [
53-
"## Setup\n",
54-
"\n",
55-
"Install required packages:\n",
56-
"- `monai` — Medical Open Network for AI, the ML framework used here\n",
57-
"- `idc-index` — Query and download IDC data (includes local metadata index)\n",
58-
"- `itk` / `itkwasm-dicom` — ITK-based DICOM readers used by MONAI's `ITKReader` and our custom DICOM-SEG loader"
59-
]
15+
"source": "## Setup environment\n\nInstall required packages:\n- `monai` — Medical Open Network for AI, the ML framework used here\n- `idc-index` — Query and download IDC data (includes local metadata index)\n- `itk` / `itkwasm-dicom` — ITK-based DICOM readers used by MONAI's `ITKReader` and our custom DICOM-SEG loader\n\n> **Colab users**: After running the cell below, restart the runtime before continuing (Runtime → Restart runtime). ITK requires a fresh runtime to load correctly after installation."
6016
},
6117
{
6218
"cell_type": "code",
@@ -65,21 +21,7 @@
6521
"id": "T4ujv4U7FCpC"
6622
},
6723
"outputs": [],
68-
"source": [
69-
"!pip install -q monai idc-index itk itkwasm-dicom\n",
70-
"\n",
71-
"# Restart runtime after installing ITK (required for ITK to load properly)\n",
72-
"import sys\n",
73-
"\n",
74-
"if \"google.colab\" in sys.modules:\n",
75-
" try:\n",
76-
" import itk # noqa: F401\n",
77-
" except ImportError:\n",
78-
" print(\"Restarting runtime to load ITK...\")\n",
79-
" import os\n",
80-
"\n",
81-
" os.kill(os.getpid(), 9)"
82-
]
24+
"source": "!pip install -q monai idc-index itk itkwasm-dicom"
8325
},
8426
{
8527
"cell_type": "markdown",
@@ -101,34 +43,7 @@
10143
"outputId": "f63082ca-81d8-4b02-8de0-662710f6e508"
10244
},
10345
"outputs": [],
104-
"source": [
105-
"import os\n",
106-
"import tempfile\n",
107-
"from pathlib import Path\n",
108-
"from typing import Hashable, Mapping\n",
109-
"\n",
110-
"import torch\n",
111-
"import numpy as np\n",
112-
"import matplotlib.pyplot as plt\n",
113-
"from matplotlib.colors import ListedColormap\n",
114-
"from idc_index import IDCClient\n",
115-
"\n",
116-
"from monai.config import KeysCollection\n",
117-
"from monai.transforms import (\n",
118-
" Compose,\n",
119-
" LoadImaged,\n",
120-
" EnsureChannelFirstd,\n",
121-
" Orientationd,\n",
122-
" Spacingd,\n",
123-
" ScaleIntensityRanged,\n",
124-
" MapTransform,\n",
125-
")\n",
126-
"from monai.data import Dataset, MetaTensor\n",
127-
"from monai.data.image_reader import ITKReader\n",
128-
"import monai\n",
129-
"\n",
130-
"monai.config.print_config()"
131-
]
46+
"source": "import os\nimport sys\nimport tempfile\nfrom pathlib import Path\nfrom typing import Hashable, Mapping\n\nimport itkwasm_dicom\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nfrom idc_index import IDCClient\nfrom matplotlib.colors import ListedColormap\n\nimport monai\nfrom monai.config import KeysCollection\nfrom monai.data import Dataset, MetaTensor\nfrom monai.data.image_reader import ITKReader\nfrom monai.transforms import (\n Compose,\n EnsureChannelFirstd,\n LoadImaged,\n MapTransform,\n Orientationd,\n ScaleIntensityRanged,\n Spacingd,\n)\n\nmonai.config.print_config()"
13247
},
13348
{
13449
"cell_type": "markdown",
@@ -562,124 +477,7 @@
562477
"outputId": "386c615d-5349-49fb-f412-7a92cfc421d9"
563478
},
564479
"outputs": [],
565-
"source": [
566-
"import itkwasm_dicom # noqa: E402\n",
567-
"\n",
568-
"\n",
569-
"class LoadDicomSegd(MapTransform):\n",
570-
" \"\"\"Load DICOM Segmentation (DICOM-SEG) files using ITKWasm.\n",
571-
"\n",
572-
" DICOM-SEG is an enhanced multiframe DICOM format that stores segmentation\n",
573-
" masks with segment metadata including recommended display colors.\n",
574-
"\n",
575-
" The affine matrix is derived directly from DICOM metadata (direction cosines,\n",
576-
" spacing, origin) with LPS→RAS conversion applied to match MONAI's ITKReader\n",
577-
" convention. No axis flipping is performed — orientation is fully encoded in\n",
578-
" the affine via the direction cosine matrix.\n",
579-
" \"\"\"\n",
580-
"\n",
581-
" def __init__(self, keys: KeysCollection, allow_missing_keys: bool = False):\n",
582-
" super().__init__(keys, allow_missing_keys)\n",
583-
"\n",
584-
" def _find_dcm_file(self, path: Path) -> Path:\n",
585-
" \"\"\"Find .dcm file in directory or return path if already a file.\"\"\"\n",
586-
" if path.is_file():\n",
587-
" return path\n",
588-
" dcm_files = list(path.glob(\"*.dcm\"))\n",
589-
" if not dcm_files:\n",
590-
" raise FileNotFoundError(f\"No .dcm files found in {path}\")\n",
591-
" return dcm_files[0]\n",
592-
"\n",
593-
" def _build_affine(self, spacing, origin, direction) -> np.ndarray:\n",
594-
" \"\"\"Build 4x4 affine matrix from DICOM spatial metadata.\n",
595-
"\n",
596-
" Converts from ITK/DICOM LPS convention to MONAI's RAS-like convention\n",
597-
" by negating X and Y world coordinates (LPS→RAS). No axis flips are\n",
598-
" applied — orientation is fully encoded in the affine via the direction\n",
599-
" cosine matrix.\n",
600-
"\n",
601-
" Args:\n",
602-
" spacing: Voxel spacing (X, Y, Z) as returned by itkwasm\n",
603-
" origin: Physical coordinates of voxel [0,0,0] in LPS\n",
604-
" direction: 3x3 direction cosine matrix D where D[i,j] is the\n",
605-
" component of voxel-axis-j's unit vector along LPS\n",
606-
" physical axis i. ITK affine formula:\n",
607-
" world_lps = D @ diag(spacing) @ voxel + origin\n",
608-
" \"\"\"\n",
609-
" lps_to_ras = np.diag([-1.0, -1.0, 1.0])\n",
610-
" affine = np.eye(4)\n",
611-
" affine[:3, :3] = lps_to_ras @ direction @ np.diag(spacing)\n",
612-
" affine[:3, 3] = lps_to_ras @ origin\n",
613-
" return affine\n",
614-
"\n",
615-
" def __call__(self, data: Mapping[Hashable, any]) -> dict[Hashable, any]:\n",
616-
" d = dict(data)\n",
617-
" for key in self.key_iterator(d):\n",
618-
" path = Path(d[key])\n",
619-
" dcm_file = self._find_dcm_file(path)\n",
620-
"\n",
621-
" # Read using ITKWasm\n",
622-
" seg_image, overlay_info = itkwasm_dicom.read_segmentation(dcm_file)\n",
623-
"\n",
624-
" # ITKWasm returns array in (Z, Y, X) order but metadata in (X, Y, Z) order.\n",
625-
" # Transpose to (X, Y, Z) to match metadata — this is a layout convention,\n",
626-
" # not an orientation flip.\n",
627-
" seg_array = np.asarray(seg_image.data).copy()\n",
628-
" seg_array = np.transpose(seg_array, (2, 1, 0))\n",
629-
"\n",
630-
" # Build affine from spatial metadata\n",
631-
" spacing = np.array(seg_image.spacing)\n",
632-
" origin = np.array(seg_image.origin)\n",
633-
" direction = np.array(seg_image.direction).reshape(3, 3)\n",
634-
"\n",
635-
" affine = self._build_affine(spacing, origin, direction)\n",
636-
"\n",
637-
" # Make contiguous (array may be non-contiguous after transpose)\n",
638-
" seg_array = np.ascontiguousarray(seg_array)\n",
639-
"\n",
640-
" # Create MONAI MetaTensor with metadata\n",
641-
" meta_tensor = MetaTensor(seg_array)\n",
642-
" meta_tensor.affine = affine\n",
643-
" meta_tensor.meta[\"filename_or_obj\"] = str(dcm_file)\n",
644-
" meta_tensor.meta[\"overlay_info\"] = overlay_info\n",
645-
" meta_tensor.meta[\"original_channel_dim\"] = \"no_channel\"\n",
646-
"\n",
647-
" d[key] = meta_tensor\n",
648-
" d[f\"{key}_meta_dict\"] = dict(meta_tensor.meta)\n",
649-
"\n",
650-
" return d\n",
651-
"\n",
652-
"\n",
653-
"# Load CT with MONAI's ITKReader\n",
654-
"ct_transforms = Compose(\n",
655-
" [\n",
656-
" LoadImaged(keys=[\"image\"], reader=ITKReader()),\n",
657-
" EnsureChannelFirstd(keys=[\"image\"]),\n",
658-
" ]\n",
659-
")\n",
660-
"\n",
661-
"# Load SEG with our custom LoadDicomSegd\n",
662-
"seg_transforms = Compose(\n",
663-
" [\n",
664-
" LoadDicomSegd(keys=[\"label\"]),\n",
665-
" EnsureChannelFirstd(keys=[\"label\"]),\n",
666-
" ]\n",
667-
")\n",
668-
"\n",
669-
"# Load both\n",
670-
"image_path = os.path.join(seg_dir, demo_pair[\"image_uid\"])\n",
671-
"seg_path = os.path.join(seg_dir, demo_pair[\"seg_uid\"])\n",
672-
"\n",
673-
"ct_data = ct_transforms({\"image\": image_path})\n",
674-
"seg_data = seg_transforms({\"label\": seg_path})\n",
675-
"\n",
676-
"ct_image = ct_data[\"image\"]\n",
677-
"seg_label = seg_data[\"label\"]\n",
678-
"\n",
679-
"print(f\"CT image shape: {ct_image.shape}\")\n",
680-
"print(f\"Segmentation shape: {seg_label.shape}\")\n",
681-
"print(f\"Unique labels: {torch.unique(seg_label)[:10].tolist()}...\") # First 10 labels"
682-
]
480+
"source": "class LoadDicomSegd(MapTransform):\n \"\"\"Load DICOM Segmentation (DICOM-SEG) files using ITKWasm.\n\n DICOM-SEG is an enhanced multiframe DICOM format that stores segmentation\n masks with segment metadata including recommended display colors.\n\n The affine matrix is derived directly from DICOM metadata (direction cosines,\n spacing, origin) with LPS→RAS conversion applied to match MONAI's ITKReader\n convention. No axis flipping is performed — orientation is fully encoded in\n the affine via the direction cosine matrix.\n \"\"\"\n\n def __init__(self, keys: KeysCollection, allow_missing_keys: bool = False):\n super().__init__(keys, allow_missing_keys)\n\n def _find_dcm_file(self, path: Path) -> Path:\n \"\"\"Find .dcm file in directory or return path if already a file.\"\"\"\n if path.is_file():\n return path\n dcm_files = list(path.glob(\"*.dcm\"))\n if not dcm_files:\n raise FileNotFoundError(f\"No .dcm files found in {path}\")\n return dcm_files[0]\n\n def _build_affine(self, spacing, origin, direction) -> np.ndarray:\n \"\"\"Build 4x4 affine matrix from DICOM spatial metadata.\n\n Converts from ITK/DICOM LPS convention to MONAI's RAS-like convention\n by negating X and Y world coordinates (LPS→RAS). No axis flips are\n applied — orientation is fully encoded in the affine via the direction\n cosine matrix.\n\n Args:\n spacing: Voxel spacing (X, Y, Z) as returned by itkwasm\n origin: Physical coordinates of voxel [0,0,0] in LPS\n direction: 3x3 direction cosine matrix D where D[i,j] is the\n component of voxel-axis-j's unit vector along LPS\n physical axis i. ITK affine formula:\n world_lps = D @ diag(spacing) @ voxel + origin\n \"\"\"\n lps_to_ras = np.diag([-1.0, -1.0, 1.0])\n affine = np.eye(4)\n affine[:3, :3] = lps_to_ras @ direction @ np.diag(spacing)\n affine[:3, 3] = lps_to_ras @ origin\n return affine\n\n def __call__(self, data: Mapping[Hashable, any]) -> dict[Hashable, any]:\n d = dict(data)\n for key in self.key_iterator(d):\n path = Path(d[key])\n dcm_file = self._find_dcm_file(path)\n\n # Read using ITKWasm\n seg_image, overlay_info = itkwasm_dicom.read_segmentation(dcm_file)\n\n # ITKWasm returns array in (Z, Y, X) order but metadata in (X, Y, Z) order.\n # Transpose to (X, Y, Z) to match metadata — this is a layout convention,\n # not an orientation flip.\n seg_array = np.asarray(seg_image.data).copy()\n seg_array = np.transpose(seg_array, (2, 1, 0))\n\n # Build affine from spatial metadata\n spacing = np.array(seg_image.spacing)\n origin = np.array(seg_image.origin)\n direction = np.array(seg_image.direction).reshape(3, 3)\n\n affine = self._build_affine(spacing, origin, direction)\n\n # Make contiguous (array may be non-contiguous after transpose)\n seg_array = np.ascontiguousarray(seg_array)\n\n # Create MONAI MetaTensor with metadata\n meta_tensor = MetaTensor(seg_array)\n meta_tensor.affine = affine\n meta_tensor.meta[\"filename_or_obj\"] = str(dcm_file)\n meta_tensor.meta[\"overlay_info\"] = overlay_info\n meta_tensor.meta[\"original_channel_dim\"] = \"no_channel\"\n\n d[key] = meta_tensor\n d[f\"{key}_meta_dict\"] = dict(meta_tensor.meta)\n\n return d\n\n\n# Load CT with MONAI's ITKReader\nct_transforms = Compose(\n [\n LoadImaged(keys=[\"image\"], reader=ITKReader()),\n EnsureChannelFirstd(keys=[\"image\"]),\n ]\n)\n\n# Load SEG with our custom LoadDicomSegd\nseg_transforms = Compose(\n [\n LoadDicomSegd(keys=[\"label\"]),\n EnsureChannelFirstd(keys=[\"label\"]),\n ]\n)\n\n# Load both\nimage_path = os.path.join(seg_dir, demo_pair[\"image_uid\"])\nseg_path = os.path.join(seg_dir, demo_pair[\"seg_uid\"])\n\nct_data = ct_transforms({\"image\": image_path})\nseg_data = seg_transforms({\"label\": seg_path})\n\nct_image = ct_data[\"image\"]\nseg_label = seg_data[\"label\"]\n\nprint(f\"CT image shape: {ct_image.shape}\")\nprint(f\"Segmentation shape: {seg_label.shape}\")\nprint(f\"Unique labels: {torch.unique(seg_label)[:10].tolist()}...\") # First 10 labels"
683481
},
684482
{
685483
"cell_type": "code",
@@ -1110,4 +908,4 @@
1110908
},
1111909
"nbformat": 4,
1112910
"nbformat_minor": 0
1113-
}
911+
}

0 commit comments

Comments
 (0)