├── .gitignore ├── LICENSE ├── README.md ├── Thumbs.db ├── cover_image.png ├── demo_data.mat ├── demo_script.m ├── fix_lighting.m ├── paint_mesh.m ├── plot_data_on_mesh.m ├── plot_mesh_brain.m ├── pull_3d_scatter_dots.m └── read_freesurfer_brain.m /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edden-gerber/ecog_fmri_visualization_matlab/c1a7ec76b021e80ff3dbfd32e5bf60fe7414b990/.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, edden-gerber 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The ECoG/fMRI visualization and data plotting toolbox for Matlab 2 | ![cover image](cover_image.png) 3 | 4 | This toolbox provides functions to visualize 3D brain models in Matlab, as well as to flexibly plot data on and around the brain surfaces. The toolbox includes a 5 | demo script and data to get you quickly familiarized with it. All functions include comprehensive documentation; type 'help to access description and usage instructions. 6 | 7 | 8 | ### Main functions: 9 | 10 | plot_mesh_brain: reads a mesh object (structure defined with cortices and faces) and plots it as a 3D patch object with graphical properties suitable for the presentation of cortical surfaces. 11 | 12 | plot_data_on_mesh: plots data on the brain patch object, either by coloring its surface according to a colormap or by adding a scatter plot object. This function is suitable both for presenting data from intracranial electrodes as well as for coloring entire surface regions as for fMRI data. Multiple layers of data can be presented with separete color maps. 13 | 14 | paint_mesh: a simpler function to paint all or part of the mesh surface. This function is called by plot_data_on_mesh, but for fmri-type data it is probably more suitable as it requires less tuning of the input arguments. 15 | 16 | 17 | 18 | ### Additional functions: 19 | 20 | read_freesurfer_brain: this is a function which can be used on machines which have FreeSurfer installed to import cortical surfaces from brain scans which have been processed by FreeSurfer into Matlab. 21 | 22 | fix_lighting: this is a simple function which corrects the angle of lighting to "headlight". It is needed since rotation of the brain objects also rotates the lighting angle. Alternatively to using this function, click anywhere on the figure created by plot_data_on_mesh. 23 | 24 | pull_3d_scatter_dots: this function "pulls" scatter plot objects toward the viewing angle. This is useful since markers plotted directly on the cortical surface are often "submerged" in it. 25 | 26 | 27 | 28 | ### Demo script: 29 | 30 | Open "demo_script.m" and run it cell-by-cell or line-by-line to get familiarized with the above functions. 31 | 32 | 33 | 34 | ### Copyright and citation: 35 | 36 | Toolbox written by Edden M. Gerber, lab of Leon Deouell, Hebrew University of Jerusalem 2015. 37 | 38 | This toolbox uses "FreezeColors.m" written by John Iversen 2005-10, john_iversen@post.harvard.edu. 39 | 40 | Thanks to Tal Golan for theoretical and technical assistance. 41 | 42 | 43 | There is no citation currently associated with this toolbox, but if you are using it please let me know at edden.gerber@gmail.com. Also please feel free to send feedback, bug reports etc. 44 | 45 | 46 | 47 | ### Bugs and issues: 48 | There are some issues related to changes in the color scheme of scatter plot objects when surfaces are painted. This is a problem with the external "FreezeColors" function. To avoid this issue it is best to paint surfaces before adding a scatter plot. 49 | -------------------------------------------------------------------------------- /Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edden-gerber/ecog_fmri_visualization_matlab/c1a7ec76b021e80ff3dbfd32e5bf60fe7414b990/Thumbs.db -------------------------------------------------------------------------------- /cover_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edden-gerber/ecog_fmri_visualization_matlab/c1a7ec76b021e80ff3dbfd32e5bf60fe7414b990/cover_image.png -------------------------------------------------------------------------------- /demo_data.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edden-gerber/ecog_fmri_visualization_matlab/c1a7ec76b021e80ff3dbfd32e5bf60fe7414b990/demo_data.mat -------------------------------------------------------------------------------- /demo_script.m: -------------------------------------------------------------------------------- 1 | %% Load data 2 | load('demo_data'); 3 | 4 | %% 5 | %% PLOTTING INTRACRANIAL ELECTRODES AND DATA 6 | %% 7 | 8 | %% First we will plot the brain (the right hemisphere): 9 | 10 | % You can rotate the brain with the 3D rotation tool (selected by default). 11 | % Since you are also rotating the light source, it needs to be manually 12 | % reset. To do this, de-select any active tool and click on the figure, or 13 | % type "fix_lighting". 14 | 15 | figure; 16 | plot_mesh_brain(brain_data.pial_right); 17 | 18 | 19 | %% Now let's add electrodes. 20 | 21 | % The data table "electrode_data_table" contains the following information 22 | % for each electrode: 23 | % * Is it visually-responsive (true/false) 24 | % * Response onset latency (in ms) 25 | % * XYZ coordinates 26 | % * The index of the vertex on the mesh brain to which it is closest. 27 | 28 | d = electrode_data_table; 29 | 30 | % We will plot all the electrodes according to their coordinates. 31 | % Each electrode will be marked by a black circle. If instead of 'k' we 32 | % included a numerical vector with length equal to the number of 33 | % electrodes, they would have been colored according to the current color 34 | % map. 35 | 36 | plot_data_on_mesh(d.xyz_coordinates,'k','markersize',30); 37 | 38 | % Note that some of the plotted circles may be partly "burried" in the 39 | % brain. To fix this, settle on the view you want and then type: 40 | % pull_3d_scatter_dots(d); 41 | % where d is the distance (e.g., 1). The scatter 42 | % objects will be pulled toward the "camera" position. 43 | 44 | 45 | %% Add colored surface patches to show the response latency of each responsive electrode 46 | 47 | % For each electrodes, a surface patch is defined by the vertices which are 48 | % within a certain distance of it (default is 3). 49 | plot_data_on_mesh(d.xyz_coordinates(d.responsive,:), d.onset_latency(d.responsive),'surface'); 50 | 51 | % change color scale to match response latency range 52 | caxis([0 200]); 53 | colorbar; 54 | 55 | 56 | 57 | %% 58 | %% PLOTTING DATA ON THE BRAIN SURFACE (AS FOR FMRI) 59 | %% 60 | 61 | %% For this part of the demo we will plot an inflated version of the same 62 | %% hemisphere 63 | figure; 64 | % The second parameter is the initial viewing position 65 | plot_mesh_brain(brain_data.inflated_right,[90 0]); 66 | 67 | 68 | %% But the inflated representation is not so clear - let's plot it again 69 | %% with gray patches to indicate sulci 70 | % Load the curvaure at each vertex 71 | curv = vertex_data.right.vertex_curvature_index; 72 | % The sulci are the negative values 73 | curv = -sign(curv); 74 | 75 | % Plot 76 | figure; 77 | plot_mesh_brain(brain_data.inflated_right,[-50 0], curv); 78 | 79 | % Finally, choose a colormap and color range to best represent the data 80 | caxis([-4 1]); 81 | colormap gray 82 | 83 | 84 | %% Now we can take a map of fMRI activity for a specific part of the 85 | %% surface, and plot it over this brain with a separate color map 86 | 87 | % The variable "fmri_data" hold a simple artificial map of fMRI activity 88 | % for a circumscribed region-of-interest on the occipital oortex. It is a 89 | % vector with length equal to the number of mesh vertices, with every 90 | % vertex outside the ROI set to NaN. 91 | 92 | % To still be able to see the folding structure under the activity map, we 93 | % will use partial transparency (0 is the default no transparency): 94 | transparency = 0.3; 95 | 96 | % Plot 97 | paint_mesh(fmri_data,transparency); 98 | 99 | % Finally the colormap and range need to be changed for this "layer" 100 | % because they are still identical to the previous ones. 101 | caxis([0 40]); 102 | colormap hot 103 | 104 | % Note that multiple layers can be painted in this way with different color 105 | % maps. 106 | 107 | %% The same activity map on the non-inflated brain 108 | figure; 109 | plot_mesh_brain(brain_data.pial_right,[-50 0]); 110 | paint_mesh(fmri_data,0.3); 111 | caxis([0 40]); 112 | colormap hot 113 | -------------------------------------------------------------------------------- /fix_lighting.m: -------------------------------------------------------------------------------- 1 | function fix_lighting(fig_handle,~) 2 | % Set the light source of the selected figure (default: current figure) to 3 | % originate from the viewing direction ("headlight"). If there is no light 4 | % source, a new one is created. 5 | 6 | % Get figure handle 7 | if nargin<1 8 | fig_handle = gcf; 9 | end 10 | 11 | % Find the light object 12 | l_handle = findobj(fig_handle,'Type','light'); 13 | 14 | % If no light source, create one 15 | if isempty(l_handle) 16 | l_handle = light; 17 | end 18 | 19 | % If there is more than one light source, take the last one 20 | if length(l_handle) > 1 21 | l_handle = l_handle(end); 22 | end 23 | 24 | % Set the light at the camera position 25 | camlight(l_handle,'headlight'); 26 | 27 | end 28 | 29 | -------------------------------------------------------------------------------- /paint_mesh.m: -------------------------------------------------------------------------------- 1 | function [ mesh_handle ] = paint_mesh( color_values, ... 2 | transparency_values, ... 3 | add_new_layer, ... 4 | mesh_object) 5 | % PAINT_MESH colors a patch object by attaching color-mapped numeric values 6 | % to the mesh vertices. It can either modify a current patch object or 7 | % create a new "layer" to add a partially-transparent color map while 8 | % preserving existing ones (e.g. add a neural activation map over a 9 | % cortical surface already colored according to curvature). In this case 10 | % the original mesh's colormap is frozen so subsequenct changes to the 11 | % colormap will affect only the plotted data layer. Multiple coloring 12 | % layers can be created in this way. 13 | % This function is called by plot_data_on_mesh and can also be used 14 | % directly. The function can only be used when there is a patch object 15 | % within the current figure. 16 | % 17 | % Usage: 18 | % PAINT_MESH(C) where C is a vector having the same length as the 19 | % number of vertices in the patch object, colors the surface according to 20 | % the current colormap. NaN values can be used to indicate transparent 21 | % vertices. 22 | % PAINT_MESH(C,T) where T is a vector with the same length as C and values 23 | % between 0 and 1, indicates the transparency of each vertex (0 is 24 | % non-transparent). By default all vertices will be non-transparent. 25 | % PAINT_MESH(C,T,new_layer) where new_layer is a boolean, indicates whether 26 | % the color map will be applied to the current patch object or painted 27 | % over it as a new object. 28 | % PAINT_MESH(C,T,new_layer,h) where h is the handle of a patch object, 29 | % speficies to which object the function should be applied. By default 30 | % the function targets the last-generated patch object in the current 31 | % figure. 32 | % H = PAINT_MESH(...) returns the handle of the painted patch object. 33 | % 34 | % Any of the optional paramters can be skipped by inputting an null array 35 | % ([]) instead. 36 | % 37 | % Example: To paint e.g. a map of fMRI activation values on part of a 38 | % cortical surface, generate a 3D cortex mesh using the plot_mesh_brain 39 | % function, then run this function with the first argument being a vector 40 | % of each vertex's color value, with non-active vertices set to NaN. 41 | % Subsequently change the colormap or caxis using standard Matlab 42 | % functions. 43 | % 44 | % NOTE: Since the mesh face colors are interpolated between vertex colors, 45 | % 46 | % Written by Edden M. Gerber, Hebrew University of Jerusalem 2015. 47 | % edden.gerber@gmail.com 48 | % 49 | % FreezeColors function written by John Iversen 2005-10, 50 | % john_iversen@post.harvard.edu. 51 | % 52 | 53 | % Handle optional input 54 | if nargin < 4 || isempty(mesh_object) 55 | % If no patch object handle was given, find one in the current figure 56 | % (if there is more than one, use the last one generated) 57 | drawnow; % give objects a chance to load 58 | mesh_object = findobj('Type','Patch'); 59 | mesh_object = mesh_object(1); 60 | end 61 | if nargin < 3 || isempty(add_new_layer) 62 | add_new_layer = true; 63 | end 64 | if nargin < 2 || isempty(transparency_values) 65 | transparency_values = zeros(length(color_values),1); 66 | end 67 | 68 | % Make sure input vectors are column vectors 69 | if size(color_values, 1) == 1 70 | color_values = color_values'; 71 | end 72 | if size(transparency_values, 1) == 1 73 | transparency_values = transparency_values'; 74 | end 75 | 76 | % Make sure input vectors are correct length 77 | if length(color_values) == length(mesh_object.Vertices) 78 | face_colors = false; 79 | elseif length(color_values) == length(mesh_object.Faces) 80 | face_colors = true; 81 | else 82 | error(['ERROR: "color_values" should be a numeric vector with length ' ... 83 | 'equal to the number of mesh vertices (' num2str(length(mesh_object.Vertices)) ... 84 | ') or mesh faces ' num2str(length(mesh_object.Faces)) '.']); 85 | end 86 | 87 | if length(transparency_values) ~= length(color_values) && ~isscalar(transparency_values) 88 | error(['ERROR: "transparency_values" should be the same length as "color_values"']); 89 | end 90 | if ~isnumeric(color_values) 91 | error('ERROR: "color_values" should be a numeric vector.'); 92 | end 93 | 94 | % If a single-value "transparency_values" input is given, set it to all 95 | % points 96 | if isscalar(transparency_values) 97 | transparency_values = ones(size(color_values)) * transparency_values; 98 | end 99 | 100 | % Interpret NaN color values as transparent 101 | tt = isnan(color_values); 102 | color_values(tt) = 0; 103 | transparency_values(tt) = 1; 104 | 105 | % Get current axis object 106 | curr_axis = mesh_object.Parent; 107 | 108 | % Create a new mesh layer if needed (it occupies the same space and will be 109 | % displayed over the existing one). 110 | if add_new_layer 111 | freezeColors(curr_axis); % Freeze the current colormap so a different 112 | % one can be painted over it. Uses a function by John Iversen. 113 | mesh_object = create_new_patch_layer(mesh_object); 114 | end 115 | 116 | % Paint the mesh object 117 | if face_colors 118 | set(mesh_object,'FaceColor','flat'); 119 | else 120 | set(mesh_object,'FaceColor','interp'); 121 | end 122 | set(mesh_object,'FaceVertexCData',color_values); 123 | 124 | % Set transparency 125 | set(curr_axis,'ALim',[0 1]); 126 | if face_colors 127 | set(mesh_object,'FaceAlpha','flat'); % this used to run in both cases 128 | else 129 | set(mesh_object,'FaceAlpha','interp'); 130 | end 131 | 132 | set(mesh_object,'FaceVertexAlphaData',1-transparency_values); 133 | 134 | % Return handle 135 | mesh_handle = mesh_object; 136 | 137 | end 138 | 139 | % External functions 140 | function new_patch = create_new_patch_layer(patch) 141 | 142 | % Create new patch object 143 | new_patch = trisurf(patch.Faces, patch.Vertices(:,1), patch.Vertices(:,2), patch.Vertices(:,3)); 144 | 145 | % Copy other patch object properties from original 146 | set(new_patch,'AlignVertexCenters',get(patch,'AlignVertexCenters'), ... 147 | 'AlphaDataMapping',get(patch,'AlphaDataMapping'), ... 148 | 'AmbientStrength',get(patch,'AmbientStrength'), ... 149 | 'BackFaceLighting',get(patch,'BackFaceLighting'), ... 150 | 'BusyAction',get(patch,'BusyAction'), ... 151 | 'ButtonDownFcn',get(patch,'ButtonDownFcn'), ... 152 | 'CData',get(patch,'CData'), ... 153 | 'CDataMapping',get(patch,'CDataMapping'), ... 154 | 'Clipping',get(patch,'Clipping'), ... 155 | 'CreateFcn',get(patch,'CreateFcn'), ... 156 | 'DeleteFcn',get(patch,'DeleteFcn'), ... 157 | 'DiffuseStrength',get(patch,'DiffuseStrength'), ... 158 | 'DisplayName',get(patch,'DisplayName'), ... 159 | 'EdgeColor',get(patch,'EdgeColor'), ... 160 | 'EdgeLighting',get(patch,'EdgeLighting'), ... 161 | 'FaceColor',get(patch,'FaceColor'), ... 162 | 'FaceLighting',get(patch,'FaceLighting'), ... 163 | 'FaceNormals',get(patch,'FaceNormals'), ... 164 | 'FaceNormalsMode',get(patch,'FaceNormalsMode'), ... 165 | 'FaceVertexAlphaData',get(patch,'FaceVertexAlphaData'), ... 166 | 'FaceVertexCData',get(patch,'FaceVertexCData'), ... 167 | 'HandleVisibility',get(patch,'HandleVisibility'), ... 168 | 'HitTest',get(patch,'HitTest'), ... 169 | 'Interruptible',get(patch,'Interruptible'), ... 170 | 'LineStyle',get(patch,'LineStyle'), ... 171 | 'LineWidth',get(patch,'LineWidth'), ... 172 | 'Marker',get(patch,'Marker'), ... 173 | 'MarkerSize',get(patch,'MarkerSize'), ... 174 | 'PickableParts',get(patch,'PickableParts'), ... 175 | 'SelectionHighlight',get(patch,'SelectionHighlight'), ... 176 | 'SpecularColorReflectance',get(patch,'SpecularColorReflectance'), ... 177 | 'SpecularExponent',get(patch,'SpecularExponent'), ... 178 | 'SpecularStrength',get(patch,'SpecularStrength'), ... 179 | 'UIContextMenu',get(patch,'UIContextMenu'), ... 180 | 'Visible',get(patch,'Visible')); 181 | end 182 | 183 | function freezeColors(varargin) 184 | % freezeColors Lock colors of plot, enabling multiple colormaps per figure. (v2.3) 185 | % 186 | % Problem: There is only one colormap per figure. This function provides 187 | % an easy solution when plots using different colomaps are desired 188 | % in the same figure. 189 | % 190 | % freezeColors freezes the colors of graphics objects in the current axis so 191 | % that subsequent changes to the colormap (or caxis) will not change the 192 | % colors of these objects. freezeColors works on any graphics object 193 | % with CData in indexed-color mode: surfaces, images, scattergroups, 194 | % bargroups, patches, etc. It works by converting CData to true-color rgb 195 | % based on the colormap active at the time freezeColors is called. 196 | % 197 | % The original indexed color data is saved, and can be restored using 198 | % unfreezeColors, making the plot once again subject to the colormap and 199 | % caxis. 200 | % 201 | % 202 | % Usage: 203 | % freezeColors applies to all objects in current axis (gca), 204 | % freezeColors(axh) same, but works on axis axh. 205 | % 206 | % Example: 207 | % subplot(2,1,1); imagesc(X); colormap hot; freezeColors 208 | % subplot(2,1,2); imagesc(Y); colormap hsv; freezeColors etc... 209 | % 210 | % Note: colorbars must also be frozen. Due to Matlab 'improvements' this can 211 | % no longer be done with freezeColors. Instead, please 212 | % use the function CBFREEZE by Carlos Adrian Vargas Aguilera 213 | % that can be downloaded from the MATLAB File Exchange 214 | % (http://www.mathworks.com/matlabcentral/fileexchange/24371) 215 | % 216 | % h=colorbar; cbfreeze(h), or simply cbfreeze(colorbar) 217 | % 218 | % For additional examples, see test/test_main.m 219 | % 220 | % Side effect on render mode: freezeColors does not work with the painters 221 | % renderer, because Matlab doesn't support rgb color data in 222 | % painters mode. If the current renderer is painters, freezeColors 223 | % changes it to zbuffer. This may have unexpected effects on other aspects 224 | % of your plots. 225 | % 226 | % See also unfreezeColors, freezeColors_pub.html, cbfreeze. 227 | % 228 | % 229 | % John Iversen (iversen@nsi.edu) 3/23/05 230 | % 231 | 232 | % Changes: 233 | % JRI (iversen@nsi.edu) 4/19/06 Correctly handles scaled integer cdata 234 | % JRI 9/1/06 should now handle all objects with cdata: images, surfaces, 235 | % scatterplots. (v 2.1) 236 | % JRI 11/11/06 Preserves NaN colors. Hidden option (v 2.2, not uploaded) 237 | % JRI 3/17/07 Preserve caxis after freezing--maintains colorbar scale (v 2.3) 238 | % JRI 4/12/07 Check for painters mode as Matlab doesn't support rgb in it. 239 | % JRI 4/9/08 Fix preserving caxis for objects within hggroups (e.g. contourf) 240 | % JRI 4/7/10 Change documentation for colorbars 241 | 242 | % Hidden option for NaN colors: 243 | % Missing data are often represented by NaN in the indexed color 244 | % data, which renders transparently. This transparency will be preserved 245 | % when freezing colors. If instead you wish such gaps to be filled with 246 | % a real color, add 'nancolor',[r g b] to the end of the arguments. E.g. 247 | % freezeColors('nancolor',[r g b]) or freezeColors(axh,'nancolor',[r g b]), 248 | % where [r g b] is a color vector. This works on images & pcolor, but not on 249 | % surfaces. 250 | % Thanks to Fabiano Busdraghi and Jody Klymak for the suggestions. Bugfixes 251 | % attributed in the code. 252 | 253 | % Free for all uses, but please retain the following: 254 | % Original Author: 255 | % John Iversen, 2005-10 256 | % john_iversen@post.harvard.edu 257 | 258 | appdatacode = 'JRI__freezeColorsData'; 259 | 260 | [h, nancolor] = checkArgs(varargin); 261 | 262 | %gather all children with scaled or indexed CData 263 | cdatah = getCDataHandles(h); 264 | 265 | %current colormap 266 | cmap = colormap; 267 | nColors = size(cmap,1); 268 | cax = caxis; 269 | 270 | % convert object color indexes into colormap to true-color data using 271 | % current colormap 272 | for hh = cdatah', 273 | g = get(hh); 274 | 275 | %preserve parent axis clim 276 | parentAx = getParentAxes(hh); 277 | originalClim = get(parentAx, 'clim'); 278 | 279 | % Note: Special handling of patches: For some reason, setting 280 | % cdata on patches created by bar() yields an error, 281 | % so instead we'll set facevertexcdata instead for patches. 282 | if ~strcmp(g.Type,'patch'), 283 | cdata = g.CData; 284 | else 285 | cdata = g.FaceVertexCData; 286 | end 287 | 288 | %get cdata mapping (most objects (except scattergroup) have it) 289 | if isfield(g,'CDataMapping'), 290 | scalemode = g.CDataMapping; 291 | else 292 | scalemode = 'scaled'; 293 | end 294 | 295 | %save original indexed data for use with unfreezeColors 296 | siz = size(cdata); 297 | setappdata(hh, appdatacode, {cdata scalemode}); 298 | 299 | %convert cdata to indexes into colormap 300 | if strcmp(scalemode,'scaled'), 301 | %4/19/06 JRI, Accommodate scaled display of integer cdata: 302 | % in MATLAB, uint * double = uint, so must coerce cdata to double 303 | % Thanks to O Yamashita for pointing this need out 304 | idx = ceil( (double(cdata) - cax(1)) / (cax(2)-cax(1)) * (nColors-1))+1; 305 | idx(idx<1) = 1; % Adde by Edden Gerber Nov. 2015. 306 | else %direct mapping 307 | idx = cdata; 308 | %10/8/09 in case direct data is non-int (e.g. image;freezeColors) 309 | % (Floor mimics how matlab converts data into colormap index.) 310 | % Thanks to D Armyr for the catch 311 | idx = floor(idx); 312 | end 313 | 314 | %clamp to [1, nColors] 315 | 316 | idx(idx>nColors) = nColors; 317 | 318 | %handle nans in idx 319 | nanmask = isnan(idx); 320 | idx(nanmask)=1; %temporarily replace w/ a valid colormap index 321 | 322 | %make true-color data--using current colormap 323 | realcolor = zeros(siz); 324 | for i = 1:3, 325 | c = cmap(idx,i); 326 | c = reshape(c,siz); 327 | c(nanmask) = nancolor(i); %restore Nan (or nancolor if specified) 328 | realcolor(:,:,i) = c; 329 | end 330 | 331 | %apply new true-color color data 332 | 333 | %true-color is not supported in painters renderer, so switch out of that 334 | if strcmp(get(gcf,'renderer'), 'painters'), 335 | set(gcf,'renderer','zbuffer'); 336 | end 337 | 338 | %replace original CData with true-color data 339 | if ~strcmp(g.Type,'patch'), 340 | if strcmp(g.Type,'scatter') 341 | set(hh,'CData',realcolor(:,:,1)); % added this (Edden, 11/6/2015) 342 | else 343 | set(hh,'CData',realcolor); 344 | end 345 | else 346 | set(hh,'faceVertexCData',permute(realcolor,[1 3 2])) 347 | end 348 | 349 | %restore clim (so colorbar will show correct limits) 350 | if ~isempty(parentAx), 351 | set(parentAx,'clim',originalClim) 352 | end 353 | 354 | end %loop on indexed-color objects 355 | 356 | 357 | % ============================================================================ % 358 | % Local functions 359 | 360 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 361 | %% getCDataHandles -- get handles of all descendents with indexed CData 362 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 363 | 364 | function hout = getCDataHandles(h) 365 | % getCDataHandles Find all objects with indexed CData 366 | 367 | %recursively descend object tree, finding objects with indexed CData 368 | % An exception: don't include children of objects that themselves have CData: 369 | % for example, scattergroups are non-standard hggroups, with CData. Changing 370 | % such a group's CData automatically changes the CData of its children, 371 | % (as well as the children's handles), so there's no need to act on them. 372 | 373 | error(nargchk(1,1,nargin,'struct')) 374 | 375 | hout = []; 376 | if isempty(h),return;end 377 | 378 | ch = get(h,'children'); 379 | for hh = ch' 380 | g = get(hh); 381 | if isfield(g,'CData'), %does object have CData? 382 | %is it indexed/scaled? 383 | if ~isempty(g.CData) && isnumeric(g.CData) && size(g.CData,3)==1, 384 | hout = [hout; hh]; %#ok %yes, add to list 385 | end 386 | else %no CData, see if object has any interesting children 387 | hout = [hout; getCDataHandles(hh)]; %#ok 388 | end 389 | end 390 | 391 | end 392 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 393 | %% getParentAxes -- return handle of axes object to which a given object belongs 394 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 395 | function hAx = getParentAxes(h) 396 | % getParentAxes Return enclosing axes of a given object (could be self) 397 | 398 | error(nargchk(1,1,nargin,'struct')) 399 | %object itself may be an axis 400 | if strcmp(get(h,'type'),'axes'), 401 | hAx = h; 402 | return 403 | end 404 | 405 | parent = get(h,'parent'); 406 | if (strcmp(get(parent,'type'), 'axes')), 407 | hAx = parent; 408 | else 409 | hAx = getParentAxes(parent); 410 | end 411 | 412 | end 413 | 414 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 415 | %% checkArgs -- Validate input arguments 416 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 417 | 418 | function [h, nancolor] = checkArgs(args) 419 | % checkArgs Validate input arguments to freezeColors 420 | 421 | nargs = length(args); 422 | error(nargchk(0,3,nargs,'struct')) 423 | 424 | %grab handle from first argument if we have an odd number of arguments 425 | if mod(nargs,2), 426 | h = args{1}; 427 | if ~ishandle(h), 428 | error('JRI:freezeColors:checkArgs:invalidHandle',... 429 | 'The first argument must be a valid graphics handle (to an axis)') 430 | end 431 | % 4/2010 check if object to be frozen is a colorbar 432 | if strcmp(get(h,'Tag'),'Colorbar'), 433 | if ~exist('cbfreeze.m'), 434 | warning('JRI:freezeColors:checkArgs:cannotFreezeColorbar',... 435 | ['You seem to be attempting to freeze a colorbar. This no longer'... 436 | 'works. Please read the help for freezeColors for the solution.']) 437 | else 438 | cbfreeze(h); 439 | return 440 | end 441 | end 442 | args{1} = []; 443 | nargs = nargs-1; 444 | else 445 | h = gca; 446 | end 447 | 448 | %set nancolor if that option was specified 449 | nancolor = [nan nan nan]; 450 | if nargs == 2, 451 | if strcmpi(args{end-1},'nancolor'), 452 | nancolor = args{end}; 453 | if ~all(size(nancolor)==[1 3]), 454 | error('JRI:freezeColors:checkArgs:badColorArgument',... 455 | 'nancolor must be [r g b] vector'); 456 | end 457 | nancolor(nancolor>1) = 1; nancolor(nancolor<0) = 0; 458 | else 459 | error('JRI:freezeColors:checkArgs:unrecognizedOption',... 460 | 'Unrecognized option (%s). Only ''nancolor'' is valid.',args{end-1}) 461 | end 462 | end 463 | 464 | end 465 | 466 | end 467 | -------------------------------------------------------------------------------- /plot_data_on_mesh.m: -------------------------------------------------------------------------------- 1 | function plot_data_on_mesh( coordinates, varargin ) 2 | % PLOT_DATA_ON_MESH plots data on a 3D mesh object, e.g. data from 3 | % electrodes/fMRI on a 3D cortical surface. Data is plotted either as a 3D 4 | % scatter plot or as colored surface patches on the mesh surface itself. 5 | % The coordinates to be plotted can be given either as XYZ triplets or as 6 | % mesh vertex indexes. The function can only be used when there is a patch 7 | % object within the current figure. 8 | % The surface coloring method works by creating a second partially- 9 | % transparent mesh object which overlaps the original. This is done to 10 | % allow separate coloring schemes for the mesh and the data (e.g. grayscale 11 | % for cortical curvature and color for overlaid activation map). The 12 | % original mesh's colormap is frozen so subsequenct changes to the colormap 13 | % will affect only the plotted data layer. Multiple coloring layers can be 14 | % created in this way. 15 | % 16 | % Note: To plot activation maps on the surface (as for fMRI) rather than 17 | % discrete points data (as for ECoG), use a patch size value of zero so 18 | % that each vertex will only receive its own color value. This will also 19 | % drastically decrease run time for large maps. Alternatively use the 20 | % function paint_mesh directly (it works the same way but will require less 21 | % input arguments). 22 | % 23 | % Usage: 24 | % PLOT_DATA_ON_MESH(C) plots circles in the locations specificed 25 | % by coordinates C. If C is an Nx3 matrix, it is read as XYZ 26 | % coordinates within the current axis. If it is a vector, it is read as 27 | % vertex indexes for the mesh object (i.e. the coordinates are those of 28 | % the indicated mesh vertices). NaN values are ignored. 29 | % PLOT_DATA_ON_MESH(C,clr) defines that color values of the plotted 30 | % coordinates. clr can be in any of the following formats: 31 | % 1. Single character (e.g. 'b') - uniform color according to the Matlab 32 | % color code scheme. 33 | % 2. RGB triples - input is an Nx3 matrix where N is the number of 34 | % plotted coordinates and values range between 0 and 1. 35 | % 3. Color map - input is an Nx1 numerical vector, and values are mapped 36 | % to colors accoring to the figure's colormap. This is the only 37 | % implemented coloring method for surface patches. 38 | % PLOT_DATA_ON_MESH(...,'scatter') defines the plotting method as "scatter 39 | % plot", i.e. indicated coordinates will be plotted using the Matlab's 40 | % scatter3 function. This is the default plotting method. 41 | % PLOT_DATA_ON_MESH(...,'surface') defines the plotting method as "surface 42 | % patches", i.e. indicated coordinates will be plotted by coloring the 43 | % mesh surface in a given radius around them. 44 | % PLOT_DATA_ON_MESH(...,'markersize',s), where s is either a scalar or an 45 | % vector of length N where N is the number of plotted coordinates, and 46 | % the scatterplot method is used, defines the scatterplot marker size(s). 47 | % Default value is 100. 48 | % PLOT_DATA_ON_MESH(...,'scatterarg',arg), where arg is a cell array and 49 | % the scatterplot method is used, provides encapsulated optional 50 | % arguments to Matlab's scatter3 function to allow full control of its 51 | % functionality. For example: 52 | % plot_data_on_mesh(...,'scatterarg',{'filled','markertype','*'}). 53 | % PLOT_DATA_ON_MESH(...,'patchsize',s), where s is a scalar and the 54 | % "surface" plotting method is used, defines the radius around each 55 | % plotted coordinate where vertices are colored according to that 56 | % coordinate's color value. Default value is 3. 57 | % PLOT_DATA_ON_MESH(...,'overlapmethod',M), when the surface plotting 58 | % method is used, defines how overlap between patches is handled. M is a 59 | % string which can be set to any of the following: 60 | % 1. 'max' - maximal value of the two overlapping coordinates is used, in 61 | % absolute value but preserving the sign (e.g. max(-3,1)=-3). This is the 62 | % default value. 63 | % 2. 'mean' - mean value of the overlapping coordinate is used. 64 | % 3. 'sum' - summed value of the overlapping coordinate is used. 65 | % PLOT_DATA_ON_MESH(...,'trasparemcy',T), where the surface plotting method 66 | % is used and T is a scalar between 0 and 1, defines the degree of 67 | % transparency of the colored surface patches. Default value is 0. 68 | % PLOT_DATA_ON_MESH(...,'colormap',cm) where the surface plotting method is 69 | % used and cm is a 64x3 matrix, defines the surface patches color map. 70 | % This can be also set through the colormap function. 71 | % PLOT_DATA_ON_MESH(...,'shownumbers') will add number labels which will 72 | % float next to the plotted data (e.g. plotted coordinates will be 73 | % labeled as 1,2,3...). 74 | % 75 | % Written by Edden M. Gerber, Hebrew University of Jerusalem 2015, 76 | % edden.gerber@gmail.com 77 | 78 | DEFAULT_PLOT_METHOD = 'scatter'; 79 | DEFAULT_MARKER_SIZE = 100; 80 | DEFAULT_MARKER_COLOR = [0 0 1]; 81 | DEFAULT_PATCH_SIZE = 3; 82 | DEFAULT_OVERLAP_METHOD = 'max'; 83 | DEFAULT_TRANSPARENCY = 0; 84 | 85 | % Handle mandatory input 86 | ignore_coord = isnan(coordinates(:,1)); 87 | xyz_coord = true; 88 | if size(coordinates,2) == 1 % a vector of mesh vertices instead of 3D coordinates 89 | h_mesh = findobj(gcf,'Type','Patch'); 90 | if isempty(h_mesh) 91 | error('No patch object found in the current figure (i.e. no brain plotted).'); 92 | end 93 | mesh_vert = h_mesh.Vertices; 94 | vert_idx = coordinates; 95 | coordinates = mesh_vert(vert_idx(~ignore_coord),:); 96 | xyz_coord = false; 97 | elseif size(coordinates,2) ~= 3 98 | error('ERROR: coordinates argument should be a nx3 or a nx1 matrix (3D coordinates or mesh vertex list)'); 99 | end 100 | 101 | % Handle optional input 102 | method = DEFAULT_PLOT_METHOD; % default 103 | marker_size = DEFAULT_MARKER_SIZE; % default 104 | patch_size = DEFAULT_PATCH_SIZE; % default 105 | surface_overlap_method = DEFAULT_OVERLAP_METHOD; 106 | show_numbers = false; 107 | scatter_color_values = DEFAULT_MARKER_COLOR; % default 108 | surface_color_values = ones(size(coordinates,1),1); % default 109 | transparency_value = DEFAULT_TRANSPARENCY; 110 | color_map = colormap; 111 | scatter_arg = {}; 112 | narg = size(varargin,2); 113 | arg = 1; 114 | while arg <= narg 115 | if ischar(varargin{arg}) 116 | switch lower(varargin{arg}) 117 | case 'scatter' 118 | method = 'scatter'; 119 | arg = arg + 1; 120 | case 'surface' 121 | method = 'surface'; 122 | arg = arg + 1; 123 | case 'scatterarg' 124 | if narg > arg && iscell(varargin{arg+1}) 125 | scatter_arg = varargin{arg+1}; 126 | arg = arg + 2; 127 | else 128 | error('"ScatterArg" argument should be followed by a cell array of strings'); 129 | end 130 | case 'patchsize' 131 | if narg > arg && isscalar(varargin{arg+1}) 132 | patch_size = varargin{arg+1}; 133 | else 134 | error('"PatchSize" argument should be followed by a scalar'); 135 | end 136 | arg = arg + 2; 137 | case 'transparency' 138 | if narg > arg && isscalar(varargin{arg+1}) 139 | if varargin{arg+1} < 0 || varargin{arg+1} > 1 140 | error('"transparency" argument should be followed by a scalar between 0 and 1'); 141 | else 142 | transparency_value = varargin{arg+1}; 143 | end 144 | else 145 | error('"transparency" argument should be followed by a scalar'); 146 | end 147 | arg = arg + 2; 148 | case 'markersize' 149 | if isnumeric(varargin{arg+1}) 150 | marker_size = varargin{arg+1}; 151 | else 152 | error('"MarkerSize" argument should be numeric'); 153 | end 154 | arg = arg + 2; 155 | case 'colormap' 156 | if isnumeric(varargin{arg+1}) && size(varargin{arg+1},1)==64 && size(varargin{arg+1},2)==3 157 | color_map = varargin{arg+1}; 158 | arg = arg + 2; 159 | else 160 | error('"colormap" argument should be a 64x3 numeric matrix'); 161 | end 162 | case 'overlapmethod' 163 | if strcmpi(lower(varargin{arg+1}),'max') 164 | surface_overlap_method = 'max'; 165 | arg = arg + 2; 166 | elseif strcmpi(lower(varargin{arg+1}),'mean') 167 | surface_overlap_method = 'mean'; 168 | arg = arg + 2; 169 | elseif strcmpi(lower(varargin{arg+1}),'sum') 170 | surface_overlap_method = 'sum'; 171 | arg = arg + 2; 172 | else 173 | error('"OverlapMethod" argument should be followed by ''max'' or ''mean''.'); 174 | end 175 | case 'shownumbers' 176 | show_numbers = true; 177 | arg = arg + 1; 178 | otherwise 179 | if length(varargin{arg}) == 1 % single letter indicates color code 180 | scatter_color_values = varargin{arg}; 181 | else 182 | error(['Unknown parameter "' varargin{arg} '".']); 183 | end 184 | arg = arg + 1; 185 | end 186 | elseif islogical(varargin{arg}) 187 | varargin{arg} = double(varargin{arg}); 188 | elseif isnumeric(varargin{arg}) 189 | if size(varargin{arg}(~ignore_coord,:),1) == size(coordinates,1) 190 | scatter_color_values = varargin{arg}(~ignore_coord,:); 191 | surface_color_values = varargin{arg}(~ignore_coord,:); 192 | else 193 | error('Electrode color data must have the same length as the coordinates'); 194 | end 195 | arg = arg + 1; 196 | else 197 | error('Input argument should be numeric or string.'); 198 | end 199 | end 200 | 201 | switch method 202 | case 'scatter' 203 | % 3-D scatter plot of electrode coordinates 204 | scatter3(coordinates(:,1),coordinates(:,2),coordinates(:,3),marker_size,scatter_color_values,scatter_arg{:}); 205 | 206 | case 'surface' 207 | % First find the mesh data object if we haven't yet 208 | if ~exist('mesh_vert','var') 209 | h_mesh = findobj(gcf,'Type','Patch'); 210 | if isempty(h_mesh) 211 | error('No patch object found in the current figure (i.e. no brain plotted).'); 212 | end 213 | mesh_vert = h_mesh.Vertices; 214 | end 215 | 216 | % If XYZ coordinates are given, replace the them with the nearest 217 | % points on the mesh 218 | if xyz_coord 219 | for e=1:size(coordinates,1) 220 | dist=[mesh_vert(:,1)-coordinates(e,1) mesh_vert(:,2)-coordinates(e,2) mesh_vert(:,3)-coordinates(e,3)]; 221 | dist=sqrt(sum(dist.^2,2)); 222 | [~,idx] = min(dist); 223 | coordinates(e,:) = mesh_vert(idx,:); 224 | end 225 | end 226 | 227 | % Create the color values by masking the electrode color values 228 | % with a maximal distance mask of each vertex from the electrode 229 | vertex_color_values = zeros(1,length(mesh_vert)); 230 | if patch_size > 0 231 | num_overlapping_values = zeros(length(mesh_vert),1); 232 | for e = 1:size(coordinates,1) 233 | distx=abs(mesh_vert(:,1)-coordinates(e,1)); 234 | disty=abs(mesh_vert(:,2)-coordinates(e,2)); 235 | distz=abs(mesh_vert(:,3)-coordinates(e,3)); 236 | nearby_vert_list = sqrt(distx.^2+disty.^2+distz.^2) <= patch_size; 237 | 238 | num_overlapping_values = num_overlapping_values + nearby_vert_list; 239 | if strcmp(surface_overlap_method,'sum') % overlapping values are summed 240 | vertex_color_values = vertex_color_values + surface_color_values(e) * nearby_vert_list'; 241 | elseif strcmp(surface_overlap_method,'mean') % overlapping values are summed and then averaged (after this loop) 242 | vertex_color_values = vertex_color_values + surface_color_values(e) * nearby_vert_list'; 243 | else % max: overlapping values take the max value (in absolute value, but preserving the sign) 244 | v = [vertex_color_values ; surface_color_values(e) * nearby_vert_list']; 245 | vertex_color_values = sum(v .* [max(abs(v))==abs(v(1,:)) ; max(abs(v))==abs(v(2,:))]); 246 | end 247 | end 248 | if strcmp(surface_overlap_method,'mean') 249 | num_overlapping_values(num_overlapping_values==0) = 1; 250 | vertex_color_values = vertex_color_values ./ num_overlapping_values'; 251 | end 252 | else % patch size is zero - each vertex will only be given its own 253 | % color value 254 | vertex_color_values(vert_idx) = surface_color_values; 255 | end 256 | 257 | % Create a new external semi-transparent surface and paint it: 258 | transparency_map = ones(length(vertex_color_values),1); 259 | transparency_map(vertex_color_values ~= 0) = transparency_value; 260 | 261 | paint_mesh(vertex_color_values', transparency_map); 262 | colormap(color_map); 263 | set(gca,'CLim',[0 max(vertex_color_values)]); 264 | end 265 | 266 | % Plot coordinate number labels 267 | if show_numbers 268 | pushed_out_coord = coordinates * 1.05; 269 | for e = 1:size(coordinates,1) 270 | text(pushed_out_coord(e,1),pushed_out_coord(e,2),pushed_out_coord(e,3),num2str(e)); 271 | end 272 | end 273 | 274 | end 275 | 276 | -------------------------------------------------------------------------------- /plot_mesh_brain.m: -------------------------------------------------------------------------------- 1 | function [ handle ] = plot_mesh_brain( brain_mesh, ... 2 | view_position, ... 3 | vertex_color_values, ... 4 | transparency, ... 5 | color_map ) 6 | %PLOT_MESH_BRAIN plots a 3D brain image defined by a mesh object 7 | % 8 | % Usage: 9 | % PLOT_MESH_BRAIN(Brain), when "Brain" is a structure with a "vertices" 10 | % field and a "faces" field, plots the 3D image defined by this 11 | % structure. 12 | % PLOT_MESH_BRAIN(Brain,P) sets the view position, with P being a 13 | % [theta phi] two-element vector. Default value is [0 0] (posterior 14 | % view). 15 | % PLOT_MESH_BRAIN(Brain,P,C) where C is a Nx1 vector, and N is the number 16 | % of vertices on the brain mesh, defines the surface color values of the 17 | % brain according to the figure's color map. If C is an Nx3 matrix, then 18 | % each row defines the RBG color value of the vertex. The default 19 | % colormap is based on the upper ("hot") half of the "jet" colormap, with 20 | % value 0 mapped to the gray "brain color" so that the minimal value will 21 | % be colored gray rather than another color. 22 | % PLOT_MESH_BRAIN(Brain,P,C,T) with T being a scalar between 0 and 1 23 | % indicates the level of transparency of the surface mesh, with 0 being 24 | % completely opaque (default). 25 | % PLOT_MESH_BRAIN(Brain,P,C,T,cm) with cm being a 64x3 matrix, defines the 26 | % colormap for vector C. The default color map is "jet", with the lowest 27 | % value replaced by a default brain color (light gray). It's also 28 | % possible to use the colormap function as for any Matlab figure. 29 | % h = PLOT_MESH_BRAIN(...) returns the handle of the created patch object. 30 | % 31 | % Any of the optional paramters can be skipped by inputting an null array 32 | % ([]) instead. 33 | % 34 | % Use the camera toolbar to change viewing angle. Left-clicking on the 35 | % figure (with no tool selected) will fix the lighting angle (to 36 | % "headlight"). It's also possible to do this by calling the external 37 | % fix_lighting function. 38 | % 39 | % Written by Edden M. Gerber, Hebrew University of Jerusalem 2015. 40 | % edden.gerber@gmail.com 41 | % 42 | 43 | DEFAULT_BRAIN_COLOR = [0.85 0.85 0.85]; % Light gray 44 | 45 | % Handle input 46 | if nargin < 5 || isempty(color_map) 47 | % Generate "heat" color map with zero being the default brain color 48 | color_map = jet(64); 49 | color_map = [interp1(1:2:63,color_map(33:64,1),1:63)' interp1(1:2:63,color_map(33:64,2),1:63)' interp1(1:2:63,color_map(33:64,3),1:63)']; 50 | % Add as minimum value the default color: 51 | color_map = [DEFAULT_BRAIN_COLOR ; color_map]; 52 | end 53 | if nargin < 4 || isempty(transparency) 54 | transparency = 0; 55 | end 56 | if nargin < 3 || isempty(vertex_color_values) 57 | % no color information - use the default brain color 58 | vertex_color_values = zeros(length(brain_mesh.vertices),1); 59 | end 60 | if nargin < 2 || isempty(view_position) 61 | view_position = [0 0]; 62 | end 63 | % If input is row vector, change to column 64 | if size(vertex_color_values,1) == 1 65 | vertex_color_values = vertex_color_values'; 66 | end 67 | 68 | if ~isfield(brain_mesh,'faces') || ~isfield(brain_mesh,'vertices') 69 | error('brain_mesh input structure should include fields "faces" and "vertices".'); 70 | end 71 | 72 | % Plot 73 | handle = trisurf(brain_mesh.faces, brain_mesh.vertices(:,1), brain_mesh.vertices(:,2), brain_mesh.vertices(:,3),... 74 | 'FaceLighting','gouraud'); 75 | set(handle,'FaceVertexCData',vertex_color_values); 76 | colormap(color_map); 77 | set(handle,'FaceAlpha',1-transparency); 78 | shading('interp'); 79 | material('dull'); 80 | axis('xy'); 81 | axis('tight'); 82 | axis('equal'); 83 | axis('off'); 84 | hold('all'); 85 | view(view_position); 86 | l = light(); 87 | camlight(l,'headlight'); % light source is at the camera position 88 | cameratoolbar('Show'); % use this toolbar for 3D navigation 89 | if all(vertex_color_values==0) 90 | caxis([0 1]); % to force 0 to be at the bottom of the color scale and not the middle. 91 | end 92 | if isempty(get(gcf,'WindowButtonDownFcn')) % Set only if this function is not already set for this figure 93 | set(gcf,'WindowButtonDownFcn',@fix_lighting); % Clicking on the figure will fix the light angle 94 | end 95 | if isempty(get(gcf,'Name')) % Set only if the name is not already set for this figure 96 | set(gcf,'Name','Click on figure (with no tool selected) to fix lighing angle.'); % Clicking on the figure will fix the light angle 97 | end 98 | 99 | % Activate 3D rotation tool 100 | rotate3d on; 101 | 102 | end 103 | 104 | function fix_lighting(fig_handle,~) 105 | % Set the light source of the selected figure (default: current figure) to 106 | % originate from the viewing direction ("headlight"). If there is no light 107 | % source, a new one is created. 108 | 109 | % Get figure handle 110 | if nargin<1 111 | fig_handle = gcf; 112 | end 113 | 114 | % Find the light object 115 | l_handle = findobj(fig_handle,'Type','light'); 116 | 117 | % If no light source, create one 118 | if isempty(l_handle) 119 | l_handle = light; 120 | end 121 | 122 | % If there is more than one light source, take the last one 123 | if length(l_handle) > 1 124 | l_handle = l_handle(end); 125 | end 126 | 127 | % Set the light at the camera position 128 | camlight(l_handle,'headlight'); 129 | 130 | end 131 | 132 | -------------------------------------------------------------------------------- /pull_3d_scatter_dots.m: -------------------------------------------------------------------------------- 1 | function pull_3d_scatter_dots( pull_length, source_coordinates ) 2 | % pull dots of a 3D scatter plot towards the camera position so that they 3 | % are not hidden by another object (e.g. mesh object). 4 | 5 | if nargin < 2 6 | source_coordinates = [0 0 0]; 7 | end 8 | if nargin < 1 9 | pull_length = 1; 10 | end 11 | 12 | h = gca; 13 | cam_pos = h.CameraPosition; 14 | 15 | direction_vector = cam_pos - source_coordinates; 16 | 17 | direction_vector = direction_vector / norm(direction_vector) * pull_length; 18 | 19 | for i = 1:length(h.Children) 20 | if strcmp(h.Children(i).Type,'scatter') 21 | h.Children(i).XData = h.Children(i).XData + direction_vector(1); 22 | h.Children(i).YData = h.Children(i).YData + direction_vector(2); 23 | h.Children(i).ZData = h.Children(i).ZData + direction_vector(3); 24 | end 25 | % if strcmp(h.Children(i).Type,'text') 26 | % h.Children(i).Position = h.Children(i).Position + direction_vector(1); 27 | % end 28 | end 29 | 30 | 31 | end 32 | 33 | -------------------------------------------------------------------------------- /read_freesurfer_brain.m: -------------------------------------------------------------------------------- 1 | function brain_data = read_freesurfer_brain( fs_subject_dir, fs_dir ) 2 | % READ_FREESURFER_BRAIN encapsulates FreeSurfer's shell and Matlab 3 | % functions to read pial and inflated cortical surfaces into Matlab as a 4 | % mesh object (after an anatomical scan has been fully processed by 5 | % FreeSurfer). Mesh objects are defined by "vertices", e.g. points in 3D 6 | % space, and "faces" which are triangual surfaces connecting three 7 | % vertices. 8 | % You can use the function "plot_mesh_brain" to plot the resulting cortical 9 | % surfaces as Matlab objects. 10 | % This code uses FreeSurfer's Matlab function "read_surf". Make sure you 11 | % have the FreeSurfer Matlab library (freesurfer/matlab) on your path. 12 | % 13 | % Usage: 14 | % brain_data = READ_FREESURFER_BRAIN(fs_subject_dir) reads the cortical 15 | % surface data from the directory given in fs_subject_dir, which should 16 | % be the base folder in which the results of FreeSurfer's algorithm for a 17 | % subject's brain scan are saved. The output structure brain_data holds 18 | % the mesh data (vertices and faces) for the left and right pial and 19 | % inflated surfaces. 20 | % READ_FREESURFER_BRAIN(fs_subject_dir, fs_dir) defines the base working 21 | % folder for the FreeSurfer program (e.g. /usr/local/freesurfer). It is 22 | % best to modify the default value for this variable so that it is not 23 | % necessary to manually set it. 24 | % 25 | % 26 | % Written by Edden M. Gerber, Hebrew University of Jerusalem 2015, 27 | % edden.gerber@gmail.com 28 | % 29 | 30 | % Handle optional input 31 | DEFAULT_FS_DIR = '/usr/local/freesurfer'; % It's recommended to change this to the FS directory on your machine. 32 | 33 | if nargin < 2 || isempty(fs_dir) 34 | fs_dir = DEFAULT_FS_DIR; 35 | end 36 | 37 | 38 | % Get transformation matrices. These transformations are necessary to 39 | % load the surfaces from their original FreeSurfer "surface" coordinate 40 | % system to standard XYZ coordinates. 41 | % This part of the code uses the "system" function to execute FreeSurfer's 42 | % "mri_info" shell command. 43 | fs_shell_initialize_cmd = ['export FREESURFER_HOME=' fs_dir '; source $FREESURFER_HOME/SetUpFreeSurfer.sh; ']; 44 | [~, cmdout] = system([fs_shell_initialize_cmd 'mri_info --vox2ras ' fs_subject_dir '/mri/orig.mgz']); 45 | transformations.ijk2xyz = affine3d(str2num(cmdout)'); 46 | [~, cmdout] = system([fs_shell_initialize_cmd 'mri_info --vox2ras-tkr ' fs_subject_dir '/mri/orig.mgz']); 47 | transformations.ijk2xyz_FsMesh = affine3d(str2num(cmdout)'); 48 | 49 | 50 | % Create output structure 51 | brain_data = struct; 52 | 53 | 54 | % Read pial surfaces to Matlab and apply transformations: 55 | % Right hemisphere 56 | [brain_data.pial_right.vertices,brain_data.pial_right.faces] = read_surf(fullfile(fs_subject_dir,'surf','rh.pial')); 57 | brain_data.pial_right.faces = brain_data.pial_right.faces + 1; 58 | brain_data.pial_right.vertices = transformations.ijk2xyz_FsMesh.transformPointsInverse(brain_data.pial_right.vertices); 59 | brain_data.pial_right.vertices = transformations.ijk2xyz.transformPointsForward(brain_data.pial_right.vertices); 60 | % Left hemisphere 61 | [brain_data.pial_left.vertices,brain_data.pial_left.faces] = read_surf(fullfile(fs_subject_dir,'surf','lh.pial')); 62 | brain_data.pial_left.faces = brain_data.pial_left.faces + 1; 63 | brain_data.pial_left.vertices = transformations.ijk2xyz_FsMesh.transformPointsInverse(brain_data.pial_left.vertices); 64 | brain_data.pial_left.vertices = transformations.ijk2xyz.transformPointsForward(brain_data.pial_left.vertices); 65 | 66 | 67 | % Read inflated surfaces to Matlab and apply transformations: 68 | % Right hemisphere 69 | [brain_data.inflated_right.vertices,brain_data.inflated_right.faces] = read_surf(fullfile(fs_subject_dir,'surf','rh.inflated')); 70 | brain_data.inflated_right.faces = brain_data.inflated_right.faces + 1; 71 | brain_data.inflated_right.vertices = transformations.ijk2xyz_FsMesh.transformPointsInverse(brain_data.inflated_right.vertices); 72 | brain_data.inflated_right.vertices = transformations.ijk2xyz.transformPointsForward(brain_data.inflated_right.vertices); 73 | % Left hemisphere 74 | [brain_data.inflated_left.vertices,brain_data.inflated_left.faces] = read_surf(fullfile(fs_subject_dir,'surf','lh.inflated')); 75 | brain_data.inflated_left.faces = brain_data.inflated_left.faces + 1; 76 | brain_data.inflated_left.vertices = transformations.ijk2xyz_FsMesh.transformPointsInverse(brain_data.inflated_left.vertices); 77 | brain_data.inflated_left.vertices = transformations.ijk2xyz.transformPointsForward(brain_data.inflated_left.vertices); 78 | 79 | 80 | % Pull apart the two inflated hemispheres so they do not overlap if plotted together (originally they are both placed in the center of the coordinate space: 81 | [brain_data.inflated_left, brain_data.inflated_right] = pull_apart_inflated_hemispheres(brain_data.inflated_left, brain_data.inflated_right, 10); 82 | 83 | end 84 | 85 | --------------------------------------------------------------------------------