Reproducibility of hemodynamic simulations of cerebral aneurysms across imaging modalities 3DRA and CTA

Arjan Geers

This notebook reproduces* the data analysis presented in:

Geers AJ, Larrabide I, Radaelli AG, Bogunovic H, Kim M, Gratama van Andel HAF, Majoie CB, VanBavel E, Frangi AF. Patient-specific computational hemodynamics of intracranial aneurysms from 3D rotational angiography and CT angiography: An in vivo reproducibility study. American Journal of Neuroradiology, 32(3):581–586, 2011.

The goal of the study was to determine the reproducibility of blood flow simulations of cerebral aneurysms. Patients with a total of 10 cerebral aneurysms were imaged with both 3D rotational angiography (3DRA) and computed tomographic angiography (CTA). Each image independently was segmented to obtain a vascular model, the same boundary conditions were imposed, and a CFD simulation was obtained.

*Originally, data was analyzed in MATLAB R2010b and the boxplot was created in Mathematica 7.

Preamble


In [ ]:
%matplotlib inline

In [ ]:
import os
import numpy as np
import pandas as pd
from scipy import stats
import matplotlib.pyplot as plt

Data

The data used in this notebook is also available on FigShare:

Geers AJ, Larrabide I, Radaelli AG, Bogunovic H, Kim M, Gratama van Andel HAF, Majoie CB, VanBavel E, Frangi AF. Reproducibility of hemodynamic simulations of cerebral aneurysms across imaging modalities 3DRA and CTA: Geometric and hemodynamic data. FigShare, 2015. DOI: 10.6084/m9.figshare.1354056

Variables are defined as follows (TA: time-averaged; PS: peak systole; ED: end diastole):

  • A_N: Aneurysm neck area
  • V_A: Aneurysm volume
  • Q_P: TA flow rate in the parent vessel just proximal to the aneurysm
  • Q_A: TA flow rate into the aneurysm
  • NQ_A: Q_A / Q_P
  • WSS_P: Average TA WSS on the wall of a parent vessel segment just proximal to the aneurysm
  • WSS_A: Average TA WSS on the aneurysm wall
  • NWSS_A: WSS_A / WSS_P
  • LWSS_A: Portion of the aneurysm wall with WSS < 0.4 Pa at ED
  • MWSS_A: Maximum WSS on the aneurysm wall at PS
  • 90WSS_A: 90th percentile value of the WSS on the aneurysm wall at PS
  • N90WSS_A: 90WSS_A normalized by the average WSS on the aneurysm wall at PS

In [ ]:
df_input = pd.read_csv(os.path.join('data', '3dracta.csv'), index_col=[0, 1])
df_input

Extract separate dataframes for 3DRA and CTA.


In [ ]:
df_3dra = df_input.xs('3dra', level='modality')
df_cta = df_input.xs('cta', level='modality')

Statistics

Calculate the relative difference between 3DRA and CTA wrt 3DRA. Per variable, get the mean and standard error of this relative difference over all aneurysms.


In [ ]:
df_reldiff = 100 * abs(df_3dra - df_cta)/df_3dra
s_mean = df_reldiff.mean()
s_standarderror = pd.Series(stats.sem(df_reldiff), index=df_input.columns)

Test differences between 3DRA and CTA with the Wilcoxon signed rank test.

Note: MATLAB was used to perform this test for the paper. Its 'signrank' function defaults to using the 'exact method' if a dataset has 15 or fewer observations and the 'approximate method' otherwise. See the documentation for more details. SciPy's 'wilcoxon' function has currently (version 1.3.0) no equivalent option and always uses the 'approximate method'.


In [ ]:
pvalue = np.empty(len(df_input.columns))
for i, variable in enumerate(df_input.columns):
    pvalue[i] = stats.wilcoxon(df_3dra[variable], df_cta[variable])[1]
s_pvalue = pd.Series(pvalue, index=df_input.columns)

Determine the number of aneurysms for which a variable is lower for CTA than for 3DRA.


In [ ]:
numberofcases = np.empty(len(df_input.columns))
for i, variable in enumerate(df_input.columns):
    numberofcases[i] = sum(df_3dra.loc[j, variable] > df_cta.loc[j, variable]
                           for j in df_input.index.levels[0])
s_numberofcases = pd.Series(numberofcases, index=df_input.columns)

Compose a dataframe with the obtained statistical results, corresponding to the 'online table' of the journal paper.


In [ ]:
d = {'M': s_numberofcases,
     'P': s_pvalue,
     'Mean (%)': s_mean,
     'SE (%)': s_standarderror}

df_output = pd.DataFrame(d, columns=['M', 'P', 'Mean (%)', 'SE (%)'])
df_output

Boxplot

Make boxplots showing the distributions of the relative differences over all aneurysms.


In [ ]:
# extract arrays to plot from dataframe
array_yticklabels = ['$\mathregular{' + variable.replace('%', '\%') + '}$'
                     for variable in df_reldiff.columns]
array_reldiff = df_reldiff.values

# create plot
fig, ax = plt.subplots()
bp = ax.boxplot(array_reldiff, sym='+', vert=0, patch_artist=True)

# set labels
ax.set_xlabel('Relative difference (%)', fontsize=18)
ax.set_xlim(0, 130)
ax.set_yticklabels(array_yticklabels, fontsize=12)

# format box, whiskers, etc.
plt.setp(ax.get_xticklabels(), fontsize=12)
plt.setp(bp['boxes'], color='black')
plt.setp(bp['medians'], color='white')
plt.setp(bp['whiskers'], color='black', linestyle='-')
plt.setp(bp['fliers'], color='black', markersize=5)
plt.tight_layout()

In [ ]: