Spaces:
Running on Zero
Running on Zero
Download tests/test_pipeline.py from rfick/brain-zero: direct link, hf CLI and curl.
- Browser
- Download file 24.2 kB
-
https://huggingface.co/spaces/rfick/brain-zero/resolve/main/tests/test_pipeline.py
- Command line
-
hf download hf://spaces/rfick/brain-zero/tests/test_pipeline.py
-
curl -L -o test_pipeline.py https://huggingface.co/spaces/rfick/brain-zero/resolve/main/tests/test_pipeline.py
24.2 kB
| """The pipeline's pieces that need no GPU and no layout: protocols and their measurements, DiSCo's own table as | |
| a protocol, the noise, the score on the ground truth itself, the config's consistency.""" | |
| import numpy as np | |
| import pytest | |
| from space import pipeline as P | |
| from space.sources import disco as D | |
| CFG = P.config() | |
| def test_the_config_names_every_shell_and_preset_shape(): | |
| shapes = set(CFG["shapes"]) | |
| assert {s["shape"] for s in CFG["disco"]["shells"]} <= shapes | |
| for name, preset in CFG["presets"].items(): | |
| assert {s["shape"] for s in preset["shells"]} <= shapes, name | |
| assert P.preset_protocol(CFG, name).name == name | |
| for s in CFG["shapes"].values(): | |
| assert 0 < s["delta"] < s["Delta"] < s["TE"] | |
| def test_a_protocols_measurements_are_its_rows_in_order(): | |
| p = P.Protocol((P.Shell("d12-D24", 1000, 30), P.Shell("d8-D20", 6000, 12)), n_b0=2) | |
| m = P.measurements(p, CFG["shapes"]) | |
| assert p.n_meas == 44 and m.bvals.shape == (44,) and m.dirs.shape == (44, 3) | |
| assert m.b0.sum() == 2 and list(m.shape[:2]) == ["d12-D24"] * 2 # the b = 0 rows take the first shell's timing | |
| assert (m.bvals[2:32] == 1000).all() and (m.bvals[32:] == 6000).all() | |
| np.testing.assert_allclose(np.linalg.norm(m.dirs, axis=1), 1.0, atol=1e-12) | |
| assert (m.dirs[2:, 2] > 0).all() # a hemisphere: no antipodal pairs | |
| np.testing.assert_allclose(m.delta[32:], CFG["shapes"]["d8-D20"]["delta"]) | |
| np.testing.assert_allclose(m.Delta[:32], CFG["shapes"]["d12-D24"]["Delta"]) | |
| def test_protocol_refusals(): | |
| with pytest.raises(ValueError, match="at least one shell"): | |
| P.Protocol(()) | |
| with pytest.raises(ValueError, match="b = 0"): | |
| P.Protocol((P.Shell("d12-D24", 1000, 30),), n_b0=0) | |
| with pytest.raises(ValueError, match="b > 0"): | |
| P.Protocol((P.Shell("d12-D24", 0, 30),)) | |
| with pytest.raises(ValueError, match="directions must be"): | |
| P.Protocol((P.Shell("d12-D24", 1000, 3),), directions=np.zeros((2, 3))) | |
| with pytest.raises(KeyError, match="no timing class"): | |
| P.measurements(P.Protocol((P.Shell("d99-D99", 1000, 3),)), CFG["shapes"]) | |
| def test_discos_table_is_a_protocol_of_its_own_rows(): | |
| """The dataset's 364 rows: 4 shells on 3 timing classes, every row's direction its own, b = 0 rows first, the | |
| rows' b-values those of the table.""" | |
| p, idx = D.disco_protocol(CFG) | |
| m = P.measurements(p, CFG["shapes"]) | |
| bv = np.loadtxt(P.DATA_DIR + "/DiSCo_gradients.bvals").ravel()[idx] # the table's rows in the protocol's order | |
| assert p.name == "DiSCo 364" and p.n_meas == 364 == len(idx) and sorted(idx) == list(range(364)) | |
| assert [s.b for s in p.shells] == [1000, 1925, 3094, 13192] # the table's shells, rounded means | |
| assert [s.shape for s in p.shells] == ["d10.2-D16.7", "d10.2-D16.7", "d7.6-D45.9", "d17.7-D35.8"] | |
| assert m.b0.sum() == p.n_b0 == 4 and m.b0[:p.n_b0].all() | |
| np.testing.assert_array_equal(m.bvals, bv) # every row's own b-value, exactly | |
| assert sorted(np.unique(np.round(bv)).tolist()) == [0, 1000, 1925, 3094, 13192] | |
| np.testing.assert_allclose(np.linalg.norm(m.dirs, axis=1), 1.0, atol=1e-6) | |
| np.testing.assert_allclose(m.delta[m.shape == "d7.6-D45.9"], 7.6e-3) | |
| def test_noise_is_rician_at_the_given_snr_and_leaves_nan_alone(): | |
| dwi = np.full((5, 5, 5, 40), 0.5); dwi[0, 0, 0] = np.nan | |
| out = P.add_noise(dwi, 20.0, seed=1) | |
| assert np.isnan(out[0, 0, 0]).all() and np.isfinite(out[1:]).all() | |
| assert abs(out[1:].std() - 0.05) < 0.005 and (out[1:] >= 0).all() # sigma = 1 / SNR, a magnitude | |
| assert P.add_noise(dwi, None) is dwi | |
| with pytest.raises(ValueError, match="positive"): | |
| P.add_noise(dwi, 0.0) | |
| out_t = P.add_noise(dwi, 20.0, seed=1, backend="torch") # numpy's stream: same distribution, another realisation | |
| assert np.isnan(out_t[0, 0, 0]).all() and abs(out_t[1:].std() - 0.05) < 0.005 and not np.array_equal(out_t[1:], out[1:]) | |
| def test_the_snr_is_at_m0_and_each_voxels_b0_snr_follows_its_s0_factor(): | |
| """A voxel whose b = 0 signal is half of M0 gets twice the sigma on its normalised signal (SNR 20 at M0 is SNR 10 | |
| there); the factor map must cover the voxels and be positive; the b = 0 SNR summary reads it back.""" | |
| dwi = np.full((2, 1, 1, 4000), 0.5); factor = np.array([[[1.0]], [[0.5]]]) | |
| out = P.add_noise(dwi, 20.0, factor, seed=3) | |
| assert abs(out[0].std() - 0.05) < 0.004 and abs(out[1].std() - 0.10) < 0.008 | |
| with pytest.raises(ValueError, match="positive map"): | |
| P.add_noise(dwi, 20.0, np.array([[[1.0]], [[0.0]]])) | |
| with pytest.raises(ValueError, match="positive map"): | |
| P.add_noise(dwi, 20.0, np.ones((3, 1, 1))) | |
| S = np.ones((2, 1, 1, 3)); S[1] *= 0.4; M0 = np.array([[[1.0]], [[1.0]]]) | |
| norm, f = P.s0_normalised(S, np.array([True, False, False]), M0) | |
| np.testing.assert_allclose(norm, 1.0); np.testing.assert_allclose(f.ravel(), [1.0, 0.4]) | |
| res = P.Result(None, None, norm, None, f, None, None, None, None, {}, snr=30.0) | |
| assert P.b0_snr(res) == dict(median=21.0, min=12.0, max=30.0) | |
| assert P.b0_snr(P.Result(None, None, norm, None, f, None, None, None, None, {})) is None | |
| class _Source(D.Disco): | |
| """The ground truth and the config without any replay data (D.Disco loads the files, nothing else).""" | |
| mode = "test" | |
| def test_the_score_of_the_ground_truth_is_one_and_the_pairs_add_up(): | |
| gt = _Source(CFG) | |
| s = gt.score(gt.gt_count, gt.reference(None, None)[0]) | |
| assert s["pearson_count"] == pytest.approx(1.0) and s["false_pairs"] == 0 == s["missed_pairs"] | |
| assert s["connected_pairs"] == s["gt_pairs"] == 25 == P.connected_pairs(gt.gt_count) | |
| empty = gt.score(np.zeros((16, 16)), gt.reference(None, None)[0]) | |
| assert empty["missed_pairs"] == 25 and empty["connected_pairs"] == 0 and np.isnan(empty["pearson_count"]) # a constant matrix: NaN, no warning | |
| assert np.isnan(P.pearson([1, 1, 1], [1, 2, 3])) and P.pearson([1, 2, 3], [2, 4, 6]) == pytest.approx(1.0) | |
| def test_the_volumes_round_trip(tmp_path): | |
| """The DWI and FOD written as NIfTI read back with the table beside them.""" | |
| import nibabel as nib | |
| p = P.Protocol((P.Shell("d12-D24", 1000, 6),), n_b0=1) | |
| m = P.measurements(p, CFG["shapes"]) | |
| dwi = np.random.default_rng(0).random((4, 4, 4, 7)); dwi[0, 0, 0] = np.nan | |
| sh = np.random.default_rng(1).random((4, 4, 4, 45)) | |
| res = P.Result(p, m, dwi, np.zeros((4, 4, 4)), np.ones((4, 4, 4)), sh, None, None, None, {}, {}) | |
| paths = P.write_volumes(res, str(tmp_path), prefix="t", affine=np.eye(4)) | |
| back = np.asarray(nib.load(paths["dwi"]).dataobj) | |
| np.testing.assert_allclose(back, np.nan_to_num(dwi).astype(np.float32)) | |
| np.testing.assert_allclose(np.loadtxt(paths["bvals"]), m.bvals); np.testing.assert_allclose(np.loadtxt(paths["bvecs"]).T, m.dirs, atol=1e-8) | |
| assert np.asarray(nib.load(paths["fod"]).dataobj).shape == (4, 4, 4, 45) | |
| def test_the_backend_and_residency_come_from_the_config_or_the_environment(monkeypatch): | |
| monkeypatch.delenv("DISCO_BACKEND", raising=False); monkeypatch.delenv("DISCO_RESIDENT", raising=False) | |
| assert P.backend(CFG) == CFG["compute"]["backend"] == "jax" and P.resident(CFG) is True | |
| monkeypatch.setenv("DISCO_BACKEND", "torch"); monkeypatch.setenv("DISCO_RESIDENT", "0") | |
| assert P.backend(CFG) == "torch" and P.resident(CFG) is False | |
| monkeypatch.setenv("DISCO_BACKEND", "numpy") | |
| with pytest.raises(ValueError, match="backend"): | |
| P.backend(CFG) | |
| def test_the_mode_and_the_columns_come_from_the_config_or_the_environment(monkeypatch): | |
| monkeypatch.delenv("DISCO_MODE", raising=False); monkeypatch.delenv("DISCO_COLUMNS", raising=False) | |
| assert P.mode(CFG) == "demo" and P.columns_uri(CFG).startswith("hf://") | |
| monkeypatch.setenv("DISCO_MODE", "full"); monkeypatch.setenv("DISCO_COLUMNS", "/columns") | |
| assert P.mode(CFG) == "full" and P.columns_uri(CFG) == "/columns" | |
| monkeypatch.setenv("DISCO_MODE", "fast") | |
| with pytest.raises(ValueError, match="mode"): | |
| P.mode(CFG) | |
| def test_a_free_pulse_timing_is_the_shells_own_in_full_mode(): | |
| p = P.Protocol((P.Shell.free(2000, 12, 0.02, 0.05, 0.09),), n_b0=1) | |
| assert p.shells[0].shape == "d20-D50-TE90" and p.shells[0].free_timing | |
| m = P.measurements(p, CFG["shapes"]) | |
| np.testing.assert_allclose(m.delta, 0.02); np.testing.assert_allclose(m.Delta, 0.05); np.testing.assert_allclose(m.TE, 0.09) | |
| with pytest.raises(ValueError, match="delta < Delta < TE"): | |
| P.Shell.free(2000, 12, 0.06, 0.05, 0.09) | |
| def test_a_table_of_rows_becomes_a_protocol_b0_first_then_each_group_in_order(): | |
| """protocol_from_rows: the one row-grouping used by DiSCo's table and by an uploaded scheme; its refusals.""" | |
| b = np.array([1000, 0, 2000, 1000, 0]); dirs = np.array([[1, 0, 0], [0, 0, 0], [0, 2, 0], [0, 0, 1], [0, 0, 0]], float) | |
| group = np.array([0, 0, 1, 0, 0]) | |
| p, idx = P.protocol_from_rows(b, dirs, group, lambda g, rows: P.Shell("d12-D24", float(b[rows[0]]), len(rows)), name="t") | |
| assert list(idx) == [1, 4, 0, 3, 2] and p.n_b0 == 2 and [(s.b, s.n_dirs) for s in p.shells] == [(1000.0, 2), (2000.0, 1)] | |
| np.testing.assert_allclose(np.linalg.norm(p.directions, axis=1), 1.0) | |
| np.testing.assert_array_equal(p.directions[:2], [[0, 0, 1], [0, 0, 1]]) | |
| with pytest.raises(ValueError, match="b = 0"): | |
| P.protocol_from_rows(b[[0, 2]], dirs[[0, 2]], group[[0, 2]], None, name="t") | |
| with pytest.raises(ValueError, match="zero direction"): | |
| P.protocol_from_rows(b, np.zeros((5, 3)), group, None, name="t") | |
| with pytest.raises(ValueError, match="dirs must be"): | |
| P.protocol_from_rows(b, dirs[:3], group, None, name="t") | |
| def test_a_camino_scheme_becomes_a_protocol_with_every_rows_timing(tmp_path): | |
| """Two shells with their own delta / Delta at one TE plus a b = 0 row, written as Camino STEJSKALTANNER rows | |
| (|G| in T/m, times in s): one free shell per distinct timing, the rows' own b-values and directions, b = 0 first.""" | |
| rows = ["VERSION: STEJSKALTANNER"] | |
| rng = np.random.default_rng(0) | |
| rows.append("0 0 0 0 0.0300 0.0100 0.0800") | |
| for delta, Delta, G in ((0.0100, 0.0300, 0.0400), (0.0150, 0.0400, 0.0500)): | |
| for _ in range(4): | |
| u = rng.normal(size=3); u /= np.linalg.norm(u) | |
| rows.append(f"{u[0]:.6f} {u[1]:.6f} {u[2]:.6f} {G:.4f} {Delta:.4f} {delta:.4f} 0.0800") | |
| path = tmp_path / "t.scheme"; path.write_text("\n".join(rows) + "\n") | |
| p = P.protocol_from_scheme(str(path)) | |
| assert p.n_meas == 9 and p.n_b0 == 1 and len(p.shells) == 2 and all(s.free_timing for s in p.shells) | |
| m = P.measurements(p, CFG["shapes"]) | |
| assert m.b0[0] and (m.delta[1:5] == 0.0100).all() and (m.delta[5:] == 0.0150).all() and np.allclose(m.TE, 0.08) | |
| assert np.allclose(np.linalg.norm(m.dirs, axis=1), 1.0) | |
| np.testing.assert_allclose(sorted(s.b for s in p.shells), [305, 1409], atol=1) # Stejskal-Tanner b of 40 and 50 mT/m | |
| assert p.shells[0].shape == "d10-D30-TE80" | |
| def test_the_physics_is_the_catalogue_at_the_nearest_field_with_the_tiers_as_switches(): | |
| """3 T is cited; 0.064 T and 11.7 T take the nearest cited field (1.5 T, 7 T) and say so; a tier off leaves that | |
| part out of the tissue; all off is bare (no tissue); the refusals.""" | |
| c3 = P.catalogue(3.0); assert c3["catalogue_field"] == 3.0 and set(c3["T2"]) == set(P.CATALOGUE_POOLS) and c3["T2"]["myelin"] < c3["T2"]["intra"] | |
| two = P.catalogue(3.0, ("intra", "extra")); assert set(two["T2"]) == {"intra", "extra"} | |
| with pytest.raises(ValueError, match="no relaxation for the pools"): | |
| P.catalogue(3.0, ("intra", "csf")) | |
| assert P.catalogue(0.064)["catalogue_field"] == 1.5 and P.catalogue(11.7)["catalogue_field"] == 7.0 | |
| ph = P.Physics.at(3.0) | |
| assert ph.pools == ("intra", "extra") | |
| tis = ph.tissue() | |
| assert tis.T2 == two["T2"] and tis.T1 == two["T1"] and tis.rho2 == c3["rho2"] and tis.chi_iso == c3["chi_iso"] and tis.chi_aniso == c3["chi_aniso"] | |
| filled = ph.tissue(unseeded=("myelin",)) # the unseeded spec pool takes the first pool's value | |
| assert set(filled.T2) == {"intra", "extra", "myelin"} and filled.T2["myelin"] == two["T2"]["intra"] | |
| assert not ph.bare and "relaxation+contact+field" in ph.label() | |
| no_field = P.Physics.at(7.0, field=False, b0_direction=P.b0_direction(90, 0)) | |
| assert no_field.tissue().chi_iso is None and no_field.tissue().chi_aniso == 0.0 and no_field.tissue().rho2 == c3["rho2"] | |
| np.testing.assert_allclose(no_field.b0_direction, (1, 0, 0), atol=1e-12) | |
| bare = P.Physics.at(3.0, relaxation=False, contact=False, field=False) | |
| assert bare.bare and bare.tissue() is None and bare.label() == "bare diffusion" | |
| assert P.tissue_and_scanner(bare) == (None, None) and P.tissue_and_scanner(None) == (None, None) | |
| assert P.tissue_and_scanner(no_field)[1] is None and P.tissue_and_scanner(ph)[1] == 3.0 # the field goes with the field tier | |
| only_relax = P.Physics.at(3.0, contact=False, field=False, T2={"intra": 0.08, "extra": 0.08}) | |
| assert only_relax.tissue().T2["intra"] == 0.08 and only_relax.tissue().rho2 is None | |
| with pytest.raises(ValueError, match="tesla"): | |
| P.Physics.at(3000.0) | |
| with pytest.raises(ValueError, match="same pools"): | |
| P.Physics.at(3.0, T2={"intra": 0.05}) | |
| with pytest.raises(ValueError, match="per pool"): | |
| P.Physics.at(3.0, T2={"intra": -0.05, "extra": 0.05}) | |
| with pytest.raises(ValueError, match="unit vector"): | |
| P.Physics.at(3.0, b0_direction=(0, 0, 2)) | |
| np.testing.assert_allclose(np.linalg.norm(P.B0_TRANSVERSE), 1.0) | |
| assert P.Physics.at(3.0).along_z and not no_field.along_z | |
| def test_a_tractogram_sample_keeps_whole_streamlines_in_order(): | |
| from dmipy_tract.tractogram import Tractogram | |
| rng = np.random.default_rng(1) | |
| counts = rng.integers(2, 6, size=50) | |
| tg = Tractogram(rng.normal(size=(counts.sum(), 3)), np.concatenate([[0], np.cumsum(counts)]), np.arange(50), np.zeros((50, 2), np.int8)) | |
| s = P.sample_tractogram(tg, 10, seed=0) | |
| assert len(s) == 10 and np.all(np.diff(s.seed_index) > 0) | |
| for k, i in enumerate(s.seed_index): | |
| np.testing.assert_array_equal(s[k], tg[int(i)]) | |
| assert P.sample_tractogram(tg, 100) is tg | |
| def test_the_gradient_a_shell_needs_and_the_scanners_that_can_play_it(): | |
| """b = 1000 at δ 12 / Δ 24 ms needs 70 mT/m (γ² G² δ² (Δ − δ/3)); the Prisma (80 mT/m) plays it, the Swoop (23 | |
| mT/m) does not; DiSCo's b = 13183 shell needs more than either; None means no limit.""" | |
| G = P.gradient_needed(1000, 0.012, 0.024) | |
| assert 0.069 < G < 0.071 | |
| assert P.limits("siemens_magnetom_prisma_3T").G_max == 0.08 and P.limits("hyperfine_swoop_64mT").G_max < 0.03 | |
| p = P.Protocol((P.Shell("d12-D24", 1000, 30),), n_b0=1) | |
| assert P.playable(p, CFG["shapes"], "siemens_magnetom_prisma_3T")[0][-1] and not P.playable(p, CFG["shapes"], "hyperfine_swoop_64mT")[0][-1] | |
| assert P.playable(p, CFG["shapes"], None)[0][5] is None and P.playable(p, CFG["shapes"], None)[0][-1] | |
| disco, _ = D.disco_protocol(CFG) | |
| assert all(r[-1] for r in P.playable(disco, CFG["shapes"], None)) | |
| assert not all(r[-1] for r in P.playable(disco, CFG["shapes"], "siemens_magnetom_prisma_3T")) | |
| free = P.Protocol((P.Shell.free(1000, 30, 0.012, 0.024, 0.06),), n_b0=1) | |
| assert abs(P.playable(free, CFG["shapes"], None)[0][4] - G) < 1e-12 | |
| def test_the_pair_spread_over_repeated_runs(): | |
| M1 = np.zeros((16, 16)); M1[0, 1] = M1[1, 0] = 10; M1[2, 3] = M1[3, 2] = 4 | |
| M2 = M1.copy(); M2[0, 1] = M2[1, 0] = 14; M2[2, 3] = M2[3, 2] = 0; M2[4, 5] = M2[5, 4] = 2 | |
| sp = P.pair_spread([M1, M2], [dict(pearson_count=0.8), dict(pearson_count=0.9)], key="pearson_count") | |
| assert sp["n"] == 2 and abs(sp["pearson_mean"] - 0.85) < 1e-12 and abs(sp["pearson_std"] - np.std([0.8, 0.9], ddof=1)) < 1e-12 | |
| assert sp["pairs_any"] == 3 and sp["pairs_always"] == 1 and sp["mean"][0, 1] == 12 and abs(sp["std"][0, 1] - np.std([10, 14], ddof=1)) < 1e-12 | |
| one = P.pair_spread([M1], [dict(pearson_count=0.8)], key="pearson_count") | |
| assert one["pearson_std"] == 0.0 and one["std"].max() == 0.0 | |
| def test_floor_stats_are_over_the_voxels_with_signal(): | |
| dwi = np.ones((3, 3, 3, 2)); dwi[0, 0, 0] = np.nan | |
| floor = np.arange(27, dtype=float).reshape(3, 3, 3) / 27 | |
| res = P.Result(None, None, dwi, floor, None, None, None, None, None, {}) | |
| f = P.floor_stats(res) | |
| assert f["n"] == 26 and f["max"] == floor[2, 2, 2] and f["median"] == np.median(floor.ravel()[1:]) | |
| def test_the_layout_at_the_pinned_revision_holds_the_configs_classes_and_tiers(): | |
| """The manifest the Space will download: every [shapes] class with the config's timing, and the tiers when the | |
| tissue panel starts on (needs the Hub; skipped offline).""" | |
| import json | |
| hf = pytest.importorskip("huggingface_hub") | |
| d = CFG["data"] | |
| import requests | |
| from huggingface_hub.errors import HfHubHTTPError, LocalEntryNotFoundError, OfflineModeIsEnabled | |
| try: | |
| path = hf.hf_hub_download(d["repo"], f"{d['moments']}/manifest.json", repo_type="dataset", revision=d["revision"]) | |
| except (requests.ConnectionError, HfHubHTTPError, LocalEntryNotFoundError, OfflineModeIsEnabled) as e: # offline, or the Hub unreachable | |
| pytest.skip(f"the Hub is not reachable: {e!r}"[:120]) | |
| with open(path) as f: | |
| m = json.load(f) | |
| assert m["source"]["manifest_sha256"] == d["source_manifest_sha256"] | |
| missing = [c for c in CFG["shapes"] if c not in m["columns"]] | |
| assert not missing, f"the layout at {d['revision'][:12]} lacks {missing}" | |
| for name, t in CFG["shapes"].items(): | |
| enc = m["shapes"][name].get("encoding") or {} | |
| for k in ("delta", "Delta", "TE"): | |
| v = enc.get(k); v = v[0] if isinstance(v, list) else v | |
| if v is not None: | |
| assert abs(v - t[k]) < 1e-9, (name, k, v, t[k]) | |
| if CFG["physics"]["default_on"]: | |
| assert m.get("tiers"), "the tissue panel starts on but the layout has no tiers" | |
| def test_the_tensor_fit_recovers_a_synthetic_tensor_and_refuses_too_few_rows(): | |
| """A voxel with D = diag(1.7, 0.3, 0.3) um^2/ms at b = 0 and 1000 over 30 directions: MD 0.767, FA 0.799; a voxel | |
| outside the mask is NaN; fewer than six directions below b_max refuse.""" | |
| p = P.Protocol((P.Shell("d12-D24", 1000, 30), P.Shell("d8-D20", 3000, 12)), n_b0=1) | |
| m = P.measurements(p, CFG["shapes"]) | |
| D = np.diag([1.7e-3, 0.3e-3, 0.3e-3]) | |
| S = np.exp(-m.bvals * np.einsum("ni,ij,nj->n", m.dirs, D, m.dirs)) | |
| dwi = np.tile(S, (2, 2, 1, 1)); mask = np.ones((2, 2, 1), bool); mask[0, 0, 0] = False | |
| md, fa = P.dti(dwi, m, mask) | |
| assert np.isnan(md[0, 0, 0]) and abs(md[1, 1, 0] - 0.7667) < 1e-3 and abs(fa[1, 1, 0] - 0.7990) < 1e-3 | |
| with pytest.raises(ValueError, match="six or more"): | |
| P.dti(dwi, m, mask, b_max=500) | |
| def test_layer_differences_are_per_shell_between_consecutive_layers(): | |
| p = P.Protocol((P.Shell("d12-D24", 1000, 6), P.Shell("d8-D20", 3000, 6)), n_b0=1) | |
| m = P.measurements(p, CFG["shapes"]) | |
| a = np.ones((2, 1, 1, 13)); b = a.copy(); b[..., 1:7] += 0.01; b[..., 7:] += 0.05; c = b.copy(); c[..., 7:] -= 0.02 | |
| rows = P.layer_differences([("bare", a), ("+ relaxation", b), ("A", c)], m, np.ones((2, 1, 1), bool)) | |
| assert [(r[0], r[1], r[2]) for r in rows] == [("bare", "+ relaxation", 1000.0), ("bare", "+ relaxation", 3000.0), ("+ relaxation", "A", 1000.0), ("+ relaxation", "A", 3000.0)] | |
| np.testing.assert_allclose([r[3] for r in rows], [0.01, 0.05, 0.0, 0.02], atol=1e-12) | |
| def test_the_ingredients_are_the_layouts_tier_maps_in_the_pages_units(): | |
| """The ingredient maps on a stand-in layout: the intra fraction as is, the wall contact as +l in micrometres from | |
| the stored -l, the survival and the field spread only with their tiers on, the maps asked at the run's tissue, | |
| field and direction on the layout's backend.""" | |
| calls = [] | |
| class _M: | |
| manifest = {"tiers": {"D_walk": 6e-10}} | |
| def tier_maps(self, shape, tissue=None, scanner=None, *, backend, resident, b0_direction): | |
| calls.append((shape, tissue is not None, scanner, backend, resident, tuple(b0_direction))) | |
| return dict(pool={"intra": np.full((2, 1, 1), 0.4), "extra": np.full((2, 1, 1), 0.6)}, contact=np.array([[[-2e-5]], [[np.nan]]]), | |
| contact_weight=np.full((2, 1, 1), 0.9), phase=np.zeros((2, 1, 1)), phase_std=np.full((2, 1, 1), 0.1)) | |
| lay = D.Layout.__new__(D.Layout); lay.moments = _M(); lay.tiers = True; lay.backend = "torch"; lay.unseeded = ("myelin",) | |
| meas = P.measurements(P.Protocol((P.Shell("d12-D24", 1000, 6),), n_b0=1), P.config()["shapes"]) | |
| ph = P.Physics.at(3.0) | |
| out = D.Layout.ingredients(lay, meas, ph) | |
| assert out["D_walk"] == 6e-10 and out["intra_fraction"][0, 0, 0] == 0.4 | |
| np.testing.assert_allclose(out["wall_contact_um"].ravel(), [20.0, np.nan]) | |
| assert out["contact_survival"][0, 0, 0] == 0.9 and out["field_rad"][0, 0, 0] == 0.1 | |
| assert calls[-1] == ("d12-D24", True, 3.0, "torch", True, (0.0, 0.0, 1.0)) | |
| bare = D.Layout.ingredients(lay, meas, None) | |
| assert bare["contact_survival"] is None and bare["field_rad"] is None and bare["wall_contact_um"][0, 0, 0] == 20.0 | |
| assert calls[-1][1:3] == (False, None) | |
| lay.tiers = False | |
| assert D.Layout.ingredients(lay, meas, ph) is None | |
| def test_a_machine_reaches_the_layout_as_a_delivery_at_the_placement(): | |
| """On a stand-in layout: the ideal scanner asks the image for the commanded acquisition (no delivery); a machine | |
| asks the layout's delivery for every class at the machine's catalogue entry and the grid centred at the physics' | |
| offset, and hands it to that class's image; a machine with its own gradient on a layout without background moments, | |
| and with a transmit law on one without RF schedules, is refused by name.""" | |
| from dmipy_sim.phantom import Grid | |
| seen = [] | |
| class _M: | |
| grid = Grid(shape=(2, 1, 1), voxel_size_m=(25e-6,) * 3, origin_m=(0.0, 0.0, 0.0)) | |
| background = None | |
| manifest = {"shapes": {"d12-D24": {}, "d8-D20": {}}} | |
| def delivery(self, shape, b, dirs, scanner, grid): | |
| seen.append(("delivery", shape, scanner.name, tuple(np.round(np.asarray(grid.isocenter_m) - np.asarray(self.grid.isocenter_m), 6)))) | |
| return ("delivered", shape) | |
| def image(self, shape, b, dirs, *, backend, resident, tissue, scanner, b0_direction, delivered=None): | |
| seen.append(("image", shape, delivered, scanner, tuple(b0_direction))) | |
| return np.ones((2, 1, 1, len(b))), np.zeros((2, 1, 1)) | |
| lay = D.Layout.__new__(D.Layout); lay.moments = _M(); lay.tiers = True; lay.backend = "jax"; lay.unseeded = ("myelin",) | |
| lay.M0 = np.ones((2, 1, 1)); lay.mask = np.ones((2, 1, 1), bool); lay.pools = ("intra", "extra"); lay.shapes = CFG["shapes"] | |
| meas = P.measurements(P.Protocol((P.Shell("d12-D24", 1000, 3), P.Shell("d8-D20", 2000, 3)), n_b0=1), CFG["shapes"]) | |
| D.Layout.replay(lay, meas, P.Physics.at(3.0)) | |
| assert [s[2] for s in seen if s[0] == "image"] == [None, None] | |
| seen.clear() | |
| ph = P.Physics.at(3.0, scanner="siemens_magnetom_prisma_3T", offset_m=(0.0, 0.0, 0.05)) | |
| D.Layout.validate(lay, P.Protocol((P.Shell("d12-D24", 1000, 3),), n_b0=1), ph) | |
| D.Layout.replay(lay, meas, ph) | |
| assert [s for s in seen if s[0] == "delivery"] == [("delivery", "d12-D24", "siemens_magnetom_prisma_3T", (0.0, 0.0, -0.05)), | |
| ("delivery", "d8-D20", "siemens_magnetom_prisma_3T", (0.0, 0.0, -0.05))] | |
| assert [s[2] for s in seen if s[0] == "image"] == [("delivered", "d12-D24"), ("delivered", "d8-D20")] | |
| swoop = P.Physics.at(0.064, scanner="hyperfine_swoop_64mT", b0_direction=(0.0, 1.0, 0.0)) | |
| with pytest.raises(ValueError, match="background moments"): | |
| D.Layout.validate(lay, P.Protocol((P.Shell("d12-D24", 100, 3),), n_b0=1), swoop) | |
| lay.moments.background = {"amplitude": 5e-3} | |
| with pytest.raises(ValueError, match="RF schedules"): | |
| D.Layout.validate(lay, P.Protocol((P.Shell("d12-D24", 100, 3),), n_b0=1), swoop) | |