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.

A nameless source

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")
Name them

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)
Result

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.

Summary

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)