First we will, pretty obviously, import that Jupyter functions from nanslice
. We are also going to use urllib
and tarfile
to get some example data from https://peerj.com/articles/2632. The data is a group template T2-weighted image, the difference in group average quantitative T1, the p-value for a two group comparison between control and treated animals, and a brain mask.
Important - The %matplotlib notebook
magic is required if you want to use the interactive three-plane viewer. If you are only making static plots you do not need it.
In [ ]:
%matplotlib notebook
!pip install nanslice
import urllib.request
import tarfile
url = 'https://osf.io/hmtyr/download'
urllib.request.urlretrieve(url, 'nanslice_example.tar.gz')
tgz = tarfile.open('nanslice_example.tar.gz')
tgz.extractall()
tgz.close()
data_dir = 'nanslice_example/'
import nanslice.jupyter as ns
The files are now contained in the data_dir
directory. nanslice
provides a simple three_plane
function to give a quick view through an image, and an interactive three_plane_viewer
. For convenience you can pass these functions a file name instead of loading the file first.
Note If you "Run All" cells in this notebook, you will have to run the interactive viewer cell again to enable the sliders. This is because the interactive viewer requires a call to matplotlib.pyplot.ion()
. The non-interactive plots require a call to matplotlib.pyplot.ioff()
, otherwise extra white-space is shown. Calling this in later cells turns off the sliders.
In [ ]:
ns.three_plane(data_dir + 'template_T2w.nii.gz', title='An Image')
In [ ]:
ns.three_plane(data_dir + 'template_T2w.nii.gz', interactive=True, title='Drag the Sliders')
However, if you are going to use the same image multiple times, e.g. a structural template image, then it makes sense to load it first
The above examples are the fatest way to display an image in nanslice
. However, if we want to have a more advanced plot or use overlays, then we need to use a Layer
object. Here, we add a mask to the base image. The bounding box of the slices will be automatically cropped to the mask, making better use of the space in the plot.
In [ ]:
base = ns.Layer(data_dir + 'template_T2w.nii.gz', mask=data_dir + 'study_mask.nii.gz')
ns.three_plane(base, title='Mask')
Now that we have a Layer
object, we can re-use it for future plots without having to reload the image from disk. For example, we can change the colormap (this can also be specified when creating the Layer
). We can also add a colormap to the plot.
In [ ]:
base.cmap = 'viridis'
ns.three_plane(base, cbar=True, title='Colormap')
Now we are ready to start adding overlays to images. First we will add a standard single-coded statistical overlay, in this case a p-value thresholded at p < 0.05. Because this p-value was generated with FSL randomise, it is stored as 1-p so we threshold at (1 - p) > 0.95. We also add a colorbar, and specify that we want to take it from layer 1 (Python arrays count from 0, so this is the 1st overlay). Note that the colorbar has been automatically labelled using the label
property of the Layer
.
In [ ]:
base.cmap = 'gist_gray'
pval = ns.Layer(data_dir + 'T1_tfce_p_tstat1.nii.gz', cmap='Reds', clim=(0.95,1.0), label='1-p', mask_threshold=0.95)
ns.three_plane([base, pval],cbar=1, title='P-Value Overlay')
One of the key reasons nanslice
was written was to demonstrate dual-coded overlays, where both color and transparency have meaning - see https://www.cell.com/neuron/fulltext/S0896-6273(12)00428-X. To create a dual-coded overlay we specify an 'alpha' or transparency image when creating the Layer
. In this case we will encode the difference in group T1 (essentially effect-size) as color and the p-value (significance) as transparency. Areas of strong color are hence interesting regions, but we will also be able to see areas with a large effect but low significance which normally would be hidden with a simply thresholded single-coded statistical overlay. Note that now the colorbar has become an 'alphabar', with two meaningful axes. Traditional significance levels can be indicated with a contour. Using this method we see that there are there several areas of T1 change that appear anatomically plausible but do not meet the traditional level of significance after multiple comparisons correction.
In [ ]:
dual = ns.Layer(data_dir + 'T1_difference.nii.gz',
cmap='RdYlBu_r', clim=(-100, 100), scale=1000, label='T1 Difference (ms)',
alpha=data_dir + 'T1_tfce_p_tstat1.nii.gz', alpha_lim=(0.5, 1.0), alpha_label='1-p')
ns.three_plane([base, dual], cbar=1, contour=0.95, title='All the Bells & Whistles')
The above examples all use the three_plane
function. We can get better control of which slices are shown using the slice_axis
and slices
function. slice_axis
is a thin wrapper that specifies a set of slices along one axis, while slices
allows full control over which slices are drawn.
In [ ]:
ns.slice_axis([base, dual], nrows=2, ncols=5,
slice_axis='z', slice_lims=(0.3, 0.75),
cbar=1, contour=0.95, title='Two Rows')
In [ ]:
slice_ax = ['x','y','z','x','y','z','x','y','z']
slice_pos = [0.2, 0.2, 0.2, 0.3, 0.3, 0.3, 0.5, 0.5, 0.5]
ns.slices([base, dual], nrows=3, ncols=3,
slice_axes=slice_ax, slice_pos=slice_pos,
cbar=1, contour=0.95, title='3x3 Grid')
In [ ]: