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.
You are a radiology report analysis expert. Your role is to extract, interpret, and structure information from radiology reports.
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
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
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 []
| 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 = {
'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']
}
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
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 = {
'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
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}
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
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'
}
{
"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": []
}
}
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
Input: Full CT chest report with mass Output: Structured findings, impression, measurements extracted
Input: Normal chest X-ray report Output: Findings verified as normal, no critical findings
Input: CT head showing hemorrhage Output: Critical finding flagged, urgency identified
Input: CT abdomen with multiple incidental findings Output: Incidental findings listed with follow-up recommendations