from copy import deepcopy
import matplotlib.pyplot as plt
import numpy as np
import dcmri.ui as ui
import dcmri.lib as lib
import dcmri.sig as sig
import dcmri.utils as utils
import dcmri.pk_aorta as pk_aorta
import dcmri.pk as pk
import dcmri.liver as liver
[docs]
class AortaPortalLiver(ui.Model):
"""Joint model for aorta, portal vein and liver signals.
This model uses a whole-body model to simultaneously predict signals in
aorta, portal vein and liver.
For more detail on the whole-body model, see :ref:`whole-body-tissues`.
For more detail on the liver model, see :ref:`liver-tissues`.
Args:
kinetics (str, optional): Tracer-kinetic liver model. See table
:ref:`table-liver-models` for options - only dual-inlet models
are allowed. Defaults to '2I-EC'.
stationary (str, optional): For intracellular tracers - stationarity
regime of the hepatocytes. The options are 'UE', 'E', 'U' or None.
For more detail see :ref:`liver-tissues`. Defaults to 'UE'.
sequence (str, optional): imaging sequence. Possible values are 'SS'
and 'SSI' (steady-state with aortic inflow correction). Defaults
to 'SS'.
free (dict, optional): Dictionary with free parameters and their
bounds. If not provided, a default set of free parameters is used.
Defaults to None.
params (dict, optional): values for the parameters of the tissue,
specified as keyword parameters. Defaults are used for any that are
not provided. See tables :ref:`AortaLiver-parameters` and
:ref:`AortaLiver-defaults` for a list of parameters and their
default values.
See Also:
`AortaLiver`
Example:
Use the model to reconstruct concentrations from experimentally
derived signals.
.. plot::
:include-source:
:context: close-figs
>>> import matplotlib.pyplot as plt
>>> import dcmri as dc
Use `fake_tissue` to generate synthetic test data from
experimentally-derived concentrations:
Use `fake_liver` to generate synthetic test data:
>>> time, aif, vif, roi, _ = dc.fake_liver(sequence='SSI')
Since this model generates 3 time curves, the x- and y-data are
tuples:
>>> xdata, ydata = (time, time, time), (aif, vif, roi)
Build an aorta-portal-liver model and parameters to match the
conditions of the fake liver data:
>>> model = dc.AortaPortalLiver(
... kinetics = '2I-IC',
... sequence = 'SSI',
... dt = 0.5,
... tmax = 180,
... weight = 70,
... agent = 'gadoxetate',
... dose = 0.2,
... rate = 3,
... field_strength = 3.0,
... t0 = 10,
... TR = 0.005,
... FA = 15,
... TS = 0.5,
... )
Train the model on the data:
>>> model.train(xdata, ydata, xtol=1e-3)
Plot the reconstructed signals and concentrations and compare
against the experimentally derived data:
>>> model.plot(xdata, ydata)
We can also have a look at the model parameters after training:
>>> model.print_params(round_to=3)
--------------------------------
Free parameters with their stdev
--------------------------------
First bolus arrival time (BAT): 14.616 (1.1) sec
Cardiac output (CO): 100.09 (2.736) mL/sec
Heart-lung mean transit time (Thl): 14.402 (1.375) sec
Heart-lung dispersion (Dhl): 0.391 (0.013)
Organs blood mean transit time (To): 27.811 (5.291) sec
Organs extraction fraction (Eo): 0.29 (0.105)
Organs extravascular mean transit time (Toe): 70.621 (102.614) sec
Body extraction fraction (Eb): 0.013 (0.23)
Aorta inflow time (TF): 0.409 (0.014) sec
Liver extracellular volume fraction (ve): 0.479 (0.112) mL/cm3
Liver plasma flow (Fp): 0.018 (0.001) mL/sec/cm3
Arterial flow fraction (fa): 0.087 (0.074)
Arterial transit time (Ta): 2.398 (1.356) sec
Hepatocellular uptake rate (khe): 0.006 (0.003) mL/sec/cm3
Hepatocellular mean transit time (Th): 683.604 (2554.75) sec
Gut mean transit time (Tg): 10.782 (0.614) sec
Gut dispersion (Dg): 0.893 (0.07)
----------------------------
Fixed and derived parameters
----------------------------
Hematocrit (H): 0.45
Arterial venous blood flow (Fa): 0.002 mL/sec/cm3
Portal venous blood flow (Fv): 0.016 mL/sec/cm3
Extracellular mean transit time (Te): 27.216 sec
Biliary tissue excretion rate (Kbh): 0.001 mL/sec/cm3
Hepatocellular tissue uptake rate (Khe): 0.012 mL/sec/cm3
Biliary excretion rate (kbh): 0.001 mL/sec/cm3
Notes:
Table :ref:`AortaPortalLiver-parameters` lists the parameters that are
relevant in each regime. Table :ref:`AortaPortalLiver-defaults` list
all possible parameters and their default settings.
.. _AortaPortalLiver-parameters:
.. list-table:: **Aorta-Liver parameters**
:widths: 20 30 30
:header-rows: 1
* - Parameters
- When to use
- Further detail
* - dt, tmax
- Always
- Time axis for forward model
* - t0, dose_tolerance
- Always
- For estimating baseline signal
* - field_strength, weight, agent, dose, rate
- Always
- Injection protocol
* - R10a, R10l, S0a, S0v, S0l
- Always
- Precontrast R1 (:ref:`relaxation-params`) and
S0 (:ref:`params-per-sequence`)for aorta and liver
* - FA, TR, TS
- Always
- :ref:`params-per-sequence`
* - TF
- If **sequence** is 'SSI'
- To model aorta inflow effects
* - BAT, CO, Thl, Dhl, To, Eo, Tie, Eb
- Always
- :ref:`whole-body-tissues`
* - Tg , Dg
- Always
- Gut dispersion
* - H, ve, Fp, fa, Ta, Tg, khe, khe_i, kh_f, Th, Th_i, Th_f.
- Depends on **kinetics** and **stationary**
- :ref:`table-liver-models`
.. _AortaPortalLiver-defaults:
.. list-table:: **Aorta-Liver parameter defaults**
:widths: 5 10 10 10 10
:header-rows: 1
* - Parameter
- Type
- Value
- Bounds
- Free/Fixed
* -
- **Simulation**
-
-
-
* - dt
- Simulation
- 0.5
- [0, inf]
- Fixed
* - tmax
- Simulation
- 120
- [0, inf]
- Fixed
* - dose_tolerance
- Simulation
- 0.1
- [0, 1]
- Fixed
* -
- **Injection**
-
-
-
* - field_strength
- Injection
- 3
- [0, inf]
- Fixed
* - weight
- Injection
- 70
- [0, inf]
- Fixed
* - agent
- Injection
- 'gadoxetate'
- None
- Fixed
* - dose
- Injection
- 0.0125
- [0, inf]
- Fixed
* - rate
- Injection
- 1
- [0, inf]
- Fixed
* -
- **Signal**
-
-
-
* - R10a
- Signal
- 0.7
- [0, inf]
- Fixed
* - R10l
- Signal
- 0.7
- [0, inf]
- Fixed
* - S0a
- Signal
- 1
- [0, inf]
- Free
* - S0v
- Signal
- 1
- [0, inf]
- Free
* - S0l
- Signal
- 1
- [0, inf]
- Free
* -
- **Sequence**
-
-
-
* - FA
- Sequence
- 15
- [0, inf]
- Fixed
* - S0
- Sequence
- 1
- [0, inf]
- Fixed
* - TF
- Sequence
- 0.5
- [0, 10]
- Free
* - TR
- Sequence
- 0.005
- [0, inf]
- Fixed
* - TS
- Sequence
- 0
- [0, inf]
- Fixed
* -
- **Whole body**
-
-
-
* - BAT
- Whole body
- 1200
- [0, inf]
- Free
* - CO
- Whole body
- 100
- [0, inf]
- Free
* - Thl
- Whole body
- 10
- [0, 30]
- Free
* - Dhl
- Whole body
- 0.2
- [0.05, 0.95]
- Free
* - To
- Whole body
- 20
- [0, 60]
- Free
* - Eo
- Whole body
- 0.15
- [0, 0.5]
- Free
* - Toe
- Whole body
- 120
- [0, 800]
- Free
* - Eb
- Whole body
- 0.05
- [0.01, 0.15]
- Free
* -
- **Portal vein**
-
-
-
* - Tg
- Kinetic
- 15
- [0.1, 60]
- Free
* - Dg
- Kinetic
- 0.85
- [0, 1]
- Free
* - uv
- Kinetic
- 1
- [0, 1]
- Free
* -
- **Liver**
-
-
-
* - H
- Kinetic
- 0.45
- [0, 1]
- Fixed
* - ve
- Kinetic
- 0.3
- [0.01, 0.6]
- Free
* - Fp
- Kinetic
- 0.01
- [0, 0.1]
- Free
* - fa
- Kinetic
- 0.2
- [0, 0.1]
- Free
* - Ta
- Kinetic
- 0.5
- [0, 3]
- Free
* - khe
- Kinetic
- 0.003
- [0, 0.1]
- Free
* - khe_i
- Kinetic
- 0.003
- [0, 0.1]
- Free
* - khe_f
- Kinetic
- 0.003
- [0, 0.1]
- Free
* - Th
- Kinetic
- 1800
- [600, 36000]
- Free
* - Th_i
- Kinetic
- 1800
- [600, 36000]
- Free
* - Th_f
- Kinetic
- 1800
- [600, 36000]
- Free
* - vol
- Kinetic
- 1000
- [0, 10000]
- Free
"""
def __init__(self, kinetics='2I-EC', stationary='UE',
sequence='SS', free=None, **params):
# Configuration
self.organs = '2cxm'
self.kinetics = kinetics
self.sequence = sequence
self.stationary = stationary
self._check_config()
self._set_defaults(free=free, **params)
# Internal flags
self._predict = None
def _check_config(self):
if self.sequence not in ['SS', 'SSI']:
raise ValueError(
'Sequence ' + str(self.sequence) + ' is not available.')
if self.kinetics[0] != '2':
raise ValueError('Only dual-inlet models are allowed.')
liver.params_liver(self.kinetics, self.stationary)
def _params(self):
return (PARAMS | PARAMS_WHOLE_BODY | PARAMS_SEQUENCE
| PARAMS_LIVER | PARAMS_PORTAL | PARAMS_DERIVED)
def _model_pars(self):
pars_sequence = {
'SS': ['FA', 'TR', 'TS'],
'SSI': ['TF', 'FA', 'TR', 'TS'],
}
pars = list(PARAMS.keys())
pars += list(PARAMS_WHOLE_BODY.keys())
pars += pars_sequence[self.sequence]
pars += liver.params_liver(self.kinetics, self.stationary)
pars += list(PARAMS_PORTAL.keys())
pars += ['vol']
return pars
def _par_values(self, kin=False, export=False, seq=None):
if seq is not None:
pars = {
'SS': ['FA', 'TR'],
'SSI': ['TF', 'FA', 'TR'],
}
pars = pars[self.sequence]
return {par: getattr(self, par) for par in pars}
if kin:
pars = liver.params_liver(self.kinetics, self.stationary)
return {par: getattr(self, par) for par in pars}
if export:
pars = self._par_values()
all = self._model_pars()
p1 = liver.params_liver(self.kinetics, self.stationary)
p2 = list(PARAMS_WHOLE_BODY.keys())
p3 = list(PARAMS_DERIVED.keys())
p4 = list(PARAMS_PORTAL.keys())
retain = p1 + p2 + p3 + p4 + ['TF']
discard = set(all) - set(retain)
return {p: pars[p] for p in pars if p not in discard}
pars = self._model_pars()
p = {par: getattr(self, par) for par in pars}
try:
p['Fa'] = p['Fp']*p['fa']
except KeyError:
pass
try:
p['Fv'] = p['Fp']*(1-p['fa'])
except KeyError:
pass
try:
p['Te'] = _div(p['ve'], p['Fp'])
except KeyError:
pass
try:
p['Th'] = np.mean([p['Th_i'], p['Th_f']])
except KeyError:
pass
try:
p['khe'] = np.mean([p['khe_i'], p['khe_f']])
except KeyError:
pass
try:
p['Kbh'] = _div(1, p['Th'])
except KeyError:
pass
try:
p['Khe'] = _div(p['khe'], p['ve'])
except KeyError:
pass
try:
p['kbh'] = _div(1-p['ve'], p['Th'])
except KeyError:
pass
try:
p['kbh_i'] = _div(1-p['ve'], p['Th_i'])
except KeyError:
pass
try:
p['kbh_f'] = _div(1-p['ve'], p['Th_f'])
except KeyError:
pass
try:
p['E'] = p['khe']/(p['khe']+p['Fp'])
except KeyError:
try:
p['E'] = p['khe']/(p['khe']+self.Fp)
except:
pass
try:
p['Ktrans'] = (1-p['E'])*p['khe']
except KeyError:
pass
if p['vol'] is not None:
try:
p['CL'] = p['khe']*p['vol']
except KeyError:
pass
return p
def _conc_aorta(self) -> np.ndarray:
if self.organs == 'comp':
organs = ['comp', (self.To,)]
else:
organs = ['2cxm', ([self.To, self.Toe], self.Eo)]
self.t = np.arange(0, self.tmax, self.dt)
conc = lib.ca_conc(self.agent)
Ji = lib.ca_injection(
self.t, self.weight, conc, self.dose, self.rate, self.BAT)
Jb = pk_aorta.flux_aorta(
Ji, E=self.Eb, dt=self.dt, tol=self.dose_tolerance,
heartlung = ['pfcomp', (self.Thl, self.Dhl)],
organs = organs)
self.ca = Jb/self.CO
return self.t, self.ca
def _relax_aorta(self):
t, cb = self._conc_aorta()
rb = lib.relaxivity(self.field_strength, 'blood', self.agent)
return t, self.R10a + rb*cb
def _predict_aorta(self, xdata: np.ndarray) -> np.ndarray:
self.tmax = max(xdata)+self.dt
if self.TS is not None:
self.tmax += self.TS
t, R1b = self._relax_aorta()
if self.sequence == 'SSI':
signal = sig.signal_spgr(
self.S0a, R1b, self.TF, self.TR, self.FA, n0=1)
else:
signal = sig.signal_ss(self.S0a, R1b, self.TR, self.FA)
return utils.sample(xdata, t, signal, self.TS)
def _conc_portal(self):
self.cv = pk.flux_chain(self.ca, self.Tg, self.Dg, dt=self.dt)
return self.cv
def _relax_portal(self):
rb = lib.relaxivity(self.field_strength, 'blood', self.agent)
cv = self._conc_portal()
return self.R10a + rb*self.uv*cv
def _predict_portal(self, xdata):
t = np.arange(0, self.tmax, self.dt)
R1v = self._relax_portal()
signal = sig.signal_ss(self.S0v, R1v, self.TR, self.FA)
return utils.sample(xdata, t, signal, self.TS)
def _conc_liver(self, sum=True):
pars = self._par_values(kin=True)
return liver.conc_liver(
self.ca, dt=self.dt, sum=sum, cv=self.cv, **pars)
def _relax_liver(self):
t = np.arange(0, self.tmax, self.dt)
Cl = self._conc_liver(sum=False)
rp = lib.relaxivity(self.field_strength, 'plasma', self.agent)
rh = lib.relaxivity(self.field_strength, 'hepatocytes', self.agent)
if 'IC' in self.kinetics:
return t, self.R10l + rp*Cl[0, :] + rh*Cl[1, :]
else:
return t, self.R10l + rp*Cl
def _predict_liver(self, xdata):
t, R1l = self._relax_liver()
signal = sig.signal_ss(self.S0l, R1l, self.TR, self.FA)
return utils.sample(xdata, t, signal, self.TS)
[docs]
def conc(self, sum=True):
"""Concentrations in aorta. portal vein and liver.
Args:
sum (bool, optional): If set to true, the liver concentrations are
the sum over both compartments. If set to false, the
compartmental concentrations are returned individually.
Defaults to True.
Returns:
tuple: time points, aorta blood concentrations, portal-venous
blood concentrations, liver concentrations.
"""
t, cb = self._conc_aorta()
cv = self._conc_portal()
C = self._conc_liver(sum=sum)
return t, cb, cv, C
[docs]
def relax(self):
"""Relaxation rates in aorta, portal vein and liver.
Returns:
tuple: time points, aorta blood R1, portal-venous blood R1, liver
blood R1.
"""
t, R1b = self._relax_aorta()
R1v = self._relax_portal()
t, R1l = self._relax_liver()
return t, R1b, R1v, R1l
[docs]
def predict(self, time: tuple) -> tuple:
"""Predict the signals at given time
Args:
xdata (tuple): tuple of 3 arrays with time points for aorta,
portal vein and liver, in that order. The 3 arrays can be
different in length and value.
Returns:
tuple: tuple of 3 arrays with signals for aorta, portal vein and
liver, in that order. The arrays can be different in length and
value but each has to have the same length as its corresponding
array of time points.
"""
# Public interface
if self._predict is None:
signala = self._predict_aorta(time[0])
signalv = self._predict_portal(time[1])
signall = self._predict_liver(time[2])
return signala, signalv, signall
# Private interface with different input & output types
elif self._predict == 'aorta':
return self._predict_aorta(time)
elif self._predict == 'portal':
return self._predict_portal(time)
elif self._predict == 'liver':
return self._predict_liver(time)
[docs]
def train(self, time: tuple, signal: tuple, **kwargs):
"""Train the free parameters
Args:
time (tuple): tuple of 3 arrays with time points for aorta,
portal vein and liver, in that order. The arrays can be
different in length and value.
signal (array-like): tuple of 3 arrays with signals for aorta,
portal vein and liver, in that order. The arrays can be
different in length and value but each has to have the same
length as its corresponding array of time points.
kwargs: any keyword parameters accepted by
`scipy.optimize.curve_fit`.
Returns:
AortaPortalLiver: the trained model
"""
# Estimate BAT and S0a from data
pars = self._par_values(seq=self.sequence)
if self.sequence == 'SSI':
Srefa = sig.signal_spgr(1, self.R10a, self.TF, self.TR, self.FA, n0=1)
else:
Srefa = sig.signal_ss(1, self.R10a, self.TR, self.FA)
Srefv = sig.signal_ss(1, self.R10a, self.TR, self.FA)
Srefl = sig.signal_ss(1, self.R10l, self.TR, self.FA)
n0 = max([np.sum(time[0] < self.t0), 1])
self.S0a = np.mean(signal[0][:n0]) / Srefa
self.S0v = np.mean(signal[1][:n0]) / Srefv
self.S0l = np.mean(signal[2][:n0]) / Srefl
self.BAT = time[0][np.argmax(signal[0])] - (1-self.Dhl)*self.Thl
self.BAT = max([self.BAT, 0])
# Copy the original free parameters to restore later
free = deepcopy(self.free)
# Train free aorta parameters on aorta data
self._predict = 'aorta'
pars = list(PARAMS_WHOLE_BODY.keys())
self.free = {s: free[s] for s in pars if s in free}
ui.train(self, time[0], signal[0], **kwargs)
# Train free aorta parameters on portal venous data
self._predict = 'portal'
pars = list(PARAMS_PORTAL.keys())
self.free = {s: free[s] for s in pars if s in free}
ui.train(self, time[1], signal[1], **kwargs)
# Train free liver parameters on liver data
self._predict = 'liver'
pars = list(PARAMS_LIVER.keys())
self.free = {s: free[s] for s in pars if s in free}
ui.train(self, time[2], signal[2], **kwargs)
# Train all parameters on all data
self._predict = None
self.free = free
return ui.train(self, time, signal, **kwargs)
[docs]
def plot(self,
time: tuple,
signal: tuple,
xlim=None, ref=None,
fname=None, show=True):
"""Plot the model fit against data
Args:
time (tuple): tuple of 3 arrays with time points for aorta,
portal vein and liver, in that order. The two arrays can be
different in length and value.
signal (array-like): tuple of 3 arrays with signals for aorta,
portal vein and liver, in that order. The arrays can be
different in length and value but each has to have the same
length as its corresponding array of time points.
xlim (array_like, optional): 2-element array with lower and upper
boundaries of the x-axis. Defaults to None.
ref (tuple, optional): Tuple of optional test data in the form
(x,y), where x is an array with x-values and y is an array with
y-values. Defaults to None.
fname (path, optional): Filepath to save the image. If no value
is provided, the image is not saved. Defaults to None.
show (bool, optional): If True, the plot is shown. Defaults to
True.
"""
t, cb, cv, C = self.conc(sum=False)
sig = self.predict((t, t, t))
fig, ((ax1, ax2), (ax3, ax4), (ax5, ax6)) = plt.subplots(3, 2, figsize=(10, 8))
fig.subplots_adjust(wspace=0.3)
_plot_data(t, sig[0], time[0], signal[0],
ax1, xlim,
color=['lightcoral', 'darkred'],
test=None if ref is None else ref[0])
_plot_data(t, sig[1], time[1], signal[1],
ax3, xlim,
color=['orchid', 'purple'],
test=None if ref is None else ref[1])
_plot_data(t, sig[2], time[2], signal[2],
ax5, xlim,
color=['cornflowerblue', 'darkblue'],
test=None if ref is None else ref[2],
xlabel='Time (min)')
_plot_conc_aorta(t, cb, ax2, xlim)
_plot_conc_portal(t, cv, ax4, xlim)
_plot_conc_liver(self, t, C, ax6, xlim)
if fname is not None:
plt.savefig(fname=fname)
if show:
plt.show()
else:
plt.close()
[docs]
def cost(self, time, signal, metric='NRMS') -> float:
"""Return the goodness-of-fit
Args:
time (tuple): tuple of 3 arrays with time points for aorta,
portal vein and liver, in that order. The arrays can be
different in length and value.
signal (array-like): tuple of 3 arrays with signals for aorta,
portal vein and liver, in that order. The arrays can be
different in length and value but each has to have the same
length as its corresponding array of time points.
metric (str, optional): Which metric to use (see notes for
possible values). Defaults to 'NRMS'.
Returns:
float: goodness of fit.
Notes:
Available options are:
- 'RMS': Root-mean-square.
- 'NRMS': Normalized root-mean-square.
- 'AIC': Akaike information criterion.
- 'cAIC': Corrected Akaike information criterion for small models.
- 'BIC': Bayesian information criterion.
"""
return super().cost(time, signal, metric)
# Helper functions for plotting
def _plot_conc_aorta(t, cb, ax, xlim=None):
if xlim is None:
xlim = [t[0], t[-1]]
ax.set(ylabel='Concentration (mM)',
xlim=np.array(xlim)/60)
ax.plot(t/60, 0*t, color='gray')
ax.plot(t/60, 1000*cb, linestyle='-',
color='darkred', linewidth=2.0, label='Aorta')
ax.legend()
def _plot_conc_portal(t, cv, ax, xlim=None):
if xlim is None:
xlim = [t[0], t[-1]]
ax.set(ylabel='Concentration (mM)',
xlim=np.array(xlim)/60)
ax.plot(t/60, 0*t, color='gray')
ax.plot(t/60, 1000*cv, linestyle='-',
color='purple', linewidth=2.0, label='Portal vein')
ax.legend()
def _plot_conc_liver(self, t, C, ax, xlim=None):
color = 'darkblue'
if xlim is None:
xlim = [t[0], t[-1]]
ax.set(xlabel='Time (min)', ylabel='Tissue concentration (mM)',
xlim=np.array(xlim)/60)
ax.plot(t/60, 0*t, color='gray')
if 'IC' in self.kinetics:
ax.plot(t/60, 1000*C[0, :], linestyle='-.',
color=color, linewidth=2.0, label='Extracellular')
ax.plot(t/60, 1000*C[1, :], linestyle='--',
color=color, linewidth=2.0, label='Hepatocytes')
ax.plot(t/60, 1000*(C[0, :]+C[1, :]), linestyle='-',
color=color, linewidth=2.0, label='Liver')
else:
ax.plot(t/60, 1000*C, linestyle='-',
color=color, linewidth=2.0, label='Liver')
ax.legend()
def _plot_data(t: np.ndarray, sig: np.ndarray,
xdata: np.ndarray, ydata: np.ndarray,
ax, xlim, color=['black', 'black'],
test=None, xlabel=None):
if xlim is None:
xlim = [t[0], t[-1]]
ax.set(xlabel=xlabel, ylabel='MR Signal (a.u.)',
xlim=np.array(xlim)/60)
ax.plot(xdata/60, ydata, marker='o',
color=color[0], label='fitted data', linestyle='None')
ax.plot(t/60, sig, linestyle='-',
color=color[1], linewidth=3.0, label='fit')
if test is not None:
ax.plot(np.array(test[0])/60, test[1], color='black',
marker='D', linestyle='None', label='Test data')
ax.legend()
PARAMS = {
# Prediction and training
't0': {
'init': 0,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Baseline duration',
'unit': 'sec',
},
'dt': {
'init': 0.5,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Forward model time step',
'unit': 'sec',
},
'tmax': {
'init': 120,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Maximum acquisition time',
'unit': 'sec',
},
'dose_tolerance': {
'init': 0.1,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Dose tolerance',
'unit': '',
},
# Injection
'field_strength': {
'init': 3.0,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Magnetic field strength',
'unit': 'T',
},
'weight': {
'init': 70.0,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Subject weight',
'unit': 'kg',
},
'agent': {
'init': 'gadoxetate',
'default_free': False,
'bounds': None,
'name': 'Contrast agent',
'unit': None,
},
'dose': {
'init': lib.ca_std_dose('gadoxetate')/2,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Contrast agent dose',
'unit': 'mL/kg',
},
'rate': {
'init': 1,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Contrast agent injection rate',
'unit': 'mL/sec',
},
# Signal
'R10a': {
'init': 1/lib.T1(3.0, 'blood'),
'default_free': False,
'bounds': [0, np.inf],
'name': 'Aorta baseline R1',
'unit': 'Hz',
},
'S0a': {
'init': 1,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Aorta signal scale factor',
'unit': 'a.u.',
},
'R10l': {
'init': 1/lib.T1(3.0, 'liver'),
'default_free': False,
'bounds': [0, np.inf],
'name': 'Liver baseline R1',
'unit': 'Hz',
},
'S0l': {
'init': 1,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Liver signal scale factor',
'unit': 'a.u.',
},
'S0v': {
'init': 1,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Portal venous signal scale factor',
'unit': 'a.u.',
},
}
PARAMS_WHOLE_BODY = {
# Body
'BAT': {
'init': 60,
'default_free': True,
'bounds': [0, np.inf],
'name': 'Bolus arrival time',
'unit': 'sec',
},
'CO': {
'init': 100,
'default_free': True,
'bounds': [0, 300],
'name': 'Cardiac output',
'unit': 'mL/sec',
},
'Thl': {
'init': 10,
'default_free': True,
'bounds': [0, 30],
'name': 'Heart-lung mean transit time',
'unit': 'sec',
},
'Dhl': {
'init': 0.2,
'default_free': True,
'bounds': [0.05, 0.95],
'name': 'Heart-lung dispersion',
'unit': '',
},
'To': {
'init': 20,
'default_free': True,
'bounds': [0, 60],
'name': 'Organs blood mean transit time',
'unit': 'sec',
},
'Eo': {
'init': 0.15,
'default_free': True,
'bounds': [0, 0.5],
'name': 'Organs extraction fraction',
'unit': '',
},
'Toe': {
'init': 120,
'default_free': True,
'bounds': [0, 800],
'name': 'Organs extravascular mean transit time',
'unit': 'sec',
},
'Eb': {
'init': 0.05,
'default_free': True,
'bounds': [0.01, 0.15],
'name': 'Body extraction fraction',
'unit': '',
},
}
PARAMS_SEQUENCE = {
'TR': {
'init': 0.005,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Repetition time',
'unit': 'sec',
},
'FA': {
'init': 15.0,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Flip angle',
'unit': 'deg',
},
'TF': {
'init': 0.5,
'default_free': True,
'bounds': [0, 5],
'name': 'Aorta inflow time',
'unit': 'sec',
},
'TS': {
'init': None,
'default_free': False,
'bounds': [0, np.inf],
'name': 'Sampling time',
'unit': 'sec',
},
}
PARAMS_PORTAL = {
'Tg': {
'init': 15.0,
'default_free': True,
'bounds': [0.1, 60],
'name': 'Gut mean transit time',
'unit': 'sec',
},
'Dg': {
'init': 0.85,
'default_free': True,
'bounds': [0, 1],
'name': 'Gut dispersion',
'unit': '',
},
'uv': {
'init': 1.0,
'default_free': True,
'bounds': [0, 1],
'name': 'Portal vein volume fraction',
'unit': '',
},
}
PARAMS_LIVER = {
'H': {
'init': 0.45,
'default_free': False,
'bounds': [0, 1],
'name': 'Hematocrit',
'unit': '',
},
've': {
'init': 0.3,
'default_free': True,
'bounds': [0.01, 0.6],
'name': 'Liver extracellular volume fraction',
'unit': 'mL/cm3',
},
'Fp': {
'init': 0.01,
'default_free': True,
'bounds': [0.00, 0.1],
'name': 'Liver plasma flow',
'unit': 'mL/sec/cm3',
},
'fa': {
'init': 0.2,
'default_free': True,
'bounds': [0.0, 1.0],
'name': 'Arterial flow fraction',
'unit': '',
},
'Ta': {
'init': 0.5,
'default_free': True,
'bounds': [0.0, 3],
'name': 'Arterial transit time',
'unit': 'sec',
},
'khe': {
'init': 0.003,
'default_free': True,
'bounds': [0.0, 0.1],
'name': 'Hepatocellular uptake rate',
'unit': 'mL/sec/cm3',
},
'khe_i': {
'init': 0.003,
'default_free': True,
'bounds': [0.0, 0.1],
'name': 'Initial hepatocellular uptake rate',
'unit': 'mL/sec/cm3',
'pixel_par': True,
},
'khe_f': {
'init': 0.003,
'default_free': True,
'bounds': [0.0, 0.1],
'name': 'Final hepatocellular uptake rate',
'unit': 'mL/sec/cm3',
'pixel_par': True,
},
'Th': {
'init': 30*60,
'default_free': True,
'bounds': [10*60, 10*60*60],
'name': 'Hepatocellular mean transit time',
'unit': 'sec',
},
'Th_i': {
'init': 30*60,
'default_free': True,
'bounds': [10*60, 10*60*60],
'name': 'Initial hepatocellular mean transit time',
'unit': 'sec',
'pixel_par': True,
},
'Th_f': {
'init': 30*60,
'default_free': True,
'bounds': [10*60, 10*60*60],
'name': 'Final hepatocellular mean transit time',
'unit': 'sec',
'pixel_par': True,
},
'vol': {
'init': None,
'default_free': False,
'bounds': [0, 10000],
'name': 'Liver volume',
'unit': 'cm3',
},
}
PARAMS_DERIVED = {
'Fv': {
'name': 'Portal venous blood flow',
'unit': 'mL/sec/cm3',
},
'Fa': {
'name': 'Arterial venous blood flow',
'unit': 'mL/sec/cm3',
},
'Te': {
'name': 'Extracellular mean transit time',
'unit': 'sec',
},
'kbh': {
'name': 'Biliary excretion rate',
'unit': 'mL/sec/cm3',
},
'kbh_i': {
'name': 'Initial biliary excretion rate',
'unit': 'mL/sec/cm3',
},
'kbh_f': {
'name': 'Final biliary excretion rate',
'unit': 'mL/sec/cm3',
},
'Kbh': {
'name': 'Biliary tissue excretion rate',
'unit': 'mL/sec/cm3',
},
'Khe': {
'name': 'Hepatocellular tissue uptake rate',
'unit': 'mL/sec/cm3',
},
'E': {
'name': 'Liver extraction fraction',
'unit': '',
},
'Ktrans': {
'name': 'Hepatic plasma clearance',
'unit': 'mL/sec/cm3',
},
'CL': {
'name': 'Liver blood clearance',
'unit': 'mL/sec',
},
}
def _div(a, b):
with np.errstate(divide='ignore', invalid='ignore'):
return np.divide(a, b)