Example #1
0
 def _copy_options(self):
     """
     Set options for copying to/from clipboard or file.
     """
     self.opt_widget = CSV_option_box(self) # important: Handle must be class attribute
     #self.opt_widget.show() # modeless dialog, i.e. non-blocking
     self.opt_widget.exec_() # modal dialog (blocking)
Example #2
0
    def _open_csv_win(self):
        """
        Pop-up window for CSV options
        """
        if self.but_csv_options.isChecked():
            qstyle_widget(self.but_csv_options, "changed")
        else:
            qstyle_widget(self.but_csv_options, "normal")

        if dirs.csv_options_handle is None:
            # no handle to the window? Create a new instance
            if self.but_csv_options.isChecked():
                # Important: Handle to window must be class attribute, otherwise it
                # (and the attached window) is deleted immediately when it goes
                # out of scope
                dirs.csv_options_handle = CSV_option_box(self)
                dirs.csv_options_handle.sig_tx.connect(self.process_sig_rx)
                dirs.csv_options_handle.show()  # modeless i.e. non-blocking popup window
        else:
            if not self.but_csv_options.isChecked():  # this should not happen
                if dirs.csv_options_handle is None:
                    logger.warning("CSV options window is already closed!")
                else:
                    dirs.csv_options_handle.close()

        self.emit({'ui_changed': 'csv'})
Example #3
0
class Input_Coeffs(QWidget):
    """
    Create widget with a (sort of) model-view architecture for viewing /
    editing / entering data contained in `self.ba` which is a list of two numpy
    arrays:

    - `self.ba[0]` contains the numerator coefficients ("b")
    - `self.ba[1]` contains the denominator coefficients ("a")

    The list don't neccessarily have the same length but they are always defined.
    For FIR filters, `self.ba[1][0] = 1`, all other elements are zero.

    The length of both lists can be egalized with `self._equalize_ba_length()`.

    Views / formats are handled by the ItemDelegate() class.


    """
    sig_tx = pyqtSignal(object) # emitted when filter has been saved
    sig_rx = pyqtSignal(object) # incoming from input_tab_widgets

    def __init__(self, parent):
        super(Input_Coeffs, self).__init__(parent)

        self.opt_widget = None # handle for pop-up options widget
        self.tool_tip = "Display and edit filter coefficients."
        self.tab_label = "b,a"
        
        self.data_changed = True # initialize flag: filter data has been changed
        self.fx_specs_changed = True # fixpoint specs have been changed outside

        self.ui = Input_Coeffs_UI(self) # create the UI part with buttons etc.
        self._construct_UI()

#------------------------------------------------------------------------------
    def process_sig_rx(self, dict_sig=None):
        """
        Process signals coming from sig_rx
        """
        logger.debug("process_sig_rx(): vis={0}\n{1}"\
                    .format(self.isVisible(), pprint_log(dict_sig)))

        if dict_sig['sender'] == __name__:
            logger.debug("Stopped infinite loop\n{0}".format(pprint_log(dict_sig)))
            return

        if  'ui_changed' in dict_sig and dict_sig['ui_changed'] == 'csv':
            self.ui._set_load_save_icons()

        elif self.isVisible():
            if self.data_changed or 'data_changed' in dict_sig:
                self.load_dict()
                self.data_changed = False
            if self.fx_specs_changed or ('fx_sim' in dict_sig and dict_sig['fx_sim'] == 'specs_changed'):
                self.qdict2ui()
                self.fx_specs_changed = False
        else:
            # TODO: draw wouldn't be necessary for 'view_changed', only update view 
            if 'data_changed' in dict_sig:
                self.data_changed = True
            elif 'fx_sim' in dict_sig and dict_sig['fx_sim'] == 'specs_changed':
                self.fx_specs_changed = True

#------------------------------------------------------------------------------
    def _construct_UI(self):
        """
        Intitialize the widget, consisting of:
        - top chkbox row
        - coefficient table
        - two bottom rows with action buttons
        """
        # ---------------------------------------------------------------------
        #   Coefficient table widget
        # ---------------------------------------------------------------------
        self.tblCoeff = QTableWidget(self)
        self.tblCoeff.setAlternatingRowColors(True)
        self.tblCoeff.horizontalHeader().setHighlightSections(True) # highlight when selected
        self.tblCoeff.horizontalHeader().setFont(self.ui.bfont)

#        self.tblCoeff.QItemSelectionModel.Clear
        self.tblCoeff.setDragEnabled(True)
#        self.tblCoeff.setDragDropMode(QAbstractItemView.InternalMove) # doesn't work like intended
        self.tblCoeff.setItemDelegate(ItemDelegate(self))

        # ============== Main UI Layout =====================================
        layVMain = QVBoxLayout()
        layVMain.setAlignment(Qt.AlignTop) # this affects only the first widget (intended here)
        layVMain.addWidget(self.ui)
        layVMain.addWidget(self.tblCoeff)

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

        self.setLayout(layVMain)

        self.myQ = fx.Fixed(fb.fil[0]['fxqc']['QCB']) # initialize fixpoint object
        self.load_dict() # initialize + refresh table with default values from filter dict
        # TODO: this needs to be optimized - self._refresh is being called in both routines
        self._set_number_format()

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        #----------------------------------------------------------------------
        # LOCAL (UI) SIGNALS & SLOTs
        #----------------------------------------------------------------------
        # wdg.textChanged() is emitted when contents of widget changes
        # wdg.textEdited() is only emitted for user changes
        # wdg.editingFinished() is only emitted for user changes
        self.ui.butEnable.clicked.connect(self._refresh_table)
        self.ui.spnDigits.editingFinished.connect(self._refresh_table)

        self.ui.cmbQFrmt.currentIndexChanged.connect(self._set_number_format)
        self.ui.butFromTable.clicked.connect(self._copy_from_table)
        self.ui.butToTable.clicked.connect(self._copy_to_table)

        self.ui.cmbFilterType.currentIndexChanged.connect(self._filter_type)

        self.ui.butDelCells.clicked.connect(self._delete_cells)
        self.ui.butAddCells.clicked.connect(self._add_cells)
        self.ui.butLoad.clicked.connect(self.load_dict)
        self.ui.butSave.clicked.connect(self._save_dict)
        self.ui.butClear.clicked.connect(self._clear_table)
        self.ui.ledEps.editingFinished.connect(self._set_eps)
        self.ui.butSetZero.clicked.connect(self._set_coeffs_zero)

        # store new settings and refresh table
        self.ui.cmbFormat.currentIndexChanged.connect(self.ui2qdict)
        self.ui.cmbQOvfl.currentIndexChanged.connect(self.ui2qdict)
        self.ui.cmbQuant.currentIndexChanged.connect(self.ui2qdict)
        self.ui.ledWF.editingFinished.connect(self.ui2qdict)
        self.ui.ledWI.editingFinished.connect(self.ui2qdict)
        self.ui.ledW.editingFinished.connect(self._W_changed)

        self.ui.ledScale.editingFinished.connect(self._set_scale)

        self.ui.butQuant.clicked.connect(self.quant_coeffs)

        self.ui.sig_tx.connect(self.sig_tx)
        # =====================================================================

#------------------------------------------------------------------------------
    def _filter_type(self, ftype=None):
        """
        Get / set 'FIR' and 'IIR' filter from cmbFilterType combobox and set filter
            dict and table properties accordingly.

        When argument fil_type is not None, set the combobox accordingly.

        Reload from filter dict unless ftype is specified [does this make sense?!]
        """
        if ftype in {'FIR', 'IIR'}:
            ret=qset_cmb_box(self.ui.cmbFilterType, ftype)
            if ret == -1:
                logger.warning("Unknown filter type {0}".format(ftype))

        if self.ui.cmbFilterType.currentText() == 'IIR':
            fb.fil[0]['ft'] = 'IIR'
            self.col = 2
            self.tblCoeff.setColumnCount(2)
            self.tblCoeff.setHorizontalHeaderLabels(["b", "a"])
        else:
            fb.fil[0]['ft'] = 'FIR'
            self.col = 1
            self.tblCoeff.setColumnCount(1)
            self.tblCoeff.setHorizontalHeaderLabels(["b"])
            self.ba[1] = np.zeros_like(self.ba[1]) # enforce FIR filter
            self.ba[1][0] = 1.

        self._equalize_ba_length()
        qstyle_widget(self.ui.butSave, 'changed')
        self._refresh_table()

#------------------------------------------------------------------------------
    def _W_changed(self):
        """
        Set fractional and integer length `WF` and `WI` when wordlength `W` has
        been changed. Try to preserve `WI` or `WF` settings depending on the
        number format (integer or fractional).
        """
        W = safe_eval(self.ui.ledW.text(), self.myQ.W, return_type='int', sign='pos')

        if W < 2:
            logger.warn("W must be > 1, restoring previous value.")
            W = self.myQ.W # fall back to previous value
        self.ui.ledW.setText(str(W))

        if qget_cmb_box(self.ui.cmbQFrmt) == 'qint': # integer format, preserve WI bits
            WI = W - self.myQ.WF - 1
            self.ui.ledWI.setText(str(WI))
            self.ui.ledScale.setText(str(1 << (W-1)))
        else: # fractional format, preserve WF bit setting
            WF = W - self.myQ.WI - 1
            if WF < 0:
                self.ui.ledWI.setText(str(W - 1))
                WF = 0
            self.ui.ledWF.setText(str(WF))

        self.ui2qdict()

        #------------------------------------------------------------------------------
    def _set_scale(self):
        """
        Triggered by `ui.ledScale`
        Set scale for calculating floating point value from fixpoint representation
        and vice versa
        """
        # if self.ui.ledScale.isModified() ... self.ui.ledScale.setModified(False)
        scale = safe_eval(self.ui.ledScale.text(), self.myQ.scale, return_type='float', sign='pos')
        self.ui.ledScale.setText(str(scale))
        self.ui2qdict()

#------------------------------------------------------------------------------
    def _refresh_table_item(self, row, col):
        """
        Refresh the table item with the index `row, col` from self.ba
        """
        item = self.tblCoeff.item(row, col)
        if item: # does item exist?
            item.setText(str(self.ba[col][row]).strip('()'))
        else: # no, construct it:
            self.tblCoeff.setItem(row,col,QTableWidgetItem(
                  str(self.ba[col][row]).strip('()')))
        self.tblCoeff.item(row, col).setTextAlignment(Qt.AlignRight|Qt.AlignCenter)

#------------------------------------------------------------------------------
    def _refresh_table(self):
        """
        (Re-)Create the displayed table from `self.ba` (list with 2 one-dimensional
        numpy arrays). Data is displayed via `ItemDelegate.displayText()` in
        the number format set by `self.frmt`.

        - self.ba[0] -> b coefficients
        - self.ba[1] -> a coefficients
        
        The table dimensions are set according to the filter type set in 
        `fb.fil[0]['ft']` which is either 'FIR' or 'IIR' and by the number of 
        rows in `self.ba`.

        Called at the end of nearly every method.
        """
        if np.ndim(self.ba) == 1 or fb.fil[0]['ft'] == 'FIR':
            self.num_rows = len(self.ba[0])
        else:
            self.num_rows = max(len(self.ba[1]), len(self.ba[0]))

        # logger.warning("np.shape(ba) = {0}".format(np.shape(self.ba)))

        params['FMT_ba'] = int(self.ui.spnDigits.text())

        # When format is 'float', disable all fixpoint options
        is_float = (qget_cmb_box(self.ui.cmbFormat, data=False).lower() == 'float')

        self.ui.spnDigits.setVisible(is_float) # number of digits can only be selected
        self.ui.lblDigits.setVisible(is_float) # for format = 'float'
        self.ui.cmbQFrmt.setVisible(not is_float) # hide unneeded widgets for format = 'float'
        self.ui.lbl_W.setVisible(not is_float)
        self.ui.ledW.setVisible(not is_float)

        self.ui.frmQSettings.setVisible(not is_float) # hide all q-settings for float

        if self.ui.butEnable.isChecked():
            self.ui.butEnable.setIcon(QIcon(':/circle-x.svg'))
            self.ui.frmButtonsCoeffs.setVisible(True)
            self.tblCoeff.setVisible(True)

            # check whether filter is FIR and only needs one column
            if fb.fil[0]['ft'] == 'FIR':
                self.num_cols = 1
                self.tblCoeff.setColumnCount(1)
                self.tblCoeff.setHorizontalHeaderLabels(["b"])
                qset_cmb_box(self.ui.cmbFilterType, 'FIR')
            else:
                self.num_cols = 2
                self.tblCoeff.setColumnCount(2)
                self.tblCoeff.setHorizontalHeaderLabels(["b", "a"])
                qset_cmb_box(self.ui.cmbFilterType, 'IIR')

                self.ba[1][0] = 1.0 # restore fa[0] = 1 of denonimator polynome

            self.tblCoeff.setRowCount(self.num_rows)
            self.tblCoeff.setColumnCount(self.num_cols)
            # Create strings for index column (vertical header), starting with "0"
            idx_str = [str(n) for n in range(self.num_rows)]
            self.tblCoeff.setVerticalHeaderLabels(idx_str)

            self.tblCoeff.blockSignals(True)
            for col in range(self.num_cols):
                for row in range(self.num_rows):
                    self._refresh_table_item(row, col)

            # make a[0] selectable but not editable
            if fb.fil[0]['ft'] == 'IIR':
                item = self.tblCoeff.item(0,1)
                item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
                item.setFont(self.ui.bfont)

            self.tblCoeff.blockSignals(False)

            self.tblCoeff.resizeColumnsToContents()
            self.tblCoeff.resizeRowsToContents()
            self.tblCoeff.clearSelection()

        else:
            self.ui.frmButtonsCoeffs.setVisible(False)
            self.ui.butEnable.setIcon(QIcon(':/circle-check.svg'))
            self.tblCoeff.setVisible(False)

#------------------------------------------------------------------------------
    def load_dict(self):
        """
        Load all entries from filter dict `fb.fil[0]['ba']` into the coefficient
        list `self.ba` and update the display via `self._refresh_table()`.

        The filter dict is a "normal" 2D-numpy float array for the b and a coefficients
        while the coefficient register `self.ba` is a list of two float ndarrays to allow
        for different lengths of b and a subarrays while adding / deleting items.
        """

        self.ba = [0., 0.] # initial list with two elements
        self.ba[0] = np.array(fb.fil[0]['ba'][0]) # deep copy from filter dict to
        self.ba[1] = np.array(fb.fil[0]['ba'][1]) # coefficient register

        # set quantization comboBoxes from dictionary
        self.qdict2ui()

        self._refresh_table()
        qstyle_widget(self.ui.butSave, 'normal')

    #------------------------------------------------------------------------------
    def _copy_options(self):
        """
        Set options for copying to/from clipboard or file.
        """
        self.opt_widget = CSV_option_box(self) # important: Handle must be class attribute
        #self.opt_widget.show() # modeless dialog, i.e. non-blocking
        self.opt_widget.exec_() # modal dialog (blocking)

    #------------------------------------------------------------------------------
    def _copy_from_table(self):
        """
        Copy data from coefficient table `self.tblCoeff` to clipboard / file in
        CSV format.
        """
        qtable2text(self.tblCoeff, self.ba, self, 'ba', self.myQ.frmt, title="Export Filter Coefficients")

    #------------------------------------------------------------------------------
    def _copy_to_table(self):
        """
        Read data from clipboard / file and copy it to `self.ba` as float / cmplx
        # TODO: More checks for swapped row <-> col, single values, wrong data type ...
        """
        data_str = qtext2table(self, 'ba', title="Import Filter Coefficients") # returns ndarray of str
        if data_str is None: # file operation has been aborted or some other error
            return

        logger.debug("importing data: dim - shape = {0} - {1} - {2}\n{3}"\
                       .format(type(data_str), np.ndim(data_str), np.shape(data_str), data_str))

        conv = self.myQ.frmt2float # frmt2float_vec?
        frmt = self.myQ.frmt

        if np.ndim(data_str) > 1:
            num_cols, num_rows = np.shape(data_str)
            orientation_horiz = num_cols > num_rows # need to transpose data
        elif np.ndim(data_str) == 1:
            num_rows = len(data_str)
            num_cols = 1
            orientation_horiz = False
        else:
            logger.error("Imported data is a single value or None.")
            return None
        logger.info("_copy_to_table: c x r = {0} x {1}".format(num_cols, num_rows))
        if orientation_horiz:
            self.ba = [[],[]]
            for c in range(num_cols):
                self.ba[0].append(conv(data_str[c][0], frmt))
                if num_rows > 1:
                    self.ba[1].append(conv(data_str[c][1], frmt))
            if num_rows > 1:
                self._filter_type(ftype='IIR')
            else:
                self._filter_type(ftype='FIR')
        else:
            self.ba[0] = [conv(s, frmt) for s in data_str[0]]
            if num_cols > 1:
                self.ba[1] = [conv(s, frmt) for s in data_str[1]]
                self._filter_type(ftype='IIR')
            else:
                self.ba[1] = [1]
                self._filter_type(ftype='FIR')

        self.ba[0] = np.asarray(self.ba[0])
        self.ba[1] = np.asarray(self.ba[1])

        self._equalize_ba_length()
        qstyle_widget(self.ui.butSave, 'changed')
        self._refresh_table()

#------------------------------------------------------------------------------
    def _set_number_format(self):
        """
        Triggered by `contruct_UI()`, `qdict2ui()`and by `ui.cmbQFrmt.currentIndexChanged()`
        
        Set one of three number formats: Integer, fractional, normalized fractional
        (triggered by self.ui.cmbQFrmt combobox)
        """

        qfrmt = qget_cmb_box(self.ui.cmbQFrmt)
        is_qfrac = False
        W = safe_eval(self.ui.ledW.text(), self.myQ.W, return_type='int', sign='pos')
        if qfrmt == 'qint':
            self.ui.ledWI.setText(str(W - 1))
            self.ui.ledWF.setText("0")
        elif qfrmt == 'qnfrac': # normalized fractional format
            self.ui.ledWI.setText("0")
            self.ui.ledWF.setText(str(W - 1))
        else: # qfrmt == 'qfrac':
            is_qfrac = True
            
        WI = safe_eval(self.ui.ledWI.text(), self.myQ.WI, return_type='int')

        self.ui.ledScale.setText(str(1 << WI))
        self.ui.ledWI.setEnabled(is_qfrac)
        self.ui.lblDot.setEnabled(is_qfrac)
        self.ui.ledWF.setEnabled(is_qfrac)
        self.ui.ledW.setEnabled(not is_qfrac)
        self.ui.ledScale.setEnabled(False)

        self.ui2qdict() # save UI to dict and to class attributes
        
#------------------------------------------------------------------------------
    def _update_MSB_LSB(self):
        """
        Update the infos (LSB, MSB, Max)
        """
        self.ui.lblLSB.setText("{0:.{1}g}".format(self.myQ.LSB, params['FMT_ba']))
        self.ui.lblMSB.setText("{0:.{1}g}".format(self.myQ.MSB, params['FMT_ba']))
        self.ui.lblMAX.setText("{0:.6g}".format(self.myQ.MAX))


#------------------------------------------------------------------------------
    def qdict2ui(self):
        """
        Triggered by:
        - process_sig_rx()  if self.fx_specs_changed or dict_sig['fx_sim'] == 'specs_changed'
        - 
        Set the UI from the quantization dict and update the fixpoint object.
        When neither WI == 0 nor WF == 0, set the quantization format to general
        fractional format qfrac.
        """
        self.ui.ledWI.setText(qstr(fb.fil[0]['fxqc']['QCB']['WI']))
        self.ui.ledWF.setText(qstr(fb.fil[0]['fxqc']['QCB']['WF']))
        self.ui.ledW.setText(qstr(fb.fil[0]['fxqc']['QCB']['W']))
        if fb.fil[0]['fxqc']['QCB']['WI'] != 0 and fb.fil[0]['fxqc']['QCB']['WF'] != 0:
            qset_cmb_box(self.ui.cmbQFrmt, 'qfrac', data=True)

        self.ui.ledScale.setText(qstr(fb.fil[0]['fxqc']['QCB']['scale']))
        qset_cmb_box(self.ui.cmbQuant, fb.fil[0]['fxqc']['QCB']['quant'])
        qset_cmb_box(self.ui.cmbQOvfl,  fb.fil[0]['fxqc']['QCB']['ovfl'])

        self.myQ.setQobj(fb.fil[0]['fxqc']['QCB']) # update class attributes

        self._set_number_format() # quant format has been changed, update display
        self._update_MSB_LSB()

#------------------------------------------------------------------------------
    def ui2qdict(self):
        """
        Triggered by modifying 
        `ui.cmbFormat`, `ui.cmbQOvfl`, `ui.cmbQuant`, `ui.ledWF`, `ui.ledWI`
        or `ui.ledW` (via `_W_changed()`)
        or `ui.cmbQFrmt` (via `_set_number_format()`)
        or `ui.ledScale()` (via `_set_scale()`)
        or 'qdict2ui()' via `_set_number_format()`
        
        Read out the settings of the quantization comboboxes.

        - Store them in the filter dict `fb.fil[0]['fxqc']['QCB']` and as class
            attributes in the fixpoint object `self.myQ`

        - Emit a signal with `'view_changed':'q_coeff'`

        - Refresh the table
        """
        fb.fil[0]['fxqc']['QCB'] = {
                'WI':safe_eval(self.ui.ledWI.text(), self.myQ.WI, return_type='int'),
                'WF':safe_eval(self.ui.ledWF.text(), self.myQ.WF, return_type='int', sign='poszero'),
                'W':safe_eval(self.ui.ledW.text(), self.myQ.W, return_type='int', sign='pos'),
                'quant':qstr(self.ui.cmbQuant.currentText()),
                'ovfl':qstr(self.ui.cmbQOvfl.currentText()),
                'frmt':qstr(self.ui.cmbFormat.currentText().lower()),
                'scale':qstr(self.ui.ledScale.text())
                }

        self.myQ.setQobj(fb.fil[0]['fxqc']['QCB']) # update fixpoint object

        self.sig_tx.emit({'sender':__name__, 'view_changed':'q_coeff'})
        
        self._update_MSB_LSB()

        self._refresh_table()

#------------------------------------------------------------------------------
    def _save_dict(self):
        """
        Save the coefficient register `self.ba` to the filter dict `fb.fil[0]['ba']`.
        """

        logger.debug("_save_dict called")

        fb.fil[0]['N'] = max(len(self.ba[0]), len(self.ba[1])) - 1

        self.ui2qdict()

        if fb.fil[0]['ft'] == 'IIR':
            fb.fil[0]['fc'] = 'Manual_IIR'
        else:
            fb.fil[0]['fc'] = 'Manual_FIR'

        # save, check and convert coeffs, check filter type
        try:
            fil_save(fb.fil[0], self.ba, 'ba', __name__)
        except Exception as e:
            # catch exception due to malformatted coefficients:
            logger.error("While saving the filter coefficients, "
                         "the following error occurred:\n{0}".format(e))

        if __name__ == '__main__':
            self.load_dict() # only needed for stand-alone test

        self.sig_tx.emit({'sender':__name__, 'data_changed':'input_coeffs'})
        # -> input_tab_widgets

        qstyle_widget(self.ui.butSave, 'normal')

#------------------------------------------------------------------------------
    def _clear_table(self):
        """
        Clear self.ba: Initialize coeff for a poles and a zero @ origin,
        a = b = [1; 0].

        Refresh QTableWidget
        """
        self.ba = [np.asarray([1., 0.]), np.asarray([1., 0.])]

        self._refresh_table()
        qstyle_widget(self.ui.butSave, 'changed')


#------------------------------------------------------------------------------
    def _equalize_ba_length(self):
        """
        test and equalize if b and a subarray have different lengths:
        """
        try:
            a_len = len(self.ba[1])
        except IndexError:
            self.ba.append(np.array(1))
            a_len = 1

        D = len(self.ba[0]) - a_len

        if D > 0: # b is longer than a
            self.ba[1] = np.append(self.ba[1], np.zeros(D))
        elif D < 0: # a is longer than b
            if fb.fil[0]['ft'] == 'IIR':
                self.ba[0] = np.append(self.ba[0], np.zeros(-D))
            else:
                self.ba[1] = self.ba[1][:D] # discard last D elements of a

#------------------------------------------------------------------------------
    def _delete_cells(self):
        """
        Delete all selected elements in self.ba by:
        - determining the indices of all selected cells in the P and Z arrays
        - deleting elements with those indices
        - equalizing the lengths of b and a array by appending the required
          number of zeros.
        When nothing is selected, delete the last row.
        Finally, the QTableWidget is refreshed from self.ba.
        """
        sel = qget_selected(self.tblCoeff)['sel'] # get indices of all selected cells

        if not any(sel) and len(self.ba[0]) > 0: # delete last row
            self.ba = np.delete(self.ba, -1, axis=1)
        elif np.all(sel[0] == sel[1]) or fb.fil[0]['ft'] == 'FIR':
            # only complete rows selected or FIR -> delete row
            self.ba = np.delete(self.ba, sel[0], axis=1)
        else:
            self.ba[0][sel[0]] = 0
            self.ba[1][sel[1]] = 0
            #self.ba[0] = np.delete(self.ba[0], sel[0])
            #self.ba[1] = np.delete(self.ba[1], sel[1])    
        # test and equalize if b and a array have different lengths:
        self._equalize_ba_length()
        # if length is less than 2, clear the table: this ain't no filter!
        if len(self.ba[0]) < 2:
            self._clear_table() # sets 'changed' attribute
        else:
            self._refresh_table()
            qstyle_widget(self.ui.butSave, 'changed')

#------------------------------------------------------------------------------
    def _add_cells(self):
        """
        Add the number of selected rows to self.ba and fill new cells with
        zeros from the bottom. If nothing is selected, add one row at the bottom.
        Refresh QTableWidget.
        """
        # get indices of all selected cells
        sel = qget_selected(self.tblCoeff)['sel']

        if not any(sel): # nothing selected, append one row of zeros to table
            self.ba = np.insert(self.ba, len(self.ba[0]), 0, axis=1) #"insert" row after last
        elif np.all(sel[0] == sel[1]) or fb.fil[0]['ft'] == 'FIR': # only complete rows selected
            self.ba = np.insert(self.ba, sel[0], 0, axis=1)
#        elif len(sel[0]) == len(sel[1]):
#            self.ba = np.insert(self.ba, sel, 0, axis=1)
#       not allowed, sel needs to be a scalar or one-dimensional
        else:
            logger.warning("It is only possible to insert complete rows!")
            # The following doesn't work because the subarrays wouldn't have 
            # the same length for a moment
            #self.ba[0] = np.insert(self.ba[0], sel[0], 0)
            #self.ba[1] = np.insert(self.ba[1], sel[1], 0)
            return
        # insert 'sel' contiguous rows  before 'row':
        # self.ba[0] = np.insert(self.ba[0], row, np.zeros(sel))

        self._equalize_ba_length()
        self._refresh_table()
        # don't tag as 'changed' when only zeros have been added at the end
        if any(sel):
            qstyle_widget(self.ui.butSave, 'changed')

#------------------------------------------------------------------------------
    def _set_eps(self):
        """
        Set all coefficients = 0 in self.ba with a magnitude less than eps
        and refresh QTableWidget
        """
        self.ui.eps = safe_eval(self.ui.ledEps.text(), return_type='float', sign='pos', alt_expr=self.ui.eps)
        self.ui.ledEps.setText(str(self.ui.eps))

#------------------------------------------------------------------------------
    def _set_coeffs_zero(self):
        """
        Set all coefficients = 0 in self.ba with a magnitude less than eps
        and refresh QTableWidget
        """
        self._set_eps()
        idx = qget_selected(self.tblCoeff)['idx'] # get all selected indices

        test_val = 0. # value against which array is tested
        targ_val = 0. # value which is set when condition is true
        changed = False

        if not idx: # nothing selected, check whole table
            b_close = np.logical_and(np.isclose(self.ba[0], test_val, rtol=0, atol=self.ui.eps),
                                    (self.ba[0] != targ_val))
            if np.any(b_close): # found at least one coeff where condition was true
                self.ba[0] = np.where(b_close, targ_val, self.ba[0])
                changed = True

            if  fb.fil[0]['ft'] == 'IIR':
                a_close = np.logical_and(np.isclose(self.ba[1], test_val, rtol=0, atol=self.ui.eps),
                                    (self.ba[1] != targ_val))
                if np.any(a_close):
                    self.ba[1] = np.where(a_close, targ_val, self.ba[1])
                    changed = True

        else: # only check selected cells
            for i in idx:
                if np.logical_and(np.isclose(self.ba[i[0]][i[1]], test_val, rtol=0, atol=self.ui.eps),
                                  (self.ba[i[0]][i[1]] != targ_val)):
                    self.ba[i[0]][i[1]] = targ_val
                    changed = True
        if changed:
            qstyle_widget(self.ui.butSave, 'changed') # mark save button as changed

        self._refresh_table()

#------------------------------------------------------------------------------
    def quant_coeffs(self):
        """
        Quantize selected / all coefficients in self.ba and refresh QTableWidget
        """
        idx = qget_selected(self.tblCoeff)['idx'] # get all selected indices
        if not idx: # nothing selected, quantize all elements
            self.ba[0] = self.myQ.fixp(self.ba, scaling='multdiv')[0]
            if fb.fil[0]['ft'] == "IIR":
                self.ba[1] = self.myQ.fixp(self.ba, scaling='multdiv')[0]
        else:
            for i in idx:
                self.ba[i[0]][i[1]] = self.myQ.fixp(self.ba[i[0]][i[1]], scaling = 'multdiv')

        qstyle_widget(self.ui.butSave, 'changed')
        self._refresh_table()