Python Block with Vectors: Difference between revisions

From GNU Radio
Jump to navigation Jump to search
 
(43 intermediate revisions by 6 users not shown)
Line 1: Line 1:
{{TutorialNavigation}}
<div style="float:right">
 
{{Template:BeginnerTutorials}}
</div>
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.
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.


Line 7: Line 8:
== Starting the Flowgraph ==
== Starting the Flowgraph ==


This tutorial uses vectors, please complete the [[Streams_and_Vectors|Streams and Vectors]] tutorial before moving on.
This tutorial uses vectors. Please complete the [[Streams_and_Vectors|Streams and Vectors]] tutorial before moving on.


Add the following blocks to the flowgraph:
Add the blocks to the flowgraph:
* Signal Source
* Signal Source
* Throttle
* Throttle
Line 20: Line 21:
* Variable
* Variable


Modify the following block properties:
Change the block properties:
* Signal Source
* Signal Source
** Output Type: float
** Output Type: float
Line 41: Line 42:
== Accepting Vector Inputs and Outputs ==
== 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.
The ''Embedded Python Block'' needs to be modified to:
* accept vector inputs
* produce vector outputs
* change the data types to ''float''
 
With respect to the [[Streams_and_Vectors|Streams and Vectors]] tutorial, from a higher-level point of view, ''Streams'' are typically just a special case of ''Vectors'', ones that have just one data element in parallel instead of multiple.  This is why we can just change the <code>in_sig</code> and <code>out_sig</code> parameters to use vectors rather than streams. 
 
Since the case of streams is so common it has its own simple syntax, whereas the syntax for vectors is just a bit more complex.  In particular, when using vectors, we must specify the vector's length along with the data type of the elements in the vectors.  We do so using a tuple in Python.
 
Double-click on the "Embedded Python Block" to edit the source code.


Change ''example_param'' in the function definition to ''vectorSize'':
Change ''example_param'' in the function definition to ''vectorSize'':
Line 49: Line 59:
<pre>name='Max Hold Block',</pre>
<pre>name='Max Hold Block',</pre>


Define the input and output to be a tuple enveloped by a list:
Define the input and output vector signals using a tuple:
<pre>in_sig=[(np.float32,vectorSize)],
<pre>in_sig=[(np.float32,vectorSize)],
out_sig=[(np.float32,vectorSize)]</pre>
out_sig=[(np.float32,vectorSize)]</pre>
Line 63: Line 73:




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


[[File:PythonVectorConnectMaxHold.png|800px]]
[[File:PythonVectorConnectMaxHold.png|800px]]
Line 69: Line 79:
== Warning for Vector Length Mismatches ==
== Warning for Vector Length Mismatches ==


The ''Embedded Python Block'' has one quirk that other [[OutOfTreeModules|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()__'',
The ''Embedded Python Block'' has one difference that other [[OutOfTreeModules|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,
<pre>def __init__(self, vectorSize=16):</pre>
<pre>def __init__(self, vectorSize=16):</pre>


Line 77: Line 87:
out_sig=[(np.float32,vectorSize)]</pre>
out_sig=[(np.float32,vectorSize)]</pre>


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, <u>even if the parameter is passed in through the block properties!</u>
when determining if the flowgraph is correct. In this case ''vectorSize=16''. GRC assumes the input and output ports are vectors with length 16, <u>even if a different parameter is passed in through the block properties!</u>. 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:
 
[[File:MismatchVectorSizeOption1.png|800px]]
 
 
<pre>Traceback (most recent call last):
  File "/home/username/vectorinput.py", line 250, in <module>
    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</pre>
 
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:
 
[[File:MismatchVectorSizeOption2.png|800px]]
 
== Indexing Streams ==
 
For a stream, inputs and outputs can be indexed using both the port number and the sample index.


Indexing based on the port number returns all of the input samples for a specific port. For example,


* show examples of both types of mismatch
<pre>input_items[0]</pre>


returns all input samples on port 0.


The following line returns the 4th input sample on port 0:


<pre>input_items[0][3]</pre>


<u>Warning!</u> The flowgraph will show an error if the length of the vector in your block definition does not match the vector sizes of the other blocks! Since there is not a syntax error the ''Embedded Python Block'' will not report an error in the properties window. Instead the only indication of an error will be that the blocks will not connect:
The indexing for streams is generalized to:


* TODO: update this image
<pre>input_items[portIndex][sampleIndex]
[[File:VectorSizeError.png|800px]]
output_items[portIndex][sampleIndex]</pre>
 
The image shows how to visualize indexing streams:
 
[[File:IndexingStreams.png|500px]]


== Indexing Vectors ==
== Indexing Vectors ==


Vectors add an additional dimension, represented as ''vectorIndex'' below, onto both ''input_items'' and ''output_items'':
The input ''input_items'' and output ''output_items'' include an extra dimension when using vectors.
 
Vectors add an additional dimension, represented as ''vectorIndex'' below. The ''input_items'' and ''output_items'' are now three-dimensional arrays:
<pre>input_items[portIndex][vectorIndex][sampleIndex]
<pre>input_items[portIndex][vectorIndex][sampleIndex]
output_items[portIndex][vectorIndex][sampleIndex]</pre>
output_items[portIndex][vectorIndex][sampleIndex]</pre>


* TODO: put an image here
Indexing based on the ''portIndex'' returns a two-dimensional array of all vectors and samples, for example:
 
<pre>input_items[portIndex]
output_items[portIndex]</pre>
 
Indexing based on ''portIndex'' and ''vectorIndex'' returns an single-dimensional array of samples, for example:
 
<pre>input_items[portIndex][vectorIndex]
output_items[portIndex][vectorIndex]</pre>
 
Indexing based on ''portIndex'', ''vectorIndex'' and ''sampleIndex'' returns a single sample.
 
<pre>input_items[portIndex][vectorIndex][sampleIndex]
output_items[portIndex][vectorIndex][sampleIndex]</pre>
 
A visual example vector indexing is given below:
 
[[File:VectorIndexing.png|700px]]


== Creating Max Hold Function ==
== 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]'':
The ''work()'' function is modified to include the max hold function. Add a loop over all of the vectors in ''input_items[0]'':
<pre>for vectorIndex in range(len(input_items[0])):</pre>
<pre>for vectorIndex in range(len(input_items[0])):</pre>


Line 117: Line 180:




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:
Save the code (CTRL + S). Run the flowgraph. The output will shows a sinusoid and a sinusoid with a max-hold applied every 16 samples:


[[File:MaxHoldOutput.png|600px]]
[[File:MaxHoldOutput.png|600px]]


== Multiple Vector Ports ==
== Multiple Vector Ports ==


The ''Max Hold Block'' will be modified to include a second vector input and output port.
The ''Max Hold Block'' is modified to add a second vector input and output port.


Add the following blocks to the workspace:
Add the following blocks to the workspace:
Line 144: Line 206:
** Number of Inputs: 2
** Number of Inputs: 2


Connect the blocks according to the flowgraph:
Connect the blocks:


[[File:PythonVectorNoiseSource.png|800px]]
[[File:PythonVectorNoiseSource.png|800px]]




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


Save the code. Connect the blocks according to the flowgraph:
[[File:PythonBlockSecondVector.png|700px]]
 
The ''work()'' function is modified to perform the max hold function over both input ports.


[[File:PythonBlockSecondVector.png|700px]]
Include an outer loop over all input ports:
 
<pre>for portIndex in range(len(input_items)):</pre>
 
Change all indexing ''[0]'' to ''[portIndex]'':
 
<pre>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</pre>
 
The code should now look like the following:
 
[[File:PythonVectorFinalCode.png|700px]]
 
 
Save the code and connect the blocks:
 
[[File:PythonVectorFinalFlowgraph.png|800px]]
 
 
Run the flowgraph. Two max-hold outputs will now be generated, one for the noise source and one for the sinusoid:
 
[[File:TwoMaxHoldOutputs.png|700px]]




The ''work()'' function has to be modified to perform the max hold function over both input ports now.
The next tutorial, [[Python_Block_Message_Passing|Python Block Message Passing]] describes how to send and receive messages using the ''Embedded Python Block''.

Latest revision as of 12:45, 27 July 2024

Beginner Tutorials

Introducing GNU Radio

  1. What is GNU Radio?
  2. Installing GNU Radio
  3. Your First Flowgraph

Flowgraph Fundamentals

  1. Python Variables in GRC
  2. Variables in Flowgraphs
  3. Runtime Updating Variables
  4. Signal Data Types
  5. Converting Data Types
  6. Packing Bits
  7. Streams and Vectors
  8. Hier Blocks and Parameters

Creating and Modifying Python Blocks

  1. Creating Your First Block
  2. Python Block With Vectors
  3. Python Block Message Passing
  4. Python Block Tags

DSP Blocks

  1. Low Pass Filter Example
  2. Designing Filter Taps
  3. Sample Rate Change
  4. Frequency Shifting
  5. Reading and Writing Binary Files

SDR Hardware

  1. RTL-SDR FM Receiver
  2. B200-B205mini FM Receiver

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

Change the 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:

PythonVectorStartingFlowgraph.png

Accepting Vector Inputs and Outputs

The Embedded Python Block needs to be modified to:

  • accept vector inputs
  • produce vector outputs
  • change the data types to float

With respect to the Streams and Vectors tutorial, from a higher-level point of view, Streams are typically just a special case of Vectors, ones that have just one data element in parallel instead of multiple. This is why we can just change the in_sig and out_sig parameters to use vectors rather than streams.

Since the case of streams is so common it has its own simple syntax, whereas the syntax for vectors is just a bit more complex. In particular, when using vectors, we must specify the vector's length along with the data type of the elements in the vectors. We do so using a tuple in Python.

Double-click on the "Embedded Python 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 vector signals using a tuple:

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:

PythonVectorDefineBlock.png


Save. Connect the Max Hold Block to the rest of the flowgraph:

PythonVectorConnectMaxHold.png

Warning for Vector Length Mismatches

The Embedded Python Block has one difference 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 vectorSize=16. GRC assumes the input and output ports are 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:

MismatchVectorSizeOption1.png


Traceback (most recent call last):
  File "/home/username/vectorinput.py", line 250, in <module>
    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:

MismatchVectorSizeOption2.png

Indexing Streams

For a stream, inputs and outputs can be indexed using both the port number and the sample index.

Indexing based on the port number returns all of the input samples for a specific port. For example,

input_items[0]

returns all input samples on port 0.

The following line returns the 4th input sample on port 0:

input_items[0][3]

The indexing for streams is generalized to:

input_items[portIndex][sampleIndex]
output_items[portIndex][sampleIndex]

The image shows how to visualize indexing streams:

IndexingStreams.png

Indexing Vectors

The input input_items and output output_items include an extra dimension when using vectors.

Vectors add an additional dimension, represented as vectorIndex below. 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, for example:

input_items[portIndex]
output_items[portIndex]

Indexing based on portIndex and vectorIndex returns an single-dimensional array of samples, for example:

input_items[portIndex][vectorIndex]
output_items[portIndex][vectorIndex]

Indexing based on portIndex, vectorIndex and sampleIndex returns a single sample.

input_items[portIndex][vectorIndex][sampleIndex]
output_items[portIndex][vectorIndex][sampleIndex]

A visual example vector indexing is given below:

VectorIndexing.png

Creating Max Hold Function

The work() function is 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:

MaxHoldWorkFunction.png


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

MaxHoldOutput.png

Multiple Vector Ports

The Max Hold Block is modified to add 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:

PythonVectorNoiseSource.png


Edit the code for the Max Hold Block. Add a second vector input and output:

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

PythonBlockSecondVector.png

The work() function is modified to perform the max hold function over both input ports.

Include an 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:

PythonVectorFinalCode.png


Save the code and connect the blocks:

PythonVectorFinalFlowgraph.png


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

TwoMaxHoldOutputs.png


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