Skip to content

Commit 2f853cf

Browse files
Update brain_plots to allow changing electrode size and opacity by el… (#204)
* Update brain_plots to allow changing electrode size and opacity by electrode * Update brain_plots.py * Update __init__.py
1 parent a7d8f63 commit 2f853cf

File tree

2 files changed

+50
-11
lines changed

2 files changed

+50
-11
lines changed

naplib/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,5 @@ def set_logging(level: Union[int, str]):
5656
from .data import Data, join_fields, concat
5757
import naplib.naplab
5858

59-
__version__ = "2.0.0"
59+
__version__ = "2.1.0"
6060

naplib/visualization/brain_plots.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import matplotlib.pyplot as plt
33
import numpy as np
44
import plotly.graph_objs as go
5+
import plotly
56
from matplotlib.cm import ScalarMappable
67
from 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

176189
def 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

Comments
 (0)