4646from ...utils import (
4747 Bunch ,
4848 _auto_weakref ,
49+ _check_fname ,
4950 _check_option ,
5051 _ensure_int ,
52+ _path_like ,
5153 _ReuseCycle ,
5254 _to_rgb ,
5355 _validate_type ,
@@ -3078,21 +3080,27 @@ def add_annotation(
30783080 color = None ,
30793081 hover = True ,
30803082 ):
3081- """Add an annotation file .
3083+ """Add an annotation (i.e. an atlas of many labels) to the brain figure .
30823084
30833085 Parameters
30843086 ----------
3085- annot : str
3086- Either path to annotation file or annotation name.
3087+ annot : path-like | str | list of Label
3088+ Either path to annotation file, an annotation name, or a list of
3089+ :class:`mne.Label` objects.
3090+
3091+ DEPRECATED: The annotation can be specified as a ``(labels, ctab)`` tuple
3092+ per hemisphere, i.e. ``annot=(labels, ctab)`` for a single hemisphere or
3093+ ``annot=((lh_labels, lh_ctab), (rh_labels, rh_ctab))`` for both hemispheres.
3094+
3095+ .. versionadded:: 1.13
3096+ The ability to supply a list of :class:`~mne.Label` objects.
30873097 borders : bool | int
30883098 Show only label borders. If int, specify the number of steps
30893099 (away from the true border) along the cortical mesh to include
30903100 as part of the border definition.
30913101 %(alpha)s Default is 1.
30923102 hemi : str | None
3093- If None, it is assumed to belong to the hemisphere being
3094- shown. If two hemispheres are being shown, data must exist
3095- for both hemispheres.
3103+ Optionally restrict the annotation to the given hemisphere.
30963104 remove_existing : bool
30973105 If True (default), remove old annotations.
30983106 color : matplotlib-style color code
@@ -3105,19 +3113,49 @@ def add_annotation(
31053113 """
31063114 from ...label import read_labels_from_annot
31073115
3116+ if (isinstance (annot , tuple ) and isinstance (annot [0 ], np .ndarray )) or (
3117+ isinstance (annot , (tuple , list )) and isinstance (annot [0 ], tuple )
3118+ ):
3119+ # Deprecated old style of passing a (labels, cmap) pair per hemisphere.
3120+ # Shortcut to old code that can be removed in version 1.14.
3121+ warn (
3122+ "Passing the annotation as a `(label, cmap)` tuple is deprecated and "
3123+ "will be removed in MNE-Python version 1.14." ,
3124+ FutureWarning ,
3125+ )
3126+ self ._old_add_annotation (
3127+ annot ,
3128+ borders = borders ,
3129+ alpha = alpha ,
3130+ hemi = hemi ,
3131+ remove_existing = remove_existing ,
3132+ color = color ,
3133+ )
3134+ return
3135+
3136+ _validate_type (annot , ("path-like" , str , list ), "annot" )
3137+
31083138 hemis = self ._check_hemis (hemi )
31093139 kwargs = dict ()
3110- if os .path .isfile (annot ):
3111- kwargs ["annot_fname" ] = annot
3112- else :
3113- kwargs ["parc" ] = annot
31143140
3115- for hemi in hemis :
3116- labels = read_labels_from_annot (
3117- self ._subject , hemi = hemi , subjects_dir = self ._subjects_dir , ** kwargs
3118- )
3141+ for hemi_idx , hemi in enumerate (hemis ):
3142+ if _path_like (annot ):
3143+ if os .path .isfile (annot ):
3144+ kwargs ["annot_fname" ] = annot
3145+ else :
3146+ kwargs ["parc" ] = annot
3147+ labels = read_labels_from_annot (
3148+ self ._subject ,
3149+ hemi = hemi ,
3150+ subjects_dir = self ._subjects_dir ,
3151+ ** kwargs ,
3152+ )
3153+ name = annot
3154+ else :
3155+ labels = [label for label in annot if label .hemi == hemi ]
3156+ name = "annotation" # placeholder name for the annotation
31193157 n_labels = len (labels )
3120- ids = np .zeros (self .geo [hemi ].coords .shape [0 ], dtype = int )
3158+ ids = np .full (self .geo [hemi ].coords .shape [0 ], - 1 , dtype = int )
31213159 cmap = np .zeros ((len (labels ) + 1 , 4 ))
31223160 cmap [:, 3 ] = 1
31233161 cmap [0 ] = np .array (self ._brain_color )
@@ -3131,7 +3169,7 @@ def add_annotation(
31313169 label .center_of_mass (subjects_dir = self ._subjects_dir )
31323170 ]
31333171 self ._annots [hemi ].append (
3134- dict (name = annot , labels = labels , ids = ids , centroids = centroids )
3172+ dict (name = name , labels = labels , ids = ids , centroids = centroids )
31353173 )
31363174 del labels
31373175
@@ -3151,7 +3189,7 @@ def add_annotation(
31513189 colormap = ctable * 255 ,
31523190 rng = [0 , n_labels ],
31533191 opacity = alpha ,
3154- name = annot ,
3192+ name = name ,
31553193 )
31563194
31573195 if hover :
@@ -3175,6 +3213,99 @@ def on_annotation_hover(iren, event):
31753213 )
31763214 self ._renderer ._update ()
31773215
3216+ # DEPRECATED: Can be removed in version 1.14. Also remove _read_annot from
3217+ # mne/labels.py.
3218+ def _old_add_annotation (
3219+ self , annot , borders = True , alpha = 1 , hemi = None , remove_existing = True , color = None
3220+ ):
3221+ from ...label import _read_annot
3222+
3223+ hemis = self ._check_hemis (hemi )
3224+
3225+ # Figure out where the data is coming from
3226+ if _path_like (annot ):
3227+ if os .path .isfile (annot ):
3228+ filepath = _check_fname (annot , overwrite = "read" )
3229+ file_hemi , annot = filepath .name .split ("." , 1 )
3230+ if len (hemis ) > 1 :
3231+ if file_hemi == "lh" :
3232+ filepaths = [filepath , filepath .parent / ("rh." + annot )]
3233+ elif file_hemi == "rh" :
3234+ filepaths = [filepath .parent / ("lh." + annot ), filepath ]
3235+ else :
3236+ raise RuntimeError (
3237+ "To add both hemispheres simultaneously, filename must "
3238+ 'begin with "lh." or "rh."'
3239+ )
3240+ else :
3241+ filepaths = [filepath ]
3242+ else :
3243+ filepaths = []
3244+ for hemi in hemis :
3245+ filepath = op .join (
3246+ self ._subjects_dir ,
3247+ self ._subject ,
3248+ "label" ,
3249+ "." .join ([hemi , annot , "annot" ]),
3250+ )
3251+ if not os .path .exists (filepath ):
3252+ raise ValueError (f"Annotation file { filepath } does not exist" )
3253+ filepaths += [filepath ]
3254+ annots = []
3255+ for hemi , filepath in zip (hemis , filepaths ):
3256+ # Read in the data
3257+ labels , cmap , _ = _read_annot (filepath )
3258+ annots .append ((labels , cmap ))
3259+ else :
3260+ annots = [annot ] if len (hemis ) == 1 else annot
3261+ annot = "annotation"
3262+
3263+ for hemi , (labels , cmap ) in zip (hemis , annots ):
3264+ # Maybe zero-out the non-border vertices
3265+ self ._to_borders (labels , hemi , borders )
3266+
3267+ # Handle null labels properly
3268+ cmap [:, 3 ] = 255
3269+ bgcolor = np .round (np .array (self ._brain_color ) * 255 ).astype (int )
3270+ bgcolor [- 1 ] = 0
3271+ cmap [cmap [:, 4 ] < 0 , 4 ] += 2 ** 24 # wrap to positive
3272+ cmap [cmap [:, 4 ] <= 0 , :4 ] = bgcolor
3273+ if np .any (labels == 0 ) and not np .any (cmap [:, - 1 ] <= 0 ):
3274+ cmap = np .vstack ((cmap , np .concatenate ([bgcolor , [0 ]])))
3275+
3276+ # Set label ids sensibly
3277+ order = np .argsort (cmap [:, - 1 ])
3278+ cmap = cmap [order ]
3279+ ids = np .searchsorted (cmap [:, - 1 ], labels )
3280+ cmap = cmap [:, :4 ]
3281+
3282+ # Set the alpha level
3283+ alpha_vec = cmap [:, 3 ]
3284+ alpha_vec [alpha_vec > 0 ] = alpha * 255
3285+
3286+ # Override the cmap when a single color is used
3287+ if color is not None :
3288+ rgb = np .round (np .multiply (_to_rgb (color ), 255 ))
3289+ cmap [:, :3 ] = rgb .astype (cmap .dtype )
3290+
3291+ ctable = cmap .astype (np .float64 )
3292+ for _ in self ._iter_views (hemi ):
3293+ mesh = self .layered_meshes [hemi ]
3294+ mesh .add_overlay (
3295+ scalars = ids ,
3296+ colormap = ctable ,
3297+ rng = [np .min (ids ), np .max (ids )],
3298+ opacity = alpha ,
3299+ name = annot ,
3300+ )
3301+ self ._annots [hemi ].append (annot )
3302+ if not self .time_viewer or self .traces_mode == "vertex" :
3303+ self ._renderer ._set_colormap_range (
3304+ mesh ._actor , cmap .astype (np .uint8 ), None
3305+ )
3306+
3307+ self ._renderer ._update ()
3308+
31783309 def _create_caption (self ):
31793310 from vtkmodules .vtkRenderingAnnotation import vtkCaptionActor2D
31803311
0 commit comments