Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 29 additions & 61 deletions doc/source/core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,17 @@ Data objects
These objects directly represent data as arrays of numerical values with
associated metadata (units, sampling frequency, etc.).

:py:class:`AnalogSignal`:
A regular sampling of a continuous, analog signal.

:py:class:`AnalogSignalArray`:
A regular sampling of a multichannel continuous analog signal. This representation (as a 2D NumPy array) may be more efficient for subsequent analysis than the equivalent list of individual :py:class:`AnalogSignal` objects.

:py:class:`Spike`:
One action potential characterized by its time and waveform.
A regular sampling of a single- or multi-channel continuous analog signal.

:py:class:`SpikeTrain`:
A set of action potentials (spikes) emitted by the same unit in a period of time (with optional waveforms).

:py:class:`Event` and :py:class:`EventArray`:
A time point representng an event in the data, or an array of such time points.
:py:class:`Event`:
An array of time points representing one or more events in the data.

:py:class:`Epoch` and :py:class:`EpochArray`:
An interval of time representing a period of time in the data, or an array of such intervals.
:py:class:`Epoch`:
An array of time intervals representing one or more periods of time in the data.

Container objects
-----------------
Expand All @@ -43,7 +37,7 @@ There is a simple hierarchy of containers:
A container for heterogeneous discrete or continous data sharing a common clock (time basis) but not necessarily the same sampling rate, start time or end time. A :py:class:`Segment` can be considered as equivalent to a "trial", "episode", "run", "recording", etc., depending on the experimental context. May contain any of the data objects.

:py:class:`Block`:
The top-level container gathering all of the data, discrete and continuous, for a given recording session. Contains :class:`Segment` and :class:`RecordingChannelGroup` objects.
The top-level container gathering all of the data, discrete and continuous, for a given recording session. Contains :class:`Segment`, :class:`Unit` and :class:`RecordingChannelGroup` objects.

Grouping objects
----------------
Expand All @@ -53,26 +47,20 @@ were recorded on which electrodes, which spike trains were obtained from which
membrane potential signals, etc. They contain references to data objects that
cut across the simple container hierarchy.

:py:class:`RecordingChannel`:
Links :py:class:`AnalogSignal`, :py:class:`SpikeTrain`
objects that come from the same logical and/or physical channel inside a :py:class:`Block`, possibly across several :py:class:`Segment` objects.

:py:class:`RecordingChannelGroup`:
A group for associated :py:class:`RecordingChannel` objects. This has several possible uses:
* for linking several :py:class:`AnalogSignalArray` objects across several :py:class:`Segment` objects inside a :py:class:`Block`.
* for multielectrode arrays, where spikes may be recorded on more than one recording channel,
and so the :py:class:`RecordingChannelGroup` can be used to associate each :py:class:`Unit` with the
group of recording channels from which it was calculated.
* for grouping several :py:class:`RecordingChannel` objects. There are many use cases for this.
For instance, for intracellular recording, it is common to record both membrane potentials and currents at the same time,
so each :py:class:`RecordingChannelGroup` may correspond to the particular property that is being recorded. For multielectrode arrays,
:py:class:`RecordingChannelGroup` is used to gather all :py:class:`RecordingChannel` objects of the same array.

A set of indices into :py:class:`AnalogSignal` objects, representing logical and/or
physical recording channels. This has several uses:
* for linking :py:class:`AnalogSignal` objects recorded from the same (multi)electrode
across several :py:class:`Segment` objects.
* for spike sorting of extracellular signals, where spikes may be recorded on more than one
recording channel, and the :py:class:`RecordingChannelGroup` can be used to associate each
:py:class:`Unit` with the group of recording channels from which it was calculated.

:py:class:`Unit`:
A Unit gathers all the :class:`SpikeTrain` objects within a common :class:`Block`, possibly across several
Segments, that have been emitted by the same cell.
A :class:`Unit` is linked to :class:`RecordingChannelGroup` objects from which it was detected.
This replaces the :class:`Neuron` class in the previous version of Neo (v0.1).
A Unit gathers all the :class:`SpikeTrain` objects within a common :class:`Block`, possibly
across several Segments, that have been emitted by the same cell.
A :class:`Unit` is linked to the :class:`RecordingChannelGroup` object from which it was detected.

.. image:: images/base_schematic.png
:height: 500 px
Expand Down Expand Up @@ -129,61 +117,40 @@ Here is an example showing these relationships in use::
print(seg.block) # parent access


On the :ref:`neo_diagram` you can also see a magenta line reflecting the *many-to-many* relationship between :py:class:`RecordingChannel` and :py:class:`RecordingChannelGroup`. This means that each group can contain multiple channels, and each channel can belong to multiple groups.

In some cases, a one-to-many relationship is sufficient. Here is a simple example with tetrodes, in which each tetrode has its own group.::

from neo import *
from neo import Block, RecordingChannelGroup
bl = Block()

# creating individual channel
all_rc= [ ]
for i in range(16):
rc = RecordingChannel( index= i, name ='rc %d' %i)
all_rc.append(rc)

# the four tetrodes
for i in range(4):
rcg = RecordingChannelGroup( name = 'Tetrode %d' % i )
for rc in all_rc[i*4:(i+1)*4]:
rcg.recordingchannels.append(rc)
rc.recordingchannelgroups.append(rcg)
rcg = RecordingChannelGroup(name = 'Tetrode %d' % i,
channel_indexes=[0, 1, 2, 3])
bl.recordingchannelgroups.append(rcg)

# now we load the data and associate it with the created channels
# ...

Now consider a more complex example: a 1x4 silicon probe, with a neuron on channels 0,1,2 and another neuron on channels 1,2,3. We create a group for each neuron to hold the `Unit` object associated with this spikesorting group. Each group also contains the channels on which that neuron spiked. The relationship is many-to-many because channels 1 and 2 occur in multiple groups.::

from neo import *
bl = Block(name='probe data')

# create individual channels
all_rc = []
for i in range(4):
rc = RecordingChannel(index=i, name='channel %d' % i)
all_rc.append(rc)

# one group for each neuron
rcg0 = RecordingChannelGroup(name='Group 0', index=0)
for i in [0, 1, 2]:
rcg1.recordingchannels.append(all_rc[i])
rc[i].recordingchannelgroups.append(rcg0)
rcg0 = RecordingChannelGroup(name='Group 0',
channel_indexes=[0, 1, 2])
bl.recordingchannelgroups.append(rcg0)

rcg1 = RecordingChannelGroup(name='Group 1', index=1)
for i in [1, 2, 3]:
rcg1.recordingchannels.append(all_rc[i])
rc[i].recordingchannelgroups.append(rcg1)
rcg1 = RecordingChannelGroup(name='Group 1',
channel_indexes=[1, 2, 3])
bl.recordingchannelgroups.append(rcg1)

# now we add the spiketrain from Unit 0 to rcg0
# and add the spiketrain from Unit 1 to rcg1
# ...

Note that because neurons are sorted from groups of channels in this situation, it is natural that the :py:class:`RecordingChannelGroup` contains the :py:class:`Unit` object. That unit then contains its spiketrains.

There are some shortcuts for IO writers to automatically create this structure based on 'channel_indexes' entries in the annotations for each spiketrain.
Note that because neurons are sorted from groups of channels in this situation, it is natural that the :py:class:`RecordingChannelGroup` contains a reference to the :py:class:`Unit` object.
That unit then contains references to its spiketrains. Also note that recording channels can be
identified by names/labels as well as, or instead of, integer indices.


See :doc:`usecases` for more examples of how the different objects may be used.
Expand Down Expand Up @@ -216,7 +183,8 @@ For more details, see the :doc:`api_reference`.
Inheritance
===========

Some Neo objects (:py:class:`AnalogSignal`, :py:class:`SpikeTrain`, :py:class:`AnalogSignalArray`) inherit from :py:class:`Quantity`, which in turn inherits from NumPy :py:class:`ndarray`. This means that a Neo :py:class:`AnalogSignal` actually is also a :py:class:`Quantity` and an array, giving you access to all of the methods available for those objects.
Some Neo objects (:py:class:`AnalogSignal`, :py:class:`SpikeTrain`) inherit from :py:class:`Quantity`,
which in turn inherits from NumPy :py:class:`ndarray`. This means that a Neo :py:class:`AnalogSignal` actually is also a :py:class:`Quantity` and an array, giving you access to all of the methods available for those objects.

For example, you can pass a :py:class:`SpikeTrain` directly to the :py:func:`numpy.histogram` function, or an :py:class:`AnalogSignal` directly to the :py:func:`numpy.std` function.

Expand Down
12 changes: 5 additions & 7 deletions doc/source/images/generate_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ def generate_diagram(filename, rect_pos, rect_width, figsize):
for name, pos in rect_pos.items():
htotal = all_h[name]
obj = objs[name]
allrelationship = (getattr(obj, '_child_containers', []) +
getattr(obj, '_multi_parent_containers', []))
allrelationship = (list(getattr(obj, '_child_containers', [])) +
list(getattr(obj, '_multi_parent_containers', [])))

rect = Rectangle(pos, rect_width, htotal,
facecolor='w', edgecolor='k', linewidth=2.)
Expand All @@ -125,8 +125,8 @@ def generate_diagram(filename, rect_pos, rect_width, figsize):
ax.add_patch(rect)

# multi relationship
relationship = (getattr(obj, '_multi_child_objects', []) +
getattr(obj, '_multi_parent_containers', []))
relationship = (list(getattr(obj, '_multi_child_objects', [])) +
list(getattr(obj, '_multi_parent_containers', [])))
pos2 = (pos[1]+htotal - line_heigth*(1.5+len(relationship)) -
rect_height)
rect_height = len(relationship)*line_heigth
Expand Down Expand Up @@ -214,15 +214,13 @@ def generate_diagram_simple():
'Epoch': (.5+rw*bf*4, .2),

'RecordingChannelGroup': (.5+rw*bf*.8, 8.5),
'RecordingChannel': (.5+rw*bf*1.2, 5.5),

'Unit': (.5+rw*bf*2., 9.5),

'SpikeTrain': (.5+rw*bf*3, 9.5),

'IrregularlySampledSignal': (.5+rw*bf*3, 4.9),
'AnalogSignal': (.5+rw*bf*3, 2.7),
'AnalogSignalArray': (.5+rw*bf*3, .5),
'AnalogSignal': (.5+rw*bf*3, .5),
}
generate_diagram('simple_generated_diagram.svg',
rect_pos, rect_width, figsize)
Expand Down
Binary file modified doc/source/images/simple_generated_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading