예제 #1
0
    def _construct_UI(self, **kwargs):
        """
        Construct widget from quantization dict, individual settings and
        the default dict below """

        # default settings
        dict_ui = {
            'wdg_name': 'ui_w',
            'label': 'WI.WF',
            'lbl_sep': '.',
            'max_led_width': 30,
            'WI': 0,
            'WI_len': 2,
            'tip_WI': 'Number of integer bits',
            'WF': 15,
            'WF_len': 2,
            'tip_WF': 'Number of fractional bits',
            'enabled': True,
            'visible': True,
            'fractional': True,
            'combo_visible': False,
            'combo_items': ['auto', 'full', 'man'],
            'tip_combo': 'Calculate Acc. width.',
            'lock_visible': False,
            'tip_lock': 'Lock input/output quantization.'
        }  #: default values

        if self.q_dict:
            dict_ui.update(self.q_dict)

        for k, v in kwargs.items():
            if k not in dict_ui:
                logger.warning("Unknown key {0}".format(k))
            else:
                dict_ui.update({k: v})

        self.wdg_name = dict_ui['wdg_name']

        if not dict_ui['fractional']:
            dict_ui['WF'] = 0
        self.WI = dict_ui['WI']
        self.WF = dict_ui['WF']
        self.W = int(self.WI + self.WF + 1)
        if self.q_dict:
            self.q_dict.update({'WI': self.WI, 'WF': self.WF, 'W': self.W})
        else:
            self.q_dict = {'WI': self.WI, 'WF': self.WF, 'W': self.W}

        lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self)

        self.cmbW = QComboBox(self)
        self.cmbW.addItems(dict_ui['combo_items'])
        self.cmbW.setVisible(dict_ui['combo_visible'])
        self.cmbW.setToolTip(dict_ui['tip_combo'])
        self.cmbW.setObjectName("cmbW")

        self.butLock = QPushButton(self)
        self.butLock.setCheckable(True)
        self.butLock.setChecked(False)
        self.butLock.setVisible(dict_ui['lock_visible'])
        self.butLock.setToolTip(dict_ui['tip_lock'])

        self.ledWI = QLineEdit(self)
        self.ledWI.setToolTip(dict_ui['tip_WI'])
        self.ledWI.setMaxLength(dict_ui['WI_len'])  # maximum of 2 digits
        self.ledWI.setFixedWidth(
            dict_ui['max_led_width'])  # width of lineedit in points
        self.ledWI.setObjectName("WI")

        lblDot = QLabel(dict_ui['lbl_sep'], self)
        lblDot.setVisible(dict_ui['fractional'])

        self.ledWF = QLineEdit(self)
        self.ledWF.setToolTip(dict_ui['tip_WF'])
        self.ledWF.setMaxLength(dict_ui['WI_len'])  # maximum of 2 digits
        self.ledWF.setFixedWidth(
            dict_ui['max_led_width'])  # width of lineedit in points
        self.ledWF.setVisible(dict_ui['fractional'])
        self.ledWF.setObjectName("WF")

        layH = QHBoxLayout()
        layH.addWidget(lblW)
        layH.addStretch()
        layH.addWidget(self.cmbW)
        layH.addWidget(self.butLock)
        layH.addWidget(self.ledWI)
        layH.addWidget(lblDot)
        layH.addWidget(self.ledWF)
        layH.setContentsMargins(0, 0, 0, 0)

        frmMain = QFrame(self)
        frmMain.setLayout(layH)

        layVMain = QVBoxLayout()  # Widget main layout
        layVMain.addWidget(frmMain)
        layVMain.setContentsMargins(0, 5, 0, 0)  # *params['wdg_margins'])

        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        # INITIAL SETTINGS
        # ----------------------------------------------------------------------
        self.ledWI.setText(qstr(dict_ui['WI']))
        self.ledWF.setText(qstr(dict_ui['WF']))

        frmMain.setEnabled(dict_ui['enabled'])
        frmMain.setVisible(dict_ui['visible'])

        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.ledWI.editingFinished.connect(self.ui2dict)
        self.ledWF.editingFinished.connect(self.ui2dict)
        self.butLock.clicked.connect(self.butLock_clicked)
        self.cmbW.currentIndexChanged.connect(self.ui2dict)

        # initialize button icon
        self.butLock_clicked(self.butLock.isChecked())
예제 #2
0
    def _construct_UI(self):
        """
        Construct User Interface from all input subwidgets
        """
        self.butLoadFilt = QPushButton("LOAD FILTER", self)
        self.butLoadFilt.setToolTip("Load filter from disk")
        self.butSaveFilt = QPushButton("SAVE FILTER", self)
        self.butSaveFilt.setToolTip("Save filter todisk")
        layHButtons1 = QHBoxLayout()
        layHButtons1.addWidget(self.butLoadFilt)  # <Load Filter> button
        layHButtons1.addWidget(self.butSaveFilt)  # <Save Filter> button
        layHButtons1.setContentsMargins(*params['wdg_margins_spc'])

        self.butDesignFilt = QPushButton("DESIGN FILTER", self)
        self.butDesignFilt.setToolTip("Design filter with chosen specs")
        self.butQuit = QPushButton("Quit", self)
        self.butQuit.setToolTip("Exit pyfda tool")
        layHButtons2 = QHBoxLayout()
        layHButtons2.addWidget(self.butDesignFilt)  # <Design Filter> button
        layHButtons2.addWidget(self.butQuit)  # <Quit> button
        layHButtons2.setContentsMargins(*params['wdg_margins'])

        # Subwidget for selecting filter with response type rt (LP, ...),
        #    filter type ft (IIR, ...) and filter class fc (cheby1, ...)
        self.sel_fil = select_filter.SelectFilter(self)
        self.sel_fil.setObjectName("select_filter")
        self.sel_fil.sig_tx.connect(self.sig_rx_local)

        # Subwidget for selecting the frequency unit and range
        self.f_units = freq_units.FreqUnits(self)
        self.f_units.setObjectName("freq_units")
        self.f_units.sig_tx.connect(self.sig_rx_local)

        # Changing the frequency unit requires re-display of frequency specs
        # but it does not influence the actual specs (no specsChanged )
        # Activating the "Sort" button emits 'view_changed'?specs_changed'?, requiring
        # sorting and storing the frequency entries

        # Changing filter parameters / specs requires reloading of parameters
        # in other hierarchy levels, e.g. in the plot tabs

        # Subwidget for Frequency Specs
        self.f_specs = freq_specs.FreqSpecs(self)
        self.f_specs.setObjectName("freq_specs")
        self.f_specs.sig_tx.connect(self.sig_rx_local)
        self.sig_tx.connect(self.f_specs.sig_rx)
        # Subwidget for Amplitude Specs
        self.a_specs = amplitude_specs.AmplitudeSpecs(self)
        self.a_specs.setObjectName("amplitude_specs")
        self.a_specs.sig_tx.connect(self.sig_rx_local)
        # Subwidget for Weight Specs
        self.w_specs = weight_specs.WeightSpecs(self)
        self.w_specs.setObjectName("weight_specs")
        self.w_specs.sig_tx.connect(self.sig_rx_local)
        # Subwidget for target specs (frequency and amplitude)
        self.t_specs = target_specs.TargetSpecs(self,
                                                title="Target Specifications")
        self.t_specs.setObjectName("target_specs")
        self.t_specs.sig_tx.connect(self.sig_rx_local)
        self.sig_tx.connect(self.t_specs.sig_rx)
        # Subwidget for displaying infos on the design method
        self.lblMsg = QLabel(self)
        self.lblMsg.setWordWrap(True)
        layVMsg = QVBoxLayout()
        layVMsg.addWidget(self.lblMsg)

        self.frmMsg = QFrame(self)
        self.frmMsg.setLayout(layVMsg)
        layVFrm = QVBoxLayout()
        layVFrm.addWidget(self.frmMsg)
        layVFrm.setContentsMargins(*params['wdg_margins'])

        # ----------------------------------------------------------------------
        # LAYOUT for input specifications and buttons
        # ----------------------------------------------------------------------
        layVMain = QVBoxLayout(self)
        layVMain.addLayout(layHButtons1)  # <Load> & <Save> buttons
        layVMain.addWidget(self.sel_fil)  # Design method (IIR - ellip, ...)
        layVMain.addLayout(layHButtons2)  # <Design> & <Quit> buttons
        layVMain.addWidget(self.f_units)  # Frequency units
        layVMain.addWidget(self.t_specs)  # Target specs
        layVMain.addWidget(self.f_specs)  # Freq. specifications
        layVMain.addWidget(self.a_specs)  # Amplitude specs
        layVMain.addWidget(self.w_specs)  # Weight specs
        layVMain.addLayout(layVFrm)  # Text message

        layVMain.addStretch()

        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)  # main layout of widget

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx_local.connect(self.process_sig_rx_local)
        self.butLoadFilt.clicked.connect(lambda: load_filter(self))
        self.butSaveFilt.clicked.connect(lambda: save_filter(self))
        self.butDesignFilt.clicked.connect(self.start_design_filt)
        self.butQuit.clicked.connect(self.quit_program)  # emit 'quit_program'
        # ----------------------------------------------------------------------

        self.update_UI()  # first time initialization
        self.start_design_filt()  # design first filter using default values
예제 #3
0
파일: delay.py 프로젝트: xfunture/pyfda
class Delay(QWidget):

    FRMT = 'zpk' # output format of delay filter widget

    info ="""
**Delay widget**

allows entering the number of **delays** :math:`N` :math:`T_S`. It is treated as a FIR filter,
the number of delays is directly translated to a number of poles (:math:`N > 0`) 
or zeros (:math:`N < 0`).

Obviously, there is no minimum design algorithm or no design algorithm at all :-)

    """

    sig_tx = pyqtSignal(object)

    def __init__(self):
        QWidget.__init__(self)

        self.N = 5

        self.ft = 'FIR'
        
        self.rt_dicts = ('com',)

        self.rt_dict = {
            'COM': {'man': {'fo':('a', 'N'),
                            'msg':('a', 
                                "<span>Enter desired filter order <b><i>N</i></b>, corner "
                                "frequencies of pass and stop band(s), <b><i>F<sub>PB</sub></i></b>"
                                "&nbsp; and <b><i>F<sub>SB</sub></i></b>&nbsp;, and relative weight "
                                "values <b><i>W&nbsp; </i></b> (1 ... 10<sup>6</sup>) to specify how well "
                                "the bands are approximated.</span>")
                            },
                },
            'LP': {'man':{'wspecs': ('u','W_PB','W_SB'),
                          'tspecs': ('u', {'frq':('a','F_PB','F_SB'), 
                                           'amp':('u','A_PB','A_SB')})
                          },
                },
            'HP': {'man':{'wspecs': ('u','W_SB','W_PB')},
                    },
            'BP': {
                    },
            'BS': {'man':{'wspecs': ('u','W_PB','W_SB','W_PB2'),
                          'tspecs': ('u', {'frq':('a','F_PB','F_SB','F_SB2','F_PB2'), 
                                           'amp':('u','A_PB','A_SB','A_PB2')})
                          }
                }
            }

        self.info_doc = []

    #--------------------------------------------------------------------------
    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in 
        select_filter.py using the handle to the filter instance, fb.fil_inst.
        """
        self.lbl_delay = QLabel("Delays", self)
        self.lbl_delay.setObjectName('wdg_lbl_delays')
        self.led_delay = QLineEdit(self)
        self.led_delay.setText(str(self.N))
        self.led_delay.setObjectName('wdg_led_delay')
        self.led_delay.setToolTip("Number of delays, N > 0 produces poles, N < 0 zeros.")

        self.layHWin = QHBoxLayout()
        self.layHWin.setObjectName('wdg_layGWin')
        self.layHWin.addWidget(self.lbl_delay)
        self.layHWin.addWidget(self.led_delay)
        self.layHWin.setContentsMargins(0,0,0,0)
        # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits)
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layHWin)

        #----------------------------------------------------------------------
        # SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.led_delay.editingFinished.connect(self._update_UI)
        # fires when edited line looses focus or when RETURN is pressed
        #----------------------------------------------------------------------

        self._load_dict() # get initial / last setting from dictionary
        self._update_UI()
        
    def _update_UI(self):
        """
        Update UI when line edit field is changed (here, only the text is read
        and converted to integer) and store parameter settings in filter 
        dictionary
        """
        self.N = safe_eval(self.led_delay.text(), self.N, 
                                      return_type='int')
        self.led_delay.setText(str(self.N))

        if not 'wdg_fil' in fb.fil[0]:
            fb.fil[0].update({'wdg_fil':{}})
        fb.fil[0]['wdg_fil'].update({'delay':
                                        {'N':self.N}
                                    })
        
        # sig_tx -> select_filter -> filter_specs   
        self.sig_tx.emit({'sender':__name__, 'filt_changed':'delay'})


    def _load_dict(self):
        """
        Reload parameter(s) from filter dictionary (if they exist) and set 
        corresponding UI elements. _load_dict() is called upon initialization
        and when the filter is loaded from disk.
        """
        if 'wdg_fil' in fb.fil[0] and 'delay' in fb.fil[0]['wdg_fil']:
            wdg_fil_par = fb.fil[0]['wdg_fil']['delay']
            if 'N' in wdg_fil_par:
                self.N = wdg_fil_par['N']
                self.led_delay.setText(str(self.N))


    def _get_params(self, fil_dict):
        """
        Translate parameters from the passed dictionary to instance
        parameters, scaling / transforming them if needed.
        """
        self.N     = fil_dict['N']  # filter order is translated to numb. of delays
                                        
        
    def _test_N(self):
        """
        Warn the user if the calculated order is too high for a reasonable filter
        design.
        """
        if self.N > 2000:
            return qfilter_warning(self, self.N, "Delay")
        else:
            return True

    def _save(self, fil_dict, arg=None):
        """
        Convert between poles / zeros / gain, filter coefficients (polynomes)
        and second-order sections and store all available formats in the passed
        dictionary 'fil_dict'.
        """
        if arg is None:
            arg = np.zeros(self.N)
        fil_save(fil_dict, arg, self.FRMT, __name__)

    def LPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(fil_dict)


    def HPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(fil_dict)
        
    def BPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(fil_dict)

    def BSman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(fil_dict)
예제 #4
0
    def _construct_UI(self, **kwargs):
        """ Construct widget """

        dict_ui = {
            'wdg_name': 'ui_q',
            'label': '',
            'label_q': 'Quant.',
            'tip_q': 'Select the kind of quantization.',
            'cmb_q': ['round', 'fix', 'floor'],
            'cur_q': 'round',
            'label_ov': 'Ovfl.',
            'tip_ov': 'Select overflow behaviour.',
            'cmb_ov': ['wrap', 'sat'],
            'cur_ov': 'wrap',
            'enabled': True,
            'visible': True
        }  #: default widget settings

        if 'quant' in self.q_dict and self.q_dict['quant'] in dict_ui['cmb_q']:
            dict_ui['cur_q'] = self.q_dict['quant']
        if 'ovfl' in self.q_dict and self.q_dict['ovfl'] in dict_ui['cmb_ov']:
            dict_ui['cur_ov'] = self.q_dict['ovfl']

        for key, val in kwargs.items():
            dict_ui.update({key: val})
        # dict_ui.update(map(kwargs)) # same as above?

        self.wdg_name = dict_ui['wdg_name']

        lblQuant = QLabel(dict_ui['label_q'], self)
        self.cmbQuant = QComboBox(self)
        self.cmbQuant.addItems(dict_ui['cmb_q'])
        qset_cmb_box(self.cmbQuant, dict_ui['cur_q'])
        self.cmbQuant.setToolTip(dict_ui['tip_q'])
        self.cmbQuant.setObjectName('quant')

        lblOvfl = QLabel(dict_ui['label_ov'], self)
        self.cmbOvfl = QComboBox(self)
        self.cmbOvfl.addItems(dict_ui['cmb_ov'])
        qset_cmb_box(self.cmbOvfl, dict_ui['cur_ov'])
        self.cmbOvfl.setToolTip(dict_ui['tip_ov'])
        self.cmbOvfl.setObjectName('ovfl')

        # ComboBox size is adjusted automatically to fit the longest element
        self.cmbQuant.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbOvfl.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        layH = QHBoxLayout()
        if dict_ui['label'] != "":
            lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self)
            layH.addWidget(lblW)
        layH.addStretch()
        layH.addWidget(lblOvfl)
        layH.addWidget(self.cmbOvfl)
        # layH.addStretch(1)
        layH.addWidget(lblQuant)
        layH.addWidget(self.cmbQuant)
        layH.setContentsMargins(0, 0, 0, 0)

        frmMain = QFrame(self)
        frmMain.setLayout(layH)

        layVMain = QVBoxLayout()  # Widget main layout
        layVMain.addWidget(frmMain)
        layVMain.setContentsMargins(0, 0, 0, 0)  # *params['wdg_margins'])

        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        # INITIAL SETTINGS
        # ----------------------------------------------------------------------
        self.ovfl = qget_cmb_box(self.cmbOvfl, data=False)
        self.quant = qget_cmb_box(self.cmbQuant, data=False)
        frmMain.setEnabled(dict_ui['enabled'])
        frmMain.setVisible(dict_ui['visible'])

        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.cmbOvfl.currentIndexChanged.connect(self.ui2dict)
        self.cmbQuant.currentIndexChanged.connect(self.ui2dict)
예제 #5
0
class EllipZeroPhz(QWidget):

#    Since we are also using poles/residues -> let's force zpk
#    if SOS_AVAIL:
#       output format of filter design routines 'zpk' / 'ba' / 'sos'
#        FRMT = 'sos' 
#    else:
    FRMT = 'zpk'
        
    info = """
**Elliptic filters with zero phase**

(also known as Cauer filters) have the steepest rate of transition between the 
frequency response’s passband and stopband of all IIR filters. This comes
at the expense of a constant ripple (equiripple) :math:`A_PB` and :math:`A_SB`
in both pass and stop band. Ringing of the step response is increased in
comparison to Chebychev filters.
 
As the passband ripple :math:`A_PB` approaches 0, the elliptical filter becomes
a Chebyshev type II filter. As the stopband ripple :math:`A_SB` approaches 0,
it becomes a Chebyshev type I filter. As both approach 0, becomes a Butterworth
filter (butter).

For the filter design, the order :math:`N`, minimum stopband attenuation
:math:`A_SB` and the critical frequency / frequencies :math:`F_PB` where the 
gain first drops below the maximum passband ripple :math:`-A_PB` have to be specified.

The ``ellipord()`` helper routine calculates the minimum order :math:`N` and 
critical passband frequency :math:`F_C` from pass and stop band specifications.

The Zero Phase Elliptic Filter squares an elliptic filter designed in
a way to produce the required Amplitude specifications. So initially the
amplitude specs design an elliptic filter with the square root of the amp specs.
The filter is then squared to produce a zero phase filter.
The filter coefficients are applied to the signal data in a backward and forward
time fashion.  This filter can only be applied to stored signal data (not
real-time streaming data that comes in a forward time order).

We are forcing the order N of the filter to be even.  This simplifies the poles/zeros
to be complex (no real values).

**Design routines:**

``scipy.signal.ellip()``, ``scipy.signal.ellipord()``

        """
    sig_tx = pyqtSignal(object)

    def __init__(self):
        QWidget.__init__(self)

        self.ft = 'IIR'

        c = Common()
        self.rt_dict = c.rt_base_iir

        self.rt_dict_add = {
            'COM':{'man':{'msg':('a',
             "Enter the filter order <b><i>N</i></b>, the minimum stop "
             "band attenuation <b><i>A<sub>SB</sub></i></b> and frequency or "
             "frequencies <b><i>F<sub>C</sub></i></b>  where gain first drops "
             "below the max passband ripple <b><i>-A<sub>PB</sub></i></b> .")}},
            'LP': {'man':{}, 'min':{}},
            'HP': {'man':{}, 'min':{}},
            'BS': {'man':{}, 'min':{}},
            'BP': {'man':{}, 'min':{}},
            }

        self.info_doc = []
        self.info_doc.append('ellip()\n========')
        self.info_doc.append(sig.ellip.__doc__)
        self.info_doc.append('ellipord()\n==========')
        self.info_doc.append(ellipord.__doc__)

    #--------------------------------------------------------------------------
    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in
        select_filter.py using the handle to the filter instance, fb.fil_inst.
        """
        self.chkComplex   = QCheckBox("ComplexFilter", self)
        self.chkComplex.setToolTip("Designs BP or BS Filter for complex data.")
        self.chkComplex.setObjectName('wdg_lbl_el')
        self.chkComplex.setChecked(False)

        #--------------------------------------------------
        #  Layout for filter optional subwidgets
        self.layHWin = QHBoxLayout()
        self.layHWin.setObjectName('wdg_layGWin')
        #self.layHWin.addWidget(self.chkComplex)
        self.layHWin.addStretch() 
        self.layHWin.setContentsMargins(0,0,0,0)

        # Widget containing all subwidgets
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layHWin)

    def _get_params(self, fil_dict):
        """
        Translate parameters from the passed dictionary to instance
        parameters, scaling / transforming them if needed.
        For zero phase filter, we take square root of amplitude specs
        since we later square filter.  Define design around smallest amp spec
        """
        # Frequencies are normalized to f_Nyq = f_S/2, ripple specs are in dB
        self.analog = False # set to True for analog filters
        self.manual = False # default is normal design
        self.N     = int(fil_dict['N'])

        # force N to be even
        if (self.N % 2) == 1:
            self.N += 1
        self.F_PB  = fil_dict['F_PB'] * 2
        self.F_SB  = fil_dict['F_SB'] * 2
        self.F_PB2 = fil_dict['F_PB2'] * 2
        self.F_SB2 = fil_dict['F_SB2'] * 2
        self.F_PBC = None

        # find smallest spec'd linear value and rewrite dictionary
        ampPB = fil_dict['A_PB']
        ampSB = fil_dict['A_SB']

        # take square roots of amp specs so resulting squared
        # filter will meet specifications
        if (ampPB < ampSB):
            ampSB = sqrt(ampPB)
            ampPB = sqrt(1+ampPB)-1
        else:
            ampPB = sqrt(1+ampSB)-1
            ampSB = sqrt(ampSB)
        self.A_PB = lin2unit(ampPB, 'IIR', 'A_PB', unit='dB')
        self.A_SB = lin2unit(ampSB, 'IIR', 'A_SB', unit='dB')
        #logger.warning("design with "+str(self.A_PB)+","+str(self.A_SB))

        # ellip filter routines support only one amplitude spec for
        # pass- and stop band each
        if str(fil_dict['rt']) == 'BS':
            fil_dict['A_PB2'] = self.A_PB
        elif str(fil_dict['rt']) == 'BP':
            fil_dict['A_SB2'] = self.A_SB

#   partial fraction expansion to define residue vector
    def _partial(self, k, p, z, norder):
        # create diff array
        diff = p - z

        # now compute residual vector
        cone = complex(1.,0.)
        residues = zeros(norder, complex)
        for i in range(norder):
            residues[i] =  k * (diff[i] / p[i])
            for j in range(norder):
                if (j != i):
                    residues[i] = residues[i] * (cone + diff[j]/(p[i] - p[j]))

        # now compute DC term for new expansion
        sumRes = 0.
        for i in range(norder):
            sumRes = sumRes + residues[i].real

        dc = k - sumRes

        return (dc, residues)

#
# Take a causal filter and square it. The result has the square
#  of the amplitude response of the input, and zero phase. Filter
#  is noncausal.
# Input:
#   k - gain in pole/zero form
#   p - numpy array of poles
#   z - numpy array of zeros
#   g - gain in pole/residue form
#   r - numpy array of residues
#   nn- order of filter

# Output:
#   kn - new gain (pole/zero)
#   pn - new poles
#   zn - new zeros  (numpy array)
#   gn - new gain (pole/residue)
#   rn - new residues

    def _sqCausal (self, k, p, z, g, r, nn):

#       Anticausal poles have conjugate-reciprocal symmetry
#       Starting anticausal residues are conjugates (adjusted below)

        pA = conj(1./p)   # antiCausal poles
        zA = conj(z)      # antiCausal zeros (store reciprocal)
        rA = conj(r)      # antiCausal residues (to start)
        rC = zeros(nn, complex)

#       Adjust residues. Causal part first.
        for j in range(nn):

#           Evaluate the anticausal filter at each causal pole
            tmpx = rA / (1. - p[j]/pA)
            ztmp = g + sum(tmpx)

#           Adjust residue
            rC[j] = r[j]*ztmp

#       anticausal residues are just conjugates of causal residues
#        r3 = np.conj(r2)

#       Compute the constant term
        dc2 = (g + sum(r))*g - sum(rC)

#       Populate output (2nn elements)
        gn = dc2.real

#       Drop complex poles/residues in LHP, keep only UHP

        pA = conj(p)  #store AntiCasual pole (reciprocal)
        p0 = zeros(int(nn/2), complex)
        r0 = zeros(int(nn/2), complex)
        cnt = 0
        for j in range(nn):
            if (p[j].imag > 0.0):
                p0[cnt] = p[j]
                r0[cnt] = rC[j]
                cnt = cnt+1

#       Let operator know we squared filter
#        logger.info('After squaring filter, order: '+str(nn*2))

#       For now and our case, only store causal residues
#       Filters are symmetric and can generate antiCausal residues
        return (pA, zA, gn, p0, r0)


    def _test_N(self):
        """
        Warn the user if the calculated order is too high for a reasonable filter
        design.
        """
        if self.N > 30:
            return qfilter_warning(self, self.N, "Zero-phase Elliptic")
        else:
            return True

#   custom save of filter dictionary
    def _save(self, fil_dict, arg):
        """
        First design initial elliptic filter meeting sqRoot Amp specs;
         - Then create residue vector from poles/zeros;
         - Then square filter (k,p,z and dc,p,r) to get zero phase filter;
         - Then Convert results of filter design to all available formats (pz, pr, ba, sos)
        and store them in the global filter dictionary.

        Corner frequencies and order calculated for minimum filter order are
        also stored to allow for an easy subsequent manual filter optimization.
        """
        fil_save(fil_dict, arg, self.FRMT, __name__)

        # For min. filter order algorithms, update filter dict with calculated
        # new values for filter order N and corner frequency(s) F_PBC

        fil_dict['N'] = self.N
        if str(fil_dict['fo']) == 'min':
            if str(fil_dict['rt']) == 'LP' or str(fil_dict['rt']) == 'HP':
#               HP or LP - single  corner frequency
                fil_dict['F_PB'] = self.F_PBC / 2.
            else: # BP or BS - two corner frequencies
                fil_dict['F_PB'] = self.F_PBC[0] / 2.
                fil_dict['F_PB2'] = self.F_PBC[1] / 2.

#       Now generate poles/residues for custom file save of new parameters
        if (not self.manual):
            z = fil_dict['zpk'][0]
            p = fil_dict['zpk'][1]
            k = fil_dict['zpk'][2]
            n = len(z)
            gain, residues = self._partial (k, p, z, n)

            pA, zA, gn, pC, rC = self._sqCausal (k, p, z, gain, residues, n)
            fil_dict['rpk'] = [rC, pC, gn]

#           save antiCausal b,a (nonReciprocal) also [easier to compute h(n)
            try:
               fil_dict['baA'] = sig.zpk2tf(zA, pA, k)
            except Exception as e:
               logger.error(e)

#       'rpk' is our signal that this is a non-Causal filter with zero phase
#       inserted into fil dictionary after fil_save and convert
        # sig_tx -> select_filter -> filter_specs   
        self.sig_tx.emit({'sender':__name__, 'filt_changed':'ellip_zero'})

#------------------------------------------------------------------------------
#
#         DESIGN ROUTINES
#
#------------------------------------------------------------------------------

    # LP: F_PB < F_stop -------------------------------------------------------
    def LPmin(self, fil_dict):
        """Elliptic LP filter, minimum order"""
        self._get_params(fil_dict)
        self.N, self.F_PBC = ellipord(self.F_PB,self.F_SB, self.A_PB,self.A_SB,                                                        analog=self.analog)
#       force even N
        if (self.N%2)== 1:
            self.N += 1
        if not self._test_N():
            return -1              
        #logger.warning("and "+str(self.F_PBC) + " " + str(self.N))
        self._save(fil_dict, sig.ellip(self.N, self.A_PB, self.A_SB, self.F_PBC,
                            btype='low', analog=self.analog, output=self.FRMT))

    def LPman(self, fil_dict):
        """Elliptic LP filter, manual order"""
        self._get_params(fil_dict)
        if not self._test_N():
            return -1  
        self._save(fil_dict, sig.ellip(self.N, self.A_PB, self.A_SB, self.F_PB,
                            btype='low', analog=self.analog, output=self.FRMT))

    # HP: F_stop < F_PB -------------------------------------------------------
    def HPmin(self, fil_dict):
        """Elliptic HP filter, minimum order"""
        self._get_params(fil_dict)
        self.N, self.F_PBC = ellipord(self.F_PB,self.F_SB, self.A_PB,self.A_SB,
                                                          analog=self.analog)
#       force even N
        if (self.N%2)== 1:
            self.N += 1
        if not self._test_N():
            return -1  
        self._save(fil_dict, sig.ellip(self.N, self.A_PB, self.A_SB, self.F_PBC,
                        btype='highpass', analog=self.analog, output=self.FRMT))

    def HPman(self, fil_dict):
        """Elliptic HP filter, manual order"""
        self._get_params(fil_dict)
        if not self._test_N():
            return -1  
        self._save(fil_dict, sig.ellip(self.N, self.A_PB, self.A_SB, self.F_PB,
                        btype='highpass', analog=self.analog, output=self.FRMT))

    # For BP and BS, F_XX have two elements each, A_XX has only one

    # BP: F_SB[0] < F_PB[0], F_SB[1] > F_PB[1] --------------------------------
    def BPmin(self, fil_dict):
        """Elliptic BP filter, minimum order"""
        self._get_params(fil_dict)
        self.N, self.F_PBC = ellipord([self.F_PB, self.F_PB2],
            [self.F_SB, self.F_SB2], self.A_PB, self.A_SB, analog=self.analog)
        #logger.warning(" "+str(self.F_PBC) + " " + str(self.N))
        if (self.N%2)== 1:
            self.N += 1
        if not self._test_N():
            return -1  
        #logger.warning("-"+str(self.F_PBC) + " " + str(self.A_SB))
        self._save(fil_dict, sig.ellip(self.N, self.A_PB, self.A_SB, self.F_PBC,
                        btype='bandpass', analog=self.analog, output=self.FRMT))

    def BPman(self, fil_dict):
        """Elliptic BP filter, manual order"""
        self._get_params(fil_dict)
        if not self._test_N():
            return -1  
        self._save(fil_dict, sig.ellip(self.N, self.A_PB, self.A_SB,
            [self.F_PB,self.F_PB2], btype='bandpass', analog=self.analog,
                                                            output=self.FRMT))

    # BS: F_SB[0] > F_PB[0], F_SB[1] < F_PB[1] --------------------------------
    def BSmin(self, fil_dict):
        """Elliptic BP filter, minimum order"""
        self._get_params(fil_dict)
        self.N, self.F_PBC = ellipord([self.F_PB, self.F_PB2],
                                [self.F_SB, self.F_SB2], self.A_PB,self.A_SB,                                                       analog=self.analog)
#       force even N
        if (self.N%2)== 1:
            self.N += 1
        if not self._test_N():
            return -1  
        self._save(fil_dict, sig.ellip(self.N, self.A_PB, self.A_SB, self.F_PBC,
                        btype='bandstop', analog=self.analog, output=self.FRMT))

    def BSman(self, fil_dict):
        """Elliptic BS filter, manual order"""
        self._get_params(fil_dict)
        if not self._test_N():
            return -1  
        self._save(fil_dict, sig.ellip(self.N, self.A_PB, self.A_SB,
            [self.F_PB,self.F_PB2], btype='bandstop', analog=self.analog,
                                                            output=self.FRMT))
예제 #6
0
class AllpPZ(QWidget):

    FRMT = 'zpk'  # output format of delay filter widget

    info = """
**Allpass widget**

allows entering the two **poles** :math:`p`. **zeros** are calculated from the 
reciprocal values of the poles. There is no minimum algorithm, only the two 
poles can be entered manually.

    """

    sig_tx = pyqtSignal(object)

    def __init__(self):
        QWidget.__init__(self)

        self.p = [0.5, 0.5j]

        self.ft = 'IIR'

        # the following defines which subwidgets are "a"ctive, "i"nvisible or "d"eactivated
        self.rt_dicts = ('com', )
        self.rt_dict = {
            'COM': {
                'man': {
                    'fo': ('d', 'N'),
                    'msg':
                    ('a',
                     "<span>Enter poles  <b><i>p</i></b> for allpass function,"
                     "zeros will be calculated.</span>")
                },
            },
            'AP': {
                'man': {}
            }
        }

        self.info_doc = []

    #--------------------------------------------------------------------------
    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in 
        select_filter.py using the handle to the filter instance, fb.fil_inst.
        """
        self.lbl_pole1 = QLabel("Pole 1", self)
        self.lbl_pole1.setObjectName('wdg_lbl_pole1')
        self.led_pole1 = QLineEdit(self)
        self.led_pole1.setText(str(self.p[0]))
        self.led_pole1.setObjectName('wdg_led_pole1')
        self.led_pole1.setToolTip("Pole 1 for allpass filter")

        self.lbl_pole2 = QLabel("Pole 2", self)
        self.lbl_pole2.setObjectName('wdg_lbl_pole2')
        self.led_pole2 = QLineEdit(self)
        self.led_pole2.setText(str(self.p[1]))
        self.led_pole2.setObjectName('wdg_led_pole2')
        self.led_pole2.setToolTip("Pole 2 for allpass filter")

        self.layHWin = QHBoxLayout()
        self.layHWin.setObjectName('wdg_layGWin')
        self.layHWin.addWidget(self.lbl_pole1)
        self.layHWin.addWidget(self.led_pole1)
        self.layHWin.addWidget(self.lbl_pole2)
        self.layHWin.addWidget(self.led_pole2)
        self.layHWin.setContentsMargins(0, 0, 0, 0)
        # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits)
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layHWin)

        #----------------------------------------------------------------------
        # SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.led_pole1.editingFinished.connect(self._update_UI)
        self.led_pole2.editingFinished.connect(self._update_UI)
        # fires when edited line looses focus or when RETURN is pressed
        #----------------------------------------------------------------------

        self._load_dict()  # get initial / last setting from dictionary
        self._update_UI()

    def _update_UI(self):
        """
        Update UI when line edit field is changed (here, only the text is read
        and converted to integer) and store parameter settings in filter 
        dictionary
        """
        self.p[0] = safe_eval(self.led_pole1.text(),
                              self.p[0],
                              return_type='cmplx')
        self.led_pole1.setText(str(self.p[0]))

        self.p[1] = safe_eval(self.led_pole2.text(),
                              self.p[1],
                              return_type='cmplx')
        self.led_pole2.setText(str(self.p[1]))

        if not 'wdg_fil' in fb.fil[0]:
            fb.fil[0].update({'wdg_fil': {}})
        fb.fil[0]['wdg_fil'].update(
            {'poles': {
                'p1': self.p[0],
                'p2': self.p[1]
            }})

        # sig_tx -> select_filter -> filter_specs
        self.sig_tx.emit({'sender': __name__, 'filt_changed': 'pole_1_2'})

    def _load_dict(self):
        """
        Reload parameter(s) from filter dictionary (if they exist) and set 
        corresponding UI elements. _load_dict() is called upon initialization
        and when the filter is loaded from disk.
        """
        if 'wdg_fil' in fb.fil[0] and 'poles' in fb.fil[0]['wdg_fil']:
            wdg_fil_par = fb.fil[0]['wdg_fil']['poles']
            if 'p1' in wdg_fil_par:
                self.p1 = wdg_fil_par['p1']
                self.led_pole1.setText(str(self.p1))
            if 'p2' in wdg_fil_par:
                self.p2 = wdg_fil_par['p2']
                self.led_pole2.setText(str(self.p2))

    def _get_params(self, fil_dict):
        """
        Get parameters needed for filter design from the passed dictionary and 
        translate them to instance parameters, scaling / transforming them if needed.
        """
        #self.p1     = fil_dict['zpk'][1][0]  # get the first and second pole
        #self.p2     = fil_dict['zpk'][1][1]  # from central filter dect
        logger.info(fil_dict['zpk'])

    def _test_poles(self):
        """
        Warn the user if one of the poles is outside the unit circle
        """
        if abs(self.p[0]) >= 1 or abs(self.p[1]) >= 1:
            return qfilter_warning(self, self.p[0], "Delay")
        else:
            return True

    def _save(self, fil_dict, arg=None):
        """
        Convert between poles / zeros / gain, filter coefficients (polynomes)
        and second-order sections and store all available formats in the passed
        dictionary 'fil_dict'.
        """
        if arg is None:
            logger.error("Passed empty filter dict")
        logger.info(arg)
        fil_save(fil_dict, arg, self.FRMT, __name__)

        fil_dict['N'] = len(self.p)

#------------------------------------------------------------------------------
# Filter design routines
#------------------------------------------------------------------------------
# The method name MUST be "FilterType"+"MinMan", e.g. LPmin or BPman

    def APman(self, fil_dict):
        """
        Calculate z =1/p* for a given set of poles p. If p=0, set z=0.
        The gain factor k is calculated from z and p at z = 1.
        """
        self._get_params(fil_dict)  # not needed here
        if not self._test_poles():
            return -1
        self.z = [0, 0]
        if self.p[0] != 0:
            self.z[0] = np.conj(1 / self.p[0])
        if type(self.p[0]) == complex:
            pass
        if self.p[1] != 0:
            self.z[1] = np.conj(1 / self.p[1])

        k = np.abs(
            np.polyval(np.poly(self.p), 1) / np.polyval(np.poly(self.z), 1))
        zpk_list = [self.z, self.p, k]

        self._save(fil_dict, zpk_list)
예제 #7
0
    def _construct_UI(self):
        """
        Construct UI with comboboxes for selecting filter:

        - cmbResponseType for selecting response type rt (LP, HP, ...)

        - cmbFilterType for selection of filter type (IIR, FIR, ...)

        - cmbFilterClass for selection of design design class (Chebyshev, ...)

        and populate them from the "filterTree" dict during the initial run.
        Later, calling _set_response_type() updates the three combo boxes.

        See filterbroker.py for structure and content of "filterTree" dict

        """
        # ----------------------------------------------------------------------
        # Combo boxes for filter selection
        # ----------------------------------------------------------------------
        self.cmbResponseType = QComboBox(self)
        self.cmbResponseType.setObjectName("comboResponseType")
        self.cmbResponseType.setToolTip("Select filter response type.")
        self.cmbFilterType = QComboBox(self)
        self.cmbFilterType.setObjectName("comboFilterType")
        self.cmbFilterType.setToolTip(
            "<span>Choose filter type, either recursive (Infinite Impulse Response) "
            "or transversal (Finite Impulse Response).</span>")
        self.cmbFilterClass = QComboBox(self)
        self.cmbFilterClass.setObjectName("comboFilterClass")
        self.cmbFilterClass.setToolTip("Select the filter design class.")

        # Adapt comboboxes size dynamically to largest element
        self.cmbResponseType.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbFilterClass.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        # ----------------------------------------------------------------------
        # Populate combo box with initial settings from fb.fil_tree
        # ----------------------------------------------------------------------
        # Translate short response type ("LP") to displayed names ("Lowpass")
        # (correspondence is defined in pyfda_rc.py) and populate rt combo box
        #
        rt_list = sorted(list(fb.fil_tree.keys()))

        for rt in rt_list:
            try:
                self.cmbResponseType.addItem(rc.rt_names[rt], rt)
            except KeyError as e:
                logger.warning(
                    f"KeyError: {e} has no corresponding full name in rc.rt_names."
                )
        idx = self.cmbResponseType.findData('LP')  # find index for 'LP'

        if idx == -1:  # Key 'LP' does not exist, use first entry instead
            idx = 0

        self.cmbResponseType.setCurrentIndex(idx)  # set initial index
        rt = qget_cmb_box(self.cmbResponseType)

        for ft in fb.fil_tree[rt]:
            self.cmbFilterType.addItem(rc.ft_names[ft], ft)
        self.cmbFilterType.setCurrentIndex(0)  # set initial index
        ft = qget_cmb_box(self.cmbFilterType)

        for fc in fb.fil_tree[rt][ft]:
            self.cmbFilterClass.addItem(fb.filter_classes[fc]['name'], fc)
        self.cmbFilterClass.setCurrentIndex(0)  # set initial index

        # ----------------------------------------------------------------------
        # Layout for Filter Type Subwidgets
        # ----------------------------------------------------------------------

        layHFilWdg = QHBoxLayout()  # container for filter subwidgets
        layHFilWdg.addWidget(self.cmbResponseType)  # LP, HP, BP, etc.
        layHFilWdg.addStretch()
        layHFilWdg.addWidget(self.cmbFilterType)  # FIR, IIR
        layHFilWdg.addStretch()
        layHFilWdg.addWidget(self.cmbFilterClass)  # bessel, elliptic, etc.

        # ----------------------------------------------------------------------
        # Layout for dynamic filter subwidgets (empty frame)
        # ----------------------------------------------------------------------
        # see Summerfield p. 278
        self.layHDynWdg = QHBoxLayout()  # for additional dynamic subwidgets

        # ----------------------------------------------------------------------
        # Filter Order Subwidgets
        # ----------------------------------------------------------------------
        self.lblOrder = QLabel("<b>Order:</b>")
        self.chkMinOrder = QCheckBox("Minimum", self)
        self.chkMinOrder.setToolTip(
            "<span>Minimum filter order / # of taps is determined automatically.</span>"
        )
        self.lblOrderN = QLabel("<b><i>N =</i></b>")
        self.ledOrderN = QLineEdit(str(fb.fil[0]['N']), self)
        self.ledOrderN.setToolTip("Filter order (# of taps - 1).")

        # --------------------------------------------------
        #  Layout for filter order subwidgets
        # --------------------------------------------------
        layHOrdWdg = QHBoxLayout()
        layHOrdWdg.addWidget(self.lblOrder)
        layHOrdWdg.addWidget(self.chkMinOrder)
        layHOrdWdg.addStretch()
        layHOrdWdg.addWidget(self.lblOrderN)
        layHOrdWdg.addWidget(self.ledOrderN)

        # ----------------------------------------------------------------------
        # OVERALL LAYOUT (stack standard + dynamic subwidgets vertically)
        # ----------------------------------------------------------------------
        self.layVAllWdg = QVBoxLayout()
        self.layVAllWdg.addLayout(layHFilWdg)
        self.layVAllWdg.addLayout(self.layHDynWdg)
        self.layVAllWdg.addLayout(layHOrdWdg)

        # ==============================================================================
        frmMain = QFrame(self)
        frmMain.setLayout(self.layVAllWdg)

        layHMain = QHBoxLayout()
        layHMain.addWidget(frmMain)
        layHMain.setContentsMargins(*rc.params['wdg_margins'])

        self.setLayout(layHMain)

        # ==============================================================================

        # ------------------------------------------------------------
        # SIGNALS & SLOTS
        # ------------------------------------------------------------
        # Connect comboBoxes and setters, propgate change events hierarchically
        #  through all widget methods and emit 'filt_changed' in the end.
        self.cmbResponseType.currentIndexChanged.connect(
            lambda: self._set_response_type(enb_signal=True))  # 'LP'
        self.cmbFilterType.currentIndexChanged.connect(
            lambda: self._set_filter_type(enb_signal=True))  # 'IIR'
        self.cmbFilterClass.currentIndexChanged.connect(
            lambda: self._set_design_method(enb_signal=True))  # 'cheby1'
        self.chkMinOrder.clicked.connect(
            lambda: self._set_filter_order(enb_signal=True))  # Min. Order
        self.ledOrderN.editingFinished.connect(
            lambda: self._set_filter_order(enb_signal=True))  # Manual Order
예제 #8
0
    def _construct_UI(self) -> None:
        """
        Intitialize the main GUI, consisting of:

        - A combo box to select the filter topology and an image of the topology

        - The input quantizer

        - The UI of the fixpoint filter widget

        - Simulation and export buttons
        """
# ------------------------------------------------------------------------------
        # Define frame and layout for the dynamically updated filter widget
        # The actual filter widget is instantiated in self.set_fixp_widget() later on

        self.layH_fx_wdg = QHBoxLayout()
        # self.layH_fx_wdg.setContentsMargins(*params['wdg_margins'])
        frmHDL_wdg = QFrame(self)
        frmHDL_wdg.setLayout(self.layH_fx_wdg)
        # frmHDL_wdg.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

# ------------------------------------------------------------------------------
#       Initialize fixpoint filter combobox, title and description
# ------------------------------------------------------------------------------
        self.cmb_fx_wdg = QComboBox(self)
        self.cmb_fx_wdg.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.lblTitle = QLabel("not set", self)
        self.lblTitle.setWordWrap(True)
        self.lblTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        layHTitle = QHBoxLayout()
        layHTitle.addWidget(self.cmb_fx_wdg)
        layHTitle.addWidget(self.lblTitle)

        self.frmTitle = QFrame(self)
        self.frmTitle.setLayout(layHTitle)
        self.frmTitle.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Input and Output Quantizer
# ------------------------------------------------------------------------------
#       - instantiate widgets for input and output quantizer
#       - pass the quantization (sub-?) dictionary to the constructor
# ------------------------------------------------------------------------------

        self.wdg_w_input = UI_W(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='w_input', label='', lock_visible=True)
        self.wdg_w_input.sig_tx.connect(self.process_sig_rx_local)

        cmb_q = ['round', 'floor', 'fix']

        self.wdg_w_output = UI_W(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='w_output', label='')
        self.wdg_w_output.sig_tx.connect(self.process_sig_rx_local)

        self.wdg_q_output = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='q_output',
                                 label='Output Format <i>Q<sub>Y&nbsp;</sub></i>:',
                                 cmb_q=cmb_q, cmb_ov=['wrap', 'sat'])
        self.wdg_q_output.sig_tx.connect(self.sig_rx_local)

        if HAS_DS:
            cmb_q.append('dsm')
        self.wdg_q_input = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='q_input',
                                label='Input Format <i>Q<sub>X&nbsp;</sub></i>:',
                                cmb_q=cmb_q)
        self.wdg_q_input.sig_tx.connect(self.sig_rx_local)

        # Layout and frame for input quantization
        layVQiWdg = QVBoxLayout()
        layVQiWdg.addWidget(self.wdg_q_input)
        layVQiWdg.addWidget(self.wdg_w_input)
        frmQiWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQiWdg.setLayout(layVQiWdg)
        frmQiWdg.setContentsMargins(*params['wdg_margins'])

        # Layout and frame for output quantization
        layVQoWdg = QVBoxLayout()
        layVQoWdg.addWidget(self.wdg_q_output)
        layVQoWdg.addWidget(self.wdg_w_output)
        frmQoWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQoWdg.setLayout(layVQoWdg)
        frmQoWdg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Dynamically updated image of filter topology (label as placeholder)
# ------------------------------------------------------------------------------
        # allow setting background color
        # lbl_fixp_img_palette = QPalette()
        # lbl_fixp_img_palette.setColor(QPalette(window, Qt: white))
        # lbl_fixp_img_palette.setBrush(self.backgroundRole(), QColor(150, 0, 0))
        # lbl_fixp_img_palette.setColor(QPalette: WindowText, Qt: blue)

        self.lbl_fixp_img = QLabel("img not set", self)
        self.lbl_fixp_img.setAutoFillBackground(True)
        # self.lbl_fixp_img.setPalette(lbl_fixp_img_palette)
        # self.lbl_fixp_img.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.embed_fixp_img(self.no_fx_filter_img)
        layHImg = QHBoxLayout()
        layHImg.setContentsMargins(0, 0, 0, 0)
        layHImg.addWidget(self.lbl_fixp_img)  # , Qt.AlignCenter)
        self.frmImg = QFrame(self)
        self.frmImg.setLayout(layHImg)
        self.frmImg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Simulation and export Buttons
# ------------------------------------------------------------------------------
        self.butExportHDL = QPushButton(self)
        self.butExportHDL.setToolTip(
            "Create Verilog or VHDL netlist for fixpoint filter.")
        self.butExportHDL.setText("Create HDL")

        self.butSimFx = QPushButton(self)
        self.butSimFx.setToolTip("Start fixpoint simulation.")
        self.butSimFx.setText("Sim. FX")

        self.layHHdlBtns = QHBoxLayout()
        self.layHHdlBtns.addWidget(self.butSimFx)
        self.layHHdlBtns.addWidget(self.butExportHDL)
        # This frame encompasses the HDL buttons sim and convert
        frmHdlBtns = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmHdlBtns.setLayout(self.layHHdlBtns)
        frmHdlBtns.setContentsMargins(*params['wdg_margins'])

# -------------------------------------------------------------------
#       Top level layout
# -------------------------------------------------------------------
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(frmHDL_wdg)
        splitter.addWidget(frmQoWdg)
        splitter.addWidget(self.frmImg)

        # setSizes uses absolute pixel values, but can be "misused" by specifying values
        # that are way too large: in this case, the space is distributed according
        # to the _ratio_ of the values:
        splitter.setSizes([3000, 3000, 5000])

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmTitle)
        layVMain.addWidget(frmHdlBtns)
        layVMain.addWidget(frmQiWdg)
        layVMain.addWidget(splitter)
        layVMain.addStretch()
        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        self.sig_rx_local.connect(self.process_sig_rx_local)
        # dynamic connection in `self._update_fixp_widget()`:
        # -----
        # if hasattr(self.fx_filt_ui, "sig_rx"):
        #     self.sig_rx.connect(self.fx_filt_ui.sig_rx)
        # if hasattr(self.fx_filt_ui, "sig_tx"):
        #     self.fx_filt_ui.sig_tx.connect(self.sig_rx_local)
        # ----
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.cmb_fx_wdg.currentIndexChanged.connect(self._update_fixp_widget)
        self.butExportHDL.clicked.connect(self.exportHDL)
        self.butSimFx.clicked.connect(lambda x: self.emit({'fx_sim': 'start'}))
예제 #9
0
class MA(QWidget):

    FRMT = (
        'zpk', 'ba'
    )  # output format(s) of filter design routines 'zpk' / 'ba' / 'sos'

    info = """
**Moving average filters**

can only be specified via their length and the number of cascaded sections.

The minimum order to obtain a certain attenuation at a given frequency is
calculated via the si function.

Moving average filters can be implemented very efficiently in hard- and software
as they require no multiplications but only addition and subtractions. Probably
only the lowpass is really useful, as the other response types only filter out resp.
leave components at ``f_S/4`` (bandstop resp. bandpass) resp. leave components
near ``f_S/2`` (highpass).

**Design routines:**

``ma.calc_ma()``
    """
    sig_tx = pyqtSignal(object)
    from pyfda.libs.pyfda_qt_lib import emit

    def __init__(self):
        QWidget.__init__(self)

        self.delays = 12  # number of delays per stage
        self.stages = 1  # number of stages

        self.ft = 'FIR'

        self.rt_dicts = ()
        # Common data for all filter response types:
        # This data is merged with the entries for individual response types
        # (common data comes first):

        self.rt_dict = {
            'COM': {
                'man': {
                    'fo': ('d', 'N'),
                    'msg':
                    ('a',
                     "Enter desired order (= delays) <b><i>M</i></b> per stage and"
                     " the number of <b>stages</b>. Target frequencies and amplitudes"
                     " are only used for comparison, not for the design itself."
                     )
                },
                'min': {
                    'fo': ('d', 'N'),
                    'msg':
                    ('a',
                     "Enter desired attenuation <b><i>A<sub>SB</sub></i></b> at "
                     "the corner of the stop band <b><i>F<sub>SB</sub></i></b>. "
                     "Choose the number of <b>stages</b>, the minimum order <b><i>M</i></b> "
                     "per stage will be determined. Passband specs are not regarded."
                     )
                }
            },
            'LP': {
                'man': {
                    'tspecs': ('u', {
                        'frq': ('u', 'F_PB', 'F_SB'),
                        'amp': ('u', 'A_PB', 'A_SB')
                    })
                },
                'min': {
                    'tspecs': ('a', {
                        'frq': ('a', 'F_PB', 'F_SB'),
                        'amp': ('a', 'A_PB', 'A_SB')
                    })
                }
            },
            'HP': {
                'man': {
                    'tspecs': ('u', {
                        'frq': ('u', 'F_SB', 'F_PB'),
                        'amp': ('u', 'A_SB', 'A_PB')
                    })
                },
                'min': {
                    'tspecs': ('a', {
                        'frq': ('a', 'F_SB', 'F_PB'),
                        'amp': ('a', 'A_SB', 'A_PB')
                    })
                },
            },
            'BS': {
                'man': {
                    'tspecs': ('u', {
                        'frq': ('u', 'F_PB', 'F_SB', 'F_SB2', 'F_PB2'),
                        'amp': ('u', 'A_PB', 'A_SB', 'A_PB2')
                    }),
                    'msg':
                    ('a', "\nThis is not a proper band stop, it only lets pass"
                     " frequency components around DC and <i>f<sub>S</sub></i>/2."
                     " The order needs to be odd."),
                }
            },
            'BP': {
                'man': {
                    'tspecs': ('u', {
                        'frq': (
                            'u',
                            'F_SB',
                            'F_PB',
                            'F_PB2',
                            'F_SB2',
                        ),
                        'amp': ('u', 'A_SB', 'A_PB', 'A_SB2')
                    }),
                    'msg':
                    ('a', "\nThis is not a proper band pass, it only lets pass"
                     " frequency components around <i>f<sub>S</sub></i>/4."
                     " The order needs to be odd."),
                }
            },
        }

        self.info_doc = []
#        self.info_doc.append('remez()\n=======')
#        self.info_doc.append(sig.remez.__doc__)
#        self.info_doc.append('remezord()\n==========')
#        self.info_doc.append(remezord.__doc__)

#--------------------------------------------------------------------------

    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in
        select_filter.py using the handle to the filter instance, fb.fil_inst.
        """

        self.lbl_delays = QLabel("<b><i>M =</ i></ b>", self)
        self.lbl_delays.setObjectName('wdg_lbl_ma_0')
        self.led_delays = QLineEdit(self)
        try:
            self.led_delays.setText(str(fb.fil[0]['N']))
        except KeyError:
            self.led_delays.setText(str(self.delays))
        self.led_delays.setObjectName('wdg_led_ma_0')
        self.led_delays.setToolTip("Set number of delays per stage")

        self.lbl_stages = QLabel("<b>Stages =</ b>", self)
        self.lbl_stages.setObjectName('wdg_lbl_ma_1')
        self.led_stages = QLineEdit(self)
        self.led_stages.setText(str(self.stages))

        self.led_stages.setObjectName('wdg_led_ma_1')
        self.led_stages.setToolTip("Set number of stages ")

        self.chk_norm = QCheckBox("Normalize", self)
        self.chk_norm.setChecked(True)
        self.chk_norm.setObjectName('wdg_chk_ma_2')
        self.chk_norm.setToolTip("Normalize to| H_max = 1|")

        self.layHWin = QHBoxLayout()
        self.layHWin.setObjectName('wdg_layGWin')
        self.layHWin.addWidget(self.lbl_delays)
        self.layHWin.addWidget(self.led_delays)
        self.layHWin.addStretch(1)
        self.layHWin.addWidget(self.lbl_stages)
        self.layHWin.addWidget(self.led_stages)
        self.layHWin.addStretch(1)
        self.layHWin.addWidget(self.chk_norm)
        self.layHWin.setContentsMargins(0, 0, 0, 0)
        # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits)
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layHWin)

        #----------------------------------------------------------------------
        # SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.led_delays.editingFinished.connect(self._update_UI)
        self.led_stages.editingFinished.connect(self._update_UI)
        # fires when edited line looses focus or when RETURN is pressed
        self.chk_norm.clicked.connect(self._update_UI)
        #----------------------------------------------------------------------

        self._load_dict()  # get initial / last setting from dictionary
        self._update_UI()

    def _load_dict(self):
        """
        Reload parameter(s) from filter dictionary (if they exist) and set
        corresponding UI elements. load_dict() is called upon initialization
        and when the filter is loaded from disk.
        """
        if 'wdg_fil' in fb.fil[0] and 'ma' in fb.fil[0]['wdg_fil']:
            wdg_fil_par = fb.fil[0]['wdg_fil']['ma']
            if 'delays' in wdg_fil_par:
                self.delays = wdg_fil_par['delays']
                self.led_delays.setText(str(self.delays))
            if 'stages' in wdg_fil_par:
                self.stages = wdg_fil_par['stages']
                self.led_stages.setText(str(self.stages))
            if 'normalize' in wdg_fil_par:
                self.chk_norm.setChecked(wdg_fil_par['normalize'])

    def _update_UI(self):
        """
        Update UI when line edit field is changed (here, only the text is read
        and converted to integer) and resize the textfields according to content.
        """
        self.delays = safe_eval(self.led_delays.text(),
                                self.delays,
                                return_type='int',
                                sign='pos')
        self.led_delays.setText(str(self.delays))
        self.stages = safe_eval(self.led_stages.text(),
                                self.stages,
                                return_type='int',
                                sign='pos')
        self.led_stages.setText(str(self.stages))

        self._store_entries()

    def _store_entries(self):
        """
        Store parameter settings in filter dictionary. Called from _update_UI()
        and _save()
        """
        if not 'wdg_fil' in fb.fil[0]:
            fb.fil[0].update({'wdg_fil': {}})
        fb.fil[0]['wdg_fil'].update({
            'ma': {
                'delays': self.delays,
                'stages': self.stages,
                'normalize': self.chk_norm.isChecked()
            }
        })
        # sig_tx -> select_filter -> filter_specs
        self.emit({'filt_changed': 'ma'})

    def _get_params(self, fil_dict):
        """
        Translate parameters from the passed dictionary to instance
        parameters, scaling / transforming them if needed.
        """
        # N is total order, L is number of taps per stage
        self.F_SB = fil_dict['F_SB']
        self.A_SB = fil_dict['A_SB']

    def _save(self, fil_dict):
        """
        Save MA-filters both in 'zpk' and 'ba' format; no conversion has to be
        performed except maybe deleting an 'sos' entry from an earlier
        filter design.
        """
        if 'zpk' in self.FRMT:
            fil_save(fil_dict, self.zpk, 'zpk', __name__, convert=False)

        if 'ba' in self.FRMT:
            fil_save(fil_dict, self.b, 'ba', __name__, convert=False)

        fil_convert(fil_dict, self.FRMT)

        # always update filter dict and LineEdit, in case the design algorithm
        # has changed the number of delays:
        fil_dict['N'] = self.delays * self.stages  # updated filter order
        self.led_delays.setText(str(self.delays))  # updated number of delays

        self._store_entries()

    def calc_ma(self, fil_dict, rt='LP'):
        """
        Calculate coefficients and P/Z for moving average filter based on
        filter length L = N + 1 and number of cascaded stages and save the
        result in the filter dictionary.
        """
        b = 1.
        k = 1.
        L = self.delays + 1

        if rt == 'LP':
            b0 = np.ones(L)  #  h[n] = {1; 1; 1; ...}
            i = np.arange(1, L)

            norm = L

        elif rt == 'HP':
            b0 = np.ones(L)
            b0[::2] = -1.  # h[n] = {1; -1; 1; -1; ...}

            i = np.arange(L)
            if (L % 2 == 0):  # even order, remove middle element
                i = np.delete(i, round(L / 2.))
            else:  # odd order, shift by 0.5 and remove middle element
                i = np.delete(i, int(L / 2.)) + 0.5

            norm = L

        elif rt == 'BP':
            # N is even, L is odd
            b0 = np.ones(L)
            b0[1::2] = 0
            b0[::4] = -1  # h[n] = {1; 0; -1; 0; 1; ... }

            L = L + 1
            i = np.arange(L)  # create N + 2 zeros around the unit circle, ...
            # ... remove first and middle element and rotate by L / 4
            i = np.delete(i, [0, L // 2]) + L / 4

            norm = np.sum(abs(b0))

        elif rt == 'BS':
            # N is even, L is odd
            b0 = np.ones(L)
            b0[1::2] = 0

            L = L + 1
            i = np.arange(
                L)  # create N + 2 zeros around the unit circle and ...
            i = np.delete(i,
                          [0, L // 2])  # ... remove first and middle element

            norm = np.sum(b0)

        if self.delays > 1000:
            if not qfilter_warning(None, self.delays * self.stages,
                                   "Moving Average"):
                return -1

        z0 = np.exp(-2j * np.pi * i / L)
        # calculate filter for multiple cascaded stages
        for i in range(self.stages):
            b = np.convolve(b0, b)
        z = np.repeat(z0, self.stages)

        # normalize filter to |H_max| = 1 if checked:
        if self.chk_norm.isChecked():
            b = b / (norm**self.stages)
            k = 1. / norm**self.stages
        p = np.zeros(len(z))

        # store in class attributes for the _save method
        self.zpk = [z, p, k]
        self.b = b
        self._save(fil_dict)

    def LPman(self, fil_dict):
        self._get_params(fil_dict)
        self.calc_ma(fil_dict, rt='LP')

    def LPmin(self, fil_dict):
        self._get_params(fil_dict)
        self.delays = int(
            np.ceil(
                1 /
                (self.A_SB**(1 / self.stages) * np.sin(self.F_SB * np.pi))))
        self.calc_ma(fil_dict, rt='LP')

    def HPman(self, fil_dict):
        self._get_params(fil_dict)
        self.calc_ma(fil_dict, rt='HP')

    def HPmin(self, fil_dict):
        self._get_params(fil_dict)
        self.delays = int(
            np.ceil(1 / (self.A_SB**(1 / self.stages) * np.sin(
                (0.5 - self.F_SB) * np.pi))))
        self.calc_ma(fil_dict, rt='HP')

    def BSman(self, fil_dict):
        self._get_params(fil_dict)
        self.delays = ceil_odd(self.delays)  # enforce odd order
        self.calc_ma(fil_dict, rt='BS')

    def BPman(self, fil_dict):
        self._get_params(fil_dict)
        self.delays = ceil_odd(self.delays)  # enforce odd order
        self.calc_ma(fil_dict, rt='BP')
예제 #10
0
class Equiripple(QWidget):

    FRMT = 'ba'  # output format of filter design routines 'zpk' / 'ba' / 'sos'
    # currently, only 'ba' is supported for equiripple routines

    info = """
**Equiripple filters**

have the steepest rate of transition between the frequency response’s passband
and stopband of all FIR filters. This comes at the expense of a constant ripple
(equiripple) :math:`A_PB` and :math:`A_SB` in both pass and stop band.

The filter-coefficients are calculated in such a way that the transfer function
minimizes the maximum error (**Minimax** design) between the desired gain and the
realized gain in the specified frequency bands using the **Remez** exchange algorithm.
The filter design algorithm is known as **Parks-McClellan** algorithm, in
Matlab (R) it is called ``firpm``.

Manual filter order design requires specifying the frequency bands (:math:`F_PB`,
:math:`f_SB` etc.), the filter order :math:`N` and weight factors :math:`W_PB`,
:math:`W_SB` etc.) for individual bands.

The minimum order and the weight factors needed to fulfill the target specifications
is estimated from frequency and amplitude specifications using Ichige's algorithm.

**Design routines:**

``scipy.signal.remez()``, ``pyfda_lib.remezord()``
    """

    sig_tx = pyqtSignal(object)

    def __init__(self):
        QWidget.__init__(self)

        self.grid_density = 16

        self.ft = 'FIR'

        self.rt_dicts = ('com', )

        self.rt_dict = {
            'COM': {
                'man': {
                    'fo': ('a', 'N'),
                    'msg':
                    ('a',
                     "<span>Enter desired filter order <b><i>N</i></b>, corner "
                     "frequencies of pass and stop band(s), <b><i>F<sub>PB</sub></i></b>"
                     "&nbsp; and <b><i>F<sub>SB</sub></i></b>&nbsp;, and relative weight "
                     "values <b><i>W&nbsp; </i></b> (1 ... 10<sup>6</sup>) to specify how well "
                     "the bands are approximated.</span>")
                },
                'min': {
                    'fo': ('d', 'N'),
                    'msg':
                    ('a',
                     "<span>Enter the maximum pass band ripple <b><i>A<sub>PB</sub></i></b>, "
                     "minimum stop band attenuation <b><i>A<sub>SB</sub></i></b> "
                     "and the corresponding corner frequencies of pass and "
                     "stop band(s), <b><i>F<sub>PB</sub></i></b>&nbsp; and "
                     "<b><i>F<sub>SB</sub></i></b> .</span>")
                }
            },
            'LP': {
                'man': {
                    'wspecs': ('a', 'W_PB', 'W_SB'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_PB', 'F_SB'),
                        'amp': ('u', 'A_PB', 'A_SB')
                    })
                },
                'min': {
                    'wspecs': ('d', 'W_PB', 'W_SB'),
                    'tspecs': ('a', {
                        'frq': ('a', 'F_PB', 'F_SB'),
                        'amp': ('a', 'A_PB', 'A_SB')
                    })
                }
            },
            'HP': {
                'man': {
                    'wspecs': ('a', 'W_SB', 'W_PB'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_SB', 'F_PB'),
                        'amp': ('u', 'A_SB', 'A_PB')
                    })
                },
                'min': {
                    'wspecs': ('d', 'W_SB', 'W_PB'),
                    'tspecs': ('a', {
                        'frq': ('a', 'F_SB', 'F_PB'),
                        'amp': ('a', 'A_SB', 'A_PB')
                    })
                }
            },
            'BP': {
                'man': {
                    'wspecs': ('a', 'W_SB', 'W_PB', 'W_SB2'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_SB', 'F_PB', 'F_PB2', 'F_SB2'),
                        'amp': ('u', 'A_SB', 'A_PB', 'A_SB2')
                    })
                },
                'min': {
                    'wspecs': ('d', 'W_SB', 'W_PB', 'W_SB2'),
                    'tspecs': ('a', {
                        'frq': ('a', 'F_SB', 'F_PB', 'F_PB2', 'F_SB2'),
                        'amp': ('a', 'A_SB', 'A_PB', 'A_SB2')
                    })
                },
            },
            'BS': {
                'man': {
                    'wspecs': ('a', 'W_PB', 'W_SB', 'W_PB2'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_PB', 'F_SB', 'F_SB2', 'F_PB2'),
                        'amp': ('u', 'A_PB', 'A_SB', 'A_PB2')
                    })
                },
                'min': {
                    'wspecs': ('d', 'W_PB', 'W_SB', 'W_PB2'),
                    'tspecs': ('a', {
                        'frq': ('a', 'F_PB', 'F_SB', 'F_SB2', 'F_PB2'),
                        'amp': ('a', 'A_PB', 'A_SB', 'A_PB2')
                    })
                }
            },
            'HIL': {
                'man': {
                    'wspecs': ('a', 'W_SB', 'W_PB', 'W_SB2'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_SB', 'F_PB', 'F_PB2', 'F_SB2'),
                        'amp': ('u', 'A_SB', 'A_PB', 'A_SB2')
                    })
                }
            },
            'DIFF': {
                'man': {
                    'wspecs': ('a', 'W_PB'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_PB'),
                        'amp': ('i', )
                    }),
                    'msg':
                    ('a',
                     "Enter the max. frequency up to where the differentiator "
                     "works.")
                }
            }
        }

        self.info_doc = []
        self.info_doc.append('remez()\n=======')
        self.info_doc.append(sig.remez.__doc__)
        self.info_doc.append('remezord()\n==========')
        self.info_doc.append(remezord.__doc__)

    #--------------------------------------------------------------------------
    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in 
        select_filter.py using the handle to the filter instance, fb.fil_inst.
        """
        self.lbl_remez_1 = QLabel("Grid Density", self)
        self.lbl_remez_1.setObjectName('wdg_lbl_remez_1')
        self.led_remez_1 = QLineEdit(self)
        self.led_remez_1.setText(str(self.grid_density))
        self.led_remez_1.setObjectName('wdg_led_remez_1')
        self.led_remez_1.setToolTip(
            "Number of frequency points for Remez algorithm. Increase the\n"
            "number to reduce frequency overshoot in the transition region.")

        self.layHWin = QHBoxLayout()
        self.layHWin.setObjectName('wdg_layGWin')
        self.layHWin.addWidget(self.lbl_remez_1)
        self.layHWin.addWidget(self.led_remez_1)
        self.layHWin.setContentsMargins(0, 0, 0, 0)
        # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits)
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layHWin)

        #----------------------------------------------------------------------
        # SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.led_remez_1.editingFinished.connect(self._update_UI)
        # fires when edited line looses focus or when RETURN is pressed
        #----------------------------------------------------------------------

        self._load_dict()  # get initial / last setting from dictionary
        self._update_UI()

    def _update_UI(self):
        """
        Update UI when line edit field is changed (here, only the text is read
        and converted to integer) and store parameter settings in filter 
        dictionary
        """
        self.grid_density = safe_eval(self.led_remez_1.text(),
                                      self.grid_density,
                                      return_type='int',
                                      sign='pos')
        self.led_remez_1.setText(str(self.grid_density))

        if not 'wdg_fil' in fb.fil[0]:
            fb.fil[0].update({'wdg_fil': {}})
        fb.fil[0]['wdg_fil'].update(
            {'equiripple': {
                'grid_density': self.grid_density
            }})

        # sig_tx -> select_filter -> filter_specs
        self.sig_tx.emit({'sender': __name__, 'filt_changed': 'equiripple'})

    def _load_dict(self):
        """
        Reload parameter(s) from filter dictionary (if they exist) and set 
        corresponding UI elements. _load_dict() is called upon initialization
        and when the filter is loaded from disk.
        """
        if 'wdg_fil' in fb.fil[0] and 'equiripple' in fb.fil[0]['wdg_fil']:
            wdg_fil_par = fb.fil[0]['wdg_fil']['equiripple']
            if 'grid_density' in wdg_fil_par:
                self.grid_density = wdg_fil_par['grid_density']
                self.led_remez_1.setText(str(self.grid_density))

    def _get_params(self, fil_dict):
        """
        Translate parameters from the passed dictionary to instance
        parameters, scaling / transforming them if needed.
        """
        self.N = fil_dict['N'] + 1  # remez algorithms expects number of taps
        # which is larger by one than the order!!
        self.F_PB = fil_dict['F_PB']
        self.F_SB = fil_dict['F_SB']
        self.F_PB2 = fil_dict['F_PB2']
        self.F_SB2 = fil_dict['F_SB2']
        # remez amplitude specs are linear (not in dBs)
        self.A_PB = fil_dict['A_PB']
        self.A_PB2 = fil_dict['A_PB2']
        self.A_SB = fil_dict['A_SB']
        self.A_SB2 = fil_dict['A_SB2']

        self.alg = 'ichige'

    def _test_N(self):
        """
        Warn the user if the calculated order is too high for a reasonable filter
        design.
        """
        if self.N > 2000:
            return qfilter_warning(self, self.N, "Equiripple")
        else:
            return True

    def _save(self, fil_dict, arg):
        """
        Convert between poles / zeros / gain, filter coefficients (polynomes)
        and second-order sections and store all available formats in the passed
        dictionary 'fil_dict'.
        """
        try:
            fil_save(fil_dict, arg, self.FRMT, __name__)
        except Exception as e:
            # catch exception due to malformatted coefficients:
            logger.error("While saving the equiripple filter design, "
                         "the following error occurred:\n{0}".format(e))
            return -1

        if str(fil_dict['fo']) == 'min':
            fil_dict['N'] = self.N - 1  # yes, update filterbroker

    def LPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.remez(self.N, [0, self.F_PB, self.F_SB, 0.5], [1, 0],
                      weight=[fil_dict['W_PB'], fil_dict['W_SB']],
                      fs=1,
                      grid_density=self.grid_density))

    def LPmin(self, fil_dict):
        self._get_params(fil_dict)
        (self.N, F, A, W) = remezord([self.F_PB, self.F_SB], [1, 0],
                                     [self.A_PB, self.A_SB],
                                     fs=1,
                                     alg=self.alg)
        if not self._test_N():
            return -1
        fil_dict['W_PB'] = W[0]
        fil_dict['W_SB'] = W[1]
        self._save(
            fil_dict,
            sig.remez(self.N,
                      F, [1, 0],
                      weight=W,
                      fs=1,
                      grid_density=self.grid_density))

    def HPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        if (self.N % 2 == 0):  # even order, use odd symmetry (type III)
            self._save(
                fil_dict,
                sig.remez(self.N, [0, self.F_SB, self.F_PB, 0.5], [0, 1],
                          weight=[fil_dict['W_SB'], fil_dict['W_PB']],
                          fs=1,
                          type='hilbert',
                          grid_density=self.grid_density))
        else:  # odd order,
            self._save(
                fil_dict,
                sig.remez(self.N, [0, self.F_SB, self.F_PB, 0.5], [0, 1],
                          weight=[fil_dict['W_SB'], fil_dict['W_PB']],
                          fs=1,
                          type='bandpass',
                          grid_density=self.grid_density))

    def HPmin(self, fil_dict):
        self._get_params(fil_dict)
        (self.N, F, A, W) = remezord([self.F_SB, self.F_PB], [0, 1],
                                     [self.A_SB, self.A_PB],
                                     fs=1,
                                     alg=self.alg)
        if not self._test_N():
            return -1
#        self.N = ceil_odd(N)  # enforce odd order
        fil_dict['W_SB'] = W[0]
        fil_dict['W_PB'] = W[1]
        if (self.N % 2 == 0):  # even order
            self._save(
                fil_dict,
                sig.remez(self.N,
                          F, [0, 1],
                          weight=W,
                          fs=1,
                          type='hilbert',
                          grid_density=self.grid_density))
        else:
            self._save(
                fil_dict,
                sig.remez(self.N,
                          F, [0, 1],
                          weight=W,
                          fs=1,
                          type='bandpass',
                          grid_density=self.grid_density))

    # For BP and BS, F_PB and F_SB have two elements each
    def BPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.remez(
                self.N, [0, self.F_SB, self.F_PB, self.F_PB2, self.F_SB2, 0.5],
                [0, 1, 0],
                weight=[fil_dict['W_SB'], fil_dict['W_PB'], fil_dict['W_SB2']],
                fs=1,
                grid_density=self.grid_density))

    def BPmin(self, fil_dict):
        self._get_params(fil_dict)
        (self.N, F, A,
         W) = remezord([self.F_SB, self.F_PB, self.F_PB2, self.F_SB2],
                       [0, 1, 0], [self.A_SB, self.A_PB, self.A_SB2],
                       fs=1,
                       alg=self.alg)
        if not self._test_N():
            return -1
        fil_dict['W_SB'] = W[0]
        fil_dict['W_PB'] = W[1]
        fil_dict['W_SB2'] = W[2]
        self._save(
            fil_dict,
            sig.remez(self.N,
                      F, [0, 1, 0],
                      weight=W,
                      fs=1,
                      grid_density=self.grid_density))

    def BSman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self.N = round_odd(self.N)  # enforce odd order
        self._save(
            fil_dict,
            sig.remez(
                self.N, [0, self.F_PB, self.F_SB, self.F_SB2, self.F_PB2, 0.5],
                [1, 0, 1],
                weight=[fil_dict['W_PB'], fil_dict['W_SB'], fil_dict['W_PB2']],
                fs=1,
                grid_density=self.grid_density))

    def BSmin(self, fil_dict):
        self._get_params(fil_dict)
        (N, F, A, W) = remezord([self.F_PB, self.F_SB, self.F_SB2, self.F_PB2],
                                [1, 0, 1], [self.A_PB, self.A_SB, self.A_PB2],
                                fs=1,
                                alg=self.alg)
        self.N = round_odd(N)  # enforce odd order
        if not self._test_N():
            return -1
        fil_dict['W_PB'] = W[0]
        fil_dict['W_SB'] = W[1]
        fil_dict['W_PB2'] = W[2]
        self._save(
            fil_dict,
            sig.remez(self.N,
                      F, [1, 0, 1],
                      weight=W,
                      fs=1,
                      grid_density=self.grid_density))

    def HILman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.remez(
                self.N, [0, self.F_SB, self.F_PB, self.F_PB2, self.F_SB2, 0.5],
                [0, 1, 0],
                weight=[fil_dict['W_SB'], fil_dict['W_PB'], fil_dict['W_SB2']],
                fs=1,
                type='hilbert',
                grid_density=self.grid_density))

    def DIFFman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self.N = ceil_even(self.N)  # enforce even order
        if self.F_PB < 0.1:
            logger.warning(
                "Bandwidth for pass band ({0}) is too low, inreasing to 0.1".
                format(self.F_PB))
            self.F_PB = 0.1
            fil_dict['F_PB'] = self.F_PB
            self.sig_tx.emit({
                'sender': __name__,
                'specs_changed': 'equiripple'
            })

        self._save(
            fil_dict,
            sig.remez(self.N, [0, self.F_PB], [np.pi * fil_dict['W_PB']],
                      fs=1,
                      type='differentiator',
                      grid_density=self.grid_density))
예제 #11
0
    def _construct_UI(self):
        """
        Intitialize the widget, consisting of:
        - Matplotlib widget with NavigationToolbar
        - Frame with control elements
        """
        self.bfont = QFont()
        self.bfont.setBold(True)

        self.qfft_win_select = QFFTWinSelector(self, self.win_dict)

        self.lbl_N = QLabel(to_html("N =", frmt='bi'))
        self.led_N = QLineEdit(self)
        self.led_N.setText(str(self.N_view))
        self.led_N.setMaximumWidth(qtext_width(N_x=8))
        self.led_N.setToolTip(
            "<span>Number of window data points to display.</span>")

        # By default, the enter key triggers the default 'dialog action' in QDialog
        # widgets. This activates one of the pushbuttons.
        self.but_log_t = QPushButton("dB", default=False, autoDefault=False)
        self.but_log_t.setMaximumWidth(qtext_width(" dB "))
        self.but_log_t.setObjectName("chk_log_time")
        self.but_log_t.setCheckable(True)
        self.but_log_t.setChecked(False)
        self.but_log_t.setToolTip("Display in dB")

        self.led_log_bottom_t = QLineEdit(self)
        self.led_log_bottom_t.setVisible(self.but_log_t.isChecked())
        self.led_log_bottom_t.setText(str(self.bottom_t))
        self.led_log_bottom_t.setMaximumWidth(qtext_width(N_x=6))
        self.led_log_bottom_t.setToolTip(
            "<span>Minimum display value for log. scale.</span>")

        self.lbl_log_bottom_t = QLabel(to_html("min =", frmt='bi'), self)
        self.lbl_log_bottom_t.setVisible(self.but_log_t.isChecked())

        self.but_norm_f = QPushButton("Max=1",
                                      default=False,
                                      autoDefault=False)
        self.but_norm_f.setCheckable(True)
        self.but_norm_f.setChecked(True)
        self.but_norm_f.setMaximumWidth(qtext_width(text=" Max=1 "))
        self.but_norm_f.setToolTip(
            "Normalize window spectrum for a maximum of 1.")

        self.but_half_f = QPushButton("0...½",
                                      default=False,
                                      autoDefault=False)
        self.but_half_f.setCheckable(True)
        self.but_half_f.setChecked(True)
        self.but_half_f.setMaximumWidth(qtext_width(text=" 0...½ "))
        self.but_half_f.setToolTip(
            "Display window spectrum in the range 0 ... 0.5 f_S.")

        # By default, the enter key triggers the default 'dialog action' in QDialog
        # widgets. This activates one of the pushbuttons.
        self.but_log_f = QPushButton("dB", default=False, autoDefault=False)
        self.but_log_f.setMaximumWidth(qtext_width(" dB "))
        self.but_log_f.setObjectName("chk_log_freq")
        self.but_log_f.setToolTip("<span>Display in dB.</span>")
        self.but_log_f.setCheckable(True)
        self.but_log_f.setChecked(True)

        self.lbl_log_bottom_f = QLabel(to_html("min =", frmt='bi'), self)
        self.lbl_log_bottom_f.setVisible(self.but_log_f.isChecked())

        self.led_log_bottom_f = QLineEdit(self)
        self.led_log_bottom_f.setVisible(self.but_log_t.isChecked())
        self.led_log_bottom_f.setText(str(self.bottom_f))
        self.led_log_bottom_f.setMaximumWidth(qtext_width(N_x=6))
        self.led_log_bottom_f.setToolTip(
            "<span>Minimum display value for log. scale.</span>")

        # ----------------------------------------------------------------------
        #               ### frmControls ###
        #
        # This widget encompasses all control subwidgets
        # ----------------------------------------------------------------------
        layH_win_select = QHBoxLayout()
        layH_win_select.addWidget(self.qfft_win_select)
        layH_win_select.setContentsMargins(0, 0, 0, 0)
        layH_win_select.addStretch(1)
        frmQFFT = QFrame(self)
        frmQFFT.setObjectName("frmQFFT")
        frmQFFT.setLayout(layH_win_select)

        hline = QHLine()

        layHControls = QHBoxLayout()
        layHControls.addWidget(self.lbl_N)
        layHControls.addWidget(self.led_N)
        layHControls.addStretch(1)
        layHControls.addWidget(self.lbl_log_bottom_t)
        layHControls.addWidget(self.led_log_bottom_t)
        layHControls.addWidget(self.but_log_t)
        layHControls.addStretch(5)
        layHControls.addWidget(QVLine(width=2))
        layHControls.addStretch(5)
        layHControls.addWidget(self.but_norm_f)
        layHControls.addStretch(1)
        layHControls.addWidget(self.but_half_f)
        layHControls.addStretch(1)
        layHControls.addWidget(self.lbl_log_bottom_f)
        layHControls.addWidget(self.led_log_bottom_f)
        layHControls.addWidget(self.but_log_f)

        layVControls = QVBoxLayout()
        layVControls.addWidget(frmQFFT)
        layVControls.addWidget(hline)
        layVControls.addLayout(layHControls)

        frmControls = QFrame(self)
        frmControls.setObjectName("frmControls")
        frmControls.setLayout(layVControls)

        # ----------------------------------------------------------------------
        #               ### mplwidget ###
        #
        # Layout layVMainMpl (VBox) is defined within MplWidget, additional
        # widgets can be added below the matplotlib widget (here: frmControls)
        #
        # ----------------------------------------------------------------------
        self.mplwidget = MplWidget(self)
        self.mplwidget.layVMainMpl.addWidget(frmControls)
        self.mplwidget.layVMainMpl.setContentsMargins(0, 0, 0, 0)

        # ----------------------------------------------------------------------
        #               ### frmInfo ###
        #
        # This widget encompasses the text info box and the table with window
        # parameters.
        # ----------------------------------------------------------------------
        self.tbl_win_props = QTableWidget(self.tbl_rows, self.tbl_cols, self)
        self.tbl_win_props.setAlternatingRowColors(True)
        # Auto-resize of table can be set using the header (although it is invisible)
        self.tbl_win_props.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        # Only the columns with data are stretched, the others are minimum size
        self.tbl_win_props.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Stretch)
        self.tbl_win_props.horizontalHeader().setSectionResizeMode(
            4, QHeaderView.Stretch)
        self.tbl_win_props.verticalHeader().setVisible(False)
        self.tbl_win_props.horizontalHeader().setVisible(False)
        self.tbl_win_props.setSizePolicy(QSizePolicy.MinimumExpanding,
                                         QSizePolicy.MinimumExpanding)
        self.tbl_win_props.setFixedHeight(
            self.tbl_win_props.rowHeight(0) * self.tbl_rows +
            self.tbl_win_props.frameWidth() * 2)
        # self.tbl_win_props.setVerticalScrollBarPolicy(
        #     Qt.ScrollBarAlwaysOff)
        # self.tbl_win_props.setHorizontalScrollBarPolicy(
        #     Qt.ScrollBarAlwaysOff)

        self._construct_table(self.tbl_rows, self.tbl_cols, " ")

        self.txtInfoBox = QTextBrowser(self)

        layVInfo = QVBoxLayout(self)
        layVInfo.addWidget(self.tbl_win_props)
        layVInfo.addWidget(self.txtInfoBox)

        frmInfo = QFrame(self)
        frmInfo.setObjectName("frmInfo")
        frmInfo.setLayout(layVInfo)

        # ----------------------------------------------------------------------
        #               ### splitter ###
        #
        # This widget encompasses all subwidgets
        # ----------------------------------------------------------------------

        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(self.mplwidget)
        splitter.addWidget(frmInfo)

        # setSizes uses absolute pixel values, but can be "misused" by
        # specifying values that are way too large: in this case, the space
        # is distributed according to the _ratio_ of the values:
        splitter.setSizes([3000, 800])

        layVMain = QVBoxLayout()
        layVMain.addWidget(splitter)
        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        #           Set subplots
        #
        self.ax = self.mplwidget.fig.subplots(nrows=1, ncols=2)
        self.ax_t = self.ax[0]
        self.ax_f = self.ax[1]

        self.calc_win_draw()  # initial calculation and drawing

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        self.sig_rx.connect(self.qfft_win_select.sig_rx)

        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.but_log_f.clicked.connect(self.update_view)
        self.but_log_t.clicked.connect(self.update_view)
        self.led_log_bottom_t.editingFinished.connect(self.update_bottom)
        self.led_log_bottom_f.editingFinished.connect(self.update_bottom)

        self.led_N.editingFinished.connect(self.calc_win_draw)

        self.but_norm_f.clicked.connect(self.calc_win_draw)
        self.but_half_f.clicked.connect(self.update_view)

        self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx)
        self.tbl_win_props.itemClicked.connect(self._handle_item_clicked)

        self.qfft_win_select.sig_tx.connect(self.update_fft_win)
예제 #12
0
class EllipZeroPhz(QWidget):

    #    Since we are also using poles/residues -> let's force zpk
    FRMT = 'zpk'

    info = """
**Elliptic filters with zero phase**

(also known as Cauer filters) have the steepest rate of transition between the 
frequency response’s passband and stopband of all IIR filters. This comes
at the expense of a constant ripple (equiripple) :math:`A_PB` and :math:`A_SB`
in both pass and stop band. Ringing of the step response is increased in
comparison to Chebychev filters.
 
As the passband ripple :math:`A_PB` approaches 0, the elliptical filter becomes
a Chebyshev type II filter. As the stopband ripple :math:`A_SB` approaches 0,
it becomes a Chebyshev type I filter. As both approach 0, becomes a Butterworth
filter (butter).

For the filter design, the order :math:`N`, minimum stopband attenuation
:math:`A_SB` and the critical frequency / frequencies :math:`F_PB` where the 
gain first drops below the maximum passband ripple :math:`-A_PB` have to be specified.

The ``ellipord()`` helper routine calculates the minimum order :math:`N` and 
critical passband frequency :math:`F_C` from pass and stop band specifications.

The Zero Phase Elliptic Filter squares an elliptic filter designed in
a way to produce the required Amplitude specifications. So initially the
amplitude specs design an elliptic filter with the square root of the amp specs.
The filter is then squared to produce a zero phase filter.
The filter coefficients are applied to the signal data in a backward and forward
time fashion.  This filter can only be applied to stored signal data (not
real-time streaming data that comes in a forward time order).

We are forcing the order N of the filter to be even.  This simplifies the poles/zeros
to be complex (no real values).

**Design routines:**

``scipy.signal.ellip()``, ``scipy.signal.ellipord()``

        """
    sig_tx = pyqtSignal(object)

    def __init__(self):
        QWidget.__init__(self)

        self.ft = 'IIR'

        c = Common()
        self.rt_dict = c.rt_base_iir

        self.rt_dict_add = {
            'COM': {
                'man': {
                    'msg':
                    ('a',
                     "Enter the filter order <b><i>N</i></b>, the minimum stop "
                     "band attenuation <b><i>A<sub>SB</sub></i></b> and frequency or "
                     "frequencies <b><i>F<sub>C</sub></i></b>  where gain first drops "
                     "below the max passband ripple <b><i>-A<sub>PB</sub></i></b> ."
                     )
                }
            },
            'LP': {
                'man': {},
                'min': {}
            },
            'HP': {
                'man': {},
                'min': {}
            },
            'BS': {
                'man': {},
                'min': {}
            },
            'BP': {
                'man': {},
                'min': {}
            },
        }

        self.info_doc = []
        self.info_doc.append('ellip()\n========')
        self.info_doc.append(sig.ellip.__doc__)
        self.info_doc.append('ellipord()\n==========')
        self.info_doc.append(ellipord.__doc__)

    #--------------------------------------------------------------------------
    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in
        select_filter.py using the handle to the filter instance, fb.fil_inst.
        """
        # =============================================================================
        #         self.chkComplex   = QCheckBox("ComplexFilter", self)
        #         self.chkComplex.setToolTip("Designs BP or BS Filter for complex data.")
        #         self.chkComplex.setObjectName('wdg_lbl_el')
        #         self.chkComplex.setChecked(False)
        #
        # =============================================================================
        self.butSave = QPushButton(self)
        self.butSave.setText("SAVE")
        self.butSave.setToolTip("Save filter in proprietary format")

        #--------------------------------------------------
        #  Layout for filter optional subwidgets
        self.layHWin = QHBoxLayout()
        self.layHWin.setObjectName('wdg_layGWin')
        #self.layHWin.addWidget(self.chkComplex)
        self.layHWin.addWidget(self.butSave)
        self.layHWin.addStretch()
        self.layHWin.setContentsMargins(0, 0, 0, 0)

        # Widget containing all subwidgets
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layHWin)

        self.butSave.clicked.connect(self.save_filter)

    def _get_params(self, fil_dict):
        """
        Translate parameters from the passed dictionary to instance
        parameters, scaling / transforming them if needed.
        For zero phase filter, we take square root of amplitude specs
        since we later square filter.  Define design around smallest amp spec
        """
        # Frequencies are normalized to f_Nyq = f_S/2, ripple specs are in dB
        self.analog = False  # set to True for analog filters
        self.manual = False  # default is normal design
        self.N = int(fil_dict['N'])

        # force N to be even
        if (self.N % 2) == 1:
            self.N += 1
        self.F_PB = fil_dict['F_PB'] * 2
        self.F_SB = fil_dict['F_SB'] * 2
        self.F_PB2 = fil_dict['F_PB2'] * 2
        self.F_SB2 = fil_dict['F_SB2'] * 2
        self.F_PBC = None

        # find smallest spec'd linear value and rewrite dictionary
        ampPB = fil_dict['A_PB']
        ampSB = fil_dict['A_SB']

        # take square roots of amp specs so resulting squared
        # filter will meet specifications
        if (ampPB < ampSB):
            ampSB = sqrt(ampPB)
            ampPB = sqrt(1 + ampPB) - 1
        else:
            ampPB = sqrt(1 + ampSB) - 1
            ampSB = sqrt(ampSB)
        self.A_PB = lin2unit(ampPB, 'IIR', 'A_PB', unit='dB')
        self.A_SB = lin2unit(ampSB, 'IIR', 'A_SB', unit='dB')
        #logger.warning("design with "+str(self.A_PB)+","+str(self.A_SB))

        # ellip filter routines support only one amplitude spec for
        # pass- and stop band each
        if str(fil_dict['rt']) == 'BS':
            fil_dict['A_PB2'] = self.A_PB
        elif str(fil_dict['rt']) == 'BP':
            fil_dict['A_SB2'] = self.A_SB

#   partial fraction expansion to define residue vector

    def _partial(self, k, p, z, norder):
        # create diff array
        diff = p - z

        # now compute residual vector
        cone = complex(1., 0.)
        residues = zeros(norder, complex)
        for i in range(norder):
            residues[i] = k * (diff[i] / p[i])
            for j in range(norder):
                if (j != i):
                    residues[i] = residues[i] * (cone + diff[j] /
                                                 (p[i] - p[j]))

        # now compute DC term for new expansion
        sumRes = 0.
        for i in range(norder):
            sumRes = sumRes + residues[i].real

        dc = k - sumRes

        return (dc, residues)

#
# Take a causal filter and square it. The result has the square
#  of the amplitude response of the input, and zero phase. Filter
#  is noncausal.
# Input:
#   k - gain in pole/zero form
#   p - numpy array of poles
#   z - numpy array of zeros
#   g - gain in pole/residue form
#   r - numpy array of residues
#   nn- order of filter

# Output:
#   kn - new gain (pole/zero)
#   pn - new poles
#   zn - new zeros  (numpy array)
#   gn - new gain (pole/residue)
#   rn - new residues

    def _sqCausal(self, k, p, z, g, r, nn):

        #       Anticausal poles have conjugate-reciprocal symmetry
        #       Starting anticausal residues are conjugates (adjusted below)

        pA = conj(1. / p)  # antiCausal poles
        zA = conj(z)  # antiCausal zeros (store reciprocal)
        rA = conj(r)  # antiCausal residues (to start)
        rC = zeros(nn, complex)

        #       Adjust residues. Causal part first.
        for j in range(nn):

            #           Evaluate the anticausal filter at each causal pole
            tmpx = rA / (1. - p[j] / pA)
            ztmp = g + sum(tmpx)

            #           Adjust residue
            rC[j] = r[j] * ztmp

#       anticausal residues are just conjugates of causal residues
#        r3 = np.conj(r2)

#       Compute the constant term
        dc2 = (g + sum(r)) * g - sum(rC)

        #       Populate output (2nn elements)
        gn = dc2.real

        #       Drop complex poles/residues in LHP, keep only UHP

        pA = conj(p)  #store AntiCasual pole (reciprocal)
        p0 = zeros(int(nn / 2), complex)
        r0 = zeros(int(nn / 2), complex)
        cnt = 0
        for j in range(nn):
            if (p[j].imag > 0.0):
                p0[cnt] = p[j]
                r0[cnt] = rC[j]
                cnt = cnt + 1

#       Let operator know we squared filter
#        logger.info('After squaring filter, order: '+str(nn*2))

#       For now and our case, only store causal residues
#       Filters are symmetric and can generate antiCausal residues
        return (pA, zA, gn, p0, r0)

    def _test_N(self):
        """
        Warn the user if the calculated order is too high for a reasonable filter
        design.
        """
        if self.N > 30:
            return qfilter_warning(self, self.N, "Zero-phase Elliptic")
        else:
            return True

#   custom save of filter dictionary

    def _save(self, fil_dict, arg):
        """
        First design initial elliptic filter meeting sqRoot Amp specs;
         - Then create residue vector from poles/zeros;
         - Then square filter (k,p,z and dc,p,r) to get zero phase filter;
         - Then Convert results of filter design to all available formats (pz, pr, ba, sos)
        and store them in the global filter dictionary.

        Corner frequencies and order calculated for minimum filter order are
        also stored to allow for an easy subsequent manual filter optimization.
        """
        fil_save(fil_dict, arg, self.FRMT, __name__)

        # For min. filter order algorithms, update filter dict with calculated
        # new values for filter order N and corner frequency(s) F_PBC

        fil_dict['N'] = self.N
        if str(fil_dict['fo']) == 'min':
            if str(fil_dict['rt']) == 'LP' or str(fil_dict['rt']) == 'HP':
                #               HP or LP - single  corner frequency
                fil_dict['F_PB'] = self.F_PBC / 2.
            else:  # BP or BS - two corner frequencies
                fil_dict['F_PB'] = self.F_PBC[0] / 2.
                fil_dict['F_PB2'] = self.F_PBC[1] / 2.

#       Now generate poles/residues for custom file save of new parameters
        if (not self.manual):
            z = fil_dict['zpk'][0]
            p = fil_dict['zpk'][1]
            k = fil_dict['zpk'][2]
            n = len(z)
            gain, residues = self._partial(k, p, z, n)

            pA, zA, gn, pC, rC = self._sqCausal(k, p, z, gain, residues, n)
            fil_dict['rpk'] = [rC, pC, gn]

            #           save antiCausal b,a (nonReciprocal) also [easier to compute h(n)
            try:
                fil_dict['baA'] = sig.zpk2tf(zA, pA, k)
            except Exception as e:
                logger.error(e)

#       'rpk' is our signal that this is a non-Causal filter with zero phase
#       inserted into fil dictionary after fil_save and convert
# sig_tx -> select_filter -> filter_specs
        self.sig_tx.emit({'sender': __name__, 'filt_changed': 'ellip_zero'})

#------------------------------------------------------------------------------

    def save_filter(self):
        file_filters = ("Text file pole/residue (*.txt_rpk)")
        dlg = QFD(self)
        # return selected file name (with or without extension) and filter (Linux: full text)
        file_name, file_type = dlg.getSaveFileName_(caption="Save filter as",
                                                    directory=dirs.save_dir,
                                                    filter=file_filters)

        file_name = str(file_name)  # QString -> str() needed for Python 2.x
        # Qt5 has QFileDialog.mimeTypeFilters(), but under Qt4 the mime type cannot
        # be extracted reproducibly across file systems, so it is done manually:

        for t in extract_file_ext(
                file_filters):  # get a list of file extensions
            if t in str(file_type):
                file_type = t  # return the last matching extension

        if file_name != "":  # cancelled file operation returns empty string

            # strip extension from returned file name (if any) + append file type:
            file_name = os.path.splitext(file_name)[0] + file_type

            file_type_err = False
            try:

                # save as a custom residue/pole text output for apply with custom tool
                # make sure we have the residues
                if 'rpk' in fb.fil[0]:
                    with io.open(file_name, 'w', encoding="utf8") as f:
                        self.file_dump(f)
                else:
                    file_type_err = True
                    logger.error(
                        'Filter has no residues/poles, cannot save as *.txt_rpk file'
                    )
                if not file_type_err:
                    logger.info('Successfully saved filter as\n\t"{0}"'.format(
                        file_name))
                    dirs.save_dir = os.path.dirname(file_name)  # save new dir

            except IOError as e:
                logger.error('Failed saving "{0}"!\n{1}'.format(file_name, e))

#------------------------------------------------------------------------------

    def file_dump(self, fOut):
        """
        Dump file out in custom text format that apply tool can read to know filter coef's
        """

        #       Fixed format widths for integers and doubles
        intw = '10'
        dblW = 27
        frcW = 20

        #       Fill up character string with filter output
        filtStr = '# IIR filter\n'

        #       parameters that made filter (choose smallest eps)
        #       Amp is stored in Volts (linear units)
        #       the second amp terms aren't really used (for ellip filters)

        FA_PB = fb.fil[0]['A_PB']
        FA_SB = fb.fil[0]['A_SB']
        FAmp = min(FA_PB, FA_SB)

        #       Freq terms in radians so move from -1:1 to -pi:pi
        f_lim = fb.fil[0]['freqSpecsRange']
        f_unit = fb.fil[0]['freq_specs_unit']

        F_S = fb.fil[0]['f_S']
        if fb.fil[0]['freq_specs_unit'] == 'f_S':
            F_S = F_S * 2
        F_SB = fb.fil[0]['F_SB'] * F_S * np.pi
        F_SB2 = fb.fil[0]['F_SB2'] * F_S * np.pi
        F_PB = fb.fil[0]['F_PB'] * F_S * np.pi
        F_PB2 = fb.fil[0]['F_PB2'] * F_S * np.pi

        #       Determine pass/stop bands depending on filter response type
        passMin = []
        passMax = []
        stopMin = []
        stopMax = []

        if fb.fil[0]['rt'] == 'LP':
            passMin = [-F_PB, 0, 0]
            passMax = [F_PB, 0, 0]
            stopMin = [-np.pi, F_SB, 0]
            stopMax = [-F_SB, np.pi, 0]
            f1 = F_PB
            f2 = F_SB
            f3 = f4 = 0
            Ftype = 1
            Fname = 'Low_Pass'

        if fb.fil[0]['rt'] == 'HP':
            passMin = [-np.pi, F_PB, 0]
            passMax = [-F_PB, np.pi, 0]
            stopMin = [-F_SB, 0, 0]
            stopMax = [F_SB, 0, 0]
            f1 = F_SB
            f2 = F_PB
            f3 = f4 = 0
            Ftype = 2
            Fname = 'Hi_Pass'

        if fb.fil[0]['rt'] == 'BS':
            passMin = [-np.pi, -F_PB, F_PB2]
            passMax = [-F_PB2, F_PB, np.pi]
            stopMin = [-F_SB2, F_SB, 0]
            stopMax = [-F_SB, F_SB2, 0]
            f1 = F_PB
            f2 = F_SB
            f3 = F_SB2
            f4 = F_PB2
            Ftype = 4
            Fname = 'Band_Stop'

        if fb.fil[0]['rt'] == 'BP':
            passMin = [-F_PB2, F_PB, 0]
            passMax = [-F_PB, F_PB2, 0]
            stopMin = [-np.pi, -F_SB, F_SB2]
            stopMax = [-F_SB2, F_SB, np.pi]
            f1 = F_SB
            f2 = F_PB
            f3 = F_PB2
            f4 = F_SB2
            Ftype = 3
            Fname = 'Band_Pass'

        filtStr = filtStr + '{:{align}{width}}'.format(
            '10', align='>', width=intw) + ' IIRFILT_4SYM\n'
        filtStr = filtStr + '{:{align}{width}}'.format(
            str(Ftype), align='>', width=intw) + ' ' + Fname + '\n'
        filtStr = filtStr + '{:{d}.{p}f}'.format(FAmp, d=dblW, p=frcW) + '\n'
        filtStr = filtStr + '{: {d}.{p}f}'.format(passMin[0], d=dblW, p=frcW)
        filtStr = filtStr + '{: {d}.{p}f}'.format(passMax[0], d=dblW,
                                                  p=frcW) + '\n'
        filtStr = filtStr + '{: {d}.{p}f}'.format(passMin[1], d=dblW, p=frcW)
        filtStr = filtStr + '{: {d}.{p}f}'.format(passMax[1], d=dblW,
                                                  p=frcW) + '\n'
        filtStr = filtStr + '{: {d}.{p}f}'.format(passMin[2], d=dblW, p=frcW)
        filtStr = filtStr + '{: {d}.{p}f}'.format(passMax[2], d=dblW,
                                                  p=frcW) + '\n'
        filtStr = filtStr + '{: {d}.{p}f}'.format(stopMin[0], d=dblW, p=frcW)
        filtStr = filtStr + '{: {d}.{p}f}'.format(stopMax[0], d=dblW,
                                                  p=frcW) + '\n'
        filtStr = filtStr + '{: {d}.{p}f}'.format(stopMin[1], d=dblW, p=frcW)
        filtStr = filtStr + '{: {d}.{p}f}'.format(stopMax[1], d=dblW,
                                                  p=frcW) + '\n'
        filtStr = filtStr + '{: {d}.{p}f}'.format(stopMin[2], d=dblW, p=frcW)
        filtStr = filtStr + '{: {d}.{p}f}'.format(stopMax[2], d=dblW,
                                                  p=frcW) + '\n'
        filtStr = filtStr + '{: {d}.{p}f}'.format(f1, d=dblW, p=frcW)
        filtStr = filtStr + '{: {d}.{p}f}'.format(f2, d=dblW, p=frcW) + '\n'
        filtStr = filtStr + '{: {d}.{p}f}'.format(f3, d=dblW, p=frcW)
        filtStr = filtStr + '{: {d}.{p}f}'.format(f4, d=dblW, p=frcW) + '\n'

        #       move pol/res/gain into terms we need
        Fdc = fb.fil[0]['rpk'][2]
        rC = fb.fil[0]['rpk'][0]
        pC = fb.fil[0]['rpk'][1]
        Fnum = len(pC)

        #       Gain term
        filtStr = filtStr + '{: {d}.{p}e}'.format(Fdc, d=dblW, p=frcW) + '\n'

        #       Real pole count inside the unit circle (none of these)

        filtStr = filtStr + '{:{align}{width}}'.format(
            str(0), align='>', width=intw) + '\n'

        #       Complex pole/res count inside the unit circle

        filtStr = filtStr + '{:{i}d}'.format(Fnum, i=intw) + '\n'

        #       Now dump poles/residues
        for j in range(Fnum):
            filtStr = filtStr + '{:{i}d}'.format(j, i=intw) + ' '
            filtStr = filtStr + '{: {d}.{p}e}'.format(
                rC[j].real, d=dblW, p=frcW) + ' '
            filtStr = filtStr + '{: {d}.{p}e}'.format(
                rC[j].imag, d=dblW, p=frcW) + ' '
            filtStr = filtStr + '{: {d}.{p}e}'.format(
                pC[j].real, d=dblW, p=frcW) + ' '
            filtStr = filtStr + '{: {d}.{p}e}'.format(
                pC[j].imag, d=dblW, p=frcW) + '\n'

#       Real pole count outside the unit circle (none of these)
        filtStr = filtStr + '{:{align}{width}}'.format(
            str(0), align='>', width=intw) + '\n'

        #       Complex pole count outside the unit circle (none of these)
        filtStr = filtStr + '{:{align}{width}}'.format(
            str(0), align='>', width=intw) + '\n'

        #       Now write huge text string to file
        fOut.write(filtStr)

#------------------------------------------------------------------------------
#
#         DESIGN ROUTINES
#
#------------------------------------------------------------------------------

# LP: F_PB < F_stop -------------------------------------------------------

    def LPmin(self, fil_dict):
        """Elliptic LP filter, minimum order"""
        self._get_params(fil_dict)
        self.N, self.F_PBC = ellipord(self.F_PB,
                                      self.F_SB,
                                      self.A_PB,
                                      self.A_SB,
                                      analog=self.analog)
        #       force even N
        if (self.N % 2) == 1:
            self.N += 1
        if not self._test_N():
            return -1
        #logger.warning("and "+str(self.F_PBC) + " " + str(self.N))
        self._save(
            fil_dict,
            sig.ellip(self.N,
                      self.A_PB,
                      self.A_SB,
                      self.F_PBC,
                      btype='low',
                      analog=self.analog,
                      output=self.FRMT))

    def LPman(self, fil_dict):
        """Elliptic LP filter, manual order"""
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.ellip(self.N,
                      self.A_PB,
                      self.A_SB,
                      self.F_PB,
                      btype='low',
                      analog=self.analog,
                      output=self.FRMT))

    # HP: F_stop < F_PB -------------------------------------------------------
    def HPmin(self, fil_dict):
        """Elliptic HP filter, minimum order"""
        self._get_params(fil_dict)
        self.N, self.F_PBC = ellipord(self.F_PB,
                                      self.F_SB,
                                      self.A_PB,
                                      self.A_SB,
                                      analog=self.analog)
        #       force even N
        if (self.N % 2) == 1:
            self.N += 1
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.ellip(self.N,
                      self.A_PB,
                      self.A_SB,
                      self.F_PBC,
                      btype='highpass',
                      analog=self.analog,
                      output=self.FRMT))

    def HPman(self, fil_dict):
        """Elliptic HP filter, manual order"""
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.ellip(self.N,
                      self.A_PB,
                      self.A_SB,
                      self.F_PB,
                      btype='highpass',
                      analog=self.analog,
                      output=self.FRMT))

    # For BP and BS, F_XX have two elements each, A_XX has only one

    # BP: F_SB[0] < F_PB[0], F_SB[1] > F_PB[1] --------------------------------
    def BPmin(self, fil_dict):
        """Elliptic BP filter, minimum order"""
        self._get_params(fil_dict)
        self.N, self.F_PBC = ellipord([self.F_PB, self.F_PB2],
                                      [self.F_SB, self.F_SB2],
                                      self.A_PB,
                                      self.A_SB,
                                      analog=self.analog)
        #logger.warning(" "+str(self.F_PBC) + " " + str(self.N))
        if (self.N % 2) == 1:
            self.N += 1
        if not self._test_N():
            return -1
        #logger.warning("-"+str(self.F_PBC) + " " + str(self.A_SB))
        self._save(
            fil_dict,
            sig.ellip(self.N,
                      self.A_PB,
                      self.A_SB,
                      self.F_PBC,
                      btype='bandpass',
                      analog=self.analog,
                      output=self.FRMT))

    def BPman(self, fil_dict):
        """Elliptic BP filter, manual order"""
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.ellip(self.N,
                      self.A_PB,
                      self.A_SB, [self.F_PB, self.F_PB2],
                      btype='bandpass',
                      analog=self.analog,
                      output=self.FRMT))

    # BS: F_SB[0] > F_PB[0], F_SB[1] < F_PB[1] --------------------------------
    def BSmin(self, fil_dict):
        """Elliptic BP filter, minimum order"""
        self._get_params(fil_dict)
        self.N, self.F_PBC = ellipord([self.F_PB, self.F_PB2],
                                      [self.F_SB, self.F_SB2],
                                      self.A_PB,
                                      self.A_SB,
                                      analog=self.analog)
        #       force even N
        if (self.N % 2) == 1:
            self.N += 1
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.ellip(self.N,
                      self.A_PB,
                      self.A_SB,
                      self.F_PBC,
                      btype='bandstop',
                      analog=self.analog,
                      output=self.FRMT))

    def BSman(self, fil_dict):
        """Elliptic BS filter, manual order"""
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.ellip(self.N,
                      self.A_PB,
                      self.A_SB, [self.F_PB, self.F_PB2],
                      btype='bandstop',
                      analog=self.analog,
                      output=self.FRMT))
예제 #13
0
    def _construct_UI(self):
        """
        Intitialize the main GUI, consisting of:
            
        - A combo box to select the filter topology and an image of the topology
        
        - The input quantizer
        
        - The UI of the fixpoint filter widget
        
        - Simulation and export buttons
        """
        #------------------------------------------------------------------------------
        # Define frame and layout for the dynamically updated filter widget
        # The actual filter widget is instantiated in self.set_fixp_widget() later on

        self.layH_fx_wdg = QHBoxLayout()
        #self.layH_fx_wdg.setContentsMargins(*params['wdg_margins'])
        frmHDL_wdg = QFrame(self)
        frmHDL_wdg.setLayout(self.layH_fx_wdg)
        #frmHDL_wdg.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        #------------------------------------------------------------------------------
        #       Initialize fixpoint filter combobox, title and description
        #------------------------------------------------------------------------------
        self.cmb_wdg_fixp = QComboBox(self)
        self.cmb_wdg_fixp.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.lblTitle = QLabel("not set", self)
        self.lblTitle.setWordWrap(True)
        self.lblTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        layHTitle = QHBoxLayout()
        layHTitle.addWidget(self.cmb_wdg_fixp)
        layHTitle.addWidget(self.lblTitle)

        self.frmTitle = QFrame(self)
        self.frmTitle.setLayout(layHTitle)
        self.frmTitle.setContentsMargins(*params['wdg_margins'])

        #------------------------------------------------------------------------------
        #       Input and Output Quantizer
        #------------------------------------------------------------------------------
        #       - instantiate widgets for input and output quantizer
        #       - pass the quantization (sub-?) dictionary to the constructor
        #------------------------------------------------------------------------------

        self.wdg_w_input = UI_W(self,
                                q_dict=fb.fil[0]['fxqc']['QI'],
                                id='w_input',
                                label='',
                                lock_visible=True)
        self.wdg_w_input.sig_tx.connect(self.process_sig_rx)

        cmb_q = ['round', 'floor', 'fix']

        self.wdg_w_output = UI_W(self,
                                 q_dict=fb.fil[0]['fxqc']['QO'],
                                 id='w_output',
                                 label='')
        self.wdg_w_output.sig_tx.connect(self.process_sig_rx)

        self.wdg_q_output = UI_Q(
            self,
            q_dict=fb.fil[0]['fxqc']['QO'],
            id='q_output',
            label='Output Format <i>Q<sub>Y&nbsp;</sub></i>:',
            cmb_q=cmb_q,
            cmb_ov=['wrap', 'sat'])
        self.wdg_q_output.sig_tx.connect(self.sig_rx)

        if HAS_DS:
            cmb_q.append('dsm')
        self.wdg_q_input = UI_Q(
            self,
            q_dict=fb.fil[0]['fxqc']['QI'],
            id='q_input',
            label='Input Format <i>Q<sub>X&nbsp;</sub></i>:',
            cmb_q=cmb_q)
        self.wdg_q_input.sig_tx.connect(self.sig_rx)

        # Layout and frame for input quantization
        layVQiWdg = QVBoxLayout()
        layVQiWdg.addWidget(self.wdg_q_input)
        layVQiWdg.addWidget(self.wdg_w_input)
        frmQiWdg = QFrame(self)
        #frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQiWdg.setLayout(layVQiWdg)
        frmQiWdg.setContentsMargins(*params['wdg_margins'])

        # Layout and frame for output quantization
        layVQoWdg = QVBoxLayout()
        layVQoWdg.addWidget(self.wdg_q_output)
        layVQoWdg.addWidget(self.wdg_w_output)
        frmQoWdg = QFrame(self)
        #frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQoWdg.setLayout(layVQoWdg)
        frmQoWdg.setContentsMargins(*params['wdg_margins'])

        #------------------------------------------------------------------------------
        #       Dynamically updated image of filter topology
        #------------------------------------------------------------------------------
        # label is a placeholder for image
        self.lbl_fixp_img = QLabel("img not set", self)
        #self.lbl_fixp_img.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.embed_fixp_img(self.no_fx_filter_img)

        layHImg = QHBoxLayout()
        layHImg.setContentsMargins(0, 0, 0, 0)
        layHImg.addWidget(self.lbl_fixp_img)  #, Qt.AlignCenter)
        self.frmImg = QFrame(self)
        self.frmImg.setLayout(layHImg)
        self.frmImg.setContentsMargins(*params['wdg_margins'])
        self.resize_img()
        #------------------------------------------------------------------------------
        #       Simulation and export Buttons
        #------------------------------------------------------------------------------
        self.butExportHDL = QPushButton(self)
        self.butExportHDL.setToolTip(
            "Export fixpoint filter in Verilog format.")
        self.butExportHDL.setText("Create HDL")

        self.butSimHDL = QPushButton(self)
        self.butSimHDL.setToolTip("Start migen fixpoint simulation.")
        self.butSimHDL.setText("Sim. HDL")

        self.butSimFxPy = QPushButton(self)
        self.butSimFxPy.setToolTip("Simulate filter with fixpoint effects.")
        self.butSimFxPy.setText("Sim. FixPy")

        self.layHHdlBtns = QHBoxLayout()
        self.layHHdlBtns.addWidget(self.butSimFxPy)
        self.layHHdlBtns.addWidget(self.butSimHDL)
        self.layHHdlBtns.addWidget(self.butExportHDL)
        # This frame encompasses the HDL buttons sim and convert
        frmHdlBtns = QFrame(self)
        #frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmHdlBtns.setLayout(self.layHHdlBtns)
        frmHdlBtns.setContentsMargins(*params['wdg_margins'])

        # -------------------------------------------------------------------
        #       Top level layout
        # -------------------------------------------------------------------
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(frmHDL_wdg)
        splitter.addWidget(frmQoWdg)
        splitter.addWidget(self.frmImg)

        # setSizes uses absolute pixel values, but can be "misused" by specifying values
        # that are way too large: in this case, the space is distributed according
        # to the _ratio_ of the values:
        splitter.setSizes([3000, 3000, 5000])

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmTitle)
        layVMain.addWidget(frmHdlBtns)
        layVMain.addWidget(frmQiWdg)
        layVMain.addWidget(splitter)
        layVMain.addStretch()
        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs & EVENTFILTERS
        #----------------------------------------------------------------------
        # monitor events and generate sig_resize event when resized
        self.lbl_fixp_img.installEventFilter(self)
        # ... then redraw image when resized
        self.sig_resize.connect(self.resize_img)

        self.cmb_wdg_fixp.currentIndexChanged.connect(self._update_fixp_widget)

        self.butExportHDL.clicked.connect(self.exportHDL)
        self.butSimHDL.clicked.connect(self.fx_sim_init)
        #----------------------------------------------------------------------
        inst_wdg_list = self._update_filter_cmb()
        if len(inst_wdg_list) == 0:
            logger.warning("No fixpoint filters found!")
        else:
            logger.debug("Imported {0:d} fixpoint filters:\n{1}".format(
                len(inst_wdg_list.split("\n")) - 1, inst_wdg_list))

        self._update_fixp_widget()