class FilterCoeffs(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. """ sigFilterDesigned = pyqtSignal() # emitted when coeffs have been changed def __init__(self, parent): super(FilterCoeffs, self).__init__(parent) self.opt_widget = None # handle for pop-up options widget self.ui = FilterCoeffs_UI(self) # create the UI part with buttons etc. self._construct_UI() def _construct_UI(self): """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ # handle to central clipboard instance self.clipboard = fb.clipboard # --------------------------------------------------------------------- # 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 = fix.Fixed(fb.fil[0]["q_coeff"]) # 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() # ============== 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) # refresh table after storing new settings self.ui.cmbFormat.currentIndexChanged.connect(self._refresh_table) self.ui.cmbQOvfl.currentIndexChanged.connect(self._refresh_table) self.ui.cmbQuant.currentIndexChanged.connect(self._refresh_table) self.ui.ledWF.editingFinished.connect(self._WIWF_changed) self.ui.ledWI.editingFinished.connect(self._WIWF_changed) self.ui.ledW.editingFinished.connect(self._W_changed) self.ui.ledScale.editingFinished.connect(self._set_scale) self.ui.butQuant.clicked.connect(self.quant_coeffs) # ===================================================================== #------------------------------------------------------------------------------ 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"]) if not ftype: self._refresh_table() # self.load_dict() overwrites current info - is this ok? #------------------------------------------------------------------------------ def _WIWF_changed(self): """ Store values for 'WI' and 'WF' when either has been changed, update wordlength `W` accordingly and update table """ self._store_q_settings() self._refresh_table() #------------------------------------------------------------------------------ def _W_changed(self): """ Set fractional and integer length `WF` and `WI` when wordlength Ẁ` has been changed. Try to preserve `WI` or `WF` settings depending on the number format (integer or fractional). """ # if self.ui.ledW.isModified() ... self.ui.ledW.setModified(False) 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._store_q_settings() self._refresh_table() #------------------------------------------------------------------------------ def _set_number_format(self): """ Set one of three number formats: Integer, fractional, normalized fractional """ 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") self.ui.ledScale.setText(str(1 << (W-1))) elif qfrmt == 'qnfrac': # normalized fractional format self.ui.ledWI.setText("0") self.ui.ledWF.setText(str(W - 1)) self.ui.ledScale.setText("1") else: # qfrmt == 'qfrac': is_qfrac = True 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(is_qfrac) self._store_q_settings() self._refresh_table() #------------------------------------------------------------------------------ def _set_scale(self): """ 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._store_q_settings() self._refresh_table() #------------------------------------------------------------------------------ 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 columns of float scalars). Data is displayed via `ItemDelegate.displayText()` in the number format set by `self.frmt`. The table dimensions are set according to the dimensions of `self.ba`: - self.ba[0] -> b coefficients - self.ba[1] -> a coefficients Called at the end of nearly every method. """ try: self.num_rows = max(len(self.ba[1]), len(self.ba[0])) except IndexError: self.num_rows = len(self.ba[0]) logger.debug("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) if self.ui.butEnable.isChecked(): self.ui.frmQSettings.setVisible(not is_float) # hide all q-settings for float self.ui.butEnable.setIcon(QIcon(':/circle-x.svg')) self.tblCoeff.setVisible(True) self._store_q_settings() # store updated quantization / format settings # 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.frmQSettings.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 comboBoxes from dictionary self._load_q_settings() 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) #------------------------------------------------------------------------------ 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', comment="filter coefficients ") if data_str == -1: # file operation has been aborted 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 _load_q_settings(self): """ load the quantization settings from the filter dict and set the widgets accordingly. Update the fixpoint object. """ self.myQ.setQobj(fb.fil[0]['q_coeff']) q_coeff = self.myQ.q_obj self.ui.ledWI.setText(str(q_coeff['WI'])) self.ui.ledWF.setText(str(q_coeff['WF'])) qset_cmb_box(self.ui.cmbQuant, q_coeff['quant']) qset_cmb_box(self.ui.cmbQOvfl, q_coeff['ovfl']) qset_cmb_box(self.ui.cmbFormat, q_coeff['frmt']) self.ui.ledScale.setText(str(q_coeff['scale'])) self.ui.ledW.setText(str(self.myQ.W)) 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}".format(self.myQ.float2frmt(self.myQ.MAX/self.myQ.scale))) #------------------------------------------------------------------------------ def _store_q_settings(self): """ Read out the settings of the quantization comboboxes and store them in the filter dict. Update the fixpoint object. """ fb.fil[0]['q_coeff'] = { '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='pos'), 'quant':qstr(self.ui.cmbQuant.currentText()), 'ovfl':qstr(self.ui.cmbQOvfl.currentText()), 'frmt':qstr(self.ui.cmbFormat.currentText()), 'scale':qstr(self.ui.ledScale.text()) } self._load_q_settings() # update widgets and the fixpoint object self.myQ #------------------------------------------------------------------------------ def _save_dict(self): """ Save the coefficient register `self.ba` to the filter dict `fb.fil[0]['ba']`. """ logger.debug("_save_entries called") fb.fil[0]['N'] = max(len(self.ba[0]), len(self.ba[1])) - 1 self._store_q_settings() 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 fil_save(fb.fil[0], self.ba, 'ba', __name__) if __name__ == '__main__': self.load_dict() # only needed for stand-alone test self.sigFilterDesigned.emit() # -> filter_specs # -> input_tab_widgets -> pyfdax -> plt_tab_widgets.updateAll() 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 np.any(sel) and len(self.ba[0]) > 0: self.ba[0] = np.delete(self.ba[0], -1) self.ba[1] = np.delete(self.ba[1], -1) else: 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 np.any(sel): # nothing selected, append zeros to table np.append(self.ba[0], 0) np.append(self.ba[1], 0) else: self.ba[0] = np.insert(self.ba[0], sel[0], 0) self.ba[1] = np.insert(self.ba[1], sel[1], 0) # 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 np.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 = self.myQ.fixp(self.ba, scaling='multdiv') 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()
class FilterCoeffs_UI(QWidget): """ Create the UI for the FilterCoeffs class """ def __init__(self, parent): super(FilterCoeffs_UI, self).__init__(parent) self.eps = 1.e-6 # initialize tolerance value """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ self.bfont = QFont() self.bfont.setBold(True) self.bifont = QFont() self.bifont.setBold(True) self.bifont.setItalic(True) # q_icon_size = QSize(20, 20) # optional, size is derived from butEnable ####################################################################### # frmMain # # This frame contains all the buttons ####################################################################### # --------------------------------------------- # layHDisplay # # UI Elements for controlling the display # --------------------------------------------- self.butEnable = QPushButton(self) self.butEnable.setIcon(QIcon(':/circle-x.svg')) q_icon_size = self.butEnable.iconSize() # <- uncomment this for manual sizing self.butEnable.setIconSize(q_icon_size) self.butEnable.setCheckable(True) self.butEnable.setChecked(True) self.butEnable.setToolTip("<span>Show / hide filter coefficients as an editable table." "For high order systems, table display might be slow.</span>") fix_formats = ['Dec', 'Hex', 'Bin', 'CSD'] self.cmbFormat = QComboBox(self) model = self.cmbFormat.model() item = QtGui.QStandardItem('Float') item.setData('child', Qt.AccessibleDescriptionRole) model.appendRow(item) item = QtGui.QStandardItem('Fixp.:') item.setData('parent', Qt.AccessibleDescriptionRole) item.setData(0, QtGui.QFont.Bold) item.setFlags(item.flags() & ~Qt.ItemIsEnabled)# | Qt.ItemIsSelectable)) model.appendRow(item) for idx in range(len(fix_formats)): item = QtGui.QStandardItem(fix_formats[idx]) # item.setForeground(QtGui.QColor('red')) model.appendRow(item) self.cmbFormat.insertSeparator(1) qset_cmb_box(self.cmbFormat, 'float') self.cmbFormat.setToolTip('Set the display format.') self.cmbFormat.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.spnDigits = QSpinBox(self) self.spnDigits.setRange(0,16) self.spnDigits.setValue(params['FMT_ba']) self.spnDigits.setToolTip("Number of digits to display.") self.lblDigits = QLabel("Digits", self) self.lblDigits.setFont(self.bifont) self.cmbQFrmt = QComboBox(self) q_formats = [('Integer', 'qint' ), ('Norm. Frac.', 'qnfrac'), ('Fractional', 'qfrac')] for q in q_formats: self.cmbQFrmt.addItem(*q) self.lbl_W = QLabel("W = ", self) self.lbl_W.setFont(self.bifont) self.ledW = QLineEdit(self) self.ledW.setToolTip("Specify total wordlength.") self.ledW.setText("16") self.ledW.setMaxLength(2) # maximum of 2 digits self.ledW.setFixedWidth(30) # width of lineedit in points(?) layHDisplay = QHBoxLayout() layHDisplay.setAlignment(Qt.AlignLeft) layHDisplay.addWidget(self.butEnable) layHDisplay.addWidget(self.cmbFormat) layHDisplay.addWidget(self.spnDigits) layHDisplay.addWidget(self.lblDigits) layHDisplay.addWidget(self.cmbQFrmt) layHDisplay.addWidget(self.lbl_W) layHDisplay.addWidget(self.ledW) layHDisplay.addStretch() # ----------------------------------------------------------------- # layHButtonsCoeffs1 # # UI Elements for loading / storing / manipulating cells and rows # ----------------------------------------------------------------- self.cmbFilterType = QComboBox(self) self.cmbFilterType.setObjectName("comboFilterType") self.cmbFilterType.setToolTip("<span>Select between IIR and FIR filter for manual entry." "Changing the type reloads the filter from the filter dict.</span>") self.cmbFilterType.addItems(["FIR","IIR"]) self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.butAddCells = QPushButton(self) self.butAddCells.setIcon(QIcon(':/row_insert_above.svg')) self.butAddCells.setIconSize(q_icon_size) self.butAddCells.setToolTip("<SPAN>Select cells to insert a new cell above each selected cell. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, add a row at the end.</SPAN>") self.butDelCells = QPushButton(self) self.butDelCells.setIcon(QIcon(':/row_delete.svg')) self.butDelCells.setIconSize(q_icon_size) self.butDelCells.setToolTip("<SPAN>Delete selected cell(s) from the table. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, delete the last row.</SPAN>") self.butSave = QPushButton(self) self.butSave.setIcon(QIcon(':/upload.svg')) self.butSave.setIconSize(q_icon_size) self.butSave.setToolTip("<span>Save coefficients and update all plots. " "No modifications are saved before!</span>") self.butLoad = QPushButton(self) self.butLoad.setIcon(QIcon(':/download.svg')) self.butLoad.setIconSize(q_icon_size) self.butLoad.setToolTip("Reload coefficients.") self.butClear = QPushButton(self) self.butClear.setIcon(QIcon(':/trash.svg')) self.butClear.setIconSize(q_icon_size) self.butClear.setToolTip("Clear all entries.") self.butFromTable = QPushButton(self) self.butFromTable.setIcon(QIcon(':/to_clipboard.svg')) self.butFromTable.setIconSize(q_icon_size) self.butFromTable.setToolTip("<span>" "Copy table to clipboard / file, SELECTED items are copied as " "displayed. When nothing is selected, the whole table " "is copied with full precision in decimal format. </span>") self.butToTable = QPushButton(self) self.butToTable.setIcon(QIcon(':/from_clipboard.svg')) self.butToTable.setIconSize(q_icon_size) self.butToTable.setToolTip("<span>Copy clipboard / file to table.</span>") butSettingsClipboard = QPushButton(self) butSettingsClipboard.setIcon(QIcon(':/settings.svg')) butSettingsClipboard.setIconSize(q_icon_size) butSettingsClipboard.setToolTip("<span>Adjust settings for CSV format and whether " "to copy to/from clipboard or file.</span>") layHButtonsCoeffs1 = QHBoxLayout() layHButtonsCoeffs1.addWidget(self.cmbFilterType) layHButtonsCoeffs1.addWidget(self.butAddCells) layHButtonsCoeffs1.addWidget(self.butDelCells) layHButtonsCoeffs1.addWidget(self.butClear) layHButtonsCoeffs1.addWidget(self.butSave) layHButtonsCoeffs1.addWidget(self.butLoad) layHButtonsCoeffs1.addWidget(self.butFromTable) layHButtonsCoeffs1.addWidget(self.butToTable) layHButtonsCoeffs1.addWidget(butSettingsClipboard) layHButtonsCoeffs1.addStretch() #---------------------------------------------------------------------- # layHButtonsCoeffs2 # # Eps / set zero settings # --------------------------------------------------------------------- self.butSetZero = QPushButton("= 0", self) self.butSetZero.setToolTip("<span>Set selected coefficients = 0 with a magnitude < ε. " "When nothing is selected, test the whole table.</span>") self.butSetZero.setIconSize(q_icon_size) lblEps = QLabel(self) lblEps.setText("<b><i>for b, a</i> <</b>") self.ledEps = QLineEdit(self) self.ledEps.setToolTip("Specify tolerance value.") layHButtonsCoeffs2 = QHBoxLayout() layHButtonsCoeffs2.addWidget(self.butSetZero) layHButtonsCoeffs2.addWidget(lblEps) layHButtonsCoeffs2.addWidget(self.ledEps) layHButtonsCoeffs2.addStretch() ####################################################################### # frmQSettings # # This frame contains all quantization settings ####################################################################### #------------------------------------------------------------------- # layHW_Scale # # QFormat and scale settings # --------------------------------------------------------------------- lbl_Q = QLabel("Q =", self) lbl_Q.setFont(self.bifont) self.ledWI = QLineEdit(self) self.ledWI.setToolTip("Specify number of integer bits.") self.ledWI.setText("0") self.ledWI.setMaxLength(2) # maximum of 2 digits self.ledWI.setFixedWidth(30) # width of lineedit in points(?) self.lblDot = QLabel(".", self) # class attribute, visibility is toggled self.lblDot.setFont(self.bfont) self.ledWF = QLineEdit(self) self.ledWF.setToolTip("Specify number of fractional bits.") self.ledWF.setText("15") self.ledWF.setMaxLength(2) # maximum of 2 digits # self.ledWF.setFixedWidth(30) # width of lineedit in points(?) self.ledWF.setMaximumWidth(30) self.lblScale = QLabel("<b><i>Scale</i> =</b>", self) self.ledScale = QLineEdit(self) self.ledScale.setToolTip("Set the scale for converting float to fixpoint representation.") self.ledScale.setText(str(1)) layHWI_WF = QHBoxLayout() layHWI_WF.addWidget(lbl_Q) layHWI_WF.addWidget(self.ledWI) layHWI_WF.addWidget(self.lblDot) layHWI_WF.addWidget(self.ledWF) layHWI_WF.addStretch() layHScale = QHBoxLayout() layHScale.addWidget(self.lblScale) layHScale.addWidget(self.ledScale) layHScale.addStretch() layHW_Scale = QHBoxLayout() layHW_Scale.addLayout(layHWI_WF) layHW_Scale.addLayout(layHScale) #------------------------------------------------------------------- # layGQOpt # # Quantization / Overflow / MSB / LSB settings # --------------------------------------------------------------------- lblQOvfl = QLabel("Ovfl.:", self) lblQOvfl.setFont(self.bifont) lblQuant = QLabel("Quant.:", self) lblQuant.setFont(self.bifont) self.cmbQOvfl = QComboBox(self) qOvfl = ['wrap', 'sat'] self.cmbQOvfl.addItems(qOvfl) qset_cmb_box(self.cmbQOvfl, 'sat') self.cmbQOvfl.setToolTip("Select overflow behaviour.") # ComboBox size is adjusted automatically to fit the longest element self.cmbQOvfl.setSizeAdjustPolicy(QComboBox.AdjustToContents) layHQOvflOpt = QHBoxLayout() layHQOvflOpt.addWidget(lblQOvfl) layHQOvflOpt.addWidget(self.cmbQOvfl) layHQOvflOpt.addStretch() self.cmbQuant = QComboBox(self) qQuant = ['none', 'round', 'fix', 'floor'] self.cmbQuant.addItems(qQuant) qset_cmb_box(self.cmbQuant, 'round') self.cmbQuant.setToolTip("Select the kind of quantization.") self.cmbQuant.setSizeAdjustPolicy(QComboBox.AdjustToContents) layHQuantOpt = QHBoxLayout() layHQuantOpt.addWidget(lblQuant) layHQuantOpt.addWidget(self.cmbQuant) layHQuantOpt.addStretch() self.butQuant = QPushButton(self) self.butQuant.setToolTip("<span>Quantize selected coefficients / " "whole table with specified settings.</span>") self.butQuant.setIcon(QIcon(':/quantize.svg')) self.butQuant.setIconSize(q_icon_size) self.butQuant.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) lblMSBtxt = QLabel(self) lblMSBtxt.setText("<b><i>MSB</i><sub>10</sub> =</b>") self.lblMSB = QLabel(self) layHMSB = QHBoxLayout() layHMSB.addWidget(lblMSBtxt) layHMSB.addWidget(self.lblMSB) layHMSB.addStretch() lblLSBtxt = QLabel(self) lblLSBtxt.setText("<b><i>LSB</i><sub>10</sub> =</b>") self.lblLSB = QLabel(self) # layHLSB = QHBoxLayout() layHLSB.addWidget(lblLSBtxt) layHLSB.addWidget(self.lblLSB) layHLSB.addStretch() layGQOpt = QGridLayout() layGQOpt.addLayout(layHQOvflOpt,0,0) layGQOpt.addLayout(layHQuantOpt, 0,1) layGQOpt.addWidget(self.butQuant, 0, 2, Qt.AlignCenter) layGQOpt.addLayout(layHMSB,1,0) layGQOpt.addLayout(layHLSB,1,1) #------------------------------------------------------------------- # Display MAX # --------------------------------------------------------------------- lblMAXtxt = QLabel(self) lblMAXtxt.setText("<b><i>Max =</i></b>") self.lblMAX = QLabel(self) layHCoeffs_MAX = QHBoxLayout() layHCoeffs_MAX.addWidget(lblMAXtxt) layHCoeffs_MAX.addWidget(self.lblMAX) layHCoeffs_MAX.addStretch() ####################################################################### # Now put all the coefficient HBoxes into frmQSettings # --------------------------------------------------------------------- layVButtonsQ = QVBoxLayout() layVButtonsQ.addLayout(layHW_Scale) layVButtonsQ.addLayout(layGQOpt) layVButtonsQ.addLayout(layHCoeffs_MAX) layVButtonsQ.setContentsMargins(0,5,0,0) # This frame encompasses all Quantization Settings self.frmQSettings = QFrame(self) self.frmQSettings.setLayout(layVButtonsQ) # ######################## Main UI Layout ############################ # layout for frame (UI widget) layVMainF = QVBoxLayout() layVMainF.addLayout(layHDisplay) layVMainF.addLayout(layHButtonsCoeffs1) layVMainF.addLayout(layHButtonsCoeffs2) layVMainF.addWidget(self.frmQSettings) # This frame encompasses all UI elements frmMain = QFrame(self) frmMain.setLayout(layVMainF) layVMain = QVBoxLayout() layVMain.setAlignment(Qt.AlignTop) # this affects only the first widget (intended here) layVMain.addWidget(frmMain) layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(layVMain) ####################################################################### #--- set initial values from dict / signal slot connections------------ self.spnDigits.setValue(params['FMT_ba']) self.ledEps.setText(str(self.eps)) butSettingsClipboard.clicked.connect(self._copy_options) #------------------------------------------------------------------------------ def _copy_options(self): """ Set options for copying to/from clipboard or file, storing the selections in `params` """ 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)
class Input_PZ_UI(QWidget): """ Create the UI for the FilterPZ class """ sig_rx = pyqtSignal(object) # incoming sig_tx = pyqtSignal(object) # outgoing def __init__(self, parent): """ Pass instance `parent` of parent class (FilterCoeffs) """ super(Input_PZ_UI, self).__init__(parent) # self.parent = parent # instance of the parent (not the base) class self.eps = 1.e-4 # # tolerance value for e.g. setting P/Z to zero """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ self.bfont = QFont() self.bfont.setBold(True) self.bifont = QFont() self.bifont.setBold(True) self.bifont.setItalic(True) # q_icon_size = QSize(20, 20) # optional, size is derived from butEnable # --------------------------------------------- # UI Elements for controlling the display # --------------------------------------------- self.butEnable = QPushButton(self) self.butEnable.setIcon(QIcon(':/circle-x.svg')) q_icon_size = self.butEnable.iconSize() # <- set this for manual icon sizing self.butEnable.setIconSize(q_icon_size) self.butEnable.setCheckable(True) self.butEnable.setChecked(True) self.butEnable.setToolTip("<span>Show / hide poles and zeros in an editable table." " For high order systems, the table display might be slow.</span>") self.cmbPZFrmt = QComboBox(self) pz_formats = [('Cartesian', 'cartesian'), ('Polar (rad)', 'polar_rad'), ('Polar (pi)', 'polar_pi'), ('Polar (°)', 'polar_deg')] # display text, data # π: u'3C0, °: u'B0, ∠: u'2220 for pz in pz_formats: self.cmbPZFrmt.addItem(*pz) self.cmbPZFrmt.setSizeAdjustPolicy(QComboBox.AdjustToContents) # self.cmbPZFrmt.setEnabled(False) self.cmbPZFrmt.setToolTip("<span>Set display format for poles and zeros to" " either cartesian (x + jy) or polar (r * ∠ Ω)." " Type 'o' for '°', '<' for '∠' and 'pi' for 'π'.</span>") self.spnDigits = QSpinBox(self) self.spnDigits.setRange(0,16) self.spnDigits.setToolTip("Number of digits to display.") self.lblDigits = QLabel("Digits", self) self.lblDigits.setFont(self.bifont) self.cmbCausal = QComboBox(self) causal_types = ['Causal', 'Acausal', 'Anticausal'] for cs in causal_types: self.cmbCausal.addItem(cs) qset_cmb_box(self.cmbCausal, 'Causal') self.cmbCausal.setToolTip('<span>Set the system type. Not implemented yet.</span>') self.cmbCausal.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbCausal.setEnabled(False) layHDisplay = QHBoxLayout() layHDisplay.setAlignment(Qt.AlignLeft) layHDisplay.addWidget(self.butEnable) layHDisplay.addWidget(self.cmbPZFrmt) layHDisplay.addWidget(self.spnDigits) layHDisplay.addWidget(self.lblDigits) layHDisplay.addWidget(self.cmbCausal) layHDisplay.addStretch() # --------------------------------------------- # UI Elements for setting the gain # --------------------------------------------- self.lblNorm = QLabel(to_html("Normalize:", frmt='bi'), self) self.cmbNorm = QComboBox(self) self.cmbNorm.addItems(["None", "1", "Max"]) self.cmbNorm.setToolTip("<span>Set the gain <i>k</i> so that H(f)<sub>max</sub> is " "either 1 or the max. of the previous system.</span>") self.lblGain = QLabel(to_html("k =", frmt='bi'), self) self.ledGain = QLineEdit(self) self.ledGain.setToolTip("<span>Specify gain factor <i>k</i>" " (only possible for Normalize = 'None').</span>") self.ledGain.setText(str(1.)) self.ledGain.setObjectName("ledGain") layHGain = QHBoxLayout() layHGain.addWidget(self.lblNorm) layHGain.addWidget(self.cmbNorm) layHGain.addWidget(self.lblGain) layHGain.addWidget(self.ledGain) layHGain.addStretch() # --------------------------------------------- # UI Elements for loading / storing / manipulating cells and rows # --------------------------------------------- # self.cmbFilterType = QComboBox(self) # self.cmbFilterType.setObjectName("comboFilterType") # self.cmbFilterType.setToolTip("Select between IIR and FIR filte for manual entry.") # self.cmbFilterType.addItems(["FIR","IIR"]) # self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.butAddCells = QPushButton(self) self.butAddCells.setIcon(QIcon(':/row_insert_above.svg')) self.butAddCells.setIconSize(q_icon_size) self.butAddCells.setToolTip("<SPAN>Select cells to insert a new cell above each selected cell. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, add a row at the end.</SPAN>") self.butDelCells = QPushButton(self) self.butDelCells.setIcon(QIcon(':/row_delete.svg')) self.butDelCells.setIconSize(q_icon_size) self.butDelCells.setToolTip("<SPAN>Delete selected cell(s) from the table. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, delete the last row.</SPAN>") self.butSave = QPushButton(self) self.butSave.setIcon(QIcon(':/upload.svg')) self.butSave.setIconSize(q_icon_size) self.butSave.setToolTip("<span>Copy P/Z table to filter dict and update all plots and widgets.</span>") self.butLoad = QPushButton(self) self.butLoad.setIcon(QIcon(':/download.svg')) self.butLoad.setIconSize(q_icon_size) self.butLoad.setToolTip("Reload P/Z table from filter dict.") self.butClear = QPushButton(self) self.butClear.setIcon(QIcon(':/trash.svg')) self.butClear.setIconSize(q_icon_size) self.butClear.setToolTip("Clear all table entries.") self.butFromTable = QPushButton(self) self.butFromTable.setIconSize(q_icon_size) self.butToTable = QPushButton(self) self.butToTable.setIconSize(q_icon_size) self._set_load_save_icons() butSettingsClipboard = QPushButton(self) butSettingsClipboard.setIcon(QIcon(':/settings.svg')) butSettingsClipboard.setIconSize(q_icon_size) butSettingsClipboard.setToolTip("<span>Select CSV format and whether " "to copy to/from clipboard or file.</span>") layHButtonsCoeffs1 = QHBoxLayout() # layHButtonsCoeffs1.addWidget(self.cmbFilterType) layHButtonsCoeffs1.addWidget(self.butAddCells) layHButtonsCoeffs1.addWidget(self.butDelCells) layHButtonsCoeffs1.addWidget(self.butClear) layHButtonsCoeffs1.addWidget(self.butSave) layHButtonsCoeffs1.addWidget(self.butLoad) layHButtonsCoeffs1.addWidget(self.butFromTable) layHButtonsCoeffs1.addWidget(self.butToTable) layHButtonsCoeffs1.addWidget(butSettingsClipboard) layHButtonsCoeffs1.addStretch() #------------------------------------------------------------------- # Eps / set zero settings # --------------------------------------------------------------------- self.butSetZero = QPushButton("= 0", self) self.butSetZero.setToolTip("<span>Set selected poles / zeros = 0 with a magnitude < ε. " "When nothing is selected, test the whole table.</span>") self.butSetZero.setIconSize(q_icon_size) lblEps = QLabel(self) lblEps.setText("<b><i>for ε</i> <</b>") self.ledEps = QLineEdit(self) self.ledEps.setToolTip("Specify tolerance value.") layHButtonsCoeffs2 = QHBoxLayout() layHButtonsCoeffs2.addWidget(self.butSetZero) layHButtonsCoeffs2.addWidget(lblEps) layHButtonsCoeffs2.addWidget(self.ledEps) layHButtonsCoeffs2.addStretch() # ######################## Main UI Layout ############################ # layout for frame (UI widget) layVMainF = QVBoxLayout() layVMainF.addLayout(layHDisplay) layVMainF.addLayout(layHGain) layVMainF.addLayout(layHButtonsCoeffs1) layVMainF.addLayout(layHButtonsCoeffs2) # This frame encompasses all UI elements frmMain = QFrame(self) frmMain.setLayout(layVMainF) layVMain = QVBoxLayout() layVMain.setAlignment(Qt.AlignTop) # this affects only the first widget (intended here) layVMain.addWidget(frmMain) layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(layVMain) #--- set initial values from dict ------------ self.spnDigits.setValue(params['FMT_pz']) self.ledEps.setText(str(self.eps)) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- butSettingsClipboard.clicked.connect(self._copy_options) self.sig_rx.connect(self._set_load_save_icons) #------------------------------------------------------------------------------ def _copy_options(self): """ Set options for copying to/from clipboard or file, storing the selections in `params` """ 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) self._set_load_save_icons() self.sig_tx.emit({'sender':__name__, 'ui_changed': 'csv'}) #------------------------------------------------------------------------------ def _set_load_save_icons(self): """ Set icons / tooltipps for loading and saving data to / from file or clipboard depending on selected options. """ if params['CSV']['clipboard']: self.butFromTable.setIcon(QIcon(':/to_clipboard.svg')) self.butFromTable.setToolTip("<span>" "Copy table to clipboard, SELECTED items are copied as " "displayed. When nothing is selected, the whole table " "is copied with full precision in decimal format.</span>") self.butToTable.setIcon(QIcon(':/from_clipboard.svg')) self.butToTable.setToolTip("<span>Copy clipboard to table.</span>") else: self.butFromTable.setIcon(QIcon(':/save.svg')) self.butFromTable.setToolTip("<span>" "Save table to file, SELECTED items are copied as " "displayed. When nothing is selected, the whole table " "is copied with full precision in decimal format.</span>") self.butToTable.setIcon(QIcon(':/file.svg')) self.butToTable.setToolTip("<span>Load table from file.</span>")
class FilterCoeffs(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. """ sigFilterDesigned = pyqtSignal() # emitted when coeffs have been changed def __init__(self, parent): super(FilterCoeffs, self).__init__(parent) self.opt_widget = None # handle for pop-up options widget self.ui = FilterCoeffs_UI(self) # create the UI part with buttons etc. self._construct_UI() def _construct_UI(self): """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ # Create clipboard instance self.clipboard = QApplication.clipboard() # --------------------------------------------------------------------- # 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 = fix.Fixed(fb.fil[0]["q_coeff"]) # 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() # ============== 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) # refresh table after storing new settings self.ui.cmbFormat.currentIndexChanged.connect(self._refresh_table) self.ui.cmbQOvfl.currentIndexChanged.connect(self._refresh_table) self.ui.cmbQuant.currentIndexChanged.connect(self._refresh_table) self.ui.ledWF.editingFinished.connect(self._WIWF_changed) self.ui.ledWI.editingFinished.connect(self._WIWF_changed) self.ui.ledW.editingFinished.connect(self._W_changed) self.ui.ledScale.editingFinished.connect(self._set_scale) self.ui.butQuant.clicked.connect(self.quant_coeffs) # ===================================================================== #------------------------------------------------------------------------------ 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"]) if not ftype: self._refresh_table() # self.load_dict() overwrites current info - is this ok? #------------------------------------------------------------------------------ def _WIWF_changed(self): """ Store values for 'WI' and 'WF' when either has been changed, update wordlength `W` accordingly and update table """ self._store_q_settings() self._refresh_table() #------------------------------------------------------------------------------ def _W_changed(self): """ Set fractional and integer length `WF` and `WI` when wordlength Ẁ` has been changed. Try to preserve `WI` or `WF` settings depending on the number format (integer or fractional). """ # if self.ui.ledW.isModified() ... self.ui.ledW.setModified(False) 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._store_q_settings() self._refresh_table() #------------------------------------------------------------------------------ def _set_number_format(self): """ Set one of three number formats: Integer, fractional, normalized fractional """ 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") self.ui.ledScale.setText(str(1 << (W-1))) elif qfrmt == 'qnfrac': # normalized fractional format self.ui.ledWI.setText("0") self.ui.ledWF.setText(str(W - 1)) self.ui.ledScale.setText("1") else: # qfrmt == 'qfrac': is_qfrac = True 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(is_qfrac) self._store_q_settings() self._refresh_table() #------------------------------------------------------------------------------ def _set_scale(self): """ 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._store_q_settings() self._refresh_table() #------------------------------------------------------------------------------ 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 columns of float scalars). Data is displayed via `ItemDelegate.displayText()` in the number format set by `self.frmt`. The table dimensions are set according to the dimensions of `self.ba`: - self.ba[0] -> b coefficients - self.ba[1] -> a coefficients Called at the end of nearly every method. """ try: self.num_rows = max(len(self.ba[1]), len(self.ba[0])) except IndexError: self.num_rows = len(self.ba[0]) logger.debug("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) if self.ui.butEnable.isChecked(): self.ui.frmQSettings.setVisible(not is_float) # hide all q-settings for float self.ui.butEnable.setIcon(QIcon(':/circle-x.svg')) self.tblCoeff.setVisible(True) self._store_q_settings() # store updated quantization / format settings # 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.frmQSettings.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 comboBoxes from dictionary self._load_q_settings() 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 in CSV format or to file. """ qtable2text(self.tblCoeff, self.ba, self, 'ba', self.myQ.frmt) #------------------------------------------------------------------------------ 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', comment="filter coefficients ") 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 _load_q_settings(self): """ load the quantization settings from the filter dict and set the widgets accordingly. Update the fixpoint object. """ self.myQ.setQobj(fb.fil[0]['q_coeff']) q_coeff = self.myQ.q_obj self.ui.ledWI.setText(str(q_coeff['WI'])) self.ui.ledWF.setText(str(q_coeff['WF'])) qset_cmb_box(self.ui.cmbQuant, q_coeff['quant']) qset_cmb_box(self.ui.cmbQOvfl, q_coeff['ovfl']) qset_cmb_box(self.ui.cmbFormat, q_coeff['frmt']) self.ui.ledScale.setText(str(q_coeff['scale'])) self.ui.ledW.setText(str(self.myQ.W)) 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}".format(self.myQ.float2frmt(self.myQ.MAX, scaling='none'))) #------------------------------------------------------------------------------ def _store_q_settings(self): """ Read out the settings of the quantization comboboxes and store them in the filter dict. Update the fixpoint object. """ fb.fil[0]['q_coeff'] = { '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='pos'), 'quant':qstr(self.ui.cmbQuant.currentText()), 'ovfl':qstr(self.ui.cmbQOvfl.currentText()), 'frmt':qstr(self.ui.cmbFormat.currentText()), 'scale':qstr(self.ui.ledScale.text()) } self._load_q_settings() # update widgets and the fixpoint object self.myQ #------------------------------------------------------------------------------ def _save_dict(self): """ Save the coefficient register `self.ba` to the filter dict `fb.fil[0]['ba']`. """ logger.debug("_save_entries called") fb.fil[0]['N'] = max(len(self.ba[0]), len(self.ba[1])) - 1 self._store_q_settings() 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 fil_save(fb.fil[0], self.ba, 'ba', __name__) if __name__ == '__main__': self.load_dict() # only needed for stand-alone test self.sigFilterDesigned.emit() # -> filter_specs # -> input_tab_widgets -> pyfdax -> plt_tab_widgets.updateAll() 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 np.any(sel) and len(self.ba[0]) > 0: self.ba[0] = np.delete(self.ba[0], -1) self.ba[1] = np.delete(self.ba[1], -1) else: 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 len(self.ba[0]) < 2: self._clear_table() 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 np.any(sel): sel[0] = [len(self.ba[0])] sel[1] = [len(self.ba[1])] self.ba[0] = np.insert(self.ba[0], sel[0], 0) self.ba[1] = np.insert(self.ba[1], sel[1], 0) # 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() #------------------------------------------------------------------------------ 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 """ self._store_q_settings() # read comboboxes and store setting in filter dict # always save quantized coefficients in fractional format # -> change output format to 'float' before quantizing and storing in self.ba self.myQ.frmt = 'float' idx = qget_selected(self.tblCoeff)['idx'] # get all selected indices if not idx: # nothing selected, quantize all elements self.ba = self.myQ.fixp(self.ba, scaling='div') else: for i in idx: self.ba[i[0]][i[1]] = self.myQ.fixp(self.ba[i[0]][i[1]], scaling = 'div') qstyle_widget(self.ui.butSave, 'changed') self._refresh_table()
class Input_Coeffs_UI(QWidget): """ Create the UI for the FilterCoeffs class """ sig_rx = pyqtSignal(dict) # incoming sig_tx = pyqtSignal(dict) # outgoing def __init__(self, parent): super(Input_Coeffs_UI, self).__init__(parent) self.eps = 1.e-6 # initialize tolerance value """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ self.bfont = QFont() self.bfont.setBold(True) self.bifont = QFont() self.bifont.setBold(True) self.bifont.setItalic(True) # q_icon_size = QSize(20, 20) # optional, size is derived from butEnable ####################################################################### # frmMain # # This frame contains all the buttons ####################################################################### # --------------------------------------------- # layHDisplay # # UI Elements for controlling the display # --------------------------------------------- self.butEnable = QPushButton(self) self.butEnable.setIcon(QIcon(':/circle-x.svg')) q_icon_size = self.butEnable.iconSize() # <- uncomment this for manual sizing self.butEnable.setIconSize(q_icon_size) self.butEnable.setCheckable(True) self.butEnable.setChecked(True) self.butEnable.setToolTip("<span>Show / hide filter coefficients in an editable table." " For high order systems, table display might be slow.</span>") fix_formats = ['Dec', 'Hex', 'Bin', 'CSD'] self.cmbFormat = QComboBox(self) model = self.cmbFormat.model() item = QtGui.QStandardItem('Float') item.setData('child', Qt.AccessibleDescriptionRole) model.appendRow(item) item = QtGui.QStandardItem('Fixp.:') item.setData('parent', Qt.AccessibleDescriptionRole) item.setData(0, QtGui.QFont.Bold) item.setFlags(item.flags() & ~Qt.ItemIsEnabled)# | Qt.ItemIsSelectable)) model.appendRow(item) for idx in range(len(fix_formats)): item = QtGui.QStandardItem(fix_formats[idx]) # item.setForeground(QtGui.QColor('red')) model.appendRow(item) self.cmbFormat.insertSeparator(1) qset_cmb_box(self.cmbFormat, 'float') self.cmbFormat.setToolTip('Set the display format.') self.cmbFormat.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.spnDigits = QSpinBox(self) self.spnDigits.setRange(0,16) self.spnDigits.setValue(params['FMT_ba']) self.spnDigits.setToolTip("Number of digits to display.") self.lblDigits = QLabel("Digits", self) self.lblDigits.setFont(self.bifont) self.cmbQFrmt = QComboBox(self) q_formats = [('Integer', 'qint' ), ('Norm. Frac.', 'qnfrac'), ('Fractional', 'qfrac')] for q in q_formats: self.cmbQFrmt.addItem(*q) self.lbl_W = QLabel("W = ", self) self.lbl_W.setFont(self.bifont) self.ledW = QLineEdit(self) self.ledW.setToolTip("Specify total wordlength.") self.ledW.setText("16") self.ledW.setMaxLength(2) # maximum of 2 digits self.ledW.setFixedWidth(30) # width of lineedit in points(?) layHDisplay = QHBoxLayout() layHDisplay.setAlignment(Qt.AlignLeft) layHDisplay.addWidget(self.butEnable) layHDisplay.addWidget(self.cmbFormat) layHDisplay.addWidget(self.spnDigits) layHDisplay.addWidget(self.lblDigits) layHDisplay.addWidget(self.cmbQFrmt) layHDisplay.addWidget(self.lbl_W) layHDisplay.addWidget(self.ledW) layHDisplay.addStretch() # ----------------------------------------------------------------- # layHButtonsCoeffs1 # # UI Elements for loading / storing / manipulating cells and rows # ----------------------------------------------------------------- self.cmbFilterType = QComboBox(self) self.cmbFilterType.setObjectName("comboFilterType") self.cmbFilterType.setToolTip("<span>Select between IIR and FIR filter for manual entry." "Changing the type reloads the filter from the filter dict.</span>") self.cmbFilterType.addItems(["FIR","IIR"]) self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.butAddCells = QPushButton(self) self.butAddCells.setIcon(QIcon(':/row_insert_above.svg')) self.butAddCells.setIconSize(q_icon_size) self.butAddCells.setToolTip("<SPAN>Select cells to insert a new cell above each selected cell. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, add a row at the end.</SPAN>") self.butDelCells = QPushButton(self) self.butDelCells.setIcon(QIcon(':/row_delete.svg')) self.butDelCells.setIconSize(q_icon_size) self.butDelCells.setToolTip("<SPAN>Delete selected cell(s) from the table. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, delete the last row.</SPAN>") self.butSave = QPushButton(self) self.butSave.setIcon(QIcon(':/upload.svg')) self.butSave.setIconSize(q_icon_size) self.butSave.setToolTip("<span>Copy coefficient table to filter dict and update all plots and widgets.</span>") self.butLoad = QPushButton(self) self.butLoad.setIcon(QIcon(':/download.svg')) self.butLoad.setIconSize(q_icon_size) self.butLoad.setToolTip("Reload coefficient table from filter dict.") self.butClear = QPushButton(self) self.butClear.setIcon(QIcon(':/trash.svg')) self.butClear.setIconSize(q_icon_size) self.butClear.setToolTip("Clear all table entries.") self.butFromTable = QPushButton(self) self.butFromTable.setIconSize(q_icon_size) self.butToTable = QPushButton(self) self.butToTable.setIconSize(q_icon_size) self._set_load_save_icons() butSettingsClipboard = QPushButton(self) butSettingsClipboard.setIcon(QIcon(':/settings.svg')) butSettingsClipboard.setIconSize(q_icon_size) butSettingsClipboard.setToolTip("<span>Select CSV format and whether " "to copy to/from clipboard or file.</span>") layHButtonsCoeffs1 = QHBoxLayout() layHButtonsCoeffs1.addWidget(self.cmbFilterType) layHButtonsCoeffs1.addWidget(self.butAddCells) layHButtonsCoeffs1.addWidget(self.butDelCells) layHButtonsCoeffs1.addWidget(self.butClear) layHButtonsCoeffs1.addWidget(self.butSave) layHButtonsCoeffs1.addWidget(self.butLoad) layHButtonsCoeffs1.addWidget(self.butFromTable) layHButtonsCoeffs1.addWidget(self.butToTable) layHButtonsCoeffs1.addWidget(butSettingsClipboard) layHButtonsCoeffs1.addStretch() #---------------------------------------------------------------------- # layHButtonsCoeffs2 # # Eps / set zero settings # --------------------------------------------------------------------- self.butSetZero = QPushButton("= 0", self) self.butSetZero.setToolTip("<span>Set selected coefficients = 0 with a magnitude < ε. " "When nothing is selected, test the whole table.</span>") self.butSetZero.setIconSize(q_icon_size) lblEps = QLabel(self) lblEps.setText("<b><i>for b, a</i> <</b>") self.ledEps = QLineEdit(self) self.ledEps.setToolTip("Specify tolerance value.") layHButtonsCoeffs2 = QHBoxLayout() layHButtonsCoeffs2.addWidget(self.butSetZero) layHButtonsCoeffs2.addWidget(lblEps) layHButtonsCoeffs2.addWidget(self.ledEps) layHButtonsCoeffs2.addStretch() ####################################################################### # frmQSettings # # This frame contains all quantization settings ####################################################################### #------------------------------------------------------------------- # layHW_Scale # # QFormat and scale settings # --------------------------------------------------------------------- lbl_Q = QLabel("Q =", self) lbl_Q.setFont(self.bifont) self.ledWI = QLineEdit(self) self.ledWI.setToolTip("Specify number of integer bits.") self.ledWI.setText("0") self.ledWI.setMaxLength(2) # maximum of 2 digits self.ledWI.setFixedWidth(30) # width of lineedit in points(?) self.lblDot = QLabel(".", self) # class attribute, visibility is toggled self.lblDot.setFont(self.bfont) self.ledWF = QLineEdit(self) self.ledWF.setToolTip("Specify number of fractional bits.") self.ledWF.setText("15") self.ledWF.setMaxLength(2) # maximum of 2 digits # self.ledWF.setFixedWidth(30) # width of lineedit in points(?) self.ledWF.setMaximumWidth(30) self.lblScale = QLabel("<b><i>Scale</i> =</b>", self) self.ledScale = QLineEdit(self) self.ledScale.setToolTip("Set the scale for converting float to fixpoint representation.") self.ledScale.setText(str(1)) layHWI_WF = QHBoxLayout() layHWI_WF.addWidget(lbl_Q) layHWI_WF.addWidget(self.ledWI) layHWI_WF.addWidget(self.lblDot) layHWI_WF.addWidget(self.ledWF) layHWI_WF.addStretch() layHScale = QHBoxLayout() layHScale.addWidget(self.lblScale) layHScale.addWidget(self.ledScale) layHScale.addStretch() layHW_Scale = QHBoxLayout() layHW_Scale.addLayout(layHWI_WF) layHW_Scale.addLayout(layHScale) #------------------------------------------------------------------- # layGQOpt # # Quantization / Overflow / MSB / LSB settings # --------------------------------------------------------------------- lblQOvfl = QLabel("Ovfl.:", self) lblQOvfl.setFont(self.bifont) lblQuant = QLabel("Quant.:", self) lblQuant.setFont(self.bifont) self.cmbQOvfl = QComboBox(self) qOvfl = ['wrap', 'sat'] self.cmbQOvfl.addItems(qOvfl) qset_cmb_box(self.cmbQOvfl, 'sat') self.cmbQOvfl.setToolTip("Select overflow behaviour.") # ComboBox size is adjusted automatically to fit the longest element self.cmbQOvfl.setSizeAdjustPolicy(QComboBox.AdjustToContents) layHQOvflOpt = QHBoxLayout() layHQOvflOpt.addWidget(lblQOvfl) layHQOvflOpt.addWidget(self.cmbQOvfl) layHQOvflOpt.addStretch() self.cmbQuant = QComboBox(self) qQuant = ['none', 'round', 'fix', 'floor'] self.cmbQuant.addItems(qQuant) qset_cmb_box(self.cmbQuant, 'round') self.cmbQuant.setToolTip("Select the kind of quantization.") self.cmbQuant.setSizeAdjustPolicy(QComboBox.AdjustToContents) layHQuantOpt = QHBoxLayout() layHQuantOpt.addWidget(lblQuant) layHQuantOpt.addWidget(self.cmbQuant) layHQuantOpt.addStretch() self.butQuant = QPushButton(self) self.butQuant.setToolTip("<span>Quantize selected coefficients / " "whole table with specified settings.</span>") self.butQuant.setIcon(QIcon(':/quantize.svg')) self.butQuant.setIconSize(q_icon_size) self.butQuant.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) lblMSBtxt = QLabel(self) lblMSBtxt.setText("<b><i>MSB</i><sub>10</sub> =</b>") self.lblMSB = QLabel(self) layHMSB = QHBoxLayout() layHMSB.addWidget(lblMSBtxt) layHMSB.addWidget(self.lblMSB) layHMSB.addStretch() lblLSBtxt = QLabel(self) lblLSBtxt.setText("<b><i>LSB</i><sub>10</sub> =</b>") self.lblLSB = QLabel(self) # layHLSB = QHBoxLayout() layHLSB.addWidget(lblLSBtxt) layHLSB.addWidget(self.lblLSB) layHLSB.addStretch() layGQOpt = QGridLayout() layGQOpt.addLayout(layHQOvflOpt,0,0) layGQOpt.addLayout(layHQuantOpt, 0,1) layGQOpt.addWidget(self.butQuant, 0, 2, Qt.AlignCenter) layGQOpt.addLayout(layHMSB,1,0) layGQOpt.addLayout(layHLSB,1,1) #------------------------------------------------------------------- # Display MAX # --------------------------------------------------------------------- lblMAXtxt = QLabel(self) lblMAXtxt.setText("<b><i>Max =</i></b>") self.lblMAX = QLabel(self) layHCoeffs_MAX = QHBoxLayout() layHCoeffs_MAX.addWidget(lblMAXtxt) layHCoeffs_MAX.addWidget(self.lblMAX) layHCoeffs_MAX.addStretch() ####################################################################### # Now put all the coefficient HBoxes into frmQSettings # --------------------------------------------------------------------- layVButtonsQ = QVBoxLayout() layVButtonsQ.addLayout(layHW_Scale) layVButtonsQ.addLayout(layGQOpt) layVButtonsQ.addLayout(layHCoeffs_MAX) layVButtonsQ.setContentsMargins(0,5,0,0) # This frame encompasses all Quantization Settings self.frmQSettings = QFrame(self) self.frmQSettings.setLayout(layVButtonsQ) # ######################## Main UI Layout ############################ # layout for frame (UI widget) layVMainF = QVBoxLayout() layVMainF.addLayout(layHDisplay) layVMainF.addLayout(layHButtonsCoeffs1) layVMainF.addLayout(layHButtonsCoeffs2) layVMainF.addWidget(self.frmQSettings) # This frame encompasses all UI elements frmMain = QFrame(self) frmMain.setLayout(layVMainF) layVMain = QVBoxLayout() layVMain.setAlignment(Qt.AlignTop) # this affects only the first widget (intended here) layVMain.addWidget(frmMain) layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(layVMain) ####################################################################### #--- set initial values from dict ------------ self.spnDigits.setValue(params['FMT_ba']) self.ledEps.setText(str(self.eps)) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- butSettingsClipboard.clicked.connect(self._copy_options) self.sig_rx.connect(self._set_load_save_icons) #------------------------------------------------------------------------------ def _copy_options(self): """ Set options for copying to/from clipboard or file, storing the selections in `params` """ 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) self._set_load_save_icons() self.sig_tx.emit({'sender':__name__, 'ui_changed': 'csv'}) #------------------------------------------------------------------------------ def _set_load_save_icons(self): """ Set icons / tooltipps for loading and saving data to / from file or clipboard depending on selected options. """ if params['CSV']['clipboard']: self.butFromTable.setIcon(QIcon(':/to_clipboard.svg')) self.butFromTable.setToolTip("<span>" "Copy table to clipboard, SELECTED items are copied as " "displayed. When nothing is selected, the whole table " "is copied with full precision in decimal format.</span>") self.butToTable.setIcon(QIcon(':/from_clipboard.svg')) self.butToTable.setToolTip("<span>Copy clipboard to table.</span>") else: self.butFromTable.setIcon(QIcon(':/save.svg')) self.butFromTable.setToolTip("<span>" "Save table to file, SELECTED items are copied as " "displayed. When nothing is selected, the whole table " "is copied with full precision in decimal format.</span>") self.butToTable.setIcon(QIcon(':/file.svg')) self.butToTable.setToolTip("<span>Load table from file.</span>")
class Input_PZ_UI(QWidget): """ Create the UI for the FilterPZ class """ sig_rx = pyqtSignal(object) # incoming sig_tx = pyqtSignal(object) # outgoing def __init__(self, parent): """ Pass instance `parent` of parent class (FilterCoeffs) """ super(Input_PZ_UI, self).__init__(parent) # self.parent = parent # instance of the parent (not the base) class self.eps = 1.e-4 # # tolerance value for e.g. setting P/Z to zero """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ self.bfont = QFont() self.bfont.setBold(True) self.bifont = QFont() self.bifont.setBold(True) self.bifont.setItalic(True) # q_icon_size = QSize(20, 20) # optional, size is derived from butEnable # --------------------------------------------- # UI Elements for controlling the display # --------------------------------------------- self.butEnable = QPushButton(self) self.butEnable.setIcon(QIcon(':/circle-x.svg')) q_icon_size = self.butEnable.iconSize( ) # <- set this for manual icon sizing self.butEnable.setIconSize(q_icon_size) self.butEnable.setCheckable(True) self.butEnable.setChecked(True) self.butEnable.setToolTip( "<span>Show / hide poles and zeros in an editable table." " For high order systems, the table display might be slow.</span>") self.cmbPZFrmt = QComboBox(self) pz_formats = [('Cartesian', 'cartesian'), ('Polar (rad)', 'polar_rad'), ('Polar (pi)', 'polar_pi'), ('Polar (°)', 'polar_deg')] # display text, data # π: u'3C0, °: u'B0, ∠: u'2220 for pz in pz_formats: self.cmbPZFrmt.addItem(*pz) self.cmbPZFrmt.setSizeAdjustPolicy(QComboBox.AdjustToContents) # self.cmbPZFrmt.setEnabled(False) self.cmbPZFrmt.setToolTip( "<span>Set display format for poles and zeros to" " either cartesian (x + jy) or polar (r * ∠ Ω)." " Type 'o' for '°', '<' for '∠' and 'pi' for 'π'.</span>" ) self.spnDigits = QSpinBox(self) self.spnDigits.setRange(0, 16) self.spnDigits.setToolTip("Number of digits to display.") self.lblDigits = QLabel("Digits", self) self.lblDigits.setFont(self.bifont) self.cmbCausal = QComboBox(self) causal_types = ['Causal', 'Acausal', 'Anticausal'] for cs in causal_types: self.cmbCausal.addItem(cs) qset_cmb_box(self.cmbCausal, 'Causal') self.cmbCausal.setToolTip( '<span>Set the system type. Not implemented yet.</span>') self.cmbCausal.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbCausal.setEnabled(False) layHDisplay = QHBoxLayout() layHDisplay.setAlignment(Qt.AlignLeft) layHDisplay.addWidget(self.butEnable) layHDisplay.addWidget(self.cmbPZFrmt) layHDisplay.addWidget(self.spnDigits) layHDisplay.addWidget(self.lblDigits) layHDisplay.addWidget(self.cmbCausal) layHDisplay.addStretch() # --------------------------------------------- # UI Elements for setting the gain # --------------------------------------------- self.lblNorm = QLabel(to_html("Normalize:", frmt='bi'), self) self.cmbNorm = QComboBox(self) self.cmbNorm.addItems(["None", "1", "Max"]) self.cmbNorm.setToolTip( "<span>Set the gain <i>k</i> so that H(f)<sub>max</sub> is " "either 1 or the max. of the previous system.</span>") self.lblGain = QLabel(to_html("k =", frmt='bi'), self) self.ledGain = QLineEdit(self) self.ledGain.setToolTip( "<span>Specify gain factor <i>k</i>" " (only possible for Normalize = 'None').</span>") self.ledGain.setText(str(1.)) self.ledGain.setObjectName("ledGain") layHGain = QHBoxLayout() layHGain.addWidget(self.lblNorm) layHGain.addWidget(self.cmbNorm) layHGain.addWidget(self.lblGain) layHGain.addWidget(self.ledGain) layHGain.addStretch() # --------------------------------------------- # UI Elements for loading / storing / manipulating cells and rows # --------------------------------------------- # self.cmbFilterType = QComboBox(self) # self.cmbFilterType.setObjectName("comboFilterType") # self.cmbFilterType.setToolTip("Select between IIR and FIR filte for manual entry.") # self.cmbFilterType.addItems(["FIR","IIR"]) # self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.butAddCells = QPushButton(self) self.butAddCells.setIcon(QIcon(':/row_insert_above.svg')) self.butAddCells.setIconSize(q_icon_size) self.butAddCells.setToolTip( "<SPAN>Select cells to insert a new cell above each selected cell. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, add a row at the end.</SPAN>") self.butDelCells = QPushButton(self) self.butDelCells.setIcon(QIcon(':/row_delete.svg')) self.butDelCells.setIconSize(q_icon_size) self.butDelCells.setToolTip( "<SPAN>Delete selected cell(s) from the table. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, delete the last row.</SPAN>") self.butSave = QPushButton(self) self.butSave.setIcon(QIcon(':/upload.svg')) self.butSave.setIconSize(q_icon_size) self.butSave.setToolTip( "<span>Copy P/Z table to filter dict and update all plots and widgets.</span>" ) self.butLoad = QPushButton(self) self.butLoad.setIcon(QIcon(':/download.svg')) self.butLoad.setIconSize(q_icon_size) self.butLoad.setToolTip("Reload P/Z table from filter dict.") self.butClear = QPushButton(self) self.butClear.setIcon(QIcon(':/trash.svg')) self.butClear.setIconSize(q_icon_size) self.butClear.setToolTip("Clear all table entries.") self.butFromTable = QPushButton(self) self.butFromTable.setIconSize(q_icon_size) self.butToTable = QPushButton(self) self.butToTable.setIconSize(q_icon_size) self._set_load_save_icons() butSettingsClipboard = QPushButton(self) butSettingsClipboard.setIcon(QIcon(':/settings.svg')) butSettingsClipboard.setIconSize(q_icon_size) butSettingsClipboard.setToolTip( "<span>Select CSV format and whether " "to copy to/from clipboard or file.</span>") layHButtonsCoeffs1 = QHBoxLayout() # layHButtonsCoeffs1.addWidget(self.cmbFilterType) layHButtonsCoeffs1.addWidget(self.butAddCells) layHButtonsCoeffs1.addWidget(self.butDelCells) layHButtonsCoeffs1.addWidget(self.butClear) layHButtonsCoeffs1.addWidget(self.butSave) layHButtonsCoeffs1.addWidget(self.butLoad) layHButtonsCoeffs1.addWidget(self.butFromTable) layHButtonsCoeffs1.addWidget(self.butToTable) layHButtonsCoeffs1.addWidget(butSettingsClipboard) layHButtonsCoeffs1.addStretch() #------------------------------------------------------------------- # Eps / set zero settings # --------------------------------------------------------------------- self.butSetZero = QPushButton("= 0", self) self.butSetZero.setToolTip( "<span>Set selected poles / zeros = 0 with a magnitude < ε. " "When nothing is selected, test the whole table.</span>") self.butSetZero.setIconSize(q_icon_size) lblEps = QLabel(self) lblEps.setText("<b><i>for ε</i> <</b>") self.ledEps = QLineEdit(self) self.ledEps.setToolTip("Specify tolerance value.") layHButtonsCoeffs2 = QHBoxLayout() layHButtonsCoeffs2.addWidget(self.butSetZero) layHButtonsCoeffs2.addWidget(lblEps) layHButtonsCoeffs2.addWidget(self.ledEps) layHButtonsCoeffs2.addStretch() # ######################## Main UI Layout ############################ # layout for frame (UI widget) layVMainF = QVBoxLayout() layVMainF.addLayout(layHDisplay) layVMainF.addLayout(layHGain) layVMainF.addLayout(layHButtonsCoeffs1) layVMainF.addLayout(layHButtonsCoeffs2) # This frame encompasses all UI elements frmMain = QFrame(self) frmMain.setLayout(layVMainF) layVMain = QVBoxLayout() layVMain.setAlignment( Qt.AlignTop) # this affects only the first widget (intended here) layVMain.addWidget(frmMain) layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(layVMain) #--- set initial values from dict ------------ self.spnDigits.setValue(params['FMT_pz']) self.ledEps.setText(str(self.eps)) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- butSettingsClipboard.clicked.connect(self._copy_options) self.sig_rx.connect(self._set_load_save_icons) #------------------------------------------------------------------------------ def _copy_options(self): """ Set options for copying to/from clipboard or file, storing the selections in `params` """ 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) self._set_load_save_icons() self.sig_tx.emit({'sender': __name__, 'ui_changed': 'csv'}) #------------------------------------------------------------------------------ def _set_load_save_icons(self): """ Set icons / tooltipps for loading and saving data to / from file or clipboard depending on selected options. """ if params['CSV']['clipboard']: self.butFromTable.setIcon(QIcon(':/to_clipboard.svg')) self.butFromTable.setToolTip( "<span>" "Copy table to clipboard, SELECTED items are copied as " "displayed. When nothing is selected, the whole table " "is copied with full precision in decimal format.</span>") self.butToTable.setIcon(QIcon(':/from_clipboard.svg')) self.butToTable.setToolTip("<span>Copy clipboard to table.</span>") else: self.butFromTable.setIcon(QIcon(':/save.svg')) self.butFromTable.setToolTip( "<span>" "Save table to file, SELECTED items are copied as " "displayed. When nothing is selected, the whole table " "is copied with full precision in decimal format.</span>") self.butToTable.setIcon(QIcon(':/file.svg')) self.butToTable.setToolTip("<span>Load table from file.</span>")