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 ui2dict(self): """ 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='pos')) self.ledWI.setText(qstr(self.WI)) self.WF = int( safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='pos')) 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(): name = self.sender().objectName() logger.debug("sender: {0}".format(name)) dict_sig = {'sender': __name__, 'ui': name} self.sig_tx.emit(dict_sig) else: logger.error("sender without name, shouldn't happen!")
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='pos') 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='pos') self.ledWF.setText(qstr(self.WF)) else: logger.warning("No key 'WF' in dict!") self.W = self.WF + self.WI + 1
def ui2dict(self): # was: save_ui """ Update the attributes `self.WI`, `self.WF` and `self.W` when one of the QLineEdit widgets has been edited. Return a dict with the three parameters when called directly, cast values to standard python int format (instead of e.g. np.int64) to avoid problems with HDL simulators downstream """ self.WI = int( safe_eval(self.ledWI.text(), self.WI, return_type="int", sign='pos')) self.ledWI.setText(qstr(self.WI)) self.WF = int( safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='pos')) 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}) dict_sig = {'sender': __name__, 'fixp_changed': ''} self.sig_tx.emit(dict_sig)
def _store_q_settings(self): """ Read out the settings of the quantization comboboxes and store them in the filter dict. Update the fixpoint object and refresh table """ 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.sig_tx.emit({'sender': __name__, 'view_changed': 'q_coeff'}) self._load_q_settings( ) # update widgets and the fixpoint object self.myQ self._refresh_table()
def exportHDL(self): """ Synthesize HDL description of filter """ 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") # ============================================================================= # # remove the suffix from the filename: # # if hdl_type == '.vhd': # hdl = 'VHDL' # elif hdl_type == '.v': # hdl = 'Verilog' # else: # logger.error('Unknown file extension "{0}", cancelling.'.format(hdl_type)) # return # # ============================================================================= 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_hdlfilter(self.fxqc_dict) code = self.fx_wdg_inst.to_verilog(self.fxqc_dict) 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 ui2dict(self): """ Update the attributes `self.WI` and `self.WF` and the filter dict when one of the QLineEdit widgets has been edited. """ self.WI = safe_eval(self.ledWI.text(), self.WI, return_type="int", sign='pos') self.ledWI.setText(qstr(self.WI)) self.WF = safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='pos') self.ledWF.setText(qstr(self.WF)) fb.fil[0]["q_coeff"].update({'WI':self.WI, 'WF':self.WF}) self.W = self.WI + self.WF + 1
def ui2dict(self): # was: save_ui """ Update the attributes `self.WI`, `self.WF` and `self.W` when one of the QLineEdit widgets has been edited. Return a dict with the three parameters when called directly """ self.WI = safe_eval(self.ledWI.text(), self.WI, return_type="int", sign='pos') self.ledWI.setText(qstr(self.WI)) self.WF = safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='pos') self.ledWF.setText(qstr(self.WF)) self.W = self.WI + self.WF + 1 return {'WI':self.WI, 'WF':self.WF, 'W':self.W}
def exportHDL(self): """ Synthesize HDL description of filter using myHDL module """ dlg = QFD(self) # instantiate file dialog object file_types = "Verilog (*.v);;VHDL (*.vhd)" 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 `hdl_type` 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 # remove the suffix from the filename: hdl_file_name = os.path.splitext(os.path.basename(hdl_file))[0] if hdl_type == '.vhd': hdl = 'VHDL' elif hdl_type == '.v': hdl = 'Verilog' else: logger.error( 'Unknown file extension "{0}", cancelling.'.format( hdl_type)) return logger.info('Creating hdl_file "{0}"'.format( os.path.join(hdl_dir_name, hdl_file_name + hdl_type))) try: self.update_fxqc_dict() self.hdl_filter_inst.setup(self.fxqc_dict) self.hdl_filter_inst.convert(hdl=hdl, name=hdl_file_name, path=hdl_dir_name) logger.info("HDL conversion finished!") except (IOError, TypeError) as e: logger.warning(e)
def dict2ui(self, w_dict): """ Update the widgets `WI` and `WF` from the dict passed as the argument """ if 'WI' in w_dict: self.WI = safe_eval(w_dict['WI'], self.WI, return_type="int", sign='pos') self.ledWI.setText(qstr(self.WI)) else: logger.warning("No key 'WI' in dict!") if 'WF' in w_dict: self.WF = safe_eval(w_dict['WF'], self.WF, return_type="int", sign='pos') self.ledWF.setText(qstr(self.WF)) else: logger.warning("No key 'WF' in dict!")
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 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 dict2ui(self, c_dict=None): """ Update the ui and the attributes `self.WI` and `self.WF` from the filter dict. `dict2ui()` has to be called when the coefficients or the word format has been changed outside the class, e.g. by a new filter design or by changing the coefficient format in `input_coeffs.py`. """ if not c_dict: c_dict = fb.fil[0]['q_coeff'] self.WI = c_dict['WI'] self.WF = c_dict['WF'] self.ledWI.setText(qstr(self.WI)) self.ledWF.setText(qstr(self.WF)) self.W = self.WI + self.WF + 1 self.c_dict = build_coeff_dict()
def _store_q_settings(self): """ Read out the settings of the quantization comboboxes and store them in the filter dict. Update the fixpoint object and refresh table """ 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.sig_tx.emit({'sender':__name__, 'view_changed':'q_coeff'}) self._load_q_settings() # update widgets and the fixpoint object self.myQ self._refresh_table()
def exportHDL(self): """ Synthesize HDL description of filter using myHDL module """ dlg = QFD(self) # instantiate file dialog object file_types = "Verilog (*.v);;VHDL (*.vhd)" 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 `hdl_type` 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 # remove the suffix from the filename: hdl_file_name = os.path.splitext(os.path.basename(hdl_file))[0] if hdl_type == '.vhd': hdl = 'VHDL' elif hdl_type == '.v': hdl = 'Verilog' else: logger.error('Unknown file extension "{0}", cancelling.'.format(hdl_type)) return logger.info('Creating hdl_file "{0}"'.format( os.path.join(hdl_dir_name, hdl_file_name + hdl_type))) try: self.update_fxqc_dict() self.hdl_filter_inst.setup(self.fxqc_dict) self.hdl_filter_inst.convert(hdl=hdl, name=hdl_file_name, path=hdl_dir_name) logger.info("HDL conversion finished!") except (IOError, TypeError) as e: logger.warning(e)
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(qstr(q_coeff['WI'])) self.ui.ledWF.setText(qstr(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(qstr(q_coeff['scale'])) self.ui.ledW.setText(qstr(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 ui2qdict(self): """ 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='pos'), '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 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 ui2dict(self): """ Update the attributes `self.WI` and `self.WF` and the filter dict when one of the QLineEdit widgets has been edited. Cast values to standard python int format (instead of e.g. np.int64) to avoid problems with HDL simulators downstream """ self.WI = int( safe_eval(self.ledWI.text(), self.WI, return_type="int", sign='pos')) self.ledWI.setText(qstr(self.WI)) self.WF = int( safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='pos')) self.ledWF.setText(qstr(self.WF)) fb.fil[0]["q_coeff"].update({'WI': self.WI, 'WF': self.WF}) self.W = int(self.WI + self.WF + 1)
def qdict2ui(self): """ 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 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: polar_str = text.split('*' + self.angle_char, 1) if len(polar_str) < 2: # input is real or imaginary 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 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 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: polar_str = text.split('*' + self.angle_char, 1) if len(polar_str) < 2: # input is real or imaginary 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 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 fixp(self, y, scaling='mult'): """ Return fixed-point integer or fractional representation for `y` (scalar or array-like) with the same shape as `y`. Saturation / two's complement wrapping happens outside the range +/- MSB, requantization (round, floor, fix, ...) is applied on the ratio `y / LSB`. Parameters ---------- y: scalar or array-like object input value (floating point format) to be quantized scaling: String Determine the scaling before and after quantizing / saturation *'mult'* float in, int out: `y` is multiplied by `self.scale` *before* quantizing / saturating **'div'**: int in, float out: `y` is divided by `self.scale` *after* quantizing / saturating. **'multdiv'**: float in, float out (default): both of the above For all other settings, `y` is transformed unscaled. Returns ------- float scalar or ndarray with the same shape as `y`, in the range `-2*self.MSB` ... `2*self.MSB-self.LSB` Examples -------- >>> q_obj_a = {'WI':1, 'WF':6, 'ovfl':'sat', 'quant':'round'} >>> myQa = Fixed(q_obj_a) # instantiate fixed-point object myQa >>> myQa.resetN() # reset overflow counter >>> a = np.arange(0,5, 0.05) # create input signal >>> aq = myQa.fixed(a) # quantize input signal >>> plt.plot(a, aq) # plot quantized vs. original signal >>> print(myQa.N_over, "overflows!") # print number of overflows >>> # Convert output to same format as input: >>> b = np.arange(200, dtype = np.int16) >>> btype = np.result_type(b) >>> # MSB = 2**7, LSB = 2**(-2): >>> q_obj_b = {'WI':7, 'WF':2, 'ovfl':'wrap', 'quant':'round'} >>> myQb = Fixed(q_obj_b) # instantiate fixed-point object myQb >>> bq = myQb.fixed(b) >>> bq = bq.astype(btype) # restore original variable type """ #====================================================================== # (1) : INITIALIZATION # Convert input argument into proper floating point scalars / # arrays and initialize flags #====================================================================== scaling = scaling.lower() if np.shape(y): # Input is an array: # Create empty arrays for result and overflows with same shape as y # for speedup, test for invalid types SCALAR = False y = np.asarray(y) # convert lists / tuples / ... to numpy arrays yq = np.zeros(y.shape) over_pos = over_neg = np.zeros(y.shape, dtype=bool) self.ovr_flag = np.zeros(y.shape, dtype=int) if np.issubdtype(y.dtype, np.number): pass elif y.dtype.kind in {'U', 'S'}: # string or unicode try: y = y.astype(np.float64) # try to convert to float except (TypeError, ValueError): try: np.char.replace(y, ' ', '') # remove all whitespace y = y.astype(complex) # try to convert to complex except (TypeError, ValueError ) as e: # try converting elements recursively y = list( map( lambda y_scalar: self.fixp(y_scalar, scaling=scaling), y)) else: logger.error("Argument '{0}' is of type '{1}',\n" "cannot convert to float.".format(y, y.dtype)) y = np.zeros(y.shape) else: # Input is a scalar SCALAR = True # get rid of errors that have occurred upstream if y is None or str(y) == "": y = 0 # If y is not a number, remove whitespace and try to convert to # to float and or to complex format: elif not np.issubdtype(type(y), np.number): y = qstr(y) y = y.replace(' ', '') # remove all whitespace try: y = float(y) except (TypeError, ValueError): try: y = complex(y) except (TypeError, ValueError) as e: logger.error("Argument '{0}' yields \n {1}".format( y, e)) y = 0.0 over_pos = over_neg = yq = 0 self.ovr_flag = 0 # convert pseudo-complex (imag = 0) and complex values to real y = np.real_if_close(y) if np.iscomplexobj(y): logger.warning( "Casting complex values to real before quantization!") # quantizing complex objects is not supported yet y = y.real y_in = y # y before scaling / quantizing #====================================================================== # (2) : INPUT SCALING # Multiply by `scale` factor before requantization and saturation # when `scaling=='mult'`or 'multdiv' #====================================================================== if scaling in {'mult', 'multdiv'}: y = y * self.scale #====================================================================== # (3) : QUANTIZATION # Divide by LSB to obtain an intermediate format where the # quantization step size = 1. # Next, apply selected quantization method to convert # floating point inputs to "fixpoint integers". # Finally, multiply by LSB to restore original scale. #===================================================================== y = y / self.LSB if self.quant == 'floor': yq = np.floor(y) # largest integer i, such that i <= x (= binary truncation) elif self.quant == 'round': yq = np.round(y) # rounding, also = binary rounding elif self.quant == 'fix': yq = np.fix(y) # round to nearest integer towards zero ("Betragsschneiden") elif self.quant == 'ceil': yq = np.ceil(y) # smallest integer i, such that i >= x elif self.quant == 'rint': yq = np.rint(y) # round towards nearest int elif self.quant == 'none': yq = y # return unquantized value else: raise Exception('Unknown Requantization type "%s"!' % (self.quant)) yq = yq * self.LSB logger.debug("y_in={0} | y={1} | yq={2}".format(y_in, y, yq)) #====================================================================== # (4) : Handle Overflow / saturation w.r.t. to the MSB, returning a # result in the range MIN = -2*MSB ... + 2*MSB-LSB = MAX #===================================================================== if self.ovfl == 'none': pass else: # Bool. vectors with '1' for every neg./pos overflow: over_neg = (yq < self.MIN) over_pos = (yq > self.MAX) # create flag / array of flags for pos. / neg. overflows self.ovr_flag = over_pos.astype(int) - over_neg.astype(int) # No. of pos. / neg. / all overflows occured since last reset: self.N_over_neg += np.sum(over_neg) self.N_over_pos += np.sum(over_pos) self.N_over = self.N_over_neg + self.N_over_pos # Replace overflows with Min/Max-Values (saturation): if self.ovfl == 'sat': yq = np.where(over_pos, self.MAX, yq) # (cond, true, false) yq = np.where(over_neg, self.MIN, yq) # Replace overflows by two's complement wraparound (wrap) elif self.ovfl == 'wrap': yq = np.where( over_pos | over_neg, yq - 4. * self.MSB * np.fix( (np.sign(yq) * 2 * self.MSB + yq) / (4 * self.MSB)), yq) else: raise Exception('Unknown overflow type "%s"!' % (self.ovfl)) return None #====================================================================== # (5) : OUTPUT SCALING # Divide result by `scale` factor when `scaling=='div'`or 'multdiv' # to obtain correct scaling for floats # - frmt2float() always returns float # - input_coeffs when quantizing the coefficients # float2frmt passes on the scaling argument #====================================================================== if scaling in {'div', 'multdiv'}: yq = yq / self.scale if SCALAR and isinstance(yq, np.ndarray): yq = yq.item() # convert singleton array to scalar return yq
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 frmt2float(self, y, frmt=None): """ Return floating point representation for fixpoint scalar `y` given in format `frmt`. When input format is float, return unchanged. Else: - Remove illegal characters and leading '0's - Count number of fractional places `frc_places` and remove radix point - Calculate decimal, fractional representation `y_dec` of string, using the base and the number of fractional places - Calculate two's complement for `W` bits (only for negative bin and hex numbers) - Calculate fixpoint float representation `y_float = fixp(y_dec, scaling='div')`, dividing the result by `scale`. Parameters ---------- y: scalar or string to be quantized with the numeric base specified by `frmt`. frmt: string (optional) any of the formats `float`, `dec`, `bin`, `hex`, `csd`) When `frmt` is unspecified, the instance parameter `self.frmt` is used Returns ------- quantized floating point (`dtype=np.float64`) representation of input string """ if y == "": return 0 if frmt is None: frmt = self.frmt frmt = frmt.lower() y_float = y_dec = None if frmt == 'float32': float_frmt = np.float32 elif frmt == 'float16': float_frmt = np.float16 if frmt == 'float': # this handles floats, np scalars + arrays and strings / string arrays try: y_float = np.float64(y) except ValueError: try: y_float = np.complex(y).real except Exception as e: y_float = None logger.warning("Can't convert {0}: {1}".format(y, e)) return y_float else: # {'dec', 'bin', 'hex', 'csd'} # Find the number of places before the first radix point (if there is one) # and join integer and fractional parts # when returned string is empty, skip general conversions and rely on error handling # of individual routines # remove illegal characters and trailing zeros val_str = re.sub(self.FRMT_REGEX[frmt], r'', qstr(y)).lstrip('0') if len(val_str) > 0: val_str = val_str.replace( ',', '.') # ',' -> '.' for German-style numbers if val_str[ 0] == '.': # prepend '0' when the number starts with '.' val_str = '0' + val_str # count number of fractional places in string try: _, frc_str = val_str.split( '.') # split into integer and fractional places frc_places = len(frc_str) except ValueError: # no fractional part frc_places = 0 raw_str = val_str.replace( '.', '') # join integer and fractional part logger.debug("y={0}, val_str={1}, raw_str={2} ".format( y, val_str, raw_str)) else: return 0.0 # (1) calculate the decimal value of the input string using np.float64() # which takes the number of decimal places into account. # (2) quantize and saturate # (3) divide by scale if frmt == 'dec': # try to convert string -> float directly with decimal point position try: y_dec = y_float = self.fixp(val_str, scaling='div') except Exception as e: logger.warning(e) elif frmt in {'hex', 'bin'}: # - Glue integer and fractional part to a string without radix point # - Check for a negative sign, use this information only in the end # - Divide by <base> ** <number of fractional places> for correct scaling # - Strip MSBs outside fixpoint range # - Transform numbers in negative 2's complement to negative floats. # - Calculate the fixpoint representation for correct saturation / quantization neg_sign = False try: if raw_str[0] == '-': neg_sign = True raw_str = raw_str.lstrip('-') y_dec = abs(int(raw_str, self.base) / self.base**frc_places) if y_dec == 0: # avoid log2(0) return 0 int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0) # When number is outside fixpoint range, discard MSBs: if int_bits > self.WI + 1: if frmt == 'hex': raw_str = np.binary_repr(int(raw_str, 16)) # discard the upper bits outside the valid range raw_str = raw_str[int_bits - self.WI - 1:] # recalculate y_dec for truncated string y_dec = int(raw_str, 2) / self.base**frc_places if y_dec == 0: # avoid log2(0) error in code below return 0 int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0) # ... and int_bits # now, y_dec is in the correct range: if int_bits <= self.WI: # positive number pass elif int_bits == self.WI + 1: # negative, calculate 2's complemente y_dec = y_dec - (1 << int_bits) # quantize / saturate / wrap & scale the integer value: if neg_sign: y_dec = -y_dec y_float = self.fixp(y_dec, scaling='div') except Exception as e: logger.warning(e) y_dec = y_float = None logger.debug("MSB={0} | LSB={1} | scale={2}".format( self.MSB, self.LSB, self.scale)) logger.debug("y_in={0} | y_dec={1}".format(y, y_dec)) # ---- elif frmt == 'csd': # - Glue integer and fractional part to a string without radix point # - Divide by 2 ** <number of fractional places> for correct scaling # - Calculate fixpoint representation for saturation / overflow effects y_dec = csd2dec_vec(raw_str) # csd -> integer if y_dec is not None: y_float = self.fixp(y_dec / 2**frc_places, scaling='div') # ---- else: logger.error('Unknown output format "%s"!'.format(frmt)) if frmt != "float": logger.debug("MSB={0:g} | scale={1:g} | raw_str={2} | val_str={3}"\ .format(self.MSB, self.scale, raw_str, val_str)) logger.debug("y={0} | y_dec = {1} | y_float={2}".format( y, y_dec, y_float)) if y_float is not None: return y_float else: return 0.0
def _construct_UI(self, **kwargs): """ Construct widget from default settings, """ # default settings dict_ui = { '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 } #: default values 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}) # dict_ui.update(map(kwargs)) # same as above? if not dict_ui['fractional']: dict_ui['WF'] = 0 self.WI = dict_ui['WI'] self.WF = dict_ui['WF'] lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self) 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(?) 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']) layH = QHBoxLayout() layH.addWidget(lblW) layH.addStretch() 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)
def displayText(self, text, locale): return "{:.{n_digits}g}".format(safe_eval(qstr(text), return_type='cmplx'), n_digits = params['FMT_pz'])
def displayText(self, text, locale): return "{:.{n_digits}g}".format(safe_eval(qstr(text), return_type='cmplx'), n_digits=params['FMT_pz'])
def _construct_UI(self, **kwargs): """ Construct widget from quantization dict, individual settings and the default dict below """ # default settings dict_ui = { '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}) 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.but_clicked) self.cmbW.currentIndexChanged.connect(self.ui2dict) # initialize button icon self.but_clicked(self.butLock.isChecked())