Example 3. Creating a Butterworth Filter
Instrument:
N1000A
N109x
UXR Scope
Flex Apps:
FlexDCA
FlexRT
Meas. mode:
Scope
Eye
Jitter
TDR
Package License:
L-RND
![](../../../../Resources/Images/FlexDCA/userFuncM2_1.png)
This Python example creates a Butterworth user operator in the FlexDCA / Python environment. Although not required, it is a good practice to give the XML file and script files the same or similar base filename, but using different file name extensions of course! As the number of your measurements grow, you'll appreciate being able to quickly associate the different XML and script files at a glance.
Color has been added to the following example lines of the XML and script code to make them easier to read.
Because Example 1 showed how to install a user measurement, this example will not repeat the procedure.
![]() |
![]() |
Required Files
Butterworth.xml
Copy
Butterworth.xml
<?xml version="1.0" encoding="utf-8"?>
<Function xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
Application="FlexDCA" MinimumSoftwareVersion="5.00.328">
<Name>Butterworth</Name>
<Abbreviation>Butter</Abbreviation>
<Comments>Implements a Butterworth filter.</Comments>
<Script>Butterworth.py</Script>
<Icon></Icon>
<FunctionType>1 Source</FunctionType>
<Variable Name = "FType" Value = "low"/>
<Control Name = "N">
<Integer>
<Min>1</Min>
<!--Python Butterworth implementation has problems above 4th order.-->
<Max>4</Max>
<Units>None</Units>
<Default>2</Default>
</Integer>
</Control>
<Control Name = "Fc">
<Double>
<Min>10e6</Min>
<Max>1000e9</Max>
<Resolution>1000</Resolution>
<Units>Hertz</Units>
<Default>7.5e9</Default>
</Double>
</Control>
</Function>
Butterworth.py
Copy
Butterworth.py
""" Python 3.9 FlexDCA Butterworth User Operator.
"""
import math
import numpy as np
from scipy.fftpack import fft, ifft
from scipy.signal import butter, freqz, lfilter
# Expects a dictionary mapping from variable name to value.
def algorithm(inputVars):
# Initialize
FiltData = []
XOrg = 0.0
XInc = 0.0
Gain = 1.0
FilterWidth = 0.0
FilterDelay = 0.0
XUnits = 'Same'
YUnits = 'Same'
ErrorMsg = ''
# N, Fc, and FType are additional variables specified in the XML config file
# for this filter.
if ('N' not in inputVars) or ('Fc' not in inputVars) or ('FType' not in inputVars):
return { 'ErrorMsg' : 'Please configure the operator by loading the XML configuration file ' \
'rather than loading the Python script directly.' }
# Gather custom controls
N = inputVars['N']
Fc = inputVars['Fc']
FType = inputVars['FType']
# Gather system info
SrcData = inputVars['SrcData']
XOrg = inputVars['XOrg']
XInc = inputVars['XInc']
IsPeriodic = inputVars['IsPeriodic']
FilterDelay = N * 0.112 / Fc
FilterWidth = N / Fc
num_points = len(SrcData)
if num_points > 0:
# Create the filter
Wn = 2 * XInc * Fc
b, a = butter(N, Wn, FType)
if IsPeriodic:
# Perform filtering circularly using FFTs
fft_src_data = fft(SrcData)
_unused, H = freqz(b, a, num_points, True)
FiltData = ifft(fft_src_data * H).real
# Rotate to remove FilterDelay.
shift = int(round(FilterDelay / XInc))
FiltData = np.roll(FiltData, -shift)
else:
start_index = int(math.ceil(FilterWidth / XInc))
if start_index >= num_points:
ErrorMsg = 'Too few points. Pipeline eats up entire waveform.'
else:
# Perform filtering
FiltData = lfilter(b, a, SrcData)
# Remove filter delay and pipeline
FiltData = FiltData[start_index:]
leading_pipeline = FilterWidth - FilterDelay
XOrg = XOrg + leading_pipeline
# Return output variables as dictionary.
return { 'FiltData' : FiltData,
'XOrg' : XOrg,
'XInc' : XInc,
'Gain' : Gain,
'FilterWidth' : FilterWidth,
'FilterDelay' : FilterDelay,
'XUnits' : XUnits,
'YUnits' : YUnits,
'ErrorMsg' : ErrorMsg
}