Skip to content

Commit 268036e

Browse files
Merge pull request #817 from apdavison/view-group
Replace ChannelIndex and Unit by View and Group
2 parents 44ad955 + 1ad87a8 commit 268036e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+6146
-9354
lines changed

doc/source/core.rst

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ There is a simple hierarchy of containers:
3838
May contain any of the data objects.
3939
* :py:class:`Block`: The top-level container gathering all of the data, discrete and continuous,
4040
for a given recording session.
41-
Contains :class:`Segment`, :class:`Unit` and :class:`ChannelIndex` objects.
41+
Contains :class:`Segment` and :class:`Group` objects.
4242

4343

4444
Grouping/linking objects
@@ -49,18 +49,16 @@ were recorded on which electrodes, which spike trains were obtained from which
4949
membrane potential signals, etc. They contain references to data objects that
5050
cut across the simple container hierarchy.
5151

52-
* :py:class:`ChannelIndex`: A set of indices into :py:class:`AnalogSignal` objects,
53-
representing logical and/or physical recording channels. This has two uses:
52+
* :py:class:`ChannelView`: A set of indices into :py:class:`AnalogSignal` objects,
53+
representing logical and/or physical recording channels.
54+
For spike sorting of extracellular signals, where spikes may be recorded on more than one
55+
recording channel, the :py:class:`ChannelView` can be used to reference the group of recording channels
56+
from which the spikes were obtained.
5457

55-
1. for linking :py:class:`AnalogSignal` objects recorded from the same (multi)electrode
56-
across several :py:class:`Segment`\s.
57-
2. for spike sorting of extracellular signals, where spikes may be recorded on more than one
58-
recording channel, and the :py:class:`ChannelIndex` can be used to associate each
59-
:py:class:`Unit` with the group of recording channels from which it was obtained.
60-
61-
* :py:class:`Unit`: links the :class:`SpikeTrain` objects within a :class:`Block`,
62-
possibly across multiple Segments, that were emitted by the same cell.
63-
A :class:`Unit` is linked to the :class:`ChannelIndex` object from which the spikes were detected.
58+
* :py:class:`Group`: Can contain any of the data objects, views, or other groups,
59+
outside the hierarchy of the segment and block containers.
60+
A common use is to link the :class:`SpikeTrain` objects within a :class:`Block`,
61+
possibly across multiple Segments, that were emitted by the same neuron.
6462

6563
* :py:class:`CircularRegionOfInterest`, :py:class:`RectangularRegionOfInterest` and :py:class:`PolygonRegionOfInterest`
6664
are three subclasses that link :class:`ImageSequence` objects to signals (:class:`AnalogSignal` objects)
@@ -105,14 +103,14 @@ In general, an object can access its children with an attribute *childname+s* in
105103
* :attr:`Block.segments`
106104
* :attr:`Segments.analogsignals`
107105
* :attr:`Segments.spiketrains`
108-
* :attr:`Block.channel_indexes`
106+
* :attr:`Block.groups`
109107

110108
These relationships are bi-directional, i.e. a child object can access its parent:
111109

112110
* :attr:`Segment.block`
113111
* :attr:`AnalogSignal.segment`
114112
* :attr:`SpikeTrain.segment`
115-
* :attr:`ChannelIndex.block`
113+
* :attr:`Group.block`
116114

117115
Here is an example showing these relationships in use::
118116

@@ -134,38 +132,39 @@ Here is an example showing these relationships in use::
134132

135133
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.::
136134

137-
from neo import Block, ChannelIndex
135+
from neo import Block, Group
138136
bl = Block()
139137

140138
# the four tetrodes
141139
for i in range(4):
142-
chx = ChannelIndex(name='Tetrode %d' % i,
143-
index=[0, 1, 2, 3])
144-
bl.channelindexes.append(chx)
140+
group = Group(name='Tetrode %d' % i)
141+
bl.groups.append(group)
145142

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

149-
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 :class:`Unit` object associated with this spike sorting 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.::
146+
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.
147+
We create a group for each neuron to hold the spiketrains for each spike sorting group together with
148+
the channels on which that neuron spiked::
150149

151150
bl = Block(name='probe data')
152151

153152
# one group for each neuron
154-
chx0 = ChannelIndex(name='Group 0',
155-
index=[0, 1, 2])
156-
bl.channelindexes.append(chx0)
153+
view0 = ChannelView(recorded_signals, index=[0, 1, 2])
154+
unit0 = Group(view0, name='Group 0')
155+
bl.groups.append(unit0)
157156

158-
chx1 = ChannelIndex(name='Group 1',
159-
index=[1, 2, 3])
160-
bl.channelindexes.append(chx1)
157+
view1 = ChannelView(recorded_signals, index=[1, 2, 3])
158+
unit1 = Group(view1, name='Group 1')
159+
bl.groups.append(unit1)
161160

162-
# now we add the spiketrain from Unit 0 to chx0
163-
# and add the spiketrain from Unit 1 to chx1
161+
# now we add the spiketrains from Unit 0 to unit0
162+
# and add the spiketrains from Unit 1 to unit1
164163
# ...
165164

166-
Note that because neurons are sorted from groups of channels in this situation, it is natural that the :py:class:`ChannelIndex` contains a reference to the :py:class:`Unit` object.
167-
That unit then contains references to its spiketrains. Also note that recording channels can be
168-
identified by names/labels as well as, or instead of, integer indices.
165+
166+
Now each putative neuron is represented by a :class:`Group` containing the spiktrains of that neuron
167+
and a view of the signal selecting only those channels from which the spikes were obtained.
169168

170169

171170
See :doc:`usecases` for more examples of how the different objects may be used.

doc/source/grouping.rst

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
*************************
2+
Grouping and linking data
3+
*************************
4+
5+
6+
... to do
7+
8+
9+
10+
11+
Migrating from ChannelIndex/Unit to ChannelView/Group
12+
==============================================
13+
14+
15+
Examples
16+
--------
17+
18+
A simple example with two tetrodes. Here the :class:`ChannelIndex` was not being
19+
used for grouping, simply to associate a name with each channel.
20+
21+
Using :class:`ChannelIndex`::
22+
23+
import numpy as np
24+
from quantities import kHz, mV
25+
from neo import Block, Segment, ChannelIndex, AnalogSignal
26+
27+
block = Block()
28+
segment = Segment()
29+
segment.block = block
30+
block.segments.append(segment)
31+
32+
for i in (0, 1):
33+
signal = AnalogSignal(np.random.rand(1000, 4) * mV,
34+
sampling_rate=1 * kHz,)
35+
segment.analogsignals.append(signal)
36+
chx = ChannelIndex(name=f"Tetrode #{i + 1}",
37+
index=[0, 1, 2, 3],
38+
channel_names=["A", "B", "C", "D"])
39+
chx.analogsignals.append(signal)
40+
block.channel_indexes.append(chx)
41+
42+
Using array annotations, we annotate the channels of the :class:`AnalogSignal` directly::
43+
44+
import numpy as np
45+
from quantities import kHz, mV
46+
from neo import Block, Segment, AnalogSignal
47+
48+
block = Block()
49+
segment = Segment()
50+
segment.block = block
51+
block.segments.append(segment)
52+
53+
for i in (0, 1):
54+
signal = AnalogSignal(np.random.rand(1000, 4) * mV,
55+
sampling_rate=1 * kHz,
56+
channel_names=["A", "B", "C", "D"])
57+
segment.analogsignals.append(signal)
58+
59+
60+
Now a more complex example: a 1x4 silicon probe, with a neuron on channels 0,1,2 and another neuron on channels 1,2,3.
61+
We create a :class:`ChannelIndex` for each neuron to hold the :class:`Unit` object associated with this spike sorting group.
62+
Each :class:`ChannelIndex` also contains the list of channels on which that neuron spiked.
63+
64+
::
65+
66+
import numpy as np
67+
from quantities import ms, mV, kHz
68+
from neo import Block, Segment, ChannelIndex, Unit, SpikeTrain, AnalogSignal
69+
70+
block = Block(name="probe data")
71+
segment = Segment()
72+
segment.block = block
73+
block.segments.append(segment)
74+
75+
# create 4-channel AnalogSignal with dummy data
76+
signal = AnalogSignal(np.random.rand(1000, 4) * mV,
77+
sampling_rate=10 * kHz)
78+
# create spike trains with dummy data
79+
# we will pretend the spikes have been extracted from the dummy signal
80+
spiketrains = [
81+
SpikeTrain(np.arange(5, 100) * ms, t_stop=100 * ms),
82+
SpikeTrain(np.arange(7, 100) * ms, t_stop=100 * ms)
83+
]
84+
segment.analogsignals.append(signal)
85+
segment.spiketrains.extend(spiketrains)
86+
# assign each spiketrain to a neuron (Unit)
87+
units = []
88+
for i, spiketrain in enumerate(spiketrains):
89+
unit = Unit(name=f"Neuron #{i + 1}")
90+
unit.spiketrains.append(spiketrain)
91+
units.append(unit)
92+
93+
# create a ChannelIndex for each unit, to show which channels the spikes come from
94+
chx0 = ChannelIndex(name="Channel Group 1", index=[0, 1, 2])
95+
chx0.units.append(units[0])
96+
chx0.analogsignals.append(signal)
97+
units[0].channel_index = chx0
98+
chx1 = ChannelIndex(name="Channel Group 2", index=[1, 2, 3])
99+
chx1.units.append(units[1])
100+
chx1.analogsignals.append(signal)
101+
units[1].channel_index = chx1
102+
103+
block.channel_indexes.extend((chx0, chx1))
104+
105+
106+
Using :class:`ChannelView` and :class`Group`::
107+
108+
import numpy as np
109+
from quantities import ms, mV, kHz
110+
from neo import Block, Segment, ChannelView, Group, SpikeTrain, AnalogSignal
111+
112+
block = Block(name="probe data")
113+
segment = Segment()
114+
segment.block = block
115+
block.segments.append(segment)
116+
117+
# create 4-channel AnalogSignal with dummy data
118+
signal = AnalogSignal(np.random.rand(1000, 4) * mV,
119+
sampling_rate=10 * kHz)
120+
# create spike trains with dummy data
121+
# we will pretend the spikes have been extracted from the dummy signal
122+
spiketrains = [
123+
SpikeTrain(np.arange(5, 100) * ms, t_stop=100 * ms),
124+
SpikeTrain(np.arange(7, 100) * ms, t_stop=100 * ms)
125+
]
126+
segment.analogsignals.append(signal)
127+
segment.spiketrains.extend(spiketrains)
128+
# assign each spiketrain to a neuron (now using Group)
129+
units = []
130+
for i, spiketrain in enumerate(spiketrains):
131+
unit = Group(spiketrain, name=f"Neuron #{i + 1}")
132+
units.append(unit)
133+
134+
# create a ChannelView of the signal for each unit, to show which channels the spikes come from
135+
# and add it to the relevant Group
136+
view0 = ChannelView(signal, index=[0, 1, 2], name="Channel Group 1")
137+
units[0].add(view0)
138+
view1 = ChannelView(signal, index=[1, 2, 3], name="Channel Group 2")
139+
units[1].add(view1)
140+
141+
block.groups.extend(units)
142+
143+
144+
Now each putative neuron is represented by a :class:`Group` containing the spiktrains of that neuron
145+
and a view of the signal selecting only those channels from which the spikes were obtained.
6.63 KB
Loading

0 commit comments

Comments
 (0)