Source code for visbrain.objects.source_obj

"""Base class for objects of type source."""
from warnings import warn
import logging
import numpy as np
from itertools import product
from scipy.spatial.distance import cdist

from vispy import scene
from vispy.scene import visuals
import vispy.visuals.transforms as vist

from .visbrain_obj import VisbrainObject, CombineObjects
from ._projection import _project_sources_data
from .roi_obj import RoiObj
from ..utils import (tal2mni, color2vb, normalize, vispy_array,
                     wrap_properties, array2colormap)


logger = logging.getLogger('visbrain')
PROJ_STR = "%i sources visibles and not masked used for the %s"


[docs]class SourceObj(VisbrainObject): """Create a source object. Parameters ---------- name : string Name of the source object. xyz : array_like Array of positions of shape (n_sources, 2) or (n_sources, 3). data : array_like | None Array of weights of shape (n_sources,). color : array_like/string/tuple | 'red' Marker's color. Use a string (i.e 'green') to use the same color across markers or a list of colors of length n_sources to use different colors for markers. alpha : float | 1. Transparency level. symbol : string | 'disc' Symbol to use for sources. Allowed style strings are: disc, arrow, ring, clobber, square, diamond, vbar, hbar, cross, tailed_arrow, x, triangle_up, triangle_down, and star. radius_min / radius_max : float | 5.0/10.0 Define the minimum and maximum source's possible radius. By default if all sources have the same value, the radius will be radius_min. edge_color : string/list/array_like | 'black' Edge color of source's markers. edge_width : float | 0. Edge width source's markers. system : {'mni', 'tal'} Specify if the coodinates are in the MNI space ('mni') or Talairach ('tal'). mask : array_like | None Array of boolean values to specify masked sources. For example, if data are p-values, mask could be non-significant sources. mask_color : array_like/tuple/string | 'gray' Color to use for masked sources. text : list | None Text to attach to each source. For example, text could be the name of each source. text_size : float | 3. Text size attached to sources. text_color : array_like/string/tuple | 'black' Text color attached to sources. text_bold : bool | False Specify if the text attached to sources should be bold. text_translate : tuple | (0., 2., 0.) Translate the text along the (x, y, z) axis. visible : bool/array_like | True Specify which source's have to be displayed. If visible is True, all sources are displayed, False all sources are hiden. Alternatively, use an array of shape (n_sources,) to select which sources to display. transform : VisPy.visuals.transforms | None VisPy transformation to set to the parent node. parent : VisPy.parent | None Markers object parent. verbose : string Verbosity level. _z : float | 10. In case of (n_sources, 2) use _z to specify the elevation. kw : dict | {} Optional arguments are used to control the colorbar (See :class:`ColorbarObj`). Notes ----- List of supported shortcuts : * **s** : save the figure * **<delete>** : reset camera Examples -------- >>> import numpy as np >>> from visbrain.objects import SourceObj >>> n_sources = 100 >>> pos = np.random.uniform(-10, 10, (n_sources, 3)) >>> color = ['orange'] * 50 + ['red'] * 50 >>> data = np.random.rand(n_sources) >>> text = ['s' + str(k) for k in range(n_sources)] >>> s = SourceObj('test', pos, color=color, data=data, radius_min=10., >>> radius_max=20., edge_color='black', edge_width=1., >>> text=text, text_size=10.) >>> s.preview(axis=True) """ ########################################################################### ########################################################################### # BUILT IN ########################################################################### ###########################################################################
[docs] def __init__(self, name, xyz, data=None, color='red', alpha=1., symbol='disc', radius_min=5., radius_max=10., edge_width=0., edge_color='black', system='mni', mask=None, mask_color='gray', text=None, text_size=3., text_color='black', text_bold=False, text_translate=(0., 2., 0.), visible=True, transform=None, parent=None, verbose=None, _z=-10., **kw): """Init.""" VisbrainObject.__init__(self, name, parent, transform, verbose, **kw) # _______________________ CHECKING _______________________ # XYZ : sh = xyz.shape assert sh[1] in [2, 3] self._n_sources = sh[0] pos = xyz if sh[1] == 3 else np.c_[xyz, np.full((len(self),), _z)] # Radius min and max : assert all([isinstance(k, (int, float)) for k in ( radius_min, radius_max)]) radius_max = max(radius_min, radius_max) self._radius_min, self._radius_max = radius_min, radius_max # Data : if data is None: data = np.ones((len(self),)) else: data = np.asarray(data).ravel() assert len(data) == len(self) self._data = vispy_array(data) # System : pos = pos if system == 'mni' else tal2mni(pos) self._xyz = vispy_array(pos) # Color : self._color = color # Edges : self._edge_color, self._edge_width = edge_color, edge_width # Mask : if mask is None: mask = [False] * len(self) self._mask = np.asarray(mask).ravel().astype(bool) assert len(self._mask) == len(self) self._mask_color = color2vb(mask_color) # Text : self._text_size = text_size self._text_color = text_color self._text_translate = text_translate # _______________________ MARKERS _______________________ self._sources = visuals.Markers(pos=self._xyz, name='Markers', edge_color=edge_color, edge_width=edge_width, symbol=symbol, parent=self._node) # _______________________ TEXT _______________________ tvisible = text is None self._text = [''] * len(self) if tvisible else text self._text = np.array(self._text) assert len(self._text) == len(self) self._sources_text = visuals.Text(self._text, pos=self._xyz, bold=text_bold, name='Text', color=color2vb(text_color), font_size=text_size, parent=self._node) self._sources_text.visible = not tvisible tr = vist.STTransform(translate=text_translate) self._sources_text.transform = tr # _______________________ UPDATE _______________________ # Radius / color : self.visible = visible self._update_radius() self._update_color() self.alpha = alpha
def __len__(self): """Get the number of sources.""" return self._n_sources def __bool__(self): """Return if all source are visible.""" return np.all(self._visible) def __iter__(self): """Loop over visible xyz coordinates. At each step, the coordinates are (1, 3) and not (3,). """ xyz = self.xyz # get only visible coordinates for k in range(xyz.shape[0]): yield xyz[[k], :] def __add__(self, value): """Add two SourceObj instances. This method return a SourceObj with xyz coodinates and the source's data but only for visible sources; """ assert isinstance(value, SourceObj) name = self._name + ' + ' + value._name xyz = np.r_[self._xyz, value._xyz] data = np.r_[self._data, value._data] text = np.r_[self._text, value._text] visible = np.r_[self._visible, value.visible] return SourceObj(name, xyz, data=data, text=text, visible=visible) ########################################################################### ########################################################################### # UPDATE ########################################################################### ###########################################################################
[docs] def update(self): """Update the source object.""" self._sources._vbo.set_data(self._sources._data) self._sources.update() self._sources_text.update()
def _update_radius(self): """Update marker's radius.""" logger.debug("Weird edge arround markers (source_obj.py)") if np.unique(self._data).size == 1: radius = self._radius_min * np.ones((len(self,))) else: radius = normalize(self._data.copy(), tomin=self._radius_min, tomax=self._radius_max) self._sources._data['a_size'] = radius to_hide = self.hide # Marker size + egde width = 0 and text='' for hidden sources : self._sources._data['a_size'][to_hide] = 0. self._sources._data['a_edgewidth'][to_hide] = 0. text = np.array(self._text.copy()) text[to_hide] = '' self._sources_text.text = text self.update() def _update_color(self): """Update marker's color.""" # Get marker's background color : if isinstance(self._color, str): # color='white' bg_color = color2vb(self._color, length=len(self)) elif isinstance(self._color, list): # color=['white', 'green'] assert len(self._color) == len(self) bg_color = np.squeeze(np.array([color2vb(k) for k in self._color])) elif isinstance(self._color, np.ndarray): # color = [[0, 0, 0], ...] csh = self._color.shape assert (csh[0] == len(self)) and (csh[1] >= 3) if self._color.shape[1] == 3: # RGB self._color = np.c_[self._color, np.full(len(self), self._alpha)] bg_color = self._color.copy() # Update masked marker's color : bg_color[self._mask, :] = self._mask_color self._sources._data['a_bg_color'] = bg_color self.update() def _get_camera(self): """Get the most adapted camera.""" d_mean = self._xyz.mean(0) dist = 1.1 * np.linalg.norm(self._xyz, axis=1).max() return scene.cameras.TurntableCamera(center=d_mean, scale_factor=dist) ########################################################################### ########################################################################### # PROJECTIONS ########################################################################### ###########################################################################
[docs] def project_sources(self, b_obj, project='modulation', radius=10., contribute=False, cmap='viridis', clim=None, vmin=None, under='black', vmax=None, over='red', mask_color=None): """Project source's activity or repartition onto the brain object. Parameters ---------- b_obj : {BrainObj, RoiObj} The object on which to project sources. project : {'modulation', 'repartition'} Project either the source's data ('modulation') or get the number of contributing sources per vertex ('repartition'). radius : float The radius under which activity is projected on vertices. contribute: bool | False Specify if sources contribute on both hemisphere. cmap : string | 'viridis' The colormap to use. clim : tuple | None The colorbar limits. If None, (data.min(), data.max()) will be used instead. vmin : float | None Minimum threshold. vmax : float | None Maximum threshold. under : string/tuple/array_like | 'gray' The color to use for values under vmin. over : string/tuple/array_like | 'red' The color to use for values over vmax. mask_color : string/tuple/array_like | 'gray' The color to use for the projection of masked sources. If None, the color of the masked sources is going to be used. """ kw = self._update_cbar_args(cmap, clim, vmin, vmax, under, over) self._default_cblabel = "Source's %s" % project _project_sources_data(self, b_obj, project, radius, contribute, mask_color=mask_color, **kw)
########################################################################### ########################################################################### # PHYSIO ########################################################################### ###########################################################################
[docs] def analyse_sources(self, roi_obj='talairach', replace_bad=True, bad_patterns=[-1, 'undefined', 'None'], distance=None, replace_with='Not found', keep_only=None): """Analyse sources using Region of interest (ROI). This method can be used to identify in which structure is located a source. Parameters ---------- roi_obj : string/list | 'talairach' The ROI object to use. Use either 'talairach', 'brodmann' or 'aal' to use a predefined ROI template. Otherwise, use a RoiObj object or a list of RoiObj. replace_bad : bool | True Replace bad values (True) or not (False). bad_patterns : list | [-1, 'undefined', 'None'] Bad patterns to replace if replace_bad is True. replace_with : string | 'Not found' Replace bad patterns with this string. keep_only : list | None List of string patterns to keep only sources that match. Returns ------- df : pandas.DataFrames A Pandas DataFrame or a list of DataFrames if roi_obj is a list. """ # List of predefined ROI objects : proi = ['brodmann', 'aal', 'talairach'] # Define the ROI object if needed : if isinstance(roi_obj, (str, list, tuple)): if isinstance(roi_obj, str): roi_obj = [roi_obj] if all([isinstance(k, str) for k in roi_obj]): roi_obj = [RoiObj(k) for k in roi_obj if k in proi] elif isinstance(roi_obj, RoiObj): roi_obj = [roi_obj] assert all([isinstance(k, RoiObj) for k in roi_obj]) # Convert predefined ROI into RoiObj objects : logger.info("Analyse source's locations using the %s " "atlas" % ', '.join([k.name for k in roi_obj])) if isinstance(roi_obj, (list, tuple)): test_r = all([k in proi or isinstance(k, RoiObj) for k in roi_obj]) if not test_r: raise TypeError("roi_obj should either be 'brodmann', 'aal', " "'talairach' or a list or RoiObj objects.") # Get all of the DataFrames : df = [k.localize_sources(self._xyz, self._text, replace_bad, bad_patterns, replace_with, distance) for k in roi_obj] # Merge multiple DataFrames : if len(df) > 1: logger.info('Merging DataFrames') import pandas as pd df_full = df.copy() df = df_full[0] for i, k in enumerate(df_full[1::]): df = pd.merge(df, k, on=['Text', 'X', 'Y', 'Z', 'hemisphere'], suffixes=('_' + roi_obj[0].name, '_' + roi_obj[i + 1].name)) else: df = df[0] # Keep only sources that match with patterns : if isinstance(keep_only, (list, tuple)): idx_to_keep = [] for k, i in product(df.keys(), keep_only): idx_to_keep.append(np.array(df[k], dtype=object) == i) idx_to_keep = np.vstack(idx_to_keep).sum(0).astype(bool) df = df.loc[idx_to_keep] self.visible = idx_to_keep logger.info("%i sources found in %s" % (len(df), ', '.join(keep_only))) return df
[docs] def color_sources(self, analysis=None, color_by=None, data=None, roi_to_color=None, color_others='black', hide_others=False, cmap='viridis', clim=None, vmin=None, vmax=None, under='gray', over='red'): """Custom color sources methods. This method can be used to color sources : * According to a data vector. In that case, source's colors are inferred using colormap inputs (i.e cmap, vmin, vmax, clim, under and over) * According to ROI analysis (using the `analysis` and `color_by` input parameters) Parameters ---------- data : array_like | None A vector of data with the same length as the number os sources. The color is inferred from this data vector and can be controlled using the cmap, clim, vmin, vmax, under and over parameters. analysis : pandas.DataFrames | None ROI analysis runned using the analyse_sources method. color_by : string | None A column name of the analysis DataFrames. This columns is then used to identify the color to set to each source inside ROI. roi_to_color : dict | None Define custom colors to ROI. For example use {'BA4': 'red', 'BA32': 'blue'} to define custom colors. If roi_to_color is None, random colors will be used instead. color_others : array_like/tuple/string | 'black' Specify how to color sources that are not found using the roi_to_color dictionary. hide_others : bool | False Show or hide sources that are not found using the roi_to_color dictionary. """ if isinstance(data, np.ndarray): assert len(data) == len(self) and (data.ndim == 1) logger.info("Color %s using a data vector" % self.name) kw = self._update_cbar_args(cmap, clim, vmin, vmax, under, over) colors = array2colormap(data, **kw) elif (analysis is not None) and (color_by is not None): # Group analysis : assert color_by in list(analysis.columns) logger.info("Color %s according to the %s" % (self.name, color_by)) gp = analysis.groupby(color_by).groups # Compute color : if roi_to_color is None: # random color # Predefined colors and define unique color for each ROI : colors = np.zeros((len(self), 3), dtype=np.float32) u_col = np.random.uniform(.1, .8, (len(gp), 3)) u_col = u_col.astype(np.float32) # Assign color to the ROI : for k, index in enumerate(gp.values()): colors[list(index), :] = u_col[k, :] elif isinstance(roi_to_color, dict): # user defined colors colors = color2vb(color_others, length=len(self)) keep_visible = np.zeros(len(self), dtype=bool) for roi_name, roi_col in roi_to_color.items(): if roi_name in list(gp.keys()): colors[list(gp[roi_name]), :] = color2vb(roi_col) keep_visible[list(gp[roi_name])] = True else: warn("%s not found in the %s column of analysis" "." % (roi_name, color_by)) if hide_others: self.visible = keep_visible else: raise TypeError("roi_to_color must either be None or a " "dictionary like {'roi_name': 'red'}.") self.color = colors
[docs] def set_visible_sources(self, select='all', v=None, distance=5.): """Select sources that are either inside or outside the mesh. Parameters ---------- select : {'inside', 'outside', 'close', 'all', 'none', 'left', 'right'} Custom source selection. Use 'inside' or 'outside' to select sources respectively inside or outside the volume. Use 'close' to select sources that are closed to the surface (see the distance parameter below). Finally, use 'all' (or True), 'none' (or None, False) to show or hide all of the sources. v : array_like | None The vertices of shape (nv, 3) or (nv, 3, 3) if index faced. distance : float | 5. Distance between the source and the surface. """ select = select.lower() if isinstance(select, str) else select assert select in ['all', 'inside', 'outside', 'none', 'close', None, True, False, 'left', 'right'] assert isinstance(distance, (int, float)) xyz = self._xyz if select in ['inside', 'outside', 'close']: logger.info("Select sources %s vertices" % select) if v.ndim == 2: # index faced vertices v = v[:, np.newaxis, :] # Predifined inside : nv, index_faced = v.shape[0], v.shape[1] v = v.reshape(nv * index_faced, 3) inside = np.ones((xyz.shape[0],), dtype=bool) # Loop over ALL oh the sources : for i in range(len(self)): # Get the euclidian distance : eucl = cdist(v, xyz[[i], :]) # Get the closest vertex : eucl_argmin = eucl.argmin() # Get distance to zero : xyz_t0 = np.sqrt((xyz[[i], :] ** 2).sum()) v_t0 = np.sqrt((v[eucl_argmin, :] ** 2).sum()) if select in ['inside', 'outside']: inside[i] = xyz_t0 <= v_t0 elif select == 'close': inside[i] = np.abs(xyz_t0 - v_t0) > distance self.visible = inside if select == 'inside' else np.invert(inside) elif select in ['all', 'none', None, True, False]: cond = select in ['all', True] self.visible = cond self.visible_obj = cond msg = 'Display' if cond else 'Hide' logger.info("%s all sources" % msg) elif select in ['left', 'right']: logger.info('Select sources in the %s hemisphere' % select) vec = xyz[:, 0] self.visible = vec <= 0 if select == 'left' else vec >= 0
[docs] def fit_to_vertices(self, v): """Move sources to the closest vertex. Parameters ---------- v : array_like The vertices of shape (nv, 3) or (nv, 3, 3) if index faced. """ if v.ndim == 2: # index faced vertices v = v[:, np.newaxis, :] # Predifined inside : nv, index_faced = v.shape[0], v.shape[1] v = v.reshape(nv * index_faced, 3) new_pos = np.zeros_like(self._xyz) # Loop over visible and not-masked sources : for i, k in enumerate(self): # Get the euclidian distance : eucl = cdist(v, k) # Set new coordinate using the closest vertex : new_pos[i, :] = v[eucl.argmin(), :] # Finally update data sources and text : self._sources._data['a_position'] = new_pos self._sources_text.pos = new_pos self.update()
########################################################################### ########################################################################### # PROPERTIES ########################################################################### ########################################################################### # ----------- XYZ ----------- @property def xyz(self): """Get the visible xyz value.""" return self._xyz[self.visible_and_not_masked] # ----------- DATA ----------- @property def data(self): """Get the data value.""" return self._data[self.visible_and_not_masked] @data.setter @wrap_properties def data(self, value): """Set data value.""" assert isinstance(value, np.ndarray) and len(value) == len(self) self._data = value # ----------- TEXT ----------- @property def text(self): """Get the text value.""" return np.array(self._text)[self.visible_and_not_masked] @text.setter @wrap_properties def text(self, value): """Set text value.""" assert len(value) == len(self._text) self._text = value self._sources_text.visible = True self._update_radius() # ----------- VISIBLE_AND_NOT_MASKED ----------- @property def visible_and_not_masked(self): """Get the visible_and_not_masked value.""" return np.logical_and(self._visible, ~self.mask) # ----------- RADIUSMIN ----------- @property def radius_min(self): """Get the radius_min value.""" return self._radius_min @radius_min.setter @wrap_properties def radius_min(self, value): """Set radius_min value.""" assert isinstance(value, (int, float)) self._radius_min = min(self._radius_max, value) self._update_radius() # ----------- RADIUSMAX ----------- @property def radius_max(self): """Get the radius_max value.""" return self._radius_max @radius_max.setter @wrap_properties def radius_max(self, value): """Set radius_max value.""" assert isinstance(value, (int, float)) self._radius_max = max(self._radius_min, value) self._update_radius() # ----------- SYMBOL ----------- @property def symbol(self): """Get the symbol value.""" return self._sources.symbol @symbol.setter @wrap_properties def symbol(self, value): """Set symbol value.""" assert isinstance(value, str) self._sources.symbol = value self._sources.update() # ----------- EDGE_WIDTH ----------- @property def edge_width(self): """Get the edge_width value.""" return self._edge_width @edge_width.setter @wrap_properties def edge_width(self, value): """Set edge_width value.""" assert isinstance(value, (int, float)) self._edge_width = value self._sources._data['a_edgewidth'] = value self.update() # ----------- EDGE_COLOR ----------- @property def edge_color(self): """Get the edge_color value.""" return self._edge_color @edge_color.setter @wrap_properties def edge_color(self, value): """Set edge_color value.""" color = color2vb(value, alpha=self.alpha) self._sources._data['a_fg_color'] = color self._edge_color = color self.update() # ----------- ALPHA ----------- @property def alpha(self): """Get the alpha value.""" return self._alpha @alpha.setter @wrap_properties def alpha(self, value): """Set alpha value.""" assert isinstance(value, (int, float)) assert 0 <= value <= 1 self._alpha = value self._sources._data['a_fg_color'][:, -1] = value self._sources._data['a_bg_color'][:, -1] = value self.update() # ----------- COLOR ----------- @property def color(self): """Get the color value.""" return self._color @color.setter @wrap_properties def color(self, value): """Set color value.""" self._color = value self._update_color() # ----------- MASK ----------- @property def mask(self): """Get the mask value.""" return self._mask @mask.setter @wrap_properties def mask(self, value): """Set mask value.""" assert len(value) == len(self) self._mask = value self._update_color() # ----------- IS_MASKED ----------- @property def is_masked(self): """Get the is_masked value.""" return any(self._mask) # ----------- MASKCOLOR ----------- @property def mask_color(self): """Get the mask_color value.""" return self._mask_color @mask_color.setter @wrap_properties def mask_color(self, value): """Set mask_color value.""" self._mask_color = color2vb(value) self._update_color() # ----------- VISIBLE ----------- @property def visible(self): """Get the visible value.""" return self._visible @visible.setter @wrap_properties def visible(self, value): """Set visible value.""" if isinstance(value, bool): self._visible = np.full((len(self),), value) else: self._visible = np.asarray(value).ravel().astype(bool) assert len(self._visible) == len(self) self._update_radius() # ----------- HIDE ----------- @property def hide(self): """Get the hide value.""" return np.invert(self._visible) # ----------- TEXT_SIZE ----------- @property def text_size(self): """Get the text_size value.""" return self._text_size @text_size.setter @wrap_properties def text_size(self, value): """Set text_size value.""" assert isinstance(value, (int, float)) self._text_size = value self._sources_text.font_size = value self._sources_text.update() # ----------- TEXT_COLOR ----------- @property def text_color(self): """Get the text_color value.""" return self._text_color @text_color.setter @wrap_properties def text_color(self, value): """Set text_color value.""" color = color2vb(value) self._sources_text.color = color self._text_color = color self._sources_text.update() # ----------- TEXT_TRANSLATE ----------- @property def text_translate(self): """Get the text_translate value.""" return self._text_translate @text_translate.setter @wrap_properties def text_translate(self, value): """Set text_translate value.""" assert len(value) == 3 self._sources_text.transform.translate = value self._text_translate = value self._sources_text.update()
class CombineSources(CombineObjects): """Combine sources objects. Parameters ---------- sobjs : SourceObj/list | None List of source objects. select : string | None The name of the source object to select. parent : VisPy.parent | None Markers object parent. """ def __init__(self, sobjs=None, select=None, parent=None, **kwargs): """Init.""" CombineObjects.__init__(self, SourceObj, sobjs, select, parent) def project_sources(self, b_obj, project='modulation', radius=10., contribute=False, cmap='viridis', clim=None, vmin=None, under='black', vmax=None, over='red', mask_color=None): """Project source's activity or repartition onto the brain object.""" kw = self._update_cbar_args(cmap, clim, vmin, vmax, under, over) self._default_cblabel = "Source's %s" % project _project_sources_data(self, b_obj, project, radius, contribute, mask_color=mask_color, **kw) def fit_to_vertices(self, v): """See sources doc.""" for k in self: k.fit_to_vertices(v) def set_visible_sources(self, *args, **kwargs): """See sources doc.""" for k in self: k.set_visible_sources(*args, **kwargs) def analyse_sources(self, *args, **kwargs): """See sources doc.""" import pandas as pd df = [] for k in self: df.append(k.analyse_sources(*args, **kwargs)) return pd.concat(df, ignore_index=True) # ----------- _XYZ ----------- @property def _xyz(self): """Get the _xyz value.""" _xyz = np.array([]) for k in self: _xyz = np.r_[_xyz, k._xyz] if _xyz.size else k._xyz return _xyz # ----------- XYZ ----------- @property def xyz(self): """Get the xyz value.""" xyz = np.array([]) for k in self: xyz = np.r_[xyz, k.xyz] if xyz.size else k.xyz return xyz # ----------- _DATA ----------- @property def _data(self): """Get the _data value.""" _data = np.array([]) for k in self: _data = np.r_[_data, k._data] if _data.size else k._data return _data # ----------- DATA ----------- @property def data(self): """Get the data value.""" data = np.array([]) for k in self: data = np.r_[data, k.data] if data.size else k.data return data # ----------- _TEXT ----------- @property def _text(self): """Get the _text value.""" _text = np.array([]) for k in self: _text = np.r_[_text, k._text] if _text.size else k._text return _text # ----------- TEXT ----------- @property def text(self): """Get the text value.""" text = np.array([]) for k in self: text = np.r_[text, k.text] if text.size else k.text return text # ----------- VISIBLE ----------- @property def visible(self): """Get the visible value.""" visible = np.array([]) for k in self: visible = np.r_[visible, k.visible] if visible.size else k.visible return visible # ----------- MASK ----------- @property def mask(self): """Get the mask value.""" mask = np.array([]) for k in self: mask = np.r_[mask, k.mask] if mask.size else k.mask return mask # ----------- IS_MASKED ----------- @property def is_masked(self): """Get the is_masked value.""" is_masked = [] for k in self: is_masked.append(k.is_masked) return any(is_masked) # ----------- VISIBLE_AND_NOT_MASKED ----------- @property def visible_and_not_masked(self): """Get the visible_and_not_masked value.""" vnm = np.array([]) for k in self: vnm_obj = k.visible_and_not_masked vnm = np.r_[vnm, vnm_obj] if vnm.size else vnm_obj return vnm # proj_doc = """v : array_like # The vertices of shape (nv, 3) or (nv, 3, 3) if index faced. # radius : float # The radius under which activity is projected on vertices. # contribute: bool | False # Specify if sources contribute on both hemisphere.""" # for k in ['project_modulation', 'project_repartition', 'get_masked_index']: # st = 'SourceObj.%s.__doc__' % k # exec('%s = %s.format(proj_doc)' % (st, st))