def _init_axes_time(self): """ Clear the axes of the time domain matplotlib widgets and (re)draw the plots. """ self.plt_time_stim = qget_cmb_box(self.ui.cmb_plt_time_stim, data=False).lower() self.plt_time_resp = qget_cmb_box(self.ui.cmb_plt_time_resp, data=False).lower() plt_time = self.plt_time_resp != "none" or self.plt_time_stim != "none" self.mplwidget_t.fig.clf() # clear figure with axes if plt_time: num_subplots = 1 + (self.cmplx and self.plt_time_resp != "none") self.mplwidget_t.fig.subplots_adjust(hspace = 0.5) self.ax_r = self.mplwidget_t.fig.add_subplot(num_subplots,1 ,1) self.ax_r.clear() self.ax_r.get_xaxis().tick_bottom() # remove axis ticks on top self.ax_r.get_yaxis().tick_left() # remove axis ticks right if self.cmplx and self.plt_time_resp != "none": self.ax_i = self.mplwidget_t.fig.add_subplot(num_subplots, 1, 2, sharex = self.ax_r) self.ax_i.clear() self.ax_i.get_xaxis().tick_bottom() # remove axis ticks on top self.ax_i.get_yaxis().tick_left() # remove axis ticks right if self.ACTIVE_3D: # not implemented / tested yet self.ax3d = self.mplwidget_t.fig.add_subplot(111, projection='3d')
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']`. """ 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]))))) 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 _init_axes_freq(self): """ Clear the axes of the frequency domain matplotlib widgets and calculate the fft """ self.plt_freq_stim = qget_cmb_box(self.ui.cmb_plt_freq_stim, data=False).lower() self.plt_freq_resp = qget_cmb_box(self.ui.cmb_plt_freq_resp, data=False).lower() self.plt_freq_disabled = self.plt_freq_stim == "none" and self.plt_freq_resp == "none" if not self.ui.chk_log_freq.isChecked() and len(self.mplwidget_f.fig.get_axes()) == 2: self.mplwidget_f.fig.clear() # get rid of second axis when returning from log mode by clearing all if len(self.mplwidget_f.fig.get_axes()) == 0: # empty figure, no axes self.ax_fft = self.mplwidget_f.fig.add_subplot(111) self.ax_fft.get_xaxis().tick_bottom() # remove axis ticks on top self.ax_fft.get_yaxis().tick_left() # remove axis ticks right self.ax_fft.set_title("FFT of Transient Response") for ax in self.mplwidget_f.fig.get_axes(): # clear but don't delete all axes ax.cla() if self.ui.chk_log_freq.isChecked() and len(self.mplwidget_f.fig.get_axes()) == 1: # create second axis scaled for noise power scale if it doesn't exist yet self.ax_fft_noise = self.ax_fft.twinx() self.ax_fft_noise.is_twin = True self.calc_fft()
def _init_axes_time(self): """ Clear the axes of the time domain matplotlib widgets and (re)draw the plots. """ self.plt_time_stim = qget_cmb_box(self.ui.cmb_plt_time_stim, data=False).lower() self.plt_time_resp = qget_cmb_box(self.ui.cmb_plt_time_resp, data=False).lower() plt_time = self.plt_time_resp != "none" or self.plt_time_stim != "none" self.mplwidget_t.fig.clf() # clear figure with axes if plt_time: num_subplots = 1 + (self.cmplx and self.plt_time_resp != "none") self.mplwidget_t.fig.subplots_adjust(hspace=0.5) self.ax_r = self.mplwidget_t.fig.add_subplot(num_subplots, 1, 1) self.ax_r.clear() self.ax_r.get_xaxis().tick_bottom() # remove axis ticks on top self.ax_r.get_yaxis().tick_left() # remove axis ticks right if self.cmplx and self.plt_time_resp != "none": self.ax_i = self.mplwidget_t.fig.add_subplot(num_subplots, 1, 2, sharex=self.ax_r) self.ax_i.clear() self.ax_i.get_xaxis().tick_bottom() # remove axis ticks on top self.ax_i.get_yaxis().tick_left() # remove axis ticks right if self.ACTIVE_3D: # not implemented / tested yet self.ax3d = self.mplwidget_t.fig.add_subplot(111, projection='3d')
def _init_axes_freq(self): """ Clear the axes of the frequency domain matplotlib widgets and calculate the fft """ self.plt_freq_stim = qget_cmb_box(self.ui.cmb_plt_freq_stim, data=False).lower() self.plt_freq_resp = qget_cmb_box(self.ui.cmb_plt_freq_resp, data=False).lower() self.plt_freq_disabled = self.plt_freq_stim == "none" and self.plt_freq_resp == "none" if not self.ui.chk_log_freq.isChecked() and len( self.mplwidget_f.fig.get_axes()) == 2: self.mplwidget_f.fig.clear( ) # get rid of second axis when returning from log mode by clearing all if len(self.mplwidget_f.fig.get_axes()) == 0: # empty figure, no axes self.ax_fft = self.mplwidget_f.fig.add_subplot(111) self.ax_fft.get_xaxis().tick_bottom() # remove axis ticks on top self.ax_fft.get_yaxis().tick_left() # remove axis ticks right self.ax_fft.set_title("FFT of Transient Response") for ax in self.mplwidget_f.fig.get_axes( ): # clear but don't delete all axes ax.cla() if self.ui.chk_log_freq.isChecked() and len( self.mplwidget_f.fig.get_axes()) == 1: # create second axis scaled for noise power scale if it doesn't exist yet self.ax_fft_noise = self.ax_fft.twinx() self.ax_fft_noise.is_twin = True self.calc_fft()
def _construct_UI(self, **kwargs): """ Construct widget """ dict_ui = {'label_q':'Quant.', 'tip_q':'Select the kind of quantization.', 'cmb_q':['round', 'fix', 'floor'], 'cur_q':'round', 'label_ov':'Ovfl.', 'tip_ov':'Select overflow behaviour.', 'cmb_ov':['wrap', 'sat'], 'cur_ov':'wrap', 'enabled':True, 'visible':True } for key, val in kwargs.items(): dict_ui.update({key:val}) # dict_ui.update(map(kwargs)) # same as above? lblQuant = QLabel(dict_ui['label_q'], self) self.cmbQuant = QComboBox(self) self.cmbQuant.addItems(dict_ui['cmb_q']) qset_cmb_box(self.cmbQuant, dict_ui['cur_q']) self.cmbQuant.setToolTip(dict_ui['tip_q']) lblOvfl = QLabel(dict_ui['label_ov'], self) self.cmbOvfl = QComboBox(self) self.cmbOvfl.addItems(dict_ui['cmb_ov']) qset_cmb_box(self.cmbOvfl, dict_ui['cur_ov']) self.cmbOvfl.setToolTip(dict_ui['tip_ov']) # ComboBox size is adjusted automatically to fit the longest element self.cmbQuant.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbOvfl.setSizeAdjustPolicy(QComboBox.AdjustToContents) layH = QHBoxLayout() layH.addWidget(lblOvfl) layH.addWidget(self.cmbOvfl) layH.addStretch() layH.addWidget(lblQuant) layH.addWidget(self.cmbQuant) layH.setContentsMargins(0,0,0,0) frmMain = QFrame(self) frmMain.setLayout(layH) layVMain = QVBoxLayout() # Widget main layout layVMain.addWidget(frmMain) layVMain.setContentsMargins(5,0,0,0)#*params['wdg_margins']) self.setLayout(layVMain) #---------------------------------------------------------------------- # INITIAL SETTINGS #---------------------------------------------------------------------- self.ovfl = qget_cmb_box(self.cmbOvfl, data=False) self.quant = qget_cmb_box(self.cmbQuant, data=False) frmMain.setEnabled(dict_ui['enabled']) frmMain.setVisible(dict_ui['visible']) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- self.cmbOvfl.currentIndexChanged.connect(self.save_ui) self.cmbQuant.currentIndexChanged.connect(self.save_ui)
def _update_time_freq(self): """ Trigger 'draw' when one of the comboboxes PltTime or PltFreq is modified, enable frequency domain controls only when needed """ self.plt_time = qget_cmb_box(self.cmbPltTime, data=False) self.plt_freq = qget_cmb_box(self.cmbPltFreq, data=False) self.wdgHControlsF.setVisible(self.plt_freq != "None") self.sig_tx.emit({'sender': __name__, 'view_changed': ''})
def _W_changed(self): """ Set fractional and integer length `WF` and `WI` when wordlength Ẁ` has been changed. Try to preserve `WI` or `WF` settings depending on the number format (integer or fractional). """ # if self.ui.ledW.isModified() ... self.ui.ledW.setModified(False) W = safe_eval(self.ui.ledW.text(), self.myQ.W, return_type='int', sign='pos') if W < 2: logger.warn("W must be > 1, restoring previous value.") W = self.myQ.W # fall back to previous value self.ui.ledW.setText(str(W)) if qget_cmb_box(self.ui.cmbQFrmt) == 'qint': # integer format, preserve WI bits WI = W - self.myQ.WF - 1 self.ui.ledWI.setText(str(WI)) self.ui.ledScale.setText(str(1 << (W-1))) else: # fractional format, preserve WF bit setting WF = W - self.myQ.WI - 1 if WF < 0: self.ui.ledWI.setText(str(W - 1)) WF = 0 self.ui.ledWF.setText(str(WF)) self._store_q_settings() self._refresh_table()
def _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 _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 _set_number_format(self): """ Set one of three number formats: Integer, fractional, normalized fractional """ qfrmt = qget_cmb_box(self.ui.cmbQFrmt) is_qfrac = False W = safe_eval(self.ui.ledW.text(), self.myQ.W, return_type='int', sign='pos') if qfrmt == 'qint': self.ui.ledWI.setText(str(W - 1)) self.ui.ledWF.setText("0") self.ui.ledScale.setText(str(1 << (W-1))) elif qfrmt == 'qnfrac': # normalized fractional format self.ui.ledWI.setText("0") self.ui.ledWF.setText(str(W - 1)) self.ui.ledScale.setText("1") else: # qfrmt == 'qfrac': is_qfrac = True self.ui.ledWI.setEnabled(is_qfrac) self.ui.lblDot.setEnabled(is_qfrac) self.ui.ledWF.setEnabled(is_qfrac) self.ui.ledW.setEnabled(not is_qfrac) self.ui.ledScale.setEnabled(is_qfrac) self._store_q_settings() self._refresh_table()
def _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"} f2_en = self.stim in {"Cos", "Sine"} 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__, 'data_changed': 'stim'})
def _set_number_format(self): """ Set one of three number formats: Integer, fractional, normalized fractional """ qfrmt = qget_cmb_box(self.ui.cmbQFrmt) is_qfrac = False W = safe_eval(self.ui.ledW.text(), self.myQ.W, return_type='int', sign='pos') if qfrmt == 'qint': self.ui.ledWI.setText(str(W - 1)) self.ui.ledWF.setText("0") self.ui.ledScale.setText(str(1 << (W-1))) elif qfrmt == 'qnfrac': # normalized fractional format self.ui.ledWI.setText("0") self.ui.ledWF.setText(str(W - 1)) self.ui.ledScale.setText("1") else: # qfrmt == 'qfrac': is_qfrac = True self.ui.ledWI.setEnabled(is_qfrac) self.ui.lblDot.setEnabled(is_qfrac) self.ui.ledWF.setEnabled(is_qfrac) self.ui.ledW.setEnabled(not is_qfrac) self.ui.ledScale.setEnabled(is_qfrac) self._store_q_settings() self._refresh_table()
def _W_changed(self): """ Set fractional and integer length `WF` and `WI` when wordlength Ẁ` has been changed. Try to preserve `WI` or `WF` settings depending on the number format (integer or fractional). """ # if self.ui.ledW.isModified() ... self.ui.ledW.setModified(False) W = safe_eval(self.ui.ledW.text(), self.myQ.W, return_type='int', sign='pos') if W < 2: logger.warn("W must be > 1, restoring previous value.") W = self.myQ.W # fall back to previous value self.ui.ledW.setText(str(W)) if qget_cmb_box(self.ui.cmbQFrmt) == 'qint': # integer format, preserve WI bits WI = W - self.myQ.WF - 1 self.ui.ledWI.setText(str(WI)) self.ui.ledScale.setText(str(1 << (W-1))) else: # fractional format, preserve WF bit setting WF = W - self.myQ.WI - 1 if WF < 0: self.ui.ledWI.setText(str(W - 1)) WF = 0 self.ui.ledWF.setText(str(WF)) self._store_q_settings() self._refresh_table()
def _set_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_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 _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='pos') 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__, 'data_changed': 'noi'})
def _set_number_format(self): """ 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 _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'], 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_w_coeffs.sig_tx.connect(self.update_q_coeff) # self.wdg_w_coeffs.setEnabled(False) # 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='', fractional=True, combo_visible=True) self.wdg_w_accu.sig_tx.connect(self.process_sig_rx) self.wdg_q_accu = UI_Q(self, fb.fil[0]['fxqc']['QA'], label='Accu Format <i>Q<sub>A </sub></i>:') self.wdg_q_accu.sig_tx.connect(self.process_sig_rx) # 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') #------------------------------------------------------------------------------ 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 _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 _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.sigUnitChanged.emit() # -> input_widgets
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.sigUnitChanged.emit() # -> input_widgets
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 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 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 __init__(self, parent): super(FilterPZ, self).__init__(parent) self.Hmax_last = 1 # initial setting for maximum gain self.angle_char = "<" # "∠" may give problems with some encodings self.angle_char = unichr_23(int('2220', 16)) self.ui = FilterPZ_UI(self) # create the UI part with buttons etc. self.norm_last = qget_cmb_box(self.ui.cmbNorm, data=False) # initial setting of cmbNorm self._construct_UI() # construct the rest of the UI
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_S2 = fb.fil[0]['f_S'] / 2. #========= select frequency range to be displayed ===================== #=== shift, scale and select: W -> F, H_cplx -> H_c F = self.W * f_S2 / 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_S2 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.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 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_S2 = fb.fil[0]['f_S'] / 2. #========= select frequency range to be displayed ===================== #=== shift, scale and select: W -> F, H_cplx -> H_c F = self.W * f_S2 / 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_S2 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.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_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 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 _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 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 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, 'wdg'): # construct dyn. subwidgets if available self._construct_dyn_widgets() else: self.frmDynWdg.setVisible(False) # no subwidget, hide empty frame self.fc_last = fb.fil[0]['fc'] self.load_filter_order(enb_signal)
def __init__(self, parent): super(FilterPZ, self).__init__(parent) self.Hmax_last = 1 # initial setting for maximum gain self.angle_char = "<" # "∠" may give problems with some encodings self.angle_char = unichr_23(int('2220', 16)) self.ui = FilterPZ_UI(self) # create the UI part with buttons etc. self.norm_last = qget_cmb_box(self.ui.cmbNorm, data=False) # initial setting of cmbNorm self._construct_UI() # construct the rest of the UI self.load_dict() # initialize table from filterbroker self._refresh_table() # initialize table with values self.setup_signal_slot() # setup signal-slot connections and eventFilters
def fx_select(self): """ Select between fixpoint and floating point simulation """ self.sim_select = qget_cmb_box(self.ui.cmb_sim_select, data=False) self.fx_sim = (self.sim_select == 'Fixpoint') self.ui.but_run.setVisible(self.fx_sim) self.ui.chk_fx_scale.setVisible(self.fx_sim) self.ui.chk_fx_range.setVisible(self.fx_sim) self.hdl_dict = None if self.fx_sim: qstyle_widget(self.ui.but_run, "changed") self.fx_run() else: self.draw()
def fx_select(self): """ Select between fixpoint and floating point simulation """ self.sim_select = qget_cmb_box(self.ui.cmb_sim_select, data=False) self.fx_sim = (self.sim_select == 'Fixpoint') self.ui.but_run.setVisible(self.fx_sim) self.ui.chk_fx_scale.setVisible(self.fx_sim) self.ui.chk_fx_range.setVisible(self.fx_sim) self.hdl_dict = None if self.fx_sim: qstyle_widget(self.ui.but_run, "changed") self.fx_run() else: self.draw()
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 _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"} f2_en = self.stim in {"Cos", "Sine"} a2_en = self.stim in {"Cos", "Sine"} dc_en = self.stim not in {"Step", "StepErr"} 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(a2_en) self.ledAmp2.setVisible(a2_en) self.lblDC.setVisible(dc_en) self.ledDC.setVisible(dc_en) self.sig_tx.emit({'sender':__name__, 'data_changed':'stim'})
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 _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='pos') 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>") self.sig_tx.emit({'sender':__name__, 'data_changed':'noi'})
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. "Chebychev 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.fil_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.fil_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 _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 sigUnitChanged signal """ 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.sigUnitChanged.emit() # -> input_widgets
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 sigUnitChanged signal """ 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.sigUnitChanged.emit() # -> input_widgets
def _update_win_fft(self, dict_sig=None): """ Update window type for FFT """ def _update_param1(): self.lblWinPar1.setText( to_html(self.win_dict['par'][0][0] + " =", frmt='bi')) self.ledWinPar1.setText(str(self.win_dict['par'][2][0])) self.ledWinPar1.setToolTip(self.win_dict['par'][4][0]) def _update_param2(): self.lblWinPar2.setText( to_html(self.win_dict['par'][0][1] + " =", frmt='bi')) self.ledWinPar2.setText(str(self.win_dict['par'][2][1])) self.ledWinPar2.setToolTip(self.win_dict['par'][4][1]) #------------------------------------------------------------------------------ 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: _update_param1() if n_par > 1: _update_param2() self.nenbw = self.N * np.sum(np.square(self.win)) / (np.square( np.sum(self.win))) self.scale = self.N / np.sum(self.win) self.win *= self.scale # correct gain for periodic signals (coherent gain) if not dict_sig or type(dict_sig) != dict: self.sig_tx.emit({'sender': __name__, 'data_changed': 'win'})
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. "Chebychev 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.fil_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.fil_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 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 'ui' in dict_sig: if 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__}) self.sig_tx.emit(dict_sig)
def __init__(self, parent): super(Input_PZ, self).__init__(parent) self.data_changed = True # initialize flag: filter data has been changed self.ui_changed = True # initialize flag: ui for csv options has been changed self.Hmax_last = 1 # initial setting for maximum gain self.angle_char = "<" # "∠" may give problems with some encodings self.angle_char = unichr_23(int('2220', 16)) self.tab_label = "P/Z" self.tool_tip = "Display and edit filter poles and zeros." self.ui = Input_PZ_UI(self) # create the UI part with buttons etc. self.norm_last = qget_cmb_box(self.ui.cmbNorm, data=False) # initial setting of cmbNorm self._construct_UI() # construct the rest of the UI self.load_dict() # initialize table from filterbroker self._refresh_table() # initialize table with values self.setup_signal_slot() # setup signal-slot connections and eventFilters
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 __init__(self, parent): super(Input_PZ, self).__init__(parent) self.data_changed = True # initialize flag: filter data has been changed self.ui_changed = True # initialize flag: ui for csv options has been changed self.Hmax_last = 1 # initial setting for maximum gain self.angle_char = "\u2220" self.tab_label = "P/Z" self.tool_tip = "Display and edit filter poles and zeros." self.ui = Input_PZ_UI(self) # create the UI part with buttons etc. self.norm_last = qget_cmb_box(self.ui.cmbNorm, data=False) # initial setting of cmbNorm self._construct_UI() # construct the rest of the UI self.load_dict() # initialize table from filterbroker self._refresh_table() # initialize table with values self.setup_signal_slot( ) # setup signal-slot connections and eventFilters
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)) continue except KeyError as e: logger.warning( "No fixpoint filter for filter type {0} available.". format(e)) 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)) return inst_wdg_str
def _update_fixp_widget(self): """ This method is called at the initialization of the ``input_fixpoint_specs`` widget and when a new fixpoint filter implementation is selected in the combo box: - Destruct old instance of fixpoint filter widget - Import and instantiate new fixpoint filter widget e.g. after changing the filter topology - Try to load image for filter topology - Update the UI of the widget - Instantiate ``self.hdl_filter_inst = self.fx_wdg_inst.hdlfilter`` """ # destruct old fixpoint widget instance if hasattr(self, "fx_wdg_inst"): # is a fixpoint widget loaded? try: self.layHWdg.removeWidget(self.fx_wdg_inst) # remove widget from layout self.fx_wdg_inst.deleteLater() # delete QWidget when scope has been left except AttributeError as e: logger.error("Could not destruct_UI!\n{0}".format(e)) # instantiate new fixpoint widget class as self.fx_wdg_inst # which instantiates filter class as self.fx_wdg_inst.hdlfilter cmb_wdg_fx_cur = qget_cmb_box(self.cmb_wdg_fixp, data=False) if cmb_wdg_fx_cur: # at least one valid fixpoint widget found self.fx_wdg_found = True fx_mod_name = qget_cmb_box(self.cmb_wdg_fixp, data=True) # module name and path fx_mod = importlib.import_module(fx_mod_name) # get module fx_wdg_class = getattr(fx_mod, cmb_wdg_fx_cur) # get class self.fx_wdg_inst = fx_wdg_class(self) # instantiate the widget self.layHWdg.addWidget(self.fx_wdg_inst, stretch=1) # and add it to layout self.wdg_dict2ui() # initialize the fixpoint subwidgets from the fxqc_dict #---- connect signals to fx_wdg_inst ---- if hasattr(self.fx_wdg_inst, "sig_rx"): self.sig_rx.connect(self.fx_wdg_inst.sig_rx) #if hasattr(self.fx_wdg_inst, "sig_tx"): #self.fx_wdg_inst.sig_tx.connect(self.sig_rx) #---- instantiate and scale graphic of filter topology ---- if not (hasattr(self.fx_wdg_inst, "img_name") and self.fx_wdg_inst.img_name): # is an image name defined? self.fx_wdg_inst.img_name = "no_img.png" # check whether file exists file_path = os.path.dirname(fx_mod.__file__) # get path of imported fixpoint widget and img_file = os.path.join(file_path, self.fx_wdg_inst.img_name) # construct full image name from it # _, file_extension = os.path.splitext(self.fx_wdg_inst.img_name) if os.path.exists(img_file): self.img_fixp = QPixmap(img_file) # if file_extension == '.png': # self.img_fixp = QPixmap(img_file) # elif file_extension == '.svg': # self.img_fixp = QtSvg.QSvgWidget(img_file) else: logger.warning("Image file {0} doesn't exist.".format(img_file)) img_file = os.path.join(file_path, "hdl_dummy.png") self.img_fixp = QPixmap(img_file) #self.lbl_img_fixp.setPixmap(QPixmap(self.img_fixp)) # fixed size self.resize_img() self.lblTitle.setText(self.fx_wdg_inst.title) #--- try to reference Python fixpoint filter instance ----- if hasattr(self.fx_wdg_inst,'fxpy_filter'): self.fxpy_filter_inst = self.fx_wdg_inst.fxpy_filter self.butSimFxPy.setEnabled(True) else: self.butSimFxPy.setEnabled(False) #--- try to reference HDL fixpoint filter instance ----- if hasattr(self.fx_wdg_inst,'hdlfilter'): self.hdl_filter_inst = self.fx_wdg_inst.hdlfilter self.butExportHDL.setEnabled(hasattr(self.fx_wdg_inst.hdlfilter, "convert")) self.butSimHDL.setEnabled(hasattr(self.fx_wdg_inst.hdlfilter, "run_sim")) self.update_fxqc_dict() #self.fx_wdg_inst.update_hdl_filter() else: self.hdl_filter_inst = None self.butSimHDL.setEnabled(False) self.butExportHDL.setEnabled(False) else: self.fx_wdg_found = False self.butSimFxPy.setEnabled(False) self.butSimHDL.setEnabled(False) self.butExportHDL.setEnabled(False)
def _construct_UI(self): """ Construct UI with comboboxes for selecting filter: - cmbResponseType for selecting response type rt (LP, HP, ...) - cmbFilterType for selection of filter type (IIR, FIR, ...) - cmbFilterClass for selection of design design class (Chebychev, ...) and populate them from the "filterTree" dict during the initial run. Later, calling _set_response_type() updates the three combo boxes. See filterbroker.py for structure and content of "filterTree" dict """ #---------------------------------------------------------------------- # Combo boxes for filter selection #---------------------------------------------------------------------- self.cmbResponseType = QComboBox(self) self.cmbResponseType.setObjectName("comboResponseType") self.cmbResponseType.setToolTip("Select filter response type.") self.cmbFilterType = QComboBox(self) self.cmbFilterType.setObjectName("comboFilterType") self.cmbFilterType.setToolTip( "<span>Select the filter type (recursive (Infinite Impulse Response), nonRecursive (Finite IR).</span>") self.cmbFilterClass = QComboBox(self) self.cmbFilterClass.setObjectName("comboFilterClass") self.cmbFilterClass.setToolTip("Select the filter design class.") # Adapt comboboxes size dynamically to largest element self.cmbResponseType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbFilterClass.setSizeAdjustPolicy(QComboBox.AdjustToContents) #---------------------------------------------------------------------- # Populate combo box with initial settings from fb.fil_tree #---------------------------------------------------------------------- # Translate short response type ("LP") to displayed names ("Lowpass") # (correspondence is defined in pyfda_rc.py) and populate rt combo box # rt_list = sorted(list(fb.fil_tree.keys())) for rt in rt_list: try: self.cmbResponseType.addItem(rc.rt_names[rt], rt) except KeyError as e: logger.warning( "KeyError: {0} has no corresponding full name in rc.rt_names.".format(e)) idx = self.cmbResponseType.findData('LP') # find index for 'LP' if idx == -1: # Key 'LP' does not exist, use first entry instead idx = 0 self.cmbResponseType.setCurrentIndex(idx) # set initial index rt = qget_cmb_box(self.cmbResponseType) for ft in fb.fil_tree[rt]: self.cmbFilterType.addItem(rc.ft_names[ft], ft) self.cmbFilterType.setCurrentIndex(0) # set initial index ft = qget_cmb_box(self.cmbFilterType) for fc in fb.fil_tree[rt][ft]: self.cmbFilterClass.addItem(fb.fil_classes[fc]['name'], fc) self.cmbFilterClass.setCurrentIndex(0) # set initial index #---------------------------------------------------------------------- # Layout for Filter Type Subwidgets #---------------------------------------------------------------------- layHFilWdg = QHBoxLayout() # container for filter subwidgets layHFilWdg.addWidget(self.cmbResponseType) #LP, HP, BP, etc. layHFilWdg.addStretch() layHFilWdg.addWidget(self.cmbFilterType) #FIR, IIR layHFilWdg.addStretch() layHFilWdg.addWidget(self.cmbFilterClass) #bessel, elliptic, etc. #---------------------------------------------------------------------- # Layout for dynamic filter subwidgets (empty frame) #---------------------------------------------------------------------- # see Summerfield p. 278 self.layHDynWdg = QHBoxLayout() # for additional dynamic subwidgets #---------------------------------------------------------------------- # Filter Order Subwidgets #---------------------------------------------------------------------- self.lblOrder = QLabel("<b>Order:</b>") self.chkMinOrder = QCheckBox("Minimum", self) self.chkMinOrder.setToolTip("<span>Minimum filter order / # of taps is determined automatically.</span>") self.lblOrderN = QLabel("<b><i>N =</i></b>") self.ledOrderN = QLineEdit(str(fb.fil[0]['N']),self) self.ledOrderN.setToolTip("Filter order (# of taps - 1).") #-------------------------------------------------- # Layout for filter order subwidgets layHOrdWdg = QHBoxLayout() layHOrdWdg.addWidget(self.lblOrder) layHOrdWdg.addWidget(self.chkMinOrder) layHOrdWdg.addStretch() layHOrdWdg.addWidget(self.lblOrderN) layHOrdWdg.addWidget(self.ledOrderN) #---------------------------------------------------------------------- # OVERALL LAYOUT (stack standard + dynamic subwidgets vertically) #---------------------------------------------------------------------- self.layVAllWdg = QVBoxLayout() self.layVAllWdg.addLayout(layHFilWdg) self.layVAllWdg.addLayout(self.layHDynWdg) self.layVAllWdg.addLayout(layHOrdWdg) #============================================================================== frmMain = QFrame(self) frmMain.setLayout(self.layVAllWdg) layHMain = QHBoxLayout() layHMain.addWidget(frmMain) layHMain.setContentsMargins(*rc.params['wdg_margins']) self.setLayout(layHMain) #============================================================================== #------------------------------------------------------------ # SIGNALS & SLOTS #------------------------------------------------------------ # Connect comboBoxes and setters, propgate change events hierarchically # through all widget methods and generate the signal sigFiltChanged # in the end. self.cmbResponseType.currentIndexChanged.connect( lambda: self._set_response_type(enb_signal=True))# 'LP' self.cmbFilterType.currentIndexChanged.connect( lambda: self._set_filter_type(enb_signal=True)) #'IIR' self.cmbFilterClass.currentIndexChanged.connect( lambda: self._set_design_method(enb_signal=True))#'cheby1' self.chkMinOrder.clicked.connect( lambda: self._set_filter_order(enb_signal=True)) # Min. Order self.ledOrderN.editingFinished.connect( lambda:self._set_filter_order(enb_signal=True)) # Manual Order
def _update_win_fft(self, dict_sig=None): """ Update window type for FFT """ def _update_param1(): self.ledWinPar1.setToolTip(tooltip) self.lblWinPar1.setText(to_html(txt_par1, frmt='bi')) self.param1 = safe_eval(self.ledWinPar1.text(), self.param1, return_type='float', sign='pos') self.ledWinPar1.setText(str(self.param1)) #---------------------------------------------------------------------- self.window_type = qget_cmb_box(self.cmb_win_fft, data=False) # self.param1 = None has_par1 = False txt_par1 = "" if self.window_type in {"Bartlett", "Triangular"}: window_name = "bartlett" elif self.window_type == "Flattop": window_name = "flattop" elif self.window_type == "Hamming": window_name = "hamming" elif self.window_type == "Hann": window_name = "hann" elif self.window_type == "Rect": window_name = "boxcar" elif self.window_type == "Kaiser": window_name = "kaiser" has_par1 = True txt_par1 = 'β =' tooltip = ("<span>Shape parameter; lower values reduce main lobe width, " "higher values reduce side lobe level, typ. value is 5.</span>") _update_param1() if not self.param1: self.param1 = 5 elif self.window_type == "Chebwin": window_name = "chebwin" has_par1 = True txt_par1 = 'Attn =' tooltip = ("<span>Side lobe attenuation in dB (typ. 80 dB).</span>") _update_param1() if not self.param1: self.param1 = 80 if self.param1 < 45: logger.warning("Attenuation needs to be larger than 45 dB!") else: logger.error("Unknown window type {0}".format(self.window_type)) # get attribute window_name from submodule sig.windows and # returning the desired window function: win_fnct = getattr(sig.windows, window_name, None) if not win_fnct: logger.error("No window function {0} in scipy.signal.windows, using rectangular window instead!"\ .format(window_name)) win_fnct = sig.windows.boxcar self.param1 = None self.lblWinPar1.setVisible(has_par1) self.ledWinPar1.setVisible(has_par1) if has_par1: self.win = win_fnct(self.N, self.param1) # use additional parameter else: self.win = win_fnct(self.N) self.nenbw = self.N * np.sum(np.square(self.win)) / (np.square(np.sum(self.win))) self.scale = self.N / np.sum(self.win) self.win *= self.scale # correct gain for periodic signals (coherent gain) if not dict_sig or type(dict_sig) != dict: self.sig_tx.emit({'sender':__name__, 'data_changed':'win'}) else: self.sig_tx.emit(dict_sig)
def draw_3d(self): """ Draw various 3D plots """ self.init_axes() bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] zz = np.array(fb.fil[0]['zpk'][0]) pp = np.array(fb.fil[0]['zpk'][1]) wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' # not used f_S = fb.fil[0]['f_S'] N_FFT = params['N_FFT'] alpha = self.diaAlpha.value()/10. cmap = cm.get_cmap(str(self.cmbColormap.currentText())) # Number of Lines /step size for H(f) stride, mesh, contour3d: stride = 10 - self.diaHatch.value() NL = 3 * self.diaHatch.value() + 5 surf_enabled = qget_cmb_box(self.cmbMode3D, data=False) in {'Surf', 'Contour'} self.cmbColormap.setEnabled(surf_enabled) self.chkColormap_r.setEnabled(surf_enabled) self.chkLighting.setEnabled(surf_enabled) self.chkColBar.setEnabled(surf_enabled) self.diaAlpha.setEnabled(surf_enabled or self.chkContour2D.isChecked()) #cNorm = colors.Normalize(vmin=0, vmax=values[-1]) #scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet) #----------------------------------------------------------------------------- # Calculate H(w) along the upper half of unity circle #----------------------------------------------------------------------------- [w, H] = sig.freqz(bb, aa, worN=N_FFT, whole=True) H = np.nan_to_num(H) # replace nans and inf by finite numbers H_abs = abs(H) H_max = max(H_abs) H_min = min(H_abs) #f = w / (2 * pi) * f_S # translate w to absolute frequencies #F_min = f[np.argmin(H_abs)] plevel_rel = 1.05 # height of plotted pole position relative to zmax zlevel_rel = 0.1 # height of plotted zero position relative to zmax if self.chkLog.isChecked(): # logarithmic scale bottom = np.floor(max(self.zmin_dB, 20*log10(H_min)) / 10) * 10 top = self.zmax_dB top_bottom = top - bottom zlevel = bottom - top_bottom * zlevel_rel if self.cmbMode3D.currentText() == 'None': # "Poleposition" for H(f) plot only plevel_top = 2 * bottom - zlevel # height of displayed pole position plevel_btm = bottom else: plevel_top = top + top_bottom * (plevel_rel - 1) plevel_btm = top else: # linear scale bottom = max(self.zmin, H_min) # min. display value top = self.zmax # max. display value top_bottom = top - bottom # top = zmax_rel * H_max # calculate display top from max. of H(f) zlevel = bottom + top_bottom * zlevel_rel # height of displayed zero position if self.cmbMode3D.currentText() == 'None': # "Poleposition" for H(f) plot only #H_max = np.clip(max(H_abs), 0, self.zmax) # make height of displayed poles same to zeros plevel_top = bottom + top_bottom * zlevel_rel plevel_btm = bottom else: plevel_top = plevel_rel * top plevel_btm = top # calculate H(jw)| along the unity circle and |H(z)|, each clipped # between bottom and top H_UC = H_mag(bb, aa, self.xy_UC, top, H_min=bottom, log=self.chkLog.isChecked()) Hmag = H_mag(bb, aa, self.z, top, H_min=bottom, log=self.chkLog.isChecked()) #=============================================================== ## plot Unit Circle (UC) #=============================================================== if self.chkUC.isChecked(): # Plot unit circle and marker at (1,0): self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, ones(len(self.xy_UC)) * bottom, lw=2, color='k') self.ax3d.plot([0.97, 1.03], [0, 0], [bottom, bottom], lw=2, color='k') #=============================================================== ## plot ||H(f)| along unit circle as 3D-lineplot #=============================================================== if self.chkHf.isChecked(): self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, H_UC, alpha = 0.5) # draw once more as dashed white line to improve visibility self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, H_UC, 'w--') if stride < 10: # plot thin vertical line every stride points on the UC for k in range(len(self.xy_UC[::stride])): self.ax3d.plot([self.xy_UC.real[::stride][k], self.xy_UC.real[::stride][k]], [self.xy_UC.imag[::stride][k], self.xy_UC.imag[::stride][k]], [np.ones(len(self.xy_UC[::stride]))[k]*bottom, H_UC[::stride][k]], linewidth=1, color=(0.5, 0.5, 0.5)) #=============================================================== ## plot Poles and Zeros #=============================================================== if self.chkPZ.isChecked(): PN_SIZE = 8 # size of P/N symbols # Plot zero markers at |H(z_i)| = zlevel with "stems": self.ax3d.plot(zz.real, zz.imag, ones(len(zz)) * zlevel, 'o', markersize=PN_SIZE, markeredgecolor='blue', markeredgewidth=2.0, markerfacecolor='none') for k in range(len(zz)): # plot zero "stems" self.ax3d.plot([zz[k].real, zz[k].real], [zz[k].imag, zz[k].imag], [bottom, zlevel], linewidth=1, color='b') # Plot the poles at |H(z_p)| = plevel with "stems": self.ax3d.plot(np.real(pp), np.imag(pp), plevel_top, 'x', markersize=PN_SIZE, markeredgewidth=2.0, markeredgecolor='red') for k in range(len(pp)): # plot pole "stems" self.ax3d.plot([pp[k].real, pp[k].real], [pp[k].imag, pp[k].imag], [plevel_btm, plevel_top], linewidth=1, color='r') #=============================================================== ## 3D-Plots of |H(z)| clipped between |H(z)| = top #=============================================================== m_cb = cm.ScalarMappable(cmap=cmap) # normalized proxy object that is mappable m_cb.set_array(Hmag) # for colorbar #--------------------------------------------------------------- ## 3D-mesh plot #--------------------------------------------------------------- if self.cmbMode3D.currentText() == 'Mesh': # fig_mlab = mlab.figure(fgcolor=(0., 0., 0.), bgcolor=(1, 1, 1)) # self.ax3d.set_zlim(0,2) self.ax3d.plot_wireframe(self.x, self.y, Hmag, rstride=5, cstride=stride, linewidth=1, color='gray') #--------------------------------------------------------------- ## 3D-surface plot #--------------------------------------------------------------- # http://stackoverflow.com/questions/28232879/phong-shading-for-shiny-python-3d-surface-plots elif self.cmbMode3D.currentText() == 'Surf': if MLAB: ## Mayavi surf = mlab.surf(self.x, self.y, H_mag, colormap='RdYlBu', warp_scale='auto') # Change the visualization parameters. surf.actor.property.interpolation = 'phong' surf.actor.property.specular = 0.1 surf.actor.property.specular_power = 5 # s = mlab.contour_surf(self.x, self.y, Hmag, contour_z=0) mlab.show() else: if self.chkLighting.isChecked(): ls = LightSource(azdeg=0, altdeg=65) # Create light source object rgb = ls.shade(Hmag, cmap=cmap) # Shade data, creating an rgb array cmap_surf = None else: rgb = None cmap_surf = cmap # s = self.ax3d.plot_surface(self.x, self.y, Hmag, # alpha=OPT_3D_ALPHA, rstride=1, cstride=1, cmap=cmap, # linewidth=0, antialiased=False, shade=True, facecolors = rgb) # s.set_edgecolor('gray') s = self.ax3d.plot_surface(self.x, self.y, Hmag, alpha=alpha, rstride=1, cstride=1, linewidth=0, antialiased=False, facecolors=rgb, cmap=cmap_surf, shade=True) s.set_edgecolor(None) #--------------------------------------------------------------- ## 3D-Contour plot #--------------------------------------------------------------- elif self.cmbMode3D.currentText() == 'Contour': s = self.ax3d.contourf3D(self.x, self.y, Hmag, NL, alpha=alpha, cmap=cmap) #--------------------------------------------------------------- ## 2D-Contour plot # TODO: 2D contour plots do not plot correctly together with 3D plots in # current matplotlib 1.4.3 -> disable them for now # TODO: zdir = x / y delivers unexpected results -> rather plot max(H) # along the other axis? # TODO: colormap is created depending on the zdir = 'z' contour plot # -> set limits of (all) other plots manually? if self.chkContour2D.isChecked(): # self.ax3d.contourf(x, y, Hmag, 20, zdir='x', offset=xmin, # cmap=cmap, alpha = alpha)#, vmin = bottom)#, vmax = top, vmin = bottom) # self.ax3d.contourf(x, y, Hmag, 20, zdir='y', offset=ymax, # cmap=cmap, alpha = alpha)#, vmin = bottom)#, vmax = top, vmin = bottom) s = self.ax3d.contourf(self.x, self.y, Hmag, NL, zdir='z', offset=bottom - (top - bottom) * 0.05, cmap=cmap, alpha=alpha) # plot colorbar for suitable plot modes if self.chkColBar.isChecked() and (self.chkContour2D.isChecked() or str(self.cmbMode3D.currentText()) in {'Contour', 'Surf'}): self.colb = self.mplwidget.fig.colorbar(m_cb, ax=self.ax3d, shrink=0.8, aspect=20, pad=0.02, fraction=0.08) #---------------------------------------------------------------------- ## Set view limits and labels #---------------------------------------------------------------------- if not self.mplwidget.mplToolbar.a_lk.isChecked(): self.ax3d.set_xlim3d(self.xmin, self.xmax) self.ax3d.set_ylim3d(self.ymin, self.ymax) self.ax3d.set_zlim3d(bottom, top) else: self._restore_axes() self.ax3d.set_xlabel('Re')#(fb.fil[0]['plt_fLabel']) self.ax3d.set_ylabel('Im') #(r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega}) / T_S \; \rightarrow $') # self.ax3d.set_zlabel(r'$|H(z)|\; \rightarrow $') self.ax3d.set_title(r'3D-Plot of $|H(\mathrm{e}^{\mathrm{j} \Omega})|$ and $|H(z)|$') self.redraw()
def _refresh_table(self): """ (Re-)Create the displayed table from `self.ba` (list with 2 columns of float scalars). Data is displayed via `ItemDelegate.displayText()` in the number format set by `self.frmt`. The table dimensions are set according to the dimensions of `self.ba`: - self.ba[0] -> b coefficients - self.ba[1] -> a coefficients Called at the end of nearly every method. """ try: self.num_rows = max(len(self.ba[1]), len(self.ba[0])) except IndexError: self.num_rows = len(self.ba[0]) logger.debug("np.shape(ba) = {0}".format(np.shape(self.ba))) params['FMT_ba'] = int(self.ui.spnDigits.text()) # When format is 'float', disable all fixpoint options is_float = (qget_cmb_box(self.ui.cmbFormat, data=False).lower() == 'float') self.ui.spnDigits.setVisible(is_float) # number of digits can only be selected self.ui.lblDigits.setVisible(is_float) # for format = 'float' self.ui.cmbQFrmt.setVisible(not is_float) # hide unneeded widgets for format = 'float' self.ui.lbl_W.setVisible(not is_float) self.ui.ledW.setVisible(not is_float) if self.ui.butEnable.isChecked(): self.ui.frmQSettings.setVisible(not is_float) # hide all q-settings for float self.ui.butEnable.setIcon(QIcon(':/circle-x.svg')) self.tblCoeff.setVisible(True) self._store_q_settings() # store updated quantization / format settings # check whether filter is FIR and only needs one column if fb.fil[0]['ft'] == 'FIR': self.num_cols = 1 self.tblCoeff.setColumnCount(1) self.tblCoeff.setHorizontalHeaderLabels(["b"]) qset_cmb_box(self.ui.cmbFilterType, 'FIR') else: self.num_cols = 2 self.tblCoeff.setColumnCount(2) self.tblCoeff.setHorizontalHeaderLabels(["b", "a"]) qset_cmb_box(self.ui.cmbFilterType, 'IIR') self.ba[1][0] = 1.0 # restore fa[0] = 1 of denonimator polynome self.tblCoeff.setRowCount(self.num_rows) self.tblCoeff.setColumnCount(self.num_cols) # Create strings for index column (vertical header), starting with "0" idx_str = [str(n) for n in range(self.num_rows)] self.tblCoeff.setVerticalHeaderLabels(idx_str) self.tblCoeff.blockSignals(True) for col in range(self.num_cols): for row in range(self.num_rows): self._refresh_table_item(row, col) # make a[0] selectable but not editable if fb.fil[0]['ft'] == 'IIR': item = self.tblCoeff.item(0,1) item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled) item.setFont(self.ui.bfont) self.tblCoeff.blockSignals(False) self.tblCoeff.resizeColumnsToContents() self.tblCoeff.resizeRowsToContents() self.tblCoeff.clearSelection() else: self.ui.frmQSettings.setVisible(False) self.ui.butEnable.setIcon(QIcon(':/circle-check.svg')) self.tblCoeff.setVisible(False)