Skip to content

Commit 226ca15

Browse files
m9hclaude
andcommitted
Add pseudo-CT validation tests using TFUScapes CT data
Validates the HU → acoustic property and HU → conductivity chains against real pseudo-CT data from TFUScapes: - CT-derived acoustic properties in physical range - CT tissue labels match high-HU skull regions - Archie's Law: dense skull has lower conductivity than brain 3/3 tests GREEN. SynthRAD2023 requires Grand Challenge registration; BabelBrain 270GB requires access request. TFUScapes provides immediate validation with the CT field already downloaded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1ab6de9 commit 226ca15

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

tests/test_pseudo_ct_tfuscapes.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""TDD: Validate HU → acoustic properties using TFUScapes CT data."""
2+
import numpy as np
3+
import pytest
4+
from pathlib import Path
5+
6+
SAMPLE = Path(__file__).parent.parent / "benchmarks" / "tfuscapes_data" / "sample_0.npz"
7+
8+
9+
@pytest.mark.skipif(not SAMPLE.exists(), reason="TFUScapes sample not downloaded")
10+
def test_ct_to_acoustic_properties_physical():
11+
"""Acoustic properties derived from TFUScapes CT should be physical."""
12+
from benchmarks.pseudo_ct_validation import hu_to_acoustic_properties
13+
14+
d = np.load(SAMPLE)
15+
ct = d["ct"]
16+
props = hu_to_acoustic_properties(ct)
17+
18+
# Sound speed in physical range
19+
assert props["sound_speed"].min() >= 1400
20+
assert props["sound_speed"].max() <= 4500
21+
22+
# Density: water to bone
23+
assert props["density"].min() >= 1000
24+
assert props["density"].max() <= 3000
25+
26+
# Skull regions (HU > 700) should have higher c than brain
27+
skull = ct > 700
28+
brain = (ct > 50) & (ct < 200)
29+
if skull.any() and brain.any():
30+
c_skull = props["sound_speed"][skull].mean()
31+
c_brain = props["sound_speed"][brain].mean()
32+
assert c_skull > c_brain, f"Skull c={c_skull:.0f} not > brain c={c_brain:.0f}"
33+
34+
35+
@pytest.mark.skipif(not SAMPLE.exists(), reason="TFUScapes sample not downloaded")
36+
def test_ct_labels_match_acoustic_segmentation():
37+
"""CT-derived labels should match our tissue segmentation thresholds."""
38+
from benchmarks.tfuscapes_compare import ct_to_labels
39+
40+
d = np.load(SAMPLE)
41+
labels = ct_to_labels(d["ct"])
42+
43+
unique = np.unique(labels)
44+
# Should have at least water, skull, and some brain tissue
45+
assert 0 in unique # water/air
46+
assert 2 in unique # skull
47+
48+
# Skull voxels should correspond to high HU regions
49+
skull_mask = labels == 2
50+
skull_hu = d["ct"][skull_mask]
51+
assert skull_hu.mean() > 700, f"Skull HU mean too low: {skull_hu.mean():.0f}"
52+
53+
54+
@pytest.mark.skipif(not SAMPLE.exists(), reason="TFUScapes sample not downloaded")
55+
def test_archies_law_conductivity_from_ct():
56+
"""Archie's Law should produce physical conductivity from CT porosity."""
57+
from benchmarks.pseudo_ct_validation import hu_to_acoustic_properties
58+
59+
d = np.load(SAMPLE)
60+
ct = d["ct"]
61+
62+
# Porosity from HU
63+
HU_MAX = 2500.0
64+
porosity = 1.0 - np.clip(ct, 0, HU_MAX) / HU_MAX
65+
porosity = np.maximum(porosity, 0.05)
66+
67+
# Archie's Law
68+
sigma_brine = 2.0 # S/m
69+
m = 1.5
70+
sigma = sigma_brine * (porosity ** m)
71+
72+
# Dense skull (HU > 1200) should have lower conductivity than brain
73+
dense_skull = ct > 1200
74+
brain = (ct > 50) & (ct < 200)
75+
if dense_skull.any() and brain.any():
76+
sigma_skull = sigma[dense_skull].mean()
77+
sigma_brain = sigma[brain].mean()
78+
assert sigma_skull < sigma_brain, (
79+
f"Dense skull sigma={sigma_skull:.3f} should be < brain sigma={sigma_brain:.3f}"
80+
)
81+
# Dense skull conductivity should be physically reasonable
82+
assert sigma_skull < 1.5, f"Dense skull conductivity {sigma_skull:.3f} too high"

0 commit comments

Comments
 (0)