Episode 9: Naming Your Channels
Name the channels once, near the source, with a ChannelLabeler, so every scope, recording and node after it knows which channel is which.
Most sources carry no channel names at all. That is honest, a generator does not know where you put the electrodes, but it means nothing downstream can say more than “channel 3”. A CsvWriter handed a nameless stream invents Ch01, Ch02, … for its header, and the recording is stuck with those names afterwards.
Eight channels from a Generator, and two Collector nodes: one straight from the source, one after the labeler, so the names can be compared.
19 p = gp.Pipeline()
20 source = gp.Generator(sampling_rate=SAMPLING_RATE, channel_count=8,
21 signal_amplitude=1.0)
22 before = gp.Collector(name="before")
23 after = gp.Collector(name="after")
Labels can be passed directly as labels=["Fz", "C3", ...], or taken from a Montage. The montage is worth it when the names are electrodes: Montage.standard_1020 refuses a name that is not a real 10-20 position, so a typo is caught here rather than outliving the experiment inside a recording.
25 # A Montage is a list of electrode names, with an optional reference.
26 # standard_1020 refuses a name that is not a real 10-20 position
27 montage = gp.Montage.standard_1020(ELECTRODES, reference="Cz")
28 labeler = gp.ChannelLabeler(montage=montage, name="labeler")
29
30 p.connect(source, before)
31 p.connect(source, labeler)
32 p.connect(labeler, after)
Run the script. Before the labeler there are no names; after it, the names travel with the data, onto a scope’s axis, into a recording’s header, and into any node that selects a channel by name instead of by number. The last line is the typo, refused:
before ChannelLabeler: []
after ChannelLabeler: ['Fz', 'C3', 'Cz', 'C4', 'P3', 'Pz', 'P4', 'Oz']
refused: not 10-20 electrode names: ['Zz']. Known names: ['A1', 'A2', ...]
Note which names the montage knows: the classic 10-20 positions. Denser 10-10 names such as PO7 are not among them, so an electrode set that uses those takes the plain labels= route.
In this episode, you learned how to:
name channels once with ChannelLabeler
take the names from a Montage that checks them
see where names go and where none come from
Proceed to Episode 10, where a Reference decides what zero means.
Nodes in this episode: Generator, ChannelLabeler, Collector
File s2e9_naming_your_channels.py – View file on GitHub
1"""Season 2, Episode 9: a ChannelLabeler names the channels at the source."""
2
3import time
4
5import gpype as gp
6
7SAMPLING_RATE = 250
8ELECTRODES = ["Fz", "C3", "Cz", "C4", "P3", "Pz", "P4", "Oz"]
9
10
11def labels_of(collector):
12 """The channel names a collector saw, or an empty list."""
13 result = collector.result
14 return list(result.labels) if result and result.labels else []
15
16
17if __name__ == "__main__":
18
19 p = gp.Pipeline()
20 source = gp.Generator(sampling_rate=SAMPLING_RATE, channel_count=8,
21 signal_amplitude=1.0)
22 before = gp.Collector(name="before")
23 after = gp.Collector(name="after")
24
25 # A Montage is a list of electrode names, with an optional reference.
26 # standard_1020 refuses a name that is not a real 10-20 position
27 montage = gp.Montage.standard_1020(ELECTRODES, reference="Cz")
28 labeler = gp.ChannelLabeler(montage=montage, name="labeler")
29
30 p.connect(source, before)
31 p.connect(source, labeler)
32 p.connect(labeler, after)
33
34 p.start()
35 time.sleep(0.5)
36 p.stop()
37 p.close()
38
39 print("before ChannelLabeler:", labels_of(before))
40 print("after ChannelLabeler:", labels_of(after))
41
42 # A typo is caught here, not in a recording that outlives the experiment
43 try:
44 gp.Montage.standard_1020(["Fz", "Cz", "Zz"])
45 except ValueError as error:
46 print("refused:", error)