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)