From f6f7fceb336db2feccb028d83280af26e00fd71a Mon Sep 17 00:00:00 2001 From: mjt320 Date: Fri, 22 Mar 2024 17:58:25 +0000 Subject: [PATCH 1/3] added linear R1 to signal model added scipy to pyproject.toml --- pyproject.toml | 8 ++++---- src/osipi/__init__.py | 6 +++++- src/osipi/_signal.py | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/osipi/_signal.py diff --git a/pyproject.toml b/pyproject.toml index 074f37d..42e221f 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ [project] name = "osipi" version = "0.1.2" -dependencies = [ - "numpy", +dependencies = [ + "numpy", "scipy" ] # optional information @@ -13,7 +13,7 @@ dependencies = [ description = "The authorative python package for perfusion MRI" readme = "README.md" authors = [ - { name = "Luis Torres", email = "luistorres@flywheel.io" }, + { name = "Luis Torres", email = "luistorres@flywheel.io" }, { name = "Steven Sourbron", email = "s.sourbron@sheffield.ac.uk" }, ] license = { file = "LICENSE" } @@ -52,7 +52,7 @@ tests = [ docs = [ "sphinx", "pydata-sphinx-theme", - "myst-parser", + "myst-parser", "sphinx-copybutton", "sphinx-design", "sphinx-remove-toctrees", diff --git a/src/osipi/__init__.py b/src/osipi/__init__.py index ff2f20b..0d6f65d 100755 --- a/src/osipi/__init__.py +++ b/src/osipi/__init__.py @@ -8,4 +8,8 @@ from ._tissue import ( tofts, extended_tofts -) \ No newline at end of file +) + +from ._signal import ( + R1_to_s_linear +) diff --git a/src/osipi/_signal.py b/src/osipi/_signal.py new file mode 100644 index 0000000..eef9e48 --- /dev/null +++ b/src/osipi/_signal.py @@ -0,0 +1,39 @@ +import numpy as np +from scipy.interpolate import interp1d +from ._convolution import exp_conv +import warnings + + +def R1_to_s_linear(R1: float, k:float) -> float: + """Linear model for relationship between R1 and magnitude signal, s = k.R1 + + Args: + R1 (float): longitudinal relaxation rate in units of /s. [OSIPI code Q.EL1.001] + k (float): proportionality constant in units of arb. unit s [OSIPI code Q.GE1.009] + + Returns: + float: magnitude signal in arb. unit [OSIPI code Q.MS1.001] + + References: + - Lexicon url: https://osipi.github.io/OSIPI_CAPLEX/perfusionModels/#LinModel_SM2 + - Lexicon code: M.SM2.001 + - OSIPI name: Linear model + - Adapted from equation given in the Lexicon + + Example: + + Convert a single R1 value to the corresponding signal intensity. + + Import packages: + + >>> import osipi + + Calculate signal: + + >>> R1 = 3.0 # R1 in units of /s + >>> k = 150.0 # constant of proportionality in units of arb. unit s + >>> s = osipi.R1_to_s_linear(R1, k) + >>> print(s) + """ + # calculate signal + return k*R1 From c131f29c797b621d64694cc849bf442cd81eabde Mon Sep 17 00:00:00 2001 From: mjt320 Date: Fri, 29 Mar 2024 16:08:07 +0000 Subject: [PATCH 2/3] handle/require 1D array input add example --- .../signal/plot_signal_R1_to_s_linear.py | 27 ++++++++++++++++ src/osipi/_signal.py | 32 ++++++------------- 2 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 docs/examples/signal/plot_signal_R1_to_s_linear.py diff --git a/docs/examples/signal/plot_signal_R1_to_s_linear.py b/docs/examples/signal/plot_signal_R1_to_s_linear.py new file mode 100644 index 0000000..3fc06b4 --- /dev/null +++ b/docs/examples/signal/plot_signal_R1_to_s_linear.py @@ -0,0 +1,27 @@ +""" +============== +Linear model for relationship between R1 and magnitude signal +============== + +Demonstrating the linear model for relationship between R1 and magnitude signal, s = k.R1 +""" + +# %% +# Import necessary packages +import numpy as np +import matplotlib.pyplot as plt +import osipi + +# %% +# Convert a series of R1 values to the corresponding signal intensities. + +R1 = np.array([0.0, 1.5, 3.0, 4.0, 10.0]) # R1 in units of /s +k = np.float64(150.0) # constant of proportionality in units of arb. unit s +signal = osipi.R1_to_s_linear(R1, k) # signal in arb. unit +print(f'Signal: {signal}') + +# Plot signal vs. R1 +plt.plot(R1, signal, 'ro-') +plt.xlabel('R1 (/sec)') +plt.ylabel('signal (arb. unit)') +plt.show() \ No newline at end of file diff --git a/src/osipi/_signal.py b/src/osipi/_signal.py index eef9e48..7c4383b 100644 --- a/src/osipi/_signal.py +++ b/src/osipi/_signal.py @@ -1,39 +1,27 @@ import numpy as np -from scipy.interpolate import interp1d -from ._convolution import exp_conv -import warnings +from numpy.typing import NDArray -def R1_to_s_linear(R1: float, k:float) -> float: +def R1_to_s_linear(R1: NDArray[np.float64], k: np.float64) -> NDArray[np.float64]: """Linear model for relationship between R1 and magnitude signal, s = k.R1 Args: - R1 (float): longitudinal relaxation rate in units of /s. [OSIPI code Q.EL1.001] - k (float): proportionality constant in units of arb. unit s [OSIPI code Q.GE1.009] + R1 (1D array of np.float64): vector of longitudinal relaxation rate in units of /s. [OSIPI code Q.EL1.001] + k (np.float64): proportionality constant in units of arb. unit s [OSIPI code Q.GE1.009] Returns: - float: magnitude signal in arb. unit [OSIPI code Q.MS1.001] + 1D array of floats: vector of magnitude signal in arb. unit [OSIPI code Q.MS1.001] References: - Lexicon url: https://osipi.github.io/OSIPI_CAPLEX/perfusionModels/#LinModel_SM2 - Lexicon code: M.SM2.001 - OSIPI name: Linear model - Adapted from equation given in the Lexicon - - Example: - - Convert a single R1 value to the corresponding signal intensity. - - Import packages: - - >>> import osipi - - Calculate signal: - - >>> R1 = 3.0 # R1 in units of /s - >>> k = 150.0 # constant of proportionality in units of arb. unit s - >>> s = osipi.R1_to_s_linear(R1, k) - >>> print(s) """ + # check R1 is a 1D array of floats + if not (isinstance(R1, np.ndarray) and R1.ndim == 1 and R1.dtype == np.float64): + raise TypeError("R1 must be a 1D NumPy array of np.float64") # calculate signal return k*R1 + + From 55f8a2f69f71a90af423d4fd4c0c859a270ae7af Mon Sep 17 00:00:00 2001 From: mjt320 Date: Fri, 29 Mar 2024 16:27:28 +0000 Subject: [PATCH 3/3] test added --- tests/test_signal.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/test_signal.py diff --git a/tests/test_signal.py b/tests/test_signal.py new file mode 100644 index 0000000..2502d25 --- /dev/null +++ b/tests/test_signal.py @@ -0,0 +1,20 @@ +import numpy as np +import osipi + + +def test_signal_R1_to_s_linear(): + + # 1. Simple use case + R1 = np.array([0.0, 1.5, 3.0, 4.0, 10.0]) # R1 in units of /s + k = np.float64(150.0) # constant of proportionality in units of arb. unit s + signal_truth = np.array([0.0, 225.0, 450.0, 600.0, 1500.0]) # expected signal in arb. unit + signal = osipi.R1_to_s_linear(R1, k) # estimated signal + np.testing.assert_allclose(signal_truth, signal, rtol=0, atol=1e-7) + + +if __name__ == "__main__": + + test_signal_R1_to_s_linear() + + print('All signal functionality tests passed!!') +