Skip to content

Instantly share code, notes, and snippets.

@dmd
Created November 7, 2025 16:41
Show Gist options
  • Select an option

  • Save dmd/522f8175e27259d387d9e86fd0045ec7 to your computer and use it in GitHub Desktop.

Select an option

Save dmd/522f8175e27259d387d9e86fd0045ec7 to your computer and use it in GitHub Desktop.
---
name: micvna-orthanc
description: Use when querying micvna (Orthanc DICOM server) for medical imaging data, looking up studies by MRN, accession numbers, or study dates. Essential for DICOM data workflows.
---
# micvna (Orthanc) DICOM Server Skill
## When to Use This Skill
Use this skill when:
- Querying DICOM data from micvna
- Looking up studies by MRN (Patient ID)
- Finding accession numbers for specific study dates
- Retrieving DICOM metadata (study descriptions, dates, etc.)
- Creating scripts that query Orthanc
## Server Configuration
```python
server = "http://micvna.mclean.harvard.edu:8042"
# Authentication is handled automatically via ~/.netrc
```
**API Documentation:** https://orthanc.uclouvain.be/book/users/rest.html
**Authentication:** Credentials are automatically read from `~/.netrc` file by the requests library. No explicit auth parameter needed in the code.
## Key Endpoints
- `POST /tools/find` - Query studies/patients/series/instances
- `GET /studies/{id}` - Get study details
- `GET /studies/{id}/tags?simplify` - Get simplified DICOM tags
- `GET /patients/{id}` - Get patient details
- `GET /changes` - Monitor new instances (for real-time monitoring)
## Common Patterns
### 1. Query Studies by MRN (Patient ID)
```python
def search_patient(patient_id):
"""Search for studies by patient ID (MRN) in Orthanc."""
query = {
"Level": "Study",
"Query": {
"PatientID": patient_id
},
"Expand": True
}
r = requests.post(f"{server}/tools/find", json=query)
r.raise_for_status()
return r.json()
```
**Returns:** List of studies for that patient
### 2. Get Study Details
```python
def get_study_info(study_id):
"""Get detailed study information including accession number and date."""
r = requests.get(f"{server}/studies/{study_id}")
r.raise_for_status()
study = r.json()
# Get main tags
main_tags = study.get("MainDicomTags", {})
# Get detailed tags if needed
tags_response = requests.get(f"{server}/studies/{study_id}/tags?simplify")
if tags_response.ok:
all_tags = tags_response.json()
else:
all_tags = {}
return {
"date": main_tags.get("StudyDate", all_tags.get("StudyDate", "")),
"accession_number": main_tags.get("AccessionNumber", all_tags.get("AccessionNumber", "")),
"study_description": main_tags.get("StudyDescription", all_tags.get("StudyDescription", ""))
}
```
### 3. Find Accession Number by MRN and Date
```python
def find_accession_for_date(mrn, target_date_yyyymmdd):
"""Find accession number for specific MRN and study date."""
studies = search_patient(mrn)
for study in studies:
study_id = study.get("ID")
if not study_id:
continue
info = get_study_info(study_id)
if info.get("date") == target_date_yyyymmdd:
return info.get("accession_number", "")
return "" # Not found
```
## Date Handling
**IMPORTANT:** DICOM StudyDate is in YYYYMMDD format (e.g., "20160116")
### Converting from User Input
```python
from datetime import datetime
def parse_date(date_str):
"""Convert MM/DD/YYYY to YYYYMMDD for DICOM comparison."""
dt = datetime.strptime(date_str.strip(), "%m/%d/%Y")
return dt.strftime("%Y%m%d")
# Example
user_date = "01/16/2016"
dicom_date = parse_date(user_date) # "20160116"
```
## DICOM Tag Reference
Common tags returned by Orthanc:
### MainDicomTags (Study Level)
- `StudyDate` - Date of study (YYYYMMDD)
- `StudyTime` - Time of study (HHMMSS)
- `AccessionNumber` - Unique identifier for the study
- `StudyDescription` - Description of study
- `StudyInstanceUID` - DICOM unique identifier
### PatientMainDicomTags
- `PatientID` - MRN/Patient identifier
- `PatientName` - Patient's name
- `PatientBirthDate` - Birth date (YYYYMMDD)
## Error Handling
### Missing Accession Numbers
Not all studies have accession numbers:
```python
accession = info.get("accession_number", "")
if not accession:
print(f"Warning: No accession number for study {study_id}")
```
### Failed Queries
```python
try:
studies = search_patient(mrn)
except requests.exceptions.RequestException as e:
print(f"Error querying patient {mrn}: {e}")
studies = []
```
## Common Workflows
### Workflow 1: Get All Studies for Multiple MRNs
```python
mrns = ["219306", "220464", "206655"]
for mrn in mrns:
print(f"Searching MRN: {mrn}")
studies = search_patient(mrn)
print(f" Found {len(studies)} studies")
for study in studies:
info = get_study_info(study["ID"])
print(f" Date: {info['date']}, Accession: {info['accession_number']}")
```
### Workflow 2: Find Specific Studies by Date
```python
# User provides dates in MM/DD/YYYY format
target_dates = {
"219306": ["01/16/2016", "04/09/2016"],
"220464": ["02/02/2016", "04/30/2016"]
}
for mrn, dates in target_dates.items():
studies = search_patient(mrn)
for user_date in dates:
dicom_date = parse_date(user_date)
for study in studies:
info = get_study_info(study["ID"])
if info["date"] == dicom_date:
print(f"Match: MRN {mrn}, Date {user_date}, Accession {info['accession_number']}")
```
### Workflow 3: Process Data from File with Progress Reporting
```python
import csv
from pathlib import Path
# Read input file with MRNs and dates
with open("input.txt", 'r') as f:
reader = csv.DictReader(f, delimiter='\t')
rows = list(reader)
print(f"Processing {len(rows)} patients...\n", file=sys.stderr)
results = []
for i, row in enumerate(rows, 1):
mrn = row.get('MRN', '')
date = row.get('Date', '')
print(f"{i}/{len(rows)}: Processing MRN {mrn}", file=sys.stderr)
if not mrn or not date:
continue
# Convert date format
dicom_date = parse_date(date)
# Search for study
print(f" Searching for date {date} ({dicom_date})...", file=sys.stderr)
studies = search_patient(mrn)
print(f" Found {len(studies)} studies", file=sys.stderr)
# Find matching study
accession = ""
for study in studies:
info = get_study_info(study["ID"])
if info["date"] == dicom_date:
accession = info["accession_number"]
print(f" ✓ Match found: {accession}", file=sys.stderr)
break
if not accession:
print(f" ✗ No matching study found", file=sys.stderr)
results.append({
"mrn": mrn,
"date": date,
"accession": accession
})
# Write output
with open("output.txt", 'w') as f:
writer = csv.DictWriter(f, fieldnames=["mrn", "date", "accession"], delimiter='\t')
writer.writeheader()
writer.writerows(results)
print(f"\n✓ Output written to output.txt", file=sys.stderr)
```
## Script Dependencies
Use uv with inline script metadata:
```python
#!/usr/bin/env python3
# /// script
# dependencies = [
# "requests",
# ]
# ///
import requests
import sys
import csv
from pathlib import Path
from datetime import datetime
```
## Best Practices
### ✅ DO: Use the simplified tags endpoint
`/studies/{id}/tags?simplify` is easier to work with than raw DICOM tags
### ✅ DO: Handle missing data gracefully
Not all DICOM fields are guaranteed to be present
### ✅ DO: Print progress for batch operations
```python
print(f"Searching for MRN {mrn}...", file=sys.stderr)
print(f" Found {len(studies)} studies", file=sys.stderr)
```
### ✅ DO: Query by PatientID, then filter results
More reliable than trying to query by multiple fields
### ✅ DO: Check both MainDicomTags and detailed tags
Some information may only be in one or the other
### ❌ DON'T: Assume all studies have accession numbers
Always check if the field exists before using it
### ❌ DON'T: Compare dates as strings without formatting
Convert user dates to YYYYMMDD format first
### ❌ DON'T: Make excessive requests
Cache study information when processing multiple MRNs
## Real-Time Monitoring
For monitoring new DICOM instances as they arrive:
```python
# Reset changes feed to start from now
requests.delete(f"{server}/changes")
since = 0
while True:
sleep(1)
result = requests.get(f"{server}/changes", params={"since": since}).json()
since = result["Last"]
new_instances = [
x["Path"] for x in result["Changes"]
if x["ChangeType"] == "NewInstance"
]
for instance in new_instances:
try:
tags = requests.get(f"{server}{instance}/tags?simplify").json()
study_desc = tags.get("StudyDescription", "Unknown")
print(f"{datetime.now()}: New study - {study_desc}")
except KeyError:
print(f"{datetime.now()}: New study - no description")
```
## Summary
**micvna is an Orthanc DICOM server that:**
- Stores medical imaging studies
- Allows queries by Patient ID (MRN)
- Returns DICOM metadata including dates and accession numbers
- Uses YYYYMMDD date format
- Requires no authentication for queries
- Provides both simple and detailed tag access
- Supports real-time monitoring of new studies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment