Generated banner for Imaging Data Commons
Generated logo for Imaging Data Commons

Imaging Data Commons

K-DenseCommunityCreated by Andrey Fedorov, @fedorovDistributed by K-Dense

  • Skill
  • Tool integration
  • Science and research
  • MIT
  • Low risk
  • Has scripts

From the author

Queries and downloads public cancer imaging data from NCI Imaging Data Commons. Supports IDC collection discovery, DICOM access, radiology (CT, MR, PET) and pathology AI datasets, metadata SQL, visualization, licensing, and citations. Uses public metadata and download routes without authentication; optional BigQuery and Google Healthcare routes require Google credentials.
— the description by Andrey Fedorov, @fedorov

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The Imaging Data Commons (IDC) skill provides a programmatic interface for accessing large-scale public cancer imaging data. It supports discovery, metadata querying, and file downloads for radiology (CT, MR, PET) and pathology datasets. The skill offers multiple access paths, including a Python client for local workflows, a REST API for lightweight metadata retrieval, and an MCP server for interactive discovery. It handles complex data structures, including DICOM hierarchies and derived analysis results, while ensuring users can verify licensing and citation requirements for research and clinical applications.

What it does

  • Queries and filters public cancer imaging metadata using SQL or attribute-based selection.
  • Downloads DICOM imaging files directly from public cloud storage buckets.
  • Provides browser-based visualization URLs for radiology and pathology images.
  • Retrieves licensing information and citation formats for selected datasets.
  • Supports integration with DICOMweb-compatible tools and PACS systems.
  • Enables programmatic cohort building and manifest generation for large-scale data analysis.

When to use it

  • When you need to find and download specific cancer imaging datasets for research.
  • When you need to perform metadata-driven cohort selection across large imaging collections.
  • When you want to visualize medical images without installing local DICOM viewer software.
  • When you need to integrate IDC data access into automated Python or shell-based pipelines.
  • When you need to verify the licensing terms of specific imaging series before use.

Advantages

  • No authentication required for standard public data access.
  • Supports both lightweight REST-based metadata queries and heavy-duty local downloads.
  • Provides consistent access to versioned data for reproducible research.
  • Includes built-in tools for citation and license management.
  • Offers flexible directory templating for organized local data storage.

Usage examples

“Find all MR series for the 'rider_pilot' collection.”
Returns a list of SeriesInstanceUIDs and metadata for the requested MR images.
“Download the first 5 CT series from the 'nlst' collection to my local data folder.”
Downloads the specified DICOM files into the target directory using the default hierarchy.
“Get a viewer URL for a specific study.”
Provides a direct link to an OHIF or SLIM viewer instance for the requested study.
“What are the license terms for the current selection?”
Returns a breakdown of license types (e.g., CC BY, CC BY-NC) for the selected series.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/k-dense-ai/imaging-data-commons/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip imaging-data-commons.zip -d .agents/skills

Documentation by Andrey Fedorov, @fedorov

Imaging Data Commons

Overview

Query and download public cancer imaging data from the National Cancer Institute Imaging Data Commons (IDC). No authentication required for data access.

Expected network access: IDC metadata is reachable three ways — a bundled local DuckDB index (offline after installation; additional indices are fetched from GitHub and clinical tables from S3), or the hosted IDC service over MCP or REST (api.imaging.datacommons.cancer.gov, no authentication). File downloads use public GCS (storage.googleapis.com) and AWS S3 (s3.amazonaws.com) — no authentication required. DICOMweb access uses either the public IDC proxy (proxy.imaging.datacommons.cancer.gov, no auth) or the Google Cloud Healthcare API (healthcare.googleapis.com, requires GCP authentication). Optional BigQuery queries (bigquery.googleapis.com) also require GCP authentication. Citation resolution contacts DOI services. Public IDC routes require no credentials; optional Google clients use Application Default Credentials.

Reviewed 2026-09-30: idc-index 0.12.5, idc-index-data 24.2.2, IDC v24; hosted API 3.0.0b3. Recheck at use time.

Choose the access path first. There is no single default: the cheapest correct path depends on the session and the task.

  1. Session already has the IDC MCP server? Route discovery and metadata there — see IDC MCP Server.

  2. Otherwise, is idc-index installed and current? Run python scripts/check_version.py. If it passes, use idc-index for everything.

  3. Not installed, and the task is read-only metadata — counts, attribute values, collection lookups, SQL under 10 000 rows, licenses, citations, viewer URLs? Use the REST API over curl; do not install anything. Installing costs ~77 MB of packaged index data plus pandas, pyarrow, and duckdb, which a metadata question does not need. See Data Access Options.

  4. Not installed, and the task needs more than metadata — downloading files, pandas or plotting, pydicom/SimpleITK, pathology tiling, results past 10 000 rows, or a version-pinned script the user re-runs? Install idc-index: check_version.py exits non-zero and prints the exact install command for the running interpreter. Prefer a virtual environment, then restart Python.

idc-index (GitHub) is still the most capable Python path, with query and download helpers in one client. check_version.py never installs anything itself — it also flags a newer idc-index or skill release when one exists.

Setup for the idc-index path: use the intended interpreter for scripts/check_version.py and confirm its meets pinned minimum message before continuing. A launch failure is not a pass.

from idc_index import IDCClient
client = IDCClient()

# Verify IDC data version (should be "v24")
print(f"IDC data version: {client.get_idc_version()}")

Download and image-processing examples are illustrative unless noted; see the reference review notes for verification scope.

Core workflow: query metadata with client.sql_query() → download with client.download_from_selection() → visualize with client.get_viewer_URL(). Python examples below assume this client; Data Access Options has the REST equivalents. For current data scale, run the summary query in references/sql_patterns.md or GET /v3/stats.

IDC MCP Server

IDC operates a hosted MCP server at https://api.imaging.datacommons.cancer.gov/mcp (streamable HTTP, no authentication). Where it is available it complements — it does not replace — the idc-index workflow below.

Identify it by the MCP resource idc://guide, or by three or more of the tool names build_cohort, get_cohort_urls, list_analysis_results, and get_idc_version. Generic names such as run_sql are not evidence on their own. If identification is ambiguous, use idc-index.

If this session has the server, treat it as authoritative for discovery and metadata — IDC version, counts, attribute values, cohort building, metadata SQL — and follow the server's own instructions rather than re-deriving them from this file. Its data version is whatever the server reports: call get_idc_version instead of relying on the version pinned in this file.

Return here for what the server does not do: downloading files, local pandas/notebook analysis, DICOMweb, BigQuery, digital pathology tiling, and reproducible scripts. Hand off by passing SeriesInstanceUIDs from the server to client.download_from_selection(...), and run scripts/check_version.py at that point.

If it is not available, the identical service is reachable with no configuration as a REST API at https://api.imaging.datacommons.cancer.gov/v3 — use it for read-only metadata rather than installing idc-index, per the routing gate in Overview. Suggest connecting the MCP server at most once, only for repeated interactive discovery, and never change the user's configuration yourself.

See references/mcp_guide.md for the tool inventory, handoff patterns, and per-host notes.

When to Use This Skill

  • Finding publicly available radiology (CT, MR, PET) or pathology (slide microscopy) images

  • Selecting image subsets by cancer type, modality, anatomical site, or other metadata

  • Downloading DICOM data from IDC

  • Checking data licenses before use in research or commercial applications

  • Visualizing medical images in a browser without local DICOM viewer software

Quick Navigation

Inline below: the MCP/REST routing rules, the IDC data model, the index tables and how they join, the core API patterns (query, download, visualize, license, cite), best practices, and troubleshooting.

Reference Guides (load on demand):

GuideWhen to Load
index_tables_guide.mdComplex JOINs, schema discovery, DataFrame access
use_cases.mdEnd-to-end workflows: training datasets, batch downloads, DICOM reading with pydicom/SimpleITK, pipeline integration
sql_patterns.mdQuick SQL patterns for filter discovery, annotations, size estimation
clinical_data_guide.mdClinical/tabular data, imaging+clinical joins, value mapping
licensing_and_citation.mdCommercial-use questions, mixed-license cohorts, citation formats
cloud_storage_guide.mdDirect S3/GCS access, versioning, UUID mapping
dicomweb_guide.mdDICOMweb endpoints, PACS integration
digital_pathology_guide.mdSlide microscopy (SM), annotations (ANN), pathology workflows
bigquery_guide.mdFull DICOM metadata, private elements (requires GCP)
cli_guide.mdCommand-line tools (idc download, manifest files)
parquet_access_guide.mdDirect Parquet queries via GCS (no idc-index install needed)
mcp_guide.mdHosted IDC MCP server: tool inventory, identification, handoff to idc-index
rest_api_guide.mdHosted IDC REST API: endpoints, filter syntax, SQL over HTTP, manifests

IDC Data Model

IDC adds two grouping levels above the standard DICOM hierarchy (Patient → Study → Series → Instance):

  • collection_id: Groups patients by disease, modality, or research focus (e.g., tcga_luad, nlst). Treat (collection_id, PatientID) as the patient key; do not assume PatientID is globally unique.

  • analysis_result_id: Identifies derived objects (segmentations, annotations, radiomics features) across one or more original collections. Use it to find AI-generated or expert annotations, while collection_id finds original imaging data (which may itself include deposited annotations).

Key identifiers for queries:

IdentifierScopeUse for
collection_idDataset groupingFiltering by project/study
PatientIDPatientGrouping images by patient
StudyInstanceUIDDICOM studyGrouping of related series, visualization
SeriesInstanceUIDDICOM seriesGrouping of related series, visualization

Index Tables

The idc-index package provides multiple metadata index tables, accessible via SQL or as pandas DataFrames. The REST API exposes the same tables through GET /tables and POST /sql.

Important: client.indices_overview is the authoritative source for current table descriptions, available columns, and their types — query it when writing SQL or exploring data structure. It also answers "which table contains column X"; see references/index_tables_guide.md for that search pattern and full schema discovery.

Available Tables

Always call client.fetch_index("table_name") before querying any index table — it is safe and idempotent for all tables, including those loaded automatically at startup.

FamilyTablesGranularity
Coreindex (primary metadata for all current data), collections_index, analysis_results_indexseries / collection / analysis result
Modality acquisition parametersct_index, mr_index, pt_index, contrast_index1 row = 1 series of that modality
Derived objectsseg_index, rtstruct_index, ann_index, ann_group_index1 row = 1 series (or annotation group)
Microscopysm_index, sm_instance_index1 row = 1 SM series / instance
Geometry, clinical, historyvolume_geometry_index, clinical_index, version_metadata_index, prior_versions_indexsee guide

references/index_tables_guide.md has the full inventory with each table's columns and contents — load it when you need to know what a specialized table actually holds.

prior_versions_index contains historical series versions, including revised versions whose DICOM SeriesInstanceUID still occurs in index. Pin crdc_series_uuid for historical content. For "what's new" in the current release use series_init_idc_version / series_revised_idc_version in the main index table, which are not equivalent to this table's min_idc_version / max_idc_version.

Joining Tables

SeriesInstanceUID is the universal join key for all series-level specialized tables: sm_index, sm_instance_index, seg_index, ann_index, ann_group_index, contrast_index, volume_geometry_index, rtstruct_index, ct_index, mr_index, pt_index. Always join these to index on SeriesInstanceUID. The exceptions below use different column names.

Join ColumnTablesUse Case
collection_idindex, prior_versions_index, collections_index, clinical_indexLink series to collection metadata or clinical data
analysis_result_idindex, analysis_results_indexLink series to analysis result metadata (annotations, segmentations)
source_DOIindex, analysis_results_indexLink by publication DOI
segmented_SeriesInstanceUIDseg_index → indexLink segmentation to its source image series (seg_index.segmented_SeriesInstanceUID = index.SeriesInstanceUID)
referenced_SeriesInstanceUIDann_index → index, rtstruct_index → indexLink annotation or RTSTRUCT to its source image series

Note: subjects, updated, and description appear in multiple tables but have different meanings (counts vs identifiers, different update contexts). A UID-only join to prior_versions_index can match several historical revisions; use CRDC UUIDs to distinguish them.

For detailed join examples, schema discovery patterns, key columns reference, and DataFrame access, see references/index_tables_guide.md.

Clinical Data Access

Clinical (non-imaging) attributes — staging, demographics, therapy — live in per-collection tables. client.fetch_index("clinical_index") loads the dictionary mapping columns to collections; client.get_clinical_table(name) returns one table as a DataFrame.

See references/clinical_data_guide.md for the discovery workflow, coded-value mapping, and joining clinical data with imaging.

Data Access Options

MethodAuthBest ForReference
idc-indexNoDownloads, pandas analysis, unbounded queries — the most capable pathThis document
IDC MCP serverNoDiscovery, cohort building, metadata when the session already has itmcp_guide.md
IDC REST APINoMetadata with no install, from any language or shell — the default when idc-index is absentrest_api_guide.md
Direct Parquet (GCS)NoVersion-pinned queries, or results past the REST row capparquet_access_guide.md
Cloud storage (S3/GCS)NoDirect file access, bulk transfer, custom pipelinescloud_storage_guide.md
DICOMweb via IDC proxyNoTool and PACS integration; daily quota, so testing and moderate usedicomweb_guide.md
DICOMweb via Google HealthcareYes (GCP)The same DICOMweb API at production volume, without the proxy quotadicomweb_guide.md
SlicerIDCBrowserNo3D visualization and analysis in 3D Slicerhttps://github.com/ImagingDataCommons/SlicerIDCBrowser
BigQueryYes (GCP)Full DICOM metadata, private elements, SR measurements — last resortbigquery_guide.md

The IDC Portal (https://portal.imaging.datacommons.cancer.gov/) is interactive only — browser-based exploration, manual cohort selection, and download. Unlike every option above it has no programmatic interface, so point a user there to browse or click through data themselves; never use it as a step in a script or workflow.

REST API — the no-install metadata path

https://api.imaging.datacommons.cancer.gov/v3, no authentication: discovery, cohort counts and manifests, read-only SQL, clinical tables, viewer URLs, licenses, citations. It is the same service as the MCP server over plain HTTP, so it needs no configuration. It never moves image bytes — switch to idc-index to download, to get a DataFrame, or for results past 10 000 rows.

B=https://api.imaging.datacommons.cancer.gov/v3
curl -s $B/version   # idc_version, idc_index_data_version, api_version
curl -s $B/stats     # collections, patients, studies, series, instances, size_TB
curl -s "$B/attributes/Modality/values?limit=5"   # real filter values, with counts
curl -s $B/sql -H 'content-type: application/json' \
  -d '{"sql":"SELECT collection_id, COUNT(*) n FROM index GROUP BY 1 ORDER BY n DESC LIMIT 3"}'
curl -s $B/cohort/counts -H 'content-type: application/json' \
  -d '{"filters":{"terms":{"collection_id":["rider_pilot"]}}}'

The filter object always goes under filters — on cohort/counts, cohort/manifest, cohort/manifest.txt, licenses, and citations alike. A bare filter or an unrecognized key is a 422 naming the fix; an unfiltered series-enumerating request is a 400, not the whole archive. Filtered JSON responses echo filters_applied and warnings (inside counts for manifests) — read them, because they name any predicate the server dropped. A zero count with empty warnings therefore means the filter matched nothing, not that a value was miscased; miscasing produces a warning that says so.

POST /sql takes one read-only SELECT/WITH over the tables idc-index exposes plus clinical.<table>; max_rows defaults to 5 000, caps at 10 000, and truncated flags clipping. GET /attributes lists the 19 filterable attributes — clinical values, segmented anatomy, and acquisition parameters are not among them and need SQL. Request limits still apply. Use v3 only: V1 and V2 are superseded and scheduled for shutdown, so port any /v1/- or Modality_btw-style example a user brings rather than extending it.

Both sides build on idc-index-data, so compare the API's idc_index_data_version against local idc_index_data.__version__ before mixing them: the major is the IDC data release (24.x.y serves v24); minor/patch index builds may correct metadata. If the API is a whole release ahead, idc-index cannot download the extra series — mixed selections can omit unrecognized UIDs, while wholly unmatched selections raise — so either upgrade it (run scripts/check_version.py for the right command) or transfer directly from the bucket with s5cmd --no-sign-request.

See references/rest_api_guide.md for the endpoint reference, filter grounding, limits, and the manifest-based download flow.

Cloud storage organization

All DICOM files live in public buckets mirrored between AWS S3 and GCS, organized by CRDC UUIDs (not DICOM UIDs) to support versioning, as <crdc_series_uuid>/<crdc_instance_uuid>.dcm. Access is free (no egress fees) via AWS CLI, gsutil, or s5cmd with anonymous access; use the series_aws_url column for S3 URLs. Bucket names do not establish a license; query license_short_name for each selected series. See references/cloud_storage_guide.md for the full bucket list and UUID mapping.

DICOMweb access

IDC data is available via DICOMweb (Google Cloud Healthcare API) for PACS integration and DICOMweb-compatible tools: a public proxy (no auth, daily quota) for testing and moderate queries, or Google Healthcare (GCP auth) for production volumes. See references/dicomweb_guide.md.

Direct Parquet access

The idc-index metadata tables are also published as Parquet on a public GCS bucket (idc-index-data-artifacts), queryable with DuckDB or pandas. This needs DuckDB installed For ad-hoc metadata prefer REST /sql; choose Parquet for pinned versions or large results. A separate public S3 export also includes clinical tables and full BigQuery metadata. See references/parquet_access_guide.md.

Core Capabilities

The patterns below are the ones that go wrong when recalled from memory rather than checked. Worked examples for each area live in the reference guides named inline.

1. Discovery — enumerate values before filtering on them

Filtering on a guessed Modality or BodyPartExamined string is the most common cause of an empty result set. Enumerate first:

modalities = client.sql_query("""
    SELECT DISTINCT Modality, COUNT(*) as series_count
    FROM index
    GROUP BY Modality
    ORDER BY series_count DESC
""")
print(modalities)

The same pattern works for any filter column, optionally narrowed by another — BodyPartExamined within a Modality, Manufacturer, collection_id. On the REST path this grounding is a single call — GET /attributes/{attr}/values returns values with counts — and the cohort endpoints report a miscased value in warnings rather than as an empty result.

Two indices carry curated collection-level metadata the primary index does not, both requiring client.fetch_index(...) first: collections_index (cancer types, tumor locations, species, subject counts) and analysis_results_index (derived datasets — AI segmentations, expert annotations, radiomics — with their source collections and modalities).

Cancer type lives in collections_index.cancer_types, not in index — filtering by cancer type requires a join:

client.fetch_index("collections_index")
results = client.sql_query("""
    SELECT i.collection_id, i.PatientID, i.SeriesInstanceUID, i.Modality
    FROM index i
    JOIN collections_index c ON i.collection_id = c.collection_id
    WHERE c.cancer_types LIKE '%Breast%'
      AND i.Modality = 'MR'
    LIMIT 20
""")

client.sql_query() returns a pandas DataFrame. Confirm column names with client.get_index_schema('index') or client.indices_overview before writing a query rather than assuming them.

See references/sql_patterns.md for filter-value discovery, annotation and segmentation queries, size estimation, clinical linking, and version tracking ("what's new in vX" — use series_init_idc_version / series_revised_idc_version in index; historical objects use prior_versions_index).

2. Downloading DICOM files

The two download methods take their first two arguments in opposite order. This is the most common source of broken IDC code — check it rather than recalling it:

MethodFirst argSecond argUse when
download_from_selectiondownloadDir (required)filter kwargs (optional)Filtering by collection, patient, study, or series
download_dicom_seriesseriesInstanceUID (required)downloadDir (required)Downloading specific series by UID only

download_from_selection takes filter keyword arguments, NOT a DataFrame. The name "from_selection" refers to filtering the IDC index by criteria — not to accepting a pandas DataFrame. To download query results, extract the UIDs into a list first:

# Step 1: Query for series UIDs
series_df = client.sql_query("""
    SELECT SeriesInstanceUID
    FROM index
    WHERE Modality = 'CT'
      AND BodyPartExamined = 'CHEST'
      AND collection_id = 'nlst'
    LIMIT 5
""")

# Step 2: Extract UIDs as a list from the DataFrame
uids = list(series_df['SeriesInstanceUID'].values)

# Step 3: Pass the list to download_from_selection (NOT the DataFrame itself)
client.download_from_selection(
    downloadDir="./data/lung_ct",
    seriesInstanceUID=uids       # list of strings, not a DataFrame
)

# Alternative: download_dicom_series has seriesInstanceUID as FIRST arg (different order!)
client.download_dicom_series(
    seriesInstanceUID=uids,      # FIRST arg here
    downloadDir="./data/lung_ct"
)

# Whole collection: downloadDir is still the FIRST positional argument
client.download_from_selection(downloadDir="./data/rider", collection_id="rider_pilot")

Both default to AWS; use source_bucket_location="gcs" for Google. In 0.12.5 the most-specific selector wins, so use SQL first for intersecting criteria and verify every requested UID exists.

Downloaded files are named <crdc_instance_uuid>.dcm, not by SOPInstanceUID. The DICOM UIDs are preserved inside the file metadata, not in the filename. Read DICOM headers for the series UID; crdc_instance_uuid is not a column of the series-level index.

idc download <collection|series-uid|manifest> --download-dir ./data does the same from a shell. See references/cli_guide.md for the dirTemplate hierarchy options (Python default: %collection_id/%PatientID/%StudyInstanceUID/%Modality_%SeriesInstanceUID; dirTemplate="" flattens), manifest downloads with resume, and dry-run size estimation.

3. Visualizing IDC images
viewer_url = client.get_viewer_URL(seriesInstanceUID=uid)        # one series
viewer_url = client.get_viewer_URL(studyInstanceUID=study_uid)   # all series in a study

Returns a browser URL — nothing is downloaded. The method selects OHIF v3 for radiology or SLIM for slide microscopy automatically. Viewing by study is useful when a single DICOM Study holds several Series (T1, T2, and DWI from one MRI session).

4. Licenses and citations — obligations, not optional steps

IDC data carries license terms and attribution requirements that follow it into any downstream publication or product, and neither is inferable from the pixel data. Check the license before use, and generate citations for whatever you download.

# License breakdown for a selection
licenses = client.sql_query("""
    SELECT DISTINCT collection_id, license_short_name,
           COUNT(DISTINCT SeriesInstanceUID) as series_count
    FROM index GROUP BY collection_id, license_short_name
""")

# Citations for the same selection you downloaded (APA by default)
for citation in client.citations_from_selection(collection_id="rider_pilot"):
    print(citation)

In the reviewed v24 snapshot, about 97% of IDC data by size is CC BY (commercial use allowed with attribution) and about 3% is CC BY-NC (non-commercial only). Licenses attach to series, not collections — 39 of 176 collections carry more than one — so check the selection you actually intend to use, and note that each component retains its license obligations.

Both tasks are available from all three access paths, so stay on whichever one the session is already using: idc-index as above, POST /v3/licenses and POST /v3/citations over REST, or the get_licenses and get_citations MCP tools. See references/licensing_and_citation.md for the full license inventory, all three routes, the citation formats (APA, BibTeX, CSL JSON, RDF Turtle), and what to include when publishing.

5. Reaching past the index

Pick the access path with the routing gate in Overview; Data Access Options above is the full routing table.

Before reaching for BigQuery (which needs a Google Cloud project and access), check whether a specialized index table already has the column you want: search client.indices_overview, then client.fetch_index(...) and query locally for free. Full instance metadata, per-segment rows, and SR measurement tables require BigQuery or its public Parquet exports; these are outside the compact idc-index tables.

Best Practices

  • Check schema before writing queries — Use client.get_index_schema('index') (reads cached metadata, no SQL executed) or client.indices_overview to see all available columns and their descriptions. The version-tracking columns series_init_idc_version and series_revised_idc_version in the main index table directly answer "what's new / when was this added" questions without touching prior_versions_index.

  • Use the index for IDC data content questions - Query the IDC index directly, via client.sql_query() locally or POST /v3/sql over HTTP. Web sources (release notes, blog posts, documentation pages) are frequently out of date and will produce incorrect answers. The index is the authoritative source; use it even when web search is available.

  • Verify the IDC data version at the start of a session - client.get_idc_version(), GET /v3/version, or the MCP get_idc_version tool, depending on the path in use (currently v24). For a stale local index, run scripts/check_version.py and use the upgrade command it prints

  • Check licenses and generate citations - Query license_short_name and respect CC BY vs CC BY-NC terms; use citations_from_selection() to produce citations from source_DOI for publications

  • Explore small, then commit - Use LIMIT (or a low max_rows) while exploring, and check collection size before downloading — some collections are terabytes. See references/cli_guide.md

  • Keep downloads reproducible - Organize with dirTemplate (e.g. %collection_id/%PatientID/%Modality) and save the Series UIDs or manifest behind any dataset you build

Troubleshooting

Issue: ModuleNotFoundError: No module named 'idc_index'

  • Cause: idc-index package not installed

  • Solution: If the task is read-only metadata, do not install it — use the REST API instead (Data Access Options). Otherwise run scripts/check_version.py and use the install command it prints, which targets the running interpreter and pins the vetted version. For data analysis also add pandas, numpy, and pydicom (tested with pandas>=1.5, numpy>=1.23, pydicom>=2.3)

Issue: Download fails with connection timeout

  • Cause: Network instability or large download size

  • Solution: Download in smaller batches (10-20 series); see references/cli_guide.md for --use-s5cmd-sync resume and retry guidance

Issue: BigQuery quota exceeded or billing errors

  • Cause: Project quotas, sandbox limits, or billing configuration prevent the query

  • Solution: Use idc-index mini-index for simple queries (no billing required), or see references/bigquery_guide.md for cost optimization tips

Issue: Series UID not found or no data returned

  • Cause: Typo in UID, data not in the current IDC version, or wrong field name

  • Solution: Test with LIMIT 5 first, check field names against client.indices_overview, and confirm the series is in the current version (some old data is deprecated)

Issue: Column not found in index table (e.g., SliceThickness, PixelSpacing, KVP, EchoTime, InjectedDose)

  • Cause: The index table contains series-level metadata only; modality-specific acquisition and reconstruction parameters live in dedicated tables (ct_index, mr_index, pt_index)

  • Solution: Search client.indices_overview for the column to find its table — the loop is under Finding which table contains a column in references/index_tables_guide.md — then fetch and join on SeriesInstanceUID:

    client.fetch_index("ct_index")
    result = client.sql_query("""
        SELECT i.SeriesInstanceUID, i.Modality, c.SliceThickness, c.KVP, c.PixelSpacing_row_mm
        FROM index i
        JOIN ct_index c USING (SeriesInstanceUID)
        WHERE i.collection_id = 'your_collection'
    """)

Issue: Downloaded DICOM files won't open

  • Cause: Corrupted download, or an object type the viewer does not handle — SEG, RTSTRUCT, SR, and slide microscopy all need specialized tools

  • Solution: Inspect Modality, SOPClassUID, and transfer syntax with normal pydicom.dcmread; forced parsing is not validation. Check download integrity and decoder/viewer support before re-downloading; reserve force=True for diagnosed non-Part-10 inputs.

Resources

Reference guides and their decision triggers are listed in Quick Navigation above.

Files

references/bigquery_guide.md

BigQuery Guide for IDC

Reviewed 2026-09-30: current IDC table documentation and Google BigQuery API/cost guidance. The GoogleSQL examples below are illustrative; no authenticated BigQuery jobs were run. Use a dry run against your pinned dataset before executing them.

For most queries and downloads, use idc-index (see main SKILL.md). This guide covers BigQuery for advanced use cases requiring full DICOM metadata or complex joins.

Prerequisites

Requirements:

  1. Google account

  2. Google Cloud project with BigQuery access (sandbox supports limited public queries without a billing account; billing applies beyond applicable limits)

  3. google-cloud-bigquery[pandas] Python package for .to_dataframe(), or BigQuery console access

Authentication setup:

# Install Google Cloud SDK, then:
gcloud auth application-default login

When to Use BigQuery

Use BigQuery instead of idc-index when you need:

  • Instance-level DICOM metadata beyond the compact series-level index; extraction has depth/size limits

  • Hosted queries across large clinical/imaging tables (local DuckDB and REST also support clinical joins)

  • DICOM sequence attributes (nested structures)

  • Queries on fields not in the idc-index mini-index

  • Private DICOM elements (vendor-specific tags in OtherElements column)

  • Per-segment detail from DICOM Segmentation objects — idc-index seg_index gives series-level metadata, but not per-segment rows/number associations; use the segmentations table when those details are needed

  • Quantitative measurements from DICOM SR — radiomics features (volume, diameter, shape descriptors) without downloading and parsing SR files; no idc-index equivalent

  • Qualitative measurements from DICOM SR — coded evaluations (malignancy rating, texture, margin) without parsing SR files; no idc-index equivalent

Accessing IDC in BigQuery

The same metadata is also exported as public Parquet in idc-open-metadata, including clinical tables and SR measurements. For an unauthenticated alternative see references/parquet_access_guide.md; large instance-level joins may still favor BigQuery.

Dataset Structure

All IDC tables are in the bigquery-public-data BigQuery project.

Current version (recommended for exploration):

  • bigquery-public-data.idc_current.*

  • bigquery-public-data.idc_current_clinical.*

Versioned datasets (recommended for reproducibility):

  • bigquery-public-data.idc_v{IDC version}.*

  • bigquery-public-data.idc_v{IDC version}_clinical.*

Always use versioned datasets for reproducible research!

Key Tables

dicom_all

Primary table joining complete DICOM metadata with IDC-specific columns (collection_id, gcs_url, license). Contains exported DICOM fields plus collection and administrative metadata, one row per instance. See dicom_all.sql for the exact derivation.

SELECT 
  collection_id,
  PatientID,
  StudyInstanceUID, 
  SeriesInstanceUID,
  Modality,
  BodyPartExamined,
  SeriesDescription,
  gcs_url,
  license_short_name
FROM `bigquery-public-data.idc_current.dicom_all`
WHERE Modality = 'CT'
  AND BodyPartExamined = 'CHEST'
LIMIT 10
Derived Tables

These tables are derived from DICOM objects (Segmentation and Structured Report) and have no equivalent in idc-index. Use them to query per-segment anatomy, radiomics features, and qualitative assessments without downloading DICOM files.

segmentations — one row per segment within a DICOM SEG object. Lets you search by anatomical structure name or DICOM coded concept. The idc-index seg_index gives series-level metadata; this table gives per-segment detail.

measurement_groups — one row per SR TID1500 measurement group. The parent grouping for quantitative and qualitative measurements; links measurements to segmentations and source images.

quantitative_measurements — one row per numeric measurement within an SR TID1500 group. Contains radiomics features (volume, diameter, shape descriptors, texture) extracted from DICOM SR without downloading or parsing SR files.

qualitative_measurements — one row per coded evaluation within an SR TID1500 group. Contains assessed findings (malignancy likelihood, texture, margin type) using coded concept values.

See the Derived Tables: Detailed Documentation section below for schemas, column descriptions, and query examples.

Collection Metadata

original_collections_metadata - Collection-level descriptions

SELECT
  collection_id,
  cancer_types,
  tumor_locations,
  subjects,
  src.source_doi,
  src.modalities,
  src.license.license_short_name
FROM `bigquery-public-data.idc_current.original_collections_metadata`,
UNNEST(sources) AS src
WHERE cancer_types LIKE '%Lung%'

Common Query Patterns

Find Collections by Criteria
SELECT 
  collection_id,
  COUNT(DISTINCT PatientID) as patient_count,
  COUNT(DISTINCT SeriesInstanceUID) as series_count,
  ARRAY_AGG(DISTINCT Modality) as modalities
FROM `bigquery-public-data.idc_current.dicom_all`
WHERE BodyPartExamined LIKE '%BRAIN%'
GROUP BY collection_id
HAVING patient_count > 50
ORDER BY patient_count DESC
Get Download URLs
SELECT
  SeriesInstanceUID,
  gcs_url
FROM `bigquery-public-data.idc_current.dicom_all`
WHERE collection_id = 'rider_pilot'
  AND Modality = 'CT'
Find Studies with Multiple Modalities
SELECT
  StudyInstanceUID,
  ARRAY_AGG(DISTINCT Modality) as modalities,
  COUNT(DISTINCT SeriesInstanceUID) as series_count
FROM `bigquery-public-data.idc_current.dicom_all`
GROUP BY StudyInstanceUID
HAVING ARRAY_LENGTH(ARRAY_AGG(DISTINCT Modality)) > 1
LIMIT 100
License Filtering
SELECT
  collection_id,
  license_short_name,
  COUNT(*) as instance_count
FROM `bigquery-public-data.idc_current.dicom_all`
WHERE license_short_name = 'CC BY 4.0'
GROUP BY collection_id, license_short_name
Find Segmentations with Source Images
SELECT
  src.collection_id,
  seg.SeriesInstanceUID as seg_series,
  seg.SegmentedPropertyType,
  src.SeriesInstanceUID as source_series,
  src.Modality as source_modality
FROM `bigquery-public-data.idc_current.segmentations` seg
JOIN (SELECT DISTINCT SeriesInstanceUID, collection_id, PatientID, Modality
      FROM `bigquery-public-data.idc_current.dicom_all`) src
  ON seg.segmented_SeriesInstanceUID = src.SeriesInstanceUID
WHERE src.collection_id = 'qin_prostate_repeatability'
LIMIT 10

Derived Tables: Detailed Documentation

dicom_all is instance-level. The examples below project distinct series metadata before joining it to segment/measurement rows, preventing one measurement from being duplicated for every image slice. Retain SOPInstanceUID plus measurementGroup_number as measurement keys, and inspect units and derivation modifiers before comparing quantities.

segmentations

One row per segment within a DICOM Segmentation (SEG) object. Unlike idc-index seg_index (one row per SEG series), this table exposes each labeled region individually so you can search by anatomical structure or finding type.

Key columns:

ColumnTypeDescription
SeriesInstanceUIDSTRINGSEG series UID
SOPInstanceUIDSTRINGSEG instance UID
PatientIDSTRINGPatient identifier
StudyInstanceUIDSTRINGStudy UID
SegmentNumberINTEGERSegment index within the SEG (starting from 1)
SegmentedPropertyCategoryRECORDCoded category (e.g., "Anatomical Structure", "Morphologically Altered Structure")
SegmentedPropertyTypeRECORDSpecific structure (e.g., "Liver", "Kidney", "Neoplasm")
AnatomicRegionRECORDOptional anatomic region modifier
SegmentAlgorithmTypeSTRINGAUTOMATIC, SEMIAUTOMATIC, or MANUAL
SegmentAlgorithmNameSTRINGAlgorithm name (e.g., "TotalSegmentator")
TrackingUIDSTRINGLinks segment to SR measurements
TrackingIDSTRINGHuman-readable tracking label
segmented_SeriesInstanceUIDSTRINGSource image series UID — join to dicom_all to get collection/modality
viewer_urlSTRINGDirect IDC viewer link for the SEG

SegmentedPropertyCategory and SegmentedPropertyType are RECORD types with sub-fields CodeValue, CodingSchemeDesignator, and CodeMeaning. Use .CodeMeaning for human-readable filtering.

idc-index gap: seg_index in idc-index has total_segments, AlgorithmName, and aggregated codes, but does not expose individual segment anatomy per row. Use this table when segment-level details are required. For screening series that contain a structure, the aggregated SegmentedPropertyType_CodeMeanings array in seg_index can suffice.

Discover what structures are segmented across IDC:

SELECT
  SegmentedPropertyCategory.CodeMeaning AS category,
  SegmentedPropertyType.CodeMeaning AS structure,
  SegmentAlgorithmType,
  COUNT(DISTINCT SeriesInstanceUID) AS seg_series_count
FROM `bigquery-public-data.idc_current.segmentations`
GROUP BY 1, 2, 3
ORDER BY seg_series_count DESC
LIMIT 20

Find all SEG series containing a specific structure, with source image context:

SELECT
  seg.SeriesInstanceUID AS seg_series,
  seg.SegmentNumber,
  seg.SegmentedPropertyType.CodeMeaning AS structure,
  seg.SegmentAlgorithmType,
  seg.SegmentAlgorithmName,
  img.collection_id,
  img.PatientID,
  img.Modality,
  seg.viewer_url
FROM `bigquery-public-data.idc_current.segmentations` seg
JOIN (SELECT DISTINCT SeriesInstanceUID, collection_id, PatientID, Modality
      FROM `bigquery-public-data.idc_current.dicom_all`) img
  ON seg.segmented_SeriesInstanceUID = img.SeriesInstanceUID
WHERE seg.SegmentedPropertyType.CodeMeaning = 'Liver'
  AND seg.SegmentAlgorithmType = 'AUTOMATIC'
LIMIT 20

Find all segment types present in a collection:

SELECT
  seg.SegmentedPropertyType.CodeMeaning AS structure,
  seg.SegmentAlgorithmType,
  COUNT(DISTINCT seg.SeriesInstanceUID) AS seg_series_count
FROM `bigquery-public-data.idc_current.segmentations` seg
JOIN (SELECT DISTINCT SeriesInstanceUID, collection_id, PatientID, Modality
      FROM `bigquery-public-data.idc_current.dicom_all`) img
  ON seg.segmented_SeriesInstanceUID = img.SeriesInstanceUID
WHERE img.collection_id = 'nlst'
GROUP BY 1, 2
ORDER BY seg_series_count DESC

Link segments to SR measurements using the referenced SEG instance and segment number:

-- Find segments that have corresponding SR measurements
SELECT
  seg.SeriesInstanceUID AS seg_series,
  seg.SegmentNumber,
  seg.SegmentedPropertyType.CodeMeaning AS structure,
  qm.Quantity.CodeMeaning AS measurement,
  ROUND(CAST(qm.Value AS FLOAT64), 2) AS value,
  qm.Units.CodeMeaning AS units
FROM `bigquery-public-data.idc_current.segmentations` seg
JOIN `bigquery-public-data.idc_current.quantitative_measurements` qm
  ON seg.SOPInstanceUID = qm.segmentationInstanceUID
  AND seg.SegmentNumber = qm.segmentationSegmentNumber
WHERE seg.SegmentedPropertyType.CodeMeaning = 'Neoplasm'
  AND qm.Quantity.CodeMeaning = 'Volume from Voxel Summation'
LIMIT 10

quantitative_measurements

One row per numeric measurement in a DICOM SR TID1500 Measurement Report. Contains radiomics features (shape, intensity, texture) and clinical measurements (volume, diameter, SUV). These measurements are pre-extracted from SR — no download or DICOM parsing needed.

No compact idc-index equivalent. Available in BigQuery and its public Parquet export.

Key columns:

ColumnTypeDescription
SOPInstanceUIDSTRINGSR instance UID
SeriesInstanceUIDSTRINGSR series UID — join to dicom_all for collection/modality
SeriesDescriptionSTRINGSR series description (e.g., "TotalSegmentator(v1.5.6) shape Measurements")
PatientIDSTRINGPatient identifier
measurementGroup_numberINTEGERGroup index within the SR (0-based); join key with measurement_groups and qualitative_measurements
QuantityRECORDWhat was measured — CodeValue, CodingSchemeDesignator, CodeMeaning (e.g., "Volume from Voxel Summation")
ValueNUMERICThe numeric measurement value
UnitsRECORDUnits — CodeMeaning (e.g., "cubic millimeter", "no units", "Hounsfield Unit")
derivationModifierRECORDHow the value was derived (e.g., "Mean", "Minimum", "Maximum")
lateralityModifierRECORDLaterality qualifier
findingRECORDWhat finding was measured — CodeMeaning (e.g., "Nodule", "Organ", "Anatomical Structure")
findingSiteRECORDWhere the finding is — CodeMeaning (e.g., "Liver", "Esophagus", "Lung")
trackingIdentifierSTRINGHuman-readable tracking label (e.g., "Nodule 1", "Measurements group 26")
trackingUniqueIdentifierSTRINGTracking UID — links back to segmentations.TrackingUID
segmentationInstanceUIDSTRINGSOPInstanceUID of the referenced SEG object
segmentationSeriesUIDSTRINGSeriesInstanceUID of the referenced SEG object
segmentationSegmentNumberINTEGERSegment number within the SEG — join to segmentations.SegmentNumber
sourceSegmentedSeriesUIDSTRINGSource image series — join to dicom_all.SeriesInstanceUID

Discover available measurement types:

SELECT
  Quantity.CodeMeaning AS measurement,
  Units.CodeMeaning AS units,
  COUNT(*) AS measurement_count,
  COUNT(DISTINCT SeriesInstanceUID) AS sr_series_count
FROM `bigquery-public-data.idc_current.quantitative_measurements`
GROUP BY 1, 2
ORDER BY measurement_count DESC
LIMIT 20

Query measurements for a specific structure (e.g., liver volume across collections):

SELECT
  qm.PatientID,
  ROUND(CAST(qm.Value AS FLOAT64) / 1000, 1) AS volume_cm3,
  img.collection_id,
  qm.segmentationSeriesUID
FROM `bigquery-public-data.idc_current.quantitative_measurements` qm
JOIN (SELECT DISTINCT SeriesInstanceUID, collection_id, PatientID, Modality
      FROM `bigquery-public-data.idc_current.dicom_all`) img
  ON qm.sourceSegmentedSeriesUID = img.SeriesInstanceUID
WHERE qm.Quantity.CodeMeaning = 'Volume from Voxel Summation'
  AND qm.findingSite.CodeMeaning = 'Liver'
  AND qm.Units.CodingSchemeDesignator = 'UCUM'
  AND qm.Units.CodeValue = 'mm3'
ORDER BY volume_cm3 DESC
LIMIT 20

Retrieve all measurements for a specific patient and finding:

SELECT
  qm.measurementGroup_number,
  qm.finding.CodeMeaning AS finding,
  qm.findingSite.CodeMeaning AS finding_site,
  qm.lateralityModifier.CodeMeaning AS laterality,
  qm.Quantity.CodeMeaning AS feature,
  ROUND(CAST(qm.Value AS FLOAT64), 3) AS value,
  qm.Units.CodeMeaning AS units
FROM `bigquery-public-data.idc_current.quantitative_measurements` qm
WHERE qm.PatientID = 'LIDC-IDRI-0001'
  AND qm.finding.CodeMeaning = 'Nodule'
ORDER BY qm.measurementGroup_number, qm.Quantity.CodeMeaning

qualitative_measurements

One row per coded evaluation in a DICOM SR TID1500 Measurement Report. Instead of numeric values, these record assessed characteristics using coded concept pairs (e.g., Quantity="Malignancy", Value="4 out of 5 (Moderately Suspicious for Cancer)").

No compact idc-index equivalent. Available in BigQuery and its public Parquet export.

Key columns:

ColumnTypeDescription
SOPInstanceUIDSTRINGSR instance UID
SeriesInstanceUIDSTRINGSR series UID — join to dicom_all for collection/modality
PatientIDSTRINGPatient identifier
measurementGroup_numberINTEGERGroup index within the SR — join key with quantitative_measurements
QuantityRECORDWhat was assessed — CodeMeaning (e.g., "Malignancy", "Calcification", "Texture")
ValueRECORDThe coded answer — CodeMeaning (e.g., "4 out of 5 (Moderately Suspicious for Cancer)")
findingRECORDWhat finding was assessed — CodeMeaning (e.g., "Nodule")
findingSiteRECORDAnatomic site — CodeMeaning (e.g., "Lung")
trackingIdentifierSTRINGHuman-readable tracking label
segmentationInstanceUIDSTRINGSOPInstanceUID of the referenced SEG object
segmentationSeriesUIDSTRINGSeriesInstanceUID of the referenced SEG object
segmentationSegmentNumberINTEGERSegment number within the referenced SEG
sourceSegmentedSeriesUIDSTRINGSource image series — join to dicom_all.SeriesInstanceUID

Discover available qualitative features and their values:

SELECT
  Quantity.CodeMeaning AS feature,
  Value.CodeMeaning AS assessed_value,
  finding.CodeMeaning AS finding,
  COUNT(*) AS count
FROM `bigquery-public-data.idc_current.qualitative_measurements`
GROUP BY 1, 2, 3
ORDER BY count DESC
LIMIT 20

Find all nodules with a specific malignancy rating:

SELECT
  qm.PatientID,
  qm.trackingIdentifier AS nodule_id,
  qm.Value.CodeMeaning AS malignancy_rating,
  img.collection_id
FROM `bigquery-public-data.idc_current.qualitative_measurements` qm
JOIN (SELECT DISTINCT SeriesInstanceUID, collection_id, PatientID, Modality
      FROM `bigquery-public-data.idc_current.dicom_all`) img
  ON qm.SeriesInstanceUID = img.SeriesInstanceUID
WHERE qm.Quantity.CodeMeaning = 'Malignancy'
  AND qm.Value.CodeMeaning LIKE '%Suspicious%'
ORDER BY qm.PatientID
LIMIT 20

measurement_groups

The parent table for TID1500 measurement groups. Each row represents one measurement group within an SR, with references to the segmentation and source image but without the individual measurement values. Use this table when you need to enumerate groups or check what was tracked, without pulling all measurement values.

Key columns: SOPInstanceUID, SeriesInstanceUID, PatientID, measurementGroup_number, trackingIdentifier, trackingUniqueIdentifier, finding, findingSite, segmentationInstanceUID, segmentationSeriesUID, segmentationSegmentNumber, sourceSegmentedSeriesUID, contentSequence (raw SR content sequence).

In most workflows, join quantitative_measurements and qualitative_measurements directly using SOPInstanceUID + measurementGroup_number rather than going through measurement_groups.


Combining quantitative and qualitative measurements

The primary use case requiring both tables: correlate numeric features (volume, diameter) with coded assessments (malignancy, texture) for the same finding. Join on SOPInstanceUID + measurementGroup_number.

Example: LIDC-IDRI lung nodule analysis — malignancy rating with volume and diameter:

SELECT
  qual.PatientID,
  qual.trackingIdentifier AS nodule_id,
  qual.Value.CodeMeaning AS malignancy_rating,
  ROUND(CAST(vol.Value AS FLOAT64), 1) AS volume_mm3,
  ROUND(CAST(diam.Value AS FLOAT64), 1) AS diameter_mm
FROM `bigquery-public-data.idc_current.qualitative_measurements` qual
JOIN `bigquery-public-data.idc_current.quantitative_measurements` vol
  ON qual.SOPInstanceUID = vol.SOPInstanceUID
  AND qual.measurementGroup_number = vol.measurementGroup_number
JOIN `bigquery-public-data.idc_current.quantitative_measurements` diam
  ON qual.SOPInstanceUID = diam.SOPInstanceUID
  AND qual.measurementGroup_number = diam.measurementGroup_number
WHERE qual.Quantity.CodeMeaning = 'Malignancy'
  AND vol.Quantity.CodeMeaning = 'Volume'
  AND diam.Quantity.CodeMeaning = 'Diameter'
ORDER BY qual.PatientID, qual.trackingIdentifier
LIMIT 20

Joining all three derived tables to get full segment context:

SELECT
  seg.SegmentedPropertyType.CodeMeaning AS structure,
  qual.Quantity.CodeMeaning AS qualitative_feature,
  qual.Value.CodeMeaning AS qualitative_value,
  qm.Quantity.CodeMeaning AS quantitative_feature,
  ROUND(CAST(qm.Value AS FLOAT64), 3) AS numeric_value,
  qm.Units.CodeMeaning AS units,
  img.collection_id
FROM `bigquery-public-data.idc_current.segmentations` seg
JOIN `bigquery-public-data.idc_current.qualitative_measurements` qual
  ON seg.SOPInstanceUID = qual.segmentationInstanceUID
  AND seg.SegmentNumber = qual.segmentationSegmentNumber
JOIN `bigquery-public-data.idc_current.quantitative_measurements` qm
  ON qual.SOPInstanceUID = qm.SOPInstanceUID
  AND qual.measurementGroup_number = qm.measurementGroup_number
JOIN (SELECT DISTINCT SeriesInstanceUID, collection_id, PatientID, Modality
      FROM `bigquery-public-data.idc_current.dicom_all`) img
  ON seg.segmented_SeriesInstanceUID = img.SeriesInstanceUID
WHERE seg.SegmentedPropertyType.CodeMeaning = 'Neoplasm'
LIMIT 10

Private DICOM Elements

Private DICOM elements are vendor-specific attributes not defined in the DICOM standard. They often contain essential acquisition parameters (like diffusion b-values, gradient directions, or scanner-specific settings) that are critical for image interpretation and analysis.

Understanding Private Elements

How private elements work:

  • Private elements use odd-numbered group numbers (e.g., 0019, 0043, 2001)

  • Each vendor reserves blocks of 256 elements using Private Creator identifiers at positions (gggg,0010-00FF)

  • For example, GE uses Private Creator "GEMS_PARM_01" at (0043,0010) to reserve elements (0043,1000-10FF)

Standard vs. private tags: Some parameters exist in both forms:

ParameterStandard TagGESiemensPhilips
Diffusion b-value(0018,9087)(0043,1039)(0019,100C)(2001,1003)
Private Creator-Verify block reservation in the objectVerify block reservation in the objectVerify block reservation in the object

Older scanners typically populate only private tags; newer scanners may use standard tags. Always check both.

Challenges with private elements:

  • Require manufacturer DICOM Conformance Statements to interpret

  • Tag meanings can change between software versions

  • May be removed during de-identification for HIPAA compliance

  • Value encoding varies (string vs. numeric, different units)

Accessing Private Elements in BigQuery

Private elements are stored in the OtherElements column of dicom_all as an array of structs with Tag and Data fields.

Tag notation: DICOM notation (0043,1039) becomes BigQuery format Tag_00431039.

Private Element Query Patterns
Discover Available Private Tags

List all non-empty private tags for a collection:

SELECT
  other_elements.Tag,
  COUNT(*) AS instance_count,
  ARRAY_AGG(DISTINCT other_elements.Data[SAFE_OFFSET(0)] IGNORE NULLS LIMIT 5) AS sample_values
FROM `bigquery-public-data.idc_current.dicom_all`,
  UNNEST(OtherElements) AS other_elements
WHERE collection_id = 'qin_prostate_repeatability'
  AND Modality = 'MR'
  AND ARRAY_LENGTH(other_elements.Data) > 0
  AND other_elements.Data[SAFE_OFFSET(0)] IS NOT NULL
  AND other_elements.Data[SAFE_OFFSET(0)] != ''
GROUP BY other_elements.Tag
ORDER BY instance_count DESC

For a specific series:

SELECT
  other_elements.Tag,
  ARRAY_AGG(DISTINCT other_elements.Data[SAFE_OFFSET(0)] IGNORE NULLS) AS values
FROM `bigquery-public-data.idc_current.dicom_all`,
  UNNEST(OtherElements) AS other_elements
WHERE SeriesInstanceUID = '1.3.6.1.4.1.14519.5.2.1.7311.5101.206828891270520544417996275680'
  AND ARRAY_LENGTH(other_elements.Data) > 0
  AND other_elements.Data[SAFE_OFFSET(0)] IS NOT NULL
  AND other_elements.Data[SAFE_OFFSET(0)] != ''
GROUP BY other_elements.Tag

To identify the Private Creator for a tag, look for the reservation element in the same group. For example, if you find Tag_00431039, the Private Creator is at Tag_00430010 (the tag that reserves block 10xx in group 0043).

Identify Equipment Manufacturer

Determine what equipment produced the data to find the correct DICOM Conformance Statement:

SELECT DISTINCT Manufacturer, ManufacturerModelName
FROM `bigquery-public-data.idc_current.dicom_all`
WHERE collection_id = 'qin_prostate_repeatability'
  AND Modality = 'MR'
Access Private Element Values

Use UNNEST to access individual private elements:

SELECT
  SeriesInstanceUID,
  SeriesDescription,
  other_elements.Data[SAFE_OFFSET(0)] AS b_value
FROM `bigquery-public-data.idc_current.dicom_all`,
  UNNEST(OtherElements) AS other_elements
WHERE collection_id = 'qin_prostate_repeatability'
  AND other_elements.Tag = 'Tag_00431039'
LIMIT 10
Aggregate Values by Series

Collect all unique values across slices in a series:

SELECT
  SeriesInstanceUID,
  ANY_VALUE(SeriesDescription) AS SeriesDescription,
  ARRAY_AGG(DISTINCT other_elements.Data[SAFE_OFFSET(0)]) AS b_values
FROM `bigquery-public-data.idc_current.dicom_all`,
  UNNEST(OtherElements) AS other_elements
WHERE collection_id = 'qin_prostate_repeatability'
  AND other_elements.Tag = 'Tag_00431039'
GROUP BY SeriesInstanceUID
Combine Standard and Private Filters

Filter using both standard DICOM attributes and private element values:

SELECT
  PatientID,
  SeriesInstanceUID,
  ANY_VALUE(SeriesDescription) AS SeriesDescription,
  ARRAY_AGG(DISTINCT other_elements.Data[SAFE_OFFSET(0)]) AS b_values,
  COUNT(DISTINCT SOPInstanceUID) AS n_slices
FROM `bigquery-public-data.idc_current.dicom_all`,
  UNNEST(OtherElements) AS other_elements
WHERE collection_id = 'qin_prostate_repeatability'
  AND Modality = 'MR'
  AND other_elements.Tag = 'Tag_00431039'
  AND ImageType[SAFE_OFFSET(0)] = 'ORIGINAL'
  AND other_elements.Data[SAFE_OFFSET(0)] = '1400'
GROUP BY PatientID, SeriesInstanceUID
ORDER BY PatientID
Cross-Collection Analysis

Survey usage of a private tag across all IDC collections:

SELECT
  collection_id,
  ARRAY_TO_STRING(ARRAY_AGG(DISTINCT other_elements.Data[SAFE_OFFSET(0)] IGNORE NULLS), ', ') AS values_found,
  ARRAY_AGG(DISTINCT Manufacturer IGNORE NULLS) AS manufacturers
FROM `bigquery-public-data.idc_current.dicom_all`,
  UNNEST(OtherElements) AS other_elements
WHERE other_elements.Tag = 'Tag_00431039'
  AND other_elements.Data[SAFE_OFFSET(0)] IS NOT NULL
  AND other_elements.Data[SAFE_OFFSET(0)] != ''
GROUP BY collection_id
ORDER BY collection_id
Workflow: Finding and Using Private Tags
  1. Discover available private tags in your collection using the discovery query above

  2. Identify the manufacturer to know which conformance statement to consult

  3. Find the DICOM Conformance Statement from the manufacturer's website (see Resources below)

  4. Search the conformance statement for the parameter you need (e.g., "b_value", "gradient") to understand what each tag contains

  5. Convert tag to BigQuery format: (gggg,eeee) → Tag_ggggeeee

  6. Query and verify results visually in the IDC Viewer

Data Quality Notes
  • Some private tags encode packed values (e.g., GE diffusion fields); values such as "1000000600" are not directly interpretable as b-values without the vendor convention

  • IDC data is de-identified; private tags containing PHI may have been removed or modified

  • The same tag may have different meanings across software versions

  • Inspect source headers and vendor documentation to validate private-tag interpretation; the IDC Viewer provides complementary image QA

Private Element Resources

Manufacturer DICOM Conformance Statements:

DICOM Standard:

Community Resources:

Using Query Results with idc-index

Combine BigQuery for complex queries with idc-index for downloads (no GCP auth needed for downloads):

from google.cloud import bigquery
from idc_index import IDCClient

# Initialize BigQuery client
# Requires: the Python google-cloud-bigquery[pandas] package
# Auth: gcloud auth application-default login
# Project: executes the query and supplies its quota/billing context
bq_client = bigquery.Client(project="your-gcp-project-id")

# Query for series with specific criteria
query = """
SELECT DISTINCT SeriesInstanceUID
FROM `bigquery-public-data.idc_current.dicom_all`
WHERE collection_id = 'tcga_luad'
  AND Modality = 'CT'
  AND Manufacturer = 'GE MEDICAL SYSTEMS'
LIMIT 100
"""

df = bq_client.query(query).to_dataframe()
print(f"Found {len(df)} GE CT series")

# Download with idc-index (no GCP auth required)
idc_client = IDCClient()
idc_client.download_from_selection(
    seriesInstanceUID=list(df['SeriesInstanceUID'].values),
    downloadDir="./tcga_luad_thin_ct"
)

Cost and Optimization

Pricing: On-demand analysis is priced per TiB scanned; the reviewed US rate is $6.25/TiB with the first 1 TiB/month free. Verify the region and current BigQuery pricing before estimating a job.

Minimize data scanned:

  • Select only needed columns (not SELECT *)

  • Use partition/cluster filters when the table layout supports them

  • Use LIMIT to bound returned rows; it generally does not bound scanned bytes

  • Do not assume dicom_all is smaller than dicom_metadata; compare dry-run estimates

  • Preview queries in BQ console (free, shows bytes to scan)

Check cost before running:

query_job = bq_client.query(
    query, job_config=bigquery.QueryJobConfig(dry_run=True, use_query_cache=False)
)
print(f"Query will scan {query_job.total_bytes_processed / 1e9:.2f} GB")

For execution, set maximum_bytes_billed in QueryJobConfig to your budget in bytes; BigQuery refuses a job estimated to exceed it. See estimate and control costs.

Use materialized tables: IDC provides both views (table_name_view) and materialized tables (table_name). Always use the materialized tables (faster, lower cost).

Clinical Data

Clinical data is in separate datasets with collection-specific tables. All clinical data available via idc-index is also available in BigQuery, with the same content and structure. Use BigQuery when you need complex cross-collection queries or joins that aren't possible with the local idc-index tables.

Datasets:

  • bigquery-public-data.idc_current_clinical - current release (for exploration)

  • bigquery-public-data.idc_v{version}_clinical - versioned datasets (for reproducibility)

Discover the current clinical table inventory rather than relying on a fixed count. Not all collections have clinical data (started in IDC v11).

Clinical Table Naming

Most collections use a single table: <collection_id>_clinical

Exception: ACRIN collections use multiple tables for different data types (e.g., acrin_6698_A0, acrin_6698_A1, etc.).

Metadata Tables

Two metadata tables help navigate clinical data:

table_metadata - Collection-level information:

SELECT
  collection_id,
  table_name,
  table_description
FROM `bigquery-public-data.idc_current_clinical.table_metadata`
WHERE collection_id = 'nlst'

column_metadata - Attribute-level details with value mappings:

SELECT
  collection_id,
  table_name,
  column,
  column_label,
  data_type,
  values
FROM `bigquery-public-data.idc_current_clinical.column_metadata`
WHERE collection_id = 'nlst'
  AND column_label LIKE '%stage%'

The values field contains observed attribute values with their descriptions (same as in idc-index clinical_index).

Common Clinical Queries

List available clinical tables:

SELECT table_name
FROM `bigquery-public-data.idc_current_clinical.INFORMATION_SCHEMA.TABLES`
WHERE table_name NOT IN ('table_metadata', 'column_metadata')

Find collections with specific clinical attributes:

SELECT DISTINCT collection_id, table_name, column, column_label
FROM `bigquery-public-data.idc_current_clinical.column_metadata`
WHERE LOWER(column_label) LIKE '%chemotherapy%'

Query clinical data for a collection:

-- Example: NLST cancer staging data
SELECT
  dicom_patient_id,
  clinical_stag,
  path_stag,
  de_stag
FROM `bigquery-public-data.idc_current_clinical.nlst_canc`
WHERE clinical_stag IS NOT NULL
LIMIT 10

Join clinical with imaging data:

SELECT
  d.PatientID,
  d.StudyInstanceUID,
  d.Modality,
  c.clinical_stag,
  c.path_stag
FROM (SELECT DISTINCT collection_id, PatientID, StudyInstanceUID, Modality
      FROM `bigquery-public-data.idc_current.dicom_all`) d
JOIN `bigquery-public-data.idc_current_clinical.nlst_canc` c
  ON d.PatientID = c.dicom_patient_id
WHERE d.collection_id = 'nlst'
  AND d.Modality = 'CT'
  AND c.clinical_stag = '400'  -- Stage IV
LIMIT 20

Cross-collection clinical search:

-- Find all collections with staging information
SELECT
  cm.collection_id,
  cm.table_name,
  cm.column,
  cm.column_label
FROM `bigquery-public-data.idc_current_clinical.column_metadata` cm
WHERE LOWER(cm.column_label) LIKE '%stage%'
ORDER BY cm.collection_id
Key Column: dicom_patient_id

Every clinical table includes dicom_patient_id, which matches the DICOM PatientID attribute in imaging tables. This is the join key between clinical and imaging data.

Note: Clinical table schemas vary significantly by collection. Always check available columns first:

SELECT column_name, data_type
FROM `bigquery-public-data.idc_current_clinical.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name = 'nlst_canc'

See references/clinical_data_guide.md for detailed workflows using idc-index, which provides the same clinical data without requiring BigQuery authentication.

Important Notes

  • Tables are read-only (public dataset)

  • Schema changes between IDC versions

  • Use versioned datasets for reproducibility

  • Some DICOM sequences >15 levels deep are not extracted

  • Very large sequences (>1MB) may be truncated

  • Always check data license before use

Common Errors

Issue: Billing must be enabled

  • Cause: The requested operation exceeds sandbox capabilities or needs billing configuration

  • Solution: Check sandbox limits and project configuration, enable billing when needed, or use idc-index/public Parquet exports

Issue: Query exceeds resource limits

  • Cause: Query scans too much data or is too complex

  • Solution: Inspect dry-run bytes and the plan, use applicable partition/cluster filters, and break into smaller queries; LIMIT alone is not a scan-cost control

Issue: Column not found

  • Cause: Field name typo or not in selected table

  • Solution: Check table schema first with INFORMATION_SCHEMA.COLUMNS

Issue: Permission denied

  • Cause: Not authenticated to Google Cloud

  • Solution: Run gcloud auth application-default login or set GOOGLE_APPLICATION_CREDENTIALS

Resources

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs Python

The author says: Requires network access for hosted APIs, index fetching, citations, and downloads. Local Python workflows target idc-index 0.12.5; BigQuery and Google Healthcare require Google credentials.

Security

What our scanner noticed· Low risk

  • Instructions that use the network
  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Andrey Fedorov, @fedorov
Publisher
K-Dense (Community)
Distributed by
K-Dense
Source
K-Dense-AI/scientific-agent-skills · skills/imaging-data-commons · commit a25cf9e
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.md)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Andrey Fedorov, @fedorov or K-Dense.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download imaging-data-commons.zip