Tri-Netra-AI / tests /test_v9b_components.py
anannyavyas1's picture
Upload folder using huggingface_hub (part 2)
3366f95 verified
Raw
History Blame Contribute Delete
8.79 kB
"""Unit tests for v9b modules: JEPA, DDPM decoder, SDF tower, two-tower
combiner, JEPA conformal, mesh extraction, MNI152 registration.
Run with: python tests/test_v9b_components.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import torch
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from src.research.jepa import ( # noqa: E402
IJEPAModel, ViTEncoder, JEPAPredictor, make_jepa_masks,
)
from src.research.latent_diffusion_decoder import LatentConditionedDDPM # noqa: E402
from src.research.sdf_geometric_tower import GeometricSDFTower # noqa: E402
from src.research.two_tower_anomaly import combine_two_towers, normalize_per_tower # noqa: E402
from src.research.jepa_conformal import JepaConformalCalibrator, weighted_quantile # noqa: E402
from src.research.mesh_extraction import extract_tumor_mesh, stack_2d_to_pseudo_3d # noqa: E402
from src.research.mni152_registration import ( # noqa: E402
voxel_to_mni_approx, distances_to_landmarks, tumor_atlas_report,
)
from src.research.geometric_prior import synthetic_brain_sdf_template # noqa: E402
# ----------------- JEPA -----------------
def test_vit_encoder_shape():
enc = ViTEncoder(image_size=64, patch_size=8, embed_dim=64, depth=2, heads=4)
x = torch.randn(2, 3, 64, 64)
z = enc(x)
assert z.shape == (2, 64, 64), f"got {z.shape}" # (B, N=8*8, D=64)
def test_vit_encoder_keep_indices():
enc = ViTEncoder(image_size=64, patch_size=8, embed_dim=64, depth=2, heads=4)
x = torch.randn(2, 3, 64, 64)
keep = torch.arange(20).unsqueeze(0).expand(2, -1)
z = enc(x, keep_indices=keep)
assert z.shape == (2, 20, 64)
def test_jepa_predictor_shape():
pred = JEPAPredictor(embed_dim=64, predictor_dim=32, depth=2, heads=4, grid_size=8)
ctx = torch.randn(2, 20, 64)
ci = torch.arange(20).unsqueeze(0).expand(2, -1)
ti = torch.arange(20, 30).unsqueeze(0).expand(2, -1)
out = pred(ctx, ci, ti)
assert out.shape == (2, 10, 64)
def test_make_jepa_masks():
masks = make_jepa_masks(grid_size=16, batch_size=4, n_target=2)
assert masks["context_indices"].shape[0] == 4
assert masks["target_indices"].shape[0] == 4
assert masks["context_indices"].max() < 16 * 16
assert masks["target_indices"].max() < 16 * 16
def test_ijepa_training_forward():
model = IJEPAModel(image_size=64, patch_size=8, embed_dim=64, depth=2,
heads=4, predictor_dim=32, predictor_depth=2)
x = torch.randn(2, 3, 64, 64)
masks = make_jepa_masks(model.grid_size, 2)
out = model(x, masks)
assert "loss" in out and out["loss"].requires_grad
out["loss"].backward()
def test_ijepa_ema_update():
model = IJEPAModel(image_size=64, patch_size=8, embed_dim=32, depth=2,
heads=4, predictor_dim=16, predictor_depth=2)
# Snapshot target weights
p0 = next(model.target_encoder.parameters()).clone()
# Modify context_encoder
for p in model.context_encoder.parameters():
p.data.add_(torch.ones_like(p) * 0.1)
model.ema_update()
p1 = next(model.target_encoder.parameters())
# Target should have moved toward context (since momentum < 1)
assert not torch.allclose(p0, p1), "EMA didn't update target weights"
def test_ijepa_prediction_error_map():
model = IJEPAModel(image_size=32, patch_size=8, embed_dim=32, depth=2,
heads=4, predictor_dim=16, predictor_depth=2)
x = torch.randn(1, 3, 32, 32)
emap = model.prediction_error_map(x)
assert emap.shape == (1, 1, 32, 32)
assert (emap >= 0).all()
# ----------------- DDPM -----------------
def test_ddpm_training_loss():
ddpm = LatentConditionedDDPM(in_chans=3, base_ch=8, cond_dim=16,
num_train_timesteps=100)
x = torch.randn(2, 3, 32, 32)
cond = torch.randn(2, 16)
loss = ddpm.training_loss(x, cond)
assert loss.requires_grad
loss.backward()
def test_ddpm_ddim_sampling():
ddpm = LatentConditionedDDPM(in_chans=3, base_ch=8, cond_dim=16,
num_train_timesteps=100)
cond = torch.randn(1, 16)
out = ddpm.ddim_sample((1, 3, 32, 32), cond, num_steps=5, device="cpu")
assert out.shape == (1, 3, 32, 32)
# ----------------- SDF tower -----------------
def test_sdf_tower_forward():
tower = GeometricSDFTower(image_size=64, base_ch=8)
x = torch.randn(2, 3, 64, 64)
sdf = tower(x)
assert sdf.shape == (2, 1, 64, 64)
def test_sdf_tower_training_loss():
tower = GeometricSDFTower(image_size=64, base_ch=8)
x = torch.randn(2, 3, 64, 64)
tpl = synthetic_brain_sdf_template(64).unsqueeze(0).unsqueeze(0).expand(2, -1, -1, -1)
loss = tower.training_loss(x, tpl)
assert loss.requires_grad
# ----------------- Two-tower combiner -----------------
def test_combine_two_towers_weighted_sum():
a = torch.rand(2, 1, 32, 32)
g = torch.rand(2, 1, 32, 32)
c = combine_two_towers(a, g, mode="weighted_sum", lambda_app=0.5, lambda_geo=0.5)
assert c.shape == (2, 1, 32, 32)
assert (c >= 0).all() and (c <= 1).all()
def test_combine_two_towers_and():
a = torch.rand(2, 1, 32, 32)
g = torch.rand(2, 1, 32, 32)
c = combine_two_towers(a, g, mode="and", q_app=0.5, q_geo=0.5)
assert c.shape == (2, 1, 32, 32)
assert set(c.unique().tolist()).issubset({0.0, 1.0})
# ----------------- JEPA conformal -----------------
def test_weighted_quantile_uniform():
v = np.linspace(0, 1, 100)
w = np.ones(100)
q = weighted_quantile(v, w, 0.9)
assert 0.85 < q < 0.95
def test_jepa_conformal_calibrator():
np.random.seed(0)
scores = np.random.exponential(1.0, 200).tolist()
cal = JepaConformalCalibrator(alpha=0.10)
report = cal.calibrate(scores, verbose=False)
assert report.empirical_coverage >= 0.85
assert cal.q > 0
def test_jepa_conformal_predict_voxelwise():
cal = JepaConformalCalibrator(alpha=0.10)
cal.calibrate(np.random.exponential(1.0, 100).tolist())
errors = torch.rand(2, 1, 16, 16)
mask = cal.predict_voxelwise_certified(errors)
assert mask.shape == (2, 1, 16, 16)
assert mask.dtype == torch.bool
# ----------------- Mesh extraction -----------------
def test_extract_tumor_mesh_simple():
try:
import skimage # noqa
except ImportError:
print("scikit-image not installed, skipping mesh test")
return
vol = np.zeros((10, 32, 32), dtype=np.float32)
vol[3:7, 12:20, 12:20] = 1.0
mesh = extract_tumor_mesh(vol)
assert mesh["n_verts"] > 0
assert mesh["n_faces"] > 0
assert mesh["volume_mm3"] == 4 * 8 * 8 # cube voxels
def test_stack_2d_to_pseudo_3d():
m = np.ones((32, 32))
vol = stack_2d_to_pseudo_3d(m)
assert vol.shape == (1, 32, 32)
# ----------------- MNI152 registration -----------------
def test_voxel_to_mni_center():
mni = voxel_to_mni_approx((0, 128, 128), (1, 256, 256), input_orientation="axial_2d")
assert -1 < mni[0] < 1 # center x
assert -20 < mni[1] < 20 # center y near y=0
assert mni[2] == 0.0 # 2D -> z=0
def test_distances_to_landmarks():
d = distances_to_landmarks((0, 0, 0))
for v in d.values():
assert v >= 0
def test_tumor_atlas_report_full():
vol = np.zeros((1, 256, 256), dtype=np.float32)
vol[0, 100:150, 100:150] = 1.0
rep = tumor_atlas_report(vol)
assert rep["volume_mm3"] == 50 * 50
assert rep["centroid_mni"] is not None
assert len(rep["nearest_landmarks"]) == 3
if __name__ == "__main__":
print("=== JEPA ===")
test_vit_encoder_shape(); test_vit_encoder_keep_indices()
test_jepa_predictor_shape(); test_make_jepa_masks()
test_ijepa_training_forward(); test_ijepa_ema_update()
test_ijepa_prediction_error_map()
print("=== DDPM decoder ===")
test_ddpm_training_loss(); test_ddpm_ddim_sampling()
print("=== SDF tower ===")
test_sdf_tower_forward(); test_sdf_tower_training_loss()
print("=== Two-tower combiner ===")
test_combine_two_towers_weighted_sum(); test_combine_two_towers_and()
print("=== JEPA conformal ===")
test_weighted_quantile_uniform(); test_jepa_conformal_calibrator()
test_jepa_conformal_predict_voxelwise()
print("=== Mesh extraction ===")
test_extract_tumor_mesh_simple(); test_stack_2d_to_pseudo_3d()
print("=== MNI152 registration ===")
test_voxel_to_mni_center(); test_distances_to_landmarks()
test_tumor_atlas_report_full()
print("\nAll v9b component tests passed.")