Problem
In Germany (and many other countries), MRI results are almost always delivered on CD/DVD. These discs follow the DICOM standard structure:
CD-ROM (D:\)
├── DICOMDIR ← Standard index file (lists all studies/series/images)
├── DICOM/
│ ├── IM000001.dcm
│ ├── IM000002.dcm
│ └── ...
└── viewer.exe ← Bundled Windows-only viewer (usually useless)
Currently, LocalProvider scans directories recursively for DICOM files, which works but:
- Misses the DICOMDIR - a structured index that gives instant access to the patient/study/series hierarchy
- Is slow on CDs - optical drives are slow; scanning hundreds of files takes minutes
- Doesn't detect CD drives - users have to manually find the mount path
Proposed Solution
Enhance LocalProvider (not a new provider) to auto-detect and parse DICOMDIR files.
Implementation Plan
1. Add DICOMDIR detection to LocalProvider._load_directory()
# src/medcheck/providers/local.py
def _load_directory(self, root: Path) -> list[DicomSeries]:
# Check for DICOMDIR first (standard on CDs)
dicomdir_path = root / "DICOMDIR"
if dicomdir_path.exists():
return self._load_from_dicomdir(dicomdir_path)
# Fallback: recursive scan (existing behavior)
return self._scan_directory(root)
2. Implement DICOMDIR parsing
def _load_from_dicomdir(self, dicomdir_path: Path) -> list[DicomSeries]:
"""Load DICOM data using DICOMDIR index (standard on CDs)."""
from rich.console import Console
console = Console()
console.print("[blue]DICOMDIR found - using structured index[/blue]")
ds = pydicom.dcmread(str(dicomdir_path))
root = dicomdir_path.parent
series_map: dict[str, list[Any]] = {}
series_meta: dict[str, dict[str, Any]] = {}
for patient_record in ds.patient_records:
for study_record in patient_record.children:
for series_record in study_record.children:
series_desc = getattr(series_record, "SeriesDescription",
f"series_{getattr(series_record, 'SeriesNumber', 0)}")
series_num = getattr(series_record, "SeriesNumber", 0)
for image_record in series_record.children:
# DICOMDIR stores relative file paths
file_parts = image_record.ReferencedFileID
if isinstance(file_parts, str):
file_parts = [file_parts]
dcm_path = root / Path(*file_parts)
if dcm_path.exists():
try:
dcm = pydicom.dcmread(str(dcm_path), force=True)
if hasattr(dcm, "PixelData"):
if series_desc not in series_map:
series_map[series_desc] = []
series_meta[series_desc] = {
"series_number": series_num,
"modality": getattr(series_record, "Modality", ""),
}
series_map[series_desc].append(dcm)
except Exception:
continue
result: list[DicomSeries] = []
for desc, datasets in series_map.items():
meta = series_meta.get(desc, {})
result.append(DicomSeries(
description=desc,
series_number=meta.get("series_number", 0),
modality=meta.get("modality", ""),
slices=datasets,
metadata=meta,
))
console.print(f"[green]Loaded {len(result)} series from DICOMDIR[/green]")
return sorted(result, key=lambda s: s.series_number)
3. Rename existing scan to _scan_directory()
def _scan_directory(self, root: Path) -> list[DicomSeries]:
"""Fallback: recursively scan directory for DICOM files."""
# ... existing _load_directory code ...
4. Add tests
# tests/unit/test_providers/test_local_dicomdir.py
def test_load_from_dicomdir(tmp_path: Path):
"""Test that DICOMDIR is auto-detected and parsed."""
# Create a minimal DICOMDIR structure
dicom_dir = tmp_path / "DICOM"
dicom_dir.mkdir()
# Create test DICOM files
_create_test_dicom(dicom_dir / "IM000001.dcm", "pd_sag", 1)
_create_test_dicom(dicom_dir / "IM000002.dcm", "pd_sag", 1)
# Create DICOMDIR (pydicom can generate these)
# ... or use a fixture file
provider = LocalProvider()
result = provider.fetch(str(tmp_path), {})
assert len(result) >= 1
def test_fallback_without_dicomdir(tmp_path: Path):
"""Test that directories without DICOMDIR still work (existing behavior)."""
_create_test_dicom(tmp_path / "slice.dcm", "test", 1)
provider = LocalProvider()
result = provider.fetch(str(tmp_path), {})
assert len(result) >= 1
def test_cd_drive_detection():
"""Test CD/DVD drive path detection (platform-specific)."""
# Windows: D:\, E:\
# Linux: /media/cdrom, /mnt/cdrom
# This test verifies the detection logic, not actual hardware
pass
Files to modify
| File |
Change |
src/medcheck/providers/local.py |
Add _load_from_dicomdir(), refactor _load_directory() |
tests/unit/test_providers/test_local.py |
Add DICOMDIR tests |
docs/quickstart.md |
Add "From CD" usage example |
README.md |
Update data sources table |
CLI usage after implementation
# Point directly at CD drive
medcheck analyze --source D:\
# Or mount point on Linux
medcheck analyze --source /media/cdrom
# Also works with copied CD contents
medcheck analyze --source ./copied-from-cd/
Nice-to-have (follow-up issues)
Context
This is a high-value feature for the German/European market where CD delivery is standard practice. Many patients receive CDs but have no way to analyze them beyond the bundled Windows viewer.
References
Problem
In Germany (and many other countries), MRI results are almost always delivered on CD/DVD. These discs follow the DICOM standard structure:
Currently,
LocalProviderscans directories recursively for DICOM files, which works but:Proposed Solution
Enhance
LocalProvider(not a new provider) to auto-detect and parse DICOMDIR files.Implementation Plan
1. Add DICOMDIR detection to
LocalProvider._load_directory()2. Implement DICOMDIR parsing
3. Rename existing scan to
_scan_directory()4. Add tests
Files to modify
src/medcheck/providers/local.py_load_from_dicomdir(), refactor_load_directory()tests/unit/test_providers/test_local.pydocs/quickstart.mdREADME.mdCLI usage after implementation
Nice-to-have (follow-up issues)
--copy-local ./cache/)Context
This is a high-value feature for the German/European market where CD delivery is standard practice. Many patients receive CDs but have no way to analyze them beyond the bundled Windows viewer.
References
pydicom.filereader.dcmread()natively supports DICOMDIR files