Preclinical AIF#

Hide code cell source
import os
import numpy
from matplotlib import pyplot as plt
from matplotlib.transforms import offset_copy
import csv
import seaborn as sns
import pandas as pd
import json
from pathlib import Path

Background#

This preclinical AIF is derived from DCE-MRI data of rats described in the the paper of McGrath et al. (MRM 2009).

Test data#

To create the AIF the parameters of the functional form are copied from Table 1, Model B of the reference. The original data had a temp resolution of 0.5 s and tot acquisition time of 300s. This data was labeled as ‘Original_AIF’. Permutations of this AIF were used to test the implementations:

  • various temporal resolutions: 0.5, 1, 2, 2.5, 5, 7.5 s

  • various acquistion times: 3, 5, 7, 10 min

  • shifts of the AIF with a temporal resolution of 1.5 s (dt): 0, dt, 2dt, 5dt, 2, 5, 10, 18, 31

As we don’t expect many errors in implementing the population AIFs, the tolerances were set tight.
Tolerances: absolute + relative = 0.0001 mM + 0.01 and 0.1 mM + 0.1 for the data with a shift.

Import data#

# Load the meta data
meta = json.load(open("../test/results-meta.json"))
# Loop over each entry and collect the dataframe
df = []
for entry in meta:
    if (entry['category'] == 'PopulationAIF') & (entry['method'] == 'preclinical') :
        fpath, fname, category, method, author = entry.values()
        df_entry = pd.read_csv(Path(fpath, fname)).assign(author=author)
        df.append(df_entry)
    
# Concat all entries
df = pd.concat(df)
# label data source
df['source']=''
df.loc[df['label'].str.contains('original'),'source']='original'
df.loc[df['label'].str.contains('acq_time_'),'source']='acq_time'
df.loc[df['label'].str.contains('temp_res'),'source']='temp_res' 
df.loc[df['label'].str.contains('delay'),'source']='delay' 

author_list = df.author.unique()
no_authors = len(author_list)

Results#

Original AIF#

data_original = df[(df['source']=='original')]  

fig, axs = plt.subplots(1, no_authors, sharey='none',figsize=(10,6))

for current_author in range(no_authors):
    plt.subplot(1,no_authors,current_author+1)
    subset_data = data_original[data_original['author'] == author_list[current_author]]
    plt.plot(subset_data.time_ref, subset_data.cb_measured, color='black',label ="AIF")
    plt.plot(subset_data.time_ref, subset_data.aif_ref, color='darkgrey', linestyle='dashed', label='ref')
    
    plt.title(author_list[current_author], fontsize=14)
    plt.xlabel('time (s)', fontsize=14)
    plt.ylabel('C (mM)', fontsize=14)
    plt.xticks(fontsize=14)
    plt.yticks(fontsize=14)
    plt.legend(bbox_to_anchor=(1.04,0.5), loc="center left", fontsize=14)
_images/d6621e6eb84a1a9a1c95f26ac81f7a8401e53d03bdda59415da8297477370e7f.png

Different acquisition times#

There are no errors for different acquisition times.
This test data was added to check whether the contributions can work with acquisition times different from the original reference.

data_acqtime = df[(df['source']=='acq_time')]
acq_list = data_acqtime.label.unique()
no_acq = len(acq_list)

fig, ax = plt.subplots(no_acq, no_authors, sharex='col', sharey='row', figsize=(6,9))

if no_authors > 1:
    for current_acq in range(no_acq):
        for current_author in range(no_authors):
            subset_data = data_acqtime[(data_acqtime['author'] == author_list[current_author]) & (data_acqtime['label'] == acq_list[current_acq])]
            ax[current_acq,current_author].plot(subset_data.time_ref, subset_data.cb_measured, color='black',label ="AIF")
            ax[current_acq,current_author].plot(subset_data.time_ref, subset_data.aif_ref, color='darkgrey', linestyle='dashed', label='ref')

            if current_acq == 0:
                ax[current_acq,current_author].set_title(author_list[current_author], fontsize=14)
            if current_author == 0:
                ax[current_acq,current_author].set_ylabel('C (mM)', fontsize=14)
            if current_acq == no_acq-1:
                ax[current_acq,current_author].set_xlabel('time (s)', fontsize=14)
            ax[current_acq,current_author].tick_params(axis='x', labelsize=12)
            ax[current_acq,current_author].tick_params(axis='y', labelsize=12)

    ax[no_acq-1,no_authors-1].legend(bbox_to_anchor=(1.04,0.5), loc="center left", fontsize=14)

    # add extra labels for rows (example taken from: https://microeducate.tech/row-and-column-headers-in-matplotlibs-subplots/)
    pad = 5
    for a, row in zip(ax[:,0], acq_list):
        a.annotate(row, xy=(0, 0.5), xytext=(-a.yaxis.labelpad - pad, 0),
                    xycoords=a.yaxis.label, textcoords='offset points',
                    ha='right', va='center', rotation=90, fontsize=14)

    
else:
    for current_acq in range(no_acq):
        subset_data = data_acqtime[(data_acqtime['author'] == author_list[current_author]) & (data_acqtime['label'] == acq_list[current_acq])]
        ax[current_acq].plot(subset_data.time_ref, subset_data.cb_measured, color='black',label ="AIF")
        ax[current_acq].plot(subset_data.time_ref, subset_data.aif_ref, color='darkgrey', linestyle='dashed', label='ref')
        ax[current_acq].set_ylabel('C (mM)', fontsize=14)
        
        if current_acq == 0:
            ax[current_acq].set_title(author_list[current_author], fontsize=14)         
        if current_acq == no_acq-1:
            ax[current_acq].set_xlabel('time (s)', fontsize=14)
        ax[current_acq].tick_params(axis='x', labelsize=14)
        ax[current_acq].tick_params(axis='y', labelsize=14)
            
    ax[no_acq-1].legend(bbox_to_anchor=(1.04,0.5), loc="center left", fontsize=14)
    #add extra labels for rows
    pad = 5        
    for a, row in zip(ax, acq_list):
        a.annotate(row, xy=(0, 0.5), xytext=(-a.yaxis.labelpad - pad, 0),
                xycoords=a.yaxis.label, textcoords='offset points',
                ha='right', va='center', rotation=90, fontsize=14)

        
fig.tight_layout()
fig.subplots_adjust(left=0.15, top=0.95)
_images/f0d939ef6948fe4af320ee3b3f9ab7699c7fd55d49c54493896980de0c800bf8.png

Different temporal resolutions#

There are no errors for various temporal resolutions
This test data was added to check whether the contributions can work with temporal resolutions different from the original reference.

data_res = df[(df['source']=='temp_res')]
res_list = data_res.label.unique()
no_res = len(res_list)

fig, ax = plt.subplots(no_res, no_authors, sharex='col', sharey='row', figsize=(6,12))

if no_authors > 1:
    for current_res in range(no_res):
        for current_author in range(no_authors):
            subset_data = data_res[(data_res['author'] == author_list[current_author]) & (data_res['label'] == res_list[current_res])]
            ax[current_res,current_author].plot(subset_data.time_ref, subset_data.cb_measured, color='black',label ="AIF")
            ax[current_res,current_author].plot(subset_data.time_ref, subset_data.aif_ref, color='darkgrey', linestyle='dashed', label='ref')

            if current_res == 0:
                ax[current_res,current_author].set_title(author_list[current_author], fontsize=14)
            if current_author == 0:
                ax[current_res,current_author].set_ylabel('C (mM)', fontsize=14)
            if current_res == no_res-1:
                ax[current_res,current_author].set_xlabel('time (s)', fontsize=14)
            ax[current_res,current_author].tick_params(axis='x', labelsize=12)
            ax[current_res,current_author].tick_params(axis='y', labelsize=12)

    ax[no_res-1,no_authors-1].legend(bbox_to_anchor=(1.04,0.5), loc="center left", fontsize=14)
    
    #add extra labels for rows
    pad = 5
    for a, row in zip(ax[:,0], res_list):
        a.annotate(row, xy=(0, 0.5), xytext=(-a.yaxis.labelpad - pad, 0),
                    xycoords=a.yaxis.label, textcoords='offset points',
                    ha='right', va='center', rotation=90, fontsize=14)
    
else:
    for current_res in range(no_res):
        subset_data = data_res[(data_res['author'] == author_list[current_author]) & (data_res['label'] == res_list[current_res])]
        ax[current_res].plot(subset_data.time_ref, subset_data.cb_measured, color='black',label ="AIF")
        ax[current_res].plot(subset_data.time_ref, subset_data.aif_ref, color='darkgrey', linestyle='dashed', label='ref')
        ax[current_res].set_ylabel('C (mM)', fontsize=14)
        
        if current_res == 0:
            ax[current_res].set_title(author_list[current_author], fontsize=14)         
        if current_res == no_res-1:
            ax[current_res].set_xlabel('time (s)', fontsize=14)
        ax[current_res].tick_params(axis='x', labelsize=12)
        ax[current_res].tick_params(axis='y', labelsize=12)
    
    #add extra labels for rows
    pad = 5
    for a, row in zip(ax, res_list):
        a.annotate(row, xy=(0, 0.5), xytext=(-a.yaxis.labelpad - pad, 0),
                    xycoords=a.yaxis.label, textcoords='offset points',
                    ha='right', va='center', rotation=90, fontsize=14)

    ax[no_res-1].legend(bbox_to_anchor=(1.04,0.5), loc="center left", fontsize=14)

fig.tight_layout()
fig.subplots_adjust(left=0.15, top=0.95)
_images/c4bff1cd8e06dd5a61d883a46d69a7f14b19c3fe017677da8fb91617d6bf46a2.png

Variations in bolus arrival time#

This test data was added to check how contributions deal with a difference in bolus arrival time. Figures are zoomed on the first 50 s to show the differences in the peak due to differences in bolus arrival time. In this case there was no difference between reference and measured AIFs.

data_delay = df[(df['source']=='delay')]
delay_list = data_delay.label.unique()
no_delay = len(delay_list)

fig, ax = plt.subplots(no_delay, no_authors, sharex='col', sharey='row', figsize=(6,16))

if no_authors > 1:
    for current_delay in range(no_delay):
        for current_author in range(no_authors):
            subset_data = data_delay[(data_delay['author'] == author_list[current_author]) & (data_delay['label'] == delay_list[current_delay])]
            ax[current_delay,current_author].plot(subset_data.time_ref, subset_data.cb_measured, color='black',label ="AIF")
            ax[current_delay,current_author].plot(subset_data.time_ref, subset_data.aif_ref, color='darkgrey', linestyle='dashed', label='ref')

            if current_delay == 0:
                ax[current_delay,current_author].set_title(author_list[current_author], fontsize=14)
            if current_author == 0:
                ax[current_delay,current_author].set_ylabel('C (mM)', fontsize=14)
            if current_delay == no_res-1:
                ax[current_delay,current_author].set_xlabel('time (s)', fontsize=14)

            ax[current_delay,current_author].set_xlim([0, 50])
            ax[current_delay,current_author].tick_params(axis='x', labelsize=12)
            ax[current_delay,current_author].tick_params(axis='y', labelsize=12)

    ax[no_delay-1,no_authors-1].legend(bbox_to_anchor=(1.04,0.5), loc="center left", fontsize=14)

    # add extra labels for rows
    pad = 5
    for a, row in zip(ax[:,0], delay_list):
        a.annotate(row, xy=(0, 0.5), xytext=(-a.yaxis.labelpad - pad, 0),
                    xycoords=a.yaxis.label, textcoords='offset points',
                    ha='right', va='center', rotation=90, fontsize=14)
    
else:
    for current_delay in range(no_delay):
        subset_data = data_delay[(data_delay['author'] == author_list[current_author]) & (data_delay['label'] == delay_list[current_delay])]
        ax[current_delay].plot(subset_data.time_ref, subset_data.cb_measured, color='black',label ="AIF")
        ax[current_delay].plot(subset_data.time_ref, subset_data.aif_ref, color='darkgrey', linestyle='dashed', label='ref')
        ax[current_delay].set_ylabel('C (mM)', fontsize=14)
        
        if current_delay == 0:
            ax[current_delay].set_title(author_list[current_author], fontsize=14)         
        if current_delay == no_delay-1:
            ax[current_delay].set_xlabel('time (s)', fontsize=14)
        ax[current_delay].tick_params(axis='x', labelsize=12)
        ax[current_delay].tick_params(axis='y', labelsize=12)
            
        ax[current_delay].set_xlim([0, 50])
    
    #add extra labels for rows
    pad = 5
    for a, row in zip(ax, delay_list):
        a.annotate(row, xy=(0, 0.5), xytext=(-a.yaxis.labelpad - pad, 0),
                    xycoords=a.yaxis.label, textcoords='offset points',
                    ha='right', va='center', rotation=90, fontsize=14)

    ax[no_delay-1].legend(bbox_to_anchor=(1.04,0.5), loc="center left", fontsize=14)

fig.tight_layout()
fig.subplots_adjust(left=0.15, top=0.95)
_images/c4e7d4d09f466aaaf0ce2b9b785b56ee687dce33c6e844d7f4c9a802d1346d40.png

Notes#

Additional notes/remarks

References#

McGrath et al. “Comparison of Model-Based Arterial Input Functions forDynamic Contrast-Enhanced MRI in Tumor Bearing Rats”, Magn Reson Med (2009), DOI: 10.1002/mrm.21959