""" This module implements :class:`ChannelView`, which represents a subset of the channels in an :class:`AnalogSignal` or :class:`IrregularlySampledSignal`. It replaces the indexing function of the former :class:`ChannelIndex`. """ import numpy as np from .baseneo import BaseNeo from .basesignal import BaseSignal from .dataobject import ArrayDict class ChannelView(BaseNeo): """ A tool for indexing a subset of the channels within an :class:`AnalogSignal` or :class:`IrregularlySampledSignal`; *Required attributes/properties*: :obj: (AnalogSignal or IrregularlySampledSignal) The signal being indexed. :index: (list/1D-array) boolean or integer mask to select the channels of interest. *Recommended attributes/properties*: :name: (str) A label for the view. :description: (str) Text description. :file_origin: (str) Filesystem path or URL of the original data file. :array_annotations: (dict) Dict mapping strings to numpy arrays containing annotations for all data points Note: Any other additional arguments are assumed to be user-specific metadata and stored in :attr:`annotations`. """ _single_parent_objects = ('Segment',) _single_parent_attrs = ('segment',) _necessary_attrs = ( ('index', np.ndarray, 1, np.dtype('i')), ('obj', ('AnalogSignal', 'IrregularlySampledSignal'), 1) ) # "mask" would be an alternative name, proposing "index" for # backwards-compatibility with ChannelIndex def __init__(self, obj, index, name=None, description=None, file_origin=None, array_annotations=None, **annotations): super().__init__(name=name, description=description, file_origin=file_origin, **annotations) if not (isinstance(obj, BaseSignal) or ( hasattr(obj, "proxy_for") and issubclass(obj.proxy_for, BaseSignal))): raise ValueError("Can only take a ChannelView of an AnalogSignal " "or an IrregularlySampledSignal") self.obj = obj # check type and dtype of index and convert index to a common form # (accept list or array of bool or int, convert to int array) self.index = np.array(index) if len(self.index.shape) != 1: raise ValueError("index must be a 1D array") if self.index.dtype == np.bool: # convert boolean mask to integer index if self.index.size != self.obj.shape[-1]: raise ValueError("index size does not match number of channels in signal") self.index, = np.nonzero(self.index) # allow any type of integer representation elif self.index.dtype.char not in np.typecodes['AllInteger']: raise ValueError("index must be of a list or array of data type boolean or integer") if not hasattr(self, 'array_annotations') or not self.array_annotations: self.array_annotations = ArrayDict(self._get_arr_ann_length()) if array_annotations is not None: self.array_annotate(**array_annotations) @property def shape(self): return (self.obj.shape[0], self.index.size) def _get_arr_ann_length(self): return self.shape[-1] def array_annotate(self, **array_annotations): self.array_annotations.update(array_annotations) def resolve(self): """ Return a copy of the underlying object containing just the subset of channels defined by the index. """ return self.obj[:, self.index]