Connectome viewing

Dear All,

I was wondering if one can see the connectome (expressed as a connection weight matrix to be imported in brainstorm) on the cortical parcellation only, with parcels representing nodes, and connection/edge strength/weights expressed simply as colors from a color bar, see attached figure. It would be cool if this was feasible, as brainstorm allows

  • import/visualization of surfaces and surface labels from freesurfer,
  • native and flattened/inflated views of surface files, and sliding between the two
  • visualization of the medial hemispheric side
    which is what I need. Please let me know. If it cannot be done, please suggest a connectome viewer that can do all these things, thank you.
    Octavian
1 Like

It is possible to import cortical maps:

There are probably some missing blocks between this and the type of data you have.
What do the files you want to import in Brainstorm look like?

Dear Francois, the data are symmetric tractography-based connectome weight matrices, with parcels as nodes, very generic. Thank you, Octavian

The [ROI x ROI] square matrices could be loaded as [ROI x Time] in Brainstorm.

You could try to do the following:

  • Load your connectome matrix in Matlab, make it available directly as a matrix, not a structure with multiple fields.
  • Save them as "matrix" files in the Brainstorm database: right-click on a folder > File > Import data matrix
  • Export the structure to Matlab, edit the field Description to document the ROI names, import again
    https://neuroimage.usc.edu/brainstorm/Tutorials/Scripting#Custom_processing
  • Then use the process "Full source maps from scouts" : you'd need to have all the scouts corresponding to these values available in an atlas of the cortex surface

Dear Francois,

It went vey well for the 1st 3 steps. When pulling the reimported data matrix into process window, and using the process "Full source maps from scouts", it asks me for a channel file, then head model. Is this expected? Since I do not have recordings for this particular pt, I imported a generic 'not aligned' scalp 10-20 montage, then proceeded to calculate a mock head model, however I get a lot of warnings that channels are outside the scalp and the OpenMEEG takes for ever and does not converge. Is there a way to import a generic channel file to be glued to the scalp to proceed with the head model, then apply the source map from scouts process? Thank you, Octavian

it asks me for a channel file, then head model. Is this expected?

Yes. The source file that is generated depends on the head model selected in the folder.
This process can handle both surface and volume head models, therefore the output depends on the head model.

It would be possible to rewrite this process so that it could work with no input, getting the information from the cortex surface instead of the head model, but it wouldn't be possible to use this file for simulations.
Since this process is designed mainly for running simulations, and that it is not complicated to compute a simple head model, I prefer not changing anything.

Since I do not have recordings for this particular pt, I imported a generic 'not aligned' scalp 10-20 montage, then proceeded to calculate a mock head model, however I get a lot of warnings that channels are outside the scalp and the OpenMEEG takes for ever and does not converge.

After setting the channel file in the folder, right-click on it > MRI registration > Edit: align the electrodes on the head surface and project them on the scalp (see the tooltips of the buttons in the toolbar).
Then compute a simpler forward model to save time (3-shell sphere).

Dear Francois, it worked! I am still cleaning up a bit.

One other question re scouts. I understand freesurfer embeds the label (parcel name) info in both their .annot and *+aseg.mgz files, I assume BST reads the labels (and colors) directly from these files and not matching a LUT file, if so, can you point me to the code snippet for it (for both file types), many thanks, Octavian

The .annot files include the text labels and RGB colors. They are read with FreeSurfer's read_annotation.m, included in Brainstorm (brainstorm3/external/freesurfer):

The .mgz files do not include labels or colors. You need to refer to the FreeSurfer ASEG LUT file: https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/AnatomicalROI/FreeSurferColorLUT

There is a version of it included in Brainstorm, as .m code (I'm editing a lot the code for the anatomical labeling these days, so it might change soon):

Dear Francois,
Thank you for the code. Regarding the nxn connectome matrix taken as a nXt (scouts X time) by brainstorm, it worked, see attached. I plan writing up a custom process to take out the head /source modeling part. The view attached is with the scout parcellation selected but not displayed, not to confound the colorscale (together, parcels and simulations plotted together do not look good). The one issue is the colorbar, which shows an invalid scale; I assume this does not include mix in the original scout/annot colormap with the simulation color code. How one could fix the 'invalid' scale issue. Also, I assume brainstorm reads off the min and mx source intensity automatically, and those are the min and max connection strengths at time t (one matrix column) filtered through the source imaging/head model etc. If that is correct, how one can change the min colormap value displayed to threshold the matrix display (fewer or mode colored (>0) parcels. Also, is there a way to change the colormap for a linear one to one where variation in gradient can be controlled? One can always adjust the input matrix for display, I wanted to know the range of possibilities. Thank you, Octavian.

The colormap scaling is guessed in function bst_colormaps.m by function bst_getunits.m:
https://github.com/brainstorm-tools/brainstorm3/blob/master/toolbox/core/bst_colormaps.m#L1420

If the DisplayUnits is not specified in the file structure and the tag "sloreta" not present in the file, for cortex maps in the "results_...mat" format, it uses the following code:

    case {'results', 'sources', 'source'}
        % Results in Amper.meter (display in picoAmper.meter)
        if (val < 1e-4)
            valFactor = 1e12;
            valUnits  = 'pA.m';
        % Results without units (zscore, stat...)
        else
            valFactor = 1;
            valUnits  = 'No units';
        end

So if the maximum value in the file is > 0.0001, it will try to display them with no scaling factor.,
If the maximum value in the file is < 0.0001, it considers the values are pAm and multiplies them by 1e12.
Then it looks for a list of possible ticks to display in the colorbar (function GetTicks).
If there are no decent scaling, it shows "Invalid scale" instead.

Things you can do to change this behavior:

  • Save a "timefreq" file structure instead, which is more tolerant for the type of data stored : https://neuroimage.usc.edu/brainstorm/Tutorials/TimeFrequency#On_the_hard_drive
  • Set the field "DisplayUnits" to anything non-empty, it should skip the scaling by 1e12, and you might get more meaningful ticks evaluation
  • Multiply your values by a fixed scalar in order to have the maximum value > 1e-4

Regarding the colormapping, you can edit the colormap interactively, set the min-max and behavior of the mapping :
https://neuroimage.usc.edu/brainstorm/Tutorials/Colormaps#Custom_color_arrays
https://neuroimage.usc.edu/brainstorm/Tutorials/Colormaps#Color_mapping

Does it help?

Thank you, Francois, it is beautiful, see attached. It is exactly what I needed, especially since I also have the 'amplitude' slider that can threshold the connectivity/column and reduce or increase the no of parcels based on their connectivity amplitude. I wrote a custom process to make without the headmodel/channel file. I find that BST becomes a world class connectivity viewer without competition for this kind of problem (edges as colored node scouts).
There is one more step to make it really interactive... I want to introduce a callback as follows: on right-double click on a parcel/scout, the viewer would jump to the matrix column (or row) corresponding to that scout (the '<<' and '>>' do a good job but I want to go directly to that parcel connectivity column, also because other actions could be attached, such as changing the color of just the selected parcel to emphasize it, also since self-connectivity is in most cases irrelevant depending on how the matrix is constructed); this is akin to the callback corresponding to the left-click on a sensor to highlight it (yellow to red) in gui\figure3d.m. Since I cannot hope this would be hard-coded, I wanted to know what would be the best way to do it, so that it applies only to the matrix-as-timeseries matrix/source files generated by my custom process, and does not modify the default figure3d.m or other files in BST. Can the source files be tagged with smth as 'custom' in their structure, the presence of this tag which would call a figure3d_custom.m with this and other callbacks? Another way I see this being done is to drag the source file in Process one, and write/apply a custom process that would mostly replicate opening the figure (corresponding to double-clicking of the source file under Data) , perhaps sending to a custom figure3d_custom file containing the callback; if the latter is better, it would be nice to have an example of a process that simply opens a source file with the corresponding surface, scouts and sources etc, as if was done the regular way, for starters. Thank you again,
Octavian

I'm glad you managed to create some new useful tools from Brainstorm.

If you want to go further with optimized interactive exploration, there are two possible scenarios:

  1. You develop these tools only for yourself and direct collaborators:
    • We will not modify the core Brainstorm functions,
    • I can give you entry points to override the existing figures callbacks dynamically: this would require running a script after creating a figure in order to make it interact in the way you want.
    • Get the scouts graphical objects in the current figure:
      h = [findobj(gcf, 'Tag', 'ScoutPatch'); findobj(gcf, 'Tag', 'ScoutContour'); findobj(gcf, 'Tag', 'ScoutMarker')];
    • Attach a callback to these objects:
      set(h, 'ButtonDownFcn', @test_callback)
    • With the callback function defined in test_callback.m:
      function test_callback(hScout,ev)
      [sScout, iScout] = panel_scout('GetScoutWithHandle', hScout); % Find the selected scout
      if ~isempty(sScout)
      disp(sScout.Label)
      end
    • From here, you can do anything you want in terms of interactivity: change the current time depending on the scout that was clicked, update the graphic properties of the object that was clicked (eg. color the border in red).
    • The Scripting tutorial reference sections might help you, as well as the examples in tutorial_introduction.m :
      https://neuroimage.usc.edu/brainstorm/Tutorials/Scripting#Reference:_Display_functions
      brainstorm3/toolbox/script/tutorial_introduction.m at master · brainstorm-tools/brainstorm3 · GitHub
  2. You would like to make this a new feature of Brainstorm available to other users:
    • You'd need to define a clear use case, starting from publicly available datasets and useful for other users interested in exploring this same dataset. This use case would become an online tutorial, with the same structure as the tutorials in the section "Other analysis scenarios" (eg. https://neuroimage.usc.edu/brainstorm/Tutorials/Epileptogenicity)
    • We'd need to make sure that all the input data can be loaded easily into the Brainstorm database (I could help you with this)
    • This could be done after you experiment with personal hacks (as in scenario #1), but it would save time to think about it in advance, as it may require to save additional fields in the database, or using a different data container (eg. using a timefreq or timefreq_connect file might be more adapted than a result file).
    • In that context, we could make permanent modifications to the Brainstorm functions (figure_3d.m, panel_scout.m, panel_time.m...)

Another way I see this being done is to drag the source file in Process one, and write/apply a custom process that would mostly replicate opening the figure

Not a good option. The process functions are supposed to be silent functions that can run on a server without any display, with not interactivity. I'd rather modify only the functions related with the interactive exploration of the database and data (add a section to tree_callbacks.m, a new view_...m file, edit figure_3d.m).

Saving the file in the database with a callback is technically possible but not very resilient. It makes the maintenance of the software much more complicated, as the functions could not be renamed after the file is created.

Thank you, Francois, this is very helpful! I am in the 'hack phase' for now, as I come closer to fruition with this project, I ll submit a more formal proposal for integration. Octavian

Dear Francois,

It is going well. I decided to follow your advice and run a script after a target figure is opened from the gui.

  1. One thing I need to do is to mark the 'surface center' of each scout (I decided to do this by rendering an ecog electrode there, for many, including labeling, reasons; for now it is for all scouts, in the future only activated scouts (above threshold connectivity) would have their center marked). I did this by using the seed coordinates for each scout, but the ecog displays are buried in the brain for some scouts, what I would need to get is the coordinates of the seed projections on the scout surface (or some other better way to get the scout center on the actual scout), or even a bit outside ~ 1 mm the brain, for visibility
    Are these projections the scoutMarker, if so, is there a way to retrieve those coordinates from h?

h = [findobj(gcf, 'Tag', 'ScoutPatch'); findobj(gcf, 'Tag', 'ScoutContour'); findobj(gcf, 'Tag', 'ScoutMarker')];

I would need the surface center for all scouts, not just for the active or selected scout (via callback).

If the scoutMarker is not at the surface center of the scout, is there a way to get that center for all scouts through some other manner?

  1. After the ecog projection is done, I need a way to update the ecog coordinates and geometry whenever the surface is smoothed, since everything changes with smoothing; is there a way to add a callback/listener to the script whenever the smoothing is applied via the gui slider, so the whole projection is repeated/updated? It is one thing to add callbacks for mouse clicks, and another to some functions executed in other parts of the code.
  2. On the same note, how to retrieve the vertices coordinates of an already smoothed surface. Say I open the surface already smoothed to some degree, and I do not use the slider further. It looks like Global.Data.Surface.Vertices contains only the coordinates of the original, unsmoothed surface.
    Thank you, Octavian

Dear Francois,

I solved 1 and 3 above, I would appreciate your guidance with no 2/ smoothing callback issue. Also, the seed coordinates are closer to the centroid of a scout. If the scout is ~concave, the ~centroid is not in the middle of the patch. I have been reading about the 'label' center, farthest away scout vertex from the contour, or the 'pole of inaccessibility'. I was wondering if there is an 'easy' way of estimating it (one option is to convert 3d scouts to 2d via Mollweide projection, then input its 2d contour to the polylabel javascript package, but this is a long shot at this time). Thank you, Octavian

The scout marker is placed at the seed of the scout, slightly away from the cortex surface itself. The seed is not always at the center of the scout when manually designed, but it is always somewhere close to its center of mass for atlases.

It's coordinates are obtained with the function panel_scout('GetScoutPosition', Vertices, VertexNormals, iSeed, factor). If you want them further away from the surface, increase factor.

There are also ECOG functions that can project points on the inner skull surface: panel_ieeg('ProjectContacts') and panel_ieeg('GetChannelNormal'):

After the ecog projection is done, I need a way to update the ecog coordinates and geometry whenever the surface is smoothed, since everything changes with smoothing;

If you project on the inner skull (maybe just to get a stable normal vector), smoothing would have a lot less impact and might make updates after smoothing unnecessary. And it would be easier because updating the position of the ECOG sensors might require that you delete and plot them again.

is there a way to add a callback/listener to the script whenever the smoothing is applied via the gui slider, so the whole projection is repeated/updated?

You can modify the callback functions of all the elements of the interface. You can use this to add some personal processing before calling the original process function. For example:

function addHook()

    panelSurface = bst_get('PanelControls', 'Surface');
    oldCallback = java_getcb(panelSurface.jSliderSurfSmoothValue, 'MouseReleasedCallback');
    
    java_setcb(panelSurface.jSliderSurfSmoothValue, 'MouseReleasedCallback', @(h,ev)newCallback(h,ev));
    % Equivalent: 
    % java_setcb(panelSurface.jSliderSurfSmoothValue, 'MouseReleasedCallback', @newCallback);

    function newCallback(h,ev)
        disp(['Slider clicked: ' char(ev.getSource().toString())]);
        oldCallback(h, ev);
        % Or call directly the original callback defined in panel_surface:
        %panel_surface('SliderCallback', h, ev, 'SurfSmoothValue');
    end
end

On the same note, how to retrieve the vertices coordinates of an already smoothed surface.

Directly in the Vertices property of the scouts patches.

I'm not sure the barycenter of a 2D projection would give you something much better. It would also have cases where the "center" is on the border of the region.

If a simple barycenter of the 3D points is not sufficient, you'd need to use something that integrates the vertex-vertex connectivity of the surface (VertConn). There are maybe some graph analysis functions that give you the most central node in a graph.

Thank you, Francois, for your suggestions, they are really savers!

  1. The smooth slider callback modification works great. The one issue is that since the callback is modified by my script, it has to be restored to defaults before the fig is closed. So I added a callback to do just that:

    set(gcf, 'CloseRequestFcn', @restoreSliderSurfSmooth_Callback); 
    
         function restoreSliderSurfSmooth_Callback(h, ev)
             disp('Handle restored.');
             java_setcb(panelSurface.jSliderSurfSmoothValue, 'MouseReleasedCallback', oldSliderSurfSmooth_Callback);
             bst_figures('DeleteFigure', h)
         end
    

This works well, but if the script throws an error for whatever reason and fig is not closed, the callback is not changed until one does not get out and in in brainstorm, I wonder if there is a way to change and return the original callback quicker (same mouse release).

  1. I followed your suggestion, and indeed VertConn does the trick to find a more suitable scout label center than the centroid/ seed. Here it is:

     [sScouts, ~, ~] = panel_scout('GetScouts');
     for iii = 1:size(sScouts, 1)
         scoutConn = sSurf.VertConn(sScouts(iii).Vertices, sScouts(iii).Vertices);
         scoutConnGraph = graph(scoutConn);
         vertImportance = centrality(scoutConnGraph, 'closeness');  % or 'eigenvector'
         [~, iMaxImportance] = max(vertImportance);
         maxVertex = sScouts(iii).Vertices(iMaxImportance);
         labelCenters(iii).Loc = panel_scout('GetScoutPosition', Vertices, VertexNormals, maxVertex, 0.0004); 
     end
    
  2. I may miss something, but when I change the colorbar for the scouts themselves (from the gui, not script), and I save the changes by closing the figure, I do not seem to be able to restore the original parcellation color, and this propagates to other figures/surfaces for the same subject. It seems that the only way here is to reimport freesurfer anatomy, please advise.

Thank you,
Octavian

In the callback function you use to override the MouseReleasedCallback of the slider: encapsulate everything that manipulates the (possibly invalid) figure handle in a try/catch block, so that it never crashes and always manage to call the original callback.

I followed your suggestion, and indeed VertConn does the trick to find a more suitable scout label center than the centroid/ seed.

This is good to know, I didn't know about these new graph/centrality tools in Matlab! I could be useful for some of the scout management functions, including for the definition of the seed for anatomical parcellations imported from FreeSurfer.

I may miss something, but when I change the colorbar for the scouts themselves (from the gui, not script), and I save the changes by closing the figure, I do not seem to be able to restore the original parcellation color, and this propagates to other figures/surfaces for the same subject. It seems that the only way here is to reimport freesurfer anatomy, please advise.

I'm not sure what you mean with the colorbar for the scouts themselves. Can you try illustrating more precisely what you do?

Dear Francois,

  1. If I select say destrieux parcellation for merged hemispheres, select all scouts, and change the face color to gray, then close the figure, modifications are saved. How would I reverse to the original destrieux color for a particular subject (without reimporting anatomy)? Clicking Atlas/Undo all modifications does not work after the modifications are saved by closing the fig, also Scout/Set color does not have a default option, so one would need to select scouts individually for coloring, this was not meant to recolor a parcellation back to the original (see attached).I could do it in matlab, but need to know if this is a gui option, otherwise I have to explicitly include it in script.

  2. More important: the callback hack has worked well. In contrast to most other buttons and sliders, I cannot identify the surface panel Reset object (panel_surface m line 189), so I cannot use
    oldResectReset_Callback = java_getcb(panelSurface.jButton('Reset'), 'MouseReleasedCallback');
    I have tried various combinations but panelSurface does not see them to identify the button, which was not defined by name as others. I tried to use addlistener but does not seem to work.


    Thank you very much for advice. Octavian