def _update_UI(self): """ Update UI when line edit field is changed (here, only the text is read and converted to integer) and store parameter settings in filter dictionary """ self.p[0] = safe_eval(self.led_pole1.text(), self.p[0], return_type='cmplx') self.led_pole1.setText(str(self.p[0])) self.p[1] = safe_eval(self.led_pole2.text(), self.p[1], return_type='cmplx') self.led_pole2.setText(str(self.p[1])) if not 'wdg_fil' in fb.fil[0]: fb.fil[0].update({'wdg_fil': {}}) fb.fil[0]['wdg_fil'].update( {'poles': { 'p1': self.p[0], 'p2': self.p[1] }}) # sig_tx -> select_filter -> filter_specs self.sig_tx.emit({'sender': __name__, 'filt_changed': 'pole_1_2'})
def 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 _set_number_format(self): """ Triggered by `contruct_UI()`, `qdict2ui()`and by `ui.cmbQFrmt.currentIndexChanged()` Set one of three number formats: Integer, fractional, normalized fractional (triggered by self.ui.cmbQFrmt combobox) """ qfrmt = qget_cmb_box(self.ui.cmbQFrmt) is_qfrac = False W = safe_eval(self.ui.ledW.text(), self.myQ.W, return_type='int', sign='pos') if qfrmt == 'qint': self.ui.ledWI.setText(str(W - 1)) self.ui.ledWF.setText("0") elif qfrmt == 'qnfrac': # normalized fractional format self.ui.ledWI.setText("0") self.ui.ledWF.setText(str(W - 1)) else: # qfrmt == 'qfrac': is_qfrac = True WI = safe_eval(self.ui.ledWI.text(), self.myQ.WI, return_type='int') self.ui.ledScale.setText(str(1 << WI)) self.ui.ledWI.setEnabled(is_qfrac) self.ui.lblDot.setEnabled(is_qfrac) self.ui.ledWF.setEnabled(is_qfrac) self.ui.ledW.setEnabled(not is_qfrac) self.ui.ledScale.setEnabled(False) self.ui2qdict() # save UI to dict and to class attributes
def update_N(self, emit=True): """ Update values for `self.N` and `self.win_dict['N']`, for `self.N_start` and `self.N_end` from the corresponding QLineEditWidgets. When `emit==True`, fire `'ui_changed': 'N'` to update the FFT window and the `plot_impz` widgets. In contrast to `view_changed`, this also forces a recalculation of the transient response. This method is called by: - `self._construct_ui()` with `emit==False` - `plot_impz()` with `emit==False` when the automatic calculation of N has to be updated (e.g. order of FIR Filter has changed - signal-slot connection when `N_start` or `N_end` QLineEdit widgets have been changed (`emit==True`) """ if not isinstance(emit, bool): logger.error("update N: emit={0}".format(emit)) self.N_start = safe_eval(self.led_N_start.text(), self.N_start, return_type='int', sign='poszero') self.led_N_start.setText(str(self.N_start)) # update widget self.N_user = safe_eval(self.led_N_points.text(), self.N_user, return_type='int', sign='poszero') if self.N_user == 0: # automatic calculation self.N = self.calc_n_points(self.N_user) # widget remains set to 0 self.led_N_points.setText("0") # update widget else: self.N = self.N_user self.led_N_points.setText(str(self.N)) # update widget # total number of points to be calculated: N + N_start self.N_end = self.N + self.N_start self.N_frame_user = safe_eval(self.led_N_frame.text(), self.N_frame_user, return_type='int', sign='poszero') if self.N_frame_user == 0: self.N_frame = self.N_end # use N_end for frame length self.led_N_frame.setText( "0") # update widget with "0" as set by user else: self.N_frame = self.N_frame_user self.led_N_frame.setText(str(self.N_frame)) # update widget # recalculate displayed freq. index values when freq. unit == 'k' if fb.fil[0]['freq_specs_unit'] == 'k': self.update_freqs() if emit: # use `'ui_changed'` as this triggers recalculation of the transient # response self.emit({'ui_changed': 'N'})
def update_N(self, emit=True): # called directly from impz or locally # between local triggering and updates upstream """ Update values for self.N and self.N_start from the QLineEditWidget, update the window and fire "ui_changed" """ if not isinstance(emit, bool): logger.error("update N: emit={0}".format(emit)) self.N_start = safe_eval(self.led_N_start.text(), self.N_start, return_type='int', sign='poszero') self.led_N_start.setText(str(self.N_start)) # update widget self.N_user = safe_eval(self.led_N_points.text(), self.N_user, return_type='int', sign='poszero') if self.N_user == 0: # automatic calculation self.N = self.calc_n_points(self.N_user) # widget remains set to 0 self.led_N_points.setText("0") # update widget else: self.N = self.N_user self.led_N_points.setText(str(self.N)) # update widget self.N_end = self.N + self.N_start # total number of points to be calculated: N + N_start # FFT window needs to be updated due to changed number of data points self._update_win_fft(emit=False) # don't emit anything here if emit: self.sig_tx.emit({'sender':__name__, 'ui_changed':'N'})
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 _log_clicked(self): """ Change scale and settings to log / lin when log setting is changed Update min / max settings when lineEdits have been edited """ # clicking but_log triggered the slot or initialization if self.sender() is None or self.sender().objectName() == 'but_log': if self.but_log.isChecked(): self.ledBottom.setText(str(self.zmin_dB)) self.zmax_dB = np.round(20 * np.log10(self.zmax), 2) self.ledTop.setText(str(self.zmax_dB)) else: self.ledBottom.setText(str(self.zmin)) self.zmax = np.round(10**(self.zmax_dB / 20), 2) self.ledTop.setText(str(self.zmax)) else: # finishing a lineEdit field triggered the slot if self.but_log.isChecked(): self.zmin_dB = safe_eval( self.ledBottom.text(), self.zmin_dB, return_type='float') self.ledBottom.setText(str(self.zmin_dB)) self.zmax_dB = safe_eval( self.ledTop.text(), self.zmax_dB, return_type='float') self.ledTop.setText(str(self.zmax_dB)) else: self.zmin = safe_eval( self.ledBottom.text(), self.zmin, return_type='float') self.ledBottom.setText(str(self.zmin)) self.zmax = safe_eval(self.ledTop.text(), self.zmax, return_type='float') self.ledTop.setText(str(self.zmax)) self.draw()
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 _update_UI(self): """ Update UI when line edit field is changed (here, only the text is read and converted to integer) and resize the textfields according to content. """ self.delays = safe_eval(self.led_delays.text(), self.delays, return_type='int', sign='pos') self.led_delays.setText(str(self.delays)) self.stages = safe_eval(self.led_stages.text(), self.stages, return_type='int', sign='pos') self.led_stages.setText(str(self.stages)) self._store_entries()
def _store_entry(source): if self.spec_edited: if source.objectName() == "stimFreq1": self.f1 = safe_eval(source.text(), self.f1 * fb.fil[0]['f_S'], return_type='float') / fb.fil[0]['f_S'] source.setText(str(params['FMT'].format(self.f1 * fb.fil[0]['f_S']))) elif source.objectName() == "stimFreq2": self.f2 = safe_eval(source.text(), self.f2 * fb.fil[0]['f_S'], return_type='float') / fb.fil[0]['f_S'] source.setText(str(params['FMT'].format(self.f2 * fb.fil[0]['f_S']))) self.spec_edited = False # reset flag self.sig_tx.emit({'sender':__name__, 'ui_changed':'stim'})
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 _update_phi1(self): """ Update value for self.phi1 from QLineEditWidget""" self.phi1 = safe_eval(self.ledPhi1.text(), self.phi1, return_type='float') self.ledPhi1.setText(str(self.phi1)) self.emit({'ui_changed': 'phi1'})
def _update_noi(self): """ Update type + value + label for self.noi for noise""" self.noise = qget_cmb_box(self.cmbNoise, data=False).lower() self.lblNoi.setVisible(self.noise != 'none') self.ledNoi.setVisible(self.noise != 'none') if self.noise != 'none': self.noi = safe_eval(self.ledNoi.text(), 0, return_type='float', sign='poszero') self.ledNoi.setText(str(self.noi)) if self.noise == 'gauss': self.lblNoi.setText(to_html("σ =", frmt='bi')) self.ledNoi.setToolTip( "<span>Standard deviation of statistical process," "noise power is <i>P</i> = σ<sup>2</sup></span>") elif self.noise == 'uniform': self.lblNoi.setText(to_html("Δ =", frmt='bi')) self.ledNoi.setToolTip( "<span>Interval size for uniformly distributed process " "(e.g. quantization step size for quantization noise), " "centered around 0. Noise power is " "<i>P</i> = Δ<sup>2</sup>/12.</span>") elif self.noise == 'prbs': self.lblNoi.setText(to_html("A =", frmt='bi')) self.ledNoi.setToolTip( "<span>Amplitude of bipolar Pseudorandom Binary Sequence. " "Noise power is <i>P</i> = A<sup>2</sup>.</span>") self.sig_tx.emit({'sender': __name__, 'ui_changed': 'noi'})
def _update_phi2(self): """ Update value for self.phi2 from the QLineEditWidget""" self.phi2 = safe_eval(self.ledPhi2.text(), self.phi2, return_type='float') self.ledPhi2.setText(str(self.phi2)) self.emit({'ui_changed': 'phi2'})
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 _W_changed(self): """ Set fractional and integer length `WF` and `WI` when wordlength `W` has been changed. Try to preserve `WI` or `WF` settings depending on the number format (integer or fractional). """ W = safe_eval(self.ui.ledW.text(), self.myQ.W, return_type='int', sign='pos') if W < 2: logger.warn("W must be > 1, restoring previous value.") W = self.myQ.W # fall back to previous value self.ui.ledW.setText(str(W)) if qget_cmb_box(self.ui.cmbQFrmt) == 'qint': # integer format, preserve WI bits WI = W - self.myQ.WF - 1 self.ui.ledWI.setText(str(WI)) self.ui.ledScale.setText(str(1 << (W-1))) else: # fractional format, preserve WF bit setting WF = W - self.myQ.WI - 1 if WF < 0: self.ui.ledWI.setText(str(W - 1)) WF = 0 self.ui.ledWF.setText(str(WF)) self.ui2qdict()
def 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 _update_settings_nfft(self): """ Update value for self.par1 from QLineEdit Widget""" params['N_FFT'] = safe_eval(self.led_settings_NFFT.text(), params['N_FFT'], sign='pos', return_type='int') self.led_settings_NFFT.setText(str(params['N_FFT'])) self.sig_tx.emit({'sender': __name__, 'data_changed': 'n_fft'})
def update_bottom(self): """ Update log bottom settings """ self.bottom_t = safe_eval(self.led_log_bottom_t.text(), self.bottom_t, sign='neg', return_type='float') self.led_log_bottom_t.setText(str(self.bottom_t)) self.bottom_f = safe_eval(self.led_log_bottom_f.text(), self.bottom_f, sign='neg', return_type='float') self.led_log_bottom_f.setText(str(self.bottom_f)) self.update_view()
def _set_eps(self): """ Set tolerance value """ self.ui.eps = safe_eval(self.ui.ledEps.text(), alt_expr=self.ui.eps, sign='pos') self.ui.ledEps.setText(str(self.ui.eps))
def _update_stim_par1(self): """ Update value for self.par1 from QLineEditWidget""" self.stim_par1 = safe_eval(self.ledStimPar1.text(), self.stim_par1, sign='pos', return_type='float') self.ledStimPar1.setText(str(self.stim_par1)) self.emit({'ui_changed': 'stim_par1'})
def _update_BW2(self): """ Update value for self.BW2 from QLineEditWidget""" self.BW2 = safe_eval(self.led_BW2.text(), self.BW2, return_type='float', sign='pos') self.led_BW2.setText(str(self.BW2)) self.emit({'ui_changed': 'BW2'})
def _store_gain(self, source): """ When the textfield of `source` has been edited (flag `self.spec_edited` = True), store it in the shadow dict. This is triggered by `QEvent.focusOut` or RETURN key. """ if self.spec_edited: self.zpk[2] = safe_eval(source.text(), alt_expr=str(self.zpk[2])) self.spec_edited = False # reset flag
def _update_BW1(self): """ Update value for self.BW1 from QLineEditWidget""" self.BW1 = safe_eval(self.led_BW1.text(), self.BW1, return_type='float', sign='pos') self.led_BW1.setText(str(self.BW1)) self._update_scale_impz() self.emit({'ui_changed': 'BW1'})
def _set_scale(self): """ Triggered by `ui.ledScale` Set scale for calculating floating point value from fixpoint representation and vice versa """ # if self.ui.ledScale.isModified() ... self.ui.ledScale.setModified(False) scale = safe_eval(self.ui.ledScale.text(), self.myQ.scale, return_type='float', sign='pos') self.ui.ledScale.setText(str(scale)) self.ui2qdict()
def _log_clicked(self): """ Change scale and settings to log / lin when log setting is changed Update min / max settings when lineEdits have been edited """ self.log = self.chkLog.isChecked() if self.sender().objectName( ) == 'chkLog': # clicking chkLog triggered the slot if self.log: self.ledBottom.setText(str(self.zmin_dB)) self.zmax_dB = np.round(20 * log10(self.zmax), 2) self.ledTop.setText(str(self.zmax_dB)) self.lblTopdB.setVisible(True) self.lblBottomdB.setVisible(True) else: self.ledBottom.setText(str(self.zmin)) self.zmax = np.round(10**(self.zmax_dB / 20), 2) self.ledTop.setText(str(self.zmax)) self.lblTopdB.setVisible(False) self.lblBottomdB.setVisible(False) else: # finishing a lineEdit field triggered the slot if self.log: self.zmin_dB = safe_eval(self.ledBottom.text(), self.zmin_dB, return_type='float') self.ledBottom.setText(str(self.zmin_dB)) self.zmax_dB = safe_eval(self.ledTop.text(), self.zmax_dB, return_type='float') self.ledTop.setText(str(self.zmax_dB)) else: self.zmin = safe_eval(self.ledBottom.text(), self.zmin, return_type='float') self.ledBottom.setText(str(self.zmin)) self.zmax = safe_eval(self.ledTop.text(), self.zmax, return_type='float') self.ledTop.setText(str(self.zmax)) self.draw()
def _store_entry(source): if self.spec_edited: if source.objectName() == "stimFreq1": self.f1 = safe_eval(source.text(), self.f1 * self.f_scale, return_type='float') / self.f_scale source.setText(str(params['FMT'].format(self.f1 * self.f_scale))) elif source.objectName() == "stimFreq2": self.f2 = safe_eval(source.text(), self.f2 * self.f_scale, return_type='float') / self.f_scale source.setText(str(params['FMT'].format(self.f2 * self.f_scale))) self.spec_edited = False # reset flag self.sig_tx.emit({'sender':__name__, 'ui_changed':'stim'}) # nothing has changed, but display frequencies in rounded format anyway elif source.objectName() == "stimFreq1": source.setText(str(params['FMT'].format(self.f1 * self.f_scale))) elif source.objectName() == "stimFreq2": source.setText(str(params['FMT'].format(self.f2 * self.f_scale)))
def _read_param2(self): """Read out textbox when editing is finished and update dict and fft window""" param = safe_eval(self.ledWinPar2.text(), self.win_dict['par'][1]['val'], return_type='float') if param < self.win_dict['par'][1]['min']: param = self.win_dict['par'][1]['min'] elif param > self.win_dict['par'][1]['max']: param = self.win_dict['par'][1]['max'] self.ledWinPar2.setText(str(param)) self.win_dict['par'][1]['val'] = param self._update_win_fft()
def _store_entry(): """ Update filter dictionary, set line edit entry with reduced precision again. """ if self.spec_edited: fb.fil[0].update({'f_S':safe_eval(source.text(), fb.fil[0]['f_S'], sign='pos')}) # TODO: ?! self._freq_range(emit_sig_range = False) # update plotting range self.sig_tx.emit({'sender':__name__, 'view_changed':'f_S'}) self.spec_edited = False # reset flag, changed entry has been saved
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))