class Input_Info(QWidget): """ Create widget for displaying infos about filter specs and filter design method """ sig_rx = pyqtSignal(object) # incoming signals from input_tab_widgets sig_tx = pyqtSignal(object) from pyfda.libs.pyfda_qt_lib import emit def __init__(self, parent=None): super(Input_Info, self).__init__(parent) self.tab_label = 'Info' self.tool_tip = ( "<span>Display the achieved filter specifications" " and more info about the filter design algorithm.</span>") self._construct_UI() self.load_dict() def process_sig_rx(self, dict_sig=None): """ Process signals coming from sig_rx """ # logger.debug("Processing {0}: {1}".format(type(dict_sig).__name__, dict_sig)) if 'data_changed' in dict_sig or 'view_changed' in dict_sig\ or 'specs_changed' in dict_sig: self.load_dict() def _construct_UI(self): """ Intitialize the widget, consisting of: - Checkboxes for selecting the info to be displayed - A large text window for displaying infos about the filter design algorithm """ bfont = QFont() bfont.setBold(True) # ============== UI Layout ===================================== # widget / subwindow for filter infos # self.butFiltPerf = QToolButton("H(f)", self) self.butFiltPerf = QPushButton(self) self.butFiltPerf.setText("H(f)") self.butFiltPerf.setCheckable(True) self.butFiltPerf.setChecked(True) self.butFiltPerf.setToolTip("Display frequency response at test frequencies.") self.butDebug = QPushButton(self) self.butDebug.setText("Debug") self.butDebug.setCheckable(True) self.butDebug.setChecked(False) self.butDebug.setToolTip("Show debugging options.") self.butAbout = QPushButton("About", self) # pop-up "About" window self.butSettings = QPushButton("Settings", self) # self.butSettings.setCheckable(True) self.butSettings.setChecked(False) self.butSettings.setToolTip("Display and set some settings") layHControls1 = QHBoxLayout() layHControls1.addWidget(self.butFiltPerf) layHControls1.addWidget(self.butAbout) layHControls1.addWidget(self.butSettings) layHControls1.addWidget(self.butDebug) self.butDocstring = QPushButton("Doc$", self) self.butDocstring.setCheckable(True) self.butDocstring.setChecked(False) self.butDocstring.setToolTip("Display docstring from python filter method.") self.butRichText = QPushButton("RTF", self) self.butRichText.setCheckable(HAS_DOCUTILS) self.butRichText.setChecked(HAS_DOCUTILS) self.butRichText.setEnabled(HAS_DOCUTILS) self.butRichText.setToolTip("Render documentation in Rich Text Format.") self.butFiltDict = QPushButton("FiltDict", self) self.butFiltDict.setToolTip("Show filter dictionary for debugging.") self.butFiltDict.setCheckable(True) self.butFiltDict.setChecked(False) self.butFiltTree = QPushButton("FiltTree", self) self.butFiltTree.setToolTip("Show filter tree for debugging.") self.butFiltTree.setCheckable(True) self.butFiltTree.setChecked(False) layHControls2 = QHBoxLayout() layHControls2.addWidget(self.butDocstring) # layHControls2.addStretch(1) layHControls2.addWidget(self.butRichText) # layHControls2.addStretch(1) layHControls2.addWidget(self.butFiltDict) # layHControls2.addStretch(1) layHControls2.addWidget(self.butFiltTree) self.frmControls2 = QFrame(self) self.frmControls2.setLayout(layHControls2) self.frmControls2.setVisible(self.butDebug.isChecked()) self.frmControls2.setContentsMargins(0, 0, 0, 0) lbl_settings_NFFT = QLabel(to_html("N_FFT =", frmt='bi'), self) self.led_settings_NFFT = QLineEdit(self) self.led_settings_NFFT.setText(str(params['N_FFT'])) self.led_settings_NFFT.setToolTip("<span>Number of FFT points for frequency " "domain widgets.</span>") layGSettings = QGridLayout() layGSettings.addWidget(lbl_settings_NFFT, 1, 0) layGSettings.addWidget(self.led_settings_NFFT, 1, 1) self.frmSettings = QFrame(self) self.frmSettings.setLayout(layGSettings) self.frmSettings.setVisible(self.butSettings.isChecked()) self.frmSettings.setContentsMargins(0, 0, 0, 0) layVControls = QVBoxLayout() layVControls.addLayout(layHControls1) layVControls.addWidget(self.frmControls2) layVControls.addWidget(self.frmSettings) self.frmMain = QFrame(self) self.frmMain.setLayout(layVControls) self.tblFiltPerf = QTableWidget(self) self.tblFiltPerf.setAlternatingRowColors(True) # self.tblFiltPerf.verticalHeader().setVisible(False) self.tblFiltPerf.horizontalHeader().setHighlightSections(False) self.tblFiltPerf.horizontalHeader().setFont(bfont) self.tblFiltPerf.verticalHeader().setHighlightSections(False) self.tblFiltPerf.verticalHeader().setFont(bfont) self.txtFiltInfoBox = QTextBrowser(self) self.txtFiltDict = QTextBrowser(self) self.txtFiltTree = QTextBrowser(self) layVMain = QVBoxLayout() layVMain.addWidget(self.frmMain) # layVMain.addLayout(self.layHControls) splitter = QSplitter(self) splitter.setOrientation(Qt.Vertical) splitter.addWidget(self.tblFiltPerf) splitter.addWidget(self.txtFiltInfoBox) splitter.addWidget(self.txtFiltDict) splitter.addWidget(self.txtFiltTree) # setSizes uses absolute pixel values, but can be "misused" by specifying values # that are way too large: in this case, the space is distributed according # to the _ratio_ of the values: splitter.setSizes([3000, 10000, 1000, 1000]) layVMain.addWidget(splitter) layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(layVMain) # ---------------------------------------------------------------------- # GLOBAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.sig_rx.connect(self.process_sig_rx) # ---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.butFiltPerf.clicked.connect(self._show_filt_perf) self.butAbout.clicked.connect(self._about_window) self.butSettings.clicked.connect(self._show_settings) self.led_settings_NFFT.editingFinished.connect(self._update_settings_nfft) self.butDebug.clicked.connect(self._show_debug) self.butFiltDict.clicked.connect(self._show_filt_dict) self.butFiltTree.clicked.connect(self._show_filt_tree) self.butDocstring.clicked.connect(self._show_doc) self.butRichText.clicked.connect(self._show_doc) def _about_window(self): self.about_widget = AboutWindow(self) # important: Handle must be class attribute # self.opt_widget.show() # modeless dialog, i.e. non-blocking self.about_widget.exec_() # modal dialog (blocking) # ------------------------------------------------------------------------------ def _show_debug(self): """ Show / hide debug options depending on the state of the debug button """ self.frmControls2.setVisible(self.butDebug.isChecked()) # ------------------------------------------------------------------------------ def _show_settings(self): """ Show / hide settings options depending on the state of the settings button """ self.frmSettings.setVisible(self.butSettings.isChecked()) def _update_settings_nfft(self): """ Update value for self.par1 from QLineEdit Widget""" params['N_FFT'] = safe_eval(self.led_settings_NFFT.text(), params['N_FFT'], sign='pos', return_type='int') self.led_settings_NFFT.setText(str(params['N_FFT'])) self.emit({'data_changed': 'n_fft'}) # ------------------------------------------------------------------------------ def load_dict(self): """ update docs and filter performance """ self._show_doc() self._show_filt_perf() self._show_filt_dict() self._show_filt_tree() # ------------------------------------------------------------------------------ def _show_doc(self): """ Display info from filter design file and docstring """ if hasattr(ff.fil_inst, 'info'): if self.butRichText.isChecked(): self.txtFiltInfoBox.setText(publish_string( self._clean_doc(ff.fil_inst.info), writer_name='html', settings_overrides={'output_encoding': 'unicode'})) else: self.txtFiltInfoBox.setText(textwrap.dedent(ff.fil_inst.info)) else: self.txtFiltInfoBox.setText("") if self.butDocstring.isChecked() and hasattr(ff.fil_inst, 'info_doc'): if self.butRichText.isChecked(): self.txtFiltInfoBox.append( '<hr /><b>Python module docstring:</b>\n') for doc in ff.fil_inst.info_doc: self.txtFiltInfoBox.append(publish_string( self._clean_doc(doc), writer_name='html', settings_overrides={'output_encoding': 'unicode'})) else: self.txtFiltInfoBox.append('\nPython module docstring:\n') for doc in ff.fil_inst.info_doc: self.txtFiltInfoBox.append(self._clean_doc(doc)) self.txtFiltInfoBox.moveCursor(QTextCursor.Start) def _clean_doc(self, doc): """ Remove uniform number of leading blanks from docstrings for subsequent processing of rich text. The first line is treated differently, _all_ leading blanks are removed (if any). This allows for different formats of docstrings. """ lines = doc.splitlines() result = lines[0].lstrip() + "\n" + textwrap.dedent("\n".join(lines[1:])) return result # ------------------------------------------------------------------------------ def _show_filt_perf(self): """ Print filter properties in a table at frequencies of interest. When specs are violated, colour the table entry in red. """ antiC = False def _find_min_max(self, f_start, f_stop, unit='dB'): """ Find minimum and maximum magnitude and the corresponding frequencies for the filter defined in the filter dict in a given frequency band [f_start, f_stop]. """ w = np.linspace(f_start, f_stop, params['N_FFT'])*2*np.pi [w, H] = sig.freqz(bb, aa, worN=w) # add antiCausals if we have them if (antiC): # # Evaluate transfer function of anticausal half on the same freq grid. # wa, ha = sig.freqz(bbA, aaA, worN=w) ha = ha.conjugate() # # Total transfer function is the product # H = H*ha f = w / (2.0 * pi) # frequency normalized to f_S H_abs = abs(H) H_max = max(H_abs) H_min = min(H_abs) F_max = f[np.argmax(H_abs)] # find the frequency where H_abs F_min = f[np.argmin(H_abs)] # becomes max resp. min if unit == 'dB': H_max = 20*log10(H_max) H_min = 20*log10(H_min) return F_min, H_min, F_max, H_max # ------------------------------------------------------------------ self.tblFiltPerf.setVisible(self.butFiltPerf.isChecked()) if self.butFiltPerf.isChecked(): bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] # 'rpk' means nonCausal filter if 'rpk' in fb.fil[0]: antiC = True bbA = fb.fil[0]['baA'][0] aaA = fb.fil[0]['baA'][1] bbA = bbA.conjugate() aaA = aaA.conjugate() f_S = fb.fil[0]['f_S'] f_lbls = [] f_vals = [] a_lbls = [] a_targs = [] a_targs_dB = [] a_test = [] ft = fb.fil[0]['ft'] # get filter type ('IIR', 'FIR') unit = fb.fil[0]['amp_specs_unit'] unit = 'dB' # fix this for the moment # construct pairs of corner frequency and corresponding amplitude # labels in ascending frequency for each response type if fb.fil[0]['rt'] in {'LP', 'HP', 'BP', 'BS', 'HIL'}: if fb.fil[0]['rt'] == 'LP': f_lbls = ['F_PB', 'F_SB'] a_lbls = ['A_PB', 'A_SB'] elif fb.fil[0]['rt'] == 'HP': f_lbls = ['F_SB', 'F_PB'] a_lbls = ['A_SB', 'A_PB'] elif fb.fil[0]['rt'] == 'BP': f_lbls = ['F_SB', 'F_PB', 'F_PB2', 'F_SB2'] a_lbls = ['A_SB', 'A_PB', 'A_PB', 'A_SB2'] elif fb.fil[0]['rt'] == 'BS': f_lbls = ['F_PB', 'F_SB', 'F_SB2', 'F_PB2'] a_lbls = ['A_PB', 'A_SB', 'A_SB', 'A_PB2'] elif fb.fil[0]['rt'] == 'HIL': f_lbls = ['F_PB', 'F_PB2'] a_lbls = ['A_PB', 'A_PB'] # Try to get lists of frequency / amplitude specs from the filter dict # that correspond to the f_lbls / a_lbls pairs defined above # When one of the labels doesn't exist in the filter dict, delete # all corresponding amplitude and frequency entries. err = [False] * len(f_lbls) # initialize error list f_vals = [] a_targs = [] for i in range(len(f_lbls)): try: f = fb.fil[0][f_lbls[i]] f_vals.append(f) except KeyError as e: f_vals.append('') err[i] = True logger.debug(e) try: a = fb.fil[0][a_lbls[i]] a_dB = lin2unit(fb.fil[0][a_lbls[i]], ft, a_lbls[i], unit) a_targs.append(a) a_targs_dB.append(a_dB) except KeyError as e: a_targs.append('') a_targs_dB.append('') err[i] = True logger.debug(e) for i in range(len(f_lbls)): if err[i]: del f_lbls[i] del f_vals[i] del a_lbls[i] del a_targs[i] del a_targs_dB[i] f_vals = np.asarray(f_vals) # convert to numpy array logger.debug("F_test_labels = %s" % f_lbls) # Calculate frequency response at test frequencies [w_test, a_test] = sig.freqz(bb, aa, 2.0 * pi * f_vals.astype(float)) # add antiCausals if we have them if (antiC): wa, ha = sig.freqz(bbA, aaA, 2.0 * pi * f_vals.astype(float)) ha = ha.conjugate() a_test = a_test*ha (F_min, H_min, F_max, H_max) = _find_min_max(self, 0, 1, unit='V') # append frequencies and values for min. and max. filter reponse to # test vector f_lbls += ['Min.', 'Max.'] # QTableView does not support direct formatting, use QLabel f_vals = np.append(f_vals, [F_min, F_max]) a_targs = np.append(a_targs, [np.nan, np.nan]) a_targs_dB = np.append(a_targs_dB, [np.nan, np.nan]) a_test = np.append(a_test, [H_min, H_max]) # calculate response of test frequencies in dB a_test_dB = -20*log10(abs(a_test)) # get filter type ('IIR', 'FIR') for dB <-> lin conversion ft = fb.fil[0]['ft'] # unit = fb.fil[0]['amp_specs_unit'] unit = 'dB' # make this fixed for the moment # build a list with the corresponding target specs: a_targs_pass = [] eps = 1e-3 for i in range(len(f_lbls)): if 'PB' in f_lbls[i]: a_targs_pass.append((a_test_dB[i] - a_targs_dB[i]) < eps) a_test[i] = 1 - abs(a_test[i]) elif 'SB' in f_lbls[i]: a_targs_pass.append(a_test_dB[i] >= a_targs_dB[i]) else: a_targs_pass.append(True) self.targs_spec_passed = np.all(a_targs_pass) logger.debug( "H_targ = {0}\n" "H_test = {1}\n" "H_test_dB = {2}\n" "F_test = {3}\n" "H_targ_pass = {4}\n" "passed: {5}\n".format(a_targs, a_test, a_test_dB, f_vals, a_targs_pass, self.targs_spec_passed)) self.tblFiltPerf.setRowCount(len(a_test)) # number of table rows self.tblFiltPerf.setColumnCount(5) # number of table columns self.tblFiltPerf.setHorizontalHeaderLabels([ 'f/{0:s}'.format(fb.fil[0]['freq_specs_unit']), 'Spec\n(dB)', '|H(f)|\n(dB)', 'Spec', '|H(f)|']) self.tblFiltPerf.setVerticalHeaderLabels(f_lbls) for row in range(len(a_test)): self.tblFiltPerf.setItem( row, 0, QTableWidgetItem(str('{0:.4g}'.format(f_vals[row]*f_S)))) self.tblFiltPerf.setItem( row, 1, QTableWidgetItem(str('%2.3g'%(-a_targs_dB[row])))) self.tblFiltPerf.setItem( row, 2, QTableWidgetItem(str('%2.3f'%(-a_test_dB[row])))) if a_targs[row] < 0.01: self.tblFiltPerf.setItem( row, 3, QTableWidgetItem(str('%.3e'%(a_targs[row])))) else: self.tblFiltPerf.setItem( row, 3, QTableWidgetItem(str('%2.4f'%(a_targs[row])))) if a_test[row] < 0.01: self.tblFiltPerf.setItem( row, 4, QTableWidgetItem(str('%.3e'%(abs(a_test[row]))))) else: self.tblFiltPerf.setItem( row, 4, QTableWidgetItem(str('%.4f'%(abs(a_test[row]))))) if not a_targs_pass[row]: self.tblFiltPerf.item(row, 1).setBackground(QtGui.QColor('red')) self.tblFiltPerf.item(row, 3).setBackground(QtGui.QColor('red')) self.tblFiltPerf.resizeColumnsToContents() self.tblFiltPerf.resizeRowsToContents() # ------------------------------------------------------------------------------ def _show_filt_dict(self): """ Print filter dict for debugging """ self.txtFiltDict.setVisible(self.butFiltDict.isChecked()) fb_sorted = [str(key) + ' : ' + str(fb.fil[0][key]) for key in sorted(fb.fil[0].keys())] dictstr = pprint.pformat(fb_sorted) # dictstr = pprint.pformat(fb.fil[0]) self.txtFiltDict.setText(dictstr) # ------------------------------------------------------------------------------ def _show_filt_tree(self): """ Print filter tree for debugging """ self.txtFiltTree.setVisible(self.butFiltTree.isChecked()) ftree_sorted = ['<b>' + str(key) + ' : ' + '</b>' + str(fb.fil_tree[key]) for key in sorted(fb.fil_tree.keys())] dictstr = pprint.pformat(ftree_sorted, indent=4) # dictstr = pprint.pformat(fb.fil[0]) self.txtFiltTree.setText(dictstr)
def _construct_UI(self, **kwargs): """ Construct widget from quantization dict, individual settings and the default dict below """ # default settings dict_ui = { 'wdg_name': 'ui_w', 'label': 'WI.WF', 'lbl_sep': '.', 'max_led_width': 30, 'WI': 0, 'WI_len': 2, 'tip_WI': 'Number of integer bits', 'WF': 15, 'WF_len': 2, 'tip_WF': 'Number of fractional bits', 'enabled': True, 'visible': True, 'fractional': True, 'combo_visible': False, 'combo_items': ['auto', 'full', 'man'], 'tip_combo': 'Calculate Acc. width.', 'lock_visible': False, 'tip_lock': 'Lock input/output quantization.' } #: default values if self.q_dict: dict_ui.update(self.q_dict) for k, v in kwargs.items(): if k not in dict_ui: logger.warning("Unknown key {0}".format(k)) else: dict_ui.update({k: v}) self.wdg_name = dict_ui['wdg_name'] if not dict_ui['fractional']: dict_ui['WF'] = 0 self.WI = dict_ui['WI'] self.WF = dict_ui['WF'] self.W = int(self.WI + self.WF + 1) if self.q_dict: self.q_dict.update({'WI': self.WI, 'WF': self.WF, 'W': self.W}) else: self.q_dict = {'WI': self.WI, 'WF': self.WF, 'W': self.W} lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self) self.cmbW = QComboBox(self) self.cmbW.addItems(dict_ui['combo_items']) self.cmbW.setVisible(dict_ui['combo_visible']) self.cmbW.setToolTip(dict_ui['tip_combo']) self.cmbW.setObjectName("cmbW") self.butLock = QPushButton(self) self.butLock.setCheckable(True) self.butLock.setChecked(False) self.butLock.setVisible(dict_ui['lock_visible']) self.butLock.setToolTip(dict_ui['tip_lock']) self.ledWI = QLineEdit(self) self.ledWI.setToolTip(dict_ui['tip_WI']) self.ledWI.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits self.ledWI.setFixedWidth( dict_ui['max_led_width']) # width of lineedit in points self.ledWI.setObjectName("WI") lblDot = QLabel(dict_ui['lbl_sep'], self) lblDot.setVisible(dict_ui['fractional']) self.ledWF = QLineEdit(self) self.ledWF.setToolTip(dict_ui['tip_WF']) self.ledWF.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits self.ledWF.setFixedWidth( dict_ui['max_led_width']) # width of lineedit in points self.ledWF.setVisible(dict_ui['fractional']) self.ledWF.setObjectName("WF") layH = QHBoxLayout() layH.addWidget(lblW) layH.addStretch() layH.addWidget(self.cmbW) layH.addWidget(self.butLock) layH.addWidget(self.ledWI) layH.addWidget(lblDot) layH.addWidget(self.ledWF) layH.setContentsMargins(0, 0, 0, 0) frmMain = QFrame(self) frmMain.setLayout(layH) layVMain = QVBoxLayout() # Widget main layout layVMain.addWidget(frmMain) layVMain.setContentsMargins(0, 5, 0, 0) # *params['wdg_margins']) self.setLayout(layVMain) # ---------------------------------------------------------------------- # INITIAL SETTINGS # ---------------------------------------------------------------------- self.ledWI.setText(qstr(dict_ui['WI'])) self.ledWF.setText(qstr(dict_ui['WF'])) frmMain.setEnabled(dict_ui['enabled']) frmMain.setVisible(dict_ui['visible']) # ---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.ledWI.editingFinished.connect(self.ui2dict) self.ledWF.editingFinished.connect(self.ui2dict) self.butLock.clicked.connect(self.butLock_clicked) self.cmbW.currentIndexChanged.connect(self.ui2dict) # initialize button icon self.butLock_clicked(self.butLock.isChecked())
class Input_Specs(QWidget): """ Build widget for entering all filter specs """ # class variables (shared between instances if more than one exists) sig_rx_local = pyqtSignal( object) # incoming from subwidgets -> process_sig_rx_local sig_rx = pyqtSignal(object) # incoming from subwidgets -> process_sig_rx sig_tx = pyqtSignal(object) # from process_sig_rx: propagate local signals from pyfda.libs.pyfda_qt_lib import emit def __init__(self, parent=None): super(Input_Specs, self).__init__(parent) self.tab_label = "Specs" self.tool_tip = "Enter and view filter specifications." self._construct_UI() def process_sig_rx_local(self, dict_sig=None): """ Flag signals coming in from local subwidgets with `propagate=True` before proceeding with processing in `process_sig_rx`. """ self.process_sig_rx(dict_sig, propagate=True) def process_sig_rx(self, dict_sig=None, propagate=False): """ Process signals coming in via subwidgets and sig_rx All signals terminate here unless the flag `propagate=True`. The sender name of signals coming in from local subwidgets is changed to its parent widget (`input_specs`) to prevent infinite loops. """ # logger.debug(f"SIG_RX: {pprint_log(dict_sig)}") if dict_sig['id'] == id(self): # logger.warning(f"Stopped infinite loop:\n\tPropagate = {propagate}\ # \n{pprint_log(dict_sig)}") return elif 'view_changed' in dict_sig: self.f_specs.load_dict() self.t_specs.load_dict() elif 'specs_changed' in dict_sig: self.f_specs.sort_dict_freqs() self.t_specs.f_specs.sort_dict_freqs() self.color_design_button("changed") elif 'filt_changed' in dict_sig: # Changing the filter design requires updating UI because number or # kind of input fields changes -> call update_UI self.update_UI(dict_sig) elif 'data_changed' in dict_sig: if dict_sig['data_changed'] == 'filter_loaded': """ Called when a new filter has been LOADED: Pass new filter data from the global filter dict by specifically calling SelectFilter.load_dict() """ self.sel_fil.load_dict() # update select_filter widget # Pass new filter data from the global filter dict & set button = "ok" self.load_dict() if propagate: # local signals are propagated with the name of this widget, # global signals terminate here dict_sig.update({'class': self.__class__.__name__}) self.emit(dict_sig) def _construct_UI(self): """ Construct User Interface from all input subwidgets """ self.butLoadFilt = QPushButton("LOAD FILTER", self) self.butLoadFilt.setToolTip("Load filter from disk") self.butSaveFilt = QPushButton("SAVE FILTER", self) self.butSaveFilt.setToolTip("Save filter todisk") layHButtons1 = QHBoxLayout() layHButtons1.addWidget(self.butLoadFilt) # <Load Filter> button layHButtons1.addWidget(self.butSaveFilt) # <Save Filter> button layHButtons1.setContentsMargins(*params['wdg_margins_spc']) self.butDesignFilt = QPushButton("DESIGN FILTER", self) self.butDesignFilt.setToolTip("Design filter with chosen specs") self.butQuit = QPushButton("Quit", self) self.butQuit.setToolTip("Exit pyfda tool") layHButtons2 = QHBoxLayout() layHButtons2.addWidget(self.butDesignFilt) # <Design Filter> button layHButtons2.addWidget(self.butQuit) # <Quit> button layHButtons2.setContentsMargins(*params['wdg_margins']) # Subwidget for selecting filter with response type rt (LP, ...), # filter type ft (IIR, ...) and filter class fc (cheby1, ...) self.sel_fil = select_filter.SelectFilter(self) self.sel_fil.setObjectName("select_filter") self.sel_fil.sig_tx.connect(self.sig_rx_local) # Subwidget for selecting the frequency unit and range self.f_units = freq_units.FreqUnits(self) self.f_units.setObjectName("freq_units") self.f_units.sig_tx.connect(self.sig_rx_local) # Changing the frequency unit requires re-display of frequency specs # but it does not influence the actual specs (no specsChanged ) # Activating the "Sort" button emits 'view_changed'?specs_changed'?, requiring # sorting and storing the frequency entries # Changing filter parameters / specs requires reloading of parameters # in other hierarchy levels, e.g. in the plot tabs # Subwidget for Frequency Specs self.f_specs = freq_specs.FreqSpecs(self) self.f_specs.setObjectName("freq_specs") self.f_specs.sig_tx.connect(self.sig_rx_local) self.sig_tx.connect(self.f_specs.sig_rx) # Subwidget for Amplitude Specs self.a_specs = amplitude_specs.AmplitudeSpecs(self) self.a_specs.setObjectName("amplitude_specs") self.a_specs.sig_tx.connect(self.sig_rx_local) # Subwidget for Weight Specs self.w_specs = weight_specs.WeightSpecs(self) self.w_specs.setObjectName("weight_specs") self.w_specs.sig_tx.connect(self.sig_rx_local) # Subwidget for target specs (frequency and amplitude) self.t_specs = target_specs.TargetSpecs(self, title="Target Specifications") self.t_specs.setObjectName("target_specs") self.t_specs.sig_tx.connect(self.sig_rx_local) self.sig_tx.connect(self.t_specs.sig_rx) # Subwidget for displaying infos on the design method self.lblMsg = QLabel(self) self.lblMsg.setWordWrap(True) layVMsg = QVBoxLayout() layVMsg.addWidget(self.lblMsg) self.frmMsg = QFrame(self) self.frmMsg.setLayout(layVMsg) layVFrm = QVBoxLayout() layVFrm.addWidget(self.frmMsg) layVFrm.setContentsMargins(*params['wdg_margins']) # ---------------------------------------------------------------------- # LAYOUT for input specifications and buttons # ---------------------------------------------------------------------- layVMain = QVBoxLayout(self) layVMain.addLayout(layHButtons1) # <Load> & <Save> buttons layVMain.addWidget(self.sel_fil) # Design method (IIR - ellip, ...) layVMain.addLayout(layHButtons2) # <Design> & <Quit> buttons layVMain.addWidget(self.f_units) # Frequency units layVMain.addWidget(self.t_specs) # Target specs layVMain.addWidget(self.f_specs) # Freq. specifications layVMain.addWidget(self.a_specs) # Amplitude specs layVMain.addWidget(self.w_specs) # Weight specs layVMain.addLayout(layVFrm) # Text message layVMain.addStretch() layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(layVMain) # main layout of widget # ---------------------------------------------------------------------- # GLOBAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.sig_rx.connect(self.process_sig_rx) # ---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.sig_rx_local.connect(self.process_sig_rx_local) self.butLoadFilt.clicked.connect(lambda: load_filter(self)) self.butSaveFilt.clicked.connect(lambda: save_filter(self)) self.butDesignFilt.clicked.connect(self.start_design_filt) self.butQuit.clicked.connect(self.quit_program) # emit 'quit_program' # ---------------------------------------------------------------------- self.update_UI() # first time initialization self.start_design_filt() # design first filter using default values # ------------------------------------------------------------------------------ def update_UI(self, dict_sig={}): """ update_UI is called every time the filter design method or order (min / man) has been changed as this usually requires a different set of frequency and amplitude specs. At this time, the actual filter object instance has been created from the name of the design method (e.g. 'cheby1') in select_filter.py. Its handle has been stored in fb.fil_inst. fb.fil[0] (currently selected filter) is read, then general information for the selected filter type and order (min/man) is gathered from the filter tree [fb.fil_tree], i.e. which parameters are needed, which widgets are visible and which message shall be displayed. Then, the UIs of all subwidgets are updated using their "update_UI" method. """ rt = fb.fil[0]['rt'] # e.g. 'LP' ft = fb.fil[0]['ft'] # e.g. 'FIR' fc = fb.fil[0]['fc'] # e.g. 'equiripple' fo = fb.fil[0]['fo'] # e.g. 'man' # the keys of the all_widgets dict are the names of the subwidgets, # the values are a tuple with the corresponding parameters all_widgets = fb.fil_tree[rt][ft][fc][fo] # logger.debug("rt: {0} - ft: {1} - fc: {2} - fo: {3}".format(rt, ft, fc, fo)) # logger.debug("fb.fil_tree[rt][ft][fc][fo]:\n{0}".format(fb.fil_tree[rt][ft][fc][fo])) # update filter order subwidget, called by select_filter: # self.sel_fil.load_filter_order() # TARGET SPECS: is widget in the dict and is it visible (marker != 'i')? if ('tspecs' in all_widgets and len(all_widgets['tspecs']) > 1 and all_widgets['tspecs'][0] != 'i'): self.t_specs.setVisible(True) # disable all subwidgets with marker 'd': self.t_specs.setEnabled(all_widgets['tspecs'][0] != 'd') self.t_specs.update_UI(new_labels=all_widgets['tspecs'][1]) else: self.t_specs.hide() # FREQUENCY SPECS if ('fspecs' in all_widgets and len(all_widgets['fspecs']) > 1 and all_widgets['fspecs'][0] != 'i'): self.f_specs.setVisible(True) self.f_specs.setEnabled(all_widgets['fspecs'][0] != 'd') self.f_specs.update_UI(new_labels=all_widgets['fspecs']) else: self.f_specs.hide() # AMPLITUDE SPECS if ('aspecs' in all_widgets and len(all_widgets['aspecs']) > 1 and all_widgets['aspecs'][0] != 'i'): self.a_specs.setVisible(True) self.a_specs.setEnabled(all_widgets['aspecs'][0] != 'd') self.a_specs.update_UI(new_labels=all_widgets['aspecs']) else: self.a_specs.hide() # WEIGHT SPECS if ('wspecs' in all_widgets and len(all_widgets['wspecs']) > 1 and all_widgets['wspecs'][0] != 'i'): self.w_specs.setVisible(True) self.w_specs.setEnabled(all_widgets['wspecs'][0] != 'd') self.w_specs.update_UI(new_labels=all_widgets['wspecs']) else: self.w_specs.hide() # MESSAGE PANE if ('msg' in all_widgets and len(all_widgets['msg']) > 1 and all_widgets['msg'][0] != 'i'): self.frmMsg.setVisible(True) self.frmMsg.setEnabled(all_widgets['msg'][0] != 'd') self.lblMsg.setText(all_widgets['msg'][1:][0]) else: self.frmMsg.hide() # Update state of "DESIGN FILTER" button # It is disabled for "Manual_IIR" and "Manual_FIR" filter classes self.color_design_button("changed") # ------------------------------------------------------------------------------ def load_dict(self): """ Reload all specs/parameters entries from global dict fb.fil[0], using the "load_dict" methods of the individual classes """ self.sel_fil.load_dict() # select filter widget self.f_units.load_dict() # frequency units widget self.f_specs.load_dict() # frequency specification widget self.a_specs.load_dict() # magnitude specs with unit self.w_specs.load_dict() # weight specification self.t_specs.load_dict() # target specs self.color_design_button("ok") # ------------------------------------------------------------------------------ def start_design_filt(self): """ Start the actual filter design process: - store the entries of all input widgets in the global filter dict. - call the design method, passing the whole dictionary as the argument: let the design method pick the needed specs - update the input widgets in case weights, corner frequencies etc. have been changed by the filter design method - the plots are updated via signal-slot connection """ try: logger.info( "Start filter design using method\n\t'{0}.{1}{2}'".format( str(fb.fil[0]['fc']), str(fb.fil[0]['rt']), str(fb.fil[0]['fo']))) # ---------------------------------------------------------------------- # A globally accessible instance fb.fil_inst of selected filter class fc # has been instantiated in InputFilter.set_design_method, now # call the method specified in the filter dict fil[0]. # The name of the instance method is constructed from the response # type (e.g. 'LP') and the filter order (e.g. 'man'), giving e.g. 'LPman'. # The filter is designed by passing the specs in fil[0] to the method, # resulting in e.g. cheby1.LPman(fb.fil[0]) and writing back coefficients, # P/Z etc. back to fil[0]. err = ff.fil_factory.call_fil_method( fb.fil[0]['rt'] + fb.fil[0]['fo'], fb.fil[0]) # this is the same as e.g. # from pyfda.filter_design import ellip # inst = ellip.ellip() # inst.LPmin(fb.fil[0]) # ----------------------------------------------------------------------- if err > 0: self.color_design_button("error") elif err == -1: # filter design cancelled by user return else: # Update filter order. weights and freq display in case they # have been changed by the design algorithm self.sel_fil.load_filter_order() self.w_specs.load_dict() self.f_specs.load_dict() self.color_design_button("ok") self.emit({'data_changed': 'filter_designed'}) logger.info('Designed filter with order = {0}'.format( str(fb.fil[0]['N']))) # ============================================================================= # logger.debug("Results:\n" # "F_PB = %s, F_SB = %s " # "Filter order N = %s\n" # "NDim fil[0]['ba'] = %s\n\n" # "b,a = %s\n\n" # "zpk = %s\n", # str(fb.fil[0]['F_PB']), str(fb.fil[0]['F_SB']), str(fb.fil[0]['N']), # str(np.ndim(fb.fil[0]['ba'])), pformat(fb.fil[0]['ba']), # pformat(fb.fil[0]['zpk'])) # # ============================================================================= except Exception as e: if ('__doc__' in str(e)): logger.warning("Filter design:\n %s\n %s\n", e.__doc__, e) else: logger.warning("{0}".format(e)) self.color_design_button("error") def color_design_button(self, state): man = "manual" in fb.fil[0]['fc'].lower() self.butDesignFilt.setDisabled(man) if man: state = 'ok' fb.design_filt_state = state qstyle_widget(self.butDesignFilt, state) # ------------------------------------------------------------------------------ def quit_program(self): """ When <QUIT> button is pressed, send 'quit_program' """ self.emit({'quit_program': ''})
def _construct_UI(self, **kwargs): """ Construct widget """ dict_ui = { 'wdg_name': 'ui_q', 'label': '', '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 } #: default widget settings if 'quant' in self.q_dict and self.q_dict['quant'] in dict_ui['cmb_q']: dict_ui['cur_q'] = self.q_dict['quant'] if 'ovfl' in self.q_dict and self.q_dict['ovfl'] in dict_ui['cmb_ov']: dict_ui['cur_ov'] = self.q_dict['ovfl'] for key, val in kwargs.items(): dict_ui.update({key: val}) # dict_ui.update(map(kwargs)) # same as above? self.wdg_name = dict_ui['wdg_name'] 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']) self.cmbQuant.setObjectName('quant') 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']) self.cmbOvfl.setObjectName('ovfl') # ComboBox size is adjusted automatically to fit the longest element self.cmbQuant.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbOvfl.setSizeAdjustPolicy(QComboBox.AdjustToContents) layH = QHBoxLayout() if dict_ui['label'] != "": lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self) layH.addWidget(lblW) layH.addStretch() layH.addWidget(lblOvfl) layH.addWidget(self.cmbOvfl) # layH.addStretch(1) 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(0, 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.ui2dict) self.cmbQuant.currentIndexChanged.connect(self.ui2dict)