Skill Tracer
Skills › Scientific Agent Skills › imaging-data-commons › sql_patterns.md

sql_patterns.md

469 lines · skills/imaging-data-commons/references/sql_patterns.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# SQL Query Patterns for IDC
2
3**Tested with:** idc-index 0.12.5 (IDC data version v24)
4
5Quick reference for common SQL query patterns when working with IDC data. For detailed examples with context, see the "Core Capabilities" section in the main SKILL.md.
6

SQL Query Patterns for IDC

Tested with: idc-index 0.12.5 (IDC data version v24)

Quick reference for common SQL query patterns when working with IDC data. For detailed examples with context, see the "Core Capabilities" section in the main SKILL.md.

Raw lines 1 to 6
7## When to Use This Guide
8
9Load this guide when you need quick-reference SQL patterns for:
10- Discovering available filter values (modalities, body parts, manufacturers)
11- Finding annotations and segmentations across collections
12- Querying slide microscopy and annotation data
13- Estimating download sizes before download
14- Linking imaging data to clinical data
15- Filtering by 3D volume geometry validity (volume_geometry_index)
16- Finding RT Structure Set series and ROI metadata (rtstruct_index)
17- Filtering by CT/MR/PET acquisition parameters (ct_index, mr_index, pt_index)
19For table schemas, DataFrame access, and join column references, see `references/index_tables_guide.md`.

When to Use This Guide

Load this guide when you need quick-reference SQL patterns for:

  • Discovering available filter values (modalities, body parts, manufacturers)
  • Finding annotations and segmentations across collections
  • Querying slide microscopy and annotation data
  • Estimating download sizes before download
  • Linking imaging data to clinical data
  • Filtering by 3D volume geometry validity (volume_geometry_index)
  • Finding RT Structure Set series and ROI metadata (rtstruct_index)
  • Filtering by CT/MR/PET acquisition parameters (ct_index, mr_index, pt_index)

For table schemas, DataFrame access, and join column references, see references/index_tables_guide.md.

Raw lines 7 to 20
21## Prerequisites
23Needs `idc-index` installed — run `python scripts/check_version.py`, which reports the installed
24version and prints the install command for the interpreter you are running.
26```python
27from idc_index import IDCClient
28client = IDCClient()
29```

Prerequisites

Needs idc-index installed — run python scripts/check_version.py, which reports the installed version and prints the install command for the interpreter you are running.

from idc_index import IDCClient
client = IDCClient()
Raw lines 21 to 30
Runs code python scripts/check_version.py :23
31## Overall Data Scale
33Counts and total size across all of IDC — useful for orienting a user, and for sanity-checking
34that the index loaded the release you expect:
36```python
37stats = client.sql_query("""
38 SELECT
39 COUNT(DISTINCT collection_id) as collections,
40 COUNT(DISTINCT analysis_result_id) as analysis_results,
41 COUNT(DISTINCT (collection_id, PatientID)) as patients,
42 COUNT(DISTINCT StudyInstanceUID) as studies,
43 COUNT(DISTINCT SeriesInstanceUID) as series,
44 SUM(instanceCount) as instances,
45 SUM(series_size_MB)/1000000 as size_TB
46 FROM index
47""")
48print(stats)
49```

Overall Data Scale

Counts and total size across all of IDC — useful for orienting a user, and for sanity-checking that the index loaded the release you expect:

stats = client.sql_query("""
    SELECT
        COUNT(DISTINCT collection_id) as collections,
        COUNT(DISTINCT analysis_result_id) as analysis_results,
        COUNT(DISTINCT (collection_id, PatientID)) as patients,
        COUNT(DISTINCT StudyInstanceUID) as studies,
        COUNT(DISTINCT SeriesInstanceUID) as series,
        SUM(instanceCount) as instances,
        SUM(series_size_MB)/1000000 as size_TB
    FROM index
""")
print(stats)
Raw lines 31 to 50
51### Per-collection breakdown
53```python
54# Get summary statistics from primary index
55collections_summary = client.sql_query("""
56 SELECT collection_id,
57 COUNT(DISTINCT PatientID) as patients,
58 COUNT(DISTINCT SeriesInstanceUID) as series,
59 SUM(series_size_MB) as size_mb
60 FROM index
61 GROUP BY collection_id
62 ORDER BY patients DESC
63""")
64```
66For richer per-collection metadata — cancer types, tumor locations, species, supporting data —
67query `collections_index` instead; for derived datasets, `analysis_results_index`. Both need
68`client.fetch_index(...)` first:
70```python
71client.fetch_index("collections_index")
72collections_info = client.sql_query("""
73 SELECT collection_id, cancer_types, tumor_locations, species, subjects, supporting_data
74 FROM collections_index
75""")
77client.fetch_index("analysis_results_index")
78analysis_info = client.sql_query("""
79 SELECT analysis_result_id, analysis_result_title, subjects, collections, modalities
80 FROM analysis_results_index
81""")
82```

Per-collection breakdown

# Get summary statistics from primary index
collections_summary = client.sql_query("""
    SELECT collection_id,
           COUNT(DISTINCT PatientID) as patients,
           COUNT(DISTINCT SeriesInstanceUID) as series,
           SUM(series_size_MB) as size_mb
    FROM index
    GROUP BY collection_id
    ORDER BY patients DESC
""")

For richer per-collection metadata — cancer types, tumor locations, species, supporting data — query collections_index instead; for derived datasets, analysis_results_index. Both need client.fetch_index(...) first:

client.fetch_index("collections_index")
collections_info = client.sql_query("""
    SELECT collection_id, cancer_types, tumor_locations, species, subjects, supporting_data
    FROM collections_index
""")

client.fetch_index("analysis_results_index")
analysis_info = client.sql_query("""
    SELECT analysis_result_id, analysis_result_title, subjects, collections, modalities
    FROM analysis_results_index
""")
Raw lines 51 to 83
84## Discover Available Filter Values
86```python
87# What modalities exist?
88client.sql_query("SELECT DISTINCT Modality FROM index")
90# What body parts for a specific modality?
91client.sql_query("""
92 SELECT DISTINCT BodyPartExamined, COUNT(*) as n
93 FROM index WHERE Modality = 'CT' AND BodyPartExamined IS NOT NULL
94 GROUP BY BodyPartExamined ORDER BY n DESC
95""")
97# What manufacturers for MR?
98client.sql_query("""
99 SELECT DISTINCT Manufacturer, COUNT(*) as n
100 FROM index WHERE Modality = 'MR'
101 GROUP BY Manufacturer ORDER BY n DESC
102""")
103```

Discover Available Filter Values

# What modalities exist?
client.sql_query("SELECT DISTINCT Modality FROM index")

# What body parts for a specific modality?
client.sql_query("""
    SELECT DISTINCT BodyPartExamined, COUNT(*) as n
    FROM index WHERE Modality = 'CT' AND BodyPartExamined IS NOT NULL
    GROUP BY BodyPartExamined ORDER BY n DESC
""")

# What manufacturers for MR?
client.sql_query("""
    SELECT DISTINCT Manufacturer, COUNT(*) as n
    FROM index WHERE Modality = 'MR'
    GROUP BY Manufacturer ORDER BY n DESC
""")
Raw lines 84 to 104
105## Find Annotations and Segmentations
107**Note:** Not all image-derived objects belong to analysis result collections. Some annotations are deposited alongside original images. Use DICOM Modality or SOPClassUID to find all derived objects regardless of collection type.
109```python
110# Find ALL segmentations and structure sets by DICOM Modality
111# SEG = DICOM Segmentation, RTSTRUCT = Radiotherapy Structure Set
112client.sql_query("""
113 SELECT collection_id, Modality, COUNT(*) as series_count
114 FROM index
115 WHERE Modality IN ('SEG', 'RTSTRUCT')
116 GROUP BY collection_id, Modality
117 ORDER BY series_count DESC
118""")
120# Find segmentations for a specific collection (includes non-analysis-result items)
121client.sql_query("""
122 SELECT SeriesInstanceUID, SeriesDescription, analysis_result_id
123 FROM index
124 WHERE collection_id = 'tcga_luad' AND Modality = 'SEG'
125""")
127# List analysis result collections (curated derived datasets)
128client.fetch_index("analysis_results_index")
129client.sql_query("""
130 SELECT analysis_result_id, analysis_result_title, collections, modalities
131 FROM analysis_results_index
132""")
134# Find analysis results for a specific source collection
135client.sql_query("""
136 SELECT analysis_result_id, analysis_result_title
137 FROM analysis_results_index
138 WHERE Collections LIKE '%tcga_luad%'
139""")
141# Use seg_index for detailed DICOM Segmentation metadata
142client.fetch_index("seg_index")
144# Get segmentation statistics by algorithm
145client.sql_query("""
146 SELECT AlgorithmName, AlgorithmType, COUNT(*) as seg_count
147 FROM seg_index
148 WHERE AlgorithmName IS NOT NULL
149 GROUP BY AlgorithmName, AlgorithmType
150 ORDER BY seg_count DESC
151 LIMIT 10
152""")
154# Find segmentations for specific source images (e.g., chest CT)
155client.sql_query("""
156 SELECT
157 s.SeriesInstanceUID as seg_series,
158 s.AlgorithmName,
159 s.total_segments,
160 s.segmented_SeriesInstanceUID as source_series
161 FROM seg_index s
162 JOIN index src ON s.segmented_SeriesInstanceUID = src.SeriesInstanceUID
163 WHERE src.Modality = 'CT' AND src.BodyPartExamined = 'CHEST'
164 LIMIT 10
165""")
167# Find TotalSegmentator results with source image context
168client.sql_query("""
169 SELECT
170 seg_info.collection_id,
171 COUNT(DISTINCT s.SeriesInstanceUID) as seg_count,
172 SUM(s.total_segments) as total_segments
173 FROM seg_index s
174 JOIN index seg_info ON s.SeriesInstanceUID = seg_info.SeriesInstanceUID
175 WHERE s.AlgorithmName LIKE '%TotalSegmentator%'
176 GROUP BY seg_info.collection_id
177 ORDER BY seg_count DESC
178""")
180# Use ann_index and ann_group_index for Microscopy Bulk Simple Annotations
181# ann_group_index has AnnotationGroupLabel, GraphicType, NumberOfAnnotations, AlgorithmName
182client.fetch_index("ann_index")
183client.fetch_index("ann_group_index")
184client.sql_query("""
185 SELECT g.AnnotationGroupLabel, g.GraphicType, g.NumberOfAnnotations, i.collection_id
186 FROM ann_group_index g
187 JOIN ann_index a ON g.SeriesInstanceUID = a.SeriesInstanceUID
188 JOIN index i ON a.SeriesInstanceUID = i.SeriesInstanceUID
189 WHERE g.AlgorithmName IS NOT NULL
190 LIMIT 10
191""")
192# See references/digital_pathology_guide.md for AnnotationGroupLabel filtering, SM+ANN joins, and more
193```

Find Annotations and Segmentations

Note: Not all image-derived objects belong to analysis result collections. Some annotations are deposited alongside original images. Use DICOM Modality or SOPClassUID to find all derived objects regardless of collection type.

# Find ALL segmentations and structure sets by DICOM Modality
# SEG = DICOM Segmentation, RTSTRUCT = Radiotherapy Structure Set
client.sql_query("""
    SELECT collection_id, Modality, COUNT(*) as series_count
    FROM index
    WHERE Modality IN ('SEG', 'RTSTRUCT')
    GROUP BY collection_id, Modality
    ORDER BY series_count DESC
""")

# Find segmentations for a specific collection (includes non-analysis-result items)
client.sql_query("""
    SELECT SeriesInstanceUID, SeriesDescription, analysis_result_id
    FROM index
    WHERE collection_id = 'tcga_luad' AND Modality = 'SEG'
""")

# List analysis result collections (curated derived datasets)
client.fetch_index("analysis_results_index")
client.sql_query("""
    SELECT analysis_result_id, analysis_result_title, collections, modalities
    FROM analysis_results_index
""")

# Find analysis results for a specific source collection
client.sql_query("""
    SELECT analysis_result_id, analysis_result_title
    FROM analysis_results_index
    WHERE Collections LIKE '%tcga_luad%'
""")

# Use seg_index for detailed DICOM Segmentation metadata
client.fetch_index("seg_index")

# Get segmentation statistics by algorithm
client.sql_query("""
    SELECT AlgorithmName, AlgorithmType, COUNT(*) as seg_count
    FROM seg_index
    WHERE AlgorithmName IS NOT NULL
    GROUP BY AlgorithmName, AlgorithmType
    ORDER BY seg_count DESC
    LIMIT 10
""")

# Find segmentations for specific source images (e.g., chest CT)
client.sql_query("""
    SELECT
        s.SeriesInstanceUID as seg_series,
        s.AlgorithmName,
        s.total_segments,
        s.segmented_SeriesInstanceUID as source_series
    FROM seg_index s
    JOIN index src ON s.segmented_SeriesInstanceUID = src.SeriesInstanceUID
    WHERE src.Modality = 'CT' AND src.BodyPartExamined = 'CHEST'
    LIMIT 10
""")

# Find TotalSegmentator results with source image context
client.sql_query("""
    SELECT
        seg_info.collection_id,
        COUNT(DISTINCT s.SeriesInstanceUID) as seg_count,
        SUM(s.total_segments) as total_segments
    FROM seg_index s
    JOIN index seg_info ON s.SeriesInstanceUID = seg_info.SeriesInstanceUID
    WHERE s.AlgorithmName LIKE '%TotalSegmentator%'
    GROUP BY seg_info.collection_id
    ORDER BY seg_count DESC
""")

# Use ann_index and ann_group_index for Microscopy Bulk Simple Annotations
# ann_group_index has AnnotationGroupLabel, GraphicType, NumberOfAnnotations, AlgorithmName
client.fetch_index("ann_index")
client.fetch_index("ann_group_index")
client.sql_query("""
    SELECT g.AnnotationGroupLabel, g.GraphicType, g.NumberOfAnnotations, i.collection_id
    FROM ann_group_index g
    JOIN ann_index a ON g.SeriesInstanceUID = a.SeriesInstanceUID
    JOIN index i ON a.SeriesInstanceUID = i.SeriesInstanceUID
    WHERE g.AlgorithmName IS NOT NULL
    LIMIT 10
""")
# See references/digital_pathology_guide.md for AnnotationGroupLabel filtering, SM+ANN joins, and more
Raw lines 105 to 194
195## Query Slide Microscopy and Annotation Data
197Use `sm_index` for slide microscopy metadata and `ann_index`/`ann_group_index` for annotations on slides (DICOM ANN objects). Filter annotation groups by `AnnotationGroupLabel` to find annotations by name.
199```python
200client.fetch_index("sm_index")
201client.fetch_index("ann_index")
202client.fetch_index("ann_group_index")
204# Example: find annotation groups by label within a collection
205client.sql_query("""
206 SELECT g.AnnotationGroupLabel, g.GraphicType, g.NumberOfAnnotations
207 FROM ann_group_index g
208 JOIN index i ON g.SeriesInstanceUID = i.SeriesInstanceUID
209 WHERE i.collection_id = 'your_collection_id'
210 AND LOWER(g.AnnotationGroupLabel) LIKE '%keyword%'
211""")
212```
214See `references/digital_pathology_guide.md` for SM queries, ANN filtering patterns, SM+ANN cross-references, and join examples.

Query Slide Microscopy and Annotation Data

Use sm_index for slide microscopy metadata and ann_index/ann_group_index for annotations on slides (DICOM ANN objects). Filter annotation groups by AnnotationGroupLabel to find annotations by name.

client.fetch_index("sm_index")
client.fetch_index("ann_index")
client.fetch_index("ann_group_index")

# Example: find annotation groups by label within a collection
client.sql_query("""
    SELECT g.AnnotationGroupLabel, g.GraphicType, g.NumberOfAnnotations
    FROM ann_group_index g
    JOIN index i ON g.SeriesInstanceUID = i.SeriesInstanceUID
    WHERE i.collection_id = 'your_collection_id'
      AND LOWER(g.AnnotationGroupLabel) LIKE '%keyword%'
""")

See references/digital_pathology_guide.md for SM queries, ANN filtering patterns, SM+ANN cross-references, and join examples.

Raw lines 195 to 215
216## Estimate Download Size
218```python
219# Size for specific criteria
220client.sql_query("""
221 SELECT SUM(series_size_MB) as total_mb, COUNT(*) as series_count
222 FROM index
223 WHERE collection_id = 'nlst' AND Modality = 'CT'
224""")
225```

Estimate Download Size

# Size for specific criteria
client.sql_query("""
    SELECT SUM(series_size_MB) as total_mb, COUNT(*) as series_count
    FROM index
    WHERE collection_id = 'nlst' AND Modality = 'CT'
""")
Raw lines 216 to 226
227## Link to Clinical Data
229```python
230client.fetch_index("clinical_index")
232# Find collections with clinical data and their tables
233client.sql_query("""
234 SELECT collection_id, table_name, COUNT(DISTINCT column_label) as columns
235 FROM clinical_index
236 GROUP BY collection_id, table_name
237 ORDER BY collection_id
238""")
239```
241See `references/clinical_data_guide.md` for complete patterns including value mapping and patient cohort selection.

Link to Clinical Data

client.fetch_index("clinical_index")

# Find collections with clinical data and their tables
client.sql_query("""
    SELECT collection_id, table_name, COUNT(DISTINCT column_label) as columns
    FROM clinical_index
    GROUP BY collection_id, table_name
    ORDER BY collection_id
""")

See references/clinical_data_guide.md for complete patterns including value mapping and patient cohort selection.

Raw lines 227 to 242
243## Version Tracking — "What's New in IDC vX?"
245Use `series_init_idc_version` and `series_revised_idc_version` in the main `index` table. Do NOT
246use `prior_versions_index` as a list of new series: it contains historical revisions,
247including UIDs that still exist with different CRDC UUIDs in the current index.
249```python
250VERSION = 24 # Replace with target version
252# Series added for the first time in vVERSION
253client.sql_query(f"""
254 SELECT collection_id,
255 COUNT(DISTINCT SeriesInstanceUID) as new_series,
256 ROUND(SUM(series_size_MB)/1000, 2) as size_GB
257 FROM index
258 WHERE series_init_idc_version = {VERSION}
259 GROUP BY collection_id
260 ORDER BY new_series DESC
261""")
263# Series revised (updated content) in vVERSION but originally added earlier
264client.sql_query(f"""
265 SELECT collection_id,
266 COUNT(DISTINCT SeriesInstanceUID) as revised_series
267 FROM index
268 WHERE series_revised_idc_version = {VERSION}
269 AND series_init_idc_version < {VERSION}
270 GROUP BY collection_id
271 ORDER BY revised_series DESC
272""")
274# When was each collection first added to IDC?
275client.fetch_index("version_metadata_index")
276client.sql_query("""
277 WITH first_versions AS (
278 SELECT collection_id, MIN(series_init_idc_version) as first_version
279 FROM index
280 GROUP BY collection_id
281 )
282 SELECT f.collection_id, f.first_version, v.version_timestamp as first_release_date
283 FROM first_versions f
284 JOIN version_metadata_index v ON f.first_version = v.idc_version
285 ORDER BY f.first_version DESC
286""")
287```

Version Tracking — "What's New in IDC vX?"

Use series_init_idc_version and series_revised_idc_version in the main index table. Do NOT use prior_versions_index as a list of new series: it contains historical revisions, including UIDs that still exist with different CRDC UUIDs in the current index.

VERSION = 24  # Replace with target version

# Series added for the first time in vVERSION
client.sql_query(f"""
    SELECT collection_id,
           COUNT(DISTINCT SeriesInstanceUID) as new_series,
           ROUND(SUM(series_size_MB)/1000, 2) as size_GB
    FROM index
    WHERE series_init_idc_version = {VERSION}
    GROUP BY collection_id
    ORDER BY new_series DESC
""")

# Series revised (updated content) in vVERSION but originally added earlier
client.sql_query(f"""
    SELECT collection_id,
           COUNT(DISTINCT SeriesInstanceUID) as revised_series
    FROM index
    WHERE series_revised_idc_version = {VERSION}
      AND series_init_idc_version < {VERSION}
    GROUP BY collection_id
    ORDER BY revised_series DESC
""")

# When was each collection first added to IDC?
client.fetch_index("version_metadata_index")
client.sql_query("""
    WITH first_versions AS (
        SELECT collection_id, MIN(series_init_idc_version) as first_version
        FROM index
        GROUP BY collection_id
    )
    SELECT f.collection_id, f.first_version, v.version_timestamp as first_release_date
    FROM first_versions f
    JOIN version_metadata_index v ON f.first_version = v.idc_version
    ORDER BY f.first_version DESC
""")
Raw lines 243 to 288
289## Troubleshooting
291**Issue:** Query returns error "table not found"
292- **Cause:** Index not fetched before query
293- **Solution:** Call `client.fetch_index("table_name")` before using tables other than the primary `index`
295**Issue:** LIKE pattern not matching expected results
296- **Cause:** Case sensitivity or whitespace
297- **Solution:** Use `LOWER(column)` for case-insensitive matching, `TRIM()` for whitespace
299**Issue:** JOIN returns fewer rows than expected
300- **Cause:** NULL values in join columns or no matching records
301- **Solution:** Use `LEFT JOIN` to include rows without matches, check for NULLs with `IS NOT NULL`

Troubleshooting

Issue: Query returns error "table not found"

  • Cause: Index not fetched before query
  • Solution: Call client.fetch_index("table_name") before using tables other than the primary index

Issue: LIKE pattern not matching expected results

  • Cause: Case sensitivity or whitespace
  • Solution: Use LOWER(column) for case-insensitive matching, TRIM() for whitespace

Issue: JOIN returns fewer rows than expected

  • Cause: NULL values in join columns or no matching records
  • Solution: Use LEFT JOIN to include rows without matches, check for NULLs with IS NOT NULL
Raw lines 289 to 302
303## Volume Geometry Validation
305`volume_geometry_index` covers single-frame CT, MR, and PT series. Fetch it before querying.
307```python
308client.fetch_index("volume_geometry_index")
310# Candidate regularly spaced series; verify geometry and task-specific resampling needs
311client.sql_query("""
312 SELECT i.collection_id, i.SeriesInstanceUID, i.BodyPartExamined,
313 v.obliquity_degrees
314 FROM index i
315 JOIN volume_geometry_index v ON i.SeriesInstanceUID = v.SeriesInstanceUID
316 WHERE i.Modality = 'CT'
317 AND v.regularly_spaced_3d_volume = TRUE
318 LIMIT 10
319""")
321# Fraction of 3D-valid CT per collection
322client.sql_query("""
323 SELECT i.collection_id,
324 COUNT(*) as total_ct,
325 SUM(CASE WHEN v.regularly_spaced_3d_volume THEN 1 ELSE 0 END) as valid_3d,
326 ROUND(100.0 * SUM(CASE WHEN v.regularly_spaced_3d_volume THEN 1 ELSE 0 END) / COUNT(*), 1) as pct_valid
327 FROM index i
328 JOIN volume_geometry_index v ON i.SeriesInstanceUID = v.SeriesInstanceUID
329 WHERE i.Modality = 'CT'
330 GROUP BY i.collection_id
331 ORDER BY total_ct DESC
332 LIMIT 10
333""")
334```
336Key columns: `regularly_spaced_3d_volume` (composite flag), `obliquity_degrees` (0 = pure axial/sagittal/coronal), plus individual boolean checks: `single_orientation`, `orthogonal_orientation`, `unique_slice_positions`, `consistent_pixel_spacing`, `consistent_image_dimensions`, `uniform_slice_spacing`.

Volume Geometry Validation

volume_geometry_index covers single-frame CT, MR, and PT series. Fetch it before querying.

client.fetch_index("volume_geometry_index")

# Candidate regularly spaced series; verify geometry and task-specific resampling needs
client.sql_query("""
    SELECT i.collection_id, i.SeriesInstanceUID, i.BodyPartExamined,
           v.obliquity_degrees
    FROM index i
    JOIN volume_geometry_index v ON i.SeriesInstanceUID = v.SeriesInstanceUID
    WHERE i.Modality = 'CT'
      AND v.regularly_spaced_3d_volume = TRUE
    LIMIT 10
""")

# Fraction of 3D-valid CT per collection
client.sql_query("""
    SELECT i.collection_id,
           COUNT(*) as total_ct,
           SUM(CASE WHEN v.regularly_spaced_3d_volume THEN 1 ELSE 0 END) as valid_3d,
           ROUND(100.0 * SUM(CASE WHEN v.regularly_spaced_3d_volume THEN 1 ELSE 0 END) / COUNT(*), 1) as pct_valid
    FROM index i
    JOIN volume_geometry_index v ON i.SeriesInstanceUID = v.SeriesInstanceUID
    WHERE i.Modality = 'CT'
    GROUP BY i.collection_id
    ORDER BY total_ct DESC
    LIMIT 10
""")

Key columns: regularly_spaced_3d_volume (composite flag), obliquity_degrees (0 = pure axial/sagittal/coronal), plus individual boolean checks: single_orientation, orthogonal_orientation, unique_slice_positions, consistent_pixel_spacing, consistent_image_dimensions, uniform_slice_spacing.

Raw lines 303 to 337
338## RT Structure Sets
340`rtstruct_index` has one row per RTSTRUCT series. `ROINames`, `ROIGenerationAlgorithms`, and `RTROIInterpretedTypes` are string arrays
341(`VARCHAR[]` in DuckDB); use `list_contains` for membership.
343```python
344client.fetch_index("rtstruct_index")
346# RTSTRUCT series with ROI counts and names
347client.sql_query("""
348 SELECT i.collection_id, i.SeriesInstanceUID,
349 r.total_rois, r.ROINames, r.RTROIInterpretedTypes,
350 r.referenced_SeriesInstanceUID
351 FROM index i
352 JOIN rtstruct_index r ON i.SeriesInstanceUID = r.SeriesInstanceUID
353 LIMIT 10
354""")
356# Collections with the most RTSTRUCT series
357client.sql_query("""
358 SELECT i.collection_id,
359 COUNT(*) as rtstruct_series,
360 ROUND(AVG(r.total_rois), 1) as avg_rois
361 FROM index i
362 JOIN rtstruct_index r ON i.SeriesInstanceUID = r.SeriesInstanceUID
363 GROUP BY i.collection_id
364 ORDER BY rtstruct_series DESC
365 LIMIT 10
366""")
368# Find source CT series for a given RTSTRUCT
369client.sql_query("""
370 SELECT r.SeriesInstanceUID as rtstruct_uid,
371 r.total_rois, r.ROINames,
372 src.SeriesInstanceUID as source_ct_uid,
373 src.collection_id, src.BodyPartExamined
374 FROM rtstruct_index r
375 JOIN index src ON r.referenced_SeriesInstanceUID = src.SeriesInstanceUID
376 LIMIT 10
377""")
378```

RT Structure Sets

rtstruct_index has one row per RTSTRUCT series. ROINames, ROIGenerationAlgorithms, and RTROIInterpretedTypes are string arrays (VARCHAR[] in DuckDB); use list_contains for membership.

client.fetch_index("rtstruct_index")

# RTSTRUCT series with ROI counts and names
client.sql_query("""
    SELECT i.collection_id, i.SeriesInstanceUID,
           r.total_rois, r.ROINames, r.RTROIInterpretedTypes,
           r.referenced_SeriesInstanceUID
    FROM index i
    JOIN rtstruct_index r ON i.SeriesInstanceUID = r.SeriesInstanceUID
    LIMIT 10
""")

# Collections with the most RTSTRUCT series
client.sql_query("""
    SELECT i.collection_id,
           COUNT(*) as rtstruct_series,
           ROUND(AVG(r.total_rois), 1) as avg_rois
    FROM index i
    JOIN rtstruct_index r ON i.SeriesInstanceUID = r.SeriesInstanceUID
    GROUP BY i.collection_id
    ORDER BY rtstruct_series DESC
    LIMIT 10
""")

# Find source CT series for a given RTSTRUCT
client.sql_query("""
    SELECT r.SeriesInstanceUID as rtstruct_uid,
           r.total_rois, r.ROINames,
           src.SeriesInstanceUID as source_ct_uid,
           src.collection_id, src.BodyPartExamined
    FROM rtstruct_index r
    JOIN index src ON r.referenced_SeriesInstanceUID = src.SeriesInstanceUID
    LIMIT 10
""")
Raw lines 338 to 379
380## Modality Acquisition Parameters
382`ct_index`, `mr_index`, and `pt_index` (added in idc-index 0.12.3) expose acquisition and reconstruction parameters for CT, MR, and PET series. All join on `SeriesInstanceUID`. Dose-modulated CT acquisitions have `_min`/`_max` columns for tube current, exposure, and exposure time.
384```python
385client.fetch_index("ct_index")
386client.fetch_index("mr_index")
387client.fetch_index("pt_index")
389# CT: thin-slice series (≤2mm) with standard reconstruction
390client.sql_query("""
391 SELECT i.collection_id, i.SeriesInstanceUID, i.BodyPartExamined,
392 c.SliceThickness, c.ConvolutionKernel, c.KVP
393 FROM index i
394 JOIN ct_index c ON i.SeriesInstanceUID = c.SeriesInstanceUID
395 WHERE c.SliceThickness <= 2.0
396 AND c.ConvolutionKernel IS NOT NULL
397 LIMIT 10
398""")
400# CT: dose-modulated acquisitions (tube current varies across slices)
401client.sql_query("""
402 SELECT i.collection_id, c.SeriesInstanceUID,
403 c.XRayTubeCurrent_min, c.XRayTubeCurrent_max, c.SliceThickness
404 FROM ct_index c
405 JOIN index i ON c.SeriesInstanceUID = i.SeriesInstanceUID
406 WHERE c.XRayTubeCurrent_min != c.XRayTubeCurrent_max
407 LIMIT 10
408""")
410# MR: DWI series (have non-null DiffusionBValue) at 3T
411client.sql_query("""
412 SELECT i.collection_id, i.SeriesInstanceUID, i.SeriesDescription,
413 m.MagneticFieldStrength, m.DiffusionBValue
414 FROM index i
415 JOIN mr_index m ON i.SeriesInstanceUID = m.SeriesInstanceUID
416 WHERE m.DiffusionBValue IS NOT NULL
417 AND m.MagneticFieldStrength >= 2.9
418 LIMIT 10
419""")
421# MR: multi-echo series (EchoTime stored as array with multiple values)
422client.sql_query("""
423 SELECT i.collection_id, i.SeriesInstanceUID,
424 m.EchoTime, m.EchoTrainLength, m.ScanningSequence
425 FROM index i
426 JOIN mr_index m ON i.SeriesInstanceUID = m.SeriesInstanceUID
427 WHERE array_length(m.EchoTime) > 1
428 LIMIT 10
429""")
431# PET: discover FDG spellings in Radiopharmaceutical; radionuclide alone does not identify FDG
432client.sql_query("""
433 SELECT i.collection_id, i.SeriesInstanceUID,
434 p.RadionuclideCodeMeaning, p.Radiopharmaceutical, p.ReconstructionMethod,
435 p.Units, p.DecayCorrection
436 FROM index i
437 JOIN pt_index p ON i.SeriesInstanceUID = p.SeriesInstanceUID
438 WHERE LOWER(p.Radiopharmaceutical) LIKE '%fluorodeoxyglucose%'
439 OR LOWER(p.Radiopharmaceutical) LIKE '%fdg%'
440 LIMIT 10
441""")
443# PET: dynamic acquisitions (ActualFrameDuration is array with multiple values)
444client.sql_query("""
445 SELECT i.collection_id, i.SeriesInstanceUID,
446 p.NumberOfTimeSlices, p.ActualFrameDuration
447 FROM index i
448 JOIN pt_index p ON i.SeriesInstanceUID = p.SeriesInstanceUID
449 WHERE p.NumberOfTimeSlices > 1
450 LIMIT 10
451""")
452```
454`EchoTrainLength` is the echoes within an acquisition train, not the number of distinct
455`EchoTime` values. Fluorine-18 is a radionuclide used by multiple tracers; inspect
456`Radiopharmaceutical` values and source DICOM metadata before declaring a complete FDG cohort.
458Key columns by table (use `client.indices_overview["ct_index"]["schema"]` for the full list):
459- **ct_index**: `SliceThickness`, `KVP`, `ConvolutionKernel`, `SpiralPitchFactor`, `XRayTubeCurrent_min/max`, `Exposure_min/max`, `PixelSpacing_row_mm/col_mm`, `Rows`, `Columns`
460- **mr_index**: `MagneticFieldStrength`, `ScanningSequence`, `SequenceVariant`, `MRAcquisitionType`, `EchoTime` (array), `RepetitionTime`, `FlipAngle`, `DiffusionBValue` (array), `NumberOfTemporalPositions`, `ReceiveCoilName`
461- **pt_index**: `RadionuclideCodeMeaning`, `Radiopharmaceutical`, `RadionuclideTotalDose`, `ReconstructionMethod`, `DecayCorrection`, `AttenuationCorrectionMethod`, `ActualFrameDuration` (array), `NumberOfTimeSlices`

Modality Acquisition Parameters

ct_index, mr_index, and pt_index (added in idc-index 0.12.3) expose acquisition and reconstruction parameters for CT, MR, and PET series. All join on SeriesInstanceUID. Dose-modulated CT acquisitions have _min/_max columns for tube current, exposure, and exposure time.

client.fetch_index("ct_index")
client.fetch_index("mr_index")
client.fetch_index("pt_index")

# CT: thin-slice series (≤2mm) with standard reconstruction
client.sql_query("""
    SELECT i.collection_id, i.SeriesInstanceUID, i.BodyPartExamined,
           c.SliceThickness, c.ConvolutionKernel, c.KVP
    FROM index i
    JOIN ct_index c ON i.SeriesInstanceUID = c.SeriesInstanceUID
    WHERE c.SliceThickness <= 2.0
      AND c.ConvolutionKernel IS NOT NULL
    LIMIT 10
""")

# CT: dose-modulated acquisitions (tube current varies across slices)
client.sql_query("""
    SELECT i.collection_id, c.SeriesInstanceUID,
           c.XRayTubeCurrent_min, c.XRayTubeCurrent_max, c.SliceThickness
    FROM ct_index c
    JOIN index i ON c.SeriesInstanceUID = i.SeriesInstanceUID
    WHERE c.XRayTubeCurrent_min != c.XRayTubeCurrent_max
    LIMIT 10
""")

# MR: DWI series (have non-null DiffusionBValue) at 3T
client.sql_query("""
    SELECT i.collection_id, i.SeriesInstanceUID, i.SeriesDescription,
           m.MagneticFieldStrength, m.DiffusionBValue
    FROM index i
    JOIN mr_index m ON i.SeriesInstanceUID = m.SeriesInstanceUID
    WHERE m.DiffusionBValue IS NOT NULL
      AND m.MagneticFieldStrength >= 2.9
    LIMIT 10
""")

# MR: multi-echo series (EchoTime stored as array with multiple values)
client.sql_query("""
    SELECT i.collection_id, i.SeriesInstanceUID,
           m.EchoTime, m.EchoTrainLength, m.ScanningSequence
    FROM index i
    JOIN mr_index m ON i.SeriesInstanceUID = m.SeriesInstanceUID
    WHERE array_length(m.EchoTime) > 1
    LIMIT 10
""")

# PET: discover FDG spellings in Radiopharmaceutical; radionuclide alone does not identify FDG
client.sql_query("""
    SELECT i.collection_id, i.SeriesInstanceUID,
           p.RadionuclideCodeMeaning, p.Radiopharmaceutical, p.ReconstructionMethod,
           p.Units, p.DecayCorrection
    FROM index i
    JOIN pt_index p ON i.SeriesInstanceUID = p.SeriesInstanceUID
    WHERE LOWER(p.Radiopharmaceutical) LIKE '%fluorodeoxyglucose%'
       OR LOWER(p.Radiopharmaceutical) LIKE '%fdg%'
    LIMIT 10
""")

# PET: dynamic acquisitions (ActualFrameDuration is array with multiple values)
client.sql_query("""
    SELECT i.collection_id, i.SeriesInstanceUID,
           p.NumberOfTimeSlices, p.ActualFrameDuration
    FROM index i
    JOIN pt_index p ON i.SeriesInstanceUID = p.SeriesInstanceUID
    WHERE p.NumberOfTimeSlices > 1
    LIMIT 10
""")

EchoTrainLength is the echoes within an acquisition train, not the number of distinct EchoTime values. Fluorine-18 is a radionuclide used by multiple tracers; inspect Radiopharmaceutical values and source DICOM metadata before declaring a complete FDG cohort.

Key columns by table (use client.indices_overview["ct_index"]["schema"] for the full list):

  • ct_index: SliceThickness, KVP, ConvolutionKernel, SpiralPitchFactor, XRayTubeCurrent_min/max, Exposure_min/max, PixelSpacing_row_mm/col_mm, Rows, Columns
  • mr_index: MagneticFieldStrength, ScanningSequence, SequenceVariant, MRAcquisitionType, EchoTime (array), RepetitionTime, FlipAngle, DiffusionBValue (array), NumberOfTemporalPositions, ReceiveCoilName
  • pt_index: RadionuclideCodeMeaning, Radiopharmaceutical, RadionuclideTotalDose, ReconstructionMethod, DecayCorrection, AttenuationCorrectionMethod, ActualFrameDuration (array), NumberOfTimeSlices
Raw lines 380 to 462
463## Resources
465- `references/index_tables_guide.md` for table schemas, DataFrame access, and join column references
466- `references/clinical_data_guide.md` for clinical data patterns and value mapping
467- `references/digital_pathology_guide.md` for pathology-specific queries
468- `references/bigquery_guide.md` for advanced queries requiring full DICOM metadata
469- `references/parquet_access_guide.md` for direct Parquet queries without installing idc-index

Resources

  • references/index_tables_guide.md for table schemas, DataFrame access, and join column references
  • references/clinical_data_guide.md for clinical data patterns and value mapping
  • references/digital_pathology_guide.md for pathology-specific queries
  • references/bigquery_guide.md for advanced queries requiring full DICOM metadata
  • references/parquet_access_guide.md for direct Parquet queries without installing idc-index
Raw lines 463 to 469