8383 _check_option ,
8484 _to_rgb ,
8585)
86- from ._3d_overlay import _LayeredMesh
8786from .utils import (
88- mne_analyze_colormap ,
8987 _get_color_list ,
9088 _get_cmap ,
9189 plt_show ,
9290 tight_layout ,
9391 figure_nobar ,
9492 _check_time_unit ,
9593)
96-
94+ from . evoked_field import EvokedField
9795
9896verbose_dec = verbose
9997FIDUCIAL_ORDER = (FIFF .FIFFV_POINT_LPA , FIFF .FIFFV_POINT_NASION , FIFF .FIFFV_POINT_RPA )
@@ -400,7 +398,11 @@ def plot_evoked_field(
400398 vmax = None ,
401399 n_contours = 21 ,
402400 * ,
401+ show_density = True ,
402+ alpha = None ,
403+ interpolation = "nearest" ,
403404 interaction = "terrain" ,
405+ time_viewer = "auto" ,
404406 verbose = None ,
405407):
406408 """Plot MEG/EEG fields on head surface and helmet in 3D.
@@ -417,149 +419,79 @@ def plot_evoked_field(
417419 time_label : str | None
418420 How to print info about the time instant visualized.
419421 %(n_jobs)s
420- fig : instance of Figure3D | None
422+ fig : Figure3D | mne.viz.Brain | None
421423 If None (default), a new figure will be created, otherwise it will
422424 plot into the given figure.
423425
424426 .. 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.
427433
428434 .. versionadded:: 0.21
435+ .. versionadded:: 1.4
436+ ``vmax`` can be a dictionary to specify separate values for EEG and
437+ MEG fields.
429438 n_contours : int
430439 The number of contours.
431440
432441 .. 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
433457 %(interaction_scene)s
434458 Defaults to ``'terrain'``.
435459
436460 .. 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
437466 %(verbose)s
438467
439468 Returns
440469 -------
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.
443474 """
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 ,
471490 )
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 ()
563495
564496
565497@verbose
0 commit comments