Python Block with Vectors

This tutorial describes how the Python Embedded Block can be modified to accept vector inputs and outputs, and how the input_items vector indexing is different between vectors and streams.

The previous tutorial, Creating Your First Block, demonstrates how to create a Python block using the Embedded Python Block. The next tutorial, Python Block Message Passing describes how to send and receive messages using the Embedded Python Block.

Starting the Flowgraph
This tutorial uses vectors, please complete the Streams and Vectors tutorial before moving on.

Add the following blocks to the flowgraph:
 * Signal Source
 * Throttle
 * Stream to Vector
 * Embedded Python Block
 * Vector to Stream
 * QT GUI Time Sink
 * Virtual Sink
 * Virtual Source
 * Variable

Modify the following block properties:
 * Signal Source
 * Output Type: float
 * Frequency: 100
 * Variable
 * Id: vectorLength
 * Value: 16
 * Stream to Vector, Num Items: vectorLength
 * Vector to Stream, Num Items: vectorLength
 * Virtual Sink, Stream Id: sinusoid
 * Virtual Source, Stream Id: sinusoid
 * QT GUI Time Sink
 * Autoscale: Yes
 * Number of Inputs: 2

Connect the blocks according to the following flowgraph:



Accepting Vector Inputs and Outputs
The Embedded Python Block needs to be modified to accept vector inputs, produce vector outputs and change the data type to float. Double-click on the block to edit the source code.

Change example_param in the function definition to vectorSize: def __init__(self, vectorSize=16):

Change the name: name='Max Hold Block',

Define the input and output to be a tuple enveloped by a list: in_sig=[(np.float32,vectorSize)], out_sig=[(np.float32,vectorSize)]

Remove the self.example_param = example_param line.

Remove the multiplication by self.example_param: output_items[0][:] = input_items[0]

The code should now look like the following:



Save the code (CTRL + S). Connect the Max Hold Block to the rest of the flowgraph:



Warning for Vector Length Mismatches
The Embedded Python Block has one quirk that other Out of Tree Modules do not have. Before a flowgraph can be run GRC checks to ensure all of the connected data types and vector sizes match. During this process the default value of vectorSize in the __init__ function, def __init__(self, vectorSize=16):

is used to define the size of the vectors for the input and output,

in_sig=[(np.float32,vectorSize)], out_sig=[(np.float32,vectorSize)]

when determining if the flowgraph is correct. In this case since vectorSize=16, GRC will assume the input and output ports will be vectors with length 16, even if a different parameter is passed in through the block properties! . The following image shows how a vector size of 128 is passed in as a parameter which GRC does not catch as an error, but the flowgraph will crash once it's run:



Traceback (most recent call last): File "/home/username/vectorinput.py", line 250, in    main File "/home/username/vectorinput.py", line 226, in main tb = top_block_cls File "/home/username/vectorinput.py", line 188, in __init__ self.connect((self.blocks_stream_to_vector_0, 0), (self.epy_block_0, 0)) File "/usr/lib/python3/dist-packages/gnuradio/gr/hier_block2.py", line 48, in wrapped func(self, src, src_port, dst, dst_port) File "/usr/lib/python3/dist-packages/gnuradio/gr/hier_block2.py", line 111, in connect self.primitive_connect(*args) File "/usr/lib/python3/dist-packages/gnuradio/gr/runtime_swig.py", line 4531, in primitive_connect return _runtime_swig.top_block_sptr_primitive_connect(self, *args) RuntimeError: itemsize mismatch: stream_to_vector0:0 using 64, Embedded Python Block0:0 using 512

Alternatively, GRC will show an error if the default parameter does not match the vector size of the other blocks. In this case, the default vector length in the code is 128 but the passed-in parameter is 16:



Indexing Vectors
Vectors add an additional dimension, represented as vectorIndex below, onto both input_items and output_items. The input_items and output_items are now three-dimensional arrays: input_items[portIndex][vectorIndex][sampleIndex] output_items[portIndex][vectorIndex][sampleIndex]

Indexing based on the portIndex returns a two-dimensional array of all vectors and samples. Indexing based on portIndex and vectorIndex returns an single-dimensional array of samples. Indexing based on portIndex, vectorIndex and sampleIndex returns a single sample.

A visual example vector indexing is given below:



Creating Max Hold Function
The work function needs to be modified to include the max hold function. Add a loop over all of the vectors in input_items[0]: for vectorIndex in range(len(input_items[0])):

Calculate the max value of the vector: maxValue = np.max(input_items[0][vectorIndex])

Loop over each of the input samples: for sampleIndex in range(len(input_items[0][vectorIndex])):

Assign each output sample maxValue: output_items[0][vectorIndex][sampleIndex] = maxValue

The code should look like the following:



Save the code (CTRL + S). Run the flowgraph. The output will now show a sinusoid and a sinusoid with a max-hold applied every 16 samples:



Multiple Vector Ports
The Max Hold Block will be modified to include a second vector input and output port.

Add the following blocks to the workspace:
 * Noise Source
 * Stream to Vector
 * Vector to Stream
 * Virtual Sink
 * Virtual Source
 * QT GUI Time Sink

Change the following block properties:
 * Noise Source, Output Type: float
 * Stream to Vector, Num Items: vectorLength
 * Vector to Stream, Num Items: vectorLength
 * Virtual Sink, Stream Id: noise
 * Virtual Source, Stream Id: noise
 * QT GUI Time Sink
 * Autoscale: Yes
 * Number of Inputs: 2

Connect the blocks according to the flowgraph:



Edit the code for the Max Hold Block and add a second vector input and output: in_sig=[(np.float32,vectorSize),(np.float32,vectorSize)], out_sig=[(np.float32,vectorSize),(np.float32,vectorSize)]

Save the code. Connect the blocks according to the flowgraph:



The work function has to be modified to perform the max hold function over both input ports now. Include a new, outer loop over all input ports:

for portIndex in range(len(input_items)):

Change all indexing [0] to [portIndex]:

for portIndex in range(len(input_items)): for vectorIndex in range(len(input_items[portIndex])): maxValue = np.max(input_items[portIndex][vectorIndex]) for sampleIndex in range(len(input_items[portIndex][vectorIndex])): output_items[portIndex][vectorIndex][sampleIndex] = maxValue

The code should now look like the following:



Save the code and connect the final ports:



Run the flowgraph. Two max-hold outputs will now be generated, one for the noise source and one for the sinusoid:



The next tutorial, Python Block Message Passing describes how to send and receive messages using the Embedded Python Block.