Libraries

pyfda contains the following libraries:

pyfda_lib

Library with various general functions and variables needed by the pyfda routines

pyfda.libs.pyfda_lib.H_mag(num, den, z, H_max, H_min=None, log=False, div_by_0='ignore')[source]

Calculate |H(z)| at the complex frequency(ies) z (scalar or array-like). The function H(z) is given in polynomial form with numerator and denominator. When log == True, \(20 \log_{10} (|H(z)|)\) is returned.

The result is clipped at H_min, H_max; clipping can be disabled by passing None as the argument.

Parameters:
  • num (float or array-like) – The numerator polynome of H(z).

  • den (float or array-like) – The denominator polynome of H(z).

  • z (float or array-like) – The complex frequency(ies) where H(z) is to be evaluated

  • H_max (float) – The maximum value to which the result is clipped

  • H_min (float, optional) – The minimum value to which the result is clipped (default: None)

  • log (boolean, optional) – When true, return 20 * log10 (|H(z)|). The clipping limits have to be given as dB in this case.

  • div_by_0 (string, optional) – What to do when division by zero occurs during calculation (default: ‘ignore’). As the denomintor of H(z) becomes 0 at each pole, warnings are suppressed by default. This parameter is passed to numpy.seterr(), hence other valid options are ‘warn’, ‘raise’ and ‘print’.

Returns:

H_mag – The magnitude |H(z)| for each value of z.

Return type:

float or ndarray

pyfda.libs.pyfda_lib.calc_Hcomplex(fil_dict, worN, wholeF, fs=6.283185307179586)[source]

A wrapper around signal.freqz() for calculating the complex frequency response H(f) for antiCausal systems as well. The filter coefficients are are extracted from the filter dictionary.

Parameters:
  • fil_dict (dict) – dictionary with filter data (coefficients etc.)

  • worN ({None, int or array-like}) – number of points or frequencies where the frequency response is calculated

  • wholeF (bool) – when True, calculate frequency response from 0 … f_S, otherwise calculate between 0 … f_S/2

  • fs (float) – sampling frequency, used for calculation of the frequency vector. The default is 2*pi

Returns:

  • w (ndarray) – The frequencies at which h was computed, in the same units as fs. By default, w is normalized to the range [0, pi) (radians/sample).

  • h (ndarray) – The frequency response, as complex numbers.

Examples

pyfda.libs.pyfda_lib.ceil_even(x) int[source]

Return the smallest even integer not less than x. x can be integer or float.

pyfda.libs.pyfda_lib.ceil_odd(x) int[source]

Return the smallest odd integer not less than x. x can be integer or float.

pyfda.libs.pyfda_lib.clean_ascii(arg)[source]

Remove non-ASCII-characters (outside range 0 … x7F) from arg when it is a str. Otherwise, return arg unchanged.

Parameters:

arg (str) – This is a unicode string under Python 3

Returns:

arg – Input string, cleaned from non-ASCII characters when arg is a string

or

Unchanged parameter arg when not a string

Return type:

str

pyfda.libs.pyfda_lib.cmp_version(mod, version)[source]

Compare version number of installed module mod against string version and return 1, 0 or -1 if the installed version is greater, equal or less than the number in version. If mod is not installed, return -2.

Parameters:
  • mod (str) – name of the module to be compared

  • version (str) – version number in the form e.g. “0.1.6”

Returns:

result

one of the following error codes:

-2:

module is not installed

-1:

version of installed module is lower than the specified version

0:

version of installed module is equal to specied version

1:

version of installed module is higher than specified version

Return type:

int

pyfda.libs.pyfda_lib.cmplx_sort(p)[source]

sort roots based on magnitude.

pyfda.libs.pyfda_lib.cround(x, n_dig=0)[source]

Round complex number to n_dig digits. If n_dig == 0, don’t round at all, just convert complex numbers with an imaginary part very close to zero to real.

pyfda.libs.pyfda_lib.dB(lin: float, power: bool = False) float[source]

Calculate dB from linear value. If power = True, calculate 10 log …, else calculate 20 log …

pyfda.libs.pyfda_lib.expand_lim(ax, eps_x: float, eps_y: float = None) None[source]

Expand the xlim and ylim-values of passed axis by eps

Parameters:
  • ax (axes object)

  • eps_x (float) – factor by which x-axis limits are expanded

  • eps_y (float) – factor by which y-axis limits are expanded. If eps_y is None, eps_x is used for eps_y as well.

Return type:

None

pyfda.libs.pyfda_lib.fil_convert(fil_dict: dict, format_in) None[source]

Convert between poles / zeros / gain, filter coefficients (polynomes) and second-order sections and store all formats not generated by the filter design routine in the passed dictionary fil_dict.

Parameters:
  • fil_dict (dict) – filter dictionary containing a.o. all formats to be read and written.

  • format_in (string or set of strings) –

    format(s) generated by the filter design routine. Must be one of

    ’sos’:

    a list of second order sections - all other formats can easily be derived from this format

    ’zpk’:

    [z,p,k] where z is the array of zeros, p the array of poles and k is a scalar with the gain - the polynomial form can be derived from this format quite accurately

    ’ba’:

    [b, a] where b and a are the polynomial coefficients - finding the roots of the a and b polynomes may fail for higher orders

Returns:

  • None

  • Exceptions

  • ———-

  • ValueError for Nan / Inf elements or other unsuitable parameters

pyfda.libs.pyfda_lib.fil_save(fil_dict: dict, arg, format_in: str, sender: str, convert: bool = True) None[source]

Save filter design arg given in the format specified as format_in in the dictionary fil_dict. The format can be either poles / zeros / gain, filter coefficients (polynomes) or second-order sections.

Convert the filter design to the other formats if convert is True.

Parameters:
  • fil_dict (dict) – The dictionary where the filter design is saved to.

  • arg (various formats) – The actual filter design

  • format_in (string) –

    Specifies how the filter design in ‘arg’ is passed:

    ’ba’:

    Coefficient form: Filter coefficients in FIR format (b, one dimensional) are automatically converted to IIR format (b, a).

    ’zpk’:

    Zero / pole / gain format: When only zeroes are specified, poles and gain are added automatically.

    ’sos’:

    Second-order sections

  • sender (string) – The name of the method that calculated the filter. This name is stored in fil_dict together with format_in.

  • convert (boolean) – When convert = True, convert arg to the other formats.

Return type:

None

pyfda.libs.pyfda_lib.floor_even(x) int[source]

Return the largest even integer not larger than x. x can be integer or float.

pyfda.libs.pyfda_lib.floor_odd(x) int[source]

Return the largest odd integer not larger than x. x can be integer or float.

pyfda.libs.pyfda_lib.format_ticks(ax, xy: str, scale: float = 1.0, format: str = '%.1f') None[source]

Reformat numbers at x or y - axis. The scale can be changed to display e.g. MHz instead of Hz. The number format can be changed as well.

Parameters:
  • ax (axes object)

  • xy (string, either 'x', 'y' or 'xy') – select corresponding axis (axes) for reformatting

  • scale (float (default: 1.)) – rescaling factor for the axes

  • format (string (default: %.1f)) – define C-style number formats

Return type:

None

Examples

Scale all numbers of x-Axis by 1000, e.g. for displaying ms instead of s.

>>> format_ticks('x',1000.)

Two decimal places for numbers on x- and y-axis

>>> format_ticks('xy',1., format = "%.2f")
pyfda.libs.pyfda_lib.lin2unit(lin_value: float, filt_type: str, amp_label: str, unit: str = 'dB') float[source]

Convert linear amplitude specification to dB or W, depending on filter type (‘FIR’ or ‘IIR’) and whether the specifications belong to passband or stopband. This is determined by checking whether amp_label contains the strings ‘PB’ or ‘SB’ :

  • Passband:
    \[ \begin{align}\begin{aligned}\text{IIR:}\quad A_{dB} &= -20 \log_{10}(1 - lin\_value)\\\text{FIR:}\quad A_{dB} &= 20 \log_{10}\frac{1 + lin\_value}{1 - lin\_value}\end{aligned}\end{align} \]
  • Stopband:
    \[A_{dB} = -20 \log_{10}(lin\_value)\]

Returns the result as a float.

pyfda.libs.pyfda_lib.mod_version(mod=None)[source]

Return the version of the module ‘mod’. If the module is not found, return None. When no module is specified, return a string with all modules and their versions sorted alphabetically.

pyfda.libs.pyfda_lib.qstr(text)[source]

Convert text (QVariant, QString, string) or numeric object to plain string.

In Python 3, python Qt objects are automatically converted to QVariant when stored as “data” (itemData) e.g. in a QComboBox and converted back when retrieving to QString. In Python 2, QVariant is returned when itemData is retrieved. This is first converted from the QVariant container format to a QString, next to a “normal” non-unicode string.

Parameters:

text (QVariant, QString, string or numeric data type that can be converted) – to string

Return type:

The current text data as a unicode (utf8) string

pyfda.libs.pyfda_lib.round_even(x) int[source]

Return the nearest even integer from x. x can be integer or float.

pyfda.libs.pyfda_lib.round_odd(x) int[source]

Return the nearest odd integer from x. x can be integer or float.

pyfda.libs.pyfda_lib.safe_eval(expr, alt_expr=0, return_type: str = 'float', sign: str = None)[source]

Try … except wrapper around numexpr to catch various errors When evaluation fails or returns None, try evaluating alt_expr. When this also fails, return 0 to avoid errors further downstream.

Parameters:
  • expr (str or scalar) – Expression to be evaluated, is cast to a string

  • alt_expr (str or scalar) – Expression to be evaluated when evaluation of first string fails, is cast to a string.

  • return_type (str) – Type of returned variable [‘float’ (default) / ‘cmplx’ / ‘int’ / ‘’ or ‘auto’]

  • sign (str) –

    enforce positive / negative sign of result [‘pos’, ‘poszero’ / None (default)

    ’negzero’ / ‘neg’]

Returns:

the evaluated result or 0 when both arguments fail

Return type:

float (default) / complex / int

Function attribute err contains number of errors that have occurred during evaluation (0 / 1 / 2)

pyfda.libs.pyfda_lib.set_dict_defaults(d: dict, default_dict: dict) None[source]

Add the key:value pairs of default_dict to dictionary d in-place for all missing keys.

pyfda.libs.pyfda_lib.sos2zpk(sos)[source]

Taken from scipy/signal/filter_design.py - edit to eliminate first order section

Return zeros, poles, and gain of a series of second-order sections

Parameters:

sos (array_like) – Array of second-order filter coefficients, must have shape (n_sections, 6). See sosfilt for the SOS filter format specification.

Returns:

  • z (ndarray) – Zeros of the transfer function.

  • p (ndarray) – Poles of the transfer function.

  • k (float) – System gain.

Notes

Added in version 0.16.0.

pyfda.libs.pyfda_lib.to_html(text: str, frmt: str = None) str[source]
Convert text to HTML format:
  • pretty-print logger messages

  • convert “n” to “<br />

  • convert “< “ and “> “ to “&lt;” and “&gt;”

  • format strings with italic and / or bold HTML tags, depending on parameter frmt. When frmt=None, put the returned string between <span> tags to enforce HTML rendering downstream

  • replace ‘_’ by HTML subscript tags. Numbers 0 … 9 are never set to italic format

Parameters:
  • text (str) – Text to be converted

  • frmt (str) –

    define text style

    • ’b’ : bold text

    • ’i’ : italic text

    • ’bi’ or ‘ib’ : bold and italic text

Returns:

HTML - formatted text

Return type:

str

Examples

>>> to_html("F_SB", frmt='bi')
"<b><i>F<sub>SB</sub></i></b>"
>>> to_html("F_1", frmt='i')
"<i>F</i><sub>1</sub>"
pyfda.libs.pyfda_lib.unique_roots(p, tol: float = 0.001, magsort: bool = False, rtype: str = 'min', rdist: str = 'euclidian')[source]

Determine unique roots and their multiplicities from a list of roots.

Parameters:
  • p (array_like) – The list of roots.

  • tol (float, default tol = 1e-3) – The tolerance for two roots to be considered equal. Default is 1e-3.

  • magsort (Boolean, default = False) – When magsort = True, use the root magnitude as a sorting criterium (as in the version used in numpy < 1.8.2). This yields false results for roots with similar magniudes (e.g. on the unit circle) but is signficantly faster for a large number of roots (factor 20 for 500 double roots.)

  • rtype ({'max', 'min, 'avg'}, optional) – How to determine the returned root if multiple roots are within tol of each other. - ‘max’ or ‘maximum’: pick the maximum of those roots (magnitude ?). - ‘min’ or ‘minimum’: pick the minimum of those roots (magnitude?). - ‘avg’ or ‘mean’ : take the average of those roots. - ‘median’ : take the median of those roots

  • dist ({'manhattan', 'euclid'}, optional) – How to measure the distance between roots: ‘euclid’ is the euclidian distance. ‘manhattan’ is less common, giving the sum of the differences of real and imaginary parts.

Returns:

  • pout (list) – The list of unique roots, sorted from low to high (only for real roots).

  • mult (list) – The multiplicity of each root.

Notes

This utility function is not specific to roots but can be used for any sequence of values for which uniqueness and multiplicity has to be determined. For a more general routine, see numpy.unique.

Examples

>>> vals = [0, 1.3, 1.31, 2.8, 1.25, 2.2, 10.3]
>>> uniq, mult = unique_roots(vals, tol=2e-2, rtype='avg')

Check which roots have multiplicity larger than 1:

>>> uniq[mult > 1]
array([ 1.305])

Find multiples of complex roots on the unit circle: >>> vals = np.roots(1,2,3,2,1) uniq, mult = unique_roots(vals, rtype=’avg’)

pyfda.libs.pyfda_lib.unit2lin(unit_value: float, filt_type: str, amp_label: str, unit: str = 'dB') float[source]

Convert amplitude specification in dB or W to linear specs:

  • Passband:
    \[ \begin{align}\begin{aligned}\text{IIR:}\quad A_{PB,lin} &= 1 - 10 ^{-unit\_value/20}\\\text{FIR:}\quad A_{PB,lin} &= \frac{10 ^ {unit\_value/20} - 1}{10 ^ {unit\_value/20} + 1}\end{aligned}\end{align} \]
  • Stopband:
    \[A_{SB,lin} = -10 ^ {-unit\_value/20}\]

Returns the result as a float.

pyfda_sig_lib

Library with various signal processing related functions

pyfda.libs.pyfda_sig_lib.angle_zero(X, n_eps=1000.0, mode='auto', wrapped='auto')[source]

Calculate angle of argument X when abs(X) > n_eps * machine resolution. Otherwise, zero is returned.

pyfda.libs.pyfda_sig_lib.div_safe(num, den, n_eps: float = 1.0, i_scale: float = 1.0, verbose: bool = False)[source]

Perform elementwise array division after treating singularities, meaning:

  • check whether denominator (den) coefficients approach zero

  • check whether numerator (num) or denominator coefficients are non-finite, i.e. one of nan, ìnf or ninf.

At each singularity, replace denominator coefficient by 1 and numerator coefficient by 0

Parameters:
  • num (array_like) – numerator coefficients

  • den (array_like) – denominator coefficients

  • n_eps (float) – n_eps * machine resolution is the limit for the denominator below which the ratio is set to zero. The machine resolution in numpy is given by np.spacing(1), the distance to the nearest number which is equivalent to matlab’s “eps”.

  • i_scale (float) – The scale for the index i for num, den for printing the index of the singularities.

  • verbose (bool, optional) – whether to print The default is False.

Returns:

ratio – The ratio of num and den (zero at singularities)

Return type:

array_like

pyfda.libs.pyfda_sig_lib.group_delay(b, a=1, nfft=512, whole=False, analog=False, verbose=True, fs=6.283185307179586, sos=False, alg='scipy', n_eps=100)[source]

Calculate group delay of a discrete time filter, specified by numerator coefficients b and denominator coefficients a of the system function H ( z).

When only b is given, the group delay of the transversal (FIR) filter specified by b is calculated.

Parameters:
  • b (array_like) – Numerator coefficients (transversal part of filter)

  • a (array_like (optional, default = 1 for FIR-filter)) – Denominator coefficients (recursive part of filter)

  • whole (boolean (optional, default : False)) – Only when True calculate group delay around the complete unit circle (0 … 2 pi)

  • verbose (boolean (optional, default : True)) – Print warnings about frequency points with undefined group delay (amplitude = 0) and the time used for calculating the group delay

  • nfft (integer (optional, default: 512)) – Number of FFT-points

  • fs (float (optional, default: fs = 2*pi)) – Sampling frequency.

  • alg (str (default: "scipy")) –

    The algorithm for calculating the group delay:
    • ”scipy” The algorithm used by scipy’s grpdelay,

    • ”jos”: The original J.O.Smith algorithm; same as in “scipy” except that the frequency response is calculated with the FFT instead of polyval

    • ”diff”: Group delay is calculated by differentiating the phase

    • ”Shpakh”: Group delay is calculated from second-order sections

  • n_eps (integer (optional, default : 100)) – Minimum value in the calculation of intermediate values before tau_g is set to zero.

Returns:

  • tau_g (ndarray) – group delay

  • w (ndarray) – angular frequency points where group delay was computed

Notes

The following explanations follow [JOS].

Definition and direct calculation (‘diff’)

The group delay \(\tau_g(\omega)\) of discrete time (DT) and continuous time (CT) systems is the rate of change of phase with respect to angular frequency. In the following, derivative is always meant w.r.t. \(\omega\):

\[\tau_g(\omega) = -\frac{\partial }{\partial \omega}\angle H( \omega) = -\frac{\partial \phi(\omega)}{\partial \omega} = - \phi'(\omega)\]

With numpy / scipy, the group delay can be calculated directly with

w, H = sig.freqz(b, a, worN=nfft, whole=whole)
tau_g = -np.diff(np.unwrap(np.angle(H)))/np.diff(w)

The derivative can create numerical problems for e.g. phase jumps at zeros of frequency response or when the complex frequency response becomes very small e.g. in the stop band.

This can be avoided by calculating the group delay from the derivative of the logarithmic frequency response in polar form (amplitude response and phase):

\[ \begin{align}\begin{aligned}\ln ( H( \omega)) = \ln \left({H_A( \omega)} e^{j \phi(\omega)} \right) = \ln \left({H_A( \omega)} \right) + j \phi(\omega)\\ \Rightarrow \; \frac{\partial }{\partial \omega} \ln ( H( \omega)) = \frac{H_A'( \omega)}{H_A( \omega)} + j \phi'(\omega)\end{aligned}\end{align} \]

where \(H_A(\omega)\) is the amplitude response. \(H_A(\omega)\) and its derivative \(H_A'(\omega)\) are real-valued, therefore, the group delay can be calculated by separating real and imginary components (and discarding the real part):

\[\begin{align} \Re \left\{\frac{\partial }{\partial \omega} \ln ( H( \omega))\right\} &= \frac{H_A'( \omega)}{H_A( \omega)} \ \Im \left\{\frac{\partial }{\partial \omega} \ln ( H( \omega))\right\} &= \phi'(\omega) \end{align}\]

and hence

\[\tau_g(\omega) = -\phi'(\omega) = -\Im \left\{ \frac{\partial }{\partial \omega} \ln ( H( \omega)) \right\} =-\Im \left\{ \frac{H'(\omega)}{H(\omega)} \right\}\]

Note: The last term contains the complex response \(H(\omega)\), not the amplitude response \(H_A(\omega)\)!

In the following, it will be shown that the derivative of birational functions (like DT and CT filters) can be calculated very efficiently and from this the group delay.

J.O. Smith’s basic algorithm for FIR filters (‘scipy’)

An efficient form of calculating the group delay of FIR filters based on the derivative of the logarithmic frequency response has been described in [JOS] and [Lyons08] for discrete time systems.

A FIR filter is defined via its polyome \(H(z) = \sum_k b_k z^{-k}\) and has the following derivative:

\[\frac{\partial }{\partial \omega} H(z = e^{j \omega T}) = \frac{\partial }{\partial \omega} \sum_{k = 0}^N b_k e^{-j k \omega T} = -jT \sum_{k = 0}^{N} k b_{k} e^{-j k \omega T} = -jT H_R(e^{j \omega T})\]

where \(H_R\) is the “ramped” polynome, i.e. polynome \(H\) multiplied with a ramp \(k\), yielding

\[\tau_g(e^{j \omega T}) = -\Im \left\{ \frac{H'(e^{j \omega T})} {H(e^{j \omega T})} \right\} = -\Im \left\{ -j T \frac{H_R(e^{j \omega T})} {H(e^{j \omega T})} \right\} = T \, \Re \left\{\frac{H_R(e^{j \omega T})} {H(e^{j \omega T})} \right\}\]

scipy’s grpdelay directly calculates the complex frequency response \(H(e^{j\omega T})\) and its ramped function at the frequency points using the polyval function.

When zeros of the frequency response are on or near the data points of the DFT, this algorithm runs into numerical problems. Hence, it is neccessary to check whether the magnitude of the denominator is less than e.g. 100 times the machine eps. In this case, \(\tau_g\) is set to zero.

J.O. Smith’s basic algorithm for IIR filters (‘scipy’)

IIR filters are defined by

\[H(z) = \frac {B(z)}{A(z)} = \frac {\sum b_k z^k}{\sum a_k z^k},\]

their group delay can be calculated numerically via the logarithmic frequency response as well.

The derivative of \(H(z)\) w.r.t. \(\omega\) is calculated using the quotient rule and by replacing the derivatives of numerator and denominator polynomes with their ramp functions:

\[\begin{split}\begin{align} \frac{H'(e^{j \omega T})}{H(e^{j \omega T})} &= \frac{\left(B(e^{j \omega T})/A(e^{j \omega T})\right)'}{B(e^{j \omega T})/A(e^{j \omega T})} = \frac{B'(e^{j \omega T}) A(e^{j \omega T}) - A'(e^{j \omega T})B(e^{j \omega T})} { A(e^{j \omega T}) B(e^{j \omega T})} \\ &= \frac {B'(e^{j \omega T})} { B(e^{j \omega T})} - \frac { A'(e^{j \omega T})} { A(e^{j \omega T})} = -j T \left(\frac { B_R(e^{j \omega T})} {B(e^{j \omega T})} - \frac { A_R(e^{j \omega T})} {A(e^{j \omega T})}\right) \end{align}\end{split}\]

This result is substituted once more into the log. derivative from above:

\[\begin{split}\begin{align} \tau_g(e^{j \omega T}) =-\Im \left\{ \frac{H'(e^{j \omega T})}{H(e^{j \omega T})} \right\} &=-\Im \left\{ -j T \left(\frac { B_R(e^{j \omega T})} {B(e^{j \omega T})} - \frac { A_R(e^{j \omega T})} {A(e^{j \omega T})}\right) \right\} \\ &= T \Re \left\{\frac { B_R(e^{j \omega T})} {B(e^{j \omega T})} - \frac { A_R(e^{j \omega T})} {A(e^{j \omega T})} \right\} \end{align}\end{split}\]

If the denominator of the computation becomes too small, the group delay is set to zero. (The group delay approaches infinity when there are poles or zeros very close to the unit circle in the z plane.)

J.O. Smith’s algorithm for CT filters

The same process can be applied for CT systems as well: The derivative of a CT polynome \(P(s)\) w.r.t. \(\omega\) is calculated by:

\[\frac{\partial }{\partial \omega} P(s = j \omega) = \frac{\partial }{\partial \omega} \sum_{k = 0}^N c_k (j \omega)^k = j \sum_{k = 0}^{N-1} (k+1) c_{k+1} (j \omega)^{k} = j P_R(s = j \omega)\]

where \(P_R\) is the “ramped” polynome, i.e. its k th coefficient is multiplied by the ramp k + 1, yielding the same form as for DT systems (but the ramped polynome has to be calculated differently).

\[\tau_g(\omega) = -\Im \left\{ \frac{H'(\omega)}{H(\omega)} \right\} = -\Im \left\{j \frac{H_R(\omega)}{H(\omega)} \right\} = -\Re \left\{\frac{H_R(\omega)}{H(\omega)} \right\}\]

J.O. Smith’s improved algorithm for IIR filters (‘jos’)

J.O. Smith gives the following speed and accuracy optimizations for the basic algorithm:

  • convert the filter to a FIR filter with identical phase and group delay (but with different magnitude response)

  • use FFT instead of polyval to calculate the frequency response

The group delay of an IIR filter \(H(z) = B(z)/A(z)\) can also be calculated from an equivalent FIR filter \(C(z)\) with the same phase response (and hence group delay) as the original filter. This filter is obtained by the following steps:

  • The zeros of \(A(z)\) are the poles of \(1/A(z)\), its phase response is \(\angle A(z) = - \angle 1/A(z)\).

  • Transforming \(z \rightarrow 1/z\) mirrors the zeros at the unit circle, correcting the negative phase response. This can be performed numerically by “flipping” the order of the coefficients and multiplying by \(z^{-N}\) where \(N\) is the order of \(A(z)\). This operation also conjugates the coefficients (?) which mirrors the zeros at the real axis. This effect has to be compensated, yielding the polynome \(\tilde{A}(z)\). It is the “flip-conjugate” or “Hermitian conjugate” of \(A(z)\).

    Frequently (e.g. in the scipy and until recently in the Matlab implementation) the conjugate operation is omitted which gives wrong results for complex coefficients.

  • Finally, \(C(z) = B(z) \tilde{A}(z)\):

\[C(z) = B(z)\left[ z^{-N}{A}^{*}(1/z)\right] = B(z)\tilde{A}(z)\]

where

\[\begin{split}\begin{align} \tilde{A}(z) &= z^{-N}{A}^{*}(1/z) = {a}^{*}_N + {a}^{*}_{N-1}z^{-1} + \ldots + {a}^{*}_1 z^{-(N-1)}+z^{-N}\\ \Rightarrow \tilde{A}(e^{j\omega T}) &= e^{-jN \omega T}{A}^{*}(e^{-j\omega T}) \\ \Rightarrow \angle\tilde{A}(e^{j\omega T}) &= -\angle A(e^{j\omega T}) - N\omega T \end{align}\end{split}\]

In Python, the coefficients of \(C(z)\) are calculated efficiently by convolving the coefficients of \(B(z)\) and \(\tilde{A}(z)\):

c = np.convolve(b, np.conj(a[::-1]))

where \(b\) and \(a\) are the coefficient vectors of the original numerator and denominator polynomes. The actual group delay is then calculated from the equivalent FIR filter as described above.

Calculating the frequency response with the np.polyval(p,z) function at the NFFT frequency points along the unit circle, \(z = \exp(-j \omega)\), seems to be numerically less robust than using the FFT for the same task, it is also much slower.

This measure fixes already most of the problems described for narrowband IIR filters in scipy issues [Scipy_9310] and [Scipy_1175]. In my experience, these problems occur for all narrowband IIR response types.

Shpak algorithm for IIR filters

The algorithm described above is numerically efficient but not robust for narrowband IIR filters. Especially for filters defined by second-order sections, it is recommended to calculate the group delay using the D. J. Shpak’s algorithm.

Code is available at [Endolith_5828333] (GPL licensed) or at [SPA] (MIT licensed).

This algorithm sums the group delays of the individual sections which is much more robust as only second-order functions are involved. However, converting (b,a) coefficients to SOS coefficients introduces inaccuracies.

Examples

>>> b = [1,2,3] # Coefficients of H(z) = 1 + 2 z^2 + 3 z^3
>>> tau_g, td = pyfda_lib.grpdelay(b)
pyfda.libs.pyfda_sig_lib.group_delayz(b, a, w, plot=None, fs=6.283185307179586)[source]

Compute the group delay of digital filter.

Parameters:
  • b (array_like) – Numerator of a linear filter.

  • a (array_like) – Denominator of a linear filter.

  • w (array_like) – Frequencies in the same units as fs.

  • plot (callable) – A callable that takes two arguments. If given, the return parameters w and gd are passed to plot.

  • fs (float, optional) – The angular sampling frequency of the digital system.

Returns:

  • w (ndarray) – The frequencies at which gd was computed, in the same units as fs.

  • gd (ndarray) – The group delay in seconds.

pyfda.libs.pyfda_sig_lib.impz(b, a=1, FS=1, N=0, step=False)[source]

Calculate impulse response of a discrete time filter, specified by numerator coefficients b and denominator coefficients a of the system function H(z).

When only b is given, the impulse response of the transversal (FIR) filter specified by b is calculated.

Parameters:
  • b (array_like) – Numerator coefficients (transversal part of filter)

  • a (array_like (optional, default = 1 for FIR-filter)) – Denominator coefficients (recursive part of filter)

  • FS (float (optional, default: FS = 1)) – Sampling frequency.

  • N (float (optional)) – Number of calculated points. Default: N = len(b) for FIR filters, N = 100 for IIR filters

  • step (bool (optional, default False)) – return the step response instead of the impulse response

Returns:

  • hn (ndarray) – impulse or step response with length N (see above)

  • td (ndarray) – contains the time steps with same length as hn

Examples

>>> b = [1,2,3] # Coefficients of H(z) = 1 + 2 z^2 + 3 z^3
>>> h, n = dsp_lib.impz(b)
pyfda.libs.pyfda_sig_lib.quadfilt_group_delayz(b, w, fs=6.283185307179586)[source]

Compute group delay of 2nd-order digital filter.

Parameters:
  • b (array_like) – Coefficients of a 2nd-order digital filter.

  • w (array_like) – Frequencies in the same units as fs.

  • fs (float, optional) – The sampling frequency of the digital system.

Returns:

  • w (ndarray) – The frequencies at which gd was computed.

  • gd (ndarray) – The group delay in seconds.

pyfda.libs.pyfda_sig_lib.sos_group_delayz(sos, w, plot=None, fs=6.283185307179586)[source]

Compute group delay of digital filter in SOS format.

Parameters:
  • sos (array_like) – Array of second-order filter coefficients, must have shape (n_sections, 6). Each row corresponds to a second-order section, with the first three columns providing the numerator coefficients and the last three providing the denominator coefficients.

  • w (array_like) – Frequencies in the same units as fs.

  • plot (callable, optional) – A callable that takes two arguments. If given, the return parameters w and gd are passed to plot.

  • fs (float, optional) – The sampling frequency of the digital system.

Returns:

  • w (ndarray) – The frequencies at which gd was computed.

  • gd (ndarray) – The group delay in seconds.

pyfda.libs.pyfda_sig_lib.validate_sos(sos)[source]

Helper to validate a SOS input

Copied from scipy.signal._filter_design._validate_sos()

pyfda.libs.pyfda_sig_lib.zeros_with_val(N: int, val: float = 1.0, pos: int = 0)[source]

Create a 1D array of N zeros where the element at position pos has the value val.

Parameters:
  • N (int) – number of elements

  • val (scalar) – value to be inserted at position pos (default: 1)

  • pos (int) – Position of val to be inserted (default: 0)

Returns:

arr – Array with zeros except for element at position pos

Return type:

np.ndarray

pyfda.libs.pyfda_sig_lib.zorp_group_delayz(zorp, w, fs=1)[source]

Compute group delay of digital filter with a single zero/pole.

Parameters:
  • zorp (complex) – Zero or pole of a 1st-order linear filter

  • w (array_like) – Frequencies in the same units as fs.

  • fs (float, optional) – The sampling frequency of the digital system.

Returns:

  • w (ndarray) – The frequencies at which gd was computed.

  • gd (ndarray) – The group delay in seconds.

pyfda.libs.pyfda_sig_lib.zpk2array(zpk)[source]

Test whether Z = zpk[0] and P = zpk[1] have the same length, if not, equalize the lengths by adding zeros.

Test whether gain = zpk[2] is a scalar or a vector and whether it (or the first element of the vector) is != 0. If the gain is 0, set gain = 1.

Finally, convert the gain into an vector with the same length as P and Z and return the the three vectors as one array.

Parameters:

zpk (list, tuple or ndarray) – Zeros, poles and gain of the system

Return type:

zpk as an array or an error string

pyfda.libs.pyfda_sig_lib.zpk_group_delay(z, p, k, w, plot=None, fs=6.283185307179586)[source]

Compute group delay of digital filter in zpk format.

Parameters:
  • z (array_like) – Zeroes of a linear filter

  • p (array_like) – Poles of a linear filter

  • k (scalar) – Gain of a linear filter

  • w (array_like) – Frequencies in the same units as fs.

  • plot (callable, optional) – A callable that takes two arguments. If given, the return parameters w and gd are passed to plot.

  • fs (float, optional) – The sampling frequency of the digital system.

Returns:

  • w (ndarray) – The frequencies at which gd was computed.

  • gd (ndarray) – The group delay in seconds.

pyfda_qt_lib

Library with various helper functions for Qt widgets

class pyfda.libs.pyfda_qt_lib.EventTypes[source]

https://stackoverflow.com/questions/62196835/how-to-get-string-name-for-qevent-in-pyqt5 Events in Qt5: https://doc.qt.io/qt-5/qevent.html

Stores a string name for each event type.

With PySide2 str() on the event type gives a nice string name, but with PyQt5 it does not. So this method works with both systems.

Example usage (simultaneous initialization and method call / translation) > event_str = EventTypes().as_string(QEvent.UpdateRequest) > assert event_str == “UpdateRequest”

Example usage, separate initialization and method call > event types = EventTypes() > event_str = event_types.as_string(event.type())

as_string(event: Type) str[source]

Return the string name for this event.

class pyfda.libs.pyfda_qt_lib.PushButton(txt: str = '', icon: QIcon = None, N_x: int = 8, checkable: bool = True, checked: bool = False, objectName='')[source]

Create a QPushButton with a width fitting the label with bold font as well

Parameters:
  • txt (str) – Text for button (optional)

  • icon (QIcon) – Icon for button. Either txt or icon must be defined.

  • N_x (int) – Width in number of “x”

  • checkable (bool) – Whether button is checkable

  • checked (bool) – Whether initial state is checked

minimumSizeHint(self) QSize[source]
sizeHint(self) QSize[source]
class pyfda.libs.pyfda_qt_lib.QHLine(width=1)[source]

Create a thin horizontal line utilizing the HLine property of QFrames Usage:

> myline = QHLine() > mylayout.addWidget(myline)

class pyfda.libs.pyfda_qt_lib.QLabelVert(text, orientation='west', forceWidth=True)[source]

Create a vertical label

Adapted from https://pyqtgraph.readthedocs.io/en/latest/_modules/pyqtgraph/widgets/VerticalLabel.html

https://stackoverflow.com/questions/34080798/pyqt-draw-a-vertical-label

check https://stackoverflow.com/questions/29892203/draw-rich-text-with-qpainter

minimumSizeHint(self) QSize[source]
paintEvent(self, a0: QPaintEvent | None)[source]
sizeHint(self) QSize[source]
class pyfda.libs.pyfda_qt_lib.QVLine(width=2)[source]

Create a thin vertical line utilizing the HLine property of QFrames Usage:

> myline = QVLine() > mylayout.addWidget(myline)

class pyfda.libs.pyfda_qt_lib.RotatedButton[source]

Create a rotated QPushButton

Taken from

https://forum.qt.io/topic/9279/moved-how-to-rotate-qpushbutton-63/7

minimumSizeHint(self) QSize[source]
paintEvent(self, a0: QPaintEvent | None)[source]
sizeHint(self) QSize[source]
pyfda.libs.pyfda_qt_lib.emit(self, dict_sig: dict = {}, sig_name: str = 'sig_tx') None[source]

Emit a signal self.<sig_name> (defined as a class attribute) with a dict dict_sig using Qt’s emit().

  • Add the keys ‘id’ and ‘class’ with id resp. class name of the calling instance if not contained in the dict

  • If key ‘ttl’ is in the dict and its value is less than one, terminate the signal. Otherwise, reduce the value by one.

  • If the sender has passed an objectName, add it with the key “sender_name” to the dict.

pyfda.libs.pyfda_qt_lib.popup_warning(self, param1: int = 0, param2: str = '', message: str = '') bool[source]

Pop-up a warning box and require a user prompt. When message == “”, warn of very large filter orders, otherwise display the passed message

pyfda.libs.pyfda_qt_lib.qcmb_box_add_item(cmb_box, item_list, data=True, fireSignals=False, caseSensitive=False)[source]

Add an entry in combobox with text / data / tooltipp from item_list. When the item is already in combobox (searching for data or text item, depending data), do nothing. Signals are blocked during the update of the combobox unless fireSignals is set True. By default, the search is case insensitive, this can be changed by passing caseSensitive=False.

Parameters:
  • item_list (list) – List with [“new_data”, “new_text”, “new_tooltip”] to be added.

  • data (bool (default: False)) – Whether the string refers to the data or text fields of the combo box

  • fireSignals (bool (default: False)) – When True, fire a signal if the index is changed (useful for GUI testing)

  • caseInsensitive (bool (default: False)) – When true, perform case sensitive search.

Returns:

  • The index of the found item with string / data. When not found in the

  • combo box, return index -1.

pyfda.libs.pyfda_qt_lib.qcmb_box_add_items(cmb_box: QComboBox, items_list: list) None[source]

Add items to combo box cmb_box with text, data and tooltip from the list items_list.

Text and tooltip are prepared for translation via self.tr()

Parameters:
  • cmb_box (instance of QComboBox) – Combobox to be populated

  • items_list (list) –

    List of combobox entries, in the format

    (“data 1st item”, “text 1st item”, “tooltip for 1st item” # [optional]), (“data 2nd item”, “text 2nd item”, “tooltip for 2nd item”)]

Return type:

None

pyfda.libs.pyfda_qt_lib.qcmb_box_del_item(cmb_box: QComboBox, string: str, data: bool = False, fireSignals: bool = False, caseSensitive: bool = False) int[source]

Try to find the entry in combobox corresponding to string in a text field (data = False) or in a data field (data=True) and delete the item. When string is not found,do nothing. Signals are blocked during the update of the combobox unless fireSignals is set True. By default, the search is case insensitive, this can be changed by passing caseSensitive=False.

Parameters:
  • string (str) – The label in the text or data field to be deleted.

  • data (bool (default: False)) – Whether the string refers to the data or text fields of the combo box

  • fireSignals (bool (default: False)) – When True, fire a signal if the index is changed (useful for GUI testing)

  • caseInsensitive (bool (default: False)) – When true, perform case sensitive search.

Returns:

  • The index of the item with string / data. When not found in the combo box,

  • return index -1.

pyfda.libs.pyfda_qt_lib.qcmb_box_populate(cmb_box: QComboBox, items_list: list, item_init: str) int[source]

Clear and populate combo box cmb_box with text, data and tooltip from the list items_list with initial selection of init_item (data).

Text and tooltip are prepared for translation via self.tr()

Parameters:
  • cmb_box (instance of QComboBox) – Combobox to be populated

  • items_list (list) –

    List of combobox entries, in the format [“Tooltip for Combobox”, (“data 1st item”, “text 1st item”, “tooltip for 1st item”), (“data 2nd item”, “text 2nd item”, “tooltip for 2nd item”)]

    Tooltipps are optional.

  • item_init (str) – data for initial setting of combobox. When data is not found, set combobox to first item.

Returns:

ret – Index of item_init in combobox. If index == -1, item_init was not in items_list

Return type:

int

pyfda.libs.pyfda_qt_lib.qget_cmb_box(cmb_box: QComboBox, data: bool = True) str[source]

Get current itemData or Text of comboBox and convert it to string.

In Python 3, python Qt objects are automatically converted to QVariant when stored as “data” e.g. in a QComboBox and converted back when retrieving. In Python 2, QVariant is returned when itemData is retrieved. This is first converted from the QVariant container format to a QString, next to a “normal” non-unicode string.

Returns:

The current text or data of combobox as a string

pyfda.libs.pyfda_qt_lib.qget_selected(table, select_all=False, reverse=True)[source]

Get selected cells in table and return a dictionary with the following keys:

‘idx’: indices of selected cells as an unsorted list of tuples

‘sel’: list of lists of selected cells per column, by default sorted in reverse

‘cur’: current cell selection as a tuple

Parameters:
  • select_all (bool) – select all table items and create a list when True

  • reverse (bool) – return selected fields upside down when True

pyfda.libs.pyfda_qt_lib.qset_cmb_box(cmb_box: QComboBox, string: str, data: bool = False, fireSignals: bool = False, caseSensitive: bool = False) int[source]

Set combobox to the index corresponding to string in a text field (data = False) or in a data field (data=True). When string is not found in the combobox entries, select the first entry. Signals are blocked during the update of the combobox unless fireSignals is set True. By default, the search is case insensitive, this can be changed by passing caseSensitive=False.

Parameters:
  • string (str) – The label in the text or data field to be selected. When the string is not found, select the first entry of the combo box.

  • data (bool (default: False)) – Whether the string refers to the data or text fields of the combo box

  • fireSignals (bool (default: False)) – When True, fire a signal if the index is changed (useful for GUI testing)

  • caseSensitive (bool (default: False)) – When true, perform case sensitive search.

Returns:

  • The index of the string. When the string was not found in the combo box,

  • select first entry of combo box and return index -1.

pyfda.libs.pyfda_qt_lib.qstyle_widget(widget, state)[source]

Apply the “state” defined in pyfda_rc.py to the widget, e.g.: Color the >> DESIGN FILTER << button according to the filter design state.

This requires setting the property, “unpolishing” and “polishing” the widget and finally forcing an update.

  • ‘normal’ : default, no color styling

  • ‘ok’: green, filter has been designed, everything ok

  • ‘changed’: yellow, filter specs have been changed

  • ‘running’: orange, simulation is running

  • ‘error’ : red, an error has occurred during filter design

  • ‘failed’ : pink, filter fails to meet target specs (not used yet)

  • ‘u’ or ‘unused’ : grey text color

  • ‘d’ or ‘disabled’: background color darkgrey

  • ‘a’ or ‘active’ : no special style defined

pyfda.libs.pyfda_qt_lib.qtext_height(text: str = 'X', font=None) int[source]

Calculate size of text in points`.

The actual size of the string is calculated using fontMetrics and the default or the passed font

Parameters:

test (str) – string to calculate the height for (default: “X”)

Returns:

lineSpacing – The height of the text (line spacing) in points

Return type:

int

Notes

This is based on https://stackoverflow.com/questions/27433165/how-to-reimplement-sizehint-for-bold-text-in-a-delegate-qt

and

https://stackoverflow.com/questions/47285303/how-can-i-limit-text-box-width-of- # qlineedit-to-display-at-most-four-characters/47307180#47307180

https://stackoverflow.com/questions/56282199/fit-qtextedit-size-to-text-size-pyqt5

pyfda.libs.pyfda_qt_lib.qtext_width(text: str = '', N_x: int = 17, bold: bool = True, font=None) int[source]

Calculate width of text in points`. When text=`, calculate the width of number N_x of characters ‘x’.

The actual width of the string is calculated by creating a QTextDocument with the passed text and retrieving its idealWidth()

Parameters:
  • test (str) – string to calculate the width for

  • N_x (int) – When text == ‘’, calculate the width from N_x * width(‘x’)

  • bold (bool (default: True)) – When True, determine width based on bold font

Returns:

width – The width of the text in points

Return type:

int

Notes

This is based on https://stackoverflow.com/questions/27433165/how-to-reimplement-sizehint-for-bold-text-in-a-delegate-qt

and

https://stackoverflow.com/questions/47285303/how-can-i-limit-text-box-width-of- # qlineedit-to-display-at-most-four-characters/47307180#47307180

pyfda.libs.pyfda_qt_lib.qwindow_stay_on_top(win: QDialog, top: bool) None[source]

Set flags for a window such that it stays on top (True) or not

On Windows 7 the new window stays on top anyway. Additionally setting WindowStaysOnTopHint blocks the message window when trying to close pyfda.

On Windows 10 and Linux, WindowStaysOnTopHint needs to be set.

pyfda_io_lib

Library with classes and functions for file and text IO

class pyfda.libs.pyfda_io_lib.NumpyEncoder(*, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None)[source]

Special json encoder for numpy and other non-supported types, building upon https://stackoverflow.com/questions/26646362/numpy-array-is-not-json-serializable

default(obj)[source]

Implement this method in a subclass such that it returns a serializable object for o, or calls the base implementation (to raise a TypeError).

For example, to support arbitrary iterators, you could implement default like this:

def default(self, o):
    try:
        iterable = iter(o)
    except TypeError:
        pass
    else:
        return list(iterable)
    # Let the base class default method raise the TypeError
    return JSONEncoder.default(self, o)
pyfda.libs.pyfda_io_lib.coe_header(title: str) str[source]

Generate a file header (comment) for various FPGA FIR coefficient export formats with information on the filter type, corner frequencies, ripple etc

Parameters:

title (str) – A string that is written in the top of the comment section of the exported file.

Returns:

header – The string with all the gathered information

Return type:

str

pyfda.libs.pyfda_io_lib.create_file_filters(file_types: tuple, file_filters: str = '')[source]

Create a string with file filters for QFileDialog object from file_types, a tuple of file extensions and the global file_filters_dict.

When the file extension stored after last QFileDialog operation is in the tuple of file types, return this file extension for e.g. preselecting the file type in QFileDialog.

Parameters:
  • file_types (tuple of str) – list of file extensions which are used to create a file filter.

  • file_filters (str) – String with file filters for QFileDialog object with the form “Comma / Tab Separated Values (*.csv);; Audio (*.wav *.mp3)”. By default, this string is empty, but it can be used to add file filters not contained in the global file_filters_dict.

Returns:

  • file_filters (str) – String containing file filters for a QFileDialog object

  • last_file_filter (str) – Single file filter to setup the default file extension in QFileDialog

pyfda.libs.pyfda_io_lib.csv2array(f: TextIO)[source]

Convert comma-separated values from file or text to numpy array, taking into accout the settings of the CSV dict.

Parameters:
  • f (TextIO) – handle to file or file-like object, e.g.

  • open(file_name (>>> f =)

  • or ('r') #)

  • io.StringIO(text) (>>> f =)

Returns:

  • data_arr (ndarray) – numpy array of str with table data from file or None when import was unsuccessful

  • Read data as it is, splitting each row into the column items when

    • CSV_dict[‘orientation’] == cols or

    • CSV_dict[‘orientation’] == auto and cols <= rows

  • Transpose data when

    • CSV_dict[‘orientation’] == rows or

    • CSV_dict[‘orientation’] == auto and cols > rows

  • np.shape(data) returns rows, columns

  • While opening a file, the newline parameter can be used to

  • control how universal newlines works (it only applies to text mode).

  • It can be None, ‘’, ‘n’, ‘r’, and ‘rn’. It works as follows

  • - Input (If newline == None, universal newlines mode is enabled. Lines in) – the input can end in ‘n’, ‘r’, or ‘rn’, and these are translated into ‘n’ before being returned to the caller. If it is ‘’, universal newline mode is enabled, but line endings are returned to the caller untranslated. If it has any of the other legal values, input lines are only terminated by the given string, and the line ending is returned to the caller untranslated.

  • - On output, if newline is None, any ‘n’ characters written are translated – to the system default line separator, os.linesep. If newline is ‘’, no translation takes place. If newline is any of the other legal values, any ‘n’ characters written are translated to the given string.

  • Example (convert from Windows-style line endings to Linux:)

  • .. code-block:: python – fileContents = open(filename,”r”).read() f = open(filename,”w”, newline=”n”) f.write(fileContents) f.close()

  • https (//pythonconquerstheuniverse.wordpress.com/2011/05/08/newline-conversion-in-python-3/)

pyfda.libs.pyfda_io_lib.data2array(parent: object, fkey: str, title: str = 'Import', as_str: bool = False)[source]

Copy tabular data from clipboard or file to a numpy array

Parameters:
  • parent (object) – parent instance with a QFileDialog attribute.

  • fkey (str) – Key for accessing data in .npz file or Matlab workspace (.mat)

  • title (str) – title string for the file dialog box

  • as_str (bool) – When True, return ndarray in raw str format, otherwise convert to float or complex

Returns:

table data

Return type:

ndarray of str or None

The following keys from the global dict params['CSV'] are evaluated:

‘delimiter’:

str (default: <tab>), character for separating columns

‘lineterminator’:

str (default: As used by the operating system), character for terminating rows. By default, the character is selected depending on the operating system:

  • Windows: Carriage return + line feed

  • MacOS : Carriage return

  • *nix : Line feed

‘orientation’:

str (one of ‘auto’, ‘horiz’, ‘vert’) determining with which orientation the table is read.

‘header’:

str (‘auto’, ‘on’, ‘off’). When header=='on', treat first row as a header that will be discarded.

‘clipboard’:

bool (default: True). When clipboard == True, copy data from clipboard, else use a file

Parameters that are ‘auto’, will be guessed by csv.Sniffer().

pyfda.libs.pyfda_io_lib.export_coe_TI(f: TextIO) None[source]

Save FIR filter coefficients in TI coefficient format Coefficient have to be specified by an identifier ‘b0 … b191’ followed by the coefficient in normalized fractional format, e.g.

b0 .053647 b1 -.27485 b2 .16497 …

** not implemented yet **

pyfda.libs.pyfda_io_lib.export_coe_cmsis(f: TextIO) None[source]

Get coefficients in SOS format and delete 4th column containing the ‘1.0’ of the recursive parts.

See https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html https://dsp.stackexchange.com/questions/79021/iir-design-scipy-cmsis-dsp-coefficient-format https://github.com/docPhil99/DSP/blob/master/MatlabSOS2CMSIS.m

# TODO: check scipy.signal.zpk2sos for details concerning sos paring

pyfda.libs.pyfda_io_lib.export_coe_microsemi(f: TextIO) bool[source]

Save FIR filter coefficients in Microsemi coefficient format as file ‘*.txt’. Coefficients have to be in integer format, the last line has to be empty. For (anti)symmetric filter only one half of the coefficients must be specified?

pyfda.libs.pyfda_io_lib.export_coe_vhdl_package(f: TextIO) bool[source]

Save FIR filter coefficients as a VHDL package ‘*.vhd’, specifying the number base and the quantized coefficients (decimal or hex integer).

pyfda.libs.pyfda_io_lib.export_coe_xilinx(f: TextIO) bool[source]

Save FIR filter coefficients in Xilinx coefficient format as file ‘*.coe’, specifying the number base and the quantized coefficients (decimal or hex integer).

Returns error status (False if the file was saved successfully)

pyfda.libs.pyfda_io_lib.export_fil_data(parent: object, data: str, fkey: str = '', title: str = 'Export', file_types: Tuple[str, ...] = ('csv', 'mat', 'npy', 'npz'))[source]

Export filter coefficients or pole/zero data in various formats, file name and type are selected via the ui.

Parameters:
  • parent (handle to calling instance for creating file dialog instance)

  • data (str) – formatted as CSV data, i.e. rows of elements separated by ‘delimiter’, terminated by ‘lineterminator’. Some data formats

  • fkey (str) – Key for accessing data in *.npz or Matlab workspace (*.mat) file. When fkey == ‘ba’, exporting to FPGA coefficients format is enabled.

  • title (str) – title string for the file dialog box (e.g. “filter coefficients “)

  • file_types (tuple of strings) – file extension (e.g. (csv) or list of file extensions (e.g. (csv, txt) which are used to create a file filter.

pyfda.libs.pyfda_io_lib.extract_file_ext(file_type: str, return_list: bool = False) str[source]

Extract list with file extension(s), e.g. ‘.vhd’ from type description ‘VHDL (*.vhd)’ returned by QFileDialog. Depending on the OS, this may be the full file type description or just the extension like ‘(*.vhd)’.

When file_type contains no ‘(’, the passed string is returned unchanged.

For an explanation of the RegEx, see the docstring for prune_file_ext.

Parameters:
  • file_type (str)

  • return_list (bool (default = False)) – When True, return a list with file extensions (possibly empty or with only one element), when False (default) only return the first element (a string)

Returns:

The file extension between ( … ), e.g. ‘csv’ or the list of file extension or the unchanged input argument file_type when no ‘(’ was contained.

Return type:

str or list of str

pyfda.libs.pyfda_io_lib.load_data_np(file_name: str, file_type: str, fkey: str = '', as_str: bool = False) ndarray[source]

Import data from a file and convert it to a numpy array.

Parameters:
  • file_name (str) – Full path and name of the file to be imported

  • file_type (str) – File type, currently supported are ‘csv’, ‘mat’, ‘npy’, ‘npz, ‘txt’, ‘wav’.

  • fkey (str) – Key for accessing data in .npz or Matlab workspace (.mat) files with multiple entries.

  • as_str (bool) – When False (default), try to convert results to ndarray of float or complex. Otherwise, return an ndarray of str.

Returns:

Data from the file (ndarray) or None (error)

Return type:

ndarray of float / complex / int or str

pyfda.libs.pyfda_io_lib.load_filter(self) int[source]

Load filter from zipped binary numpy array or (c)pickled object to filter dictionary

pyfda.libs.pyfda_io_lib.prune_file_ext(file_type: str) str[source]

Prune file extension, e.g. ‘Text file’ from ‘Text file (*.txt)’ returned by QFileDialog file type description.

Pruning is achieved with the following regular expression:

return = re.sub('\([^\)]+\)', '', file_type)
Parameters:

file_type (str)

Returns:

The pruned file description

Return type:

str

Notes

Syntax of python regex: re.sub(pattern, replacement, string)

This returns the string obtained by replacing the leftmost non-overlapping occurrences of pattern in string by replacement.

  • ‘.’ means any character

  • ‘+’ means one or more

  • ‘[^a]’ means except for ‘a’

  • ‘([^)]+)’ : match ‘(’, gobble up all characters except ‘)’ till ‘)’

  • ‘(’ must be escaped as ‘\(’

pyfda.libs.pyfda_io_lib.qtable2csv(table: object, data: ndarray, zpk=False, formatted: bool = False) str[source]

Transform QTableWidget data to CSV formatted text

Parameters:
  • table (object) – Instance of QTableWidget

  • data (object) – Instance of the numpy variable shadowing table data

  • zpk (bool) – when True, append the gain (data[2]) to the table

  • formatted (bool) – When True, copy data as formatted in the table, otherwise copy from the model (“shadow”).

The following keys from the global dict dict params['CSV'] are evaluated:

‘delimiter’:

str (default: “,”), character for separating columns

‘lineterminator’:

str (default: As used by the operating system), character for terminating rows. By default, the character is selected depending on the operating system:

  • Windows: Carriage return + line feed

  • MacOS : Carriage return

  • *nix : Line feed

‘orientation’:

str (one of ‘auto’, ‘horiz’, ‘vert’) determining with which orientation the table is written. ‘vert’ means a line break after each entry or pair of entries which usually is not what you want. ‘auto’ doesn’t make much sense when writing, ‘horiz’ is used in this case.

‘header’:

str (default: ‘auto’). When header='on', write the first row with ‘b, a’.

‘clipboard’:

bool (default: True), when clipboard == True, copy data to clipboard, else use a file.

Returns:

Nothing, text is exported to clipboard or to file via export_fil_data

Return type:

None

pyfda.libs.pyfda_io_lib.read_csv_info_old(filename)[source]

DON’T USE ANYMORE! Get infos about the size of a csv file without actually loading the whole file into memory.

See https://stackoverflow.com/questions/64744161/best-way-to-find-out-number-of-rows-in-csv-without-loading-the-full-thing

pyfda.libs.pyfda_io_lib.read_wav_info(file)[source]

Get infos about the following properties of a wav file without actually loading the whole file into memory. This is achieved by reading the header.

pyfda.libs.pyfda_io_lib.save_data_np(file_name: str, file_type: str, data: ndarray, f_S: int = 1, fmt: str = '%f') int[source]

Save numpy ndarray data to a file in wav or csv format

Parameters:
  • file_name (str) – Full path and name of the file to be imported

  • file_type (str) – File type, currently supported are ‘csv’ or ‘wav’

  • data (np.ndarray) – Data to be saved to a file. The data dtype (uint8, int16, int32, float32) determines the bits-per-sample and PCM/float of the WAV file

  • f_S (int (optional)) – Sampling frequency (only used for WAV file format), only integer sampling frequencies are supported by the WAV format.

  • fmt (str (optional)) – Optional, default ‘%f’. Format string, only used for exporting data in CSV format. Other options are e.g. ‘%1.2f’ for reduced number of digits, ‘%d’ for integer format or ‘%s’ for strings.

Return type:

0 for success, -1 for file cancel or error

pyfda.libs.pyfda_io_lib.save_filter(self)[source]

Save filter as JSON formatted textfile, zipped binary numpy array or pickle object

pyfda.libs.pyfda_io_lib.select_file(parent: object, title: str = '', mode: str = 'r', file_types: Tuple[str, ...] = ('csv', 'txt')) Tuple[str, str][source]

Select a file from a file dialog box for either reading or writing and return the selected file name and type.

Parameters:
  • title (str) – title string for the file dialog box (e.g. “Filter Coefficients”),

  • mode (str) – file access mode, must be either “r” or “w” for read / write access

  • file_types (tuple of str) – supported file types, e.g. (‘txt’, ‘npy’, ‘mat’) which need to be keys of `file_filters_dict

Returns:

  • file_name (str) – Fully qualified name of selected file. None when operation has been cancelled.

  • file_type (str) – File type, e.g. ‘wav’. None when operation has been cancelled.

pyfda.libs.pyfda_io_lib.write_wav_frame(parent, file_name, data: array, f_S=1, title: str = 'Export')[source]

Export a frame of data in wav format

Parameters:
  • parent (handle to calling instance for creating file dialog instance)

  • data (np.array) – data to be exported

  • title (str) – title string for the file dialog box (e.g. “audio data “)

pyfda_fix_lib

Fixpoint library for converting numpy scalars and arrays to quantized numpy values and formatting reals in various formats

class pyfda.libs.pyfda_fix_lib.Fixed(q_dict)[source]

Implement binary quantization of signed scalar or array-like objects in the form WI.WF where WI and WF are the wordlength of integer resp. fractional part; total wordlength is W = WI + WF + 1 due to the sign bit.

Examples

Define a dictionary with the format options and pass it to the constructor:

>>> q_dict = {'WI':1, 'WF':14, 'ovfl':'sat', 'quant':'round'}
>>> myQ = Fixed(q_dict)  # instantiate fixpoint quantizer
>>> WI = myQ.q_dict['WI']  # access quantizer parameters
>>> myQ.set_qdict({'WF': 13, 'WI': 2})  # update quantizer parameters
Parameters:
  • q_dict (dict) – define quantization options with the following keys

  • 'WI' (*)

  • 'WF' (*)

  • 'quant' (*) –

    • ‘floor’: (default) largest integer I such that \(I \le x\)

      (= binary truncation)

    • ’round’: (binary) rounding

    • ’fix’: round to nearest integer towards zero (‘Betragsschneiden’)

    • ’ceil’: smallest integer I, such that \(I \ge x\)

    • ’rint’: round towards nearest int

    • ’none’: no quantization

  • 'ovfl' (*) –

    • ‘wrap’: do a two’s complement wrap-around

    • ’sat’ : saturate at minimum / maximum value

    • ’none’: no overflow; the integer word length is ignored

  • N_over (*) – total number of overflows (should be considered as read-only except for when an external quantizer is used)

  • Additionally

  • the (the following keys from global dict fb.fil[0] define)

  • numbers (number base and quantization/overflow behaviour for fixpoint)

  • `'fx_sim'` (*)

  • `'fx_base'` (*) –

    • ‘dec’ : decimal (base = 10)

    • ’bin’ : binary (base = 2)

    • ’hex’ : hexadecimal (base = 16)

    • ’oct’ : octal (base = 8)

    • ’csd’ : canonically signed digit (base = “3”)

  • `'qfrmt'` (*) –

    • ‘qint’ : fixpoint integer format

    • ’qfrac’ : fractional fixpoint format

q_dict

A reference to the quantization dictionary passed during construction (see above). This dictionary is updated here and can be accessed from outside.

Type:

dict

LSB

value of LSB (smallest quantization step), self.LSB = 2 ** -q_dict[‘WF’]

Type:

float

MSB

value of most significant bit (MSB), self.MSB = 2 ** (q_dict[‘WI’] - 1)

Type:

float

MIN

most negative representable value, self.MIN = -2 * self.MSB

Type:

float

MAX

largest representable value, self.MAX = 2 * self.MSB - self.LSB

Type:

float

N

total number of simulation data points

Type:

integer

N_over_neg

number of negative overflows (commented out)

Type:

integer

N_over_pos

number of positive overflows (commented out)

Type:

integer

ovr_flag

overflow flag, meaning:

0 : no overflow

+1: positive overflow

-1: negative overflow

has occured during last fixpoint conversion.

Type:

integer or integer array (same shape as input argument)

places

number of places required for printing in the selected ‘fx_base’ format. For binary formats, this is the same as the wordlength. Calculated from the numeric base ‘fx_base’ and the total word length WI + WF + 1.

Type:

integer

Overflow flags and counters are set in `self.fixp()` and reset in `self.reset_N()`

Example

class Fixed() can be used like the Matlab quantizer() object / function from the fixpoint toolbox, see (Matlab) ‘help round’ and ‘help quantizer/round’ e.g.

MATLAB

>>> q_dsp = quantizer('fixed', 'round', [16 15], 'wrap');
>>> yq = quantize(q_dsp, y)

PYTHON >>> q_dsp = {‘WI’:0, ‘WF’: 15, ‘quant’:’round’, ‘ovfl’:’wrap’} >>> my_q = Fixed(q_dsp) >>> yq = my_q.fixp(y)

fixp(y, in_frmt: str = 'qfrac', out_frmt: str = 'qfrac')[source]

Return a quantized copy yq for y (scalar or array-like) with the same shape as y. The returned data is always in float format, use float2frmt() to obtain different number formats.

This is used a.o. by the following methods / classes:

  • frmt2float(): always returns a float with RWV

  • float2frmt(): starts with RWV, passes on the scaling argument

  • input_coeffs: uses both methods above when quantizing coefficients

Saturation / two’s complement wrapping happens outside the range +/- MSB, requantization (round, floor, fix, …) is applied on the ratio y / LSB.

Fractional number format WI.WF (fb.fil[0][‘qfrmt’] = ‘qfrac’): LSB = 2 ** -WF

  • Multiply float input by 1 / self.LSB = 2**WF, obtaining integer scale

  • Quantize

  • Scale back by multiplying with self.LSB to restore fractional point

  • Find pos. and neg. overflows and replace them by wrapped or saturated values

Integer number format W = 1 + WI + WF (fb.fil[0][‘qfrmt’] = ‘qint’): LSB = 1

  • Multiply float input by 2 ** WF to obtain integer scale

  • Quantize and treat overflows in integer scale

Parameters:
  • y (scalar or array-like object of float) – input value (floating point format) to be quantized

  • in_frmt (str) –

    Determine the scaling before quantizing / saturation ‘qfrac’ (default): fractional float input, y is multiplied by 2 ** WF before quantizing / saturating.

    For all other settings, y is transformed unscaled.

  • out_frmt (str) –

    Determine the scaling after quantizing / saturation ‘qfrac’ (default): fractional fixpoint output format, y is divided by 2 ** WF after quantizing / saturating.

    For all other settings, y is transformed unscaled.

Returns:

yq – with the same shape as y, in the range -2 * self.MSB2 * self.MSB - self.LSB

Return type:

float scalar or ndarray

Examples

>>> q_obj_a = {'WI':1, 'WF':6, 'ovfl':'sat', 'quant':'round'}
>>> myQa = Fixed(q_obj_a) # instantiate fixed-point object myQa
>>> myQa.resetN()  # reset overflow counter
>>> a = np.arange(0,5, 0.05) # create input signal
>>> aq = myQa.fixp(a) # quantize input signal
>>> plt.plot(a, aq) # plot quantized vs. original signal
>>> print(myQa.q_dict('N_over'), "overflows!") # print number of overflows
>>> # Convert output to same format as input:
>>> b = np.arange(200, dtype = np.int16)
>>> btype = np.result_type(b)
>>> # MSB = 2**7, LSB = 2**(-2):
>>> q_obj_b = {'WI':7, 'WF':2, 'ovfl':'wrap', 'quant':'round'}
>>> myQb = Fixed(q_obj_b) # instantiate fixed-point object myQb
>>> bq = myQb.fixp(b)
>>> bq = bq.astype(btype) # restore original variable type
float2frmt(y) str[source]

Convert an array or single value of float / complex / string to a quantized representation in one of the formats float / int / bin / hex / csd.

Called a.o. by itemDelegate.displayText() for on-the-fly number conversion. Returns fixpoint representation for y (scalar or array-like) with numeric format self.frmt and a total wordlength of W = self.q_dict[‘WI’] + self.q_dict[‘WF’] + 1 bits. The result has the same shape as y.

The float is always quantized / saturated using self.fixp() before it is converted to different fixpoint number bases.

Parameters:

y (scalar or array-like) – y has to be an integer, float or complex decimal number

Returns:

The numeric format is set in fb.fil[0][‘fx_base’]). It has the same shape as y. For all formats except float a fixpoint representation with a total number of W = WI + WF + 1 binary digits is returned.

Return type:

str, float or an ndarray of float or string

frmt2float(y)[source]

Return floating point representation for fixpoint y (scalar or array) given in format fb.fil[0][‘fx_base’].

When input format is float, return unchanged.

Else:

  • Remove illegal characters and leading ‘0’s

  • Count number of fractional places frc_places and remove radix point

  • Calculate decimal, fractional representation y_dec of string, using the base and the number of fractional places

  • Calculate two’s complement for W bits (only for negative bin and hex numbers)

  • Calculate fixpoint float representation y_float = fixp(y_dec, out_frmt=’qfrmt’), dividing the result by 2**WF.

Parameters:

y (scalar or string or array of scalars or strings in number format float or) – fb.fil[0][‘fx_base’] (‘dec’, ‘hex’, ‘oct’, ‘bin’ or ‘csd’)

Returns:

  • Quantized floating point (dtype=np.float64) representation of input string

  • of same shape as y.

frmt2float_scalar(y: str) float[source]

Convert a string in ‘dec’, ‘bin’, ‘oct’, ‘hex’, ‘csd’ numeric format to float.

  • format is taken from the global fb.fil[0][‘fx_base’]

  • maximum wordlength is determined from the local quantization dict keys self.q_dict[‘WI’] and self.q_dict[‘WF’]

  • negative numbers can be represented by a ‘-’ sign or in two’s complement

  • represented numbers may be fractional and / or complex.

  • the result is divided by 2**WF for fb.fil[0][‘qfrmt’] == ‘qint’ in fixp()

Parameters:

y (str) – A string formatted as a decimal, binary, octal, hex or csd number representation. The number string may contain a ‘.’ or ‘,’ to represent fractal numbers. When the string contains a ‘j’m it is tried to split the string into real and imaginary part.

Returns:

The float / complex representation of the string

Return type:

float or complex

requant(x_i, QI)[source]

Change word length of input signal x_i with fractional and integer widths defined by ‘QI’ to the word format defined by self.q_dict using the quantization and saturation methods specified by self.q_dict[‘quant’] and self.q_dict[‘ovfl’].

Input and output word are aligned at their binary points.

Parameters:
  • self (Fixed() object) – self.qdict() is the quantizer dict that specifies the output word format and the requantizing / saturation methods to be used.

  • x_i (int, float or complex scalar or array-like) – signal to be requantized with quantization format defined in quantizer QI

  • QI (quantizer) – Quantizer for input word, only the keys ‘WI’ and ‘WF’ for integer and fractional wordlength are evaluated. QI.q_dict[‘WI’] = 2 and QI.q_dict[‘WF’] = 13 e.g. define Q-Format ‘2.13’ with 2 integer, 13 fractional bits and 1 implied sign bit = 16 bits total.

Returns:

y – requantized output data with same shape as input data, quantized as specified in self.qdict.

Return type:

any

Example

The following shows an example of rescaling an input word from Q2.4 to Q0.3 using wrap-around and truncation. It’s easy to see that for simple wrap-around logic, the sign of the result may change.

S | WI1 | WI0 * WF0 | WF1 | WF2 | WF3  :  WI = 2, WF = 4, W = 7
0 |  1  |  0  *  1  |  0  |  1  |  1   =  43 (dec) or 43/16 = 2 + 11/16 (float)
            *
        |  S  * WF0 | WF1 | WF2        :  WI = 0, WF = 3, W = 4
        0  *  1  |  0  |  1         =  7 (dec) or 7/8 (float)

When the input is integer format, the fractional value is calculated as an intermediate representation by multiplying the integer value by 2 ** (-WF). Integer and fractional part are truncated / extended to the output quantization specifications.

Changes in the number of integer bits dWI and fractional bits dWF are handled separately.

When operating on bit level in hardware, the following operations are used:

Fractional Bits

  • For reducing the number of fractional bits by dWF, simply right-shift the integer number by dWF. For rounding, add ‘1’ to the bit below the truncation point before right-shifting.

  • Extend the number of fractional bits by left-shifting the integer by dWF, LSB’s are filled with zeros.

Integer Bits

  • For reducing the number of integer bits by dWI, simply right-shift the integer by dWI.

  • The number of fractional bits is SIGN-EXTENDED by filling up the left-most bits with the sign bit.

resetN()[source]

Reset counters and overflow-flag of Fixed object

set_qdict(d: dict) None[source]

Update the instance quantization dict self.q_dict from passed dict d:

  • Sanitize WI and WF

  • Calculate attributes MSB, LSB, MIN and MAX

  • Calculate number of places needed for printing from qfrmt,`fx_base` and W and store it as attribute self.places

Check the docstring of class Fixed() for details on quantization

verify_q_dict_keys(q_dict: dict) None[source]

Check against self.q_dict_default dictionary whether all keys in the passed q_dict dictionary are valid.

Unknown keys throw an error message.

pyfda.libs.pyfda_fix_lib.bin2hex(bin_str: str, WI=0) str[source]

Convert number bin_str in binary format to hex formatted string. bin_str is prepended / appended with zeros until the number of bits before and after the radix point (position given by WI) is a multiple of 4.

pyfda.libs.pyfda_fix_lib.bin2oct(bin_str: str, WI=0) str[source]

Convert number bin_str in binary format to octal formatted string. bin_str is prepended / appended with zeros until the number of bits before and after the radix point (position given by WI) is a multiple of 3.

pyfda.libs.pyfda_fix_lib.csd2dec(csd_str)[source]

Convert the CSD string csd_str to a decimal, csd_str may contain ‘+’ or ‘-’, indicating whether the current bit is meant to positive or negative. All other characters are simply ignored (= replaced by zero).

csd_str has to be an integer CSD number.

Parameters:

csd_str (string) – A string with the CSD value to be converted, consisting of ‘+’, ‘-’, ‘.’ and ‘0’ characters.

Return type:

decimal (integer) value of the CSD string

Examples

+00- = +2³ - 2⁰ = +7

-0+0 = -2³ + 2¹ = -6

pyfda.libs.pyfda_fix_lib.dec2csd(dec_val, WF=0)[source]

Convert the argument dec_val to a string in CSD Format.

Parameters:
  • dec_val (scalar (integer or real)) – decimal value to be converted to CSD format

  • WF (integer) – number of fractional places. Default is WF = 0 (integer number)

Returns:

  • string – containing the CSD value

  • Original author (Harnesser)

  • https (//sourceforge.net/projects/pycsd/)

  • License (GPL2)

pyfda.libs.pyfda_fix_lib.dec2hex(val, nbits, WF=0)[source]

— currently not used, no unit test —

Return val in hex format with a wordlength of nbits in two’s complement format. The built-in hex function returns args < 0 as negative values. When val >= 2**nbits, it is “wrapped” around to the range 0 … 2**nbits-1

Parameters:
  • val (integer) – The value to be converted in decimal integer format.

  • nbits (integer) – The wordlength

Return type:

A string in two’s complement hex format

pyfda.libs.pyfda_fix_lib.qstr(text)[source]

carefully replace qstr() function - only needed for Py2 compatibility

pyfda.libs.pyfda_fix_lib.quant_coeffs(coeffs: iterable, Q, recursive: bool = False, out_frmt: str = '') ndarray[source]

Quantize the coefficients, scale and convert them to a list of integers, using the quantization settings of Fixed() instance Q and global setting fb.fil[0][‘qfrmt’] (‘qfrac’ or ‘qint’) and fb.fil[0][‘fx_sim’] (True or False)

Parameters:
  • coeffs (iterable) – An iterable of coefficients to be quantized

  • Q (object) – Instance of Fixed object containing quantization dict q_dict

  • recursive (bool) – When False (default), process all coefficients. When True, The first coefficient is ignored (must be 1)

  • out_frmt (str) – Output quantization format (“qint” or “qfrac”). When nothing is specified, use the global setting from fb.fil[0][‘qfrmt’].

Returns:

  • A numpy array of integer coeffcients, quantized and scaled with the

  • settings of the quantization object dict.