BrainAge-3D-Viewer / pattern_detect.py
bilalahmad176176's picture
Upload files 451–480 of 480
4705bea verified
Raw
History Blame Contribute Delete
6.61 kB
"""Rule-based pattern detection / differential-diagnosis hints.
Looks at the pattern of regional z-scores (and asymmetry) and flags
clinically recognized signatures. This is NOT a diagnosis engine — it
surfaces pattern hints that a radiologist should consider.
Each rule returns:
{ "pattern": short name,
"confidence": low | moderate | high,
"ddx_consider": list of possible diagnoses to rule in/out,
"supporting_findings": list of (region, z) that triggered the rule }
Patterns encoded (initial set — extensible):
- Medial temporal atrophy (AD, TLE)
- Ventriculomegaly + cortex OK (normal-pressure hydrocephalus / hydro)
- Global GM atrophy, WM OK (primary GM degeneration)
- Bilateral frontal reduction (frontotemporal pattern)
- Asymmetric focal pattern (tumor / stroke / focal pathology)
- Accelerated WM myelination (pediatric developmental anomaly)
- Posterior atrophy dominant (posterior cortical atrophy)
"""
from __future__ import annotations
from typing import Iterable
def _find(regions: Iterable[dict], name_frag: str,
min_abs_z: float = 0.0,
direction: str | None = None) -> list[dict]:
out = []
for r in regions:
if name_frag.lower() not in r.get("name", "").lower():
continue
z = r.get("z_score")
if z is None:
continue
if abs(z) < min_abs_z:
continue
if direction == "low" and z >= 0:
continue
if direction == "high" and z <= 0:
continue
out.append(r)
return out
def detect_patterns(regions: list[dict], asymmetry: list[dict],
tissue: dict) -> list[dict]:
hits = []
# ---- 1. Medial temporal atrophy (AD, TLE) ---------------------------
hipp = _find(regions, "hippocamp", min_abs_z=1.5, direction="low")
phipp = _find(regions, "parahippocamp", min_abs_z=1.0, direction="low")
if len(hipp) >= 1 and len(phipp) >= 1:
bilateral = any(r["hemi"] == "L" for r in hipp) and \
any(r["hemi"] == "R" for r in hipp)
conf = "high" if bilateral else "moderate"
hits.append({
"pattern": "Medial temporal atrophy",
"confidence": conf,
"ddx_consider": ["Alzheimer's disease (early)",
"Temporal lobe epilepsy (mesial sclerosis)",
"Limbic encephalitis"],
"supporting_findings": [f"{r['name']} z={r['z_score']:+.1f}"
for r in hipp + phipp],
})
# ---- 2. Ventriculomegaly with preserved cortex ---------------------
vent = _find(regions, "lateral ventricle", min_abs_z=1.5, direction="high")
cort = _find(regions, "cerebral cortex", direction="low")
if vent and (not cort or max(abs(r["z_score"]) for r in cort) < 1.0):
hits.append({
"pattern": "Ventriculomegaly with cortical preservation",
"confidence": "moderate",
"ddx_consider": ["Normal pressure hydrocephalus",
"Obstructive hydrocephalus",
"Ex-vacuo (if secondary to WM loss)"],
"supporting_findings": [f"{r['name']} z={r['z_score']:+.1f}"
for r in vent],
})
# ---- 3. Global GM atrophy with WM preserved ------------------------
gm_lo = [r for r in regions
if r.get("group") == "Cortical"
and r.get("z_score") is not None
and r["z_score"] < -1.0]
wm = _find(regions, "cerebral white matter")
wm_ok = all(abs(r.get("z_score", 0)) < 1.0 for r in wm) if wm else True
if len(gm_lo) >= 10 and wm_ok:
hits.append({
"pattern": "Diffuse grey matter volume loss (white matter preserved)",
"confidence": "moderate",
"ddx_consider": ["Primary GM degeneration",
"Early neurodegenerative process"],
"supporting_findings": [f"{len(gm_lo)} cortical regions with z<-1"],
})
# ---- 4. Bilateral frontal reduction --------------------------------
front_lo = [r for r in regions
if "frontal" in r.get("name", "").lower()
and r.get("z_score") is not None
and r["z_score"] < -1.5]
if len(front_lo) >= 3:
hits.append({
"pattern": "Frontal-predominant volume reduction",
"confidence": "moderate" if len(front_lo) >= 5 else "low",
"ddx_consider": ["Frontotemporal dementia (behavioral variant)",
"Chronic traumatic encephalopathy",
"Prefrontal developmental delay (pediatric)"],
"supporting_findings": [f"{r['name']} z={r['z_score']:+.1f}"
for r in front_lo[:5]],
})
# ---- 5. Asymmetric focal pattern -----------------------------------
sig_asym = [a for a in asymmetry if a.get("significant") and
abs(a.get("asymmetry_pct", 0)) >= 10.0]
if len(sig_asym) >= 2:
hits.append({
"pattern": "Asymmetric focal volume pattern",
"confidence": "low" if len(sig_asym) < 4 else "moderate",
"ddx_consider": ["Focal mass lesion (tumor)",
"Chronic infarct",
"Focal cortical dysplasia (if pediatric)",
"Mesial temporal sclerosis (if medial temporal)"],
"supporting_findings": [
f"{a['region']} AI {a['asymmetry_pct']:+.1f}%"
for a in sig_asym[:5]],
})
# ---- 6. Posterior-dominant atrophy ---------------------------------
occ = [r for r in regions if r.get("lobe") == "Occipital"
and (r.get("z_score") or 0) < -1.0]
par = [r for r in regions if r.get("lobe") == "Parietal"
and (r.get("z_score") or 0) < -1.0]
front_ok = [r for r in regions if r.get("lobe") == "Frontal"
and abs(r.get("z_score") or 0) < 1.0]
if (len(occ) + len(par)) >= 4 and len(front_ok) >= 5:
hits.append({
"pattern": "Posterior cortical volume loss",
"confidence": "moderate",
"ddx_consider": ["Posterior cortical atrophy (PCA)",
"Lewy body disease variant",
"Late-stage Alzheimer's"],
"supporting_findings": [f"occipital regions: {len(occ)}, "
f"parietal: {len(par)} with z<-1"],
})
return hits