Source code for dcmri.lib

import sys
import pickle

import numpy as np


import dcmri.pk as pk


# filepaths need to be identified with importlib_resources
# rather than __file__ as the latter does not work at runtime
# when the package is installed via pip install

if sys.version_info < (3, 9):
    # importlib.resources either doesn't exist or lacks the files()
    # function, so use the PyPI version:
    import importlib_resources
else:
    # importlib.resources has files(), so use that:
    import importlib.resources as importlib_resources


[docs] def fetch(dataset: str) -> dict: """Fetch a dataset included in dcmri Args: dataset (str): name of the dataset. See below for options. Returns: dict: Data as a dictionary. Notes: The following datasets are currently available: **tristan_rifampicin** **Background**: data are provided by the liver work package of the `TRISTAN project <https://www.imi-tristan.eu/liver>`_ which develops imaging biomarkers for drug safety assessment. The data and analysis was first presented at the ISMRM in 2024 (Min et al 2024, manuscript in press). The data were acquired in the aorta and liver of 10 healthy volunteers with dynamic gadoxetate-enhanced MRI, before and after administration of a drug (rifampicin) which is known to inhibit liver function. The assessments were done on two separate visits at least 2 weeks apart. On each visit, the volunteer had two scans each with a separate contrast agent injection of a quarter dose each. the scans were separated by a gap of about 1 hour to enable gadoxetate to clear from the liver. This design was deemed necessary for reliable measurement of excretion rate when liver function was inhibited. The research question was to what extent rifampicin inhibits gadoxetate uptake rate from the extracellular space into the liver hepatocytes (khe, mL/min/100mL) and excretion rate from hepatocytes to bile (kbh, mL/100mL/min). 2 of the volunteers only had the baseline assessment, the other 8 volunteers completed the full study. The results showed consistent and strong inhibition of khe (95%) and kbh (40%) by rifampicin. This implies that rifampicin poses a risk of drug-drug interactions (DDI), meaning it can cause another drug to circulate in the body for far longer than expected, potentially causing harm or raising a need for dose adjustment. **Data format**: The fetch function returns a list of dictionaries, one per subject visit. Each dictionary contains the following items: - **time1aorta**: array of signals in arbitrary units, for the aorta in the first scan. - **time2aorta**: array of signals in arbitrary units, for the aorta in the second scan. - **time1liver**: array of signals in arbitrary units, for the liver in the first scan. - **time2liver**: array of signals in arbitrary units, for the liver in the second scan. - **signal1aorta**: array of signals in arbitrary units, for the aorta in the first scan. - **signal2aorta**: array of signals in arbitrary units, for the aorta in the second scan. - **signal1liver**: array of signals in arbitrary units, for the liver in the first scan. - **signal2liver**: array of signals in arbitrary units, for the liver in the second scan. - **weight**: subject weight in kg. - **agent**: contrast agent generic name (str). - **dose**: 2-element list with contrast agent doses of first scan and second scan in mL/kg. - **rate**: contrast agent injection rate in mL/sec. - **FA**: Flip angle in degrees - **TR**: repretition time in sec - **t0**: baseline length in subject - **subject**: Volunteer number. - **visit**: either 'baseline' or 'rifampicin'. - **field_strength**: B0-field of scanner. - **R10b**: precontrast R1 of blood (1st scan). - **R10l**: precontrast R1 of liver (1st scan). - **R102b**: precontrast R1 of blood (2nd scan). - **R102l**: precontrast R1 of liver (2nd scan). - **Hct**: hematocrit. - **vol**: liver volume in mL. Please reference the following abstract when using these data: Thazin Min, Marta Tibiletti, Paul Hockings, Aleksandra Galetin, Ebony Gunwhy, Gerry Kenna, Nicola Melillo, Geoff JM Parker, Gunnar Schuetz, Daniel Scotcher, John Waterton, Ian Rowe, and Steven Sourbron. *Measurement of liver function with dynamic gadoxetate- enhanced MRI: a validation study in healthy volunteers*. Proc Intl Soc Mag Reson Med, Singapore 2024. **tristan_gothenburg** **Background**: The data aimed to demonstrates the effect of rifampicin on liver function of patients with impaired function. The data are provided by the liver work package of the `TRISTAN project <https://www.imi-tristan.eu/liver>`_ which develops imaging biomarkers for drug safety assessment. The data were acquired in the aorta and liver in 3 patients with dynamic gadoxetate-enhanced MRI. The study participants take rifampicin as part of their routine clinical workup, with an aim to promote their liver function. For this study, they were taken off rifampicin 3 days before the first scan, and placed back on rifampicin 3 days before the second scan. The aim was to determine the effect if rifampicin in uptake and excretion function of the liver. The data confirmed that patients had significantly reduced uptake and excretion function in the absence of rifampicin. Rifampicin adminstration promoted their excretory function but had no effect on their uptake function. **Data format**: The fetch function returns a list of dictionaries, one per subject visit. Each dictionary contains the following items: - **time1aorta**: array of signals in arbitrary units, for the aorta in the first scan. - **time2aorta**: array of signals in arbitrary units, for the aorta in the second scan. - **time1liver**: array of signals in arbitrary units, for the liver in the first scan. - **time2liver**: array of signals in arbitrary units, for the liver in the second scan. - **signal1aorta**: array of signals in arbitrary units, for the aorta in the first scan. - **signal2aorta**: array of signals in arbitrary units, for the aorta in the second scan. - **signal1liver**: array of signals in arbitrary units, for the liver in the first scan. - **signal2liver**: array of signals in arbitrary units, for the liver in the second scan. - **weight**: subject weight in kg. - **agent**: contrast agent generic name (str). - **dose**: 2-element list with contrast agent doses of first scan and second scan in mL/kg. - **rate**: contrast agent injection rate in mL/sec. - **FA**: Flip angle in degrees - **TR**: repretition time in sec - **t0**: baseline length in subject - **subject**: Volunteer number. - **visit**: either 'control' or 'drug'. - **field_strength**: B0-field of scanner. - **R10b**: precontrast R1 of blood (1st scan). - **R10l**: precontrast R1 of liver (1st scan). - **R102b**: precontrast R1 of blood (2nd scan). - **R102l**: precontrast R1 of liver (2nd scan). - **Hct**: hematocrit. - **vol**: liver volume in mL. **tristan6drugs** **Background**: data are provided by the liver work package of the `TRISTAN project <https://www.imi-tristan.eu/liver>`_ which develops imaging biomarkers for drug safety assessment. The data and analysis were first published in Melillo et al (2023). The study presented here measured gadoxetate uptake and excretion in healthy rats before and after injection of 6 test drugs (up to 6 rats per drug). Studies were performed in preclinical MRI scanners at 3 different centers and 2 different field strengths. Results demonstrated that two of the tested drugs (rifampicin and cyclosporine) showed strong inhibition of both uptake and excretion. One drug (ketoconazole) inhibited uptake but not excretion. Three drugs (pioglitazone, bosentan and asunaprevir) inhibited excretion but not uptake. **Data format**: The fetch function returns a list of dictionaries, one per scan. Each dictionary contains the following items: - **time**: array of time points in sec - **spleen**: array of spleen signals in arbitrary units - **liver**: array of liver signals in arbitrary units. - **FA**: Flip angle in degrees - **TR**: repretition time in sec - **n0**: number of precontrast acquisitions - **study**: an integer identifying the substudy the scan was taken in - **subject**: a study-specific identifier of the subject in the range 1-6. - **visit**: either 1 (baseline) or 2 (drug or vehicle/saline). - **center**: center wehere the study was performed, either E, G or D. - **field_strength**: B0-field of scanner on whuch the study was performed - **substance**: what was injected, eg. saline, vehicle or drug name. - **BAT**: Bolus arrival time - **duration**: duration on the injection in sec. Please reference the following paper when using these data: Melillo N, Scotcher D, Kenna JG, Green C, Hines CDG, Laitinen I, Hockings PD, Ogungbenro K, Gunwhy ER, Sourbron S, et al. Use of In Vivo Imaging and Physiologically-Based Kinetic Modelling to Predict Hepatic Transporter Mediated Drug–Drug Interactions in Rats. Pharmaceutics. 2023; 15(3):896. `[DOI] <https://doi.org/10.3390/pharmaceutics15030896>`_ The data were first released as supplementary material in csv format with this paper on Zenodo. Use this DOI to reference the data themselves: Gunwhy, E. R., & Sourbron, S. (2023). TRISTAN-RAT (v3.0.0). `Zenodo <https://doi.org/10.5281/zenodo.8372595>`_ **tristan_repro** **Background**: data are provided by the liver work package of the `TRISTAN project <https://www.imi-tristan.eu/liver>`_ which develops imaging biomarkers for drug safety assessment. The data and analysis were first published in Gunwhy et al (2024). The study presented here aimed to determine the repreducibility and rpeatability of gadoxetate uptake and excretion measurements in healthy rats. Data were acquired in different centers and field strengths to identify contributing factors. Some of the studies involved repeat scans in the same subject. In some studies data on the second day were taken after adminstration of a drug (rifampicin) to test if effect sizes were reproducible. **Data format**: The fetch function returns a list of dictionaries, one per scan. The dictionaries in the list contain the following items: - **time**: array of time points in sec - **spleen**: array of spleen signals in arbitrary units - **liver**: array of liver signals in arbitrary units. - **FA**: Flip angle in degrees - **TR**: repretition time in sec - **n0**: number of precontrast acquisitions - **study**: an integer identifying the substudy the scan was taken in - **subject**: a study-specific identifier of the subject in the range 1-6. - **visit**: either 1 (baseline) or 2 (drug or vehicle/saline). - **center**: center wehere the study was performed, either E, G or D. - **field_strength**: B0-field of scanner on whuch the study was performed - **substance**: what was injected, eg. saline, vehicle or drug name. - **BAT**: Bolus arrival time - **duration**: duration on the injection in sec. Please reference the following paper when using these data: Ebony R. Gunwhy, Catherine D. G. Hines, Claudia Green, Iina Laitinen, Sirisha Tadimalla, Paul D. Hockings, Gunnar Schütz, J. Gerry Kenna, Steven Sourbron, and John C. Waterton. Assessment of hepatic transporter function in rats using dynamic gadoxetate- enhanced MRI: A reproducibility study. In review. The data were first released as supplementary material in csv format with this paper on Zenodo. Use this to reference the data themselves: Gunwhy, E. R., Hines, C. D. G., Green, C., Laitinen, I., Tadimalla, S., Hockings, P. D., Schütz, G., Kenna, J. G., Sourbron, S., & Waterton, J. C. (2023). Rat gadoxetate MRI signal dataset for the IMI-WP2-TRISTAN Reproducibility study [Data set]. `Zenodo. <https://doi.org/10.5281/zenodo.7838397>`_ **tristan_mdosing** **Background** These data were taken from a preclinical study which aimed to investigate the potential of gadoxetate-enhanced DCE-MRI to study acute inhibition of hepatocyte transporters of drug-induced liver injury (DILI) causing drugs, and to study potential changes in transporter function after chronic dosing. **Data format**: The fetch function returns a list of dictionaries, one per scan.The dictionaries in the list contain the following items: - **time**: array of time points in sec - **spleen**: array of spleen signals in arbitrary units - **liver**: array of liver signals in arbitrary units. - **FA**: Flip angle in degrees - **TR**: repretition time in sec - **n0**: number of precontrast acquisitions - **study**: an integer identifying the substudy the scan was taken in - **subject**: a study-specific identifier of the subject in the range 1-6. - **visit**: either 1 (baseline) or 2 (drug or vehicle/saline). - **center**: center wehere the study was performed, either E, G or D. - **field_strength**: B0-field of scanner on whuch the study was performed - **substance**: what was injected, eg. saline, vehicle or drug name. - **BAT**: Bolus arrival time - **duration**: duration on the injection in sec. Please reference the following abstract when using these data: Mikael Montelius, Steven Sourbron, Nicola Melillo, Daniel Scotcher, Aleksandra Galetin, Gunnar Schuetz, Claudia Green, Edvin Johansson, John C. Waterton, and Paul Hockings. Acute and chronic rifampicin effect on gadoxetate uptake in rats using gadoxetate DCE-MRI. Int Soc Mag Reson Med 2021; 2674. **kruk_sk_gfr** **Background**: data taken from supplementary material of Basak et al (2018), a study funded by Kidney Research UK. The dataset includes signal-time curves for aorta, left- and right kidney as well as sequence parameters and radio-isotope single kidney GFR values for 114 scans on 100 different subjects. This includes 14 subjects who have had a scan before and after revascularization treatment. **Data format**: The fetch function returns a list of dictionaries, one per scan. The dictionaries in the list contain the following items: - **subject**: unique study ID of the participant - **time**: array of time points in sec - **aorta**: signal curve in the aorta (arbitrary units) - **visit**: for participants that had just a single visit, this has the value 'single'. For those that had multiple visits, the value is either 'pre' (before treatment) or 'post' (after treatment). - **LK**: signal curve in the left kidney (arbitrary units). - **RK**: signal curve in the right kidney (arbitrary units). - **LK vol**: volume of the left kidney (mL). - **RK vol**: volume of the right kidney (mL). - **LK iso-SK-GFR**: radio-isotope SK-GFR for the left kidney (mL/min). - **RK iso-SK-GFR**: radio-isotope SK-GFR for the right kidney (mL/min). - **LK T1**: precontrast T1-value of the left kidney (sec). - **RK T1**: precontrast T1-value of the right kidney (sec). - **TR**: repetition time or time between rf-pulses (sec) - **FA**: flip angle (degrees) - **n0**: number of precontrast acquisitions. - **field_strength**: Magnetic field strength of the scanner (T) - **agent**: Contrast agent generic name - **dose**: contrast agent dose injected (mmol/kg) - **rate**: rate of contrast agent injection (mL/sec) - **weight**: participant weight (kg) Note: if data are missing for a particular scan, they will not be in the dictionary for that scan. For instance, if a participant does not have a right kidney, the items starting with *RK* are not present. Please reference the following paper when using these data: Basak S, Buckley DL, Chrysochou C, Banerji A, Vassallo D, Odudu A, Kalra PA, Sourbron SP. Analytical validation of single-kidney glomerular filtration rate and split renal function as measured with magnetic resonance renography. Magn Reson Imaging. 2019 Jun;59:53-60. doi: 10.1016/j.mri.2019.03.005. `[URL] <https://pubmed.ncbi.nlm.nih.gov/30849485/>`_. Example: Use the AortaLiver model to fit one of the **tristan_rifampicin** datasets: .. plot:: :include-source: :context: close-figs >>> import dcmri as dc Get the data for the baseline visit of the first subject in the study: >>> data = dc.fetch('tristan_rifampicin') >>> data = data[0] Initialize the AortaLiver model with the available data: >>> model = dc.AortaLiver( >>> # >>> # Injection parameters >>> # >>> weight = data['weight'], >>> agent = data['agent'], >>> dose = data['dose'][0], >>> rate = data['rate'], >>> # >>> # Acquisition parameters >>> # >>> field_strength = data['field_strength'], >>> t0 = data['t0'], >>> TR = data['TR'], >>> FA = data['FA'], >>> # >>> # Signal parameters >>> # >>> R10a = data['R10b'], >>> R10l = data['R10l'], >>> # >>> # Tissue parameters >>> # >>> H = data['Hct'], >>> vol = data['vol'], >>> ) We are only fitting here the first scan data, so the xdata are the aorta- and liver time points of the first scan, and the ydata are the signals at these time points: >>> xdata = (data['time1aorta'], data['time1liver']) >>> ydata = (data['signal1aorta'], data['signal1liver']) Train the model using these data and plot the results to check that the model has fitted the data: >>> model.train(xdata, ydata, xtol=1e-3) >>> model.plot(xdata, ydata) """ f = importlib_resources.files('dcmri.datafiles') datafile = str(f.joinpath(dataset + '.pkl')) with open(datafile, 'rb') as fp: data_dict = pickle.load(fp) return data_dict
[docs] def ca_injection(t: np.ndarray, weight: float, conc: float, dose: float, rate: float, t0: float) -> np.ndarray: """Contrast agent flux (mmol/sec) generated by step injection. Args: t (numpy.ndarray): time points in sec where the flux is to be calculated. weight (float): weight of the subject in kg. conc (float): concentration of the contrast agent in mmol/mL. dose (float): injected dose in mL/kg body weight. rate (float): rate of contrast agent injection in mL/sec. t0 (float): start of the injection in sec. Raises: ValueError: if the injection duration is zero, or smaller than the time step of the time array. Returns: numpy.ndarray: contrast agent flux for each time point in units of mmol/sec. Example: >>> import numpy as np >>> import dcmri as dc Create an array of time points covering 20sec in steps of 1.5sec. >>> t = np.arange(0, 20, 1.5) Inject a dose of 0.2 mL/kg bodyweight at a rate of 3mL/sec starting at t=5sec. For a subject weighing 70 kg and a contrast agent with concentration 0.5M, this produces the flux: >>> dc.ca_injection(t, 70, 0.5, 5, 0.2, 3) array([0. , 0. , 0. , 0. , 1.5, 1.5, 1.5, 0. , 0. , 0. , 0. , 0. , 0. ,0. ]) """ # Get timings duration = weight * dose / rate # sec = kg * (mL/kg) / (mL/sec) dt = np.amin(t[1:] - t[:-1]) # Check consistency of timings if dose > 0: if duration == 0: msg = 'Invalid input variables. ' msg += 'The injection duration is zero.' raise ValueError(msg) if dt >= duration: msg = 'Invalid input variables. \n' msg += 'The smallest time step dt (' + str( dt) + ' sec) is larger than the injection duration 1 (' + str(duration) + 'sec). \n' msg += 'We would recommend dt to be at least 5 times smaller.' raise ValueError(msg) # Build flux Jmax = conc * rate # mmol/sec = (mmol/ml) * (ml/sec) J = np.zeros(t.size) J[(0 < t) & (t < duration)] = Jmax return np.interp(t - t0, t, J, left=0)
[docs] def ca_conc(agent: str) -> float: """Contrast agent concentration Args: agent (str): Generic contrast agent name, all lower case. Examples are 'gadobutrol', 'gadobenate', etc. Raises: ValueError: If no data are available for the agent. Returns: float: concentration in mmol/mL Sources: - `mri questions <https://mriquestions.com/so-many-gd-agents.html>`_ - `gadoxetate <https://www.bayer.com/sites/default/files/2020-11/primovist-pm-en.pdf>`_ - `gadobutrol <https://www.medicines.org.uk/emc/product/2876/smpc#gref>`_ Example: Print the concentration of the agents gadobutrol and gadoterate: .. exec_code:: import dcmri as dc print('gadobutrol is available in a solution of', dc.ca_conc('gadobutrol'), 'M') print('gadoterate is available in a solution of', dc.ca_conc('gadoterate'), 'M') """ if agent == 'gadoxetate': return 0.25 # mmol/mL if agent == 'gadobutrol': return 1.0 # mmol/mL if agent in [ 'gadopentetate', 'gadobenate', 'gadodiamide', 'gadoterate', 'gadoteridol', 'gadopiclenol', ]: return 0.5 # mmol/mL raise ValueError('No concentration data for contrast agent ' + agent)
[docs] def ca_std_dose(agent: str) -> float: """Standard injection volume (dose) in mL per kg body weight. Args: agent (str): Generic contrast agent name, all lower case. Examples are 'gadobutrol', 'gadobenate', etc. Raises: ValueError: If no data are available for the agent. Returns: float: Standard injection volume in mL/kg. Note: Available agents: - gadoxetate - gadobutrol - gadopiclenol - gadopentetate - gadobenate - gadodiamide - gadoterate - gadoteridol Sources: - `mri questions <https://mriquestions.com/so-many-gd-agents.html>`_ - `gadoxetate <https://www.bayer.com/sites/default/files/2020-11/primovist-pm-en.pdf>`_ - `gadobutrol <https://www.medicines.org.uk/emc/product/2876/smpc#gref>`_ Example: >>> import dcmri as dc >>> print('The standard clinical dose of gadobutrol is', dc.ca_std_dose('gadobutrol'), 'mL/kg') The standard clinical dose of gadobutrol is 0.1 mL/kg """ # """Standard dose in mL/kg""" # better in mmol/kg, or offer it as an option if agent == 'gadoxetate': # https://www.bayer.com/sites/default/files/2020-11/primovist-pm-en.pdf return 0.1 # mL/kg if agent == 'gadobutrol': return 0.1 # mL/kg if agent == 'gadopiclenol': return 0.1 # mL/kg if agent in [ 'gadopentetate', 'gadobenate', 'gadodiamide', 'gadoterate', 'gadoteridol', ]: return 0.2 # mL/kg # 0.5 mmol/mL = 0.1 mmol/kg raise ValueError('No dosage data for contrast agent ' + agent)
[docs] def relaxivity(field_strength=3.0, tissue='plasma', agent='gadoxetate', type='T1') -> float: """Contrast agent relaxivity values in units of Hz/M Args: field_strength (float, optional): Field strength in Tesla. Defaults to 3.0. tissue (str, optional): Tissue type - options are 'plasma', 'hepatocytes'. Defaults to 'plasma'. agent (str, optional): Generic contrast agent name, all lower case. Examples are 'gadobutrol', 'gadobenate', etc.. Defaults to 'gadoxetate'. type (str, optional): transverse (T2 or T2*) or longitudinal (T1) relaxivity. Defaults to 'T1'. Returns: float: relaxivity in Hz/M or 1/(sec*M) Note: This library is in construction and not all known values are currently available through this function. Available contrasts: - T1 Available tissues: - blood - plasma - hepatocytes Available agents: - gadopentetate - gadobutrol - gadoteridol - gadobenade - gadoterate - gadodiamide - mangafodipir - gadoversetamide - ferucarbotran - ferumoxide - gadoxetate Available field strengths: - 0.47 - 1.5 - 3 - 4.7 - 7.0 (gadoxetate) - 9.0 (gadoxetate) Sources: - Rohrer M, et al. Comparison of Magnetic Properties of MRI Contrast Media Solutions at Different Magnetic Field Strengths. Investigative Radiology 40(11):p 715-724, November 2005. DOI: `10.1097/01.rli.0000184756.66360.d3 <https://journals.lww.com/investigativeradiology/FullText/2005/11000/Comparison_of_Magnetic_Properties_of_MRI_Contrast.5.aspx>`_ - Szomolanyi P, et al. Comparison of the Relaxivities of Macrocyclic Gadolinium-Based Contrast Agents in Human Plasma at 1.5, 3, and 7 T, and Blood at 3 T. Invest Radiol. 2019 Sep;54(9):559-564. doi: `10.1097/RLI.0000000000000577 <https://pubmed.ncbi.nlm.nih.gov/31124800/>`_ Example: >>> import dcmri as dc >>> print('The plasma relaxivity of gadobutrol at 3T is', 1e-3*dc.relaxivity(3.0, 'plasma', 'gadobutrol'), 'Hz/mM') The plasma relaxivity of gadobutrol at 3T is 5.0 Hz/mM """ # Blood and plasma have (theoretically) the same relaxivity if tissue == 'blood': tissue = 'plasma' rel = {} rel['T1'] = { 'plasma': { 'gadopentetate': { # Magnevist 0.47: 3.8, 1.5: 4.1, 3.0: 3.7, 4.7: 3.8, }, 'gadobutrol': { # Gadovist 0.47: 6.1, 1.5: 5.2, 3.0: 5.0, 4.7: 4.7, }, 'gadoteridol': { # Prohance 0.47: 4.8, 1.5: 4.1, 3.0: 3.7, 4.7: 3.7, }, 'gadobenade': { # Multihance 0.47: 9.2, 1.5: 6.3, 3.0: 5.5, 4.7: 5.2, }, 'gadoterate': { # Dotarem 0.47: 4.3, 1.5: 3.6, 3.0: 3.5, 4.7: 3.3, }, 'gadodiamide': { # Omniscan 0.47: 4.4, 1.0: 4.35, # Interpolated 1.5: 4.3, 3.0: 4.0, 4.7: 3.9, }, 'mangafodipir': { # Teslascan 0.47: 3.6, 1.5: 3.6, 3.0: 2.7, 4.7: 2.2, }, 'gadoversetamide': { # Optimark 0.47: 5.7, 1.5: 4.7, 3.0: 4.5, 4.7: 4.4, }, 'ferucarbotran': { # Resovist 0.47: 15, 1.5: 7.4, 3.0: 3.3, 4.7: 1.7, }, 'ferumoxide': { # Feridex 1.5: 4.5, 3.0: 2.7, 4.7: 1.2, }, 'gadoxetate': { # Primovist 0.47: 8.7, 1.5: 8.1, 3.0: 6.4, 4.7: 6.4, 7.0: 6.2, 9.0: 6.1 }, }, } rel['T1']['hepatocytes'] = rel['T1']['plasma'] rel['T1']['hepatocytes']['gadoxetate'] = { 1.5: 14.6, 3.0: 9.8, 4.7: 7.6, 7.0: 6.0, 9.0: 6.1, } try: return 1000 * rel[type][tissue][agent][field_strength] except KeyError: msg = 'No relaxivity data for ' + agent + \ ' at ' + str(field_strength) + ' T.' raise ValueError(msg)
[docs] def T1(field_strength=3.0, tissue='blood', Hct=0.45) -> float: """T1 value of selected tissue types. Values are taken from literature, mostly from `Stanisz et al 2005 <https://doi.org/10.1002/mrm.20605>`_ Args: field_strength (float, optional): Field strength in Tesla (see below for options). Defaults to 3.0. tissue (str, optional): Tissue type (see below for options). Defaults to 'blood'. Hct (float, optional): Hematocrit value - ignored when tissue is not blood. Defaults to 0.45. Raises: ValueError: If the requested T1 values are not available. Returns: float: T1 values in sec Note: This library is in construction and not all known values are currently available through this function. Available tissue types: - skin - bone marrow - csf - muscle - heart - cartilage - white matter - gray matter - optic nerve - spinal cord - blood - spleen - liver - kidney Available field strengths: - 1.5 - 3 - 4.7 (spleen, liver) - 7.0 (spleen, liver) - 9.0 (spleen, liver) Example: >>> import dcmri as dc >>> print('The T1 of liver at 1.5T is', 1e3*dc.T1(1.5, 'liver'), 'msec') The T1 of liver at 1.5T is 602.0 msec """ # H. M. Gach, C. Tanase and F. Boada, "2D & 3D Shepp-Logan Phantom # Standards for MRI," 2008 19th International Conference on Systems # Engineering, Las Vegas, NV, USA, 2008, pp. 521-526, doi: # 10.1109/ICSEng.2008.15. T1val = { 'skin': { # Gach 2008 (scalp) 1.5: 0.324 * (1.5**0.137), 3.0: 0.324 * (3.0**0.137), }, 'bone marrow': { # Gach 2008 1.5: 0.533 * (1.5**0.088), 3.0: 0.533 * (3.0**0.088), }, 'csf': { # Gach 2008 1.5: 4.20, 3.0: 4.20, }, 'muscle': { 1.5: 1.008, 3.0: 1.412, }, 'heart': { 1.5: 1.030, 3.0: 1.471, }, 'cartilage': { 1.5: 1.024, 3.0: 1.168, }, 'white matter': { 1.5: 0.884, 3.0: 1.084, }, 'gray matter': { 1.5: 1.124, 3.0: 1.820, }, 'optic nerve': { 1.5: 0.815, 3.0: 1.083, }, 'spinal cord': { 1.5: 0.745, 3.0: 0.993, }, 'blood': { 1.0: 1.378, # Extrapolated 1.5: 1.441, 3.0: 1 / (0.52 * Hct + 0.38), # Lu MRM 2004 4.7: 1 / 1.70, # https://cds.ismrm.org/ismrm-2002/PDF4/1048.PDF 7.0: 1 / 2.29, # 10.1016/j.mri.2012.08.008 }, 'spleen': { 4.7: 1 / 0.631, 7.0: 1 / 0.611, 9.0: 1 / 0.600, }, 'liver': { 1.5: 0.602, # liver R1 in 1/sec (Waterton 2021) 3.0: 0.752, # liver R1 in 1/sec (Waterton 2021) # liver R1 in 1/sec (Changed from 1.285 on 06/08/2020) 4.7: 1 / 1.281, # liver R1 in 1/sec (Changed from 0.8350 on 06/08/2020) 7.0: 1 / 1.109, # per sec - liver R1 (https://doi.org/10.1007/s10334-021-00928-x) 9.0: 1 / 0.920, }, 'kidney': { # Reference values average over cortex and medulla from Cox et al # https://academic.oup.com/ndt/article/33/suppl_2/ii41/5078406 1.0: 1.017, # Extrapolated 1.5: (1.024 + 1.272) / 2, 3.0: (1.399 + 1.685) / 2, }, } try: return T1val[tissue][field_strength] except BaseException: msg = 'No T1 values for ' + tissue + \ ' at ' + str(field_strength) + ' T.' raise ValueError(msg)
[docs] def T2(field_strength=3.0, tissue='gray matter') -> float: """T2 value of selected tissue types. Values are taken from `Gach et al 2008 <https://ieeexplore.ieee.org/document/4616690>`_ Args: field_strength (float, optional): Field strength in Tesla. Defaults to 3.0. tissue (str, optional): Tissue type. Defaults to 'gray matter'. Raises: ValueError: If the requested T2 values are not available. Returns: float: T2 values in sec Note: This library is in construction and not all known values are currently available through this function. Available tissue types: - skin - bone marrow - csf - white matter - gray matter Available field strengths: - 1.5 - 3 Example: >>> import dcmri as dc >>> print('The T2 of skin at 1.5T is', 1e3*dc.T2(1.5, 'skin'), 'msec') The T2 of skin at 1.5T is 70.0 msec """ # H. M. Gach, C. Tanase and F. Boada, "2D & 3D Shepp-Logan Phantom # Standards for MRI," 2008 19th International Conference on Systems # Engineering, Las Vegas, NV, USA, 2008, pp. 521-526, doi: # 10.1109/ICSEng.2008.15. T2val = { 'skin': { # Gach 2008 (scalp) 1.5: 0.07, 3.0: 0.07, }, 'bone marrow': { # Gach 2008 1.5: 0.05, 3.0: 0.05, }, 'csf': { # Gach 2008 1.5: 1.99, 3.0: 1.99, }, 'white matter': { 1.5: 0.08, 3.0: 0.08, }, 'gray matter': { 1.5: 0.1, 3.0: 0.1, }, } try: return T2val[tissue][field_strength] except BaseException: msg = 'No T2 values for ' + tissue + \ ' at ' + str(field_strength) + ' T.' raise ValueError(msg)
[docs] def PD(tissue='gray matter') -> float: """Relative proton density (PD) value of selected tissue types. Values are taken from `Gach et al 2008 <https://ieeexplore.ieee.org/document/4616690>`_ Args: tissue (str, optional): Tissue type. Defaults to 'gray matter'. Raises: ValueError: If the requested PD values are not available. Returns: float: PD values (dimensionless, range 0-1) Note: This library is in construction and not all known values are currently available through this function. Available tissue types: - skin - bone marrow - csf - white matter - gray matter Example: >>> import dcmri as dc >>> print('The PD of skin is', dc.PD('skin')) The PD of skin is 0.8 """ # H. M. Gach, C. Tanase and F. Boada, "2D & 3D Shepp-Logan Phantom # Standards for MRI," 2008 19th International Conference on Systems # Engineering, Las Vegas, NV, USA, 2008, pp. 521-526, doi: # 10.1109/ICSEng.2008.15. PDval = { 'skin': 0.8, 'bone marrow': 0.12, 'csf': 0.98, 'white matter': 0.617, 'gray matter': 0.745, } try: return PDval[tissue] except BaseException: msg = 'No PD values for ' + tissue raise ValueError(msg)
[docs] def perfusion(parameter='Fb', tissue='gray matter') -> float: """perfusion parameters of selected tissue types. Args: parameter (str, optional): perfusion parameter. Options are 'Fb' (blood flow), 'vb' (Blood volume), 'PS' (permeability-surface area product) and 'vi' (interstitial volume). Defaults to 'Fb'. tissue (str, optional): Tissue type. Defaults to 'gray matter'. Raises: ValueError: If the requested parameter values are not available. Returns: float: parameter value in standard units Note: This library is in construction and not all known values are currently available through this function. Available tissue types: - skin - bone marrow - csf - white matter - gray matter Example: >>> import dcmri as dc >>> print('The BF of gray matter is', dc.perfusion('Fb', 'gray matter'), 'mL/sec/mL') The BF of gray matter is 0.01 mL/sec/mL """ if parameter == 'Fb': val = { 'skin': 0.005, # 5 kg/s/m**3 = 5000mL/sec/100*100*100 mL = 0.005mL/sec/mL 'bone marrow': 0.0013, # 0.08 ml/ml/min 'csf': 0.0, 'white matter': 0.0033, 'gray matter': 0.01, } elif parameter == 'vb': val = { 'skin': 0.03, 'bone marrow': 0.25, 'csf': 0.0, 'white matter': 0.02, 'gray matter': 0.05, } elif parameter == 'PS': # Needs verification val = { 'skin': 0.001, 'bone marrow': 0.0002, 'csf': 0.0, 'white matter': 0.0, 'gray matter': 0.0, } elif parameter == 'vi': # Needs verification val = { 'skin': 0.03, 'bone marrow': 0.2, 'csf': 0.0, 'white matter': 0.3, 'gray matter': 0.35, } try: return val[tissue] except BaseException: msg = 'No ' + parameter + ' values for ' + tissue raise ValueError(msg)
def _ellipse(array, center, axes, angle, value): n = array.shape[0] major = np.amax(axes) minor = np.amin(axes) c = np.sqrt(major**2 - minor**2) dx = np.cos(angle * np.pi / 180) dy = np.sin(angle * np.pi / 180) f1 = [center[0] + dx * c, center[1] + dy * c] f2 = [center[0] - dx * c, center[1] - dy * c] d = 2.0 / n for i in range(n): for j in range(n): x = d * (i - (n - 1) / 2) y = d * (j - (n - 1) / 2) d1 = np.sqrt((x - f1[0])**2 + (y - f1[1])**2) d2 = np.sqrt((x - f2[0])**2 + (y - f2[1])**2) if d1 + d2 <= 2 * major: array[i, j] = value return array def _shepp_logan_mask(n=256) -> dict: mask = {} # Background array = np.ones((n, n)).astype(np.bool) array = _ellipse(array, [0, 0], [0.72, 0.95], 0, 0) mask['background'] = np.flip(array, axis=0) # 1 Scalp array = _ellipse(np.zeros((n, n)).astype( np.bool), [0, 0], [0.72, 0.95], 0, 1) array = _ellipse(array, [0, 0], [0.69, 0.92], 0, 0) mask['scalp'] = np.flip(array, axis=0) # 2 Bone and marrow array = _ellipse(np.zeros((n, n)).astype( np.bool), [0, 0], [0.69, 0.92], 0, 1) array = _ellipse(array, [-0.0184, 0], [0.6624, 0.874], 0, 0) mask['bone'] = np.flip(array, axis=0) # 3 CSF array = _ellipse(np.zeros((n, n)).astype(np.bool), [-0.0184, 0], [0.6624, 0.874], 0, 1) array = _ellipse(array, [-0.0184, 0], [0.6524, 0.864], 0, 0) mask['CSF skull'] = np.flip(array, axis=0) # 4 Gray matter array = _ellipse(np.zeros((n, n)).astype(np.bool), [-0.0184, 0], [0.6524, 0.864], 0, 1) array = _ellipse(array, [0, -0.22], [0.41, 0.16], 180 - 18, 0) array = _ellipse(array, [0, 0.22], [0.31, 0.11], 180 + 18, 0) array = _ellipse(array, [0.35, 0], [0.21, 0.25], 0, 0) array = _ellipse(array, [0.1, 0], [0.046, 0.046], 0, 0) array = _ellipse(array, [-0.605, -0.08], [0.046, 0.023], 90, 0) array = _ellipse(array, [-0.605, 0.06], [0.023, 0.046], 0, 0) array = _ellipse(array, [-0.1, 0.0], [0.046, 0.046], 0, 0) array = _ellipse(array, [-0.605, 0.0], [0.023, 0.023], 0, 0) array = _ellipse(array, [-0.83, 0], [0.05, 0.05], 0, 0) array = _ellipse(array, [0.66, 0], [0.02, 0.02], 0, 0) mask['gray matter'] = np.flip(array, axis=0) # 5 CSF array = _ellipse(np.zeros((n, n)).astype(np.bool), [ 0, -0.22], [0.41, 0.16], 180 - 18, 1) array = _ellipse(array, [0.35, 0], [0.21, 0.25], 0, 0) array = _ellipse(array, [-0.1, 0.0], [0.046, 0.046], 0, 0) mask['CSF right'] = np.flip(array, axis=0) # 6 CSF array = _ellipse(np.zeros((n, n)).astype(np.bool), [ 0, 0.22], [0.31, 0.11], 180 + 18, 1) mask['CSF left'] = np.flip(array, axis=0) # 7 Tumor array = _ellipse(np.zeros((n, n)).astype( np.bool), [0.35, 0], [0.21, 0.25], 0, 1) array = _ellipse(array, [0.1, 0], [0.046, 0.046], 0, 0) mask['tumor 1'] = np.flip(array, axis=0) # 8 Tumor array = _ellipse(np.zeros((n, n)).astype(np.bool), [0.1, 0], [0.046, 0.046], 0, 1) mask['tumor 2'] = np.flip(array, axis=0) # 9 Tumor array = _ellipse(np.zeros((n, n)).astype(np.bool), [-0.605, -0.08], [0.046, 0.023], 90, 1) mask['tumor 4'] = np.flip(array, axis=0) # 10 Tumor array = _ellipse(np.zeros((n, n)).astype(np.bool), [-0.605, 0.06], [0.023, 0.046], 0, 1) mask['tumor 6'] = np.flip(array, axis=0) # 11 Tumor array = _ellipse(np.zeros((n, n)).astype(np.bool), [-0.1, 0.0], [0.046, 0.046], 0, 1) mask['tumor 3'] = np.flip(array, axis=0) # 12 Tumor array = _ellipse(np.zeros((n, n)).astype(np.bool), [-0.605, 0.0], [0.023, 0.023], 0, 1) mask['tumor 5'] = np.flip(array, axis=0) # 13 Sagittal sinus array = _ellipse(np.zeros((n, n)).astype(np.bool), [-0.83, 0], [0.05, 0.05], 0, 1) mask['sagittal sinus'] = np.flip(array, axis=0) # 14 Anterior cerebral artery array = _ellipse(np.zeros((n, n)).astype( np.bool), [0.66, 0], [0.02, 0.02], 0, 1) mask['anterior artery'] = np.flip(array, axis=0) return mask def _shepp_logan(param, n=256, B0=3) -> np.ndarray: if param == 'PD': scalp = PD('skin') bone = PD('bone marrow') csf = PD('csf') gm = PD('gray matter') tumor = 0.95 blood = 0.9 elif param == 'T2': scalp = T2(B0, 'skin') bone = T2(B0, 'bone marrow') csf = T2(B0, 'csf') gm = T2(B0, 'gray matter') tumor = 0.25 blood = 0.1 elif param == 'T1': scalp = T1(B0, 'skin') bone = T1(B0, 'bone marrow') csf = T1(B0, 'csf') gm = T1(B0, 'gray matter') tumor = 0.926 * (B0**0.217) blood = T1(B0, 'blood') elif param == 'Fb': scalp = perfusion('Fb', 'skin') bone = perfusion('Fb', 'bone marrow') csf = perfusion('Fb', 'csf') gm = perfusion('Fb', 'gray matter') tumor = 0.02 blood = 0 elif param == 'vb': scalp = perfusion('vb', 'skin') bone = perfusion('vb', 'bone marrow') csf = perfusion('vb', 'csf') gm = perfusion('vb', 'gray matter') tumor = 0.1 blood = 1 elif param == 'PS': scalp = perfusion('PS', 'skin') bone = perfusion('PS', 'bone marrow') csf = perfusion('PS', 'csf') gm = perfusion('PS', 'gray matter') tumor = 0.001 blood = 0 elif param == 'vi': scalp = perfusion('vi', 'skin') bone = perfusion('vi', 'bone marrow') csf = perfusion('vi', 'csf') gm = perfusion('vi', 'gray matter') tumor = 0.3 blood = 0 array = np.zeros((n, n)).astype(np.float64) # 1 Scalp array = _ellipse(array, [0, 0], [0.72, 0.95], 0, scalp) # 2 Bone and marrow array = _ellipse(array, [0, 0], [0.69, 0.92], 0, bone) # 3 CSF array = _ellipse(array, [-0.0184, 0], [0.6624, 0.874], 0, csf) # 4 Gray matter array = _ellipse(array, [-0.0184, 0], [0.6524, 0.864], 0, gm) # 5 CSF array = _ellipse(array, [0, -0.22], [0.41, 0.16], 180 - 18, csf) # 6 CSF array = _ellipse(array, [0, 0.22], [0.31, 0.11], 180 + 18, csf) # 7 Tumor array = _ellipse(array, [0.35, 0], [0.21, 0.25], 0, tumor) # 8 Tumor array = _ellipse(array, [0.1, 0], [0.046, 0.046], 0, tumor) # 9 Tumor array = _ellipse(array, [-0.605, -0.08], [0.046, 0.023], 90, tumor) # 10 Tumor array = _ellipse(array, [-0.605, 0.06], [0.023, 0.046], 0, tumor) # 11 Tumor array = _ellipse(array, [-0.1, 0.0], [0.046, 0.046], 0, tumor) # 12 Tumor array = _ellipse(array, [-0.605, 0.0], [0.023, 0.023], 0, tumor) # 13 Sagittal sinus array = _ellipse(array, [-0.83, 0], [0.05, 0.05], 0, blood) # 14 Anterior cerebral artery array = _ellipse(array, [0.66, 0], [0.02, 0.02], 0, blood) # Flip array = np.flip(array, axis=0) return array
[docs] def shepp_logan(*params, n=256, B0=3): """Modified Shepp-Logan phantom mimicking an axial slice through the brain. The phantom is based on an MRI adaptation of the Shepp-Logan phantom (Gach et al 2008), but with added features for use in a DC-MRI setting: (1) additional regions for anterior cerebral artery and sinus sagittalis; (2) additional optional contrasts blood flow (BF), blood volume (BV), permeability-surface area product (PS) and interstitial volume (IV). Args: params (str or tuple): parameter or parameters shown in the image. The options are 'PD' (proton density), 'T1', 'T2', 'Fb' (blood flow), 'vb' (Blood volume), 'PS' (permeability-surface area product) and 'vi' (interstitial volume). If no parameters are provided, the function returns a dictionary with 14 masks, one for each region. n (int, optional): matrix size. Defaults to 256. B0 (int, optional): field strength in T. Defaults to 3. Reference: H. M. Gach, C. Tanase and F. Boada, "2D & 3D Shepp-Logan Phantom Standards for MRI," 2008 19th International Conference on Systems Engineering, Las Vegas, NV, USA, 2008, pp. 521-526, `doi 10.1109/ICSEng.2008.15 <https://ieeexplore.ieee.org/document/4616690>`_. Returns: numpy.array or dict: if only one parameter is provided, this returns an array. In all other conditions this returns a dictionary where keys are the parameter- or region names, and values are square arrays with image values. Note: Mask names: - background - scalp - bone - CSF skull - CSF left - CSF right - gray matter - tumor 1 to tumor 6 - sagittal sinus - anterior artery Example: Generate a single contrast: .. plot:: :include-source: >>> import matplotlib.pyplot as plt >>> import dcmri as dc Simulate a synthetic blood flow image: >>> im = dc.shepp_logan('Fb') Plot the result in units of mL/min/100mL: >>> fig, ax = plt.subplots(figsize=(5, 5), ncols=1) >>> pos = ax.imshow(6000*im, cmap='gray', vmin=0.0, vmax=80) >>> fig.colorbar(pos, ax=ax, label='blood flow (mL/min/100mL)') >>> plt.show() Generate multiple contrasts in one function call: .. plot:: :include-source: >>> import matplotlib.pyplot as plt >>> import dcmri as dc Generate the MR Shepp-Logan phantom in low resolution: >>> im = dc.shepp_logan('PD', 'T1', 'T2', n=64) Plot the result: >>> fig, (ax1, ax2, ax3) = plt.subplots(figsize=(12, 5), ncols=3) >>> ax1.imshow(im['PD'], cmap='gray') >>> ax2.imshow(im['T1'], cmap='gray') >>> ax3.imshow(im['T2'], cmap='gray') >>> plt.show() Generate masks for the different regions-of-interest: .. plot:: :include-source: >>> import matplotlib.pyplot as plt >>> import dcmri as dc Generate the MR Shepp-Logan phantom masks: >>> im = dc.shepp_logan(n=128) Plot all masks: >>> fig, ax = plt.subplots(figsize=(8, 8), ncols=4, nrows=4) >>> ax[0,0].imshow(im['background'], cmap='gray') >>> ax[0,1].imshow(im['scalp'], cmap='gray') >>> ax[0,2].imshow(im['bone'], cmap='gray') >>> ax[0,3].imshow(im['CSF skull'], cmap='gray') >>> ax[1,0].imshow(im['CSF left'], cmap='gray') >>> ax[1,1].imshow(im['CSF right'], cmap='gray') >>> ax[1,2].imshow(im['gray matter'], cmap='gray') >>> ax[1,3].imshow(im['tumor 1'], cmap='gray') >>> ax[2,0].imshow(im['tumor 2'], cmap='gray') >>> ax[2,1].imshow(im['tumor 3'], cmap='gray') >>> ax[2,2].imshow(im['tumor 4'], cmap='gray') >>> ax[2,3].imshow(im['tumor 5'], cmap='gray') >>> ax[3,0].imshow(im['tumor 6'], cmap='gray') >>> ax[3,1].imshow(im['sagittal sinus'], cmap='gray') >>> ax[3,2].imshow(im['anterior artery'], cmap='gray') >>> for i in range(4): >>> for j in range(4): >>> ax[i,j].set_yticklabels([]) >>> ax[i,j].set_xticklabels([]) >>> plt.show() """ if len(params) == 0: return _shepp_logan_mask(n=n) elif len(params) == 1: return _shepp_logan(params[0], n=n, B0=B0) else: phantom = {} for p in params: phantom[p] = _shepp_logan(p, n=n, B0=B0) return phantom
[docs] def aif_parker(t, BAT: float = 0.0) -> np.ndarray: """Population AIF model as defined by `Parker et al (2006) <https://onlinelibrary.wiley.com/doi/full/10.1002/mrm.21066>`_ Args: t (array_like): time points in units of sec. BAT (float, optional): Time in seconds before the bolus arrives. Defaults to 0 sec (no delay). Returns: np.ndarray: Concentrations in M for each time point in t. If t is a scalar, the return value is a scalar too. References: Adapted from a contribution by the QBI lab of the University of Manchester to the `OSIPI code repository <https://github.com/OSIPI/DCE-DSC-MRI_CodeCollection>`_. Example: >>> import numpy as np >>> import dcmri as dc Create an array of time points covering 20sec in steps of 1.5sec, which rougly corresponds to the first pass of the Paeker AIF: >>> t = np.arange(0, 20, 1.5) Calculate the Parker AIF at these time points, and output the result in units of mM: >>> 1000*dc.aif_parker(t) array([0.08038467, 0.23977987, 0.63896354, 1.45093969, 2.75255937, 4.32881325, 5.6309778 , 6.06793854, 5.45203828, 4.1540079 , 2.79568217, 1.81335784, 1.29063036, 1.08751679]) """ # Check input types if not np.isscalar(BAT): raise ValueError('BAT must be a scalar') # Convert from secs to units used internally (mins) t_offset = np.array(t) / 60 - BAT / 60 # A1/(SD1*sqrt(2*PI)) * exp(-(t_offset-m1)^2/(2*var1)) # A1 = 0.833, SD1 = 0.055, m1 = 0.171 gaussian1 = 5.73258 * np.exp( -1.0 * (t_offset - 0.17046) * (t_offset - 0.17046) / (2.0 * 0.0563 * 0.0563)) # A2/(SD2*sqrt(2*PI)) * exp(-(t_offset-m2)^2/(2*var2)) # A2 = 0.336, SD2 = 0.134, m2 = 0.364 gaussian2 = 0.997356 * np.exp( -1.0 * (t_offset - 0.365) * (t_offset - 0.365) / (2.0 * 0.132 * 0.132)) # alpha*exp(-beta*t_offset) / (1+exp(-s(t_offset-tau))) # alpha = 1.064, beta = 0.166, s = 37.772, tau = 0.482 sigmoid = 1.050 * np.exp(-0.1685 * t_offset) / \ (1.0 + np.exp(-38.078 * (t_offset - 0.483))) pop_aif = gaussian1 + gaussian2 + sigmoid return pop_aif / 1000 # convert to M
[docs] def aif_tristan_rat(t, BAT=4.6 * 60, duration=30) -> np.ndarray: """Population AIF model for rats measured with a standard dose of gadoxetate. Args: t (array_like): time points in units of sec. BAT (float, optional): Time in seconds before the bolus arrives. Defaults to 4.6 min. duration (float, optional): Duration of the injection. Defaults to 30s. Returns: np.ndarray: Blood concentrations in M for each time point in t. If t is a scalar, the return value is a scalar too. References: - Melillo N, Scotcher D, Kenna JG, Green C, Hines CDG, Laitinen I, et al. Use of In Vivo Imaging and Physiologically-Based Kinetic Modelling to Predict Hepatic Transporter Mediated Drug-Drug Interactions in Rats. `Pharmaceutics 2023;15(3):896 <https://doi.org/10.3390/pharmaceutics15030896>`_. - Gunwhy, E. R., & Sourbron, S. (2023). TRISTAN-RAT (v3.0.0). `Zenodo <https://doi.org/10.5281/zenodo.8372595>`_ Example: .. plot:: :include-source: >>> import matplotlib.pyplot as plt >>> import numpy as np >>> import dcmri as dc Create an array of time points over 30 minutes >>> t = np.arange(0, 30*60, 0.1) Generate the rat input function for these time points: >>> cb = dc.aif_tristan_rat(t) Plot the result: >>> plt.plot(t/60, 1000*cb, 'r-') >>> plt.title('TRISTAN rat AIF') >>> plt.xlabel('Time (min)') >>> plt.ylabel('Blood concentration (mM)') >>> plt.show() """ # Constants dose = 0.0075 # mmol Fb = 2.27 / 60 # mL/sec # https://doi.org/10.1021/acs.molpharmaceut.1c00206 # (Changed from 3.61/60 on 07/03/2022) # From Brown the cardiac output of rats is # 110.4 mL/min (table 3-1) ~ 6.62L/h # From table 3-4, sum of hepatic artery and portal vein # blood flow is 17.4% of total cardiac output ~ 1.152 L/h # Mass of liver is 9.15g, with density of 1.08 kg/L, # therefore ~8.47mL # 9.18g refers to the whole liver, i.e. intracellular tissue # + extracellular space + blood # Dividing 1.152L/h for 8.47mL we obtain ~2.27 mL/min/mL liver # Calculation done with values in Table S2 of our article # lead to the same results Hct = 0.418 # Cremer et al, J Cereb Blood Flow Metab 3, 254-256 (1983) VL = 8.47 # mL # Scotcher et al 2021, DOI: 10.1021/acs.molpharmaceut.1c00206 # Supplementary material, Table S2 GFR = 1.38 / 60 # mL/sec (=1.38mL/min) # https://doi.org/10.1152/ajprenal.1985.248.5.F734 P = 0.172 # mL/sec # Estimated from rat repro study data using PBPK model # 0.62 L/h # Table 3 in Scotcher et al 2021 # DOI: 10.1021/acs.molpharmaceut.1c00206 VB = 15.8 # mL # 0.06 X BW + 0.77, Assuming body weight (BW) = 250 g # Lee and Blaufox. Blood volume in the rat. # J Nucl Med. 1985 Jan;26(1):72-6. VE = 30 # mL # All tissues, including liver. # Derived from Supplementary material, Table S2 # Scotcher et al 2021 # DOI: 10.1021/acs.molpharmaceut.1c00206 E = 0.4 # Liver extraction fraction, estimated from TRISTAN data # Using a model with free E, then median over population of controls # Derived constants VP = (1 - Hct) * VB Fp = (1 - Hct) * Fb K = GFR + E * Fp * VL # mL/sec # Influx in mmol/sec J = np.zeros(np.size(t)) Jmax = dose / duration # mmol/sec J[(t > BAT) & (t < BAT + duration)] = Jmax # Model parameters TP = VP / (K + P) TE = VE / P E = P / (K + P) Jp = pk.flux_2cxm(J, [TP, TE], E, t) cp = Jp / K return cp * (1 - Hct)