22import matplotlib .pyplot as plt
33import numpy as np
44import plotly .graph_objs as go
5+ import plotly
56from matplotlib .cm import ScalarMappable
67from matplotlib .colors import Normalize
78
@@ -160,18 +161,30 @@ def _plotly_trisurf(points3D, simplices, facecolor, opacity=1, name=""):
160161 return triangles
161162
162163
163- def _plotly_scatter3d (coords , elec_colors , name = "" ):
164+ def _plotly_scatter3d (coords , elec_colors , elec_alpha , name = "" ):
164165 marker = go .scatter3d .Marker (color = elec_colors )
166+ if not isinstance (elec_alpha , (np .ndarray , list )):
167+ elec_alpha = np .asarray ([elec_alpha ] * len (coords ))
165168 scatter = go .Scatter3d (
166169 x = coords [:, 0 ],
167170 y = coords [:, 1 ],
168171 z = coords [:, 2 ],
169172 mode = "markers" ,
170173 marker = marker ,
171174 name = name ,
175+ customdata = elec_alpha ,
172176 )
173177 return scatter
174178
179+ def _set_opacity (trace ):
180+ """https://community.plotly.com/t/varying-opacity-in-scatter-3d/75505/5"""
181+ if hasattr (trace , 'customdata' ) and isinstance (trace .customdata , float ):
182+ opacities = trace .customdata
183+ r , g , b = plotly .colors .hex_to_rgb (trace .marker .color )
184+ trace .marker .color = [
185+ f'rgba({ r } , { g } , { b } , { a } )'
186+ for a in map (lambda x : x [0 ], opacities )]
187+
175188
176189def plot_brain_elecs (
177190 brain ,
@@ -232,16 +245,17 @@ def plot_brain_elecs(
232245 since coordinates are assumed to represent coordinates in the pial space. If plotting pial,
233246 then this can be set to False (default) to show true electrode placement, or True to map
234247 to the surface.
235- elec_size : int, default=4
236- Size of the markers representing electrodes.
248+ elec_size : int | np.ndarray , default=4
249+ Size of the markers representing electrodes. If an array, should give the size for each electrode.
237250 cortex : {'classic','high_contrast','mid_contrast','low_contrast','bone'}, default='classic'
238251 How to map the sulci to greyscale. 'classic' will leave sulci untouched, which may be
239252 better for plotting the pial surface, but 'high_contrast' will enhance the contrast between
240253 gyri and sulci, which may be better for inflated surfaces.
241254 cmap : str, default='cool'
242255 Colormap for electrode values if values are provided.
243- alpha : float | np.ndarray
256+ alpha : float | np.ndarray, optional, default=1
244257 Opacity of the electrodes. Either a single float or an array of same length as number of electrodes.
258+ If None, then the colors provided should be an array of RGBA values, not just RGB.
245259 vmin : float, optional
246260 Minimum value for colormap normalization. If None, uses the min of valus.
247261 vmax : float, optional
@@ -396,11 +410,19 @@ def _plot_brain_elecs_standalone(
396410 )
397411
398412 assert isinstance (surfs , dict )
413+
414+ if isinstance (elec_size , list ):
415+ elec_size = np .asarray (elec_size )
399416
400417 if cortex not in colormap_map :
401418 raise ValueError (
402419 f"Invalid cortex. Must be one of { 'classic' ,'high_contrast' ,'low_contrast' ,'bone_r' } but got { cortex } "
403420 )
421+
422+ if isinstance (elec_alpha , list ) or isinstance (elec_alpha , np .ndarray ):
423+ update_opacity_per_elec = True
424+ else :
425+ update_opacity_per_elec = False
404426
405427 sulci_cmap_kwargs , sulci_cmap_nonlinearity = colormap_map [cortex ]
406428
@@ -547,9 +569,14 @@ def _plot_brain_elecs_standalone(
547569 )
548570 elif isinstance (colors , list ):
549571 if isinstance (elec_alpha , (float , int )):
550- elec_colors = np .asarray (
551- [mpl .colors .to_rgba (cc , elec_alpha ) for cc in colors ]
552- )
572+ if isinstance (elec_alpha , list ) or isinstance (elec_alpha , np .ndarray ):
573+ elec_colors = np .asarray (
574+ [mpl .colors .to_rgba (cc , alph ) for cc , alph in zip (colors , elec_alpha )]
575+ )
576+ else :
577+ elec_colors = np .asarray (
578+ [mpl .colors .to_rgba (cc , elec_alpha ) for cc in colors ]
579+ )
553580 else :
554581 elec_colors = np .asarray (
555582 [
@@ -559,7 +586,7 @@ def _plot_brain_elecs_standalone(
559586 )
560587 elif isinstance (colors , np .ndarray ):
561588 elec_colors = colors .copy ()
562- if elec_colors .shape [1 ] > 3 :
589+ if elec_colors .shape [1 ] == 4 and elec_alpha is not None :
563590 elec_colors [:, 3 ] = elec_alpha
564591 else :
565592 raise TypeError (
@@ -570,18 +597,27 @@ def _plot_brain_elecs_standalone(
570597 if hemi == "lh" :
571598 coords = coords [elec_isleft ]
572599 elec_colors = elec_colors [elec_isleft ]
600+ if isinstance (elec_size , (np .ndarray )):
601+ elec_size_hemi = elec_size [elec_isleft ]
602+ else :
603+ elec_size_hemi = elec_size
604+
573605 else :
574606 coords = coords [~ elec_isleft ]
575607 elec_colors = elec_colors [~ elec_isleft ]
608+ if isinstance (elec_size , (np .ndarray )):
609+ elec_size_hemi = elec_size [~ elec_isleft ]
610+ else :
611+ elec_size_hemi = elec_size
576612
577613 if backend == "plotly" :
578614 elec_colors *= 255
579615 elec_colors = elec_colors .astype ("int" )
580- scatter = _plotly_scatter3d (coords , elec_colors , name = f"elecs-{ hemi } " )
616+ scatter = _plotly_scatter3d (coords , elec_colors , elec_alpha = elec_alpha , name = f"elecs-{ hemi } " )
581617 trace_list .append (scatter )
582618 else : # mpl
583619 x , y , z = coords .T
584- ax .scatter (x , y , z , s = elec_size , c = elec_colors , ** kwargs )
620+ ax .scatter (x , y , z , s = elec_size_hemi , c = elec_colors , ** kwargs )
585621
586622 if backend == "plotly" :
587623 axis = dict (
@@ -614,6 +650,9 @@ def _plot_brain_elecs_standalone(
614650
615651 fig = go .Figure (data = trace_list , layout = layout )
616652
653+ if update_opacity_per_elec :
654+ fig .for_each_trace (_set_opacity )
655+
617656 # change electrode size to custom size if specified
618657 if elec_size is not None :
619658 fig = fig .for_each_trace (
0 commit comments