diff --git a/src/torchsim/_functional/_bssfp.py b/src/torchsim/_functional/_bssfp.py index 7e3cf754..277b657e 100644 --- a/src/torchsim/_functional/_bssfp.py +++ b/src/torchsim/_functional/_bssfp.py @@ -2,6 +2,8 @@ __all__ = ["bssfp_sim"] +from typing import Any + import numpy.typing as npt import torch @@ -15,15 +17,16 @@ def bssfp_sim( TR: float, T1: float | npt.ArrayLike, T2: float | npt.ArrayLike, - diff: str | tuple[str] = None, + diff: str | tuple[str, ...] | None = None, phase_inc: float = 180.0, B0: float | npt.ArrayLike = 0.0, chemshift: float | npt.ArrayLike = 0.0, M0: float | npt.ArrayLike = 1.0, - device: str | torch.device = None, + device: str | torch.device | None = None, + **values: Any, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """ - SPoiled Gradient Recalled echo simulator wrapper. + Balanced steady-state free-precession simulator wrapper. Parameters ---------- @@ -45,10 +48,14 @@ def bssfp_sim( The default is ``180.0`` B0 : float | npt.ArrayLike, optional Frequency offset map in Hz, default is ``0.0.`` - chemshift : float | npt.ArrayLik, optional + chemshift : float | npt.ArrayLike, optional Chemical shift in Hz, default is ``0.0``. M0 : float or array-like, optional Proton density scaling factor, default is ``1.0``. + **values : optional + Additional call-time tissue, protocol or run settings accepted by + ``Simulator.simulate``. Settings fixed only at simulator construction + remain class-interface features. device : str | torch.device, optional Computational device for simulation. The default is ``None`` (infer from input). @@ -76,4 +83,5 @@ def bssfp_sim( TR=TR, TE=TE, phase_inc=phase_inc, + **values, ) diff --git a/src/torchsim/_functional/_fse.py b/src/torchsim/_functional/_fse.py index 2ca9ee69..9e565a3d 100644 --- a/src/torchsim/_functional/_fse.py +++ b/src/torchsim/_functional/_fse.py @@ -2,6 +2,8 @@ __all__ = ["fse_sim"] +from typing import Any + import numpy.typing as npt import torch @@ -18,11 +20,12 @@ def fse_sim( TR: float | npt.ArrayLike = 1e6, exc_flip: float = 90.0, exc_phase: float = 90.0, - diff: str | tuple[str] = None, + diff: str | tuple[str, ...] | None = None, B1: float | npt.ArrayLike = 1.0, M0: float | npt.ArrayLike = 1.0, states: int = 10, - device: str | torch.device = None, + device: str | torch.device | None = None, + **values: Any, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """ Fast Spin Echo simulator wrapper. @@ -35,7 +38,7 @@ def fse_sim( Echo spacing in milliseconds. phases : float | npt.ArrayLike, optional Refocusing flip angle phases in degrees. - The default is ``90.0``. + The default is ``0.0``. TR : float | npt.ArrayLike, optional Repetition time in milliseconds. The default is ``1e6``. @@ -44,7 +47,7 @@ def fse_sim( The default is ``90.0``. exc_phase : float, optional Excitation flip angle phase in degrees. - The default is ``0.0``. + The default is ``90.0``. T1 : float | npt.ArrayLike Longitudinal relaxation time in milliseconds. T2 : float | npt.ArrayLike @@ -54,16 +57,15 @@ def fse_sim( The default is ``None`` (no differentation). B1 : float | npt.ArrayLike, optional Flip angle scaling map, default is ``1.0``. - inv_efficiency : float | npt.ArrayLike, optional - Inversion efficiency map, default is ``1.0``. M0 : float or array-like, optional Proton density scaling factor, default is ``1.0``. - TI : float | npt.ArrayLike, optional - Inversion time in milliseconds. - The default is ``0.0``. states : int, optional Number of EPG states to be retained. The default is ``10``. + **values : optional + Additional call-time tissue, protocol or run settings accepted by + ``Simulator.simulate``. Settings fixed only at simulator construction + remain class-interface features. device : str | torch.device, optional Computational device for simulation. The default is ``None`` (infer from input). @@ -93,4 +95,5 @@ def fse_sim( exc_flip=exc_flip, exc_phase=exc_phase, states=states, + **values, ) diff --git a/src/torchsim/_functional/_mp2rage.py b/src/torchsim/_functional/_mp2rage.py index 11b41094..76d18311 100644 --- a/src/torchsim/_functional/_mp2rage.py +++ b/src/torchsim/_functional/_mp2rage.py @@ -2,6 +2,8 @@ __all__ = ["mp2rage_sim"] +from typing import Any + import numpy.typing as npt import torch @@ -16,10 +18,11 @@ def mp2rage_sim( TRmp2rage: float, nshots: int | npt.ArrayLike, T1: float | npt.ArrayLike, - diff: str | tuple[str] = None, + diff: str | tuple[str, ...] | None = None, inv_efficiency: float | npt.ArrayLike = 1.0, M0: float | npt.ArrayLike = 1.0, - device: str | torch.device = None, + device: str | torch.device | None = None, + **values: Any, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """ MP2RAGE simulator wrapper. @@ -49,9 +52,10 @@ def mp2rage_sim( Inversion efficiency map, default is ``1.0``. M0 : float or array-like, optional Proton density scaling factor, default is ``1.0``. - TI : float | npt.ArrayLike, optional - Inversion time in milliseconds. - The default is ``0.0``. + **values : optional + Additional call-time tissue, protocol or run settings accepted by + ``Simulator.simulate``. Settings fixed only at simulator construction + remain class-interface features. device : str | torch.device, optional Computational device for simulation. The default is ``None`` (infer from input). @@ -78,4 +82,5 @@ def mp2rage_sim( TRspgr=TRspgr, TRmp2rage=TRmp2rage, nshots=nshots, + **values, ) diff --git a/src/torchsim/_functional/_mpnrage.py b/src/torchsim/_functional/_mpnrage.py index 1c04e6cb..2d9a33c8 100644 --- a/src/torchsim/_functional/_mpnrage.py +++ b/src/torchsim/_functional/_mpnrage.py @@ -2,6 +2,8 @@ __all__ = ["mpnrage_sim"] +from typing import Any + import numpy.typing as npt import torch @@ -14,12 +16,14 @@ def mpnrage_sim( flip: npt.ArrayLike, TR: float | npt.ArrayLike, T1: float | npt.ArrayLike, - diff: str | tuple[str] = None, + diff: str | tuple[str, ...] | None = None, B1: float | npt.ArrayLike = 1.0, inv_efficiency: float | npt.ArrayLike = 1.0, M0: float | npt.ArrayLike = 1.0, TI: float = 0.0, - device: str | torch.device = None, + phases: float | npt.ArrayLike = 0.0, + device: str | torch.device | None = None, + **values: Any, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """ MPnRAGE simulator wrapper. @@ -46,6 +50,12 @@ def mpnrage_sim( TI : float | npt.ArrayLike, optional Inversion time in milliseconds. The default is ``0.0``. + phases : float or array-like, optional + Sequence phase schedule in degrees. The default is ``0.0``. + **values : optional + Additional call-time tissue, protocol or run settings accepted by + ``Simulator.simulate``. Settings fixed only at simulator construction + remain class-interface features. device : str | torch.device, optional Computational device for simulation. The default is ``None`` (infer from input). @@ -72,4 +82,6 @@ def mpnrage_sim( flip=flip, TR=TR, TI=TI, + phases=phases, + **values, ) diff --git a/src/torchsim/_functional/_mprage.py b/src/torchsim/_functional/_mprage.py index b29db986..01126b74 100644 --- a/src/torchsim/_functional/_mprage.py +++ b/src/torchsim/_functional/_mprage.py @@ -2,6 +2,8 @@ __all__ = ["mprage_sim"] +from typing import Any + import numpy.typing as npt import torch @@ -15,10 +17,12 @@ def mprage_sim( TRspgr: float, nshots: int | npt.ArrayLike, T1: float | npt.ArrayLike, - diff: str | tuple[str] = None, + diff: str | tuple[str, ...] | None = None, inv_efficiency: float | npt.ArrayLike = 1.0, M0: float | npt.ArrayLike = 1.0, - device: str | torch.device = None, + phases: float | npt.ArrayLike = 0.0, + device: str | torch.device | None = None, + **values: Any, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """ MPRAGE simulator wrapper. @@ -46,9 +50,12 @@ def mprage_sim( Inversion efficiency map, default is ``1.0``. M0 : float or array-like, optional Proton density scaling factor, default is ``1.0``. - TI : float | npt.ArrayLike, optional - Inversion time in milliseconds. - The default is ``0.0``. + phases : float or array-like, optional + Sequence phase schedule in degrees. The default is ``0.0``. + **values : optional + Additional call-time tissue, protocol or run settings accepted by + ``Simulator.simulate``. Settings fixed only at simulator construction + remain class-interface features. device : str | torch.device, optional Computational device for simulation. The default is ``None`` (infer from input). @@ -74,4 +81,6 @@ def mprage_sim( flip=flip, TRspgr=TRspgr, nshots=nshots, + phases=phases, + **values, ) diff --git a/src/torchsim/_functional/_mrf.py b/src/torchsim/_functional/_mrf.py index f7fb4f23..97979448 100644 --- a/src/torchsim/_functional/_mrf.py +++ b/src/torchsim/_functional/_mrf.py @@ -2,6 +2,8 @@ __all__ = ["mrf_sim"] +from typing import Any + import numpy.typing as npt import torch @@ -14,14 +16,16 @@ def mrf_sim( TR: float | npt.ArrayLike, T1: float | npt.ArrayLike, T2: float | npt.ArrayLike, - diff: str | tuple[str] = None, + diff: str | tuple[str, ...] | None = None, B1: float | npt.ArrayLike = 1.0, inv_efficiency: float | npt.ArrayLike = 1.0, M0: float | npt.ArrayLike = 1.0, TI: float = 0.0, + phases: float | npt.ArrayLike = 0.0, states: int = 10, nreps: int = 1, - device: str | torch.device = None, + device: str | torch.device | None = None, + **values: Any, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """ SSFP MR Fingerprinting simulator wrapper. @@ -54,6 +58,12 @@ def mrf_sim( nreps : int, optional Number of simulation repetitions. The default is ``1``. + phases : float or array-like, optional + Sequence phase schedule in degrees. The default is ``0.0``. + **values : optional + Additional call-time tissue, protocol or run settings accepted by + ``Simulator.simulate``. Settings fixed only at simulator construction + remain class-interface features. device : str | torch.device, optional Computational device for simulation. The default is ``None`` (infer from input). @@ -80,6 +90,7 @@ def mrf_sim( flip=flip, TR=TR, TI=TI, + phases=phases, states=states, - repetitions=nreps, + **{"repetitions": nreps, **values}, ) diff --git a/src/torchsim/_functional/_spgr.py b/src/torchsim/_functional/_spgr.py index 95b9b291..91d25435 100644 --- a/src/torchsim/_functional/_spgr.py +++ b/src/torchsim/_functional/_spgr.py @@ -2,6 +2,8 @@ __all__ = ["spgr_sim"] +from typing import Any + import numpy.typing as npt import torch @@ -15,11 +17,12 @@ def spgr_sim( TR: float, T1: float | npt.ArrayLike, T2star: float | npt.ArrayLike, - diff: str | tuple[str] = None, + diff: str | tuple[str, ...] | None = None, B0: float | npt.ArrayLike = 0.0, chemshift: float | npt.ArrayLike = 0.0, M0: float | npt.ArrayLike = 1.0, - device: str | torch.device = None, + device: str | torch.device | None = None, + **values: Any, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """ SPoiled Gradient Recalled echo simulator wrapper. @@ -41,10 +44,14 @@ def spgr_sim( The default is ``None`` (no differentation). B0 : float | npt.ArrayLike, optional Frequency offset map in Hz, default is ``0.0.`` - chemshift : float | npt.ArrayLik, optional + chemshift : float | npt.ArrayLike, optional Chemical shift in Hz, default is ``0.0``. M0 : float or array-like, optional Proton density scaling factor, default is ``1.0``. + **values : optional + Additional call-time tissue, protocol or run settings accepted by + ``Simulator.simulate``. Settings fixed only at simulator construction + remain class-interface features. device : str | torch.device, optional Computational device for simulation. The default is ``None`` (infer from input). @@ -71,4 +78,5 @@ def spgr_sim( flip=flip, TR=TR, TE=TE, + **values, ) diff --git a/tests/functionals/test_fse_func.py b/tests/functionals/test_fse_func.py index 1e4af953..82c1e4de 100644 --- a/tests/functionals/test_fse_func.py +++ b/tests/functionals/test_fse_func.py @@ -41,3 +41,21 @@ def test_scalar_gradient(flip): def test_multiple_gradient(flip): _, grad = fse_sim(flip, ESP=1.0, T1=(200, 500, 1000.0), T2=100.0, diff=("T1", "T2")) assert grad.shape == (3, 2, 100) + + +def test_current_simulator_physics_passes_through(flip): + """The convenience call does not hide physics the Simulator accepts.""" + from torchsim.simulators import FSESimulator + + got = fse_sim( + flip, + ESP=5.0, + T1=1000.0, + T2=100.0, + B0=17.0, + ) + expected = FSESimulator( + flip=flip, + ESP=5.0, + ).simulate(T1=1000.0, T2=100.0, B0=17.0) + np.testing.assert_allclose(got, expected) diff --git a/tests/functionals/test_mrf_func.py b/tests/functionals/test_mrf_func.py index 0e8b1978..1af564bc 100644 --- a/tests/functionals/test_mrf_func.py +++ b/tests/functionals/test_mrf_func.py @@ -41,3 +41,22 @@ def test_scalar_gradient(flip): def test_multiple_gradient(flip): _, grad = mrf_sim(flip, TR=10.0, T1=(200, 500, 1000.0), T2=100.0, diff=("T1", "T2")) assert grad.shape == (3, 2, 100) + + +def test_current_simulator_protocol_arguments_pass_through(flip): + """The convenience call accepts protocol fields added to MRFSimulator.""" + from torchsim.simulators import MRFSimulator + + phases = np.linspace(0.0, 117.0, flip.size, dtype=np.float32) + got = mrf_sim( + flip, + TR=10.0, + T1=1000.0, + T2=100.0, + phases=phases, + B0=23.0, + ) + expected = MRFSimulator(flip=flip, TR=10.0, phases=phases).simulate( + T1=1000.0, T2=100.0, B0=23.0 + ) + np.testing.assert_allclose(got, expected)