diff --git a/applications/lofar2/model/pfb_os/dsp_fpga_lib.py b/applications/lofar2/model/pfb_os/dsp_fpga_lib.py
new file mode 100644
index 0000000000000000000000000000000000000000..55077a0538b8fa06f894d7b6861b04484d2c9019
--- /dev/null
+++ b/applications/lofar2/model/pfb_os/dsp_fpga_lib.py
@@ -0,0 +1,1199 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Mon Apr 30 10:29:42 2012
+dsp_fpga_lib (Version 8)
+@author: Muenker_2
+from: https://github.com/chipmuenk/dsp
+"""
+import sys
+import string # needed for remezord?
+import numpy as np
+import numpy.ma as ma
+from numpy import pi, asarray, absolute, sqrt, log10, arctan, ceil, hstack, mod
+
+import scipy.signal as sig
+from scipy import __version__ as sci_version
+from scipy import special # needed for remezord
+import scipy.spatial.distance as sc_dist
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+from  matplotlib import patches
+
+__version__ = "0.9"
+def versions():
+    print("Python version:", ".".join(map(str, sys.version_info[:3])))
+    print("Numpy:", np.__version__)
+    print("Scipy:", sci_version)
+    print("Matplotlib:", mpl.__version__, mpl.get_backend())
+
+mpl_rc = {'lines.linewidth'           : 1.5,
+          'lines.markersize'          : 8,  # markersize in points
+          'text.color'                : 'black',
+          'font.family'               : 'sans-serif',#'serif',
+          'font.style'                : 'normal',
+          #'mathtext.fontset'          : 'stixsans',#'stix',
+          #'mathtext.fallback_to_cm'   : True,
+          'mathtext.default'          : 'it',
+          'font.size'                 : 12,
+          'legend.fontsize'           : 12,
+          'axes.labelsize'            : 12,
+          'axes.titlesize'            : 14,
+          'axes.linewidth'            : 1, # linewidth for coordinate system
+          'axes.formatter.use_mathtext': True, # use mathtext for scientific notation.
+          #
+          'axes.grid'                 : True,
+          'grid.color'                : '#222222',
+          'axes.facecolor'            : 'white',
+          'axes.labelcolor'           : 'black',
+          'axes.edgecolor'            : 'black',
+          'grid.linestyle'            : ':',
+          'grid.linewidth'            : 0.5,
+          #
+          'xtick.top'                 : False,     # mpl >= 2.0
+          'xtick.direction'           : 'out',
+          'ytick.direction'           : 'out',
+          'xtick.color'               : 'black',
+          'ytick.color'               : 'black',
+          #
+          'figure.figsize'            : (7,4), # default figure size in inches
+          'figure.facecolor'          : 'white',
+          'figure.edgecolor'          : '#808080',
+          'figure.dpi'                : 100,
+          'savefig.dpi'               : 100,
+          'savefig.facecolor'         : 'white',
+          'savefig.edgecolor'         : 'white',
+          'savefig.bbox'              : 'tight',
+          'savefig.pad_inches'        : 0,
+          'hatch.color'               : '#808080', # mpl >= 2.0
+          'hatch.linewidth'           : 0.5,       # mpl >= 2.0
+          'animation.html'          : 'jshtml'   # javascript, mpl >= 2.1
+           }
+
+mpl_rc_33 = {'mathtext.fallback'      : 'cm'} # new since mpl 3.3
+mpl_rc_32 = {'mathtext.fallback_to_cm': True} # deprecated since mpl 3.3
+
+if mpl.__version__ < "3.3":
+    mpl_rc.update(mpl_rc_32) # lower than matplotlib 3.3
+else:
+    mpl_rc.update(mpl_rc_33)
+
+plt.rcParams.update(mpl_rc) # define plot properties
+
+def H_mag(zaehler, nenner, z, lim):
+    """ Calculate magnitude of H(z) or H(s) in polynomial form at the complex
+    coordinate z = x, 1j * y (skalar or array)
+    The result is clipped at lim."""
+#    limvec = lim * np.ones(len(z))
+    try: len(zaehler)
+    except TypeError:
+        z_val = abs(zaehler) # zaehler is a scalar
+    else:
+        z_val = abs(np.polyval(zaehler,z)) # evaluate zaehler at z
+    try: len(nenner)
+    except TypeError:
+        n_val = nenner # nenner is a scalar
+    else:
+        n_val = abs(np.polyval(nenner,z))
+
+    return np.minimum((z_val/n_val),lim)
+
+
+#----------------------------------------------
+# from scipy.sig.signaltools.py:
+def cmplx_sort(p):
+    "sort roots based on magnitude."
+    p = np.asarray(p)
+    if np.iscomplexobj(p):
+        indx = np.argsort(abs(p))
+    else:
+        indx = np.argsort(p)
+    return np.take(p, indx, 0), indx
+
+# adapted from scipy.signal.signaltools.py:
+# TODO:  comparison of real values has several problems (5 * tol ???)
+def unique_roots(p, tol=1e-3, magsort = False, rtype='min', rdist='euclidian'):
+    """
+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 = sp.signal.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 = sp.signal.unique_roots(vals, rtype='avg')
+
+"""
+
+    def manhattan(a,b):
+        """
+        Manhattan distance between a and b
+        """
+        return ma.abs(a.real - b.real) + ma.abs(a.imag - b.imag)
+
+    def euclid(a,b):
+        """
+        Euclidian distance between a and b
+        """
+        return ma.abs(a - b)
+
+    if rtype in ['max', 'maximum']:
+        comproot = ma.max  # nanmax ignores nan's
+    elif rtype in ['min', 'minimum']:
+        comproot = ma.min  # nanmin ignores nan's
+    elif rtype in ['avg', 'mean']:
+        comproot = ma.mean # nanmean ignores nan's
+#    elif rtype == 'median':
+    else:
+        raise TypeError(rtype)
+
+    if rdist in ['euclid', 'euclidian']:
+        dist_roots = euclid
+    elif rdist in ['rect', 'manhattan']:
+        dist_roots = manhattan
+    else:
+        raise TypeError(rdist)
+
+    mult = [] # initialize list for multiplicities
+    pout = [] # initialize list for reduced output list of roots
+    p = np.atleast_1d(p) # convert p to at least 1D array
+    tol = abs(tol)
+
+    if len(p) == 0:  # empty argument, return empty lists
+        return pout, mult
+
+    elif len(p) == 1: # scalar input, return arg with multiplicity = 1
+        pout = p
+        mult = [1]
+        return pout, mult
+
+    else:
+        sameroots = [] # temporary list for roots within the tolerance
+        pout = p[np.isnan(p)].tolist() # copy nan elements to pout as list
+        mult = len(pout) * [1] # generate a list with a "1" for each nan
+        #p = ma.masked_array(p[~np.isnan(p)]) # delete nan elements, convert to ma
+        p = np.ma.masked_where(np.isnan(p), p) # only masks nans, preferrable?
+
+    if np.iscomplexobj(p) and not magsort:
+
+        for i in range(len(p)): # p[i] is current root under test
+            if not p[i] is ma.masked: # has current root been "deleted" yet?
+                tolarr = dist_roots(p[i], p[i:]) < tol # test against itself and
+                # subsequent roots, giving a multiplicity of at least one
+                mult.append(np.sum(tolarr)) # multiplicity = number of "hits"
+                sameroots = p[i:][tolarr]   # pick the roots within the tolerance
+                p[i:] = ma.masked_where(tolarr, p[i:]) # and "delete" (mask) them
+                pout.append(comproot(sameroots)) # avg/mean/max of mult. root
+
+    else:
+        p,indx = cmplx_sort(p)
+        indx = -1
+        curp = p[0] + 5 * tol # needed to avoid "self-detection" ?
+        for k in range(len(p)):
+            tr = p[k]
+#            if dist_roots(tr, curp) < tol:
+            if abs(tr - curp) < tol:
+                sameroots.append(tr)
+                curp = comproot(sameroots)  # not correct for 'avg'
+                                            # of multiple (N > 2) root !
+                pout[indx] = curp
+                mult[indx] += 1
+            else:
+                pout.append(tr)
+                curp = tr
+                sameroots = [tr]
+                indx += 1
+                mult.append(1)
+
+    return np.array(pout), np.array(mult)
+
+##### original code ####
+#    p = asarray(p) * 1.0
+#    tol = abs(tol)
+#    p, indx = cmplx_sort(p)
+#    pout = []
+#    mult = []
+#    indx = -1
+#    curp = p[0] + 5 * tol
+#    sameroots = []
+#    for k in range(len(p)):
+#        tr = p[k]
+#        if abs(tr - curp) < tol:
+#            sameroots.append(tr)
+#            curp = comproot(sameroots)
+#            pout[indx] = curp
+#            mult[indx] += 1
+#        else:
+#            pout.append(tr)
+#            curp = tr
+#            sameroots = [tr]
+#            indx += 1
+#            mult.append(1)
+#    return array(pout), array(mult)
+
+#------------------------------------------------------------------------------
+
+
+def zplane(b=None, a=1, z=None, p=None, k=1,  pn_eps=1e-3, analog=False,
+          plt_ax = None, plt_poles=True, style='square', anaCircleRad=0, lw=2,
+          mps = 10, mzs = 10, mpc = 'r', mzc = 'b', plabel = '', zlabel = ''):
+    """
+    Plot the poles and zeros in the complex z-plane either from the
+    coefficients (`b,`a) of a discrete transfer function `H`(`z`) (zpk = False)
+    or directly from the zeros and poles (z,p) (zpk = True).
+
+    When only b is given, an FIR filter with all poles at the origin is assumed.
+
+    Parameters
+    ----------
+    b :  array_like
+         Numerator coefficients (transversal part of filter)
+         When b is not None, poles and zeros are determined from the coefficients
+         b and a
+
+    a :  array_like (optional, default = 1 for FIR-filter)
+         Denominator coefficients (recursive part of filter)
+
+    z :  array_like, default = None
+         Zeros
+         When b is None, poles and zeros are taken directly from z and p
+
+    p :  array_like, default = None
+         Poles
+
+    analog : boolean (default: False)
+        When True, create a P/Z plot suitable for the s-plane, i.e. suppress
+        the unit circle (unless anaCircleRad > 0) and scale the plot for
+        a good display of all poles and zeros.
+
+    pn_eps : float (default : 1e-2)
+         Tolerance for separating close poles or zeros
+
+    plt_ax : handle to axes for plotting (default: None)
+        When no axes is specified, the current axes is determined via plt.gca()
+
+    plt_poles : Boolean (default : True)
+        Plot poles. This can be used to suppress poles for FIR systems
+        where all poles are at the origin.
+
+    style : string (default: 'square')
+        Style of the plot, for style == 'square' make scale of x- and y-
+        axis equal.
+
+    mps : integer  (default: 10)
+        Size for pole marker
+
+    mzs : integer (default: 10)
+        Size for zero marker
+
+    mpc : char (default: 'r')
+        Pole marker colour
+
+    mzc : char (default: 'b')
+        Zero marker colour
+
+    lw : integer (default:  2)
+        Linewidth for unit circle
+
+    plabel, zlabel : string (default: '')
+        This string is passed to the plot command for poles and zeros and
+        can be displayed by legend()
+
+
+    Returns
+    -------
+    z, p, k : ndarray
+
+
+    Notes
+    -----
+    """
+    # TODO:
+    # - polar option
+    # - add keywords for color of circle -> **kwargs
+    # - add option for multi-dimensional arrays and zpk data
+
+    # make sure that all inputs are arrays
+    b = np.atleast_1d(b)
+    a = np.atleast_1d(a)
+    z = np.atleast_1d(z) # make sure that p, z  are arrays
+    p = np.atleast_1d(p)
+
+    if b.any(): # coefficients were specified
+        if len(b) < 2 and len(a) < 2:
+            logger.error('No proper filter coefficients: both b and a are scalars!')
+            return z, p, k
+
+        # The coefficients are less than 1, normalize the coefficients
+        if np.max(b) > 1:
+            kn = np.max(b)
+            b = b / float(kn)
+        else:
+            kn = 1.
+
+        if np.max(a) > 1:
+            kd = np.max(a)
+            a = a / abs(kd)
+        else:
+            kd = 1.
+
+        # Calculate the poles, zeros and scaling factor
+        p = np.roots(a)
+        z = np.roots(b)
+        k = kn/kd
+    elif not (len(p) or len(z)): # P/Z were specified
+        logger.error('Either b,a or z,p must be specified!')
+        return z, p, k
+
+    # find multiple poles and zeros and their multiplicities
+    if len(p) < 2: # single pole, [None] or [0]
+        if not p or p == 0: # only zeros, create equal number of poles at origin
+            p = np.array(0,ndmin=1) #
+            num_p = np.atleast_1d(len(z))
+        else:
+            num_p = [1.] # single pole != 0
+    else:
+        #p, num_p = sig.signaltools.unique_roots(p, tol = pn_eps, rtype='avg')
+        p, num_p = unique_roots(p, tol = pn_eps, rtype='avg')
+#        p = np.array(p); num_p = np.ones(len(p))
+    if len(z) > 0:
+        z, num_z = unique_roots(z, tol = pn_eps, rtype='avg')
+#        z = np.array(z); num_z = np.ones(len(z))
+        #z, num_z = sig.signaltools.unique_roots(z, tol = pn_eps, rtype='avg')
+    else:
+        num_z = []
+
+    ax = plt_ax#.subplot(111)
+    if analog == False:
+        # create the unit circle for the z-plane
+        uc = patches.Circle((0,0), radius=1, fill=False,
+                            color='grey', ls='solid', zorder=1)
+        ax.add_patch(uc)
+        if style == 'square':
+            #r = 1.1
+            #ax.axis([-r, r, -r, r]) # overridden by next option
+            ax.axis('equal')
+    #    ax.spines['left'].set_position('center')
+    #    ax.spines['bottom'].set_position('center')
+    #    ax.spines['right'].set_visible(True)
+    #    ax.spines['top'].set_visible(True)
+
+    else: # s-plane
+        if anaCircleRad > 0:
+            # plot a circle with radius = anaCircleRad
+            uc = patches.Circle((0,0), radius=anaCircleRad, fill=False,
+                                color='grey', ls='solid', zorder=1)
+            ax.add_patch(uc)
+        # plot real and imaginary axis
+        ax.axhline(lw=2, color = 'k', zorder=1)
+        ax.axvline(lw=2, color = 'k', zorder=1)
+
+    # Plot the zeros
+    ax.scatter(z.real, z.imag, s=mzs*mzs, zorder=2, marker = 'o',
+               facecolor = 'none', edgecolor = mzc, lw = lw, label=zlabel)
+    # and print their multiplicity
+    for i in range(len(z)):
+        if num_z[i] > 1:
+            ax.text(np.real(z[i]), np.imag(z[i]),'  (' + str(num_z[i]) +')',
+                            va = 'top', color=mzc)
+    if plt_poles:
+        # Plot the poles
+        ax.scatter(p.real, p.imag, s=mps*mps, zorder=2, marker='x',
+                   color=mpc, lw=lw, label=plabel)
+        # and print their multiplicity
+        for i in range(len(p)):
+            if num_p[i] > 1:
+                ax.text(np.real(p[i]), np.imag(p[i]), '  (' + str(num_p[i]) +')',
+                                va = 'bottom', color=mpc)
+
+# =============================================================================
+#            # increase distance between ticks and labels
+#            # to give some room for poles and zeros
+#         for tick in ax.get_xaxis().get_major_ticks():
+#             tick.set_pad(12.)
+#             tick.label1 = tick._get_text1()
+#         for tick in ax.get_yaxis().get_major_ticks():
+#             tick.set_pad(12.)
+#             tick.label1 = tick._get_text1()
+#
+# =============================================================================
+    xl = ax.get_xlim(); Dx = max(abs(xl[1]-xl[0]), 0.05)
+    yl = ax.get_ylim(); Dy = max(abs(yl[1]-yl[0]), 0.05)
+    ax.set_xlim((xl[0]-Dx*0.05, max(xl[1]+Dx*0.05,0)))
+    ax.set_ylim((yl[0]-Dy*0.05, yl[1] + Dy*0.05))
+
+    return z, p, k
+
+#------------------------------------------------------------------------------
+def zplane_bak(b=None, a=1, z=None, p=None, k=1,  pn_eps=1e-3, analog=False,
+              plt_ax = None, plt_poles=True, style='square', anaCircleRad=0, lw=2,
+              mps = 10, mzs = 10, mpc = 'r', mzc = 'b', plabel = '', zlabel = ''):
+        """
+        Plot the poles and zeros in the complex z-plane either from the
+        coefficients (`b,`a) of a discrete transfer function `H`(`z`) (b specified)
+        or directly from the zeros and poles (z,p specified).
+
+        When only b is given, an FIR filter with all poles at the origin is assumed.
+
+        Parameters
+        ----------
+        b :  array_like
+             Numerator coefficients (transversal part of filter)
+             When b is not None, poles and zeros are determined from the coefficients
+             b and a
+
+        a :  array_like (optional, default = 1 for FIR-filter)
+             Denominator coefficients (recursive part of filter)
+
+        z :  array_like, default = None
+             Zeros
+             When b is None, poles and zeros are taken directly from z and p
+
+        p :  array_like, default = None
+             Poles
+
+        analog : boolean (default: False)
+            When True, create a P/Z plot suitable for the s-plane, i.e. suppress
+            the unit circle (unless anaCircleRad > 0) and scale the plot for
+            a good display of all poles and zeros.
+
+        pn_eps : float (default : 1e-2)
+             Tolerance for separating close poles or zeros
+
+        plt_ax : handle to axes for plotting (default: None)
+            When no axes is specified, the current axes is determined via plt.gca()
+
+        plt_poles : Boolean (default : True)
+            Plot poles. This can be used to suppress poles for FIR systems
+            where all poles are at the origin.
+
+        style : string (default: 'square')
+            Style of the plot, for style == 'square' make scale of x- and y-
+            axis equal.
+
+        mps : integer  (default: 10)
+            Size for pole marker
+
+        mzs : integer (default: 10)
+            Size for zero marker
+
+        mpc : char (default: 'r')
+            Pole marker colour
+
+        mzc : char (default: 'b')
+            Zero marker colour
+
+        lw : integer (default:  2)
+            Linewidth for unit circle
+
+        plabel, zlabel : string (default: '')
+            This string is passed to the plot command for poles and zeros and
+            can be displayed by legend()
+
+
+        Returns
+        -------
+        z, p, k : ndarray
+
+
+        Notes
+        -----
+        """
+        # TODO:
+        # - polar option
+        # - add keywords for color of circle -> **kwargs
+        # - add option for multi-dimensional arrays and zpk data
+
+        # make sure that all inputs are arrays
+        b = np.atleast_1d(b)
+        a = np.atleast_1d(a)
+        z = np.atleast_1d(z) # make sure that p, z  are arrays
+        p = np.atleast_1d(p)
+
+        if b.any(): # coefficients were specified
+            if len(b) < 2 and len(a) < 2:
+                logger.error('No proper filter coefficients: both b and a are scalars!')
+                return z, p, k
+
+            # The coefficients are less than 1, normalize the coefficients
+            if np.max(b) > 1:
+                kn = np.max(b)
+                b = b / float(kn)
+            else:
+                kn = 1.
+
+            if np.max(a) > 1:
+                kd = np.max(a)
+                a = a / abs(kd)
+            else:
+                kd = 1.
+
+            # Calculate the poles, zeros and scaling factor
+            p = np.roots(a)
+            z = np.roots(b)
+            k = kn/kd
+        elif not (len(p) or len(z)): # P/Z were specified
+            print('Either b,a or z,p must be specified!')
+            return z, p, k
+
+        # find multiple poles and zeros and their multiplicities
+        if len(p) < 2: # single pole, [None] or [0]
+            if not p or p == 0: # only zeros, create equal number of poles at origin
+                p = np.array(0,ndmin=1) #
+                num_p = np.atleast_1d(len(z))
+            else:
+                num_p = [1.] # single pole != 0
+        else:
+            #p, num_p = sig.signaltools.unique_roots(p, tol = pn_eps, rtype='avg')
+            p, num_p = unique_roots(p, tol = pn_eps, rtype='avg')
+    #        p = np.array(p); num_p = np.ones(len(p))
+        if len(z) > 0:
+            z, num_z = unique_roots(z, tol = pn_eps, rtype='avg')
+    #        z = np.array(z); num_z = np.ones(len(z))
+            #z, num_z = sig.signaltools.unique_roots(z, tol = pn_eps, rtype='avg')
+        else:
+            num_z = []
+
+        if not plt_ax:
+            ax = plt.gca()# fig.add_subplot(111)
+        else:
+            ax = plt_ax
+
+        if analog == False:
+            # create the unit circle for the z-plane
+            uc = patches.Circle((0,0), radius=1, fill=False,
+                                color='grey', ls='solid', zorder=1)
+            ax.add_patch(uc)
+            if style == 'square':
+                r = 1.1
+                ax.axis([-r, r, -r, r])
+                ax.axis('equal')
+        #    ax.spines['left'].set_position('center')
+        #    ax.spines['bottom'].set_position('center')
+        #    ax.spines['right'].set_visible(True)
+        #    ax.spines['top'].set_visible(True)
+
+        else: # s-plane
+            if anaCircleRad > 0:
+                # plot a circle with radius = anaCircleRad
+                uc = patches.Circle((0,0), radius=anaCircleRad, fill=False,
+                                    color='grey', ls='solid', zorder=1)
+                ax.add_patch(uc)
+            # plot real and imaginary axis
+            ax.axhline(lw=2, color = 'k', zorder=1)
+            ax.axvline(lw=2, color = 'k', zorder=1)
+
+        # Plot the zeros
+        ax.scatter(z.real, z.imag, s=mzs*mzs, zorder=2, marker = 'o',
+                   facecolor = 'none', edgecolor = mzc, lw = lw, label=zlabel)
+        # and print their multiplicity
+        for i in range(len(z)):
+            if num_z[i] > 1:
+                ax.text(np.real(z[i]), np.imag(z[i]),'  (' + str(num_z[i]) +')',
+                                va = 'top', color=mzc)
+        if plt_poles:
+            # Plot the poles
+            ax.scatter(p.real, p.imag, s=mps*mps, zorder=2, marker='x',
+                       color=mpc, lw=lw, label=plabel)
+            # and print their multiplicity
+            for i in range(len(p)):
+                if num_p[i] > 1:
+                    ax.text(np.real(p[i]), np.imag(p[i]), '  (' + str(num_p[i]) +')',
+                                    va = 'bottom', color=mpc)
+
+            # increase distance between ticks and labels
+            # to give some room for poles and zeros
+        for tick in ax.get_xaxis().get_major_ticks():
+            tick.set_pad(12.)
+            tick.label1 = tick._get_text1()
+        for tick in ax.get_yaxis().get_major_ticks():
+            tick.set_pad(12.)
+            tick.label1 = tick._get_text1()
+
+        xl = ax.get_xlim(); Dx = max(abs(xl[1]-xl[0]), 0.05)
+        yl = ax.get_ylim(); Dy = max(abs(yl[1]-yl[0]), 0.05)
+        ax.set_xlim((xl[0]-Dx*0.05, max(xl[1]+Dx*0.05,0)))
+        ax.set_ylim((yl[0]-Dy*0.05, yl[1] + Dy*0.05))
+
+        return z, p, k
+
+#------------------------------------------------------------------------------
+def impz(b, a=1, FS=1, N=1, step=False):
+    """
+    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, default N=1 for automatic calculation)
+         Number of calculated points.
+         Default: N = len(b) for FIR filters, N = 100 for IIR filters
+
+    step: boolean (optional, default: step=False)
+         plot step response instead of impulse response
+
+    Returns
+    -------
+    hn : ndarray with length N (see above)
+    td : ndarray containing the time steps with same
+
+
+    Examples
+    --------
+    >>> b = [1,2,3] # Coefficients of H(z) = 1 + 2 z^2 + 3 z^3
+    >>> h, n = dsp_lib.impz(b)
+    """
+    try: len(a) #len_a = len(a)
+    except TypeError:
+         # a has len = 1 -> FIR-Filter
+        impulse = np.repeat(0.,len(b)) # create float array filled with 0.
+        try: len(b)
+        except TypeError:
+            print('No proper filter coefficients: len(a) = len(b) = 1 !')
+    else:
+        try: len(b)
+        except TypeError: b = [b,] # convert scalar to array with len = 1
+        impulse = np.repeat(0.,100)  # IIR-Filter
+    if N > 1:
+        impulse = np.repeat(0.,N)
+    impulse[0] =1.0 # create dirac impulse
+    hn = np.array(sig.lfilter(b,a,impulse)) # calculate impulse response
+    td = np.arange(len(hn)) / FS
+
+    if step:
+        hn = np.cumsum(hn) # integrate impulse response to get step response
+    return hn, td
+
+#==================================================================
+def grpdelay(b, a=1, nfft=512, whole='none', analog=False, Fs=2.*pi):
+#==================================================================
+    """
+    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 : string (optional, default : 'none')
+         Only when whole = 'whole' calculate group delay around
+         the complete unit circle (0 ... 2 pi)
+
+    N :  integer (optional, default: 512)
+         Number of FFT-points
+
+    FS : float (optional, default: FS = 2*pi)
+         Sampling frequency.
+
+
+    Returns
+    -------
+    tau_g : ndarray
+        The group delay
+
+
+    w : ndarray
+        The angular frequency points where the group delay was computed
+
+    Notes
+    -----
+    The group delay :math:`\\tau_g(\\omega)` of discrete and continuous time
+    systems is defined by
+
+    .. math::
+
+        \\tau_g(\\omega) = -  \\phi'(\\omega)
+            = -\\frac{\\partial \\phi(\\omega)}{\\partial \\omega}
+            = -\\frac{\\partial }{\\partial \\omega}\\angle H( \\omega)
+
+    A useful form for calculating the group delay is obtained by deriving the
+    *logarithmic* frequency response in polar form as described in [JOS]_ for
+    discrete time systems:
+
+    .. math::
+
+        \\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)
+
+    where :math:`H_A(\\omega)` is the amplitude response. :math:`H_A(\\omega)` and
+    its derivative :math:`H_A'(\\omega)` are real-valued, therefore, the group
+    delay can be calculated from
+
+    .. math::
+
+          \\tau_g(\\omega) = -\\phi'(\\omega) =
+          -\\Im \\left\\{ \\frac{\\partial }{\\partial \\omega}
+          \\ln ( H( \\omega)) \\right\\}
+          =-\\Im \\left\\{ \\frac{H'(\\omega)}{H(\\omega)} \\right\\}
+
+    The derivative of a polynome :math:`P(s)` (continuous-time system) or :math:`P(z)`
+    (discrete-time system) w.r.t. :math:`\\omega` is calculated by:
+
+    .. math::
+
+        \\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)
+
+        \\frac{\\partial }{\\partial \\omega} P(z = e^{j \\omega T})
+        = \\frac{\\partial }{\\partial \\omega} \\sum_{k = 0}^N c_k e^{-j k \\omega T}
+        =  -jT \\sum_{k = 0}^{N} k c_{k} e^{-j k \\omega T}
+        =  -jT P_R(z = e^{j \\omega T})
+
+    where :math:`P_R` is the "ramped" polynome, i.e. its `k` th coefficient is
+    multiplied by `k` resp. `k` + 1.
+
+    yielding:
+
+    .. math::
+
+        \\tau_g(\\omega) = -\\Im \\left\\{ \\frac{H'(\\omega)}{H(\\omega)} \\right\\}
+        \\quad \\text{ resp. } \\quad
+        \\tau_g(\\omega) = -\\Im \\left\\{ \\frac{H'(e^{j \\omega T})}
+                        {H(e^{j \\omega T})} \\right\\}
+
+
+    where::
+
+                        (H'(e^jwT))       (    H_R(e^jwT))        (H_R(e^jwT))
+        tau_g(w) = -im  |---------| = -im |-jT ----------| = T re |----------|
+                        ( H(e^jwT))       (    H(e^jwT)  )        ( H(e^jwT) )
+
+    where :math:`H(e^{j\\omega T})` is calculated via the DFT at NFFT points and
+    the derivative
+    of the polynomial terms :math:`b_k z^-k` using :math:`\\partial / \\partial w b_k e^-jkwT` = -b_k jkT e^-jkwT.
+    This is equivalent to muliplying the polynome with a ramp `k`,
+    yielding the "ramped" function H_R(e^jwT).
+
+
+
+    For analog functions with b_k s^k the procedure is analogous, but there is no
+    sampling time and the exponent is positive.
+
+
+
+    .. [JOS] Julius O. Smith III, "Numerical Computation of Group Delay" in
+        "Introduction to Digital Filters with Audio Applications",
+        Center for Computer Research in Music and Acoustics (CCRMA),
+        Stanford University, http://ccrma.stanford.edu/~jos/filters/Numerical_Computation_Group_Delay.html, referenced 2014-04-02,
+
+    .. [Lyons] Richard Lyons, "Understanding Digital Signal Processing", 3rd Ed.,
+        Prentice Hall, 2010.
+
+    Examples
+    --------
+    >>> b = [1,2,3] # Coefficients of H(z) = 1 + 2 z^2 + 3 z^3
+    >>> tau_g, td = dsp_lib.grpdelay(b)
+
+
+    """
+## 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.)
+##
+## Theory: group delay, g(w) = -d/dw [arg{H(e^jw)}],  is the rate of change of
+## phase with respect to frequency.  It can be computed as:
+##
+##               d/dw H(e^-jw)
+##        g(w) = -------------
+##                 H(e^-jw)
+##
+## where
+##         H(z) = B(z)/A(z) = sum(b_k z^k)/sum(a_k z^k).
+##
+## By the quotient rule,
+##                    A(z) d/dw B(z) - B(z) d/dw A(z)
+##        d/dw H(z) = -------------------------------
+##                               A(z) A(z)
+## Substituting into the expression above yields:
+##                A dB - B dA
+##        g(w) =  ----------- = dB/B - dA/A
+##                    A B
+##
+## Note that,
+##        d/dw B(e^-jw) = sum(k b_k e^-jwk)
+##        d/dw A(e^-jw) = sum(k a_k e^-jwk)
+## which is just the FFT of the coefficients multiplied by a ramp.
+##
+## As a further optimization when nfft>>length(a), the IIR filter (b,a)
+## is converted to the FIR filter conv(b,fliplr(conj(a))).
+    if whole !='whole':
+        nfft = 2*nfft
+    nfft = int(nfft)
+#
+    w = Fs * np.arange(0, nfft)/nfft # create frequency vector
+
+    try: len(a)
+    except TypeError:
+        a = 1; oa = 0 # a is a scalar or empty -> order of a = 0
+        c = b
+        try: len(b)
+        except TypeError: print('No proper filter coefficients: len(a) = len(b) = 1 !')
+    else:
+        oa = len(a)-1               # order of denom. a(z) resp. a(s)
+        c = np.convolve(b,a[::-1])  # a[::-1] reverses denominator coeffs a
+                                    # c(z) = b(z) * a(1/z)*z^(-oa)
+    try: len(b)
+    except TypeError: b=1; ob=0     # b is a scalar or empty -> order of b = 0
+    else:
+        ob = len(b)-1             # order of b(z)
+
+    if analog:
+        a_b = np.convolve(a,b)
+        if ob > 1:
+            br_a = np.convolve(b[1:] * np.arange(1,ob), a)
+        else:
+            br_a = 0
+        ar_b = np.convolve(a[1:] * np.arange(1,oa), b)
+
+        num = np.fft.fft(ar_b - br_a, nfft)
+        den = np.fft.fft(a_b,nfft)
+    else:
+        oc = oa + ob                  # order of c(z)
+        cr = c * np.arange(0,oc+1) # multiply with ramp -> derivative of c wrt 1/z
+
+        num = np.fft.fft(cr,nfft) #
+        den = np.fft.fft(c,nfft)  #
+#
+    minmag = 10. * np.spacing(1) # equivalent to matlab "eps"
+    polebins = np.where(abs(den) < minmag)[0] # find zeros of denominator
+#    polebins = np.where(abs(num) < minmag)[0] # find zeros of numerator
+    if np.size(polebins) > 0:  # check whether polebins array is empty
+        print('*** grpdelay warning: group delay singular -> setting to 0 at:')
+        for i in polebins:
+            print ('f = {0} '.format((Fs*i/nfft)))
+            num[i] = 0
+            den[i] = 1
+
+    if analog:
+        tau_g = np.real(num / den)
+    else:
+        tau_g = np.real(num / den) - oa
+#
+    if whole !='whole':
+        nfft = nfft//2
+        tau_g = tau_g[0:nfft]
+        w = w[0:nfft]
+
+    return tau_g, w
+
+def grp_delay_ana(b, a, w):
+    """
+    Calculate the group delay of an anlog filter.
+    """
+    w, H = sig.freqs(b, a, w)
+    H_angle = np.unwrap(np.angle(H))
+#    tau_g = np.zeros(len(w)-1)
+    tau_g = (H_angle[1:]-H_angle[:-1])/(w[0]-w[1])
+    return tau_g, w[:-1]
+
+#==================================================================
+def format_ticks(xy, scale, format="%.1f"):
+#==================================================================
+    """
+    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
+    ----------
+    xy : string, either 'x', 'y' or 'xy'
+         select corresponding axis (axes) for reformatting
+
+    scale :  real,
+
+    format : string,
+             define C-style number formats
+
+    Returns
+    -------
+    nothing
+
+
+    Examples
+    --------
+    >>> format_ticks('x',1000.)
+    Scales all numbers of x-Axis by 1000, e.g. for displaying ms instead of s.
+    >>> format_ticks('xy',1., format = "%.2f")
+    Two decimal places for numbers on x- and y-axis
+    """
+    if xy == 'x' or xy == 'xy':
+        locx,labelx = plt.xticks() # get location and content of xticks
+        plt.xticks(locx, map(lambda x: format % x, locx*scale))
+    if xy == 'y' or xy == 'xy':
+        locy,labely = plt.yticks() # get location and content of xticks
+        plt.yticks(locy, map(lambda y: format % y, locy*scale))
+
+#==================================================================
+def lim_eps(a, eps):
+#==================================================================
+    """
+    Return min / max of an array a, increased by eps*(max(a) - min(a)).
+    Handy for nice looking axes labeling.
+    """
+    mylim = (min(a) - (max(a)-min(a))*eps, max(a) + (max(a)-min(a))*eps)
+    return mylim
+
+
+
+#========================================================
+
+abs = absolute
+
+def oddround(x):
+    """Return the nearest odd integer from x."""
+
+    return x-mod(x,2)+1
+
+def oddceil(x):
+    """Return the smallest odd integer not less than x."""
+
+    return oddround(x+1)
+
+def remlplen_herrmann(fp,fs,dp,ds):
+    """
+    Determine the length of the low pass filter with passband frequency
+    fp, stopband frequency fs, passband ripple dp, and stopband ripple ds.
+    fp and fs must be normalized with respect to the sampling frequency.
+    Note that the filter order is one less than the filter length.
+
+    Uses approximation algorithm described by Herrmann et al.:
+
+    O. Herrmann, L.R. Raviner, and D.S.K. Chan, Practical Design Rules for
+    Optimum Finite Impulse Response Low-Pass Digital Filters, Bell Syst. Tech.
+    Jour., 52(6):769-799, Jul./Aug. 1973.
+    """
+
+    dF = fs-fp
+    a = [5.309e-3,7.114e-2,-4.761e-1,-2.66e-3,-5.941e-1,-4.278e-1]
+    b = [11.01217, 0.51244]
+    Dinf = log10(ds)*(a[0]*log10(dp)**2+a[1]*log10(dp)+a[2])+ \
+           a[3]*log10(dp)**2+a[4]*log10(dp)+a[5]
+    f = b[0]+b[1]*(log10(dp)-log10(ds))
+    N1 = Dinf/dF-f*dF+1
+
+    return int(oddround(N1))
+
+def remlplen_kaiser(fp,fs,dp,ds):
+    """
+    Determine the length of the low pass filter with passband frequency
+    fp, stopband frequency fs, passband ripple dp, and stopband ripple ds.
+    fp and fs must be normalized with respect to the sampling frequency.
+    Note that the filter order is one less than the filter length.
+
+    Uses approximation algorithm described by Kaiser:
+
+    J.F. Kaiser, Nonrecursive Digital Filter Design Using I_0-sinh Window
+    function, Proc. IEEE Int. Symp. Circuits and Systems, 20-23, April 1974.
+    """
+
+    dF = fs-fp
+    N2 = (-20*log10(sqrt(dp*ds))-13.0)/(14.6*dF)+1.0
+
+    return int(oddceil(N2))
+
+def remlplen_ichige(fp,fs,dp,ds):
+    """
+    Determine the length of the low pass filter with passband frequency
+    fp, stopband frequency fs, passband ripple dp, and stopband ripple ds.
+    fp and fs must be normalized with respect to the sampling frequency.
+    Note that the filter order is one less than the filter length.
+    Uses approximation algorithm described by Ichige et al.:
+    K. Ichige, M. Iwaki, and R. Ishii, Accurate Estimation of Minimum
+    Filter Length for Optimum FIR Digital Filters, IEEE Transactions on
+    Circuits and Systems, 47(10):1008-1017, October 2000.
+    """
+
+    dF = fs-fp
+    v = lambda dF,dp:2.325*((-log10(dp))**-0.445)*dF**(-1.39)
+    g = lambda fp,dF,d:(2.0/pi)*arctan(v(dF,dp)*(1.0/fp-1.0/(0.5-dF)))
+    h = lambda fp,dF,c:(2.0/pi)*arctan((c/dF)*(1.0/fp-1.0/(0.5-dF)))
+    Nc = ceil(1.0+(1.101/dF)*(-log10(2.0*dp))**1.1)
+    Nm = (0.52/dF)*log10(dp/ds)*(-log10(dp))**0.17
+    N3 = ceil(Nc*(g(fp,dF,dp)+g(0.5-dF-fp,dF,dp)+1.0)/3.0)
+    DN = ceil(Nm*(h(fp,dF,1.1)-(h(0.5-dF-fp,dF,0.29)-1.0)/2.0))
+    N4 = N3+DN
+
+    return int(N4)
+
+def remezord(freqs,amps,rips,Hz=1,alg='ichige'):
+    """
+    Filter parameter selection for the Remez exchange algorithm.
+
+    Calculate the parameters required by the Remez exchange algorithm to
+    construct a finite impulse response (FIR) filter that approximately
+    meets the specified design.
+
+    Parameters
+    ----------
+
+        freqs : list
+            A monotonic sequence of band edges specified in Hertz. All elements
+            must be non-negative and less than 1/2 the sampling frequency as
+            given by the Hz parameter. The band edges "0" and "f_S / 2" do not
+            have to be specified, hence  2 * number(amps) - 2 freqs are needed.
+
+        amps : list
+            A sequence containing the amplitudes of the signal to be
+            filtered over the various bands, e.g. 1 for the passband, 0 for the
+            stopband and 0.42 for some intermediate band.
+
+        rips : list
+            A list with the peak ripples (linear, not in dB!) for each band. For
+            the stop band this is equivalent to the minimum attenuation.
+
+        Hz : float
+            Sampling frequency
+
+        alg : string
+            Filter length approximation algorithm. May be either 'herrmann',
+            'kaiser' or 'ichige'. Depending on the specifications, some of
+            the algorithms may give better results than the others.
+
+    Returns
+    -------
+
+    numtaps,bands,desired,weight -- See help for the remez function.
+
+    Examples
+    --------
+            We want to design a lowpass with the band edges of 40 resp. 50 Hz and a
+            sampling frequency of 200 Hz, a passband peak ripple of 10%
+            and a stop band ripple of 0.01 or 40 dB.
+        >>> (L, F, A, W) = dsp.remezord([40, 50], [1, 0], [0.1, 0.01], Hz = 200)
+
+    Notes
+    -----
+
+    Supplies remezord method according to Scipy Ticket #475
+    http://projects.scipy.org/scipy/ticket/475
+    https://github.com/scipy/scipy/issues/1002
+    https://github.com/thorstenkranz/eegpy/blob/master/eegpy/filter/remezord.py
+    """
+
+    # Make sure the parameters are floating point numpy arrays:
+    freqs = asarray(freqs,'d')
+    amps = asarray(amps,'d')
+    rips = asarray(rips,'d')
+
+    # Scale ripples with respect to band amplitudes:
+    rips /= (amps+(amps==0.0))
+
+    # Normalize input frequencies with respect to sampling frequency:
+    freqs /= Hz
+
+    # Select filter length approximation algorithm:
+    if alg == 'herrmann':
+        remlplen = remlplen_herrmann
+    elif alg == 'kaiser':
+        remlplen = remlplen_kaiser
+    elif alg == 'ichige':
+        remlplen = remlplen_ichige
+    else:
+        raise ValueError('Unknown filter length approximation algorithm.')
+
+    # Validate inputs:
+    if any(freqs > 0.5):
+        raise ValueError('Frequency band edges must not exceed the Nyquist frequency.')
+    if any(freqs < 0.0):
+        raise ValueError('Frequency band edges must be nonnegative.')
+    if any(rips <= 0.0):
+        raise ValueError('Ripples must be nonnegative and non-zero.')
+    if len(amps) != len(rips):
+        raise ValueError('Number of amplitudes must equal number of ripples.')
+    if len(freqs) != 2*(len(amps)-1):
+        raise ValueError('Number of band edges must equal 2*(number of amplitudes-1)')
+
+
+    # Find the longest filter length needed to implement any of the
+    # low-pass or high-pass filters with the specified edges:
+    f1 = freqs[0:-1:2]
+    f2 = freqs[1::2]
+    L = 0
+    for i in range(len(amps)-1):
+        L = max((L,
+                 remlplen(f1[i],f2[i],rips[i],rips[i+1]),
+                 remlplen(0.5-f2[i],0.5-f1[i],rips[i+1],rips[i])))
+
+    # Cap the sequence of band edges with the limits of the digital frequency
+    # range:
+    bands = hstack((0.0,freqs,0.5))
+
+    # The filter design weights correspond to the ratios between the maximum
+    # ripple and all of the other ripples:
+    weight = max(rips)/rips
+
+    return [L,bands,amps,weight]
+
+#######################################
+# If called directly, do some example #
+#######################################
+if __name__=='__main__':
+    pass