83
83
_check_option ,
84
84
_to_rgb ,
85
85
)
86
- from ._3d_overlay import _LayeredMesh
87
86
from .utils import (
88
- mne_analyze_colormap ,
89
87
_get_color_list ,
90
88
_get_cmap ,
91
89
plt_show ,
92
90
tight_layout ,
93
91
figure_nobar ,
94
92
_check_time_unit ,
95
93
)
96
-
94
+ from . evoked_field import EvokedField
97
95
98
96
verbose_dec = verbose
99
97
FIDUCIAL_ORDER = (FIFF .FIFFV_POINT_LPA , FIFF .FIFFV_POINT_NASION , FIFF .FIFFV_POINT_RPA )
@@ -400,7 +398,11 @@ def plot_evoked_field(
400
398
vmax = None ,
401
399
n_contours = 21 ,
402
400
* ,
401
+ show_density = True ,
402
+ alpha = None ,
403
+ interpolation = "nearest" ,
403
404
interaction = "terrain" ,
405
+ time_viewer = "auto" ,
404
406
verbose = None ,
405
407
):
406
408
"""Plot MEG/EEG fields on head surface and helmet in 3D.
@@ -417,149 +419,79 @@ def plot_evoked_field(
417
419
time_label : str | None
418
420
How to print info about the time instant visualized.
419
421
%(n_jobs)s
420
- fig : instance of Figure3D | None
422
+ fig : Figure3D | mne.viz.Brain | None
421
423
If None (default), a new figure will be created, otherwise it will
422
424
plot into the given figure.
423
425
424
426
.. versionadded:: 0.20
425
- vmax : float | None
426
- Maximum intensity. Can be None to use the max(abs(data)).
427
+ .. versionadded:: 1.4
428
+ ``fig`` can also be a ``Brain`` figure.
429
+ vmax : float | dict | None
430
+ Maximum intensity. Can be a dictionary with two entries ``"eeg"`` and ``"meg"``
431
+ to specify separate values for EEG and MEG fields respectively. Can be
432
+ ``None`` to use the maximum value of the data.
427
433
428
434
.. versionadded:: 0.21
435
+ .. versionadded:: 1.4
436
+ ``vmax`` can be a dictionary to specify separate values for EEG and
437
+ MEG fields.
429
438
n_contours : int
430
439
The number of contours.
431
440
432
441
.. versionadded:: 0.21
442
+ show_density : bool
443
+ Whether to draw the field density as an overlay on top of the helmet/head
444
+ surface. Defaults to ``True``.
445
+
446
+ .. versionadded:: 1.6
447
+ alpha : float | dict | None
448
+ Opacity of the meshes (between 0 and 1). Can be a dictionary with two
449
+ entries ``"eeg"`` and ``"meg"`` to specify separate values for EEG and
450
+ MEG fields respectively. Can be ``None`` to use 1.0 when a single field
451
+ map is shown, or ``dict(eeg=1.0, meg=0.5)`` when both field maps are shown.
452
+
453
+ .. versionadded:: 1.4
454
+ %(interpolation_brain_time)s
455
+
456
+ .. versionadded:: 1.6
433
457
%(interaction_scene)s
434
458
Defaults to ``'terrain'``.
435
459
436
460
.. versionadded:: 1.1
461
+ time_viewer : bool | str
462
+ Display time viewer GUI. Can also be ``"auto"``, which will mean
463
+ ``True`` if there is more than one time point and ``False`` otherwise.
464
+
465
+ .. versionadded:: 1.6
437
466
%(verbose)s
438
467
439
468
Returns
440
469
-------
441
- fig : instance of Figure3D
442
- The figure.
470
+ fig : Figure3D | mne.viz.EvokedField
471
+ Without the time viewer active, the figure is returned. With the time
472
+ viewer active, an object is returned that can be used to control
473
+ different aspects of the figure.
443
474
"""
444
- # Update the backend
445
- from .backends .renderer import _get_renderer
446
-
447
- types = [t for t in ["eeg" , "grad" , "mag" ] if t in evoked ]
448
- _validate_type (vmax , (None , "numeric" ), "vmax" )
449
- n_contours = _ensure_int (n_contours , "n_contours" )
450
- _check_option ("interaction" , interaction , ["trackball" , "terrain" ])
451
-
452
- time_idx = None
453
- if time is None :
454
- time = np .mean ([evoked .get_peak (ch_type = t )[1 ] for t in types ])
455
- del types
456
-
457
- if not evoked .times [0 ] <= time <= evoked .times [- 1 ]:
458
- raise ValueError ("`time` (%0.3f) must be inside `evoked.times`" % time )
459
- time_idx = np .argmin (np .abs (evoked .times - time ))
460
-
461
- # Plot them
462
- alphas = [1.0 , 0.5 ]
463
- colors = [(0.6 , 0.6 , 0.6 ), (1.0 , 1.0 , 1.0 )]
464
- colormap = mne_analyze_colormap (format = "vtk" )
465
- colormap_lines = np .concatenate (
466
- [
467
- np .tile ([0.0 , 0.0 , 255.0 , 255.0 ], (127 , 1 )),
468
- np .tile ([0.0 , 0.0 , 0.0 , 255.0 ], (2 , 1 )),
469
- np .tile ([255.0 , 0.0 , 0.0 , 255.0 ], (127 , 1 )),
470
- ]
475
+ ef = EvokedField (
476
+ evoked ,
477
+ surf_maps ,
478
+ time = time ,
479
+ time_label = time_label ,
480
+ n_jobs = n_jobs ,
481
+ fig = fig ,
482
+ vmax = vmax ,
483
+ n_contours = n_contours ,
484
+ alpha = alpha ,
485
+ show_density = show_density ,
486
+ interpolation = interpolation ,
487
+ interaction = interaction ,
488
+ time_viewer = time_viewer ,
489
+ verbose = verbose ,
471
490
)
472
-
473
- renderer = _get_renderer (fig , bgcolor = (0.0 , 0.0 , 0.0 ), size = (600 , 600 ))
474
- renderer .set_interaction (interaction )
475
-
476
- for ii , this_map in enumerate (surf_maps ):
477
- surf = this_map ["surf" ]
478
- map_data = this_map ["data" ]
479
- map_type = this_map ["kind" ]
480
- map_ch_names = this_map ["ch_names" ]
481
-
482
- if map_type == "eeg" :
483
- pick = pick_types (evoked .info , meg = False , eeg = True )
484
- else :
485
- pick = pick_types (evoked .info , meg = True , eeg = False , ref_meg = False )
486
-
487
- ch_names = [evoked .ch_names [k ] for k in pick ]
488
-
489
- set_ch_names = set (ch_names )
490
- set_map_ch_names = set (map_ch_names )
491
- if set_ch_names != set_map_ch_names :
492
- message = ["Channels in map and data do not match." ]
493
- diff = set_map_ch_names - set_ch_names
494
- if len (diff ):
495
- message += ["%s not in data file. " % list (diff )]
496
- diff = set_ch_names - set_map_ch_names
497
- if len (diff ):
498
- message += ["%s not in map file." % list (diff )]
499
- raise RuntimeError (" " .join (message ))
500
-
501
- data = np .dot (map_data , evoked .data [pick , time_idx ])
502
-
503
- # Make a solid surface
504
- if vmax is None :
505
- vmax = np .max (np .abs (data ))
506
- vmax = float (vmax )
507
- alpha = alphas [ii ]
508
- mesh = _LayeredMesh (
509
- renderer = renderer ,
510
- vertices = surf ["rr" ],
511
- triangles = surf ["tris" ],
512
- normals = surf ["nn" ],
513
- )
514
- mesh .map ()
515
- color = _to_rgb (colors [ii ], alpha = True )
516
- cmap = np .array (
517
- [
518
- (
519
- 0 ,
520
- 0 ,
521
- 0 ,
522
- 0 ,
523
- ),
524
- color ,
525
- ]
526
- )
527
- ctable = np .round (cmap * 255 ).astype (np .uint8 )
528
- mesh .add_overlay (
529
- scalars = np .ones (len (data )),
530
- colormap = ctable ,
531
- rng = [0 , 1 ],
532
- opacity = alpha ,
533
- name = "surf" ,
534
- )
535
- # Now show our field pattern
536
- mesh .add_overlay (
537
- scalars = data ,
538
- colormap = colormap ,
539
- rng = [- vmax , vmax ],
540
- opacity = 1.0 ,
541
- name = "field" ,
542
- )
543
-
544
- # And the field lines on top
545
- if n_contours > 0 :
546
- renderer .contour (
547
- surface = surf ,
548
- scalars = data ,
549
- contours = n_contours ,
550
- vmin = - vmax ,
551
- vmax = vmax ,
552
- opacity = alpha ,
553
- colormap = colormap_lines ,
554
- )
555
-
556
- if time_label is not None :
557
- if "%" in time_label :
558
- time_label %= 1e3 * evoked .times [time_idx ]
559
- renderer .text2d (x_window = 0.01 , y_window = 0.01 , text = time_label )
560
- renderer .set_camera (azimuth = 10 , elevation = 60 )
561
- renderer .show ()
562
- return renderer .scene ()
491
+ if ef .time_viewer :
492
+ return ef
493
+ else :
494
+ return ef ._renderer .scene ()
563
495
564
496
565
497
@verbose
0 commit comments