def test_cmb_filter_type(self): """Test setting <Filter Type> ComboBox and the effect on the table shape""" self.init() self.set_cmb_box(self.ui.cmbFilterType, 'IIR') self.assertEqual(qget_cmb_box(self.ui.cmbFilterType, data=False), "IIR") self.ui.cmbFilterType.currentIndexChanged.emit(1) QTest.mouseClick(self.ui.cmbFilterType, Qt.LeftButton) QTest.keyClick(QApplication.instance().focusWidget(), Qt.Key_PageDown) QTest.qWait(1000) QTest.keyClick(QApplication.instance().focusWidget(), Qt.Key_Return) QTest.qWait(1000) self.assertEqual(qget_cmb_box(self.ui.cmbFilterType, data=False), "IIR") # https://vicrucann.github.io/tutorials/qttest-signals-qtreewidget/ self.assertEqual(self.form.tblCoeff.rowCount(), 3) self.assertEqual(self.form.tblCoeff.columnCount(), 2) item_10 = self.form.tblCoeff.item(0, 1) # row, col self.assertEqual(float(item_10.text()), 1) self.set_cmb_box(self.ui.cmbFilterType, 'FIR') self.assertEqual(self.form.tblCoeff.rowCount(), 3) self.assertEqual(self.form.tblCoeff.columnCount(), 1) self.log.warning("test_cmb_filter_type finished")
def update_accu_settings(self): """ Calculate number of extra integer bits needed in the accumulator (bit growth) depending on the coefficient area (sum of absolute coefficient values) for `cmbW == 'auto'` or depending on the number of coefficients for `cmbW == 'full'`. The latter works for arbitrary coefficients but requires more bits. The new values are written to the fixpoint coefficient dict `fb.fil[0]['fxqc']['QA']`. """ try: if qget_cmb_box(self.wdg_w_accu.cmbW, data=False) == "full": A_coeff = int(np.ceil(np.log2(len(fb.fil[0]['fxqc']['b'])))) elif qget_cmb_box(self.wdg_w_accu.cmbW, data=False) == "auto": A_coeff = int(np.ceil(np.log2(np.sum(np.abs(fb.fil[0]['ba'][0]))))) except Exception as e: logger.error(e) return if qget_cmb_box(self.wdg_w_accu.cmbW, data=False) == "full" or\ qget_cmb_box(self.wdg_w_accu.cmbW, data=False) == "auto": fb.fil[0]['fxqc']['QA']['WF'] = fb.fil[0]['fxqc']['QI']['WF']\ + fb.fil[0]['fxqc']['QCB']['WF'] fb.fil[0]['fxqc']['QA']['WI'] = fb.fil[0]['fxqc']['QI']['WI']\ + fb.fil[0]['fxqc']['QCB']['WI'] + A_coeff # calculate total accumulator word length fb.fil[0]['fxqc']['QA']['W'] = fb.fil[0]['fxqc']['QA']['WI']\ + fb.fil[0]['fxqc']['QA']['WF'] + 1 # update quantization settings fb.fil[0]['fxqc']['QA'].update(self.wdg_q_accu.q_dict) self.wdg_w_accu.dict2ui(fb.fil[0]['fxqc']['QA'])
def test_defaults(self): """Test GUI setting in its default state""" self.init() self.assertEqual(self.ui.spnDigits.value(), 4) self.assertEqual(qget_cmb_box(self.ui.cmbFilterType, data=False), "FIR") self.assertEqual( qget_cmb_box(self.ui.cmbFormat, data=False).lower(), "float") self.assertEqual(self.ui.butSetZero.text(), "= 0") self.assertEqual(self.form.tblCoeff.rowCount(), 3) self.assertEqual(self.form.tblCoeff.columnCount(), 1) self.assertEqual(self.form.tblCoeff.item(0, 0).text(), "1") self.log.warning("test_defaults finished")
def _normalize_gain(self): """ Normalize the gain factor so that the maximum of |H(f)| stays 1 or a previously stored maximum value of |H(f)|. Do this every time a P or Z has been changed. Called by setModelData() and when cmbNorm is activated """ norm = qget_cmb_box(self.ui.cmbNorm, data=False) self.ui.ledGain.setEnabled(norm == 'None') if norm != self.norm_last: qstyle_widget(self.ui.butSave, 'changed') if not np.isfinite(self.zpk[2]): self.zpk[2] = 1. self.zpk[2] = np.real_if_close(self.zpk[2]).item() if np.iscomplex(self.zpk[2]): logger.warning("Casting complex to real for gain k!") self.zpk[2] = np.abs(self.zpk[2]) if norm != "None": b, a = zpk2tf(self.zpk[0], self.zpk[1], self.zpk[2]) [w, H] = freqz(b, a, whole=True) Hmax = max(abs(H)) if not np.isfinite(Hmax) or Hmax > 1e4 or Hmax < 1e-4: Hmax = 1. if norm == "1": self.zpk[2] = self.zpk[2] / Hmax # normalize to 1 elif norm == "Max": if norm != self.norm_last: # setting has been changed -> 'Max' self.Hmax_last = Hmax # use current design to set Hmax_last self.zpk[2] = self.zpk[2] / Hmax * self.Hmax_last self.norm_last = norm # store current setting of combobox self._restore_gain()
def _freq_range(self, emit=True): """ Set frequency plotting range for single-sided spectrum up to f_S/2 or f_S or for double-sided spectrum between -f_S/2 and f_S/2 Emit 'view_changed':'f_range' when `emit=True` """ if type(emit) == int: # signal was emitted by combobox emit = True rangeType = qget_cmb_box(self.cmbFRange) fb.fil[0].update({'freqSpecsRangeType': rangeType}) f_max = fb.fil[0]["f_max"] if rangeType == 'whole': f_lim = [0, f_max] elif rangeType == 'sym': f_lim = [-f_max / 2., f_max / 2.] else: f_lim = [0, f_max / 2.] fb.fil[0]['freqSpecsRange'] = f_lim # store settings in dict if emit: self.emit({'view_changed': 'f_range'})
def process_sig_rx(self, dict_sig=None): logger.debug("sig_rx:\n{0}".format(pprint_log(dict_sig))) # check whether anything needs to be done locally # could also check here for 'quant', 'ovfl', 'WI', 'WF' (not needed at the moment) # if not, just pass the dict if 'ui' in dict_sig: if dict_sig['id'] == 'w_coeff': # coefficient format updated """ Update coefficient quantization settings and coefficients. The new values are written to the fixpoint coefficient dict as `fb.fil[0]['fxqc']['QCB']` and `fb.fil[0]['fxqc']['b']`. """ fb.fil[0]['fxqc'].update(self.ui2dict()) elif dict_sig['ui'] == 'cmbW': cmbW = qget_cmb_box(self.wdg_w_accu.cmbW, data=False) self.wdg_w_accu.ledWF.setEnabled(cmbW=='man') self.wdg_w_accu.ledWI.setEnabled(cmbW=='man') if cmbW in {'full', 'auto'}: self.dict2ui() self.sig_tx.emit({'sender':__name__, 'specs_changed':'cmbW'}) else: return dict_sig.update({'sender':__name__}) # currently only local self.sig_tx.emit(dict_sig)
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 _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 _set_response_type(self, enb_signal=False): """ Triggered when cmbResponseType (LP, HP, ...) is changed: Copy selection to self.rt and fb.fil[0] and reconstruct filter type combo If previous filter type (FIR, IIR, ...) exists for new rt, set the filter type combo box to the old setting """ # Read current setting of comboBox as string and store it in the filter dict fb.fil[0]['rt'] = self.rt = qget_cmb_box(self.cmbResponseType) # Get list of available filter types for new rt ft_list = list( fb.fil_tree[self.rt].keys()) # explicit list() needed for Py3 # --------------------------------------------------------------- # Rebuild filter type combobox entries for new rt setting self.cmbFilterType.blockSignals( True) # don't fire when changed programmatically self.cmbFilterType.clear() for ft in fb.fil_tree[self.rt]: self.cmbFilterType.addItem(rc.ft_names[ft], ft) # Is current filter type (e.g. IIR) in list for new rt? if fb.fil[0]['ft'] in ft_list: ft_idx = self.cmbFilterType.findText(fb.fil[0]['ft']) self.cmbFilterType.setCurrentIndex( ft_idx) # yes, set same ft as before else: self.cmbFilterType.setCurrentIndex(0) # no, set index 0 self.cmbFilterType.blockSignals(False) # --------------------------------------------------------------- self._set_filter_type(enb_signal)
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_win_fft(self): """ Update window type for FirWin """ self.alg = str(self.cmb_firwin_alg.currentText()) self.fir_window_name = qget_cmb_box(self.cmb_firwin_win, data=False) self.win = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) n_par = self.win_dict['n_par'] self.lblWinPar1.setVisible(n_par > 0) self.ledWinPar1.setVisible(n_par > 0) self.lblWinPar2.setVisible(n_par > 1) self.ledWinPar2.setVisible(n_par > 1) if n_par > 0: self.lblWinPar1.setText( to_html(self.win_dict['par'][0]['name'] + " =", frmt='bi')) self.ledWinPar1.setText(str(self.win_dict['par'][0]['val'])) self.ledWinPar1.setToolTip(self.win_dict['par'][0]['tooltip']) if n_par > 1: self.lblWinPar2.setText( to_html(self.win_dict['par'][1]['name'] + " =", frmt='bi')) self.ledWinPar2.setText(str(self.win_dict['par'][1]['val'])) self.ledWinPar2.setToolTip(self.win_dict['par'][1]['tooltip']) # sig_tx -> select_filter -> filter_specs self.sig_tx.emit({'sender': __name__, 'filt_changed': 'firwin'})
def _enable_stim_widgets(self): """ Enable / disable widgets depending on the selected stimulus""" self.stim = qget_cmb_box(self.cmbStimulus, data=False) f1_en = self.stim in { "Cos", "Sine", "Rect", "Saw", "Triang", "Comb", "PM", "FM", "AM" } f2_en = self.stim in {"Cos", "Sine", "PM", "FM", "AM"} dc_en = self.stim not in {"Step", "StepErr"} self.chk_stim_bl.setVisible(self.stim in {"Triang", "Saw", "Rect"}) self.lblAmp1.setVisible(self.stim != "None") self.ledAmp1.setVisible(self.stim != "None") self.lblPhi1.setVisible(f1_en) self.ledPhi1.setVisible(f1_en) self.lblPhU1.setVisible(f1_en) self.lblFreq1.setVisible(f1_en) self.ledFreq1.setVisible(f1_en) self.lblFreqUnit1.setVisible(f1_en) self.lblFreq2.setVisible(f2_en) self.ledFreq2.setVisible(f2_en) self.lblFreqUnit2.setVisible(f2_en) self.lblAmp2.setVisible(f2_en) self.ledAmp2.setVisible(f2_en) self.lblPhi2.setVisible(f2_en) self.ledPhi2.setVisible(f2_en) self.lblPhU2.setVisible(f2_en) self.lblDC.setVisible(dc_en) self.ledDC.setVisible(dc_en) self.sig_tx.emit({'sender': __name__, 'ui_changed': 'stim'})
def _set_amp_unit(self, source): """ Store unit for amplitude in filter dictionary, reload amplitude spec entries via load_dict and fire a sigUnitChanged signal """ fb.fil[0]['amp_specs_unit'] = qget_cmb_box(self.cmbUnitsA, data=False) self.load_dict() self.sig_tx.emit({'sender': __name__, 'view_changed': 'a_unit'})
def update_view(self): """ Draw the figure with new limits, scale etc without recalculating H(f) """ self.unitPhi = qget_cmb_box(self.cmbUnitsPhi, data=False) f_max_2 = fb.fil[0]['f_max'] / 2. #========= select frequency range to be displayed ===================== #=== shift, scale and select: W -> F, H_cplx -> H_c F = self.W * f_max_2 / np.pi if fb.fil[0]['freqSpecsRangeType'] == 'sym': # shift H and F by f_S/2 H = np.fft.fftshift(self.H_cmplx) F -= f_max_2 elif fb.fil[0]['freqSpecsRangeType'] == 'half': # only use the first half of H and F H = self.H_cmplx[0:params['N_FFT']//2] F = F[0:params['N_FFT']//2] else: # fb.fil[0]['freqSpecsRangeType'] == 'whole' # use H and F as calculated H = self.H_cmplx y_str = r'$\angle H(\mathrm{e}^{\mathrm{j} \Omega})$ in ' if self.unitPhi == 'rad': y_str += 'rad ' + r'$\rightarrow $' scale = 1. elif self.unitPhi == 'rad/pi': y_str += 'rad' + r'$ / \pi \;\rightarrow $' scale = 1./ np.pi else: y_str += 'deg ' + r'$\rightarrow $' scale = 180./np.pi fb.fil[0]['plt_phiLabel'] = y_str fb.fil[0]['plt_phiUnit'] = self.unitPhi if self.chkWrap.isChecked(): phi_plt = np.angle(H) * scale else: phi_plt = np.unwrap(np.angle(H)) * scale #--------------------------------------------------------- self.ax.clear() # need to clear, doesn't overwrite line_phi, = self.ax.plot(F, phi_plt) #--------------------------------------------------------- self.ax.xaxis.set_minor_locator(AutoMinorLocator()) # enable minor ticks self.ax.yaxis.set_minor_locator(AutoMinorLocator()) # enable minor ticks self.ax.set_title(r'Phase Frequency Response') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(y_str) self.ax.set_xlim(fb.fil[0]['freqSpecsRange']) self.redraw()
def test_cmb_filter_type(self): """Test <Filter Type> ComboBox""" self.assertEqual(qget_cmb_box(self.form.cmbFilterType, data=False), "IIR") self.assertEqual(self.form.tblCoeff.rowCount(), 3) self.assertEqual(self.form.tblCoeff.columnCount(), 2) self.set_cmb_box(self.form.cmbFilterType, 'FIR') self.assertEqual(self.form.tblCoeff.rowCount(), 3) self.assertEqual(self.form.tblCoeff.columnCount(), 1)
def _construct_UI(self): """ Intitialize the UI with widgets for coefficient format and input and output quantization """ if not 'QA' in fb.fil[0]['fxqc']: fb.fil[0]['fxqc']['QA'] = {} set_dict_defaults(fb.fil[0]['fxqc']['QA'], {'WI':0, 'WF':30, 'W':32, 'ovfl':'wrap', 'quant':'floor'}) self.wdg_w_coeffs = UI_W(self, fb.fil[0]['fxqc']['QCB'], id='w_coeff', label='Coeff. Format <i>B<sub>I.F </sub></i>:', tip_WI='Number of integer bits - edit in the "b,a" tab', tip_WF='Number of fractional bits - edit in the "b,a" tab', WI = fb.fil[0]['fxqc']['QCB']['WI'], WF = fb.fil[0]['fxqc']['QCB']['WF']) # self.wdg_q_coeffs = UI_Q(self, fb.fil[0]['fxqc']['QCB'], # cur_ov=fb.fil[0]['fxqc']['QCB']['ovfl'], # cur_q=fb.fil[0]['fxqc']['QCB']['quant']) # self.wdg_q_coeffs.sig_tx.connect(self.update_q_coeff) self.wdg_w_accu = UI_W(self, fb.fil[0]['fxqc']['QA'], label='', id='w_accu', fractional=True, combo_visible=True) self.wdg_q_accu = UI_Q(self, fb.fil[0]['fxqc']['QA'], id='q_accu', label='Accu Format <i>Q<sub>A </sub></i>:') # initial setting for accumulator cmbW = qget_cmb_box(self.wdg_w_accu.cmbW, data=False) self.wdg_w_accu.ledWF.setEnabled(cmbW=='man') self.wdg_w_accu.ledWI.setEnabled(cmbW=='man') #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs & EVENTFILTERS #---------------------------------------------------------------------- self.wdg_w_coeffs.sig_tx.connect(self.update_q_coeff) self.wdg_w_accu.sig_tx.connect(self.process_sig_rx) self.wdg_q_accu.sig_tx.connect(self.process_sig_rx) #------------------------------------------------------------------------------ layVWdg = QVBoxLayout() layVWdg.setContentsMargins(0,0,0,0) layVWdg.addWidget(self.wdg_w_coeffs) # layVWdg.addWidget(self.wdg_q_coeffs) layVWdg.addWidget(self.wdg_q_accu) layVWdg.addWidget(self.wdg_w_accu) layVWdg.addStretch() self.setLayout(layVWdg)
def test_fixpoint_defaults(self): """Test fixpoint setting in its default state""" self.init() self.set_cmb_box(self.ui.cmbFormat, 'Dec') self.assertEqual(self.ui.spnDigits.value(), 4) self.assertEqual(qget_cmb_box(self.ui.cmbFilterType, data=False), "FIR") self.assertEqual(self.ui.ledW.text(), "16") self.assertEqual(self.ui.ledWF.text(), "15") self.assertEqual(self.ui.ledWI.text(), "0") self.assertEqual( qget_cmb_box(self.ui.cmbFormat, data=False).lower(), "dec") self.assertEqual(self.get_cmb_box(self.ui.cmbQOvfl), 'wrap') self.assertEqual(self.get_cmb_box(self.ui.cmbQuant), 'floor') self.assertEqual(self.ui.butSetZero.text(), "= 0") self.assertEqual(self.form.tblCoeff.rowCount(), 3) self.assertEqual(self.form.tblCoeff.columnCount(), 1) self.assertEqual(self.form.tblCoeff.item(0, 0).text(), "1") self.log.warning("test_fixpoint_defaults finished")
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 _set_design_method(self, enb_signal=False): """ Triggered when cmbFilterClass (cheby1, ...) is changed: - read design method fc and copy it to fb.fil[0] - create / update global filter instance fb.fil_inst of fc class - update dynamic widgets (if fc has changed and if there are any) - call load filter order """ fb.fil[0]['fc'] = fc = qget_cmb_box(self.cmbFilterClass) if fc != self.fc_last: # fc has changed: # when filter has been changed, try to destroy dynamic widgets of last fc: if self.fc_last: self._destruct_dyn_widgets() #================================================================== """ Create new instance of the selected filter class, accessible via its handle fb.fil_inst """ err = ff.fil_factory.create_fil_inst(fc) logger.debug("InputFilter.set_design_method triggered: %s\n" "Returned error code %d" % (fc, err)) #================================================================== # Check whether new design method also provides the old filter order # method. If yes, don't change it, else set first available # filter order method if fb.fil[0]['fo'] not in fb.fil_tree[self.rt][self.ft][fc].keys(): fb.fil[0].update({'fo': {}}) # explicit list(dict.keys()) needed for Python 3 fb.fil[0]['fo'] = list( fb.fil_tree[self.rt][self.ft][fc].keys())[0] # ============================================================================= # logger.debug("selFilter = %s" # "filterTree[fc] = %s" # "filterTree[fc].keys() = %s" # %(fb.fil[0], fb.fil_tree[self.rt][self.ft][fc],\ # fb.fil_tree[self.rt][self.ft][fc].keys() # )) # # ============================================================================= if hasattr( ff.fil_inst, 'construct_UI'): # construct dyn. subwidgets if available self._construct_dyn_widgets() self.fc_last = fb.fil[0]['fc'] self.load_filter_order(enb_signal)
def draw(self): self.but_fir_poles.setVisible(fb.fil[0]['ft'] == 'FIR') contour = qget_cmb_box(self.cmb_overlay) in {"contour", "contourf"} self.ledBottom.setVisible(contour) self.lblBottom.setVisible(contour) self.lblBottomdB.setVisible(contour and self.but_log.isChecked()) self.ledTop.setVisible(contour) self.lblTop.setVisible(contour) self.lblTopdB.setVisible(contour and self.but_log.isChecked()) if True: self.init_axes() self.draw_pz()
def test_defaults(self): """Test GUI setting in its default state""" self.assertEqual(self.form.spnDigits.value(), 4) self.assertEqual(self.form.ledW.text(), "16") self.assertEqual(self.form.ledWF.text(), "0") self.assertEqual(self.form.ledWI.text(), "15") self.assertEqual( qget_cmb_box(self.form.cmbFormat, data=False).lower(), "float") self.assertEqual(self.form.butSetZero.text(), "= 0") self.assertEqual(self.form.tblCoeff.rowCount(), 3) self.assertEqual(self.form.tblCoeff.columnCount(), 1) self.assertEqual(self.form.tblCoeff.item(0, 0).text(), "1")
def _update_win_fft(self, arg=None, emit=True): """ Update window type for FFT with different arguments: - signal-slot connection to combo-box -> index (int), absorbed by `arg` emit is not set -> emit=True - called by _read_param() -> empty -> emit=True - called by update_N(emit=False) """ if not isinstance(emit, bool): logger.error("update win: emit={0}".format(emit)) self.window_name = qget_cmb_box(self.cmb_win_fft, data=False) self.win = calc_window_function(self.win_dict, self.window_name, N=self.N, sym=False) n_par = self.win_dict['n_par'] self.lblWinPar1.setVisible(n_par > 0) self.ledWinPar1.setVisible(n_par > 0) self.lblWinPar2.setVisible(n_par > 1) self.ledWinPar2.setVisible(n_par > 1) if n_par > 0: self.lblWinPar1.setText( to_html(self.win_dict['par'][0]['name'] + " =", frmt='bi')) self.ledWinPar1.setText(str(self.win_dict['par'][0]['val'])) self.ledWinPar1.setToolTip(self.win_dict['par'][0]['tooltip']) if n_par > 1: self.lblWinPar2.setText( to_html(self.win_dict['par'][1]['name'] + " =", frmt='bi')) self.ledWinPar2.setText(str(self.win_dict['par'][1]['val'])) self.ledWinPar2.setToolTip(self.win_dict['par'][1]['tooltip']) self.nenbw = self.N * np.sum(np.square(self.win)) / (np.square( np.sum(self.win))) self.cgain = np.sum(self.win) / self.N # coherent gain self.win /= self.cgain # correct gain for periodic signals # only emit a signal for local triggers to prevent infinite loop: # - signal-slot connection passes a bool or an integer # - local function calls don't pass anything if emit is True: self.sig_tx.emit({'sender': __name__, 'ui_changed': 'win'}) # ... but always notify the FFT widget via sig_tx_fft self.sig_tx_fft.emit({'sender': __name__, 'view_changed': 'win'})
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_filter_cmb(self) -> str: """ (Re-)Read list of available fixpoint filters for a given filter design every time a new filter design is selected. Then try to import the fixpoint designs in the list and populate the fixpoint implementation combo box `self.cmb_fx_wdg` when successfull. Returns ------- inst_wdg_str: str string with all fixpoint widgets that could be instantiated successfully """ inst_wdg_str = "" # full names of successfully instantiated widgets for logging # remember last fx widget setting: last_fx_wdg = qget_cmb_box(self.cmb_fx_wdg, data=False) self.cmb_fx_wdg.clear() fc = fb.fil[0]['fc'] if 'fix' in fb.filter_classes[fc]: self.cmb_fx_wdg.blockSignals(True) for class_name in fb.filter_classes[fc]['fix']: # get class name try: # construct module + class name ... mod_class_name = fb.fixpoint_classes[class_name]['mod'] + '.'\ + class_name # ... and display name disp_name = fb.fixpoint_classes[class_name]['name'] self.cmb_fx_wdg.addItem(disp_name, mod_class_name) inst_wdg_str += '\t' + class_name + ' : ' + mod_class_name + '\n' except AttributeError as e: logger.warning('Widget "{0}":\n{1}'.format(class_name, e)) self.embed_fixp_img(self.no_fx_filter_img) continue # with next `class_name` of for loop except KeyError as e: logger.warning("No fixpoint filter for filter type {0} available." .format(e)) self.embed_fixp_img(self.no_fx_filter_img) continue # with next `class_name` of for loop # restore last fx widget if possible idx = self.cmb_fx_wdg.findText(last_fx_wdg) # set to idx 0 if not found (returned -1) self.cmb_fx_wdg.setCurrentIndex(max(idx, 0)) self.cmb_fx_wdg.blockSignals(False) else: # no fixpoint widget self.embed_fixp_img(self.no_fx_filter_img) self._update_fixp_widget() return inst_wdg_str
def _enable_stim_widgets(self): """ Enable / disable widgets depending on the selected stimulus""" self.stim = qget_cmb_box(self.cmbStimulus, data=False) f1_en = self.stim in { "Cos", "Sine", "Chirp", "PM", "FM", "AM", "Formula", "Rect", "Saw", "Triang", "Comb" } f2_en = self.stim in { "Cos", "Sine", "Chirp", "PM", "FM", "AM", "Formula" } dc_en = self.stim not in {"Step", "StepErr"} self.chk_stim_bl.setVisible(self.stim in {"Triang", "Saw", "Rect"}) self.lblAmp1.setVisible(self.stim != "None") self.ledAmp1.setVisible(self.stim != "None") self.chk_scale_impz_f.setVisible(self.stim == 'Pulse') self.chk_scale_impz_f.setEnabled((self.noi == 0 or self.cmbNoise.currentText() == 'None')\ and self.DC == 0) self.cmbChirpMethod.setVisible(self.stim == 'Chirp') self.lblPhi1.setVisible(f1_en) self.ledPhi1.setVisible(f1_en) self.lblPhU1.setVisible(f1_en) self.lblFreq1.setVisible(f1_en) self.ledFreq1.setVisible(f1_en) self.lblFreqUnit1.setVisible(f1_en) self.lblFreq2.setVisible(f2_en) self.ledFreq2.setVisible(f2_en) self.lblFreqUnit2.setVisible(f2_en) self.lblAmp2.setVisible(f2_en and self.stim != "Chirp") self.ledAmp2.setVisible(f2_en and self.stim != "Chirp") self.lblPhi2.setVisible(f2_en and self.stim != "Chirp") self.ledPhi2.setVisible(f2_en and self.stim != "Chirp") self.lblPhU2.setVisible(f2_en and self.stim != "Chirp") self.lblDC.setVisible(dc_en) self.ledDC.setVisible(dc_en) self.lblStimFormula.setVisible(self.stim == "Formula") self.ledStimFormula.setVisible(self.stim == "Formula") self.sig_tx.emit({'sender': __name__, 'ui_changed': 'stim'})
def process_sig_rx(self, dict_sig=None): logger.warning("sig_rx:\n{0}".format(pprint_log(dict_sig))) # check whether anything needs to be done locally # could also check here for 'quant', 'ovfl', 'WI', 'WF' (not needed at the moment) # if not, just emit the dict. if 'ui' in dict_sig: if dict_sig['wdg_name'] == 'w_coeff': # coefficient format updated """ Update coefficient quantization settings and coefficients. The new values are written to the fixpoint coefficient dict as `fb.fil[0]['fxqc']['QCB']` and `fb.fil[0]['fxqc']['b']`. """ fb.fil[0]['fxqc'].update(self.ui2dict()) elif dict_sig['wdg_name'] == 'w_accu': # accu format updated cmbW = qget_cmb_box(self.wdg_w_accu.cmbW, data=False) self.wdg_w_accu.ledWF.setEnabled(cmbW == 'man') self.wdg_w_accu.ledWI.setEnabled(cmbW == 'man') if cmbW in {'full', 'auto'}\ or ('ui' in dict_sig and dict_sig['ui'] in {'WF', 'WI'}): pass elif cmbW == 'man': # switched to manual, don't do anything return # Accu quantization or overflow settings have been changed elif dict_sig['wdg_name'] == 'q_accu': pass else: logger.error(f"Unknown widget name '{dict_sig['wdg_name']}' " f"in '{__name__}' !") return # - update fixpoint accu and coefficient quantization dict # - emit {'fx_sim': 'specs_changed'} fb.fil[0]['fxqc'].update(self.ui2dict()) self.emit({'fx_sim': 'specs_changed'}) else: logger.error( f"Unknown key '{dict_sig['wdg_name']}' (should be 'ui')" f"in '{__name__}' !")
def _freq_range(self, emit_sig_range = True): """ Set frequency plotting range for single-sided spectrum up to f_S/2 or f_S or for double-sided spectrum between -f_S/2 and f_S/2 and emit 'view_changed':'f_range'. """ rangeType = qget_cmb_box(self.cmbFRange) fb.fil[0].update({'freqSpecsRangeType':rangeType}) if rangeType == 'whole': f_lim = [0, fb.fil[0]["f_S"]] elif rangeType == 'sym': f_lim = [-fb.fil[0]["f_S"]/2., fb.fil[0]["f_S"]/2.] else: f_lim = [0, fb.fil[0]["f_S"]/2.] fb.fil[0]['freqSpecsRange'] = f_lim # store settings in dict self.sig_tx.emit({'sender':__name__, 'view_changed':'f_range'})
def _set_filter_type(self, enb_signal=False): """" Triggered when cmbFilterType (IIR, FIR, ...) is changed: - read filter type ft and copy it to fb.fil[0]['ft'] and self.ft - (re)construct design method combo, adding displayed text (e.g. "Chebyshev 1") and hidden data (e.g. "cheby1") """ # Read out current setting of comboBox and convert to string fb.fil[0]['ft'] = self.ft = qget_cmb_box(self.cmbFilterType) # logger.debug("InputFilter.set_filter_type triggered: {0}".format( self.ft)) # --------------------------------------------------------------- # Get all available design methods for new ft from fil_tree and # - Collect them in fc_list # - Rebuild design method combobox entries for new ft setting: # The combobox is populated with the "long name", # the internal name is stored in comboBox.itemData self.cmbFilterClass.blockSignals(True) self.cmbFilterClass.clear() fc_list = [] for fc in sorted(fb.fil_tree[self.rt][self.ft]): self.cmbFilterClass.addItem(fb.filter_classes[fc]['name'], fc) fc_list.append(fc) logger.debug("fc_list: {0}\n{1}".format(fc_list, fb.fil[0]['fc'])) # Does new ft also provide the previous design method (e.g. ellip)? # Has filter been instantiated? if fb.fil[0]['fc'] in fc_list and ff.fil_inst: # yes, set same fc as before fc_idx = self.cmbFilterClass.findText( fb.filter_classes[fb.fil[0]['fc']]['name']) logger.debug("fc_idx : %s", fc_idx) self.cmbFilterClass.setCurrentIndex(fc_idx) else: self.cmbFilterClass.setCurrentIndex(0) # no, set index 0 self.cmbFilterClass.blockSignals(False) self._set_design_method(enb_signal)
def _enable_stim_widgets(self): """ Enable / disable widgets depending on the selected stimulus """ self.stim = qget_cmb_box(self.cmbStimulus, data=False) stim_wdg = self.stim_wdg_dict[self.stim] self.lblDC.setVisible("dc" in stim_wdg) self.ledDC.setVisible("dc" in stim_wdg) self.chk_scale_impz_f.setVisible(self.stim == 'Impulse') self.chk_scale_impz_f.setEnabled(self.DC == 0 and (self.noi == 0 or\ self.cmbNoise.currentText() == 'None')) self.lblStimPar1.setVisible("par1" in stim_wdg) self.ledStimPar1.setVisible("par1" in stim_wdg) self.chk_stim_bl.setVisible("bl" in stim_wdg) self.lblAmp1.setVisible("a1" in stim_wdg) self.ledAmp1.setVisible("a1" in stim_wdg) self.lblPhi1.setVisible("phi1" in stim_wdg) self.ledPhi1.setVisible("phi1" in stim_wdg) self.lblPhU1.setVisible("phi1" in stim_wdg) self.lblFreq1.setVisible("f1" in stim_wdg) self.ledFreq1.setVisible("f1" in stim_wdg) self.lblFreqUnit1.setVisible("f1" in stim_wdg) self.lblAmp2.setVisible("a2" in stim_wdg) self.ledAmp2.setVisible("a2" in stim_wdg) self.lblPhi2.setVisible("phi2" in stim_wdg) self.ledPhi2.setVisible("phi2" in stim_wdg) self.lblPhU2.setVisible("phi2" in stim_wdg) self.lblFreq2.setVisible("f2" in stim_wdg) self.ledFreq2.setVisible("f2" in stim_wdg) self.lblFreqUnit2.setVisible("f2" in stim_wdg) self.lblStimFormula.setVisible(self.stim == "Formula") self.ledStimFormula.setVisible(self.stim == "Formula") self.cmbChirpMethod.setVisible(self.stim == 'Chirp') self.sig_tx.emit({'sender':__name__, 'ui_changed':'stim'})
def _update_filter_cmb(self): """ (Re-)Read list of available fixpoint filters for a given filter design every time a new filter design is selected. Then try to import the fixpoint designs in the list and populate the fixpoint implementation combo box `self.cmb_wdg_fixp` when successfull. """ inst_wdg_str = "" # full names of successfully instantiated widgets for logging last_fx_wdg = qget_cmb_box( self.cmb_wdg_fixp, data=False) # remember last fx widget setting self.cmb_wdg_fixp.clear() fc = fb.fil[0]['fc'] if 'fix' in fb.filter_classes[fc]: for class_name in fb.filter_classes[fc]['fix']: # get class name try: # construct module + class name mod_class_name = fb.fixpoint_classes[class_name][ 'mod'] + '.' + class_name disp_name = fb.fixpoint_classes[class_name][ 'name'] # # and display name self.cmb_wdg_fixp.addItem(disp_name, mod_class_name) inst_wdg_str += '\t' + class_name + ' : ' + mod_class_name + '\n' except AttributeError as e: logger.warning('Widget "{0}":\n{1}'.format(class_name, e)) self.embed_fixp_img(self.no_fx_filter_img) continue except KeyError as e: logger.warning( "No fixpoint filter for filter type {0} available.". format(e)) self.embed_fixp_img(self.no_fx_filter_img) continue # restore last fxp widget if possible idx = self.cmb_wdg_fixp.findText(last_fx_wdg) # set to idx 0 if not found (returned -1) self.cmb_wdg_fixp.setCurrentIndex(max(idx, 0)) else: # no fixpoint widget self.embed_fixp_img(self.no_fx_filter_img) return inst_wdg_str