Back to the main page.

Bug 3325 - Proposed enhancement to coloring of sensors/electrodes in ft_plot_sens

Status CLOSED FIXED
Reported 2017-07-27 02:38:00 +0200
Modified 2019-08-10 12:36:46 +0200
Product: FieldTrip
Component: plotting
Version: unspecified
Hardware: PC
Operating System: Mac OS
Importance: P5 enhancement
Assigned to: Arjen Stolk
URL:
Tags:
Depends on:
Blocks:
See also:

Sandon Griffin - 2017-07-27 02:38:43 +0200

Right now, ft_plot_sens allows sensors to be colored using the 'facecolor' option, which, according to the help for ft_plot_sens, can be [r g b] values or string, for example 'brain', 'cortex', 'skin', 'black', 'red', 'r', or an Nx3 or Nx1 array where N is the number of faces (default is automatic). As it stands right now, if users want to plot individual iEEG electrodes in different colors, they have to manually create a new elec structure for each color electrode or group of electrodes, and then make multiple calls to ft_plot_sens, which can be cumbersome if electrodes are to be colored on a continuous color scale. Thus, I am proposing to add another option to facecolor: an "Nx3 or Nx1 array where N is the number of electrodes." This would be especially helpful for plotting iEEG electrodes with different colors based on some statistic associated with each electrode. The most simple implementation would be to allow facecolor to be an Nx3 array of [r g b] values or an Nx1 cell array specifying each color as a string (e.g., 'red', 'k', or 'brain'). Although, this would require the user to determine those colors based on the statistics for each electrode before plotting. I think creating a new "val" input, which can be an Nx1 array of numerical values, would be even more convenient. This new option could be implemented into ft_plot_sens the same way it is currently implemented in ft_plot_cloud, and would also require 'colormap' to be given as input if the user wanted to change it from default. I understand that ft_plot_sens is not designed to take in functional data right now, but if it is decided to keep it that way, I think the former option would still be a helpful addition to this function, and I am happy to implement either option.


Arjen Stolk - 2017-08-20 21:20:08 +0200

Bumping this thread. JM or RO, any thoughts you may have on this?


Robert Oostenveld - 2017-08-22 11:23:53 +0200

Color handling should be kept consistent between ft_plot_xxx functions, and where possible made more consistent. Looking at the present use of multiple color specification, I can find (using grep) ft_plot_cloud.m:% 'intersectcolor' = string, Nx1 cell array, or Nx3 vector specifying line color (default = 'k') ft_plot_mesh.m:% 'facecolor' = [r g b] values or string, for example 'brain', 'cortex', 'skin', 'black', 'red', 'r', or an Nx3 or Nx1 array where N is the number of faces ft_plot_mesh.m:% 'vertexcolor' = [r g b] values or string, for example 'brain', 'cortex', 'skin', 'black', 'red', 'r', or an Nx3 or Nx1 array where N is the number of vertices ft_plot_sens.m:% 'facecolor' = [r g b] values or string, for example 'brain', 'cortex', 'skin', 'black', 'red', 'r', or an Nx3 or Nx1 array where N is the number of faces (default is automatic) ft_plot_vol.m:% 'facecolor' = [r g b] values or string, for example 'brain', 'cortex', 'skin', 'black', 'red', 'r', or an Nx3 or Nx1 array where N is the number of faces ft_plot_vol.m:% 'vertexcolor' = [r g b] values or string, for example 'brain', 'cortex', 'skin', 'black', 'red', 'r', or an Nx3 or Nx1 array where N is the number of vertices I don't see 'val' or anything that resembles it in any of these, except for ft_plot_cloud, ft_plot_topo and ft_plot_topo3d, which are explicitly meant for plotting scalar values on 3-D locations. I also don't think it is appropriate for coloring geometrical objects. But I am fine with extending the color options to Nx1 char-array like transpose('rgbrgb') or numeric Nx3 with numbers between 0 and 1. Changes should include fontcolor, edgecolor, and facecolor. However, in case elec/grad/opto=false: how should the option 'style' and the option 'marker' be dealt with? Should those also support a Nx1 char array like transpose('rgbrgb') and transpose('.*+.*+')? Prior to considering any changes to the ft_plot_sens function itself, I would like to see an example script where it is clear (as in "predictable") for each figure that is made how the figure should look like. The example test script should start from gradiometers or opcodes, since electrodes are too simple. and the test script should vary coil=true/false, chantype=gradiometer/magnetometer/[], coilshape=4 options, marker='.' or '*'. Like this: =================================== grad = []; grad.label = {'1', '2', '3'}; grad.chantype = {'gradiometer', 'gradiometer', 'magnetometer'}; grad.unit = 'mm'; grad.coilpos = rand(5,3)*100; grad.coilori = rand(5,3); for i=1:5 grad.coilori(i,:) = grad.coilori(i,:) / norm(grad.coilori(i,:)); end grad.tra = [ 1 0 0 -1 0 0 1 0 0 -1 0 0 1 0 0 ]; grad.chanpos = grad.tra * grad.coilpos; grad.chanori = grad.tra * grad.coilori; for i=1:3 grad.chanori(i,:) = grad.chanori(i,:) / norm(grad.chanori(i,:)); end arg = []; arg(1).name = 'coil'; arg(1).value = {true, false}; arg(2).name = 'chantype'; arg(2).value = {[], 'gradiometer', 'magnetometer'}; arg(3).name = 'coilshape'; arg(3).value = {'point', 'circle', 'square', 'sphere'}; arg(4).name = 'marker'; arg(4).value = {'.', '*', transpose('.*+')}; arg(5).name = 'style'; arg(5).value = {'r', 'b', 'skin', transpose('rgb')}; opt = constructalloptions(arg); for i=1:size(opt,1) figure; ft_plot_sens(grad, opt{i,:}); drawnow disp('----------------------------------------'); i opt(i,:) disp('press a key'); pause end =================================== Hmm, already in the very first plot the current code fails to produce the expected figure. It turns out that 'marker' and 'style' should be mutually exclusive. I will make a fix to the master branch, include this as an initial test script and we can take it from there.


Robert Oostenveld - 2017-08-22 12:02:39 +0200

I made a test script for this bug, [bug3325-ft_plot_sens d76c4e8] made a test script for plotting sensors in preraration for http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=3325, found and fixed a bug when marker style (e.g. +) was specified both in the "style" option and in the "marker" option. and merged it under https://github.com/fieldtrip/fieldtrip/pull/495 Please take that as starting point. Not all 1152 combinations of options are relevant for further testing, but please do test the ones that you normally would not think off with iEEG electrodes.


Roemer van der Meij - 2017-08-22 19:43:40 +0200

Hey all, Since visualizing functional (scalar) data at the electrode level is something that is common when using ECoG, how about we create an ft_electrodeplot, or ft_sensplot, that takes in functional data and plots a scalar representation of it as electrodes (size/color/both) on anatomy? I'm thinking of a function that is complementary to ft_sourceplot. Plotting scalar data on anatomy is nice of course, but can also be suboptimal in some situations (e.g. sulcus coloring is less visible). Another option would be to simply extend ft_sourceplot.


Arjen Stolk - 2017-08-22 19:54:16 +0200

(In reply to Roemer van der Meij from comment #4) I like Roemer's thinking. Sandon mentioned to me earlier that the desired feature of electrode coloring (preferably also sporting a colorbar) could easily be added to ft_plot_cloud. ft_plot_cloud is currently called by ft_sourceplot. Should we maybe have another function, e.g. ft_electrodeplot, that calls ft_plot_cloud or ft_plot_sens, or build in the electrode coloring feature into ft_plot_cloud and have ft_sourceplot call it while requiring another cfg option to make it plot colored electrodes instead of clouds.


Roemer van der Meij - 2017-08-22 20:06:27 +0200

(In reply to Arjen Stolk from comment #5) It's probably easier to implement the conversion of functional data to RGB values on an a selected colormap (best to use a large colormap, e.g. 1e4+) in such an ft_electrodeplot/etc/ft_sourceplot. Can always be extended to the lower level functions if desired.


Arjen Stolk - 2017-10-11 02:40:26 +0200

Thanks for your feedback. Hopefully, we can make use of your advice one more time. After discussing with Sandon, we see two options: 1) create a host of subfunctions that share 90% of the code with ft_plot_cloud but where the end product is slightly different. this would result in functions the likes of ft_plot_sphere (color-coded sphere-shaped electrodes) and ft_plot_point/disc (color-coded disc-shaped electrodes, i.e. the default shape of ft_plot_sens) 2) alternatively, we bring all these under in a single, generic function that switches between the different plotting outputs. as for a name, I was thinking of ft_plot_elec. i know this, indeed, is close in terms of naming to ft_plot_sens, but it sounds better than ft_plot_sensdata, or ft_plot_source? we could also opt for ft_plot_ieeg (im liking this most, actually)? What do you think? Option 1 vs 2, and if 2, what name for the function?


Robert Oostenveld - 2017-10-13 12:52:37 +0200

(In reply to Arjen Stolk from comment #7) I am (general) not in favor of adding functions if the problem at hand can be solved by extending existing functions. However, I am in favor of adding new functions when the feature bloat in existing functions causes them to become too confusing. Example: 1) ft_preprocessing (old) is now ft_definetrial + ft_rejectartifact + ft_prepocessing (and some more) 2) ft_sourceanalysis (old) is now ft_prepare_sourcemodel + ft_prepare_headmodel + ft_sourceanalysis (and some more) Plotting colored sensors to me seems to be quite easy with something like ft_plot_sens(elec, 'color', rand(Nelec, 3)) just like ft_plot_topo3d(elec.elecpos, rand(Nelec, 3)) or ft_plot_topo3d(elec.chanpos, rand(Nchan, 3)) The general confusion between electrodes and channels remains, but making another function is not going to solve that either. It might be that ft_plot_sens needs some extra cleanup before such a color option can be added. Reorganizing existing code is usually better (in the long run) than adding more clutter to the code by adding more functions. I see the value in ft_plot_sens, since I would also like to plot color-coded magnetometer/gradiometer coils. Separate from this: I think that ft_plot_cloud should already have the capability of plotting colored spheres/blobs/clouds. Perhaps the question is to be considered at another level: namely whether it is to be solved in a high-level FT function (one that starts with a cfg as 1st input) versus in a low-leven function (with key-value arguments). High-level functions serve to do bookkeeping, low level functions do the actual work (and are called by the high-level ones). But here I am not sure whether the request pertains to plotting functional data, or to plotting electrodes color-coded as groups. The latter might be implemented by adding the option 'elecindex' and doing ft_plot_sens(elec, 'elecindex', 1:10, 'facecolor, 'r') ft_plot_sens(elec, 'elecindex', 11:20, 'facecolor, 'b') But from #c0 and #c6 I take it that it pertains to mapping continuous (hence functional, not categorical) values to color. Could someone post a snippet of pseudo(?) code that demonstrates the problem that should be solved? E.g. elec = ft_read_sens('GSN-HydroCel-32.sfp') timelock = []; timelock.time = (1:1000)/1000; timelock.avg = randn(36,1000); timelock.label = elec.label; ft_plot_sens(elec, 'elec', 1, 'elecshape', 'sphere', 'elecsize', 1, 'facecolor', timelock.avg(:,300)) or source = []; source.pos = elec.chanpos*10; source.unit = 'mm'; source.time = (1:1000)/1000; source.avg = randn(36,1000); cfg = []; cfg.method = 'cloud' cfg.latency = 0.300; cfg.funparameter = 'avg'; ft_sourceplot(cfg, source);


Sandon Griffin - 2017-11-09 21:21:06 +0100

Ideally, I would like to come up with a way to plot electrodes color-coded as groups as well as a way to plot them color-coded according to continuous functional data. These are very different functionalities, but minimal additions to existing plotting functions can address these issues. To plot electrodes color-coded as groups, I like Robert's idea of adding an elecidx, or more generally, a sensidx option, which would work as previously described: elec = ft_read_sens('GSN-HydroCel-32.sfp'); ft_plot_sens(elec, 'sensindex', 1:18, 'facecolor, 'r'); ft_plot_sens(elec, 'sensindex', 19:36, 'facecolor, 'b'); I believe this is more simple than altering how 'facecolor', 'edgecolor', and 'fontcolor' are handled in ft_plot_sens. As for plotting electrodes color-coded according to continuous functional data, this is actually already achieved by ft_plot_cloud, but right now, ft_plot_cloud can only plot the electrodes as a cloud of points. Therefore, I propose adding a 'cloudtype' option, which can either be 'point' (default) or 'surf', where the latter plots sphere surfaces (note: these inputs are the same as those for the 'slicetype' option in ft_plot_cloud, which only applies when 'slice' = '2d'). For example: source = []; source.pos = elec.chanpos; source.unit = 'mm'; source.val = rand(36,1); cfg = []; cfg.method = 'cloud' cfg.cloudtype = 'surf'; cfg.funparameter = 'val'; ft_sourceplot(cfg, source); We could then extend the 'cloudtype' option to replace the 'slicetype' option in ft_plot_cloud.


Arjen Stolk - 2017-12-08 08:06:19 +0100

@Robert: Sandon mentioned it should be relatively straightforward given what he has done already to extend ft_plot_cloud to also accommodate other types of 'functional coloring', that is the plotting of functional data and not the plotting of electrodes color-coded as groups. This includes functionality that scales the size of electrodes as a function of the functional data, similar to related functionalities already built into ft_plot_cloud. Alternatively, all this functionality is housed under ft_plot_sens but that isn't as easy as it sounds given that ft_plot_sens might have different expectations on the input. Can you quickly share your .02 so we can start working toward a PR (and maybe create a 'cheatsheet' of plotting options for the protocol).


Arjen Stolk - 2018-08-14 09:04:37 +0200

There's currently an issue still with ft_plot_sens in that specifying 'elecshape' as 'circular' or 'square' will throw an error*. This is because those plotting methods are currently only supported for MEG data. A workaround would be to simply not use those options and use the 'style' option. However, the style option does not allow specifying the facealpha and edgealpha. Thus, ideally, ft_plot_sens will not call the plotcoil subfunction when it's not MEG data. Agreed? * elec.elecpos = [1 1 1; 2 2 2]; elec.chanpos = elec.elecpos; elec.label = {'1';'2'}; elec.unit = 'mm'; ft_plot_sens(elec, 'elecshape', 'circle') Index exceeds matrix dimensions. Error in ft_plot_sens>plotcoil (line 443) x = coilori(i,1); Error in ft_plot_sens (line 342) plotcoil(pos, ori, [], sensize, sensshape, 'edgecolor', edgecolor, 'facecolor', facecolor, 'edgealpha', edgealpha, 'facealpha', facealpha);


Robert Oostenveld - 2018-08-15 09:47:26 +0200

why implement a workaround if the proper solution is obviously to implement these options for elecshape? This requires that the orientation is known. An estimate for the orientations for scalp electrodes or opcodes can be constructed by fitting a sphere. For ECoG electrodes that would still be reasonable, for SEEG it would be very poor. But I also don't see a case where those would make sense when plotted as a circle. mac011> git commit -a [bug3325 bfcd23126] determine orientation by fitting a sphere, added support for circle and square for all sensor types 5 files changed, 151 insertions(+), 38 deletions(-) create mode 100644 plotting/private/fitsphere.m


Robert Oostenveld - 2018-08-15 09:49:30 +0200

https://github.com/fieldtrip/fieldtrip/pull/772 See fieldtrip/plotting/test/test_ft_plot_sphere.m


Arjen Stolk - 2018-08-15 19:13:01 +0200

(In reply to Robert Oostenveld from comment #13) Thanks, I tested it here and it looks good.


Arjen Stolk - 2018-08-15 19:39:04 +0200

I was trying to achieve coloring of elecs individuals, as follows: figure; ft_plot_sens(elec, 'elec', true, 'marker', 'o', 'facecolor', [1 1 1; 0 0 0], 'edgecolor', [1 0 0; 0 0 1]); This makes use of matlab's plot3 functionality, but that functionality doesn't accept non-vector inputs for specifying colors. Do you think we should have ft_plot_sens loop around plot3 to support it, or is it something for the user to decide upon?


Robert Oostenveld - 2018-08-16 12:31:00 +0200

(In reply to Arjen Stolk from comment #15) I think it would be better to replace line 317-335 with an appropriate call to ft_plot_mesh. That one already supports plotting a cloud of points with different colors. Try for example ft_plot_mesh(randn(10,3), 'vertexcolor', rand(10,3))


Robert Oostenveld - 2019-08-10 12:36:46 +0200

This closes a whole series of bugs that have been resolved (either FIXED/WONTFIX/INVALID) for quite some time. If you disagree, please file a new issue on https://github.com/fieldtrip/fieldtrip/issues.