# -*- coding: utf-8 -*- ''' This module defines :class:`ChannelIndex`, a container for multiple data channels. :class:`ChannelIndex` derives from :class:`Container`, from :module:`neo.core.container`. ''' # needed for Python 3 compatibility from __future__ import absolute_import, division, print_function import numpy as np import quantities as pq from neo.core.container import Container class ChannelIndex(Container): ''' A container for indexing/grouping data channels. This container has several purposes: * Grouping all :class:`AnalogSignal`\s inside a :class:`Block` across :class:`Segment`\s; * Indexing a subset of the channels within an :class:`AnalogSignal`; * Container of :class:`Unit`\s. A neuron discharge (:class:`Unit`) can be seen by several electrodes (e.g. 4 for tetrodes). *Usage 1* multi :class:`Segment` recording with 2 electrode arrays:: >>> from neo.core import (Block, Segment, ChannelIndex, ... AnalogSignal) >>> from quantities import nA, kHz >>> import numpy as np >>> >>> # create a Block with 3 Segment and 2 ChannelIndex objects ... blk = Block() >>> for ind in range(3): ... seg = Segment(name='segment %d' % ind, index=ind) ... blk.segments.append(seg) ... >>> for ind in range(2): ... chx = ChannelIndex(name='Array probe %d' % ind, ... index=np.arange(64)) ... blk.channel_indexes.append(chx) ... >>> # Populate the Block with AnalogSignal objects ... for seg in blk.segments: ... for chx in blk.channel_indexes: ... a = AnalogSignal(np.random.randn(10000, 64)*nA, ... sampling_rate=10*kHz) ... chx.analogsignals.append(a) ... seg.analogsignals.append(a) *Usage 2* grouping channels:: >>> from neo.core import Block, ChannelIndex >>> import numpy as np >>> from quantities import mV, kHz >>> >>> # Create a Block ... blk = Block() >>> blk.segments.append(Segment()) >>> >>> # Create a signal with 8 channels ... sig = AnalogSignal(np.random.randn(1000, 8)*mV, sampling_rate=10*kHz) ... blk.segments[0].append(sig) ... >>> # Create a new ChannelIndex which groups three channels from the signal ... chx = ChannelIndex(channel_names=np.array(['ch1', 'ch4', 'ch6']), ... index=np.array([0, 3, 5]) >>> chx.analogsignals.append(sig) >>> blk.channel_indexes.append(chx) *Usage 3* dealing with :class:`Unit` objects:: >>> from neo.core import Block, ChannelIndex, Unit >>> >>> # Create a Block >>> blk = Block() >>> >>> # Create a new ChannelIndex and add it to the Block >>> chx = ChannelIndex(name='octotrode A') >>> blk.channel_indexes.append(chx) >>> >>> # create several Unit objects and add them to the >>> # ChannelIndex ... for ind in range(5): ... unit = Unit(name = 'unit %d' % ind, ... description='after a long and hard spike sorting') ... chx.units.append(unit) *Required attributes/properties*: :channel_indexes: (numpy.array 1D dtype='i') Index of each channel in the attached signals. *Recommended attributes/properties*: :name: (str) A label for the dataset. :description: (str) Text description. :file_origin: (str) Filesystem path or URL of the original data file. :channel_names: (numpy.array 1D dtype='S') Names for each recording channel. :coordinates: (quantity array 2D (x, y, z)) Physical or logical coordinates of all channels. Note: Any other additional arguments are assumed to be user-specific metadata and stored in :attr:`annotations`. *Container of*: :class:`AnalogSignal` :class:`IrregularlySampledSignal` :class:`Unit` ''' _container_child_objects = ('Unit',) _data_child_objects = ('AnalogSignal', 'IrregularlySampledSignal') _single_parent_objects = ('Block',) _necessary_attrs = (('index', np.ndarray, 1, np.dtype('i')),) _recommended_attrs = ((('channel_names', np.ndarray, 1, np.dtype('S')), ('channel_ids', np.ndarray, 1, np.dtype('i')), ('coordinates', pq.Quantity, 2)) + Container._recommended_attrs) def __init__(self, index, channel_names=None, channel_ids=None, name=None, description=None, file_origin=None, coordinates=None, **annotations): ''' Initialize a new :class:`ChannelIndex` instance. ''' # Inherited initialization # Sets universally recommended attributes, and places all others # in annotations super(ChannelIndex, self).__init__(name=name, description=description, file_origin=file_origin, **annotations) # Defaults if channel_names is None: channel_names = np.array([], dtype='S') if channel_ids is None: channel_ids = np.array([], dtype='i') # Store recommended attributes self.channel_names = np.array(channel_names) self.channel_ids = np.array(channel_ids) self.index = np.array(index) self.coordinates = coordinates def __getitem__(self, i): ''' Get the item or slice :attr:`i`. ''' index = self.index.__getitem__(i) if self.channel_names.size > 0: channel_names = self.channel_names[index] if not channel_names.shape: channel_names = [channel_names] else: channel_names = None if self.channel_ids.size > 0: channel_ids = self.channel_ids[index] if not channel_ids.shape: channel_ids = [channel_ids] else: channel_ids = None obj = ChannelIndex(index=np.arange(index.size), channel_names=channel_names, channel_ids=channel_ids) obj.block = self.block return obj