diff --git a/ExampleSurfacePlotFunction.m b/ExampleSurfacePlotFunction.m index b3b336b..641cebd 100644 --- a/ExampleSurfacePlotFunction.m +++ b/ExampleSurfacePlotFunction.m @@ -39,7 +39,8 @@ end if nargin < 6 - climits = [nanmin(data) nanmax(data)]; + climits = [nanmin(data(:,1)) nanmax(data(:,1))]; + % use first col in case data is in annot file format end if nargin < 7 diff --git a/bluewhitered.m b/bluewhitered.m new file mode 100644 index 0000000..e81f080 --- /dev/null +++ b/bluewhitered.m @@ -0,0 +1,101 @@ +function newmap = bluewhitered(m) +%BLUEWHITERED Blue, white, and red color map. +% BLUEWHITERED(M) returns an M-by-3 matrix containing a blue to white +% to red colormap, with white corresponding to the CAXIS value closest +% to zero. This colormap is most useful for images and surface plots +% with positive and negative values. BLUEWHITERED, by itself, is the +% same length as the current colormap. +% +% Examples: +% ------------------------------ +% figure +% imagesc(peaks(250)); +% colormap(bluewhitered(256)), colorbar +% +% figure +% imagesc(peaks(250), [0 8]) +% colormap(bluewhitered), colorbar +% +% figure +% imagesc(peaks(250), [-6 0]) +% colormap(bluewhitered), colorbar +% +% figure +% surf(peaks) +% colormap(bluewhitered) +% axis tight +% +% See also HSV, HOT, COOL, BONE, COPPER, PINK, FLAG, +% COLORMAP, RGBPLOT. +if nargin < 1 + m = size(get(gcf,'colormap'),1); +end +bottom = [0 0 0.5]; +botmiddle = [0 0.5 1]; +middle = [1 1 1]; +topmiddle = [1 0 0]; +top = [0.5 0 0]; +% Find middle +lims = get(gca, 'CLim'); +% Find ratio of negative to positive +if (lims(1) < 0) & (lims(2) > 0) + % It has both negative and positive + % Find ratio of negative to positive + ratio = abs(lims(1)) / (abs(lims(1)) + lims(2)); + neglen = round(m*ratio); + poslen = m - neglen; + + % Just negative + new = [bottom; botmiddle; middle]; + len = length(new); + oldsteps = linspace(0, 1, len); + newsteps = linspace(0, 1, neglen); + newmap1 = zeros(neglen, 3); + + for i=1:3 + % Interpolate over RGB spaces of colormap + newmap1(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); + end + + % Just positive + new = [middle; topmiddle; top]; + len = length(new); + oldsteps = linspace(0, 1, len); + newsteps = linspace(0, 1, poslen); + newmap = zeros(poslen, 3); + + for i=1:3 + % Interpolate over RGB spaces of colormap + newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); + end + + % And put 'em together + newmap = [newmap1; newmap]; + +elseif lims(1) >= 0 + % Just positive + new = [middle; topmiddle; top]; + len = length(new); + oldsteps = linspace(0, 1, len); + newsteps = linspace(0, 1, m); + newmap = zeros(m, 3); + + for i=1:3 + % Interpolate over RGB spaces of colormap + newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); + end + +else + % Just negative + new = [bottom; botmiddle; middle]; + len = length(new); + oldsteps = linspace(0, 1, len); + newsteps = linspace(0, 1, m); + newmap = zeros(m, 3); + + for i=1:3 + % Interpolate over RGB spaces of colormap + newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); + end + +end \ No newline at end of file diff --git a/checkVertsFacesRoisData.m b/checkVertsFacesRoisData.m new file mode 100644 index 0000000..b27bd35 --- /dev/null +++ b/checkVertsFacesRoisData.m @@ -0,0 +1,133 @@ +function [verts,faces,rois,data] = checkVertsFacesRoisData(varargin) +%% Checks size and shape of data describing brain surface mesh +% Ensures that verts is V x 3, faces is F x 3, rois is V x 1, and data is V x 1 +% or R x 1 +% +% +%% Syntax +% [verts,faces,rois,data] = checkVertsFacesRoisData(verts,faces,rois,data) +% +% +%% Input Arguments +% verts - xyz coordinates of vertices (three column matrix) +% faces - triangulation ie. IDs of vertices making up each face (three column matrix) +% rois - roi allocation of each vertex (V x 1 vector) +% data - data allocated to each vertex or roi (V x 1 or R x 1 vector) +% +% +%% Name-Value Arguments +% checkContents - flag to check values within matrices (true (default) | false) +% If `checkContents` is set to false, only the shape of the input matrices will +% be tested. If `checkContents` is true, then the contents will also be checked +% e.g. that `faces` uses all the vertices and that `rois` cannot have negative +% values. +% +% fillEmpty - flag to populate `rois` and `data`, if the inputs are empty (false (default) | true) +% If set to true, rois will be set to all ones, and data will be set to the +% second column of `verts`. +% +% +%% Output Arguments +% verts, faces, rois, data - transposed if needed +% +% +%% See Also +% plotSurfaceROIBoundary, read_vtk +% +% +%% Authors +% Mehul Gajwani, Monash University, 2023 +% +% + + +%% Prelims +ip = inputParser; +validationFcn = @(x) (isnumeric(x) && ismatrix(x)); +addOptional(ip, 'verts', [], @(x) validationFcn(x)); +addOptional(ip, 'faces', [], @(x) validationFcn(x)); +addOptional(ip, 'rois', [], @(x) validationFcn(x) || islogical(x)); +addOptional(ip, 'data', [], @(x) validationFcn(x) || islogical(x)); + +addParameter(ip, 'checkContents', true, @islogical); +addParameter(ip, 'fillEmpty', false, @islogical); + +parse(ip, varargin{:}); +verts = ip.Results.verts; +faces = ip.Results.faces; +rois = +ip.Results.rois; +data = +ip.Results.data; + + +%% Check shape +% Check second dimension and transpose if needed + +matrices = {verts, faces, rois, data}; +names = {'Vertices', 'Faces', 'ROIs', 'Data'}; +target2ndDim = {3, 3, 1, 1}; + +for ii = 1:length(matrices) + if isempty(matrices{ii}) + % skip - do nothing + elseif size(matrices{ii}, 2) == target2ndDim{ii} + % good - do nothing + elseif size(matrices{ii}, 1) == target2ndDim{ii} + matrices{ii} = matrices{ii}.'; + else + error('%s must have a dimension of size %d', names{ii}, target2ndDim{ii}); + end +end + +[verts, faces, rois, data] = deal(matrices{:}); + +if isallhere({verts, rois}) + assert(size(verts, 1) == size(rois, 1), ... + 'Vertices and ROIs must have the same number of points'); +end + + +%% Fill empty, if desired +if ip.Results.fillEmpty + if isempty(rois); rois = ones(size(verts, 1),1); end + if isempty(data); data = verts(:, 2); end +end + + +%% Check contents +if ip.Results.checkContents + if isallhere({verts, faces}) + if max(faces, [], "all") ~= size(verts, 1) + warning('The vertices are not all mapped to the faces'); + end + end + + if isallhere({faces}); mustBeNonnegative(faces); mustBeInteger(faces); end + + if isallhere({rois}) + mustBeNonnegative(rois); mustBeInteger(rois); + + goodRois = ( all(rois) && (length(unique(rois))==max(rois)) ) || ... % no non-zeros rois + (~all(rois) && (length(unique(rois))==max(rois)+1) ); % some non-zero rois + + if ~goodRois + warning('ROIs appear to not be sequential'); + end + end + + if isallhere({data, verts}) || isallhere({data, rois}) + assert(size(data, 1) == size(verts, 1) || size(data, 1) == max(rois), ... + 'Data should be one per vertex or one per ROI'); + end +end + +end % main + +function out = isallhere(inp) +% returns true if inp is (i) a non-empty matrix OR (ii) a cell with ALL non-empty entries +% else returns false +if ~iscell(inp); out = ~isempty(inp); return; end +out = ~any(cellfun(@isempty, inp)); +end + + + diff --git a/demo_plotSurfaceROIBoundary.m b/demo_plotSurfaceROIBoundary.m index 7711f83..4b66f0c 100644 --- a/demo_plotSurfaceROIBoundary.m +++ b/demo_plotSurfaceROIBoundary.m @@ -16,7 +16,7 @@ plotSurfaceROIBoundary(surface,lh_rand200,1:100,'faces',jet(100),2); % The following options set up the patch object to look pretty. This works -% well for the left hemisphere (medial and lateral). Change the inputs to +% well for the left hemisphere (medial and lateral). Change the inputs to % 'view' to see the brain from different angles ([-90 0] for left and [90 0] % for right I find works well) @@ -26,8 +26,7 @@ axis off axis tight axis equal -axis vis3d - +%% ax2 = axes('Position',[0.01+(1/3) 0 .3 1]); cmap = flipud(hot(130)); @@ -45,14 +44,13 @@ axis off axis tight axis equal -axis vis3d - +%% ax3 = axes('Position',[0.01+(2/3) 0 .3 1]); % This plots sulcal depth, which is defined for each vertex surface.vertices = lh_verts; -plotSurfaceROIBoundary(surface,lh_aparc,lh_sulc,'centroid',parula(100),4); +plotSurfaceROIBoundary(surface,lh_aparc,lh_sulc,'centroid',parula(100),1); camlight(80,-10); camlight(-80,-10); @@ -63,7 +61,7 @@ axis equal axis vis3d -% Demonstrate different types of plots +%% Demonstrate different types of plots surface.vertices = lh_inflated_verts; boundary_type = {'faces','midpoint','centroid','edge_vertices','edge_faces',... @@ -71,7 +69,7 @@ linewidth = 4; -% Set up the colors for each vertex. This mirrors how the redone colour +% Set up the colors for each vertex. This mirrors how the redone colour % assignment is performed by makeFaceVertexCData lh_rand200_color_map = lines(34); lh_rand200_ = lh_rand200; @@ -82,11 +80,11 @@ lh_rand200_color(isnan(lh_rand200_),:) = .5; FaceVertexCData = makeFaceVertexCData(surface.vertices,surface.faces,lh_rand200,lh_rand200,lh_rand200_color_map); - + for i = 1:10 figure('Position',[0 0 1680 933]) - + % The data here is just each ROIs own ID number if i < 6 @@ -99,10 +97,10 @@ if i == 6 savename = ['sulc_',boundary_type{i},'_flat.png']; else - savename = ['sulc_',boundary_type{i},'_interp.png']; + savename = ['sulc_',boundary_type{i},'_interp.png']; end end - + p = plotSurfaceROIBoundary(surface,lh_rand200,data,boundary_type{i},cmap,linewidth); camlight(80,-10); @@ -114,28 +112,28 @@ axis tight axis equal %axis vis3d - + % Mapping on the ROI id of each vertex to help with understanding how % this all works hold on - + s = scatter3(lh_inflated_verts(:,1)*1.01,lh_inflated_verts(:,2),lh_inflated_verts(:,3),40,lh_rand200_color,'filled'); s.Clipping = 'off'; s.MarkerEdgeColor = 'k'; s.LineWidth = 1; - - % This just zooms into the area of interest + + % This just zooms into the area of interest ylim([-25.2699 -8.7600]) zlim([20.2174 31.3705]) p.EdgeColor = 'k'; p.EdgeAlpha = .5; - + % If you wanted to make a colorbar, this is what you would have to do: % colormap(cmap) % caxis([min(data) max(data)]) % c = colorbar - + %print(['./figures/',savename],'-dpng') end diff --git a/examples/fsLR_32k_Schaefer100-1000.mat b/examples/fsLR_32k_Schaefer100-1000.mat new file mode 100644 index 0000000..bdb01b4 Binary files /dev/null and b/examples/fsLR_32k_Schaefer100-1000.mat differ diff --git a/examples/fsLR_32k_facesAndVerts.mat b/examples/fsLR_32k_facesAndVerts.mat new file mode 100644 index 0000000..7d76a9c Binary files /dev/null and b/examples/fsLR_32k_facesAndVerts.mat differ diff --git a/examples/plotBrain.md b/examples/plotBrain.md new file mode 100644 index 0000000..964c168 --- /dev/null +++ b/examples/plotBrain.md @@ -0,0 +1,320 @@ + +# plotBrain Wrapper for plotting multiple views using PLOTBRAIN + + +# Contents + - Dependencies + - Syntax + - Description + - Examples + - Input Arguments + - Name-Value Arguments + - Output Arguments + - See also + - Authors + - TODO + + +Plots multiple brain views/multiple hemispheres/multiple maps. + + +![](../IMAGES/plotBrain_1.svg) + + +![](../IMAGES/plotBrain_2.svg) + + +![](../IMAGES/plotBrain_3.svg) + +# Dependencies + +```matlab:Code(Display) +plotSurfaceROIBoundary (Stuart Oldham): +https://github.com/StuartJO/plotSurfaceROIBoundary or +https://github.com/StuartJO/BrainSurfaceAnimation +``` + + +# Syntax + +```matlab:Code(Display) +plotBrain(verts, faces, rois, data) +plotBrain(surface, rois, data) +``` + + +```matlab:Code(Display) +plotBrain('lh', {verts, faces, rois, data}) +plotBrain('rh', {verts, faces, rois, data}) +plotBrain('lh', {verts, faces, rois, data}, 'rh', {verts, faces, rois, data}) +plotBrain(___, {surface, rois, data}) +``` + + +```matlab:Code(Display) +plotBrain(___, Name, Value) +patches = plotBrain(___) +[patches, boundaries] = plotBrain(___) +[patches, boundaries, outerTiledLayout] = plotBrain(___) +[patches, boundaries, outerTiledLayout, innerTiledLayouts] = plotBrain(___) +``` + +# Description + +`plotBrain(verts, faces, rois, data)` plots the patch object with coordinates given by `verts` and `faces`, and coloured according to `rois` and `data`. Left/right hemisphere will be deduced based on the×coordinates of `verts`. + + +`plotBrain(surface, rois, data)` plots the patch object given by the struct `surface`, and coloured according to `rois` and `data`. + + +`plotBrain('lh', {verts, faces, rois, data})` plots the surface of the left hemisphere, based on the data presented in the cell array that follows. This cell array should contain the data used to generate indivual plots. + + +`plotBrain('rh', {verts, faces, rois, data})` does the same as the above for the right hemisphere. + + +`plotBrain('lh', {verts, faces, rois, data}, 'rh', {verts, faces, rois, data})` plots both the left and right hemisphere, as above. + + +`plotBrain(_, {surface, rois, data})` does the same as the above for the patch object given by struct `surface`. + + +`plotBrain(_, Name, Value)` specifies additional parameters for plotting. + + +`patches = plotBrain(_)` returns the patch objects generated by plotSurfaceROIBoundary. You can use this syntax with any of the input argument combinations in the previous syntaxes. + + +`[patches, boundaries] = plotBrain(_)` also returns the boundary plots generated by plotSurfaceROIBoundary. You can use this syntax with any of the input argument combinations in the previous syntaxes. + + +`[patches, boundaries, outerTiledLayout] = plotBrain(_)` also returns the (outer) tiledlayout in which the surfaces are plotted. You can use this syntax with any of the input argument combinations in the previous syntaxes. + + +`[patches, boundaries, outerTiledLayout, innerTiledLayouts] = plotBrain(_)` also returns the (inner) tiledlayouts when `'groupBy'` is specified. You can use this syntax with any of the input argument combinations in the previous syntaxes. + +# Examples + +```matlab:Code(Display) +figure; plotBrain('lh', {lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'}); +figure('Position', [100 100 1200 420]); plotBrain('rh', {rh_verts, rh_faces, Scha17_parcs.rh_scha100, sum(rh_verts.^2,2)-4000}, 'plotSurface', {'midpoint', @bluewhitered}, 'colorbarOn', true, 'view', {'rl', 'rm'}, 'groupBy', 'data'); +figure; [p, b] = plotBrain('lh', {lh_verts, lh_faces, logical(Scha17_parcs.lh_scha100), lh_verts(:,2)}, 'rh', {rh_verts, rh_faces, Scha17_parcs.rh_scha100, (1:50)'.^2}, 'viewOrder', {'rl', 'rm', 'll', 'lm'}, 'tiledLayoutOptions', {2, 2, 'TileSpacing', 'none'}); +``` + +# Input Arguments + +```matlab:Code(Display) +verts - xyz coordinates of surface (V×3 matrix) +``` + + +```matlab:Code(Display) +faces - triangulation of surface (F×3 matrix) +``` + + +```matlab:Code(Display) +rois - parcellation of surface (V×1 vector | V×? matrix) +Vector or matrix indicating the ROI ID of each vertex. Use 0 to index +unallocated vertices (e.g. the medial wall). If `rois` is a vector, the same +parcellation will be applied to each column in `data`. Otherwise, the number +of columns in `rois` should match the number of columns in `data` (and one +parcellation will be used for each map). +``` + + +```matlab:Code(Display) +data - brain maps to be plotted (R×1 vector | R×? matrix | V×1 vector | V×? matrix | 1×? cell array ) +Data can be specified at either the ROI level (where `size(data, 1) == max(rois)`) +or the vertex level (where `size(data, 1) == size(verts,1)`). If multiple +columns are specified or a cell array is used, each column/cell is treated as +a map, and several brains will be plotted. +``` + + +```matlab:Code(Display) +surface - structure to be plotted with fields 'vertices' and 'faces' +``` + + +```matlab:Code(Display) +lh - data to be passed to plotBrain for left hemisphere (cell array) +This should be a cell array containing the data that would be used to plot a +single view using plotBrain i.e. {verts, faces, rois, data}. +``` + + +```matlab:Code(Display) +rh - data to be passed to plotBrain for right hemisphere (cell array) +This should be a cell array containing the data that would be used to plot a +single view using plotBrain i.e. {verts, faces, rois, data}. +``` + + +# Name-Value Arguments + +```matlab:Code(Display) +viewOrder - Views to use for each of the inputs (cell array) +The views to use to plot each view of the left and right hemispheres. These +can be in the form of vectors of camera positions, or as the shorthand +'ll'/'lm'/'rl'/'rm' (left/right lateral/medial). +``` + + +```matlab:Code(Display) +hemiOrder - Which hemisphere to plot for each view (cell array) +If `viewOrder` is specified using camera angles and both left and right +hemisphere are specified, specify `hemiOrder` as a cell array of `'l'` and +`'r'` to indicate which camera angle applies to which hemiphere. +``` + + +```matlab:Code(Display) +parent - Handle to parent axes (axes handle | tiledlayout handle) +If specified as a tiledlayout handle, use in conjunction with `PARENTHANDLE.Layout.Tile` +to position the current plot within the parent tiledlayout. See also the +examples in the live script. Also consider utilising with `PARENTHANDLE.Layout.TileSpan`. +``` + + +```matlab:Code(Display) +forceTiledlayout - Flag to force 1 map to be generated onto a tiledlayout (false (default) | true) +For simplicity, if generating only 1 map, this will NOT use tiledlayout. +However, if a tiledlayout output is desired (e.g. if parent is a +tiledlayout), this should be set to `true`. +``` + + +```matlab:Code(Display) +tiledlayoutOptions - Options to be passed through when generating outerTiledlayout (cell array) +NOTE that this is unravelled and passed directly through to tiledlayout. By +default, the 'flow' layout is used. See the examples for how this can be +changed. +``` + + +```matlab:Code(Display) +groupBy - Flag to group together brain maps onto sub-tiledlayouts ('none' (default) | 'data' | 'view' ) +If set to 'none', each dataset and brainmap will be plotted on its own tile +in outerTiledlayout. Note that innerTiledlayout will not be generated in this +case. If set to 'data', the multiple views of each dataset will be generated +on their own individual tiledlayout (i.e. innerTiledlayout); these will then +be embedded into the outerTiledlayout. A similar process occurs for 'view'. +See also the examples in the live script. +``` + + +```matlab:Code(Display) +tiledlayout2Options - Options to be passed through when generating each innerTiledlayout (cell array) +NOTE that this is unravelled and passed directly through to tiledlayout. By +default, the 'flow' layout is used. See the examples for how this can be +changed. +``` + + +```matlab:Code(Display) +titles - Titles for each group/map (cell array) +There must be one element for each group (if '`groupBy`' is specified)/map. +``` + + +```matlab:Code(Display) +clim - New color limits to apply to all the maps (vector of the form |[cmin max]|) +Specification of `clim` will set `colorscheme` to global. +``` + + +```matlab:Code(Display) +colormap - Colormap for plotting (colormap name | three-column matrix of RGB triples | function handle) +Colormap for the new color scheme, specified as a colormap name, a +three-column matrix of RGB triplets, or a function handle (which may be +useful for diverging/dynamic colormaps). Note that it is difficult to change +the colors of the patch object after generation (due to the functionality of +plotSurfaceROIBoundary): setting the correct colormap when plotBrain is +called is recommended. +``` + + +```matlab:Code(Display) +colorscheme - Flag to use same clims for all brainmaps ('indiv' (default) | 'global') +If set to 'global', the maximum and minimum values from both the left and +right hemispheres will be used to set the color limits for each plot. If +'clim' is specified, those limits will instead be applied to each plot. +``` + + +```matlab:Code(Display) +colorbarOn - Flag to plot colorbar for each map/group (false (default) | true) +If set to true, a colorbar will be plotted for each indidivual map or each +group (if 'groupBy' is specified). This is recommended, as accessing axis +color properties after plotBrain is called can be difficult (due to the +functionality of plotSurfaceROIBoundary). +``` + + +```matlab:Code(Display) +colorbarOptions - Options to use when generating colorbars (cell array) +This is unravelled and passed through directly to COLORBAR. This should +contain optional arguments for modifying the generation of the colorbar. +``` + + +```matlab:Code(Display) +colorbarLocation - Location of colorbar ('east' (default) | string) +``` + + +```matlab:Code(Display) +plotSurfaceROIBoundaryOptions - options to be passed through to plotSurfaceROIBoundary (cell array) +NOTE that this is unravelled and passed through directly to +plotSurfaceROIBoundary. This should contain supplementary arguments to be +passed to plotSurfaceROIBoundary for modifying the generation of the plots, +including the method for separating ROIs. See plotSurfaceROIBoundary for more +information regarding its optional inputs. +``` + + +# Output Arguments + +```matlab:Code(Display) +p - output from plotSurfaceROIBoundary (cell array) +Cell array containing all the outputs from plotBrain, in order of tile. +``` + + +```matlab:Code(Display) +boundary_plots - output from plotSurfaceROIBoundary (cell array) +Cell array containing all the outputs from plotBrain, in order of tile. +``` + + +```matlab:Code(Display) +outerTiledLayout - tiledlayout of all brain maps (tiledlayout) +This is the tiledlayout in which all the brain maps are embedded. +``` + + +```matlab:Code(Display) +innerTiledLayouts - tiledlayouts of each view/dataset of brain maps (cell array) +If `'groupBy'` is specified, each group is plotted on its own tiledlayout, +within outerTiledlayout. This is the cell array of all of those. +``` + + +# See also + +```matlab:Code(Display) +plotSurfaceROIBoundary, VIEW, PATCH, TILEDLAYOUT +``` + + +# Authors + +Mehul Gajwani, Monash University, 2023 + +# TODO + + - consider reviewing color scale for each axis + - consider adding mechanism to change only the tiledlayout size + - consider allowing different options to be passed throught for each plot + diff --git a/examples/plotBrain_examples_live.md b/examples/plotBrain_examples_live.md new file mode 100644 index 0000000..058249b --- /dev/null +++ b/examples/plotBrain_examples_live.md @@ -0,0 +1,649 @@ +# Using plotBrain.m +# Prelims + + \item{ If not already installed, please install **plotSurfaceROIBoundary** (originally written by Stuart Oldham) from [here](https://github.com/magnesium2400/plotSurfaceROIBoundary), [here](https://github.com/StuartJO/plotSurfaceROIBoundary), or [here](https://github.com/StuartJO/BrainSurfaceAnimation)\href{https://github.com/StuartJO/BrainSurfaceAnimation}{} } + - Please load data containing surface information (vertices and faces) and parcellations (e.g. from the DATA or examples folders) + + +```matlab:Code +if ~exist('lh_verts', 'var'); try lh_verts = lh_verts_midthickness; catch; end; end +``` + +# Plotting One Brain Surface +## Introduction + + +The simplest way to using plotBrain.m requires: + + + + - Vertices and faces of the surface to be plotted (either in a struct, or in two separate variables) + - A parcellation + - This can be replaced with a column of 1's and 0's to denote vertices to be included or excluded, without specifying ROI boundaries + - Data to be plotted - at either vertex resolution or parcel resolution + + + +Note that parcels with the label 0 will be colored grey (e.g. the medial wall). + + + + +A few examples are as follows: + + + +```matlab:Code +% Parcelled data, plotted in each parcel +% Check the size and shape of each input +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'); +``` + + +![./plotBrain_examples_live_media/figure_0.png +](./plotBrain_examples_live_media/figure_0.png +) + + +```matlab:Code + +% Unparcellated data (i.e. dense vertex-level data), plotted with ROI boundaries +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, lh_verts(:,2)); +``` + + +![./plotBrain_examples_live_media/figure_1.png +](./plotBrain_examples_live_media/figure_1.png +) + + +```matlab:Code + +% Unparcellated data (i.e. dense vertex-level data), plotted without ROI boundaries +figure; plotBrain(lh_verts, lh_faces, logical(Scha17_parcs.lh_scha100), lh_verts(:,2)); +``` + + +![./plotBrain_examples_live_media/figure_2.png +](./plotBrain_examples_live_media/figure_2.png +) + + +```matlab:Code + +% Display data in the medial wall too +figure; plotBrain(lh_verts, lh_faces, ones(size(lh_verts, 1), 1), lh_verts(:,2)); +``` + + +![./plotBrain_examples_live_media/figure_3.png +](./plotBrain_examples_live_media/figure_3.png +) + + +```matlab:Code + +% You can also input a struct instead of two matrices +brainSurface = struct('vertices', lh_verts, 'faces', lh_faces); +figure; plotBrain(brainSurface, ones(size(lh_verts, 1), 1), lh_verts(:,2)); +``` + + +![./plotBrain_examples_live_media/figure_4.png +](./plotBrain_examples_live_media/figure_4.png +) + + + +When used in this way, plotBrain is a wrapper for plotSurfaceROIBoundary. Have a look at the examples and documentation for more uses of this function, including how to plot data for only certain ROIs. + + +## More Options (using Name-Value Arguments) + + +plotBrain also contains name-value inputs that aid in modifying the appearance of the figure. Some examples are below: + + + +```matlab:Code +% Change the view to left medial ('lm') +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).' - 25, 'view', 'lm'); +``` + + +![./plotBrain_examples_live_media/figure_5.png +](./plotBrain_examples_live_media/figure_5.png +) + + +```matlab:Code + +% Change the colormap +% Note that diverging/dynamic colormaps must be specified as function handles +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).' - 25, 'colormap', cool); colorbar; +``` + + +![./plotBrain_examples_live_media/figure_6.png +](./plotBrain_examples_live_media/figure_6.png +) + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).' - 25, 'colormap', @bluewhitered); colorbar; +``` + + +![./plotBrain_examples_live_media/figure_7.png +](./plotBrain_examples_live_media/figure_7.png +) + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).' - 25, 'colormap', jet(5)); colorbar; +``` + + +![./plotBrain_examples_live_media/figure_8.png +](./plotBrain_examples_live_media/figure_8.png +) + + +```matlab:Code + +% Truncate the colormap limits - compare with above map +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).' - 25, ... + 'colormap', jet(5), 'clim', [-10 10]); colorbar; +``` + + +![./plotBrain_examples_live_media/figure_9.png +](./plotBrain_examples_live_media/figure_9.png +) + + +```matlab:Code + +% Pass arguments to plotSurfaceROIBoundary +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).' - 25, ... + 'plotSurfaceROIBoundaryOptions', {'midpoint', jet(5), 1} ); colorbar; +``` + + +![./plotBrain_examples_live_media/figure_10.png +](./plotBrain_examples_live_media/figure_10.png +) + +# Plotting Multiple Brain Maps + + +Out of the box, plotBrain also includes support for plotting more than one brain at a time (using `tiledlayout`). + + +## Changing only the data map + + +You can plot more than one brain map at a time by changing your data input from a column vector to a matrix: + + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, rand(50, 4)); +``` + + +![./plotBrain_examples_live_media/figure_11.png +](./plotBrain_examples_live_media/figure_11.png +) + +## Changing only the parcellation + + +You can plot more than one parcellation at a time by changing the `rois` input (with data at the vertex level): + + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, ... + [Scha17_parcs.lh_scha100, Scha17_parcs.lh_scha200, Scha17_parcs.lh_scha300, Scha17_parcs.lh_scha400], ... + lh_verts(:,2)+lh_verts(:,3), 'plotSurface', {'midpoint', parula(100), 2}); +``` + + +![./plotBrain_examples_live_media/figure_12.png +](./plotBrain_examples_live_media/figure_12.png +) + +## Changing only the view + + +You can change the view using the `'view'` Name-Value argument: the input should be a cell array consisting of either camera angles, or the keyphrases `'ll'/'lm'/'rl'/'rm' `(left/right lateral/medial), or a combination. + + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, rand(50, 1), ... + 'view', {'ll', [-45 0], [45 0], 'lm'}); +``` + + +![./plotBrain_examples_live_media/figure_13.png +](./plotBrain_examples_live_media/figure_13.png +) + +## Changing the data map and the parcellation + + +If there are multiple parcellations and data maps provided, these will be simultaneously varied. Note that if you are providing multiple data maps at different parcellation resolutions, this will have to be as a cell arrray. + + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, ... + {Scha17_parcs.lh_scha100, Scha17_parcs.lh_scha200, Scha17_parcs.lh_scha300, Scha17_parcs.lh_scha400}, ... + {(1:50).', (1:100).', (1:150).', (1:200).'}); +``` + + +![./plotBrain_examples_live_media/figure_14.png +](./plotBrain_examples_live_media/figure_14.png +) + +## Changing the shape of the grid + + +Grids are constructed using `tiledlayout`. The default input when `tiledlayout` is called is the argument `'flow'`. However, this can be changed using the name-value argument `'tiledlayoutOptions' `in plotBrain: + + + +```matlab:Code +% Compare the two figures: +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, rand(50, 4)); +``` + + +![./plotBrain_examples_live_media/figure_15.png +](./plotBrain_examples_live_media/figure_15.png +) + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, rand(50, 4), 'tiledlayoutOptions', {1, 4}); +``` + + +![./plotBrain_examples_live_media/figure_16.png +](./plotBrain_examples_live_media/figure_16.png +) + + + + +The tiledlayout object can also be accessed (as an output argument) and modified e.g. to add titles. + + + +```matlab:Code +figure; [~,~,tl] = plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, rand(50, 1), ... + 'view', {'ll', [-45 0], [45 0], 'lm'}, 'tiledlayoutOptions', {4, 1, 'TileSpacing', 'loose'}); +for ii = 1:4 + title(nexttile(tl, ii), sprintf("View %d", ii)); +end +``` + + +![./plotBrain_examples_live_media/figure_17.png +](./plotBrain_examples_live_media/figure_17.png +) + +# Plotting Two Hemispheres + + +To plot two hemispheres, input the verts, faces, rois, and data for each hemisphere in two seperate cell arrays: + + + +```matlab:Code +figure; +plotBrain('lh', {lh_verts, lh_faces, Scha7_parcs.lh_scha100, rand(50,1)}, ... + 'rh', {rh_verts, rh_faces, Scha7_parcs.rh_scha100, rand(50,1)}); +``` + + +![./plotBrain_examples_live_media/figure_18.png +](./plotBrain_examples_live_media/figure_18.png +) + + +```matlab:Code + +``` + + + +By default, this plots in the order ll-lm-rm-rl. This can be changed easily: + + + +```matlab:Code +figure; +plotBrain('lh', {lh_verts, lh_faces, Scha7_parcs.lh_scha100, rand(50,1)}, ... + 'rh', {rh_verts, rh_faces, Scha7_parcs.rh_scha100, rand(50,1)}, ... + 'viewOrder', {'ll', 'lm', 'rl', 'rm'}); +``` + + +![./plotBrain_examples_live_media/figure_19.png +](./plotBrain_examples_live_media/figure_19.png +) + + +```matlab:Code + +``` + +# Plotting Multiple Data Maps and Views + + +If you supply multiple maps/parcellations and multiple input to the `'view'` argument, this will automatically produce a grid of brains (using `tiledlayout`) with each combination of map and view plotted. + + + + +By default, datasets will change as you move down columns, and views will change as you move across rows + + + + +This can be changed by using using the name-value argument `'tiledlayoutOptions'` and the suboption `'TileIndexing'`. + + + +```matlab:Code +% 4 datasets, 2 views +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-37.5:12.5:0), 'colormap', @bluewhitered, 'view', {[-90 0], [90 0]}, 'tiledlayoutOptions', {4, 2}); +``` + + +![./plotBrain_examples_live_media/figure_20.png +](./plotBrain_examples_live_media/figure_20.png +) + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-37.5:12.5:0), 'colormap', @bluewhitered, 'view', {[-90 0], [90 0]}, 'tiledlayoutOptions', {2, 4, 'TileSpacing', 'none', 'TileIndexing', 'columnmajor'}); +``` + + +![./plotBrain_examples_live_media/figure_21.png +](./plotBrain_examples_live_media/figure_21.png +) + + +```matlab:Code + +% 2 datasets, 4 views +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-25:25:0), 'colormap', @bluewhitered, 'view', {[-90 0], [-45 0], [45 0], [90 0]}, 'tiledlayoutOptions', {2, 4}); +``` + + +![./plotBrain_examples_live_media/figure_22.png +](./plotBrain_examples_live_media/figure_22.png +) + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-25:25:0), 'colormap', @bluewhitered, 'view', {[-90 0], [-45 0], [45 0], [90 0]}, 'tiledlayoutOptions', {4, 2, 'TileSpacing', 'none', 'TileIndexing', 'columnmajor'}); +``` + + +![./plotBrain_examples_live_media/figure_23.png +](./plotBrain_examples_live_media/figure_23.png +) + + +```matlab:Code + +% Plot all maps in one row +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-37.5:12.5:0), 'colormap', @bluewhitered, 'view', {[-90 0], [90 0]}, 'tiledlayoutOptions', {1, 8, 'TileSpacing', 'none'}); +``` + + +![./plotBrain_examples_live_media/figure_24.png +](./plotBrain_examples_live_media/figure_24.png +) + + +```matlab:Code + +``` + + +```matlab:Code +% More examples of column major indexing +% 5 datasets, 3 views +figure; [~,~,tl]=plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-50:12.5:0), 'colormap', @bluewhitered, ... + 'view', {[-90 0], [0 0], [90 0]}, 'tiledlayoutOptions', {5, 3, 'TileSpacing', 'none',}); +title(tl, 'Default (TileIndexing = rowmajor)'); +xlabel(tl, 'View changes'); +ylabel(tl, 'Map changes'); +``` + + +![./plotBrain_examples_live_media/figure_25.png +](./plotBrain_examples_live_media/figure_25.png +) + + +```matlab:Code + +figure; [~,~,tl]=plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-50:12.5:0), 'colormap', @bluewhitered, ... + 'view', {[-90 0], [0 0], [90 0]}, 'tiledlayoutOptions', {3, 5, 'TileSpacing', 'none', 'TileIndexing', 'columnmajor'}); +title(tl, 'Changed (TileIndexing = columnmajor)'); +xlabel(tl, 'Map changes'); +ylabel(tl, 'View changes'); +``` + + +![./plotBrain_examples_live_media/figure_26.png +](./plotBrain_examples_live_media/figure_26.png +) + +## Groupings + + +If using the syntax above, each map/view will be plotted on its own tile in the `tiledlayout` object. + + + + +However, you can also choose to group each map or each view together. This is useful for adding titles and colorbars to the figures. + + + + +Instead of generating \texttt{nData * nViews} tiles (which is the default), when the `'groupBy'`argument is set to` 'data', nData` tiles are generated. Each of these is it itself a `tiledlayout`, with `nView` tiles in it. + + + + +These subtiles can also be accessed as an output argument of plotBrain. + + + +```matlab:Code +% Compare the following examples: +% Plot all maps in one row +figure; [~,~,tl] = plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-37.5:12.5:0), 'colormap', @bluewhitered, ... + 'view', {[-90 0], [90 0]}, 'tiledlayoutOptions', {1, 8, 'TileSpacing', 'none'}); +for ii = 1:8; title(nexttile(tl, ii), sprintf("Map %d", ii)); end +``` + + +![./plotBrain_examples_live_media/figure_27.png +](./plotBrain_examples_live_media/figure_27.png +) + + +```matlab:Code + +% Use groupBy data +figure; [~,~,tl,tl2] = plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-37.5:12.5:0), 'colormap', @bluewhitered, ... + 'view', {[-90 0], [90 0]}, 'tiledlayoutOptions', {'flow', 'TileSpacing', 'compact'}, 'groupBy', 'data', 'titles', {'Map 1', 'Map 2', 'Map 3', 'Map 4'}); %#ok<*ASGLU> +title(tl, 'groupBy data'); +``` + + +![./plotBrain_examples_live_media/figure_28.png +](./plotBrain_examples_live_media/figure_28.png +) + + +```matlab:Code +% Try popping out this figure and adjusting the size +``` + + + +The other alternative is `'groupBy', 'view'`, if desired. + + +# Colorschemes and Colorbars + + +You can add a colorbar to each group by using the `'colorbarOn'` argument. + + + + +You can also set all maps to use the same color limits using `'colorscheme', 'global'`. + + + +```matlab:Code +%%% regular colormaps +% each map plotted on its own colorspace and its own colorbar +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, 50*rand(50, 9)+(-75:12.5:25), 'colormap', cool(100), 'colorbarOn', true); +``` + + +![./plotBrain_examples_live_media/figure_29.png +](./plotBrain_examples_live_media/figure_29.png +) + + +```matlab:Code +% each map plotted in a global colorscheme, with one global colorbar +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, 50*rand(50, 9)+(-75:12.5:25), 'colorscheme', 'global'); c = colorbar; c.Layout.Tile = 'east'; +``` + + +![./plotBrain_examples_live_media/figure_30.png +](./plotBrain_examples_live_media/figure_30.png +) + + +```matlab:Code + +%% diverging colormaps specified as function handle +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-75:12.5:25), 'colormap', @(x) bluewhitered(x), 'colorbarOn', true, 'colorbarOptions', {'Location', 'southoutside'}); +``` + + +![./plotBrain_examples_live_media/figure_31.png +](./plotBrain_examples_live_media/figure_31.png +) + + +```matlab:Code +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-75:12.5:25), 'colormap', @bluewhitered, 'colorscheme', 'global'); c = colorbar; c.Layout.Tile = 'east'; +``` + + +![./plotBrain_examples_live_media/figure_32.png +](./plotBrain_examples_live_media/figure_32.png +) + + +```matlab:Code + +``` + + + +These can also be combined with the `'groupBy'` syntax: + + + +```matlab:Code +figure; [~,~,tl,tl2] = plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-37.5:12.5:0), 'colormap', @bluewhitered, ... + 'view', {[-90 0], [90 0]}, 'groupBy', 'data', 'titles', {'Map 1', 'Map 2', 'Map 3', 'Map 4'}, 'colorbarOn', true); +``` + + +![./plotBrain_examples_live_media/figure_33.png +](./plotBrain_examples_live_media/figure_33.png +) + + +```matlab:Code + +``` + +# Titles + + +You can use the Name-Value argument `'titles'` to input a cell array of titles to be used for each plot or subplot: + + + +```matlab:Code +% For each map +figure; plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-75:12.5:25), ... + 'colormap', @bluewhitered, 'colorscheme', 'global', 'view', 'll', ... + 'titles', arrayfun(@(x) sprintf('Map %d', x), 1:9, 'UniformOutput', false)); +``` + + +![./plotBrain_examples_live_media/figure_34.png +](./plotBrain_examples_live_media/figure_34.png +) + + +```matlab:Code + +% An example when combined with groupBy +figure; [~,~,tl,tl2] = plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-37.5:12.5:0), 'colormap', @bluewhitered, ... + 'view', {[-90 0], [90 0]}, 'groupBy', 'data', 'titles', {'Map 1', 'Map 2', 'Map 3', 'Map 4'}, 'colorbarOn', true); +``` + + +![./plotBrain_examples_live_media/figure_35.png +](./plotBrain_examples_live_media/figure_35.png +) + +# Combining into Larger Figures + + +You can embed `plotBrain` figures into larger figures by using the `'parent'` Name-Value argument. See `tiledlayout` for more information regarding arrangements in larger figures. + + + +```matlab:Code +figure; +tl = tiledlayout(1,2); + +[~,~,temp] = plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-37.5:12.5:0), 'parent', tl, 'colormap', @bluewhitered, 'view', {[-90 0], [90 0]}, 'groupBy', 'data', 'tiledlayoutOptions', {4, 1}); +temp.Layout.Tile = 1; +title(temp, {'4 datasets', '2 views'}); + +[~,~,temp] = plotBrain(lh_verts, lh_faces, Scha17_parcs.lh_scha100, (1:50).'+(-25:25:0), 'parent', tl, 'colormap', @bluewhitered, 'view', {[-90 0], [-45 0], [45 0], [90 0]}, 'groupBy', 'view', 'tiledlayoutOptions', {4, 1}); +temp.Layout.Tile = 2; +title(temp, {'2 datasets', '4 views'}); +``` + + +![./plotBrain_examples_live_media/figure_36.png +](./plotBrain_examples_live_media/figure_36.png +) + diff --git a/examples/plotBrain_examples_live.mlx b/examples/plotBrain_examples_live.mlx new file mode 100644 index 0000000..def572e Binary files /dev/null and b/examples/plotBrain_examples_live.mlx differ diff --git a/examples/plotBrain_examples_live_media/figure_0.png b/examples/plotBrain_examples_live_media/figure_0.png new file mode 100644 index 0000000..7a3b838 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_0.png differ diff --git a/examples/plotBrain_examples_live_media/figure_1.png b/examples/plotBrain_examples_live_media/figure_1.png new file mode 100644 index 0000000..35261c7 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_1.png differ diff --git a/examples/plotBrain_examples_live_media/figure_10.png b/examples/plotBrain_examples_live_media/figure_10.png new file mode 100644 index 0000000..7441bb7 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_10.png differ diff --git a/examples/plotBrain_examples_live_media/figure_11.png b/examples/plotBrain_examples_live_media/figure_11.png new file mode 100644 index 0000000..5c8493c Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_11.png differ diff --git a/examples/plotBrain_examples_live_media/figure_12.png b/examples/plotBrain_examples_live_media/figure_12.png new file mode 100644 index 0000000..7c77e01 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_12.png differ diff --git a/examples/plotBrain_examples_live_media/figure_13.png b/examples/plotBrain_examples_live_media/figure_13.png new file mode 100644 index 0000000..b5f0544 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_13.png differ diff --git a/examples/plotBrain_examples_live_media/figure_14.png b/examples/plotBrain_examples_live_media/figure_14.png new file mode 100644 index 0000000..b3f8888 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_14.png differ diff --git a/examples/plotBrain_examples_live_media/figure_15.png b/examples/plotBrain_examples_live_media/figure_15.png new file mode 100644 index 0000000..0a6ddb6 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_15.png differ diff --git a/examples/plotBrain_examples_live_media/figure_16.png b/examples/plotBrain_examples_live_media/figure_16.png new file mode 100644 index 0000000..b53abd8 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_16.png differ diff --git a/examples/plotBrain_examples_live_media/figure_17.png b/examples/plotBrain_examples_live_media/figure_17.png new file mode 100644 index 0000000..528e378 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_17.png differ diff --git a/examples/plotBrain_examples_live_media/figure_18.png b/examples/plotBrain_examples_live_media/figure_18.png new file mode 100644 index 0000000..2bce9c2 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_18.png differ diff --git a/examples/plotBrain_examples_live_media/figure_19.png b/examples/plotBrain_examples_live_media/figure_19.png new file mode 100644 index 0000000..2564dc8 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_19.png differ diff --git a/examples/plotBrain_examples_live_media/figure_2.png b/examples/plotBrain_examples_live_media/figure_2.png new file mode 100644 index 0000000..593e390 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_2.png differ diff --git a/examples/plotBrain_examples_live_media/figure_20.png b/examples/plotBrain_examples_live_media/figure_20.png new file mode 100644 index 0000000..b9c1f78 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_20.png differ diff --git a/examples/plotBrain_examples_live_media/figure_21.png b/examples/plotBrain_examples_live_media/figure_21.png new file mode 100644 index 0000000..36ac228 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_21.png differ diff --git a/examples/plotBrain_examples_live_media/figure_22.png b/examples/plotBrain_examples_live_media/figure_22.png new file mode 100644 index 0000000..c956dd8 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_22.png differ diff --git a/examples/plotBrain_examples_live_media/figure_23.png b/examples/plotBrain_examples_live_media/figure_23.png new file mode 100644 index 0000000..7b340e6 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_23.png differ diff --git a/examples/plotBrain_examples_live_media/figure_24.png b/examples/plotBrain_examples_live_media/figure_24.png new file mode 100644 index 0000000..2f8ace4 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_24.png differ diff --git a/examples/plotBrain_examples_live_media/figure_25.png b/examples/plotBrain_examples_live_media/figure_25.png new file mode 100644 index 0000000..080a669 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_25.png differ diff --git a/examples/plotBrain_examples_live_media/figure_26.png b/examples/plotBrain_examples_live_media/figure_26.png new file mode 100644 index 0000000..f7c61ae Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_26.png differ diff --git a/examples/plotBrain_examples_live_media/figure_27.png b/examples/plotBrain_examples_live_media/figure_27.png new file mode 100644 index 0000000..dd96c4d Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_27.png differ diff --git a/examples/plotBrain_examples_live_media/figure_28.png b/examples/plotBrain_examples_live_media/figure_28.png new file mode 100644 index 0000000..eaa02e4 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_28.png differ diff --git a/examples/plotBrain_examples_live_media/figure_29.png b/examples/plotBrain_examples_live_media/figure_29.png new file mode 100644 index 0000000..dbc35d9 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_29.png differ diff --git a/examples/plotBrain_examples_live_media/figure_3.png b/examples/plotBrain_examples_live_media/figure_3.png new file mode 100644 index 0000000..6a5cb3a Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_3.png differ diff --git a/examples/plotBrain_examples_live_media/figure_30.png b/examples/plotBrain_examples_live_media/figure_30.png new file mode 100644 index 0000000..885fc65 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_30.png differ diff --git a/examples/plotBrain_examples_live_media/figure_31.png b/examples/plotBrain_examples_live_media/figure_31.png new file mode 100644 index 0000000..206cb0d Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_31.png differ diff --git a/examples/plotBrain_examples_live_media/figure_32.png b/examples/plotBrain_examples_live_media/figure_32.png new file mode 100644 index 0000000..1a6728b Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_32.png differ diff --git a/examples/plotBrain_examples_live_media/figure_33.png b/examples/plotBrain_examples_live_media/figure_33.png new file mode 100644 index 0000000..ef8d770 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_33.png differ diff --git a/examples/plotBrain_examples_live_media/figure_34.png b/examples/plotBrain_examples_live_media/figure_34.png new file mode 100644 index 0000000..f9796fd Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_34.png differ diff --git a/examples/plotBrain_examples_live_media/figure_35.png b/examples/plotBrain_examples_live_media/figure_35.png new file mode 100644 index 0000000..ef8d770 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_35.png differ diff --git a/examples/plotBrain_examples_live_media/figure_36.png b/examples/plotBrain_examples_live_media/figure_36.png new file mode 100644 index 0000000..12a79c9 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_36.png differ diff --git a/examples/plotBrain_examples_live_media/figure_4.png b/examples/plotBrain_examples_live_media/figure_4.png new file mode 100644 index 0000000..6a5cb3a Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_4.png differ diff --git a/examples/plotBrain_examples_live_media/figure_5.png b/examples/plotBrain_examples_live_media/figure_5.png new file mode 100644 index 0000000..645ed4a Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_5.png differ diff --git a/examples/plotBrain_examples_live_media/figure_6.png b/examples/plotBrain_examples_live_media/figure_6.png new file mode 100644 index 0000000..e847ca4 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_6.png differ diff --git a/examples/plotBrain_examples_live_media/figure_7.png b/examples/plotBrain_examples_live_media/figure_7.png new file mode 100644 index 0000000..cc1a0ce Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_7.png differ diff --git a/examples/plotBrain_examples_live_media/figure_8.png b/examples/plotBrain_examples_live_media/figure_8.png new file mode 100644 index 0000000..f0ae5da Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_8.png differ diff --git a/examples/plotBrain_examples_live_media/figure_9.png b/examples/plotBrain_examples_live_media/figure_9.png new file mode 100644 index 0000000..0cbbf97 Binary files /dev/null and b/examples/plotBrain_examples_live_media/figure_9.png differ diff --git a/makeFaceVertexCData.m b/makeFaceVertexCData.m index e26177e..94a8bba 100644 --- a/makeFaceVertexCData.m +++ b/makeFaceVertexCData.m @@ -1,7 +1,7 @@ function FaceVertexCData = makeFaceVertexCData(vertices,faces,vertex_id,data,cmap,climits,colorFaceBoundaries,unknown_color,boundary_color) % This script will assign colours to each face/vertex by assigning each -% value in 'data' a colour from 'cmap'. This code also allows you to +% value in 'data' a colour from 'cmap'. This code also allows you to % indicate if a particular area should be displayed with any colour at % all. % @@ -19,7 +19,7 @@ % value to NaN. Note that this assumes a roi with a vertex_id has no data % to plot. Additionally, if the vertex_ids are non sequential (e.g., like % what you get from an .annot file) then data can take the form of a ROI*2 -% matrix, where ROI is the number of regions, each row is a particular +% matrix, where ROI is the number of regions, each row is a particular % region with the first column being the data to plot and the second being % the region ID (should correspond to what is in vertex_id) % @@ -37,7 +37,7 @@ % suggest threshold the data in advance and all should be good. % % colorFaceBoundaries = set to 1 if you want the faces which make up the -% boundaries of each ROI to be coloured black. The code will then configure +% boundaries of each ROI to be coloured black. The code will then configure % FaceVertexCData to be a value per face instead of per vertex % % unknown_color = the color to assign to all unknown regions (areas with a @@ -46,9 +46,9 @@ % boundary_color = the color for the boundary if it is being drawn % % Output: -% +% % FaceVertexCData = the color value for each vertex or face (depending on how -% colorFaceBoundaries was configured). +% colorFaceBoundaries was configured). % % Stuart Oldham, Monash University, 2020 % Thanks to the coronavirus for giving me the time to make this script @@ -67,23 +67,23 @@ if size(data,2) > size(data,1) data = data'; end - + if length(data) ~= length(unique(vertex_id))-vert0present && length(data) ~= length(vertex_id) error('''data'' needs to either contain one value per roi, contain a value for each vertex, or be an N*2 array showing which data to plot to which ROI ID') end - - if length(data) ~= length(vertices) + + if length(data) ~= length(vertices) ROI_ids = (1:length(data))'; end else if size(data,2) ~= 2 - error('If providing ''data'' with ROI ids, then the first column needs to be the data and the second the ROI id') + error('If providing ''data'' with ROI ids, then the first column needs to be the data and the second the ROI id') end - + ROI_ids = data(:,2); data = data(:,1); - + end if nargin < 6 @@ -105,7 +105,7 @@ if nargin < 9 % Set the boundary colour (black) - boundary_color = [0 0 0]; + boundary_color = [0 0 0]; end @@ -116,7 +116,7 @@ % Check if the input_data is data for each ROI, or is data for each % vertex - + % Find the rois each face is connected to faces_roi_ids = vertex_id(faces); @@ -127,14 +127,15 @@ % Find the boundary faces - boundary = logical(diff(faces_roi_ids,2,2)); - + %boundary = logical(diff(faces_roi_ids,2,2)); % fails if the rois allocated to a face's vertices follow a pattern like [10, 20, 30] (ie they have the same difference) + boundary = any(diff(faces_roi_ids,1,2), 2); + if length(data) ~= length(vertices) % Map the data from each ROI onto each face which is part of that ROI Nrois = length(data); - + newval = [NaN; data(1:Nrois)]; oldval = [0; ROI_ids]; @@ -147,11 +148,11 @@ % Define the value of each face as the mean of the values % assigned to its associated vertices - + face_data = nanmean(data(faces),2); face_data(face_roi_id==0) = NaN; end - + % Scale the data if needed face_data(face_data(size(vertices, 1))/2; hemi = 'lh'; + else; hemi = 'rh'; end + + [patches, boundary_plots, outerTiledlayout, innerTiledlayouts] = ... + plotBrain(hemi, {vertices, faces, rois, data}, varargin{inputToStart:end}); + + return; +end + + +%% Parse Inputs +lh = ip.Results.lh; +rh = ip.Results.rh; +viewOrder = ip.Results.viewOrder; + +if isstring(viewOrder) || ischar(viewOrder); viewOrder = cellstr(viewOrder); end +hemiOrder = cellstr(ip.Results.hemiOrder); + +% generate order of views +% - if viewOrder is specified using ll/lm/rl/rm syntax, hemiOrder will be updated later +% - if viewOrder is specified using numeric values, hemiOrder must be input + +% - (otherwise) set up defaults for viewOrder if only one hemisphere is input +if iud('viewOrder') + if isempty(lh); viewOrder = {'rl'}; end + if isempty(rh); viewOrder = {'ll'}; end +end + +% generate order of hemispheres +if iud('hemiOrder') + if isempty(lh); hemiOrder = {'r'}; + elseif isempty(rh); hemiOrder = {'l'}; + else % if both lh and rh are specified, and viewOrder specifies camera angles, user needs to specify which hemisphere to plot + for ii = 1:length(viewOrder) + temp = getEntry(viewOrder, ii); + assert(isstring(temp) || ischar(temp), 'please ensure viewOrder and hemiOrder are correctly specified'); + hemiOrder{ii} = temp(1); + end + end +end + +% set up data and rois: get size of lhData and rhData, incl if empty +if ~isempty(lh) + lhRois = lh{end-1}; + lhData = lh{end}; + assert(size(lhRois,2) < 2 || size(lhData,2) < 2 || size(lhRois,2) == size(lhData, 2), ... + 'please ensure lh rois and data are correctly specified'); + nData = max([1, size(lhData, 2), size(lhRois, 2)]); +else + lhData = {}; +end +if ~isempty( rh) + rhRois = rh{end-1}; + rhData = rh{end}; + assert(size(rhRois,2) < 2 || size(rhData,2) < 2 || size(rhRois,2) == size(rhData, 2), ... + 'please ensure rh rois and data are correctly specified'); + nData = max([1, size(rhData, 2), size(rhRois, 2)]); +else + rhData = {}; +end + +assert(isempty(rh) || isempty(lh) || size(lhData, 2) == size(rhData, 2), ... + 'please ensure lh and rh rois/data are correctly specified'); + +% set up colormap +plotSurfaceOptions = ip.Results.plotSurfaceROIBoundaryOptions; +cmap = ip.Results.colormap; +if iud('plotSurfaceROIBoundaryOptions') + if ~isempty(cmap) + if (isstring(cmap) || ischar(cmap)); cmap = colormap(cmap); end + plotSurfaceOptions{2} = cmap; + end +end +cmap = plotSurfaceOptions{2}; + +% set up global colorscheme: +% - if colorscheme, 'global' is specified +colorscheme = ip.Results.colorscheme; colorMin = +Inf; colorMax = -Inf; +if strcmp(colorscheme, 'global') + for idxData = 1:nData + colorMin = min([colorMin, min(getEntry(lhData, idxData)), min(getEntry(rhData, idxData))]); + colorMax = max([colorMax, max(getEntry(lhData, idxData)), max(getEntry(rhData, idxData))]); + end + plotSurfaceOptions{4} = [colorMin, colorMax]; % pass in clims +end + +% - or if clims are specified +if ~iud('clim') && iud('colorscheme') + colorMin = min(ip.Results.clim); + colorMax = max(ip.Results.clim); + colorscheme = 'global'; +end +if colorMin == colorMax; colorMin = colorMin-1; colorMax = colorMax+1; end + +% others +groupBy = ip.Results.groupBy; +camlights = ip.Results.camlights; +tiledlayout2Options = ip.Results.tiledlayout2Options; + + +%% Set up plots +nView = length(viewOrder); +nTiles = nData * nView; + +% Do not create or use tiledlayout/nexttile if only plotting one surface and view +% Note that tl will be output as outerTiledlayout and tl2 will be innerTiledlayout +if (nTiles > 1) || ip.Results.forceTiledlayout + if isempty(ip.Results.parent); tl = tiledlayout(ip.Results.tiledlayoutOptions{:}); + else; tl = tiledlayout(ip.Results.parent, ip.Results.tiledlayoutOptions{:}); end +else + if isempty(ip.Results.parent); ax = gca; + else; ax = ip.Results.parent; end + tl = ax; % for output only +end + +% Set up nested tiledlayouts if groupBy is specified +nSubtiles = []; +if strcmp(groupBy, 'data'); nSubtiles = nData; +elseif strcmp(groupBy, 'view'); nSubtiles = nView; +end + +tl2 = {}; +if ~isempty(nSubtiles) + for ii = 1:nSubtiles + tl2{ii} = tiledlayout(tl, tiledlayout2Options{:}); %#ok + tl2{ii}.Layout.Tile = ii; %#ok + if ~iud('titles'); title(tl2{ii}, ip.Results.titles{ii}); end + end +end + +patches = {}; boundary_plots = {}; + +% if strcmp(groupBy, 'data') +% if iud('tiledlayout2Options') +% % tiledlayout2Options = {1, nView, 'TileSpacing', 'tight'}; +% end +% for idxData = 1:nData +% tl2{idxData} = tiledlayout(tl, tiledlayout2Options{:}); %#ok +% tl2{idxData}.Layout.Tile = idxData; %#ok +% if ~iud('titles'); title(tl2{idxData}, ip.Results.titles{idxData}); end +% end +% elseif strcmp(groupBy, 'view') +% if iud('tiledlayout2Options') +% % tiledlayout2Options = {1, nData, 'TileSpacing', 'tight'}; +% end +% for idxView = 1:nView +% tl2{idxView} = tiledlayout(tl, tiledlayout2Options{:}); %#ok +% tl2{idxView}.Layout.Tile = idxView; %#ok +% if ~iud('titles'); title(tl2{idxView}, ip.Results.titles{idxView}); end +% end +% end + + +%% Do plotting using plotSurfaceROIBoundary +% Plot each brain map/parcellation and view +for idxData = 1:nData + for idxView = 1:nView + + % get hemisphere data + temp = getEntry(hemiOrder, idxView); + if strcmp(temp(1), 'l'); currentHemi = lh; + else; currentHemi = rh; end + + % get verts from struct/cell/matrix + if isstruct(currentHemi{1}) + currentVerts = currentHemi{1}.vertices; + elseif iscell(currentHemi{1}) + currentVerts = getEntry(currentHemi{1}, idxData); + else + currentVerts = currentHemi{1}; + end + + % get faces from struct/cell/matrix + if isstruct(currentHemi{1}) + currentFaces = currentHemi{1}.faces; + elseif iscell(currentHemi{2}) + currentFaces = getEntry(currentHemi{2}, idxData); + else + currentFaces = currentHemi{2}; + end + + % get rois and data from cell/matrix: takes column/cell #ii or last one, whichever is lower +% currentRois = currentHemi{end-1}; +% currentData = currentHemi{end}; + currentRois = getEntry(currentHemi{end-1}, idxData);if isempty(currentRois); currentRois = ones(size(currentVerts(:,1))); end + currentData = getEntry(currentHemi{end}, idxData);if isempty(currentData); currentData = currentVerts(:,2); end + assert(size(currentRois, 2) == 1 || size(currentData, 2) == 1 || size(currentRois, 2) == size(currentData, 2), ... + 'parcellations (rois) and data should have a compatible number of columns'); + + % set current axis + if (nTiles > 1) || ip.Results.forceTiledlayout + if strcmp(groupBy, 'data') + ax = nexttile(tl2{idxData}); + elseif strcmp(groupBy, 'view') + ax = nexttile(tl2{idxView}); + else + ax = nexttile(tl); + end + end + + % set clims and truncate data if needed + if strcmp(colorscheme, 'global') || ~iud('clim') + caxis(ax, [colorMin, colorMax]); + else + colorMin = min(currentData(:)); + colorMax = max(currentData(:)); + if colorMin == colorMax; colorMin = colorMin-1; colorMax = colorMax+1; end + caxis(ax, [colorMin, colorMax]); + end %#ok<*CAXIS> + + cl = caxis; + currentData(currentData < cl(1)) = cl(1); + currentData(currentData > cl(2)) = cl(2); + + % generate cmap + if isa(cmap, 'function_handle'); temp = cmap(256); + else; temp = cmap; end + % consider trying plotSurfaceOptions{2} = cmap(); % for both cases + plotSurfaceOptions{2} = temp; + + % plot using plotSurfaceROIBoundary + [patches{end+1},boundary_plots{end+1}] = plotSurfaceROIBoundary(... + struct('vertices', currentVerts, 'faces', currentFaces), ... + currentRois, currentData, ... + plotSurfaceOptions{:}); %#ok + + colormap(ax, plotSurfaceOptions{2}); + axis off; axis equal; axis tight; + + % set title (if not set earlier) + if isempty(nSubtiles) && ~iud('titles') % no groubpBy, and titles given + title(ax, ip.Results.titles{sub2ind([nData, nView], idxData, idxView)}); + end + + % set camlights + for nCamlight = 1:size(camlights, 1) + camlight(ax, camlights(nCamlight, 1), camlights(nCamlight, 2)); + end + + % set colorbar if requested + if ip.Results.colorbarOn + if strcmp(groupBy, 'data') + if idxView == nView % so that only 1 colorbar is plotted + c = colorbar(ax, ip.Results.colorbarOptions{:}); + c.Layout.Tile = ip.Results.colorbarLocation; + end + elseif strcmp(groupBy, 'view') + if idxData == nData % so that only 1 colorbar is plotted + c = colorbar(ax, ip.Results.colorbarOptions{:}); + c.Layout.Tile = ip.Results.colorbarLocation; + end + else + colorbar(ax, ip.Results.colorbarOptions{:}); + end + end + + % Change view according to flag + if isa(viewOrder{idxView}, 'double') + view(viewOrder{idxView}); + else + switch viewOrder{idxView} + case "lm", view([90 0]); + case "ll", view([-90 0]); + case "rm", view([-90 0]); + case "rl", view([90 0]); + otherwise, view([0 90]); % top view + end + end + + end +end + +outerTiledlayout = tl; +innerTiledlayouts = tl2; + +end % end main + + + +%% Helpers + +function out = getEntry(data, colNumber) +% Get entry from matrix or cell - shorthand function for implicit expansion +% e.g. if rois has multiple columns but data has only one +% as colNumber increases, return the different columns of rois, but always the (first) column of data +if isempty(data); out = []; return; end +if isnumeric(data) || islogical(data); out = data(:,min( colNumber, size(data,2) )); +elseif iscell(data); out = data{min( colNumber, length(data) )}; end +end + + diff --git a/plotBrain_examples_live_images/figure_0.png b/plotBrain_examples_live_images/figure_0.png new file mode 100644 index 0000000..f9eb9fa Binary files /dev/null and b/plotBrain_examples_live_images/figure_0.png differ diff --git a/plotBrain_examples_live_images/figure_1.png b/plotBrain_examples_live_images/figure_1.png new file mode 100644 index 0000000..d335f1f Binary files /dev/null and b/plotBrain_examples_live_images/figure_1.png differ diff --git a/plotBrain_examples_live_images/figure_10.png b/plotBrain_examples_live_images/figure_10.png new file mode 100644 index 0000000..7e03564 Binary files /dev/null and b/plotBrain_examples_live_images/figure_10.png differ diff --git a/plotBrain_examples_live_images/figure_11.png b/plotBrain_examples_live_images/figure_11.png new file mode 100644 index 0000000..d8554aa Binary files /dev/null and b/plotBrain_examples_live_images/figure_11.png differ diff --git a/plotBrain_examples_live_images/figure_12.png b/plotBrain_examples_live_images/figure_12.png new file mode 100644 index 0000000..1a1652b Binary files /dev/null and b/plotBrain_examples_live_images/figure_12.png differ diff --git a/plotBrain_examples_live_images/figure_13.png b/plotBrain_examples_live_images/figure_13.png new file mode 100644 index 0000000..c465c20 Binary files /dev/null and b/plotBrain_examples_live_images/figure_13.png differ diff --git a/plotBrain_examples_live_images/figure_14.png b/plotBrain_examples_live_images/figure_14.png new file mode 100644 index 0000000..9e6ab39 Binary files /dev/null and b/plotBrain_examples_live_images/figure_14.png differ diff --git a/plotBrain_examples_live_images/figure_15.png b/plotBrain_examples_live_images/figure_15.png new file mode 100644 index 0000000..64fa253 Binary files /dev/null and b/plotBrain_examples_live_images/figure_15.png differ diff --git a/plotBrain_examples_live_images/figure_16.png b/plotBrain_examples_live_images/figure_16.png new file mode 100644 index 0000000..b42f63e Binary files /dev/null and b/plotBrain_examples_live_images/figure_16.png differ diff --git a/plotBrain_examples_live_images/figure_17.png b/plotBrain_examples_live_images/figure_17.png new file mode 100644 index 0000000..3640431 Binary files /dev/null and b/plotBrain_examples_live_images/figure_17.png differ diff --git a/plotBrain_examples_live_images/figure_18.png b/plotBrain_examples_live_images/figure_18.png new file mode 100644 index 0000000..49e47e5 Binary files /dev/null and b/plotBrain_examples_live_images/figure_18.png differ diff --git a/plotBrain_examples_live_images/figure_19.png b/plotBrain_examples_live_images/figure_19.png new file mode 100644 index 0000000..a12d4a3 Binary files /dev/null and b/plotBrain_examples_live_images/figure_19.png differ diff --git a/plotBrain_examples_live_images/figure_2.png b/plotBrain_examples_live_images/figure_2.png new file mode 100644 index 0000000..7a19810 Binary files /dev/null and b/plotBrain_examples_live_images/figure_2.png differ diff --git a/plotBrain_examples_live_images/figure_20.png b/plotBrain_examples_live_images/figure_20.png new file mode 100644 index 0000000..bbd2030 Binary files /dev/null and b/plotBrain_examples_live_images/figure_20.png differ diff --git a/plotBrain_examples_live_images/figure_21.png b/plotBrain_examples_live_images/figure_21.png new file mode 100644 index 0000000..ea69a98 Binary files /dev/null and b/plotBrain_examples_live_images/figure_21.png differ diff --git a/plotBrain_examples_live_images/figure_22.png b/plotBrain_examples_live_images/figure_22.png new file mode 100644 index 0000000..7291ac1 Binary files /dev/null and b/plotBrain_examples_live_images/figure_22.png differ diff --git a/plotBrain_examples_live_images/figure_23.png b/plotBrain_examples_live_images/figure_23.png new file mode 100644 index 0000000..d3492b1 Binary files /dev/null and b/plotBrain_examples_live_images/figure_23.png differ diff --git a/plotBrain_examples_live_images/figure_24.png b/plotBrain_examples_live_images/figure_24.png new file mode 100644 index 0000000..a38b38a Binary files /dev/null and b/plotBrain_examples_live_images/figure_24.png differ diff --git a/plotBrain_examples_live_images/figure_25.png b/plotBrain_examples_live_images/figure_25.png new file mode 100644 index 0000000..bc0fe5f Binary files /dev/null and b/plotBrain_examples_live_images/figure_25.png differ diff --git a/plotBrain_examples_live_images/figure_26.png b/plotBrain_examples_live_images/figure_26.png new file mode 100644 index 0000000..bfd9160 Binary files /dev/null and b/plotBrain_examples_live_images/figure_26.png differ diff --git a/plotBrain_examples_live_images/figure_27.png b/plotBrain_examples_live_images/figure_27.png new file mode 100644 index 0000000..469dd93 Binary files /dev/null and b/plotBrain_examples_live_images/figure_27.png differ diff --git a/plotBrain_examples_live_images/figure_28.png b/plotBrain_examples_live_images/figure_28.png new file mode 100644 index 0000000..9fa8af6 Binary files /dev/null and b/plotBrain_examples_live_images/figure_28.png differ diff --git a/plotBrain_examples_live_images/figure_29.png b/plotBrain_examples_live_images/figure_29.png new file mode 100644 index 0000000..35dd7a0 Binary files /dev/null and b/plotBrain_examples_live_images/figure_29.png differ diff --git a/plotBrain_examples_live_images/figure_3.png b/plotBrain_examples_live_images/figure_3.png new file mode 100644 index 0000000..7a19810 Binary files /dev/null and b/plotBrain_examples_live_images/figure_3.png differ diff --git a/plotBrain_examples_live_images/figure_30.png b/plotBrain_examples_live_images/figure_30.png new file mode 100644 index 0000000..e9e6289 Binary files /dev/null and b/plotBrain_examples_live_images/figure_30.png differ diff --git a/plotBrain_examples_live_images/figure_31.png b/plotBrain_examples_live_images/figure_31.png new file mode 100644 index 0000000..6486d1e Binary files /dev/null and b/plotBrain_examples_live_images/figure_31.png differ diff --git a/plotBrain_examples_live_images/figure_32.png b/plotBrain_examples_live_images/figure_32.png new file mode 100644 index 0000000..bd438a5 Binary files /dev/null and b/plotBrain_examples_live_images/figure_32.png differ diff --git a/plotBrain_examples_live_images/figure_33.png b/plotBrain_examples_live_images/figure_33.png new file mode 100644 index 0000000..6486d1e Binary files /dev/null and b/plotBrain_examples_live_images/figure_33.png differ diff --git a/plotBrain_examples_live_images/figure_34.png b/plotBrain_examples_live_images/figure_34.png new file mode 100644 index 0000000..7cb9c08 Binary files /dev/null and b/plotBrain_examples_live_images/figure_34.png differ diff --git a/plotBrain_examples_live_images/figure_4.png b/plotBrain_examples_live_images/figure_4.png new file mode 100644 index 0000000..7a19810 Binary files /dev/null and b/plotBrain_examples_live_images/figure_4.png differ diff --git a/plotBrain_examples_live_images/figure_5.png b/plotBrain_examples_live_images/figure_5.png new file mode 100644 index 0000000..1b3e518 Binary files /dev/null and b/plotBrain_examples_live_images/figure_5.png differ diff --git a/plotBrain_examples_live_images/figure_6.png b/plotBrain_examples_live_images/figure_6.png new file mode 100644 index 0000000..1e16e25 Binary files /dev/null and b/plotBrain_examples_live_images/figure_6.png differ diff --git a/plotBrain_examples_live_images/figure_7.png b/plotBrain_examples_live_images/figure_7.png new file mode 100644 index 0000000..e7fdd6a Binary files /dev/null and b/plotBrain_examples_live_images/figure_7.png differ diff --git a/plotBrain_examples_live_images/figure_8.png b/plotBrain_examples_live_images/figure_8.png new file mode 100644 index 0000000..b782d18 Binary files /dev/null and b/plotBrain_examples_live_images/figure_8.png differ diff --git a/plotBrain_examples_live_images/figure_9.png b/plotBrain_examples_live_images/figure_9.png new file mode 100644 index 0000000..45a481c Binary files /dev/null and b/plotBrain_examples_live_images/figure_9.png differ diff --git a/plotSurfaceROIBoundary.m b/plotSurfaceROIBoundary.m index 7955a52..ddbe550 100644 --- a/plotSurfaceROIBoundary.m +++ b/plotSurfaceROIBoundary.m @@ -1,179 +1,233 @@ -function [p,boundary_plot,BOUNDARY] = plotSurfaceROIBoundary(surface,vertex_id,data,boundary_method,cmap,linewidth,climits) - -% This script is a wrapper for findROIboundaries and makeFaceVertexCData so -% that they smoothly work together and you don't have to spend a lot of -% your own time making them work together. The code sets up the basics of -% the patch object - -% Inputs: -% -% surface = a structure with two fields: vertices (the vertices making up -% the surface) and faces (the faces of the surface) -% -% vertex_id = the roi id of each vertex -% -% data = either data for each individual roi or data for each vertex. -% If you don't want any data to be displayed for a roi or vertex, set that -% value to NaN. Note that this assumes a roi with a vertex_id has no data -% to plot. Additionally, if the vertex_ids are non sequential (e.g., like -% what you get from an .annot file) then data can take the form of a ROI*2 -% matrix, where ROI is the number of regions, each row is a particular -% region with the first column being the data to plot and the second being -% the region ID (should correspond to what is in vertex_id) -% -% boundary_method = 'faces', 'midpoint', 'centroid', 'edge_vertices', or -% 'edge_faces'. 'faces' will find the faces which exist between ROIs and -% those will be coloured black to specify the boundary. 'midpoint' finds -% the edges that connect the vertices of two different ROIs and takes the -% midpoint of the edge and uses those coordinates to define the boundary. -% 'centroid' finds the faces which exist between ROIs and uses the centroid -% of those to draw the coordinates that define the boundary. -% 'edge_vertices' finds the vertices which define the boundary of the ROI -% and uses them for the coordinates. 'edge_faces' finds the edges along -% faces which make up the boundary and uses them for the coordinates -% -% cmap = an N*3 matrix specifying the RGB values making up the colormap to -% use -% -% linewidth = the width of the boundary when using 'midpoint', or -% 'centroid'. +function [p, boundary_plot, BOUNDARY] = plotSurfaceROIBoundary(surface,varargin) +%% Plots brain surface mesh with boundaries between parcels +% This function plots a colored patch object and demarcates parcels/ROIs on that +% surface. This requires the input of the surface mesh, the ROI IDs of each +% vertex, and the data to be used to colour each vertex. +% +% This function plots 3 separate objects: (i) the main coloured patch object, +% which adjusts to the colormap of the axes on which it is plotted; (ii) another +% patch object which is used to plot the 'medial wall', and is a fixed color; +% (iii) a patch or line object to demarcate parcel boundaries (and is also a +% fixed color). Although the data values of (i) are fixed, the colors can be +% easily changed by adjust the axes e.g. with `colormap` or `caxis`. The color +% of (ii) and (iii) is best decided at function runtime, but can be adjusted +% post hoc by accessing the data in the return arguments if needed. +% +%% Syntax +% plotSurfaceROIBoundary(surface,vertex_id,data,boundary_method,cmap,linewidth) +% +% plotSurfaceROIBoundary(___,Name,Value) +% +% p = plotSurfaceROIBoundary(___) +% [p,boundary_plot] = plotSurfaceROIBoundary(___) +% [p,boundary_plot,BOUNDARY] = plotSurfaceROIBoundary(___) +% +% +%% Examples +% figure; plotSurfaceROIBoundary(struct('vertices', [cos([(0:6)';(0:6)']), sin([(0:6)';(0:6)']), [(1:7)';(0:6)']], 'faces', [[1,2,8]+(0:5)';[2,9,8]+(0:5)'])); view([45 45]); +% figure; plotSurfaceROIBoundary(struct('vertices', [cos([(0:6)';(0:6)']), sin([(0:6)';(0:6)']), [(1:7)';(0:6)']], 'faces', [[1,2,8]+(0:5)';[2,9,8]+(0:5)']), [], 1:14); view([45 45]); +% figure; plotSurfaceROIBoundary(struct('vertices', [cos([(0:6)';(0:6)']), sin([(0:6)';(0:6)']), [(1:7)';(0:6)']], 'faces', [[1,2,8]+(0:5)';[2,9,8]+(0:5)']), [0;0;1;1;1;0;0;0;1;1;1;1;0;0], 1:14); view([45 45]); +% figure; plotSurfaceROIBoundary(struct('vertices', [cos([(0:6)';(0:6)']), sin([(0:6)';(0:6)']), [(1:7)';(0:6)']], 'faces', [[1,2,8]+(0:5)';[2,9,8]+(0:5)']), [1;1;2;2;2;3;3;1;2;2;2;2;3;3], [1 3 2]); view([45 45]); +% +% See the examples in the `demo_*.m` files. +% +% +%% Input arguments +% surface - structure with fields `vertices` and `faces` +% `vertices` should be a three column matrix (V x 3) containing xyz coordinates +% of the surface mesh. `faces` should be a three column matrix containing the +% vertex coordinates used to make each face. +% +% vertex_id - ROI allocations of each vertex (V x 1 vector) +% Vertices indexed `0` will be treated as the 'medial wall' and will be +% colored according to `unknown_color`. +% +% data - data to plot for each vertex or ROI (V x 1 or R x 1 vector) +% Data indexed `NaN` will be treated as the 'medial wall' and will be colored +% according to `unknown_color`. +% +% boundary_method - method to delineate ROIs ('faces' (default) | 'midpoint' | 'centroid' | 'edge_faces' | 'edge_vertices') % -% climits = the range to apply the colormap. This will work perfectly fine -% if the range specified is larger than the data itself or if the upper -% limit is larger. However if the lower limit is larger than the smallest -% value in data, then it may get strange. If colorUnknownGrey = 1, then -% faces/vertices with a value smaller than the limit will be coloured grey, -% or potentially black if 'faces' is used. If it is set to 0 and 'faces' is -% used, those regions will be set to black while if 'centroid' or midpoint' -% are selected, the colormap will work appropriately). So if you really -% need to enforce a lower limit I would suggest threshold the data in -% advance and all should be good. +% cmap - colormap to color data (three column matrix | function handle | string) % -% Outputs: +% linewith - linewidth for plotting line between ROIs (positive scalar) % -% p = the patch surface object -% -% boundary_plot = a strcture containing a line object defining each -% boundary -% -% BOUNDARY = the boundary of the rois. For 'faces' this will be a logical -% where a value of 1 indicates that face is on the boundary between ROIs. -% For 'midpoint', 'centroid' or 'edges', BOUNDARY will be a cell where each -% element contains the coordinates of the points making up the boundary, -% which can be plotted as a line. Note that each boundary defines a -% continuous ROI, if a ROI is made up of multiple non-continuous parts -% (i.e., the ROI is made up of multiple unconnected sections), there will -% be a boundary for each of those parts - -% Extract the faces and vertices -vertices = surface.vertices; -faces = surface.faces; - -if nargin < 6 - linewidth = 2; +% climits - caxis limits (two element matrix) +% +%% Name-Value Arguments +% boundary_color - color to draw faces/lines to separate ROIs (RGB triplet) +% +% unknown_color - color to draw areas marked as unknown (RGB triplet) +% +% FaceColor - patch face coloring method ('flat' (default) | 'interp') +% +% patch_options - options to use when drawing patches (cell array) +% The contents of this cell array will be unravelled and passed to PATCH +% without modification. +% +% FacesFromRois - flag to color based on ROI data ('first' (default) | 'mean') +% Each face has 3 vertices, but is generally one color. This flag determines +% how to determine the color for each face. If set to 'first', the face will be +% the color of its first vertex. If set to 'mean', the face will be the color +% of the mean values of its vertices. +% +% +%% Output Arguments +% p - patch object containing colored data +% +% boundary_plot - struct containing boundary information with fields 'nanZeroPatch' and 'boundary' +% `nanZeroPatch` is a patch object of vertices indexed with 0 and faces indexes +% with NaN. If boundary_method is set to 'faces', `boundary` is a patch object +% of the vertices and faces along the boundary between ROIs. Otherwise, +% `boundary` is an array of `plot3` objects drawing the lines that make up the +% boundary. +% +% BOUNDARY - the boundary of the ROIs +% For 'faces' this will be a logical where a value of 1 indicates that face is +% on the boundary between ROIs. Otherwise, BOUNDARY will be a cell where each +% element contains the coordinates of the points making up a boundary, which +% can be plotted as a line. Note that each boundary defines a continuous ROI, +% if a ROI is made up of multiple non-continuous parts (i.e., the ROI is made +% up of multiple unconnected sections), there will be a boundary for each of +% those parts +% +% +%% See Also +% PATCH +% https://au.mathworks.com/help/matlab/ref/matlab.graphics.primitive.patch-properties.html +% findROIBoundaries, graphComponents +% +% +%% Authors +% Mehul Gajwani, Monash University, 2023 + + +%% Prelims +ip = inputParser; +addRequired(ip, 'surface'); +addOptional(ip, 'vertex_id', []); +addOptional(ip, 'data', []); +addOptional(ip, 'boundary_method', 'faces', ... + @(x) any(strcmp({'faces','midpoint','centroid','edge_faces','edge_vertices'},x))); +addOptional(ip, 'cmap', parula); +addOptional(ip, 'linewidth', 2); +addOptional(ip, 'climits', []); + +addParameter(ip, 'boundary_color', [0 0 0]); +addParameter(ip, 'unknown_color', [0.5 0.5 0.5]); +addParameter(ip, 'patch_options', {'EdgeColor','none','FaceLighting','gouraud'}, @(x) iscell(x)); +addParameter(ip, 'FaceColor', 'flat', @(x) any(strcmp({'flat', 'interp'}, x)) ); +addParameter(ip, 'FacesFromROIs', 'first', @(x) any(strcmp({'first', 'mean'}, x)) ); + +ip.parse(surface, varargin{:}); + + +%% Parse inputs +verts = ip.Results.surface.vertices; +faces = ip.Results.surface.faces; +rois = ip.Results.vertex_id; +data = ip.Results.data; + +% If data is in annot file format +if size(data,2) == 2 + [~,~,rois] = unique(rois, 'sorted'); + [~, temp] = sort(data(:,2)); + data = data(temp, 1); end -data_orig = data; - -if sum(vertex_id==0)>0 - vert0present = 1; -else - vert0present = 0; +[verts, faces, rois, data] = ... + checkVertsFacesRoisData(verts, faces, rois, data, 'fillEmpty', true); + +boundary_method = ip.Results.boundary_method; +cmap = ip.Results.cmap; +linewidth = ip.Results.linewidth; +climits = ip.Results.climits; +patch_options = ip.Results.patch_options; +boundary_color = ip.Results.boundary_color; +face_color_method = ip.Results.FaceColor; +facesFromRois = ip.Results.FacesFromROIs; + + +%% Format data +% put it at vertex level if it is at ROI level +% data for a ROI indexed 0 is set to NaN +if size(data, 1) == max(rois) + if any(rois == 0) + temp = [nan; data]; + data = temp(rois+1); + else + data = data(rois); + end end -if min(size(data)) == 1 - % Because some steps require concatination in a specific dimension, - % the input data needs to be configured such that it is an 1*N array +%% Start by plotting faces indexed 0 or data indexed nan +% dataFaces are the faces that contain data and should NOT be plotted as part of +% the boundary or unknown areas - if size(data,2) > size(data,1) - data = data'; - end - - if length(data) ~= length(unique(vertex_id))-vert0present && length(data) ~= length(vertex_id) - error('''data'' needs to either contain one value per roi, contain a value for each vertex, or be an N*2 array showing which data to plot to which ROI ID') - end - - if length(data) ~= length(vertices) - ROI_ids = (1:length(data))'; - end +roiFaces = rois(faces); +nanFaces = any(ismember(faces, find(isnan(data))), 2); +if strcmp(boundary_method, 'faces') + dataFaces = any(roiFaces, 2); % faces with any vertex on known region else - if size(data,2) ~= 2 - error('If providing ''data'' with ROI ids, then the first column needs to be the data and the second the ROI id') - end - - ROI_ids = data(:,2); - data = data(:,1); - + dataFaces = all(roiFaces, 2); % faces with all vertices on known regions end -if nargin < 7 - climits = [nanmin(data) nanmax(data)]; -end +boundary_plot.nanZeroPatch = ... + patch(struct('Vertices', verts, 'Faces', faces(nanFaces | ~dataFaces,:)), ... + 'FaceColor', ip.Results.unknown_color, patch_options{:}); +hold on; -% Find the boundaries of the ROIs -switch boundary_method - case 'none' -BOUNDARY = []; - case {'midpoint','centroid','edge_vertices','faces','edge_faces'} -BOUNDARY = findROIboundaries(vertices,faces,vertex_id,boundary_method); - otherwise - error([boundary_method,' is not a recognised boundary method']) + +%% MAIN: Plot the data +if strcmp(boundary_method, 'faces') + boundaryFaces = any(diff(roiFaces, [], 2), 2); + BOUNDARY = boundaryFaces; +else + boundaryFaces = zeros(size(faces,1),1); end -% Set up some options. -switch boundary_method - case 'faces' - colorFaceBoundaries = 1; -% If boundaries are defined by faces, then the face -% color method for the patch object needs to be 'flat' otherwise it won't -% work - face_color_method = 'flat'; - - case {'midpoint','centroid','edge_vertices','edge_faces'} - - colorFaceBoundaries = 0; - - if length(data) == length(vertex_id) - % If there is data for each individual vertex, use 'interp' for the - % face color - face_color_method = 'interp'; - +if strcmp(face_color_method, 'flat') + % --> manually create FaceVertexCData by using the first/mean vertex of each face + if strcmp(facesFromRois, 'first') + data = data(faces(dataFaces&~boundaryFaces,1)); else - % If there is data for each individual ROI, use 'flat' for the - % face color. - face_color_method = 'flat'; + data = mean(data(faces(dataFaces&~boundaryFaces,:))); end - case 'none' - colorFaceBoundaries = 0; - face_color_method = 'interp'; - otherwise - error('Unrecognised ''boundary_method'' option') + % --> otherwise, leave data at vertex level and allow MATLAB to interpolate end -% Get the vertex or face data and colormap to use +p = patch(struct('Vertices', verts, 'Faces', faces(dataFaces&~boundaryFaces,:)), ... + 'FaceVertexCData', data, 'FaceColor', face_color_method, patch_options{:}); + -% This is the old way of doing it, it generated a new colormap instead of assigning colours to faces/vertices -%[FaceVertexCData,new_cmap,new_climits,orig_data_climits] = makeFaceVertexCData_old(vertices,faces,vertex_id,data,cmap,colorFaceBoundaries,1,climits); +%% Plot boundary +if strcmp(boundary_method, 'faces') + boundary_plot.boundary = ... + patch(struct('Vertices', verts, 'Faces', faces(boundaryFaces,:)), ... + 'FaceColor', boundary_color, patch_options{:}); +else + BOUNDARY = findROIboundaries(verts,faces,rois,boundary_method); + if linewidth > 0 + for i = 1:length(BOUNDARY) + boundary_plot.boundary(i) = ... + plot3(BOUNDARY{i}(:,1), BOUNDARY{i}(:,2), BOUNDARY{i}(:,3), ... + 'Color', boundary_color, 'LineWidth',linewidth,'Clipping','off'); + end + end +end -FaceVertexCData = makeFaceVertexCData(vertices,faces,vertex_id,data_orig,cmap,climits,colorFaceBoundaries); -% Plot the surface using some preconfigured options -p = patch(surface); -set(p,'FaceVertexCData',FaceVertexCData,'EdgeColor','none','FaceColor',face_color_method,'Clipping','off'); -p.FaceLighting = 'gouraud'; -material dull +%% Beautify +material dull; +colormap(gca, cmap()); +if ~isempty(climits); try caxis(climits); catch; end; end -hold on +% camlight(gca, 80, -10); +% camlight(gca, -80, -10); +% view([90 0]); +% axis off equal vis3d; -% Draw the boundary if 'midpoint' or 'centroid' was used. -switch boundary_method - case {'midpoint','centroid','edge_vertices','edge_faces'} - for i = 1:length(BOUNDARY) - boundary_plot.boundary(i) = plot3(BOUNDARY{i}(:,1), BOUNDARY{i}(:,2), BOUNDARY{i}(:,3), 'Color', 'k', 'LineWidth',linewidth,'Clipping','off'); - end - otherwise - boundary_plot = []; end