Episode 2: Routing Signals
Discover how to manage signal flow within your pipelines. This episode covers combining, selecting, splitting, and mixing signals, which are the foundation for building complex, multi-stream processing setups.
The Router node in g.Pype is a flexible tool for managing signal flow in your pipeline. With a single node, you can:
Combine signals from multiple input ports into one output port
Select specific channels from one input port to one output port
Split signals from one input port to multiple output ports
Mix signals from several input ports to several output ports
All these behaviors are controlled by the input_channels and output_channels parameters.
Below, we will explore how to use the Router for each scenario.
The Router node can be configured to combine signals from multiple input sources.
To demonstrate this, create four sources source1 to source4
as shown in the code below. Each source generates a single-channel sinusoid
with frequencies 2, 4, 6, and 8 Hz, respectively. Next, create a Router node
named combiner with input_channels=[[0], [0], [0], [0]] to combine these
four single-channel sources into one multi-channel signal:
1import gpype as gp
2
3if __name__ == "__main__":
4
5 app = gp.MainApp()
6 p = gp.Pipeline()
7
8 source1, source2, source3, source4 = [
9 gp.Generator(channel_count=1,
10 signal_frequency=f,
11 signal_amplitude=25) for f in (2, 4, 6, 8)
12 ]
13
14 combiner = gp.Router(input_channels=[[0], [0], [0], [0]])
15
16 scope = gp.TimeSeriesScope()
Note – input_channels List Notation
The input_channels parameter can be a list of lists:
each entry of the outer list corresponds to an input port,
while each inner list specifies the channel indices to be
taken from that port.
We will explore an alternative dictionary-style notation below.
In this example, the combiner node configured with four input
ports, each taking the first channel of its connected signal. To connect
these input ports to the four sources created above, it is necessary to understand
the port-naming convention in g.Pype nodes.
Note – Router Input Port Naming
The Router node automatically creates its input ports from the
input_channels parameter. These ports are named implicitly as
in1 through inN, where N is the number of input ports.
Note – Default Port Naming
By default, g.Pype nodes with a single input or output port assign the
names in and out to these ports, unless specified otherwise.
Given this convention, the four input ports of the combiner node are named in1
through in4. Connect each of these ports to the corresponding source
as shown below:
16 scope = gp.TimeSeriesScope()
17
18 p.connect(source1, combiner["in1"])
19 p.connect(source2, combiner["in2"])
20 p.connect(source3, combiner["in3"])
21 p.connect(source4, combiner["in4"])
22 p.connect(combiner, scope)
23
24 app.add_widget(scope)
25
26 p.start()
27 app.run() # blocking until window is closed
28 p.stop()
Note – Port Addressing Scheme
In the connect() method of the Pipeline class, ports are addressed using
dictionary-style notation. For example, p.connect(senderNode["out"], receiverNode["in"])
connects the output port out of senderNode to the input port in of receiverNode.
For nodes with only one input and/or one output port, this explicit addressing
scheme is usually omitted. In such cases, the recommended shorthand is
p.connect(senderNode, receiverNode), which connects the single output port of
senderNode to the single input receiverNode.
Run the script and observe how the four individual sources are shown as one multi-channel signal in the scope.
Figure 21: Four different sources combined into one signal.
The Router node can select individual channels from its input and
route them to the output. To demonstrate this, let’s extract the first and last channel
from the combined signal in the previous example. To do this,
add a selector node with parameter output_channels=[[0, 3]].
This configuration routes the first and last channel of the input signal
to the output port. Place the selector node between the combiner
and the scope in the pipeline.
14 combiner = gp.Router(input_channels=[[0], [0], [0], [0]])
15 selector = gp.Router(output_channels=[[0, 3]])
16
17 scope = gp.TimeSeriesScope()
18
19 p.connect(source1, combiner["in1"])
20 p.connect(source2, combiner["in2"])
21 p.connect(source3, combiner["in3"])
22 p.connect(source4, combiner["in4"])
23 p.connect(combiner, selector)
24 p.connect(selector, scope)
Note – Internal Routing Procedure
The Router node first combines the input channels
(as defined by input_channels) into a single internal
multi-channel signal. This signal is then routed to the
output ports according to the output_channels parameter.
Note – output_channels List Notation
The output_channels parameter can be a list of lists:
Each entry of the outer list defines one output port, while
each inner list specifies the channel indices (from the
internal multi-channel signal) to be routed to that port.
We will explore an alternative dictionary-style notation below.
Run the script and observe that only the first and last channels are shown in the scope.
Figure 22: Selected first and last channel from the combined four-channel signal.
The Router node can split a signal by routing selected input channels
to multiple output ports. To demonstrate this, modify the parameter in line 15
of the code above to output_channels=[[0, 3], [2, 1]] and, for clarity,
rename the node to splitter.
This configuration creates two output ports: out1 receives the first and last channel [0, 3] of the input signal,
while out2 receives the third and second channel [2, 1]. To visualize the two data streams simulataneously,
create two scopes scope1 and scope2, and connect them to the two output ports of the
splitter node.
14 combiner = gp.Router(input_channels=[[0], [0], [0], [0]])
15 splitter = gp.Router(output_channels=[[0, 3], [1, 2]])
16
17 scope1 = gp.TimeSeriesScope()
18 scope2 = gp.TimeSeriesScope()
19
20 p.connect(source1, combiner["in1"])
21 p.connect(source2, combiner["in2"])
22 p.connect(source3, combiner["in3"])
23 p.connect(source4, combiner["in4"])
24 p.connect(combiner, splitter)
25 p.connect(splitter["out1"], scope1)
26 p.connect(splitter["out2"], scope2)
Run the script and observe how the four-channel signal is split into two data streams, each shown in its own scope.
Figure 23: Split signal shown in two scopes.
The Router node can mix signals by routing channels from multiple
input ports to multiple output ports. To illustrate this, let’s restore the
original channel order from the previous example. In that example, the
output channels were defined as output_channels=[[0, 3], [2, 1]].
Because the internal, combined signal is indexed sequentially,
the original order is restored via the index sequence 0, 2, 3, 1.
To achieve this, create a new Router node named mixer with parameters
input_channels set to [gp.Router.ALL, gp.Router.ALL] and
output_channels set to [[0, 2], [3, 1]].
Insert the mixer node between the splitter and the two scopes
in the pipeline:
14 combiner = gp.Router(input_channels=[[0], [0], [0], [0]])
15 splitter = gp.Router(output_channels=[[0, 3], [1, 2]])
16 mixer = gp.Router(input_channels=[gp.Router.ALL, gp.Router.ALL],
17 output_channels=[[0, 2], [3, 1]])
18
19 scope1 = gp.TimeSeriesScope()
20 scope2 = gp.TimeSeriesScope()
21 p.connect(source1, combiner["in1"])
22 p.connect(source2, combiner["in2"])
23 p.connect(source3, combiner["in3"])
24 p.connect(source4, combiner["in4"])
25 p.connect(combiner, splitter)
26 p.connect(splitter["out1"], mixer["in1"])
27 p.connect(splitter["out2"], mixer["in2"])
28 p.connect(mixer["out1"], scope1)
29 p.connect(mixer["out2"], scope2)
Note – Router.ALL Selector
For convenience and readability,
the gp.Router.ALL constant can be used to reference
all available channels, either received from a given input port or
routed to a given output port.
Run the script and observe how the original channel order is restored, showing the signals with increasing frequency in their respective scopes.
Figure 24: Restored order of signals.
Great! You now know how to flexibly route signals within a g.Pype pipeline. Next, we’ll begin a project that extracts alpha power from EEG signals. This project will span several upcoming episodes, gradually expanding as new nodes are introduced. Be sure to follow along with the first step below!
In this training project, we will build a g.Pype application step by step to extract alpha-band power from EEG using standard library nodes. First, create three signal sources with the Generator to synthesize a realistic composite test signal:
an alpha source
source_alphaproducing a 10 Hz sinusoid,a modulator
source_modproducing a 0.2 Hz sinusoid that modulates the alpha amplitude,a noise source
source_noiseto mimic background EEG.
To visualize these three sources (and the derived signals you will
create next) in a single TimeSeriesScope, instantiate a
Router (router) to combine them into one multi-channel stream.
1import gpype as gp
2
3if __name__ == "__main__":
4
5 app = gp.MainApp()
6 p = gp.Pipeline()
7
8 source_alpha = gp.Generator(channel_count=1,
9 signal_frequency=10,
10 signal_amplitude=25)
11
12 source_mod = gp.Generator(channel_count=1,
13 signal_frequency=0.2,
14 signal_amplitude=25)
15
16 source_noise = gp.Generator(channel_count=1,
17 noise_amplitude=25)
18
19 router = gp.Router(input_channels={"alpha": [0],
20 "modulator": [0],
21 "noise": [0]})
22
23 scope = gp.TimeSeriesScope()
Note – input_channels / output_channels Dictionary Notation
Both the input_channels and output_channels can also be
specified using a dictionary-style notation. In this form,
each dictionary key corresponds to a port name, and the associated
value is a list of channel indices to be taken from (for input ports)
or routed to (for output ports).
Using the dictionary-style notation for the input_channels parameter,
the router node is created with three input ports alpha, modulator,
and noise. Connect the three sources to these ports.
25 p.connect(source_alpha, router["alpha"])
26 p.connect(source_mod, router["modulator"])
27 p.connect(source_noise, router["noise"])
28 p.connect(router, scope)
29
30 app.add_widget(scope)
31
32 p.start()
33 app.run() # blocking until window is closed
34 p.stop()
Figure 25: Prototype signals for alpha modulation.
Finished! Run the script to see the three sources in the scope.
With your understanding now extended through the alpha power example, we are ready to conclude this episode.
In this episode, you learned how to use the Router node to
combine signals from multiple input ports into one output port,
select specific channels from one input port to one output port,
split signals from one input port to multiple output ports, and
mix signals from several input ports to several output ports.
In addition, we took the first step in our alpha power extraction project, which we will continue to expand in the upcoming episodes.
Proceed to episode 3, where we will apply basic transforms to create a surrogate EEG signal with amplitude-modulated alpha activity.
File s2e2_combiner.py – View file on GitHub
1# --------------------------------------------------------------
2# Example file s2e2_combiner.py
3# For details and usage, see g.Pype Training Season 2, Episode 2
4# --------------------------------------------------------------
5
6import gpype as gp
7
8if __name__ == "__main__":
9
10 # Create main app and pipeline
11 app = gp.MainApp()
12 p = gp.Pipeline()
13
14 # Create 4 sources with different frequencies
15 source1, source2, source3, source4 = [
16 gp.Generator(channel_count=1,
17 signal_frequency=f,
18 signal_amplitude=25) for f in (2, 4, 6, 8)
19 ]
20
21 # Create router to combine signals
22 combiner = gp.Router(input_channels=[[0], [0], [0], [0]])
23
24 # Create scope
25 scope = gp.TimeSeriesScope()
26
27 # Connect nodes
28 p.connect(source1, combiner["in1"])
29 p.connect(source2, combiner["in2"])
30 p.connect(source3, combiner["in3"])
31 p.connect(source4, combiner["in4"])
32 p.connect(combiner, scope)
33
34 # Add widget to main app
35 app.add_widget(scope)
36
37 # Start pipeline and run application
38 p.start()
39 app.run() # blocking until window is closed
40 p.stop()
File s2e2_selector.py – View file on GitHub
1# --------------------------------------------------------------
2# Example file s2e2_selector.py
3# For details and usage, see g.Pype Training Season 2, Episode 2
4# --------------------------------------------------------------
5
6import gpype as gp
7
8if __name__ == "__main__":
9
10 # Create main app and pipeline
11 app = gp.MainApp()
12 p = gp.Pipeline()
13
14 # Create 4 sources with different frequencies
15 source1, source2, source3, source4 = [
16 gp.Generator(channel_count=1,
17 signal_frequency=f,
18 signal_amplitude=25) for f in (2, 4, 6, 8)
19 ]
20
21 # Create router to combine signals
22 combiner = gp.Router(input_channels=[[0], [0], [0], [0]])
23
24 # Create router to select signals
25 selector = gp.Router(output_channels=[[0, 3]])
26
27 # Create scope
28 scope = gp.TimeSeriesScope()
29
30 # Connect nodes
31 p.connect(source1, combiner["in1"])
32 p.connect(source2, combiner["in2"])
33 p.connect(source3, combiner["in3"])
34 p.connect(source4, combiner["in4"])
35 p.connect(combiner, selector)
36 p.connect(selector, scope)
37
38 # Add widget to main app
39 app.add_widget(scope)
40
41 # Start pipeline and run application
42 p.start()
43 app.run() # blocking until window is closed
44 p.stop()
File s2e2_splitter.py – View file on GitHub
1# --------------------------------------------------------------
2# Example file s2e2_splitter.py
3# For details and usage, see g.Pype Training Season 2, Episode 2
4# --------------------------------------------------------------
5
6import gpype as gp
7
8if __name__ == "__main__":
9
10 # Create main app and pipeline
11 app = gp.MainApp()
12 p = gp.Pipeline()
13
14 # Create 4 sources with different frequencies
15 source1, source2, source3, source4 = [
16 gp.Generator(channel_count=1,
17 signal_frequency=f,
18 signal_amplitude=25) for f in (2, 4, 6, 8)
19 ]
20
21 # Create router to combine signals
22 combiner = gp.Router(input_channels=[[0], [0], [0], [0]])
23
24 # Create router to split signals
25 splitter = gp.Router(output_channels=[[0, 3], [1, 2]])
26
27 # Create scopes
28 scope1 = gp.TimeSeriesScope()
29 scope2 = gp.TimeSeriesScope()
30
31 # Connect nodes
32 p.connect(source1, combiner["in1"])
33 p.connect(source2, combiner["in2"])
34 p.connect(source3, combiner["in3"])
35 p.connect(source4, combiner["in4"])
36 p.connect(combiner, splitter)
37 p.connect(splitter["out1"], scope1)
38 p.connect(splitter["out2"], scope2)
39
40 # Add widget to main app
41 app.add_widget(scope1)
42 app.add_widget(scope2)
43
44 # Start pipeline and run application
45 p.start()
46 app.run() # blocking until window is closed
47 p.stop()
File s2e2_mixer.py – View file on GitHub
1# --------------------------------------------------------------
2# Example file s2e2_mixer.py
3# For details and usage, see g.Pype Training Season 2, Episode 2
4# --------------------------------------------------------------
5
6import gpype as gp
7
8if __name__ == "__main__":
9
10 # Create main app and pipeline
11 app = gp.MainApp()
12 p = gp.Pipeline()
13
14 # Create 4 sources with different frequencies
15 source1, source2, source3, source4 = [
16 gp.Generator(channel_count=1,
17 signal_frequency=f,
18 signal_amplitude=25) for f in (2, 4, 6, 8)
19 ]
20
21 # Create router to combine signals
22 combiner = gp.Router(input_channels=[[0], [0], [0], [0]])
23
24 # Create router to split signals
25 splitter = gp.Router(output_channels=[[0, 3], [1, 2]])
26
27 # Create router to mix signals
28 mixer = gp.Router(input_channels=[gp.Router.ALL, gp.Router.ALL],
29 output_channels=[[0, 2], [3, 1]])
30
31 # Create scopes
32 scope1 = gp.TimeSeriesScope()
33 scope2 = gp.TimeSeriesScope()
34
35 # Connect nodes
36 p.connect(source1, combiner["in1"])
37 p.connect(source2, combiner["in2"])
38 p.connect(source3, combiner["in3"])
39 p.connect(source4, combiner["in4"])
40 p.connect(combiner, splitter)
41 p.connect(splitter["out1"], mixer["in1"])
42 p.connect(splitter["out2"], mixer["in2"])
43 p.connect(mixer["out1"], scope1)
44 p.connect(mixer["out2"], scope2)
45
46 # Add widget to main app
47 app.add_widget(scope1)
48 app.add_widget(scope2)
49
50 # Start pipeline and run application
51 p.start()
52 app.run() # blocking until window is closed
53 p.stop()
File s2e2_alpha1.py – View file on GitHub
1# --------------------------------------------------------------
2# Example file s2e2_alpha1.py
3# For details and usage, see g.Pype Training Season 2, Episode 2
4# --------------------------------------------------------------
5
6import gpype as gp
7
8if __name__ == "__main__":
9
10 # Create main app and pipeline
11 app = gp.MainApp()
12 p = gp.Pipeline()
13
14 # Create alpha source
15 source_alpha = gp.Generator(channel_count=1,
16 signal_frequency=10,
17 signal_amplitude=25)
18
19 source_mod = gp.Generator(channel_count=1,
20 signal_frequency=0.2,
21 signal_amplitude=25)
22
23 source_noise = gp.Generator(channel_count=1,
24 noise_amplitude=25)
25
26 # Create router to combine signals
27 router = gp.Router(input_channels={"alpha": [0],
28 "modulator": [0],
29 "noise": [0]})
30
31 # Create scope
32 scope = gp.TimeSeriesScope()
33
34 # Connect nodes
35 p.connect(source_alpha, router["alpha"])
36 p.connect(source_mod, router["modulator"])
37 p.connect(source_noise, router["noise"])
38 p.connect(router, scope)
39
40 # Add widget to main app
41 app.add_widget(scope)
42
43 # Start pipeline and run application
44 p.start()
45 app.run() # blocking until window is closed
46 p.stop()