Applying Layers to Quantitative Maps

The data used in this tutorial can be downloaded from Zenodo

The primary way of getting data from a layers object is as a Pandas DataFrame. Your quantitative map and mask/anatomical image don’t need to have the same field of view and voxel size, they are resampled to a common space as part of the add_map method. The space parameter of the QLayers class dictates whether any maps added to the object are resampled of the same space as the layers (space=layers) or if the layers are resampled to the native space of the map (space=map).

The advantage of working in layer space is that you can produce a wide DataFrame where each row represents a region of tissue in the image and allows you to directly compare the depth and multiple quantitative parameters however it does involve a resampling operation to the quantitative parameter which may not be desirable (especially for noisy data). Working with space=map only allows you to produce a long DataFrame where each row is a single voxel of each quantitative map. Here we’re going to work with space=layers and return both formats of DataFrame so you can see the difference. Most analysis is easier with a wide DataFrame so we’ll save that variable for use in subsequent steps.

Start by importing the necessary libraries.

[1]:
import matplotlib.pyplot as plt
import nibabel as nib
import numpy as np
import seaborn as sns

from qlayers import QLayers

sns.set()
[2]:
mask_img = nib.load('data/kidney_mask.nii.gz')
qlayers = QLayers(mask_img, thickness=1, pelvis_dist=10, space='layers')
r2star_map = nib.load('data/r2star.nii.gz')
t1_map = nib.load('data/t1.nii.gz')
qlayers.add_map(r2star_map, 'r2star')
qlayers.add_map(t1_map, 't1')
Making Mesh
Smoothing Mesh
Distance Calculation: 100%|██████████| 3/3 [00:09<00:00,  3.24s/it]
Pelvis Distance Calculation: 100%|██████████| 3/3 [00:10<00:00,  3.57s/it]

DataFrames

Here is an example of how to retrieve a layers dataframe in long format. Note that every measurement is on a different row of the DataFrame.

[3]:
print('An example of a long DataFrame')
qlayers.get_df('long').dropna().sample(n=100)
An example of a long DataFrame
[3]:
depth layer measurement value
18433 6.220314 7.0 r2star 26.932120
12665 5.595113 6.0 r2star 25.610969
5335 11.626794 12.0 r2star 22.953779
6029 4.469464 5.0 r2star 21.860133
29047 4.645861 5.0 r2star 67.383193
... ... ... ... ...
25519 12.326423 13.0 t1 1909.264609
17489 7.499027 8.0 r2star 43.458317
23506 13.024991 14.0 r2star 26.698158
23549 11.341260 12.0 r2star 17.922952
15893 10.224986 11.0 t1 1796.101479

100 rows × 4 columns

And now an example of a wide DataFrame. Here each row represents properties of a single region of tissue i.e. the \(T_1\) and \(R_2^*\) of the same region.

[4]:
df = qlayers.get_df('wide')
# A little bit of data cleaning
df = df.loc[df['r2star'] > 0.1]
df = df.loc[df['t1'] > 0.1]
df = df.loc[df['layer'] > 0]
df = df.dropna()
print('An example of a wide DataFrame')
df.sample(n=100)
An example of a wide DataFrame
[4]:
depth layer r2star t1
10557 10.021963 11.0 20.356859 1845.174668
29467 0.130527 1.0 63.740803 1309.256450
914 7.301192 8.0 21.319738 1657.835906
13820 0.567545 1.0 49.005747 3884.615364
493 3.255654 4.0 17.848348 1469.497112
... ... ... ... ...
24364 7.751323 8.0 19.327940 1341.303271
6265 6.819516 7.0 20.002437 1977.369206
10580 4.323028 5.0 38.620470 1514.621767
28977 1.128854 2.0 86.808749 2208.782174
16691 9.357887 10.0 61.534533 2032.454663

100 rows × 4 columns

Layer Statistics

These large voxel-by-voxel DataFrames can be used to generate statistics for each layer.

[5]:
df[['layer', 'r2star', 't1']].groupby('layer').agg(('mean', 'median', 'std', np.count_nonzero))
[5]:
r2star t1
mean median std count_nonzero mean median std count_nonzero
layer
1.0 36.078919 34.295055 15.965442 960 1529.780783 1346.811924 813.851546 960
2.0 32.549396 28.651261 14.739961 874 1518.952653 1362.840107 642.939757 874
3.0 29.125305 24.462825 13.853552 867 1476.203398 1380.362079 451.760720 867
4.0 28.005002 22.339258 14.837777 951 1520.895359 1410.410566 496.683234 951
5.0 25.211529 21.449516 11.715109 1082 1655.215180 1514.621879 583.390482 1082
6.0 24.269633 21.373598 10.398932 1011 1732.452145 1604.757053 604.777044 1011
7.0 24.614919 21.496812 10.347911 1034 1777.863990 1701.400927 580.702461 1034
8.0 25.302156 22.356805 10.114435 1060 1823.713049 1799.114735 545.982923 1060
9.0 25.324391 22.562809 10.354526 1110 1853.555023 1856.190759 475.095100 1110
10.0 25.414645 23.365548 9.933313 1057 1818.143614 1840.166042 421.115600 1057
11.0 26.051144 23.991717 10.092395 941 1862.573946 1863.199491 482.555673 941
12.0 26.018781 24.158163 9.720444 711 1882.722874 1867.205525 517.789819 711
13.0 25.893955 24.186453 9.392482 492 1842.207103 1860.696655 403.798594 492
14.0 25.094223 23.521924 8.535586 339 1808.313892 1810.123642 396.626239 339
15.0 25.126293 22.335086 9.043769 220 1735.074765 1712.920485 348.448770 220
16.0 25.265017 22.778277 9.056639 128 1703.510220 1669.850936 336.472778 128
17.0 23.650405 22.607443 7.056175 76 1634.659105 1543.670057 280.329523 76
18.0 23.916527 22.996451 6.139407 45 1691.317080 1708.911370 260.124806 45
19.0 22.877785 22.514443 6.051280 36 1600.631487 1598.745248 230.243360 36
20.0 21.178358 20.748297 5.223452 14 1451.842383 1441.952526 128.356280 14
21.0 17.899892 17.899892 3.072073 2 1345.310897 1345.310897 18.407824 2
[6]:
fig, ax = plt.subplots()
sns.lineplot(df, x='layer', y='r2star', ax=ax, estimator='median')
ax.set_xlabel('Depth (mm)')
ax.set_ylabel('$R_2^*$ (Hz)')
ax.set_ylim((15, 35))
ax.set_title('Median $R_2^*$ by Layer')
[6]:
Text(0.5, 1.0, 'Median $R_2^*$ by Layer')
../_images/examples_applying_layers_to_quantitative_maps_9_1.png