Skip to content

Commit

Permalink
Added TCXM adapted from MJT implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedNasser8 committed Oct 7, 2024
1 parent 2ae41e2 commit f40ae1a
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 34 deletions.
8 changes: 4 additions & 4 deletions docs/examples/tissue/plot_two_cxm.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
# Plot the tissue concentrations for an extracellular volume fraction
# of 0.2, plasma volume fraction of 0.3, extraction fraction of 0.15
# and flow rate of 0.2 ml/min
Ps = 0.15 # Extraction fraction
Fp = 20 # Flow rate in ml/min
Ve = 0.1 # Extracellular volume fraction
Vp = 0.02 # Plasma volume fraction
Ps = 0.05 # Permeability surface area product in ml/min
Fp = 10 # Flow rate in ml/min
Ve = 0.2 # Extracellular volume fraction
Vp = 0.01 # Plasma volume fraction
ct = osipi.two_compartment_exchange_model(t, ca, Fp=Fp, Ps=Ps, Ve=Ve, Vp=Vp)
plt.plot(t, ct, "b-", label=f" Fp = {Fp},Ps = {Ps}, Ve = {Ve}, Vp = {Vp}")
plt.xlabel("Time (sec)")
Expand Down
2 changes: 1 addition & 1 deletion docs/references/models/tissue_models/2cxm.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
::: osipi.two_compartment_exchange_model


## Example using `osipi.two_cxm`
## Example using `osipi.two_compartment_exchange_model`

<figure class="mkd-glr-thumbcontainer" tooltip="Simulating tissue concentrations from two compartment exchange model with different settings.">
<img alt="The Two Compartment Exchange Model" src="..\..\..\..\generated\gallery\tissue\images\thumb\mkd_glr_plot_two_cxm_thumb.png" />
Expand Down
41 changes: 41 additions & 0 deletions src/osipi/_tissue.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,16 +344,53 @@ def two_compartment_exchange_model(
Vp: float,
Ta: float = 30.0,
) -> np.ndarray:
"""Two compartment exchange model
Tracer flows from the AIF to the blood plasma compartment; two-way leakage
between the plasma and extracellular compartments(EES) is permitted.
Args:
t: 1D array of times(s).
ca: Arterial concentrations in mM for each time point in t.
Fp: Blood plasma flow rate into a unit tissue volume in ml/min.
Ps: Permeability surface area product in ml/min.
Ve: Extracellular volume fraction.
Vp: Plasma volume fraction.
Ta: Arterial delay time, i.e., difference in onset time between tissue curve and AIF in units of sec.
Returns:
Ct: Tissue concentrations in mM
References:
- Lexicon url: https://osipi.github.io/OSIPI_CAPLEX/perfusionModels/#indicator-kinetic-models
- Lexicon code: M.IC1.009
- OSIPI name: Two Compartment Exchange Model
- Adapted from contributions by: MJT_UoEdinburgh_UK
"""
if not np.allclose(np.diff(t), np.diff(t)[0]):
warnings.warn(
("Non-uniform time spacing detected. Time array may be" " resampled."),
stacklevel=2,
)

# Convert units
fp_per_s = Fp / (60.0 * 100.0)
ps_per_s = Ps / 60.0

# Calculate the impulse response function
v = Ve + Vp

# Mean transit time
T = v / fp_per_s
tc = Vp / fp_per_s
te = Ve / ps_per_s

sig_p = ((T + te) + np.sqrt((T + te) ** 2 - 4 * tc * te)) / (2 * tc * te)
sig_n = ((T + tc) - np.sqrt((T + tc) ** 2 - 4 * tc * te)) / (2 * tc * te)

# Calculate the impulse response function for the plasma compartment and EES

irf_cp = (
Vp
* sig_p
Expand All @@ -363,13 +400,17 @@ def two_compartment_exchange_model(
)

irf_ce = Ve * sig_p * sig_n * (np.exp(-t * sig_n) - np.exp(-t * sig_p)) / (sig_p - sig_n)

irf_cp[[0]] /= 2
irf_ce[[0]] /= 2

dt = np.min(np.diff(t))

# get concentration in plasma and EES

Cp = dt * convolve(ca, irf_cp, mode="full", method="auto")[: len(t)]
Ce = dt * convolve(ca, irf_ce, mode="full", method="auto")[: len(t)]

# get tissue concentration
Ct = Cp + Ce
return Ct
30 changes: 1 addition & 29 deletions tests/test_tissue.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,35 +125,7 @@ def test_tissue_extended_tofts():


def test_tissue_two_compartment_exchange_model():
# 1. Basic operation of the function - test that the peak tissue
# concentration is less than the peak AIF
t = np.arange(0, 6 * 60, 1, dtype=float)
ca = osipi.aif_parker(t)
ct = osipi.two_compartment_exchange_model(t, ca, E=0.15, Fp=0.2, Ve=0.2, Vp=0.3)
assert np.round(np.max(ct)) < np.round(np.max(ca))

# 2. Basic operation of the function - test with non-uniform spacing of
# time array
t = np.geomspace(1, 6 * 60 + 1, num=360) - 1
ca = osipi.aif_parker(t)
ct = osipi.two_compartment_exchange_model(t, ca, E=0.15, Fp=0.2, Ve=0.2, Vp=0.3)
assert np.round(np.max(ct)) < np.round(np.max(ca))

# 3. The offset option - test that the tissue concentration is shifted
# from the AIF by the specified offset time
t = np.arange(0, 6 * 60, 1, dtype=float)
ca = osipi.aif_parker(t)
ct = osipi.two_compartment_exchange_model(t, ca, E=0.15, Fp=0.2, Ve=0.2, Vp=0.1, Ta=60.0)
assert (np.min(np.where(ct > 0.0)) - np.min(np.where(ca > 0.0)) - 1) * 1 == 60.0

# 4. Test specific use cases

# Test when Vp is 0 the output matches tofts model
t = np.arange(0, 6 * 60, 1, dtype=float)
ca = osipi.aif_parker(t)
ct = osipi.two_compartment_exchange_model(t, ca, E=0.15, Fp=0.2, Ve=0.2, Vp=0)
ct_tofts = osipi.tofts(t, ca, Ktrans=0.03, ve=0.2)
assert np.allclose(ct, ct_tofts, rtol=1e-4, atol=1e-3)
pass


if __name__ == "__main__":
Expand Down

0 comments on commit f40ae1a

Please sign in to comment.