def dict2ui(self, q_dict=None): """ Update the widgets `WI` and `WF` and the corresponding attributes from the dict passed as the argument """ if q_dict is None: q_dict = self.q_dict if 'WI' in q_dict: self.WI = safe_eval(q_dict['WI'], self.WI, return_type="int", sign='poszero') self.ledWI.setText(qstr(self.WI)) else: logger.warning("No key 'WI' in dict!") if 'WF' in q_dict: self.WF = safe_eval(q_dict['WF'], self.WF, return_type="int", sign='poszero') self.ledWF.setText(qstr(self.WF)) else: logger.warning("No key 'WF' in dict!") self.W = self.WF + self.WI + 1
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 ui2dict(self, s=None): """ Update the attributes `self.WI`, `self.WF` and `self.W` and `self.q_dict` when one of the QLineEdit widgets has been edited. Emit a signal with `{'ui':objectName of the sender}`. """ self.WI = int( safe_eval(self.ledWI.text(), self.WI, return_type="int", sign='poszero')) self.ledWI.setText(qstr(self.WI)) self.WF = int( safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='poszero')) self.ledWF.setText(qstr(self.WF)) self.W = int(self.WI + self.WF + 1) self.q_dict.update({'WI': self.WI, 'WF': self.WF, 'W': self.W}) if self.sender(): obj_name = self.sender().objectName() logger.debug("sender: {0}".format(obj_name)) dict_sig = {'wdg_name': self.wdg_name, 'ui': obj_name} self.emit(dict_sig) elif s == 'init': logger.debug("called by __init__") else: logger.error("sender without name!")
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 setModelData(self, editor, model, index): """ When editor has finished, read the updated data from the editor, convert it back to floating point format and store it in both the model (= QTableWidget) and in self.ba. Finally, refresh the table item to display it in the selected format (via `float2frmt()`). editor: instance of e.g. QLineEdit model: instance of QAbstractTableModel index: instance of QModelIndex """ # check for different editor environments if needed and provide a default: # if isinstance(editor, QtGui.QTextEdit): # model.setData(index, editor.toPlainText()) # elif isinstance(editor, QComboBox): # model.setData(index, editor.currentText()) # else: # super(ItemDelegate, self).setModelData(editor, model, index) if self.parent.myQ.frmt == 'float': data = safe_eval(qstr(editor.text()), self.parent.ba[index.column()][index.row()], return_type='auto') # raw data without fixpoint formatting else: data = self.parent.myQ.frmt2float(qstr(editor.text()), self.parent.myQ.frmt) # transform back to float model.setData(index, data) # store in QTableWidget # if the entry is complex, convert ba (list of arrays) to complex type if isinstance(data, complex): self.parent.ba[0] = self.parent.ba[0].astype(complex) self.parent.ba[1] = self.parent.ba[1].astype(complex) self.parent.ba[index.column()][index.row()] = data # store in self.ba qstyle_widget(self.parent.ui.butSave, 'changed') self.parent._refresh_table_item(index.row(), index.column()) # refresh table entry
def setModelData(self, editor, model, index): """ When editor has finished, read the updated data from the editor, convert it to complex format and store it in both the model (= QTableWidget) and in `zpk`. Finally, refresh the table item to display it in the selected format (via `to be defined`) and normalize the gain. editor: instance of e.g. QLineEdit model: instance of QAbstractTableModel index: instance of QModelIndex """ # check for different editor environments if needed and provide a default: # if isinstance(editor, QtGui.QTextEdit): # model.setData(index, editor.toPlainText()) # elif isinstance(editor, QComboBox): # model.setData(index, editor.currentText()) # else: # super(ItemDelegate, self).setModelData(editor, model, index) # convert entered string to complex, pass the old value as default data = self.parent.frmt2cmplx( qstr(editor.text()), self.parent.zpk[index.column()][index.row()]) model.setData(index, data) # store in QTableWidget self.parent.zpk[index.column()][index.row()] = data # and in self.ba qstyle_widget(self.parent.ui.butSave, 'changed') self.parent._refresh_table_item(index.row(), index.column()) # refresh table entry self.parent._normalize_gain() # recalculate gain
def exportHDL(self): """ Synthesize HDL description of filter """ dlg = QFD(self) # instantiate file dialog object file_types = "Verilog (*.v)" # needed for overwrite confirmation when name is entered without suffix: dlg.setDefaultSuffix('v') dlg.setWindowTitle('Export Verilog') dlg.setNameFilter(file_types) dlg.setDirectory(dirs.save_dir) # set mode "save file" instead "open file": dlg.setAcceptMode(QFD.AcceptSave) dlg.setOption(QFD.DontConfirmOverwrite, False) if dlg.exec_() == QFD.Accepted: hdl_file = qstr(dlg.selectedFiles()[0]) # hdl_type = extract_file_ext(qstr(dlg.selectedNameFilter()))[0] # ============================================================================= # # static method getSaveFileName_() is simple but unflexible # hdl_file, hdl_filter = dlg.getSaveFileName_( # caption="Save Verilog netlist as (this also defines the module name)", # directory=dirs.save_dir, filter=file_types) # hdl_file = qstr(hdl_file) # if hdl_file != "": # "operation cancelled" returns an empty string # # return '.v' or '.vhd' depending on filetype selection: # # hdl_type = extract_file_ext(qstr(hdl_filter))[0] # # sanitized dir + filename + suffix. The filename suffix is replaced # # by `v` later. # hdl_file = os.path.normpath(hdl_file) # complete path + file name # ============================================================================= hdl_dir_name = os.path.dirname(hdl_file) # extract the directory path if not os.path.isdir(hdl_dir_name): # create directory if it doesn't exist os.mkdir(hdl_dir_name) dirs.save_dir = hdl_dir_name # make this directory the new default / base dir hdl_file_name = os.path.splitext(os.path.basename(hdl_file))[0] hdl_full_name = os.path.join(hdl_dir_name, hdl_file_name + ".v") # remove all non-alphanumeric chars: vlog_mod_name = re.sub(r'\W+', '', hdl_file_name).lower() logger.info('Creating hdl_file "{0}"\n\twith top level module "{1}"' .format(hdl_full_name, vlog_mod_name)) try: self.update_fxqc_dict() self.fx_filt_ui.construct_fixp_filter() code = self.fx_filt_ui.to_hdl(name=vlog_mod_name) # logger.info(str(code)) # print verilog code to console with io.open(hdl_full_name, 'w', encoding="utf8") as f: f.write(str(code)) logger.info("HDL conversion finished!") except (IOError, TypeError) as e: logger.warning(e)
def cmplx2frmt(self, text, places=-1): """ Convert number "text" (real or complex or string) to the format defined by cmbPZFrmt. Returns: string """ # convert to "normal" string and prettify via safe_eval: data = safe_eval(qstr(text), return_type='auto') frmt = qget_cmb_box(self.ui.cmbPZFrmt) # get selected format if places == -1: full_prec = True else: full_prec = False if frmt == 'cartesian' or not (type(data) == complex): if full_prec: return "{0}".format(data) else: return "{0:.{plcs}g}".format(data, plcs=places) elif frmt == 'polar_rad': r, phi = np.absolute(data), np.angle(data, deg=False) if full_prec: return "{r} * {angle_char}{p} rad"\ .format(r=r, p=phi, angle_char=self.angle_char) else: return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g} rad"\ .format(r=r, p=phi, plcs=places, angle_char=self.angle_char) elif frmt == 'polar_deg': r, phi = np.absolute(data), np.angle(data, deg=True) if full_prec: return "{r} * {angle_char}{p}°"\ .format(r=r, p=phi, angle_char=self.angle_char) else: return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g}°"\ .format(r=r, p=phi, plcs=places, angle_char=self.angle_char) elif frmt == 'polar_pi': r, phi = np.absolute(data), np.angle(data, deg=False) / np.pi if full_prec: return "{r} * {angle_char}{p} pi"\ .format(r=r, p=phi, angle_char=self.angle_char) else: return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g} pi"\ .format(r=r, p=phi, plcs=places, angle_char=self.angle_char) else: logger.error("Unknown format {0}.".format(frmt))
def frmt2cmplx(self, text, default=0.): """ Convert format defined by cmbPZFrmt to real or complex """ conv_error = False text = qstr(text).replace( " ", "") # convert to "proper" string without blanks if qget_cmb_box(self.ui.cmbPZFrmt) == 'cartesian': return safe_eval(text, default, return_type='auto') else: # try to split text string at "*<" or the angle character polar_str = text.replace(self.angle_char, '<').split('*<', 1) if len(polar_str) < 2: # input is real or imaginary # remove special characters r = safe_eval(re.sub('[' + self.angle_char + '<∠°]', '', text), default, return_type='auto') x = r.real y = r.imag else: r = safe_eval(polar_str[0], sign='pos') if safe_eval.err > 0: conv_error = True if "°" in polar_str[1]: scale = np.pi / 180. # angle in degrees elif re.search('π$|pi$', polar_str[1]): scale = np.pi else: scale = 1. # angle in rad # remove right-most special characters (regex $) polar_str[1] = re.sub( '[' + self.angle_char + '<∠°π]$|rad$|pi$', '', polar_str[1]) phi = safe_eval(polar_str[1]) * scale if safe_eval.err > 0: conv_error = True if not conv_error: x = r * np.cos(phi) y = r * np.sin(phi) else: x = default.real y = default.imag logger.error( "Expression {0} could not be evaluated.".format(text)) return x + 1j * y
def exportHDL(self): """ Synthesize HDL description of filter """ if not hasattr(self.fx_wdg_inst, 'construct_fixp_filter'): logger.warning('Fixpoint widget has no method "construct_fixp_filter", aborting.') return dlg = QFD(self) # instantiate file dialog object file_types = "Verilog (*.v)" hdl_file, hdl_filter = dlg.getSaveFileName_( caption="Save HDL as", directory=dirs.save_dir, filter=file_types) hdl_file = qstr(hdl_file) if hdl_file != "": # "operation cancelled" returns an empty string # return '.v' or '.vhd' depending on filetype selection: # hdl_type = extract_file_ext(qstr(hdl_filter))[0] # sanitized dir + filename + suffix. The filename suffix is replaced # by `v` later. hdl_file = os.path.normpath(hdl_file) hdl_dir_name = os.path.dirname(hdl_file) # extract the directory path if not os.path.isdir(hdl_dir_name): # create directory if it doesn't exist os.mkdir(hdl_dir_name) dirs.save_dir = hdl_dir_name # make this directory the new default / base dir hdl_file_name = os.path.join(hdl_dir_name, os.path.splitext(os.path.basename(hdl_file))[0]+ ".v") logger.info('Creating hdl_file "{0}"'.format( os.path.join(hdl_dir_name, hdl_file_name))) try: self.update_fxqc_dict() self.fx_wdg_inst.construct_fixp_filter() code = self.fx_wdg_inst.to_verilog() logger.info(str(code)) with io.open(hdl_file_name, 'w', encoding="utf8") as f: f.write(str(code)) logger.info("HDL conversion finished!") except (IOError, TypeError) as e: logger.warning(e)
def setEditorData(self, editor, index): """ Pass the data to be edited to the editor: - retrieve data with full accuracy from self.ba (in float format) - requantize data according to settings in fixpoint object - represent it in the selected format (int, hex, ...) editor: instance of e.g. QLineEdit index: instance of QModelIndex """ # data = qstr(index.data()) # get data from QTableWidget data_str = qstr(safe_eval(self.parent.ba[index.column()][index.row()], return_type="auto")) if self.parent.myQ.frmt == 'float': # floating point format: pass data with full resolution editor.setText(data_str) else: # fixpoint format with base: pass requantized data with required number of places editor.setText("{0:>{1}}".format(self.parent.myQ.float2frmt(data_str), self.parent.myQ.places))
def displayText(self, text, locale): """ Display `text` with selected fixpoint base and number of places text: string / QVariant from QTableWidget to be rendered locale: locale for the text The instance parameter myQ.ovr_flag is set to +1 or -1 for positive / negative overflows, else it is 0. """ data_str = qstr(text) # convert to "normal" string if self.parent.myQ.frmt == 'float': data = safe_eval(data_str, return_type='auto') # convert to float return "{0:.{1}g}".format(data, params['FMT_ba']) elif self.parent.myQ.frmt == 'dec' and self.parent.myQ.WF > 0: # decimal fixpoint representation with fractional part return "{0:.{1}g}".format(self.parent.myQ.float2frmt(data_str), params['FMT_ba']) else: return "{0:>{1}}".format(self.parent.myQ.float2frmt(data_str), self.parent.myQ.places)
def _construct_UI(self, **kwargs): """ Construct widget from quantization dict, individual settings and the default dict below """ # default settings dict_ui = { 'wdg_name': 'ui_w', 'label': 'WI.WF', 'lbl_sep': '.', 'max_led_width': 30, 'WI': 0, 'WI_len': 2, 'tip_WI': 'Number of integer bits', 'WF': 15, 'WF_len': 2, 'tip_WF': 'Number of fractional bits', 'enabled': True, 'visible': True, 'fractional': True, 'combo_visible': False, 'combo_items': ['auto', 'full', 'man'], 'tip_combo': 'Calculate Acc. width.', 'lock_visible': False, 'tip_lock': 'Lock input/output quantization.' } #: default values if self.q_dict: dict_ui.update(self.q_dict) for k, v in kwargs.items(): if k not in dict_ui: logger.warning("Unknown key {0}".format(k)) else: dict_ui.update({k: v}) self.wdg_name = dict_ui['wdg_name'] if not dict_ui['fractional']: dict_ui['WF'] = 0 self.WI = dict_ui['WI'] self.WF = dict_ui['WF'] self.W = int(self.WI + self.WF + 1) if self.q_dict: self.q_dict.update({'WI': self.WI, 'WF': self.WF, 'W': self.W}) else: self.q_dict = {'WI': self.WI, 'WF': self.WF, 'W': self.W} lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self) self.cmbW = QComboBox(self) self.cmbW.addItems(dict_ui['combo_items']) self.cmbW.setVisible(dict_ui['combo_visible']) self.cmbW.setToolTip(dict_ui['tip_combo']) self.cmbW.setObjectName("cmbW") self.butLock = QPushButton(self) self.butLock.setCheckable(True) self.butLock.setChecked(False) self.butLock.setVisible(dict_ui['lock_visible']) self.butLock.setToolTip(dict_ui['tip_lock']) self.ledWI = QLineEdit(self) self.ledWI.setToolTip(dict_ui['tip_WI']) self.ledWI.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits self.ledWI.setFixedWidth( dict_ui['max_led_width']) # width of lineedit in points self.ledWI.setObjectName("WI") lblDot = QLabel(dict_ui['lbl_sep'], self) lblDot.setVisible(dict_ui['fractional']) self.ledWF = QLineEdit(self) self.ledWF.setToolTip(dict_ui['tip_WF']) self.ledWF.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits self.ledWF.setFixedWidth( dict_ui['max_led_width']) # width of lineedit in points self.ledWF.setVisible(dict_ui['fractional']) self.ledWF.setObjectName("WF") layH = QHBoxLayout() layH.addWidget(lblW) layH.addStretch() layH.addWidget(self.cmbW) layH.addWidget(self.butLock) layH.addWidget(self.ledWI) layH.addWidget(lblDot) layH.addWidget(self.ledWF) layH.setContentsMargins(0, 0, 0, 0) frmMain = QFrame(self) frmMain.setLayout(layH) layVMain = QVBoxLayout() # Widget main layout layVMain.addWidget(frmMain) layVMain.setContentsMargins(0, 5, 0, 0) # *params['wdg_margins']) self.setLayout(layVMain) # ---------------------------------------------------------------------- # INITIAL SETTINGS # ---------------------------------------------------------------------- self.ledWI.setText(qstr(dict_ui['WI'])) self.ledWF.setText(qstr(dict_ui['WF'])) frmMain.setEnabled(dict_ui['enabled']) frmMain.setVisible(dict_ui['visible']) # ---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.ledWI.editingFinished.connect(self.ui2dict) self.ledWF.editingFinished.connect(self.ui2dict) self.butLock.clicked.connect(self.butLock_clicked) self.cmbW.currentIndexChanged.connect(self.ui2dict) # initialize button icon self.butLock_clicked(self.butLock.isChecked())
def text(self, item): """ Return item text as string transformed by self.displayText() """ # return qstr(item.text()) # convert to "normal" string return qstr(self.displayText(item.text(), QtCore.QLocale()))
def displayText(self, text, locale): return "{:.{n_digits}g}".format(safe_eval(qstr(text), return_type='cmplx'), n_digits=params['FMT_pz'])