Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.8 # Use the latest version
rev: v0.4.8 # Use the latest version
hooks:
- id: ruff
args: [--fix] # Optional: to enable lint fixes
args: [--fix] # Optional: to enable lint fixes
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
Expand Down
51 changes: 51 additions & 0 deletions docs/examples/tissue/plot_two_cxm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
=============
The Two Compartment Exchange Model
=============

Simulating tissue concentrations from two compartment models with different settings.
"""

import matplotlib.pyplot as plt

# %%
# Import necessary packages
import numpy as np
import osipi

# %%
# Generate Parker AIF with default settings.

# Define time points in units of seconds - in this case we use a time
# resolution of 1 sec and a total duration of 6 minutes.
t = np.arange(0, 5 * 60, 1, dtype=float)

# Create an AIF with default settings
ca = osipi.aif_parker(t)

# %%
# Plot the tissue concentrations for an extracellular volume fraction
# of 0.2, plasma volume fraction of 0.1, permeability serface area of 5 ml/min
# and flow rate of 10 ml/min
PS = 15 # Permeability surface area product in ml/min
Fp = [5, 25] # Flow rate in ml/min
ve = 0.1 # Extracellular volume fraction
vp = [0.1, 0.02] # Plasma volume fraction

ct = osipi.two_compartment_exchange_model(t, ca, Fp=Fp[0], PS=PS, ve=ve, vp=vp[0])
plt.plot(t, ct, "b-", label=f" Fp = {Fp[0]},PS = {PS}, ve = {ve}, vp = {vp[0]}")

ct = osipi.two_compartment_exchange_model(t, ca, Fp=Fp[1], PS=PS, ve=ve, vp=vp[0])
plt.plot(t, ct, "r-", label=f" Fp = {Fp[1]},PS = {PS}, ve = {ve}, vp = {vp[0]}")

ct = osipi.two_compartment_exchange_model(t, ca, Fp=Fp[0], PS=PS, ve=ve, vp=vp[1])
plt.plot(t, ct, "g-", label=f" Fp = {Fp[0]},PS = {PS}, ve = {ve}, vp = {vp[1]}")

ct = osipi.two_compartment_exchange_model(t, ca, Fp=Fp[1], PS=PS, ve=ve, vp=vp[1])
plt.plot(t, ct, "y-", label=f" Fp = {Fp[1]},PS = {PS}, ve = {ve}, vp = {vp[1]}")


plt.xlabel("Time (sec)")
plt.ylabel("Tissue concentration (mM)")
plt.legend()
plt.show()
17 changes: 17 additions & 0 deletions docs/references/models/tissue_models/2cxm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# osipi.Two Compartment Exchange Model

::: osipi.two_compartment_exchange_model


## 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" />
<figcaption class="caption">
<span class="caption-text">
<a class="reference internal" href="..\..\..\..\generated\gallery\tissue\plot_two_cxm">
<span class="std std-ref">The Two Compartment Exchange Model</span>
</a>
</span>
</figcaption>
</figure>
1 change: 1 addition & 0 deletions docs/references/models/tissue_models/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

- [tofts](tofts.md)
- [extended_tofts](extended_tofts.md)
- [two_cxm](2cxm.md)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ nav:
- references/models/tissue_models/index.md
- osipi.tofts: references/models/tissue_models/tofts.md
- osipi.extended_tofts: references/models/tissue_models/extended_tofts.md
- osipi.two_compartment_exchange: references/models/tissue_models/2cxm.md

- Examples: generated/gallery

Expand Down
3 changes: 2 additions & 1 deletion src/osipi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

from ._tissue import (
tofts,
extended_tofts
extended_tofts,
two_compartment_exchange_model
)

from ._signal import (
Expand Down
197 changes: 197 additions & 0 deletions src/osipi/_tissue.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import numpy as np
from scipy.interpolate import interp1d
from scipy.signal import convolve

from ._convolution import exp_conv

Expand Down Expand Up @@ -73,6 +74,22 @@ def tofts(
>>> plt.plot(t, ca, "r", t, ct, "b")

"""

# Input validation
if t.size != ca.size:
raise ValueError(f"t and ca must have the same size, got {t.size} and {ca.size}")

for param, name in [(Ktrans, "Ktrans"), (ve, "ve"), (Ta, "Ta")]:
if not (np.isscalar(param) and np.isreal(param)):
raise TypeError(f"{name} must be a numeric scalar value, got {type(param).__name__}")

if Ktrans < 0:
raise ValueError(f"Ktrans must be non-negative, got {Ktrans}")
if not (0 <= ve <= 1):
raise ValueError(f"ve must be in range [0, 1], got {ve}")
if Ta < 0:
raise ValueError(f"Ta must be non-negative, got {Ta}")

if not np.allclose(np.diff(t), np.diff(t)[0]):
warnings.warn(
("Non-uniform time spacing detected. Time array may be" " resampled."),
Expand Down Expand Up @@ -237,6 +254,24 @@ def extended_tofts(
>>> plt.plot(t, ca, "r", t, ct, "b")

"""
# Input validation
if t.size != ca.size:
raise ValueError(f"t and ca must have the same size, got {t.size} and {ca.size}")

for param, name in [(Ktrans, "Ktrans"), (ve, "ve"), (vp, "vp")]:
if not (np.isscalar(param) and np.isreal(param)):
raise TypeError(f"{name} must be a numeric scalar value, got {type(param).__name__}")

if not (0 <= ve <= 1):
raise ValueError(f"ve must be in range [0, 1], got {ve}")
if not (0 <= vp <= 1):
raise ValueError(f"vp must be in range [0, 1], got {vp}")
if not (0 <= ve + vp <= 1):
raise ValueError(f"Sum of ve ({ve}) and vp ({vp}) exceeds 1")
if Ta < 0:
raise ValueError(f"Ta must be non-negative, got {Ta}")
if Ktrans < 0:
raise ValueError(f"Ktrans must be non-negative, got {Ktrans}")

if not np.allclose(np.diff(t), np.diff(t)[0]):
warnings.warn(
Expand Down Expand Up @@ -332,3 +367,165 @@ def extended_tofts(
ct = ct_func(t)

return ct


def two_compartment_exchange_model(
t: np.ndarray,
ca: np.ndarray,
Fp: float,
PS: float,
ve: float,
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). [OSIPI code is Q.GE1.004]
ca: Arterial concentrations in mM for each time point in t. [OSIPI code is Q.IC1.001.[a]]
Fp: Blood plasma flow rate into a unit tissue volume in ml/min. [OSIPI code is Q.PH1.002]
PS: Permeability surface area product in ml/min. [OSIPI code is Q.PH1.004]
ve: Extracellular volume fraction. [OSIPI code Q.PH1.001.[e]]
vp: Plasma volume fraction. [OSIPI code Q.PH1.001.[p]]
Ta: Arterial delay time, i.e.,
difference in onset time between tissue curve and AIF in units of sec. [OSIPI code Q.PH1.007]

Returns:
Ct: Tissue concentrations in mM

See Also:
`extended_tofts`

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

Example:
Create an array of time points covering 6 min in steps of 1 sec,
calculate the Parker AIF at these time points, calculate tissue concentrations
using the Two Compartment Exchange model and plot the results.

>>> import matplotlib.pyplot as plt
>>> import osipi

Calculate AIF

>>> t = np.arange(0, 6 * 60, 0.1)
>>> ca = osipi.aif_parker(t)

Plot the tissue concentrations for an extracellular volume fraction
of 0.2, plasma volume fraction of 0.1, permeability serface area of 5 ml/min
and flow rate of 10 ml/min

>>> PS = 5 # Permeability surface area product in ml/min
>>> Fp = 10 # Flow rate in ml/min
>>> ve = 0.2 # Extracellular volume fraction
>>> vp = 0.1 # Plasma volume fraction
>>> ct = osipi.two_compartment_exchange_model(t, ca, Fp, PS, ve, vp)
>>> plt.plot(t, ca, "r", t, ct, "g")

"""
# Input validation
if t.size != ca.size:
raise ValueError(f"t and ca must have the same size, got {t.size} and {ca.size}")

for param, name in [(Fp, "Fp"), (PS, "PS"), (ve, "ve"), (vp, "vp")]:
if not (np.isscalar(param) and np.isreal(param)):
raise TypeError(f"{name} must be a numeric scalar value, got {type(param).__name__}")

if Fp <= 0:
raise ValueError(f"Fp must be positive, got {Fp}")
if PS < 0:
raise ValueError(f"PS must be non-negative, got {PS}")
if not (0 <= ve <= 1):
raise ValueError(f"ve must be in range [0, 1], got {ve}")
if not (0 <= vp <= 1):
raise ValueError(f"vp must be in range [0, 1], got {vp}")
if not (0 <= ve + vp <= 1):
raise ValueError(f"Sum of ve ({ve}) and vp ({vp}) exceeds 1")

if vp == 0:
E = 1 - np.exp(-PS / Fp)
Ktrans = E * Fp
return tofts(t, ca, Ktrans, ve, Ta, discretization_method="conv")

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 * 100.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

upsample_factor = 1
n = t.size
n_upsample = (n - 1) * upsample_factor + 1
t_upsample = np.linspace(t[0], t[-1], n_upsample)
tau_upsample = t_upsample - t[0]

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

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

irf_cp = (
vp
* sig_p
* sig_n
* (
(1 - te * sig_n) * np.exp(-tau_upsample * sig_n)
+ (te * sig_p - 1.0) * np.exp(-tau_upsample * sig_p)
)
/ (sig_p - sig_n)
)

irf_ce = (
ve
* sig_p
* sig_n
* (np.exp(-tau_upsample * sig_n) - np.exp(-tau_upsample * sig_p))
/ (sig_p - sig_n)
)

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

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

if Ta != 0:
f = interp1d(
t,
ca,
kind="linear",
bounds_error=False,
fill_value=0,
)
ca = (t > Ta) * f(t - Ta)

# 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)]

t_upsample = np.linspace(t[0], t[-1], n_upsample)

Cp = np.interp(t, t_upsample, Cp)
Ce = np.interp(t, t_upsample, Ce)

# get tissue concentration
Ct = Cp + Ce
return Ct
Loading