Radiology report analysis
Skill aizech/clinical-skills/.codebuddy/skills/radiology-report-analysis
A collection of AI agent skills focused on medical imaging and healthcare workflows. Built for radiologists, healthcare IT professionals, and researchers who want AI coding agents to help with imaging workflows, clinical documentation, AI integration, and medical research. Works with Claude Code, Codex, Cursor, Windsurf, and many other agents.
npx -y skills add aizech/clinical-skills --skill radiology-report-analysisAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Analyze structured/free-text radiology reports, extract key findings, measurements, and impressions. Also use when the user provides a report for review, summary, data extraction, critical findings identification, or report quality assessment. For structured reporting templates, see structured-reporting.
SKILL.md
11.0 KB, as published. Nobody here has run it
Radiology Report Analysis
You are a radiology report analysis expert. Your role is to extract, interpret, and structure information from radiology reports.
Report Structure
Standard Report Sections
RADIOLOGY REPORT
├── Header Information
│ ├── Patient ID
│ ├── Study Date
│ ├── Modality
│ ├── Referring Physician
│ └── Accession Number
├── Clinical History
├── Examination/Study Description
├── Findings
│ ├── Organ System 1
│ ├── Organ System 2
│ └── ...
└── Impression
├── Primary Finding (numbered)
├── Secondary Finding
└── Recommendations
Extraction Patterns
Findings Extraction
Extract findings from free-text reports:
def extract_findings(report_text):
sections = parse_report_sections(report_text)
findings = []
# Pattern: Finding descriptions often start with bullets, numbers, or organ names
finding_patterns = [
r'[-•]\s*(.+)', # Bullet points
r'\d+\.\s+([A-Z][^:]+):\s*(.+)', # Numbered with colon
r'([A-Z][a-z]+(?:\s+[a-z]+)?):\s*(.+)', # Organ: description
]
for pattern in finding_patterns:
matches = re.finditer(pattern, report_text)
for match in matches:
findings.append({
'organ': extract_organ(match),
'description': match.group(1) if match.lastindex else match.group(0),
'severity': classify_severity(match)
})
return findings
Impression Extraction
def extract_impression(report_text):
# Look for IMPRESSION section
impression_pattern = r'IMPRESSION[:\s]+(.+?)(?:\n\n|\Z)'
match = re.search(impression_pattern, report_text, re.DOTALL | re.IGNORECASE)
if match:
impression_text = match.group(1)
# Parse numbered impressions
impressions = re.findall(r'\d+\.\s*(.+?)(?=\n\d+\.|\Z)', impression_text)
return impressions
# Fallback: last paragraph is often impression
paragraphs = report_text.split('\n\n')
return [paragraphs[-1]] if paragraphs else []
Finding Classification
Severity Levels
| Level | Description | Action |
|---|---|---|
| Critical | Life-threatening, immediate action | STAT communication |
| Urgent | Significant, timely action needed | Within hours |
| Routine | Non-urgent, follow-up as appropriate | Standard scheduling |
| Normal | No significant abnormality | None |
| Incidental | Unexpected but not clinically significant | Document, consider follow-up |
Finding Categories
FINDING_CATEGORIES = {
'mass': ['mass', 'lesion', 'nodule', 'tumor', 'growth'],
'inflammation': ['inflammation', 'edema', 'swelling'],
'fluid': ['effusion', 'ascites', 'hemorrhage', 'bleeding'],
'calcification': ['calcification', 'stone', 'calculus'],
'fracture': ['fracture', 'break', ' discontinuity'],
'occlusion': ['occlusion', 'stenosis', 'blockage', 'embolism'],
'infection': ['infection', 'abscess', 'pneumonia'],
'deformity': ['deformity', 'dislocation', 'subluxation']
}
Measurement Extraction
Extract measurements and dimensions:
def extract_measurements(text):
measurements = []
# Pattern: Number + unit combinations
measurement_pattern = r'(\d+\.?\d*)\s*(cm|mm|mm|mL|mg|%|°|bpm)'
matches = re.finditer(measurement_pattern, text, re.IGNORECASE)
for match in matches:
measurements.append({
'value': float(match.group(1)),
'unit': match.group(2).lower(),
'context': extract_context_around(text, match.start(), 50)
})
return measurements
Anatomy Extraction
def extract_anatomy(text):
anatomy_patterns = {
'brain': r'\b(brain|cerebral|intracranial|frontal|parietal|temporal|occipital|cerebellar)\b',
'lung': r'\b(lung|pulmonary|pleural|mediastinal|hilar|bronchial)\b',
'liver': r'\b(liver|hepatic|hepatobiliary)\b',
'kidney': r'\b(kidney|renal|adrenal)\b',
'spine': r'\b(spine|vertebral|disc|spinal|cord)\b',
'heart': r'\b(heart|cardiac|pericardial|aortic|valvular)\b',
'abdomen': r'\b(abdomen|abdominal|bowel|intestinal|mesenteric|peritoneal)\b'
}
findings = {}
for organ, pattern in anatomy_patterns.items():
if re.search(pattern, text, re.IGNORECASE):
findings[organ] = True
return list(findings.keys())
Critical Findings Detection
CRITICAL_FINDINGS = {
'pneumothorax': {'severity': 'critical', 'urgency': 'STAT'},
'tension pneumothorax': {'severity': 'critical', 'urgency': 'STAT'},
'large pleural effusion': {'severity': 'urgent', 'urgency': 'within_hours'},
'pulmonary embolism': {'severity': 'critical', 'urgency': 'STAT'},
'aortic dissection': {'severity': 'critical', 'urgency': 'STAT'},
'aortic aneurysm rupture': {'severity': 'critical', 'urgency': 'STAT'},
'bowel obstruction': {'severity': 'urgent', 'urgency': 'within_hours'},
'bowel perforation': {'severity': 'critical', 'urgency': 'STAT'},
'intracranial hemorrhage': {'severity': 'critical', 'urgency': 'STAT'},
'stroke': {'severity': 'critical', 'urgency': 'STAT'},
'brain herniation': {'severity': 'critical', 'urgency': 'STAT'},
'fracture': {'severity': 'routine', 'urgency': 'standard'},
'tumor': {'severity': 'routine', 'urgency': 'standard'},
'metastasis': {'severity': 'urgent', 'urgency': 'within_days'}
}
def detect_critical_findings(text):
text_lower = text.lower()
critical = []
for finding, info in CRITICAL_FINDINGS.items():
if finding in text_lower:
critical.append({
'finding': finding,
'severity': info['severity'],
'urgency': info['urgency']
})
return critical
Comparison Detection
Detect comparison with prior studies:
def detect_comparison(text):
comparison_indicators = [
'compared to', 'comparison with', 'compared with',
'prior study', 'previous', 'old study',
'stable', 'unchanged', 'improved', 'worsened',
'new', 'interval change', 'developed'
]
text_lower = text.lower()
has_comparison = any(indicator in text_lower for indicator in comparison_indicators)
if has_comparison:
return {
'has_comparison': True,
'new_findings': extract_new_findings(text),
'stable_findings': extract_stable_findings(text),
'changed_findings': extract_changed_findings(text)
}
return {'has_comparison': False}
Incidental Findings
Detect incidental findings requiring follow-up:
INCIDENTAL_FINDINGS = {
'renal cyst': {'followup': 'usually none for simple cysts <3cm'},
'gallbladder polyps': {'followup': 'ultrasound if >5mm or high risk'},
'thyroid nodules': {'followup': 'ultrasound if >1cm or suspicious features'},
'adrenal nodule': {'followup': 'CT or MRI for characterization if >1cm'},
'lung nodule': {'followup': 'depends on size and risk factors'},
'liver hemangioma': {'followup': 'usually none for classic appearance'}
}
def detect_incidental_findings(text):
incidentals = []
text_lower = text.lower()
for finding, info in INCIDENTAL_FINDINGS.items():
if finding in text_lower:
incidentals.append({
'finding': finding,
'followup_recommendation': info['followup']
})
return incidentals
Report Quality Assessment
def assess_report_quality(report):
issues = []
# Check for required sections
if 'IMPRESSION' not in report.upper():
issues.append('Missing impression section')
# Check impression length
impression = extract_impression(report)
if len(' '.join(impression)) < 10:
issues.append('Impression too brief')
# Check for specificity
if 'normal' in report.lower() and len(report) < 200:
issues.append('Normal report may lack sufficient detail')
# Check for comparison when expected
if 'follow-up' in report.lower() or 'f/u' in report.lower():
if not detect_comparison(report)['has_comparison']:
issues.append('Follow-up requested without prior comparison')
return {
'quality_score': max(0, 100 - len(issues) * 20),
'issues': issues,
'recommendation': 'Acceptable' if len(issues) <= 2 else 'Needs revision'
}
Output Formats
Structured JSON Output
{
"report_type": "CT Chest",
"accession_number": "ACC123456",
"study_date": "2026-04-03",
"findings": [
{
"organ_system": "lung",
"finding": "2.5 cm mass in right upper lobe",
"measurements": {"size": "2.5 cm"},
"location": "right upper lobe",
"severity": "routine",
"critical": false
},
{
"organ_system": "mediastinum",
"finding": "No mediastinal lymphadenopathy",
"severity": "normal",
"critical": false
}
],
"impression": [
"Lung mass, concerning for malignancy"
],
"critical_findings": [],
"incidental_findings": [],
"comparison": null,
"followup_recommended": true,
"recommendations": [
"CT-guided biopsy of lung mass",
"PET/CT for staging"
],
"quality_assessment": {
"score": 100,
"issues": []
}
}
Summary Format
ANALYSIS SUMMARY
================
Study: CT Chest with Contrast
Date: 2026-04-03
KEY FINDINGS:
• Lung: 2.5 cm mass, right upper lobe
• No lymphadenopathy
• Small pleural effusion (right)
IMPRESSION:
Lung mass, concerning for malignancy
RECOMMENDATIONS:
• CT-guided biopsy
• PET/CT for staging
Critical Findings: None
Follow-up Needed: Yes
Quality: Acceptable
Related Skills
- structured-reporting: For structured report templates
- impression-generation: For AI-assisted impression writing
- findings-extraction: For detailed data extraction
- patient-results-letter: For patient-friendly communication
- followup-tracking: For managing incidental findings
Examples
Example 1: Lung Mass Analysis
Input: Full CT chest report with mass Output: Structured findings, impression, measurements extracted
Example 2: Normal Study
Input: Normal chest X-ray report Output: Findings verified as normal, no critical findings
Example 3: Critical Finding
Input: CT head showing hemorrhage Output: Critical finding flagged, urgency identified
Example 4: Incidental Findings
Input: CT abdomen with multiple incidental findings Output: Incidental findings listed with follow-up recommendations