Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions osipy/dce/t1_mapping/vfa.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,23 @@ def _compute_t1_vfa_impl(
raise DataValidationError(msg)

params = dataset.acquisition_params
if not params.flip_angles:

if params.flip_angles is None:
msg = "VFA T1 mapping requires flip_angles in acquisition_params"
raise DataValidationError(msg)

flip_angles = np.atleast_1d(np.asarray(params.flip_angles))

if flip_angles.size == 0:
msg = "VFA T1 mapping requires at least one flip angle"
raise DataValidationError(msg)

if params.tr is None:
msg = "VFA T1 mapping requires TR in acquisition_params"
raise DataValidationError(msg)

xp = get_array_module(dataset.data)
flip_angles = xp.asarray(params.flip_angles)
flip_angles = xp.asarray(flip_angles)
tr = params.tr

# Check data dimensions match flip angles
Expand Down
30 changes: 26 additions & 4 deletions tests/unit/dce/test_t1_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from osipy.common.types import DCEAcquisitionParams, Modality
from osipy.dce.t1_mapping.binding import BoundLookLockerModel, BoundSPGRModel
from osipy.dce.t1_mapping.models import LookLockerSignalModel, SPGRSignalModel
from osipy.dce.t1_mapping.vfa import compute_t1_vfa

# ---------------------------------------------------------------------------
# Helpers for synthetic data generation
Expand Down Expand Up @@ -407,7 +408,6 @@ class TestVFAFitting:

def test_vfa_linear_recovers_t1(self) -> None:
"""VFA linear fit recovers known T1 from synthetic data."""
from osipy.dce.t1_mapping.vfa import compute_t1_vfa

t1_true = 1000.0
m0_true = 100.0
Expand All @@ -429,7 +429,6 @@ def test_vfa_linear_recovers_t1(self) -> None:

def test_vfa_nonlinear_recovers_t1(self) -> None:
"""VFA nonlinear fit recovers known T1 from synthetic data."""
from osipy.dce.t1_mapping.vfa import compute_t1_vfa

t1_true = 1000.0
m0_true = 100.0
Expand All @@ -450,7 +449,6 @@ def test_vfa_nonlinear_recovers_t1(self) -> None:

def test_vfa_array_interface(self) -> None:
"""VFA fitting works with individual arrays (no dataset)."""
from osipy.dce.t1_mapping.vfa import compute_t1_vfa

t1_true = 800.0
m0_true = 150.0
Expand All @@ -469,9 +467,33 @@ def test_vfa_array_interface(self) -> None:
t1_mean = np.nanmean(result.t1_map.values[result.quality_mask])
np.testing.assert_allclose(t1_mean, t1_true, rtol=0.01)

def test_vfa_accepts_numpy_flip_angles(self) -> None:
"""VFA fitting accepts NumPy arrays for flip_angles in the dataset."""

t1_true = 1000.0
m0_true = 100.0
tr = 5.0

flip_angles = np.array([2.0, 5.0, 10.0, 15.0, 20.0])

dataset = _make_vfa_dataset(
t1=t1_true,
m0=m0_true,
flip_angles=flip_angles.tolist(),
tr=tr,
)

# Replace the list with a NumPy array to reproduce the reported bug
dataset.acquisition_params.flip_angles = flip_angles

result = compute_t1_vfa(dataset, method="linear")

t1_mean = np.nanmean(result.t1_map.values[result.quality_mask])

np.testing.assert_allclose(t1_mean, t1_true, rtol=0.01)

def test_vfa_invalid_method_raises(self) -> None:
"""Unknown VFA method raises DataValidationError."""
from osipy.dce.t1_mapping.vfa import compute_t1_vfa

dataset = _make_vfa_dataset(1000.0, 100.0, [2.0, 5.0, 10.0], 5.0)
with pytest.raises(DataValidationError, match="Unknown VFA method"):
Expand Down