Source code for netneurotools.plotting.pysurfer_plotters

"""Functions for pysurfer-based plotting."""

import os
import numpy as np
import nibabel as nib

from ..datasets import FREESURFER_IGNORE, _get_freesurfer_subjid


[docs] def plot_conte69(data, lhlabel, rhlabel, surf='midthickness', vmin=None, vmax=None, colormap='viridis', colorbar=True, num_labels=4, orientation='horizontal', colorbartitle=None, backgroundcolor=(1, 1, 1), foregroundcolor=(0, 0, 0), **kwargs): """ Plot surface `data` on Conte69 Atlas. Parameters ---------- data : (N,) array_like Surface data for N parcels lhlabel : str Path to .gii file (generic GIFTI file) containing labels to N/2 parcels on the left hemisphere rhlabel : str Path to .gii file (generic GIFTI file) containing labels to N/2 parcels on the right hemisphere surf : {'midthickness', 'inflated', 'vinflated'}, optional Type of brain surface. Default: 'midthickness' vmin : float, optional Minimum value to scale the colormap. If None, the min of the data will be used. Default: None vmax : float, optional Maximum value to scale the colormap. If None, the max of the data will be used. Default: None colormap : str, optional Any colormap from matplotlib. Default: 'viridis' colorbar : bool, optional Wheter to display a colorbar. Default: True num_labels : int, optional The number of labels to display on the colorbar. Available only if colorbar=True. Default: 4 orientation : str, optional Defines the orientation of colorbar. Can be 'horizontal' or 'vertical'. Available only if colorbar=True. Default: 'horizontal' colorbartitle : str, optional The title of colorbar. Available only if colorbar=True. Default: None backgroundcolor : tuple of float values with RGB code in [0, 1], optional Defines the background color. Default: (1, 1, 1) foregroundcolor : tuple of float values with RGB code in [0, 1], optional Defines the foreground color (e.g., colorbartitle color). Default: (0, 0, 0) kwargs : key-value mapping Keyword arguments for `mayavi.mlab.triangular_mesh()` Returns ------- scene : mayavi.Scene Scene object containing plot """ return plot_fslr(data, lhlabel, rhlabel, surf_atlas='conte69', surf_type=surf, vmin=vmin, vmax=vmax, colormap=colormap, colorbar=colorbar, num_labels=num_labels, orientation=orientation, colorbartitle=colorbartitle, backgroundcolor=backgroundcolor, foregroundcolor=foregroundcolor, **kwargs)
[docs] def plot_fslr(data, lhlabel, rhlabel, surf_atlas='conte69', surf_type='midthickness', vmin=None, vmax=None, colormap='viridis', colorbar=True, num_labels=4, orientation='horizontal', colorbartitle=None, backgroundcolor=(1, 1, 1), foregroundcolor=(0, 0, 0), **kwargs): """ Plot surface `data` on a given fsLR32k atlas. Parameters ---------- data : (N,) array_like Surface data for N parcels lhlabel : str Path to .gii file (generic GIFTI file) containing labels to N/2 parcels on the left hemisphere rhlabel : str Path to .gii file (generic GIFTI file) containing labels to N/2 parcels on the right hemisphere surf_atlas: {'conte69', 'yerkes19'}, optional Surface atlas on which to plot 'data'. Default: 'conte69' surf_type : {'midthickness', 'inflated', 'vinflated'}, optional Type of brain surface. Default: 'midthickness' vmin : float, optional Minimum value to scale the colormap. If None, the min of the data will be used. Default: None vmax : float, optional Maximum value to scale the colormap. If None, the max of the data will be used. Default: None colormap : str, optional Any colormap from matplotlib. Default: 'viridis' colorbar : bool, optional Wheter to display a colorbar. Default: True num_labels : int, optional The number of labels to display on the colorbar. Available only if colorbar=True. Default: 4 orientation : str, optional Defines the orientation of colorbar. Can be 'horizontal' or 'vertical'. Available only if colorbar=True. Default: 'horizontal' colorbartitle : str, optional The title of colorbar. Available only if colorbar=True. Default: None backgroundcolor : tuple of float values with RGB code in [0, 1], optional Defines the background color. Default: (1, 1, 1) foregroundcolor : tuple of float values with RGB code in [0, 1], optional Defines the foreground color (e.g., colorbartitle color). Default: (0, 0, 0) kwargs : key-value mapping Keyword arguments for `mayavi.mlab.triangular_mesh()` Returns ------- scene : mayavi.Scene Scene object containing plot """ from ..datasets import fetch_conte69, fetch_yerkes19 try: from mayavi import mlab except ImportError: raise ImportError('Cannot use plot_fslr() if mayavi is not ' 'installed. Please install mayavi and try again.') from None opts = dict() opts.update(**kwargs) try: if surf_atlas == 'conte69': surface = fetch_conte69()[surf_type] elif surf_atlas == 'yerkes19': surface = fetch_yerkes19()[surf_type] except KeyError: raise ValueError('Provided surf "{}" is not valid. Must be one of ' '[\'midthickness\', \'inflated\', \'vinflated\']' .format(surf_type)) from None lhsurface, rhsurface = [nib.load(s) for s in surface] lhlabels = nib.load(lhlabel).darrays[0].data rhlabels = nib.load(rhlabel).darrays[0].data lhvert, lhface = [d.data for d in lhsurface.darrays] rhvert, rhface = [d.data for d in rhsurface.darrays] # add NaNs for medial wall data = np.append(np.nan, data) # get lh and rh data lhdata = np.squeeze(data[lhlabels.astype(int)]) rhdata = np.squeeze(data[rhlabels.astype(int)]) # plot lhplot = mlab.figure() rhplot = mlab.figure() lhmesh = mlab.triangular_mesh(lhvert[:, 0], lhvert[:, 1], lhvert[:, 2], lhface, figure=lhplot, colormap=colormap, mask=np.isnan(lhdata), scalars=lhdata, vmin=vmin, vmax=vmax, **opts) lhmesh.module_manager.scalar_lut_manager.lut.nan_color = [0.863, 0.863, 0.863, 1] lhmesh.update_pipeline() if colorbar is True: mlab.colorbar(title=colorbartitle, nb_labels=num_labels, orientation=orientation) rhmesh = mlab.triangular_mesh(rhvert[:, 0], rhvert[:, 1], rhvert[:, 2], rhface, figure=rhplot, colormap=colormap, mask=np.isnan(rhdata), scalars=rhdata, vmin=vmin, vmax=vmax, **opts) rhmesh.module_manager.scalar_lut_manager.lut.nan_color = [0.863, 0.863, 0.863, 1] rhmesh.update_pipeline() if colorbar is True: mlab.colorbar(title=colorbartitle, nb_labels=num_labels, orientation=orientation) mlab.view(azimuth=180, elevation=90, distance=450, figure=lhplot) mlab.view(azimuth=180, elevation=-90, distance=450, figure=rhplot) mlab.figure(bgcolor=backgroundcolor, fgcolor=foregroundcolor, figure=lhplot) mlab.figure(bgcolor=backgroundcolor, fgcolor=foregroundcolor, figure=rhplot) return lhplot, rhplot
[docs] def plot_fsaverage(data, *, lhannot, rhannot, order='lr', mask=None, noplot=None, subject_id='fsaverage', subjects_dir=None, vmin=None, vmax=None, **kwargs): """ Plot `data` to fsaverage brain using `annot` as parcellation. Parameters ---------- data : (N,) array_like Data for `N` parcels as defined in `annot` lhannot : str Filepath to .annot file containing labels to parcels on the left hemisphere. If a full path is not provided the file is assumed to exist inside the `subjects_dir`/`subject`/label directory. rhannot : str Filepath to .annot file containing labels to parcels on the right hemisphere. If a full path is not provided the file is assumed to exist inside the `subjects_dir`/`subject`/label directory. order : str, optional Order of the hemispheres in the data vector (either 'LR' or 'RL'). Default: 'LR' mask : (N,) array_like, optional Binary array where entries indicate whether values in `data` should be masked from plotting (True = mask; False = show). Default: None noplot : list, optional List of names in `lhannot` and `rhannot` to not plot. It is assumed these are NOT present in `data`. By default 'unknown' and 'corpuscallosum' will never be plotted if they are present in the provided annotation files. Default: None subject_id : str, optional Subject ID to use; must be present in `subjects_dir`. Default: 'fsaverage' subjects_dir : str, optional Path to FreeSurfer subject directory. If not set, will inherit from the environmental variable $SUBJECTS_DIR. Default: None vmin : float, optional Minimum value for colorbar. If not provided, a robust estimation will be used from values in `data`. Default: None vmax : float, optional Maximum value for colorbar. If not provided, a robust estimation will be used from values in `data`. Default: None kwargs : key-value pairs Provided directly to :func:`~.plot_fsvertex` without modification. Returns ------- brain : surfer.Brain Plotted PySurfer brain Examples -------- >>> import numpy as np >>> from netneurotools.datasets import fetch_cammoun2012, \ fetch_schaefer2018 >>> from netneurotools.plotting import plot_fsaverage Plotting with the Cammoun 2012 parcellation we specify `order='RL'` because many of the Lausanne connectomes have data for the right hemisphere before the left hemisphere. >>> values = np.random.rand(219) >>> scale = 'scale125' >>> cammoun = fetch_cammoun2012('fsaverage', verbose=False)[scale] >>> plot_fsaverage(values, order='RL', ... lhannot=cammoun.lh, rhannot=cammoun.rh) # doctest: +SKIP Plotting with the Schaefer 2018 parcellation we can use the default parameter for `order`: >>> values = np.random.rand(400) >>> scale = '400Parcels7Networks' >>> schaefer = fetch_schaefer2018('fsaverage', verbose=False)[scale] >>> plot_fsaverage(values, ... lhannot=schaefer.lh, ... rhannot=schaefer.rh) # doctest: +SKIP """ def _decode_list(vals): """List decoder.""" return [val.decode() if hasattr(val, 'decode') else val for val in vals] subject_id, subjects_dir = _get_freesurfer_subjid(subject_id, subjects_dir) # cast data to float (required for NaNs) data = np.asarray(data, dtype='float') order = order.lower() if order not in ('lr', 'rl'): raise ValueError('order must be either \'lr\' or \'rl\'') if mask is not None and len(mask) != len(data): raise ValueError('Provided mask must be the same length as data.') if vmin is None: vmin = np.nanpercentile(data, 2.5) if vmax is None: vmax = np.nanpercentile(data, 97.5) # parcels that should not be included in parcellation drop = FREESURFER_IGNORE.copy() if noplot is not None: if isinstance(noplot, str): noplot = [noplot] drop += list(noplot) drop = _decode_list(drop) vtx_data = [] for annot, hemi in zip((lhannot, rhannot), ('lh', 'rh')): # loads annotation data for hemisphere, including vertex `labels`! if not annot.startswith(os.path.abspath(os.sep)): annot = os.path.join(subjects_dir, subject_id, 'label', annot) labels, _, names = nib.freesurfer.read_annot(annot) names = _decode_list(names) # get appropriate data, accounting for hemispheric asymmetry currdrop = np.intersect1d(drop, names) if hemi == 'lh': if order == 'lr': split_id = len(names) - len(currdrop) ldata, rdata = np.split(data, [split_id]) if mask is not None: lmask, rmask = np.split(mask, [split_id]) elif order == 'rl': split_id = len(data) - len(names) + len(currdrop) rdata, ldata = np.split(data, [split_id]) if mask is not None: rmask, lmask = np.split(mask, [split_id]) hemidata = ldata if hemi == 'lh' else rdata # our `data` don't include the "ignored" parcels but our `labels` do, # so we need to account for that. find the label ids that correspond to # those and set them to NaN in the `data vector` inds = sorted([names.index(n) for n in currdrop]) for i in inds: hemidata = np.insert(hemidata, i, np.nan) vtx = hemidata[labels] # let's also mask data, if necessary if mask is not None: maskdata = lmask if hemi == 'lh' else rmask maskdata = np.insert(maskdata, inds - np.arange(len(inds)), np.nan) vtx[maskdata[labels] > 0] = np.nan vtx_data.append(vtx) brain = plot_fsvertex(np.hstack(vtx_data), order='lr', mask=None, subject_id=subject_id, subjects_dir=subjects_dir, vmin=vmin, vmax=vmax, **kwargs) return brain
[docs] def plot_fsvertex(data, *, order='lr', surf='pial', views='lat', vmin=None, vmax=None, center=None, mask=None, colormap='viridis', colorbar=True, alpha=0.8, label_fmt='%.2f', num_labels=3, size_per_view=500, subject_id='fsaverage', subjects_dir=None, data_kws=None, **kwargs): """ Plot vertex-wise `data` to fsaverage brain. Parameters ---------- data : (N,) array_like Data for `N` parcels as defined in `annot` order : {'lr', 'rl'}, optional Order of the hemispheres in the data vector. Default: 'lr' surf : str, optional Surface on which to plot data. Default: 'pial' views : str or list, optional Which views to plot of brain. Default: 'lat' vmin : float, optional Minimum value for colorbar. If not provided, a robust estimation will be used from values in `data`. Default: None vmax : float, optional Maximum value for colorbar. If not provided, a robust estimation will be used from values in `data`. Default: None center : float, optional Center of colormap, if desired. Default: None mask : (N,) array_like, optional Binary array where entries indicate whether values in `data` should be masked from plotting (True = mask; False = show). Default: None colormap : str, optional Which colormap to use for plotting `data`. Default: 'viridis' colorbar : bool, optional Whether to display the colorbar in the plot. Default: True alpha : [0, 1] float, optional Transparency of plotted `data`. Default: 0.8 label_fmt : str, optional Format of colorbar labels. Default: '%.2f' number_of_labels : int, optional Number of labels to display on colorbar. Default: 3 size_per_view : int, optional Size, in pixels, of each frame in the plotted display. Default: 1000 subjects_dir : str, optional Path to FreeSurfer subject directory. If not set, will inherit from the environmental variable $SUBJECTS_DIR. Default: None subject : str, optional Subject ID to use; must be present in `subjects_dir`. Default: 'fsaverage' data_kws : dict, optional Keyword arguments for Brain.add_data() Returns ------- brain : surfer.Brain Plotted PySurfer brain """ # hold off on imports until try: from surfer import Brain except ImportError: raise ImportError('Cannot use plot_fsaverage() if pysurfer is not ' 'installed. Please install pysurfer and try again.') from None subject_id, subjects_dir = _get_freesurfer_subjid(subject_id, subjects_dir) # cast data to float (required for NaNs) data = np.asarray(data, dtype='float') # handle data_kws if None if data_kws is None: data_kws = {} if mask is not None and len(mask) != len(data): raise ValueError('Provided mask must be the same length as data.') order = order.lower() if order not in ['lr', 'rl']: raise ValueError('Specified order must be either \'lr\' or \'rl\'') if vmin is None: vmin = np.nanpercentile(data, 2.5) if vmax is None: vmax = np.nanpercentile(data, 97.5) # set up brain views if not isinstance(views, (np.ndarray, list)): views = [views] # size of window will depend on # of views provided size = (size_per_view * 2, size_per_view * len(views)) brain_kws = dict(background='white', size=size) brain_kws.update(**kwargs) brain = Brain(subject_id=subject_id, hemi='split', surf=surf, subjects_dir=subjects_dir, views=views, **brain_kws) hemis = ('lh', 'rh') if order == 'lr' else ('rh', 'lh') for n, (hemi, vtx_data) in enumerate(zip(hemis, np.split(data, 2))): # let's mask data, if necessary if mask is not None: maskdata = np.asarray(np.split(mask, 2)[n], dtype=bool) vtx_data[maskdata] = np.nan # we don't want NaN values plotted so set a threshold if they exist thresh, nanmask = None, np.isnan(vtx_data) if np.any(nanmask) > 0: thresh = np.nanmin(vtx_data) - 1 vtx_data[nanmask] = thresh thresh += 0.5 # finally, add data to this hemisphere! brain.add_data(vtx_data, vmin, vmax, hemi=hemi, mid=center, thresh=thresh, alpha=1.0, remove_existing=False, colormap=colormap, colorbar=colorbar, verbose=False, **data_kws) if alpha != 1.0: surf = brain.data_dict[hemi]['surfaces'] for _, s in enumerate(surf): s.actor.property.opacity = alpha s.render() # if we have a colorbar, update parameters accordingly if colorbar: # update label format, as desired surf = brain.data_dict[hemi]['surfaces'] cmap = brain.data_dict[hemi]['colorbars'] # this updates the format of the colorbar labels if label_fmt is not None: for n, cm in enumerate(cmap): cm.scalar_bar.label_format = label_fmt surf[n].render() # this updates how many labels are shown on the colorbar if num_labels is not None: for n, cm in enumerate(cmap): cm.scalar_bar.number_of_labels = num_labels surf[n].render() return brain