def _construct_UI(self): # ===================================================================== # Controls # ===================================================================== self.butLoad = PushButton(self, icon=QIcon(':/file.svg'), checkable=False) # self.butLoad.setIconSize(q_icon_size) self.butLoad.setToolTip("Load data from file.") self.butLoad.setEnabled(False) self.lbl_info = QLabel(to_html(" coming soon ...", frmt="b")) # ---------------------------------------------------------------------- # Main Widget # ---------------------------------------------------------------------- layH_io_par = QHBoxLayout() layH_io_par.addWidget(self.butLoad) layH_io_par.addWidget(self.lbl_info) layV_io = QVBoxLayout() layV_io.addLayout(layH_io_par) layH_io = QHBoxLayout() layH_io.addLayout(layV_io) layH_io.addStretch(10) self.wdg_top = QWidget(self) self.wdg_top.setLayout(layH_io) self.wdg_top.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
def _construct_UI(self): """ Construct user interface """ # subwidget for Frequency Specs self.f_specs = freq_specs.FreqSpecs(self, title="Frequency") # subwidget for Amplitude Specs self.a_specs = amplitude_specs.AmplitudeSpecs(self, title="Amplitude") self.a_specs.setVisible(True) """ LAYOUT """ bfont = QFont() bfont.setBold(True) lblTitle = QLabel(self) # field for widget title lblTitle.setText(self.title) lblTitle.setFont(bfont) # lblTitle.setContentsMargins(2,2,2,2) layHTitle = QHBoxLayout() layHTitle.addWidget(lblTitle) layHTitle.setAlignment(Qt.AlignHCenter) layHSpecs = QHBoxLayout() layHSpecs.setAlignment(Qt.AlignTop) layHSpecs.addWidget(self.f_specs) # frequency specs layHSpecs.addWidget(self.a_specs) # ampltitude specs layVSpecs = QVBoxLayout() layVSpecs.addLayout(layHTitle) layVSpecs.addLayout(layHSpecs) layVSpecs.setContentsMargins(0, 6, 0, 0) # (left, top, right, bottom) # This is the top level widget, encompassing the other widgets frmMain = QFrame(self) frmMain.setLayout(layVSpecs) self.layVMain = QVBoxLayout() # Widget main layout self.layVMain.addWidget(frmMain) self.layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(self.layVMain) # ---------------------------------------------------------------------- # GLOBAL SIGNALS & SLOTs # ---------------------------------------------------------------------- # connect f_specs and a_specs subwidget to signalling self.f_specs.sig_tx.connect(self.sig_tx) # pass signal upwards self.sig_rx.connect(self.f_specs.sig_rx) # pass on received signals self.a_specs.sig_tx.connect(self.sig_tx) # pass signal upwards self.update_UI() # first time initialization
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 _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
class MplWidget(QWidget): """ Construct a subwidget consisting of a Matplotlib canvas and a subclassed NavigationToolbar. """ def __init__(self, parent): super(MplWidget, self).__init__(parent) # Create the mpl figure and subplot (white bg, 100 dots-per-inch). # Construct the canvas with the figure: self.plt_lim = [] # define variable for x,y plot limits if cmp_version("matplotlib", "2.2.0") >= 0: self.fig = Figure(constrained_layout=True) else: self.fig = Figure() self.canvas = FigureCanvas(self.fig) self.canvas.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # Needed for mouse modifiers (x,y, <CTRL>, ...): # Key press events in general are not processed unless you # "activate the focus of Qt onto your mpl canvas" # http://stackoverflow.com/questions/22043549/matplotlib-and-qt-mouse-press-event-key-is-always-none self.canvas.setFocusPolicy( QtCore.Qt.ClickFocus ) self.canvas.setFocus() self.canvas.updateGeometry() # Create a custom navigation toolbar, tied to the canvas and # initialize toolbar settings # self.mplToolbar = MplToolbar(self.canvas, self) self.mplToolbar.zoom_locked = False self.mplToolbar.cursor_enabled = False #self.mplToolbar.enable_plot(state = True) self.mplToolbar.sig_tx.connect(self.process_signals) layHToolbar = QHBoxLayout() layHToolbar.addWidget(self.mplToolbar, 1, QtCore.Qt.AlignLeft) layHToolbar.addStretch(1) #============================================= # Main plot widget layout #============================================= self.layVMainMpl = QVBoxLayout() self.layVMainMpl.addLayout(layHToolbar) self.layVMainMpl.addWidget(self.canvas) self.setLayout(self.layVMainMpl) #------------------------------------------------------------------------------ @pyqtSlot(object) def process_signals(self, dict_sig): """ Process sig """ # if 'enabled' in dict_sig: # self.clear_disabled_figure(dict_sig['enabled']) # else: pass #------------------------------------------------------------------------------ def save_limits(self): """ Save x- and y-limits of all axes in self.limits when zoom is unlocked """ if not self.mplToolbar.zoom_locked: for ax in self.fig.axes: self.limits = ax.axis() # save old limits #------------------------------------------------------------------------------ def redraw(self): """ Redraw the figure with new properties (grid, linewidth) """ # only execute when at least one axis exists -> tight_layout crashes otherwise if self.fig.axes: self.mplToolbar.cycle_draw_grid(cycle=False, axes=self.fig.axes) for ax in self.fig.axes: if self.mplToolbar.zoom_locked: ax.axis(self.limits) # restore old limits else: self.limits = ax.axis() # save old limits # try: # # tight_layout() crashes with small figure sizes # self.fig.tight_layout(pad = 0.1) # except(ValueError, np.linalg.linalg.LinAlgError): # logger.debug("error in tight_layout") self.canvas.draw() # now (re-)draw the figure #------------------------------------------------------------------------------ # def clear_disabled_figure(self, enabled): # """ # Clear the figure when it is disabled in the mplToolbar # """ # if not enabled: # self.fig.clf() # self.pltCanv.draw() # else: # self.redraw() #------------------------------------------------------------------------------ def plt_full_view(self): """ Zoom to full extent of data if axes is set to "navigationable" by the navigation toolbar """ #Add current view limits to view history to enable "back to previous view" self.mplToolbar.push_current() for ax in self.fig.axes: if ax.get_navigate(): ax.autoscale() self.redraw() #------------------------------------------------------------------------------ def get_full_extent(self, ax, pad=0.0): """ Get the full extent of axes system `ax`, including axes labels, tick labels and titles. Needed for inset plot in H(f) """ #http://stackoverflow.com/questions/14712665/matplotlib-subplot-background-axes-face-labels-colour-or-figure-axes-coor # For text objects, we need to draw the figure first, otherwise the extents # are undefined. self.canvas.draw() items = ax.get_xticklabels() + ax.get_yticklabels() items += [ax, ax.title, ax.xaxis.label, ax.yaxis.label] bbox = Bbox.union([item.get_window_extent() for item in items]) return bbox.expanded(1.0 + pad, 1.0 + pad) #------------------------------------------------------------------------------ def toggle_cursor(self): """ Toggle the tracking cursor """ if MPL_CURS: self.mplToolbar.cursor_enabled = not self.mplToolbar.cursor_enabled if self.mplToolbar.cursor_enabled: if hasattr(self, "cursors"): # dangling references to old cursors? for i in range(len(self.cursors)): self.cursors[i].remove() # yes, remove them! self.cursors = [] for ax in self.fig.axes: if ax.__class__.__name__ in {"AxesSubplot", "Axes3DSubplot"}: self.cursors.append(mplcursors.cursor(ax, hover=True)) else: for i in range(len(self.cursors)): self.cursors[i].remove()
def _construct_UI(self): # ----------- --------------------------------------------------- # Run control widgets # --------------------------------------------------------------- self.chk_auto_run = QCheckBox("Auto", self) self.chk_auto_run.setObjectName("chk_auto_run") self.chk_auto_run.setToolTip("<span>Update response automatically when " "parameters have been changed.</span>") self.chk_auto_run.setChecked(True) self.but_run = QPushButton(self) self.but_run.setText("RUN") self.but_run.setToolTip("Run simulation") self.but_run.setEnabled(not self.chk_auto_run.isChecked()) self.cmb_sim_select = QComboBox(self) self.cmb_sim_select.addItems(["Float","Fixpoint"]) qset_cmb_box(self.cmb_sim_select, "Float") self.cmb_sim_select.setToolTip("<span>Simulate floating-point or fixpoint response." "</span>") self.lbl_N_points = QLabel(to_html("N", frmt='bi') + " =", self) self.led_N_points = QLineEdit(self) self.led_N_points.setText(str(self.N)) self.led_N_points.setToolTip("<span>Number of displayed data points. " "<i>N</i> = 0 tries to choose for you.</span>") self.lbl_N_start = QLabel(to_html("N_0", frmt='bi') + " =", self) self.led_N_start = QLineEdit(self) self.led_N_start.setText(str(self.N_start)) self.led_N_start.setToolTip("<span>First point to plot.</span>") self.chk_fx_scale = QCheckBox("Int. scale", self) self.chk_fx_scale.setObjectName("chk_fx_scale") self.chk_fx_scale.setToolTip("<span>Display data with integer (fixpoint) scale.</span>") self.chk_fx_scale.setChecked(False) self.chk_stim_options = QCheckBox("Stim. Options", self) self.chk_stim_options.setObjectName("chk_stim_options") self.chk_stim_options.setToolTip("<span>Show stimulus options.</span>") self.chk_stim_options.setChecked(True) self.but_fft_win = QPushButton(self) self.but_fft_win.setText("WIN FFT") self.but_fft_win.setToolTip('<span> time and frequency response of FFT Window ' '(can be modified in the "Frequency" tab)</span>') self.but_fft_win.setCheckable(True) self.but_fft_win.setChecked(False) layH_ctrl_run = QHBoxLayout() layH_ctrl_run.addWidget(self.but_run) #layH_ctrl_run.addWidget(self.lbl_sim_select) layH_ctrl_run.addWidget(self.cmb_sim_select) layH_ctrl_run.addWidget(self.chk_auto_run) layH_ctrl_run.addStretch(1) layH_ctrl_run.addWidget(self.lbl_N_start) layH_ctrl_run.addWidget(self.led_N_start) layH_ctrl_run.addStretch(1) layH_ctrl_run.addWidget(self.lbl_N_points) layH_ctrl_run.addWidget(self.led_N_points) layH_ctrl_run.addStretch(2) layH_ctrl_run.addWidget(self.chk_fx_scale) layH_ctrl_run.addStretch(2) layH_ctrl_run.addWidget(self.chk_stim_options) layH_ctrl_run.addStretch(2) layH_ctrl_run.addWidget(self.but_fft_win) layH_ctrl_run.addStretch(10) #layH_ctrl_run.setContentsMargins(*params['wdg_margins']) self.wdg_ctrl_run = QWidget(self) self.wdg_ctrl_run.setLayout(layH_ctrl_run) # --- end of run control ---------------------------------------- # ----------- --------------------------------------------------- # Controls for time domain # --------------------------------------------------------------- plot_styles_list = ["None","Dots","Line","Line*","Stem","Stem*","Step","Step*"] lbl_plt_time_title = QLabel("<b>View:</b>", self) self.lbl_plt_time_stim = QLabel(to_html("Stimulus x", frmt='bi'), self) self.cmb_plt_time_stim = QComboBox(self) self.cmb_plt_time_stim.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_time_stim, self.plt_time_stim) self.cmb_plt_time_stim.setToolTip("<span>Plot style for stimulus.</span>") self.lbl_plt_time_stmq = QLabel(to_html(" Fixp. Stim. x_Q", frmt='bi'), self) self.cmb_plt_time_stmq = QComboBox(self) self.cmb_plt_time_stmq.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_time_stmq, self.plt_time_stmq) self.cmb_plt_time_stmq.setToolTip("<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>") lbl_plt_time_resp = QLabel(to_html(" Response y", frmt='bi'), self) self.cmb_plt_time_resp = QComboBox(self) self.cmb_plt_time_resp.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_time_resp, self.plt_time_resp) self.cmb_plt_time_resp.setToolTip("<span>Plot style for response.</span>") lbl_win_time = QLabel(to_html(" FFT Window", frmt='bi'), self) self.chk_win_time = QCheckBox(self) self.chk_win_time.setObjectName("chk_win_time") self.chk_win_time.setToolTip('<span>Show FFT windowing function (can be modified in the "Frequency" tab).</span>') self.chk_win_time.setChecked(False) lbl_log_time = QLabel(to_html("dB", frmt='b'), self) self.chk_log_time = QCheckBox(self) self.chk_log_time.setObjectName("chk_log_time") self.chk_log_time.setToolTip("<span>Logarithmic scale for y-axis.</span>") self.chk_log_time.setChecked(False) self.lbl_log_bottom_time = QLabel(to_html("min =", frmt='bi'), self) self.lbl_log_bottom_time.setVisible(True) self.led_log_bottom_time = QLineEdit(self) self.led_log_bottom_time.setText(str(self.bottom_t)) self.led_log_bottom_time.setToolTip("<span>Minimum display value for time " "and spectrogram plots with log. scale.</span>") self.led_log_bottom_time.setVisible(True) lbl_plt_time_spgr = QLabel(to_html(" Spectrogram", frmt='bi'), self) self.cmb_plt_time_spgr = QComboBox(self) self.cmb_plt_time_spgr.addItems(["None", "x[n]", "x_q[n]", "y[n]"]) qset_cmb_box(self.cmb_plt_time_spgr, self.plt_time_spgr) self.cmb_plt_time_spgr.setToolTip("<span>Show Spectrogram for selected signal.</span>") spgr_en = self.plt_time_spgr != "None" self.lbl_log_spgr_time = QLabel(to_html(" dB", frmt='b'), self) self.lbl_log_spgr_time.setVisible(spgr_en) self.chk_log_spgr_time = QCheckBox(self) self.chk_log_spgr_time.setObjectName("chk_log_spgr") self.chk_log_spgr_time.setToolTip("<span>Logarithmic scale for spectrogram.</span>") self.chk_log_spgr_time.setChecked(True) self.chk_log_spgr_time.setVisible(spgr_en) self.lbl_nfft_spgr_time = QLabel(to_html(" N_FFT =", frmt='bi'), self) self.lbl_nfft_spgr_time.setVisible(spgr_en) self.led_nfft_spgr_time = QLineEdit(self) self.led_nfft_spgr_time.setText(str(self.nfft_spgr_time)) self.led_nfft_spgr_time.setToolTip("<span>Number of FFT points per spectrogram segment.</span>") self.led_nfft_spgr_time.setVisible(spgr_en) self.lbl_ovlp_spgr_time = QLabel(to_html(" N_OVLP =", frmt='bi'), self) self.lbl_ovlp_spgr_time.setVisible(spgr_en) self.led_ovlp_spgr_time = QLineEdit(self) self.led_ovlp_spgr_time.setText(str(self.ovlp_spgr_time)) self.led_ovlp_spgr_time.setToolTip("<span>Number of overlap data points between spectrogram segments.</span>") self.led_ovlp_spgr_time.setVisible(spgr_en) self.lbl_mode_spgr_time = QLabel(to_html(" Mode", frmt='bi'), self) self.lbl_mode_spgr_time.setVisible(spgr_en) self.cmb_mode_spgr_time = QComboBox(self) spgr_modes = [("PSD","psd"), ("Mag.","magnitude"),\ ("Angle","angle"), ("Phase","phase")] for i in spgr_modes: self.cmb_mode_spgr_time.addItem(*i) qset_cmb_box(self.cmb_mode_spgr_time, self.mode_spgr_time, data=True) self.cmb_mode_spgr_time.setToolTip("<span>Spectrogram display mode.</span>") self.cmb_mode_spgr_time.setVisible(spgr_en) self.lbl_byfs_spgr_time = QLabel(to_html(" per f_S", frmt='b'), self) self.lbl_byfs_spgr_time.setVisible(spgr_en) self.chk_byfs_spgr_time = QCheckBox(self) self.chk_byfs_spgr_time.setObjectName("chk_log_spgr") self.chk_byfs_spgr_time.setToolTip("<span>Display spectral density i.e. scale by f_S</span>") self.chk_byfs_spgr_time.setChecked(True) self.chk_byfs_spgr_time.setVisible(spgr_en) # self.lbl_colorbar_time = QLabel(to_html(" Col.bar", frmt='b'), self) # self.lbl_colorbar_time.setVisible(spgr_en) # self.chk_colorbar_time = QCheckBox(self) # self.chk_colorbar_time.setObjectName("chk_colorbar_time") # self.chk_colorbar_time.setToolTip("<span>Enable colorbar</span>") # self.chk_colorbar_time.setChecked(True) # self.chk_colorbar_time.setVisible(spgr_en) self.chk_fx_limits = QCheckBox("Min/max.", self) self.chk_fx_limits.setObjectName("chk_fx_limits") self.chk_fx_limits.setToolTip("<span>Display limits of fixpoint range.</span>") self.chk_fx_limits.setChecked(False) layH_ctrl_time = QHBoxLayout() layH_ctrl_time.addWidget(lbl_plt_time_title) layH_ctrl_time.addStretch(1) layH_ctrl_time.addWidget(self.lbl_plt_time_stim) layH_ctrl_time.addWidget(self.cmb_plt_time_stim) # layH_ctrl_time.addWidget(self.lbl_plt_time_stmq) layH_ctrl_time.addWidget(self.cmb_plt_time_stmq) # layH_ctrl_time.addWidget(lbl_plt_time_resp) layH_ctrl_time.addWidget(self.cmb_plt_time_resp) # layH_ctrl_time.addWidget(lbl_win_time) layH_ctrl_time.addWidget(self.chk_win_time) layH_ctrl_time.addStretch(1) layH_ctrl_time.addWidget(lbl_log_time) layH_ctrl_time.addWidget(self.chk_log_time) layH_ctrl_time.addWidget(self.lbl_log_bottom_time) layH_ctrl_time.addWidget(self.led_log_bottom_time) # layH_ctrl_time.addStretch(1) # layH_ctrl_time.addWidget(lbl_plt_time_spgr) layH_ctrl_time.addWidget(self.cmb_plt_time_spgr) layH_ctrl_time.addWidget(self.lbl_log_spgr_time) layH_ctrl_time.addWidget(self.chk_log_spgr_time) layH_ctrl_time.addWidget(self.lbl_nfft_spgr_time) layH_ctrl_time.addWidget(self.led_nfft_spgr_time) layH_ctrl_time.addWidget(self.lbl_ovlp_spgr_time) layH_ctrl_time.addWidget(self.led_ovlp_spgr_time) layH_ctrl_time.addWidget(self.lbl_mode_spgr_time) layH_ctrl_time.addWidget(self.cmb_mode_spgr_time) layH_ctrl_time.addWidget(self.lbl_byfs_spgr_time) layH_ctrl_time.addWidget(self.chk_byfs_spgr_time) layH_ctrl_time.addStretch(2) layH_ctrl_time.addWidget(self.chk_fx_limits) layH_ctrl_time.addStretch(10) #layH_ctrl_time.setContentsMargins(*params['wdg_margins']) self.wdg_ctrl_time = QWidget(self) self.wdg_ctrl_time.setLayout(layH_ctrl_time) # ---- end time domain ------------------ # --------------------------------------------------------------- # Controls for frequency domain # --------------------------------------------------------------- lbl_plt_freq_title = QLabel("<b>View:</b>", self) self.lbl_plt_freq_stim = QLabel(to_html("Stimulus X", frmt='bi'), self) self.cmb_plt_freq_stim = QComboBox(self) self.cmb_plt_freq_stim.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_freq_stim, self.plt_freq_stim) self.cmb_plt_freq_stim.setToolTip("<span>Plot style for stimulus.</span>") self.lbl_plt_freq_stmq = QLabel(to_html(" Fixp. Stim. X_Q", frmt='bi'), self) self.cmb_plt_freq_stmq = QComboBox(self) self.cmb_plt_freq_stmq.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_freq_stmq, self.plt_freq_stmq) self.cmb_plt_freq_stmq.setToolTip("<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>") lbl_plt_freq_resp = QLabel(to_html(" Response Y", frmt='bi'), self) self.cmb_plt_freq_resp = QComboBox(self) self.cmb_plt_freq_resp.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_freq_resp, self.plt_freq_resp) self.cmb_plt_freq_resp.setToolTip("<span>Plot style for response.</span>") lbl_log_freq = QLabel(to_html("dB", frmt='b'), self) self.chk_log_freq = QCheckBox(self) self.chk_log_freq.setObjectName("chk_log_freq") self.chk_log_freq.setToolTip("<span>Logarithmic scale for y-axis.</span>") self.chk_log_freq.setChecked(True) self.lbl_log_bottom_freq = QLabel(to_html("min =", frmt='bi'), self) self.lbl_log_bottom_freq.setVisible(self.chk_log_freq.isChecked()) self.led_log_bottom_freq = QLineEdit(self) self.led_log_bottom_freq.setText(str(self.bottom_f)) self.led_log_bottom_freq.setToolTip("<span>Minimum display value for log. scale.</span>") self.led_log_bottom_freq.setVisible(self.chk_log_freq.isChecked()) if not self.chk_log_freq.isChecked(): self.bottom_f = 0 lbl_re_im_freq = QLabel(to_html("Re / Im", frmt='b'), self) self.chk_re_im_freq = QCheckBox(self) self.chk_re_im_freq.setObjectName("chk_re_im_freq") self.chk_re_im_freq.setToolTip("<span>Show real and imaginary part of spectrum</span>") self.chk_re_im_freq.setChecked(False) self.lbl_win_fft = QLabel(to_html("Window", frmt='bi'), self) self.cmb_win_fft = QComboBox(self) self.cmb_win_fft.addItems(get_window_names()) self.cmb_win_fft.setToolTip("FFT window type.") qset_cmb_box(self.cmb_win_fft, self.window_name) self.cmb_win_fft_variant = QComboBox(self) self.cmb_win_fft_variant.setToolTip("FFT window variant.") self.cmb_win_fft_variant.setVisible(False) self.lblWinPar1 = QLabel("Param1") self.ledWinPar1 = QLineEdit(self) self.ledWinPar1.setText("1") self.ledWinPar1.setObjectName("ledWinPar1") self.lblWinPar2 = QLabel("Param2") self.ledWinPar2 = QLineEdit(self) self.ledWinPar2.setText("2") self.ledWinPar2.setObjectName("ledWinPar2") self.chk_Hf = QCheckBox(self) self.chk_Hf.setObjectName("chk_Hf") self.chk_Hf.setToolTip("<span>Show ideal frequency response, calculated " "from the filter coefficients.</span>") self.chk_Hf.setChecked(False) self.chk_Hf_lbl = QLabel(to_html("H_id (f)", frmt="bi"), self) lbl_show_info_freq = QLabel(to_html("Info", frmt='b'), self) self.chk_show_info_freq = QCheckBox(self) self.chk_show_info_freq.setObjectName("chk_show_info_freq") self.chk_show_info_freq.setToolTip("<span>Show infos about signal power " "and window properties.</span>") self.chk_show_info_freq.setChecked(False) layH_ctrl_freq = QHBoxLayout() layH_ctrl_freq.addWidget(lbl_plt_freq_title) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(self.lbl_plt_freq_stim) layH_ctrl_freq.addWidget(self.cmb_plt_freq_stim) # layH_ctrl_freq.addWidget(self.lbl_plt_freq_stmq) layH_ctrl_freq.addWidget(self.cmb_plt_freq_stmq) # layH_ctrl_freq.addWidget(lbl_plt_freq_resp) layH_ctrl_freq.addWidget(self.cmb_plt_freq_resp) # layH_ctrl_freq.addWidget(self.chk_Hf_lbl) layH_ctrl_freq.addWidget(self.chk_Hf) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(lbl_log_freq) layH_ctrl_freq.addWidget(self.chk_log_freq) layH_ctrl_freq.addWidget(self.lbl_log_bottom_freq) layH_ctrl_freq.addWidget(self.led_log_bottom_freq) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(lbl_re_im_freq) layH_ctrl_freq.addWidget(self.chk_re_im_freq) layH_ctrl_freq.addStretch(2) layH_ctrl_freq.addWidget(self.lbl_win_fft) layH_ctrl_freq.addWidget(self.cmb_win_fft) layH_ctrl_freq.addWidget(self.cmb_win_fft_variant) layH_ctrl_freq.addWidget(self.lblWinPar1) layH_ctrl_freq.addWidget(self.ledWinPar1) layH_ctrl_freq.addWidget(self.lblWinPar2) layH_ctrl_freq.addWidget(self.ledWinPar2) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(lbl_show_info_freq) layH_ctrl_freq.addWidget(self.chk_show_info_freq) layH_ctrl_freq.addStretch(10) #layH_ctrl_freq.setContentsMargins(*params['wdg_margins']) self.wdg_ctrl_freq = QWidget(self) self.wdg_ctrl_freq.setLayout(layH_ctrl_freq) # ---- end Frequency Domain ------------------ # --------------------------------------------------------------- # Controls for stimuli # --------------------------------------------------------------- lbl_title_stim = QLabel("<b>Stimulus:</b>", self) self.lblStimulus = QLabel(to_html("Type", frmt='bi'), self) self.cmbStimulus = QComboBox(self) self.cmbStimulus.addItems(["None","Impulse","Step","StepErr","Cos","Sine", "Chirp", "Triang","Saw","Rect","Comb","AM","PM / FM","Formula"]) self.cmbStimulus.setToolTip("Stimulus type.") qset_cmb_box(self.cmbStimulus, self.stim) self.chk_stim_bl = QCheckBox("BL", self) self.chk_stim_bl.setToolTip("<span>The signal is bandlimited to the Nyquist frequency " "to avoid aliasing. However, it is much slower to generate " "than the regular version.</span>") self.chk_stim_bl.setChecked(True) self.chk_stim_bl.setObjectName("stim_bl") self.cmbChirpMethod = QComboBox(self) for t in [("Lin","Linear"),("Square","Quadratic"),("Log", "Logarithmic"), ("Hyper", "Hyperbolic")]: self.cmbChirpMethod.addItem(*t) qset_cmb_box(self.cmbChirpMethod, self.chirp_method, data=False) self.chk_scale_impz_f = QCheckBox("Scale", self) self.chk_scale_impz_f.setToolTip("<span>Scale the FFT of the impulse response with <i>N<sub>FFT</sub></i> " "so that it has the same magnitude as |H(f)|. DC and Noise need to be " "turned off.</span>") self.chk_scale_impz_f.setChecked(True) self.chk_scale_impz_f.setObjectName("scale_impz_f") self.lblDC = QLabel(to_html("DC =", frmt='bi'), self) self.ledDC = QLineEdit(self) self.ledDC.setText(str(self.DC)) self.ledDC.setToolTip("DC Level") self.ledDC.setObjectName("stimDC") layHCmbStim = QHBoxLayout() layHCmbStim.addWidget(self.cmbStimulus) layHCmbStim.addWidget(self.chk_stim_bl) layHCmbStim.addWidget(self.chk_scale_impz_f) layHCmbStim.addWidget(self.cmbChirpMethod) #---------------------------------------------- self.lblAmp1 = QLabel(to_html(" A_1", frmt='bi') + " =", self) self.ledAmp1 = QLineEdit(self) self.ledAmp1.setText(str(self.A1)) self.ledAmp1.setToolTip("Stimulus amplitude, complex values like 3j - 1 are allowed") self.ledAmp1.setObjectName("stimAmp1") self.lblAmp2 = QLabel(to_html(" A_2", frmt='bi') + " =", self) self.ledAmp2 = QLineEdit(self) self.ledAmp2.setText(str(self.A2)) self.ledAmp2.setToolTip("Stimulus amplitude 2, complex values like 3j - 1 are allowed") self.ledAmp2.setObjectName("stimAmp2") #---------------------------------------------- self.lblPhi1 = QLabel(to_html(" φ_1", frmt='bi') + " =", self) self.ledPhi1 = QLineEdit(self) self.ledPhi1.setText(str(self.phi1)) self.ledPhi1.setToolTip("Stimulus phase") self.ledPhi1.setObjectName("stimPhi1") self.lblPhU1 = QLabel(to_html("°", frmt='b'), self) self.lblPhi2 = QLabel(to_html(" φ_2", frmt='bi') + " =", self) self.ledPhi2 = QLineEdit(self) self.ledPhi2.setText(str(self.phi2)) self.ledPhi2.setToolTip("Stimulus phase 2") self.ledPhi2.setObjectName("stimPhi2") self.lblPhU2 = QLabel(to_html("°", frmt='b'), self) #---------------------------------------------- self.lblFreq1 = QLabel(to_html(" f_1", frmt='bi') + " =", self) self.ledFreq1 = QLineEdit(self) self.ledFreq1.setText(str(self.f1)) self.ledFreq1.setToolTip("Stimulus frequency 1") self.ledFreq1.setObjectName("stimFreq1") self.lblFreqUnit1 = QLabel("f_S", self) self.lblFreq2 = QLabel(to_html(" f_2", frmt='bi') + " =", self) self.ledFreq2 = QLineEdit(self) self.ledFreq2.setText(str(self.f2)) self.ledFreq2.setToolTip("Stimulus frequency 2") self.ledFreq2.setObjectName("stimFreq2") self.lblFreqUnit2 = QLabel("f_S", self) #---------------------------------------------- self.lblNoise = QLabel(to_html(" Noise", frmt='bi'), self) self.cmbNoise = QComboBox(self) self.cmbNoise.addItems(["None","Gauss","Uniform","PRBS"]) self.cmbNoise.setToolTip("Type of additive noise.") qset_cmb_box(self.cmbNoise, self.noise) self.lblNoi = QLabel("not initialized", self) self.ledNoi = QLineEdit(self) self.ledNoi.setText(str(self.noi)) self.ledNoi.setToolTip("not initialized") self.ledNoi.setObjectName("stimNoi") layGStim = QGridLayout() layGStim.addWidget(self.lblStimulus, 0, 0) layGStim.addWidget(self.lblDC, 1, 0) layGStim.addLayout(layHCmbStim, 0, 1) layGStim.addWidget(self.ledDC, 1, 1) layGStim.addWidget(self.lblAmp1, 0, 2) layGStim.addWidget(self.lblAmp2, 1, 2) layGStim.addWidget(self.ledAmp1, 0, 3) layGStim.addWidget(self.ledAmp2, 1, 3) layGStim.addWidget(self.lblPhi1, 0, 4) layGStim.addWidget(self.lblPhi2, 1, 4) layGStim.addWidget(self.ledPhi1, 0, 5) layGStim.addWidget(self.ledPhi2, 1, 5) layGStim.addWidget(self.lblPhU1, 0, 6) layGStim.addWidget(self.lblPhU2, 1, 6) layGStim.addWidget(self.lblFreq1, 0, 7) layGStim.addWidget(self.lblFreq2, 1, 7) layGStim.addWidget(self.ledFreq1, 0, 8) layGStim.addWidget(self.ledFreq2, 1, 8) layGStim.addWidget(self.lblFreqUnit1, 0, 9) layGStim.addWidget(self.lblFreqUnit2, 1, 9) layGStim.addWidget(self.lblNoise, 0, 10) layGStim.addWidget(self.lblNoi, 1, 10) layGStim.addWidget(self.cmbNoise, 0, 11) layGStim.addWidget(self.ledNoi, 1, 11) #---------------------------------------------- self.lblStimFormula = QLabel(to_html("x =", frmt='bi'), self) self.ledStimFormula = QLineEdit(self) self.ledStimFormula.setText(str(self.stim_formula)) self.ledStimFormula.setToolTip("<span>Enter formula for stimulus in numexpr syntax" "</span>") self.ledStimFormula.setObjectName("stimFormula") layH_ctrl_stim_formula = QHBoxLayout() layH_ctrl_stim_formula.addWidget(self.lblStimFormula) layH_ctrl_stim_formula.addWidget(self.ledStimFormula,10) #---------------------------------------------- #layG_ctrl_stim = QGridLayout() layH_ctrl_stim_par = QHBoxLayout() layH_ctrl_stim_par.addLayout(layGStim) layV_ctrl_stim = QVBoxLayout() layV_ctrl_stim.addLayout(layH_ctrl_stim_par) layV_ctrl_stim.addLayout(layH_ctrl_stim_formula) layH_ctrl_stim = QHBoxLayout() layH_ctrl_stim.addWidget(lbl_title_stim) layH_ctrl_stim.addStretch(1) layH_ctrl_stim.addLayout(layV_ctrl_stim) layH_ctrl_stim.addStretch(10) self.wdg_ctrl_stim = QWidget(self) self.wdg_ctrl_stim.setLayout(layH_ctrl_stim) # --------- end stimuli --------------------------------- # frequency widgets require special handling as they are scaled with f_s self.ledFreq1.installEventFilter(self) self.ledFreq2.installEventFilter(self) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- # --- run control --- self.led_N_start.editingFinished.connect(self.update_N) self.led_N_points.editingFinished.connect(self.update_N) # --- frequency control --- # careful! currentIndexChanged passes the current index to _update_win_fft self.cmb_win_fft.currentIndexChanged.connect(self._update_win_fft) self.ledWinPar1.editingFinished.connect(self._read_param1) self.ledWinPar2.editingFinished.connect(self._read_param2) # --- stimulus control --- self.chk_stim_options.clicked.connect(self._show_stim_options) self.chk_stim_bl.clicked.connect(self._enable_stim_widgets) self.cmbStimulus.currentIndexChanged.connect(self._enable_stim_widgets) self.cmbNoise.currentIndexChanged.connect(self._update_noi) self.ledNoi.editingFinished.connect(self._update_noi) self.ledAmp1.editingFinished.connect(self._update_amp1) self.ledAmp2.editingFinished.connect(self._update_amp2) self.ledPhi1.editingFinished.connect(self._update_phi1) self.ledPhi2.editingFinished.connect(self._update_phi2) self.cmbChirpMethod.currentIndexChanged.connect(self._update_chirp_method) self.ledDC.editingFinished.connect(self._update_DC) self.ledStimFormula.editingFinished.connect(self._update_stim_formula)
class Firwin(QWidget): FRMT = 'ba' # output format(s) of filter design routines 'zpk' / 'ba' / 'sos' # currently, only 'ba' is supported for firwin routines sig_tx = pyqtSignal(object) def __init__(self): QWidget.__init__(self) self.ft = 'FIR' self.fft_window = None # dictionary for firwin window settings self.win_dict = fb.fil[0]['win_fir'] c = Common() self.rt_dict = c.rt_base_iir self.rt_dict_add = { 'COM': { 'min': { 'msg': ('a', r"<br /><b>Note:</b> Filter order is only a rough approximation " "and most likely far too low!") }, 'man': { 'msg': ('a', r"Enter desired filter order <b><i>N</i></b> and " "<b>-6 dB</b> pass band corner " "frequency(ies) <b><i>F<sub>C</sub></i></b> .") }, }, 'LP': { 'man': {}, 'min': {} }, 'HP': { 'man': { 'msg': ('a', r"<br /><b>Note:</b> Order needs to be odd!") }, 'min': {} }, 'BS': { 'man': { 'msg': ('a', r"<br /><b>Note:</b> Order needs to be odd!") }, 'min': {} }, 'BP': { 'man': {}, 'min': {} }, } self.info = """**Windowed FIR filters** are designed by truncating the infinite impulse response of an ideal filter with a window function. The kind of used window has strong influence on ripple etc. of the resulting filter. **Design routines:** ``scipy.signal.firwin()`` """ #self.info_doc = [] is set in self._update_UI() #------------------- end of static info for filter tree --------------- #---------------------------------------------------------------------- def construct_UI(self): """ Create additional subwidget(s) needed for filter design: These subwidgets are instantiated dynamically when needed in select_filter.py using the handle to the filter object, fb.filObj . """ # Combobox for selecting the algorithm to estimate minimum filter order self.cmb_firwin_alg = QComboBox(self) self.cmb_firwin_alg.setObjectName('wdg_cmb_firwin_alg') self.cmb_firwin_alg.addItems(['ichige', 'kaiser', 'herrmann']) # Minimum size, can be changed in the upper hierarchy levels using layouts: self.cmb_firwin_alg.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmb_firwin_alg.hide() # Combobox for selecting the window used for filter design self.cmb_firwin_win = QComboBox(self) self.cmb_firwin_win.addItems(get_window_names()) self.cmb_firwin_win.setObjectName('wdg_cmb_firwin_win') # Minimum size, can be changed in the upper hierarchy levels using layouts: self.cmb_firwin_win.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.but_fft_win = QPushButton(self) self.but_fft_win.setText("WIN FFT") self.but_fft_win.setToolTip( "Show time and frequency response of FFT Window") self.but_fft_win.setCheckable(True) self.but_fft_win.setChecked(False) self.lblWinPar1 = QLabel("a", self) self.lblWinPar1.setObjectName('wdg_lbl_firwin_1') self.ledWinPar1 = QLineEdit(self) self.ledWinPar1.setText("0.5") self.ledWinPar1.setObjectName('wdg_led_firwin_1') self.lblWinPar1.setVisible(False) self.ledWinPar1.setVisible(False) self.lblWinPar2 = QLabel("b", self) self.lblWinPar2.setObjectName('wdg_lbl_firwin_2') self.ledWinPar2 = QLineEdit(self) self.ledWinPar2.setText("0.5") self.ledWinPar2.setObjectName('wdg_led_firwin_2') self.ledWinPar2.setVisible(False) self.lblWinPar2.setVisible(False) self.layHWin1 = QHBoxLayout() self.layHWin1.addWidget(self.cmb_firwin_win) self.layHWin1.addWidget(self.but_fft_win) self.layHWin1.addWidget(self.cmb_firwin_alg) self.layHWin2 = QHBoxLayout() self.layHWin2.addWidget(self.lblWinPar1) self.layHWin2.addWidget(self.ledWinPar1) self.layHWin2.addWidget(self.lblWinPar2) self.layHWin2.addWidget(self.ledWinPar2) self.layVWin = QVBoxLayout() self.layVWin.addLayout(self.layHWin1) self.layVWin.addLayout(self.layHWin2) self.layVWin.setContentsMargins(0, 0, 0, 0) # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits) self.wdg_fil = QWidget(self) self.wdg_fil.setObjectName('wdg_fil') self.wdg_fil.setLayout(self.layVWin) #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.cmb_firwin_alg.activated.connect(self._update_win_fft) self.cmb_firwin_win.activated.connect(self._update_win_fft) self.ledWinPar1.editingFinished.connect(self._read_param1) self.ledWinPar2.editingFinished.connect(self._read_param2) self.but_fft_win.clicked.connect(self.show_fft_win) #---------------------------------------------------------------------- self._load_dict() # get initial / last setting from dictionary self._update_win_fft() #============================================================================= # Copied from impz() #============================================================================== def _read_param1(self): """Read out textbox when editing is finished and update dict and fft window""" param = safe_eval(self.ledWinPar1.text(), self.win_dict['par'][0]['val'], sign='pos', return_type='float') if param < self.win_dict['par'][0]['min']: param = self.win_dict['par'][0]['min'] elif param > self.win_dict['par'][0]['max']: param = self.win_dict['par'][0]['max'] self.ledWinPar1.setText(str(param)) self.win_dict['par'][0]['val'] = param self._update_win_fft() def _read_param2(self): """Read out textbox when editing is finished and update dict and fft window""" param = safe_eval(self.ledWinPar2.text(), self.win_dict['par'][1]['val'], return_type='float') if param < self.win_dict['par'][1]['min']: param = self.win_dict['par'][1]['min'] elif param > self.win_dict['par'][1]['max']: param = self.win_dict['par'][1]['max'] self.ledWinPar2.setText(str(param)) self.win_dict['par'][1]['val'] = param self._update_win_fft() def _update_win_fft(self): """ Update window type for FirWin """ self.alg = str(self.cmb_firwin_alg.currentText()) self.fir_window_name = qget_cmb_box(self.cmb_firwin_win, data=False) self.win = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) n_par = self.win_dict['n_par'] self.lblWinPar1.setVisible(n_par > 0) self.ledWinPar1.setVisible(n_par > 0) self.lblWinPar2.setVisible(n_par > 1) self.ledWinPar2.setVisible(n_par > 1) if n_par > 0: self.lblWinPar1.setText( to_html(self.win_dict['par'][0]['name'] + " =", frmt='bi')) self.ledWinPar1.setText(str(self.win_dict['par'][0]['val'])) self.ledWinPar1.setToolTip(self.win_dict['par'][0]['tooltip']) if n_par > 1: self.lblWinPar2.setText( to_html(self.win_dict['par'][1]['name'] + " =", frmt='bi')) self.ledWinPar2.setText(str(self.win_dict['par'][1]['val'])) self.ledWinPar2.setToolTip(self.win_dict['par'][1]['tooltip']) # sig_tx -> select_filter -> filter_specs self.sig_tx.emit({'sender': __name__, 'filt_changed': 'firwin'}) #============================================================================= def _load_dict(self): """ Reload window selection and parameters from filter dictionary and set UI elements accordingly. load_dict() is called upon initialization and when the filter is loaded from disk. """ self.N = fb.fil[0]['N'] win_idx = 0 alg_idx = 0 if 'wdg_fil' in fb.fil[0] and 'firwin' in fb.fil[0]['wdg_fil']: wdg_fil_par = fb.fil[0]['wdg_fil']['firwin'] if 'win' in wdg_fil_par: if np.isscalar( wdg_fil_par['win']): # true for strings (non-vectors) window = wdg_fil_par['win'] else: window = wdg_fil_par['win'][0] self.ledWinPar1.setText(str(wdg_fil_par['win'][1])) if len(wdg_fil_par['win']) > 2: self.ledWinPar2.setText(str(wdg_fil_par['win'][2])) # find index for window string win_idx = self.cmb_firwin_win.findText( window, Qt.MatchFixedString) # case insensitive flag if win_idx == -1: # Key does not exist, use first entry instead win_idx = 0 if 'alg' in wdg_fil_par: alg_idx = self.cmb_firwin_alg.findText(wdg_fil_par['alg'], Qt.MatchFixedString) if alg_idx == -1: # Key does not exist, use first entry instead alg_idx = 0 self.cmb_firwin_win.setCurrentIndex( win_idx) # set index for window and self.cmb_firwin_alg.setCurrentIndex(alg_idx) # and algorithm cmbBox def _store_entries(self): """ Store window and alg. selection and parameter settings (part of self.firWindow, if any) in filter dictionary. """ if not 'wdg_fil' in fb.fil[0]: fb.fil[0].update({'wdg_fil': {}}) fb.fil[0]['wdg_fil'].update( {'firwin': { 'win': self.firWindow, 'alg': self.alg }}) def _get_params(self, fil_dict): """ Translate parameters from the passed dictionary to instance parameters, scaling / transforming them if needed. """ self.N = fil_dict['N'] self.F_PB = fil_dict['F_PB'] self.F_SB = fil_dict['F_SB'] self.F_PB2 = fil_dict['F_PB2'] self.F_SB2 = fil_dict['F_SB2'] self.F_C = fil_dict['F_C'] self.F_C2 = fil_dict['F_C2'] # firwin amplitude specs are linear (not in dBs) self.A_PB = fil_dict['A_PB'] self.A_PB2 = fil_dict['A_PB2'] self.A_SB = fil_dict['A_SB'] self.A_SB2 = fil_dict['A_SB2'] # self.alg = 'ichige' # algorithm for determining the minimum order # self.alg = self.cmb_firwin_alg.currentText() def _test_N(self): """ Warn the user if the calculated order is too high for a reasonable filter design. """ if self.N > 1000: return qfilter_warning(self, self.N, "FirWin") else: return True def _save(self, fil_dict, arg): """ Convert between poles / zeros / gain, filter coefficients (polynomes) and second-order sections and store all available formats in the passed dictionary 'fil_dict'. """ fil_save(fil_dict, arg, self.FRMT, __name__) try: # has the order been calculated by a "min" filter design? fil_dict['N'] = self.N # yes, update filterbroker except AttributeError: pass # self._store_entries() #------------------------------------------------------------------------------ def firwin(self, numtaps, cutoff, window=None, pass_zero=True, scale=True, nyq=1.0, fs=None): """ FIR filter design using the window method. This is more or less the same as `scipy.signal.firwin` with the exception that an ndarray with the window values can be passed as an alternative to the window name. The parameters "width" (specifying a Kaiser window) and "fs" have been omitted, they are not needed here. This function computes the coefficients of a finite impulse response filter. The filter will have linear phase; it will be Type I if `numtaps` is odd and Type II if `numtaps` is even. Type II filters always have zero response at the Nyquist rate, so a ValueError exception is raised if firwin is called with `numtaps` even and having a passband whose right end is at the Nyquist rate. Parameters ---------- numtaps : int Length of the filter (number of coefficients, i.e. the filter order + 1). `numtaps` must be even if a passband includes the Nyquist frequency. cutoff : float or 1D array_like Cutoff frequency of filter (expressed in the same units as `nyq`) OR an array of cutoff frequencies (that is, band edges). In the latter case, the frequencies in `cutoff` should be positive and monotonically increasing between 0 and `nyq`. The values 0 and `nyq` must not be included in `cutoff`. window : ndarray or string string: use the window with the passed name from scipy.signal.windows ndarray: The window values - this is an addition to the original firwin routine. pass_zero : bool, optional If True, the gain at the frequency 0 (i.e. the "DC gain") is 1. Otherwise the DC gain is 0. scale : bool, optional Set to True to scale the coefficients so that the frequency response is exactly unity at a certain frequency. That frequency is either: - 0 (DC) if the first passband starts at 0 (i.e. pass_zero is True) - `nyq` (the Nyquist rate) if the first passband ends at `nyq` (i.e the filter is a single band highpass filter); center of first passband otherwise nyq : float, optional Nyquist frequency. Each frequency in `cutoff` must be between 0 and `nyq`. Returns ------- h : (numtaps,) ndarray Coefficients of length `numtaps` FIR filter. Raises ------ ValueError If any value in `cutoff` is less than or equal to 0 or greater than or equal to `nyq`, if the values in `cutoff` are not strictly monotonically increasing, or if `numtaps` is even but a passband includes the Nyquist frequency. See also -------- scipy.firwin """ cutoff = np.atleast_1d(cutoff) / float(nyq) # Check for invalid input. if cutoff.ndim > 1: raise ValueError("The cutoff argument must be at most " "one-dimensional.") if cutoff.size == 0: raise ValueError("At least one cutoff frequency must be given.") if cutoff.min() <= 0 or cutoff.max() >= 1: raise ValueError( "Invalid cutoff frequency {0}: frequencies must be " "greater than 0 and less than nyq.".format(cutoff)) if np.any(np.diff(cutoff) <= 0): raise ValueError("Invalid cutoff frequencies: the frequencies " "must be strictly increasing.") pass_nyquist = bool(cutoff.size & 1) ^ pass_zero if pass_nyquist and numtaps % 2 == 0: raise ValueError( "A filter with an even number of coefficients must " "have zero response at the Nyquist rate.") # Insert 0 and/or 1 at the ends of cutoff so that the length of cutoff # is even, and each pair in cutoff corresponds to passband. cutoff = np.hstack(([0.0] * pass_zero, cutoff, [1.0] * pass_nyquist)) # `bands` is a 2D array; each row gives the left and right edges of # a passband. bands = cutoff.reshape(-1, 2) # Build up the coefficients. alpha = 0.5 * (numtaps - 1) m = np.arange(0, numtaps) - alpha h = 0 for left, right in bands: h += right * sinc(right * m) h -= left * sinc(left * m) if type(window) == str: # Get and apply the window function. from scipy.signal.signaltools import get_window win = get_window(window, numtaps, fftbins=False) elif type(window) == np.ndarray: win = window else: logger.error( "The 'window' was neither a string nor a numpy array, it could not be evaluated." ) return None # apply the window function. h *= win # Now handle scaling if desired. if scale: # Get the first passband. left, right = bands[0] if left == 0: scale_frequency = 0.0 elif right == 1: scale_frequency = 1.0 else: scale_frequency = 0.5 * (left + right) c = np.cos(np.pi * m * scale_frequency) s = np.sum(h * c) h /= s return h def _firwin_ord(self, F, W, A, alg): #http://www.mikroe.com/chapters/view/72/chapter-2-fir-filters/ delta_f = abs(F[1] - F[0]) * 2 # referred to f_Ny delta_A = np.sqrt(A[0] * A[1]) if self.fir_window_name == 'kaiser': N, beta = sig.kaiserord(20 * np.log10(np.abs(fb.fil[0]['A_SB'])), delta_f) self.ledWinPar1.setText(str(beta)) fb.fil[0]['wdg_fil'][1] = beta self._update_UI() else: N = remezord(F, W, A, fs=1, alg=alg)[0] return N def LPmin(self, fil_dict): self._get_params(fil_dict) self.N = self._firwin_ord([self.F_PB, self.F_SB], [1, 0], [self.A_PB, self.A_SB], alg=self.alg) if not self._test_N(): return -1 self.fir_window = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) fil_dict['F_C'] = (self.F_SB + self.F_PB ) / 2 # use average of calculated F_PB and F_SB self._save( fil_dict, self.firwin(self.N, fil_dict['F_C'], window=self.fir_window, nyq=0.5)) def LPman(self, fil_dict): self._get_params(fil_dict) if not self._test_N(): return -1 self.fir_window = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) self._save( fil_dict, self.firwin(self.N, fil_dict['F_C'], window=self.fir_window, nyq=0.5)) def HPmin(self, fil_dict): self._get_params(fil_dict) N = self._firwin_ord([self.F_SB, self.F_PB], [0, 1], [self.A_SB, self.A_PB], alg=self.alg) self.N = round_odd(N) # enforce odd order if not self._test_N(): return -1 self.fir_window = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) fil_dict['F_C'] = (self.F_SB + self.F_PB ) / 2 # use average of calculated F_PB and F_SB self._save( fil_dict, self.firwin(self.N, fil_dict['F_C'], window=self.fir_window, pass_zero=False, nyq=0.5)) def HPman(self, fil_dict): self._get_params(fil_dict) self.N = round_odd(self.N) # enforce odd order if not self._test_N(): return -1 self.fir_window = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) self._save( fil_dict, self.firwin(self.N, fil_dict['F_C'], window=self.fir_window, pass_zero=False, nyq=0.5)) # For BP and BS, F_PB and F_SB have two elements each def BPmin(self, fil_dict): self._get_params(fil_dict) self.N = remezord([self.F_SB, self.F_PB, self.F_PB2, self.F_SB2], [0, 1, 0], [self.A_SB, self.A_PB, self.A_SB2], fs=1, alg=self.alg)[0] if not self._test_N(): return -1 self.fir_window = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) fil_dict['F_C'] = (self.F_SB + self.F_PB ) / 2 # use average of calculated F_PB and F_SB fil_dict['F_C2'] = (self.F_SB2 + self.F_PB2 ) / 2 # use average of calculated F_PB and F_SB self._save( fil_dict, self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']], window=self.fir_window, pass_zero=False, nyq=0.5)) def BPman(self, fil_dict): self._get_params(fil_dict) if not self._test_N(): return -1 self.fir_window = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) self._save( fil_dict, self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']], window=self.fir_window, pass_zero=False, nyq=0.5)) def BSmin(self, fil_dict): self._get_params(fil_dict) N = remezord([self.F_PB, self.F_SB, self.F_SB2, self.F_PB2], [1, 0, 1], [self.A_PB, self.A_SB, self.A_PB2], fs=1, alg=self.alg)[0] self.N = round_odd(N) # enforce odd order if not self._test_N(): return -1 self.fir_window = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) fil_dict['F_C'] = (self.F_SB + self.F_PB ) / 2 # use average of calculated F_PB and F_SB fil_dict['F_C2'] = (self.F_SB2 + self.F_PB2 ) / 2 # use average of calculated F_PB and F_SB self._save( fil_dict, self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']], window=self.fir_window, pass_zero=True, nyq=0.5)) def BSman(self, fil_dict): self._get_params(fil_dict) self.N = round_odd(self.N) # enforce odd order if not self._test_N(): return -1 self.fir_window = calc_window_function(self.win_dict, self.fir_window_name, N=self.N, sym=True) self._save( fil_dict, self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']], window=self.fir_window, pass_zero=True, nyq=0.5)) #------------------------------------------------------------------------------ def show_fft_win(self): """ Pop-up FFT window """ if self.but_fft_win.isChecked(): qstyle_widget(self.but_fft_win, "changed") else: qstyle_widget(self.but_fft_win, "normal") if self.fft_window is None: # no handle to the window? Create a new instance if self.but_fft_win.isChecked(): # important: Handle to window must be class attribute # pass the name of the dictionary where parameters are stored and # whether a symmetric window or one that can be continued periodically # will be constructed self.fft_window = Plot_FFT_win(self, win_dict=self.win_dict, sym=True, title="pyFDA FIR Window Viewer") self.sig_tx.connect(self.fft_window.sig_rx) self.fft_window.sig_tx.connect(self.close_fft_win) self.fft_window.show( ) # modeless i.e. non-blocking popup window else: if not self.but_fft_win.isChecked(): if self.fft_window is None: logger.warning("FFT window is already closed!") else: self.fft_window.close() def close_fft_win(self): self.fft_window = None self.but_fft_win.setChecked(False) qstyle_widget(self.but_fft_win, "normal")
class Firwin(QWidget): FRMT = 'ba' # output format(s) of filter design routines 'zpk' / 'ba' / 'sos' # currently, only 'ba' is supported for firwin routines sig_tx = pyqtSignal( object) # local signal between FFT widget and FFTWin_Selector sig_tx_local = pyqtSignal(object) from pyfda.libs.pyfda_qt_lib import emit def __init__(self): QWidget.__init__(self) self.ft = 'FIR' win_names_list = [ "Boxcar", "Rectangular", "Barthann", "Bartlett", "Blackman", "Blackmanharris", "Bohman", "Cosine", "Dolph-Chebyshev", "Flattop", "General Gaussian", "Gauss", "Hamming", "Hann", "Kaiser", "Nuttall", "Parzen", "Slepian", "Triangular", "Tukey" ] self.cur_win_name = "Kaiser" # set initial window type self.alg = "ichige" # initialize windows dict with the list above for firwin window settings self.win_dict = get_windows_dict(win_names_list=win_names_list, cur_win_name=self.cur_win_name) # get initial / last setting from dictionary, updating self.win_dict self._load_dict() # instantiate FFT window with windows dict self.fft_widget = Plot_FFT_win(self, win_dict=self.win_dict, sym=True, title="pyFDA FIR Window Viewer") # hide window initially, this is modeless i.e. a non-blocking popup window self.fft_widget.hide() c = Common() self.rt_dict = c.rt_base_iir self.rt_dict_add = { 'COM': { 'min': { 'msg': ('a', "<br /><b>Note:</b> Filter order is only a rough " "approximation and most likely far too low!") }, 'man': { 'msg': ('a', "Enter desired filter order <b><i>N</i></b> and " "<b>-6 dB</b> pass band corner " "frequency(ies) <b><i>F<sub>C</sub></i></b> .") }, }, 'LP': { 'man': {}, 'min': {} }, 'HP': { 'man': { 'msg': ('a', r"<br /><b>Note:</b> Order needs to be odd!") }, 'min': {} }, 'BS': { 'man': { 'msg': ('a', r"<br /><b>Note:</b> Order needs to be odd!") }, 'min': {} }, 'BP': { 'man': {}, 'min': {} }, } self.info = """**Windowed FIR filters** are designed by truncating the infinite impulse response of an ideal filter with a window function. The kind of used window has strong influence on ripple etc. of the resulting filter. **Design routines:** ``scipy.signal.firwin()`` """ # self.info_doc = [] is set in self._update_UI() # ------------------- end of static info for filter tree --------------- # ------------------------------------------------------------------------------ def process_sig_rx(self, dict_sig=None): """ Process local signals from / for - FFT window widget - qfft_win_select """ logger.debug("SIG_RX - vis: {0}\n{1}".format(self.isVisible(), pprint_log(dict_sig))) if dict_sig['id'] == id(self): logger.warning(f"Stopped infinite loop:\n{pprint_log(dict_sig)}") # --- signals coming from the FFT window widget or the qfft_win_select if dict_sig['class'] in {'Plot_FFT_win', 'QFFTWinSelector'}: if 'closeEvent' in dict_sig: # hide FFT window windget and return self.hide_fft_wdg() return else: if 'view_changed' in dict_sig and 'fft_win' in dict_sig[ 'view_changed']: # self._update_fft_window() # TODO: needed? # local connection to FFT window widget and qfft_win_select self.emit(dict_sig, sig_name='sig_tx_local') # global connection to upper hierachies # send notification that filter design has changed self.emit({'filt_changed': 'firwin'}) # -------------------------------------------------------------------------- def construct_UI(self): """ Create additional subwidget(s) needed for filter design: These subwidgets are instantiated dynamically when needed in select_filter.py using the handle to the filter object, fb.filObj . """ # Combobox for selecting the algorithm to estimate minimum filter order self.cmb_firwin_alg = QComboBox(self) self.cmb_firwin_alg.setObjectName('wdg_cmb_firwin_alg') self.cmb_firwin_alg.addItems(['ichige', 'kaiser', 'herrmann']) # Minimum size, can be changed in the upper hierarchy levels using layouts: self.cmb_firwin_alg.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmb_firwin_alg.hide() self.qfft_win_select = QFFTWinSelector(self, self.win_dict) # Minimum size, can be changed in the upper hierarchy levels using layouts: # self.qfft_win_select.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.but_fft_wdg = QPushButton(self) self.but_fft_wdg.setIcon(QIcon(":/fft.svg")) but_height = self.qfft_win_select.sizeHint().height() self.but_fft_wdg.setIconSize(QSize(but_height, but_height)) self.but_fft_wdg.setFixedSize(QSize(but_height, but_height)) self.but_fft_wdg.setToolTip( '<span>Show / hide FFT widget (select window type ' ' and display its properties).</span>') self.but_fft_wdg.setCheckable(True) self.but_fft_wdg.setChecked(False) self.layHWin1 = QHBoxLayout() # self.layHWin1.addWidget(self.cmb_firwin_win) # self.layHWin1.addWidget(self.but_fft_wdg) self.layHWin1.addWidget(self.cmb_firwin_alg) self.layHWin2 = QHBoxLayout() self.layHWin2.addWidget(self.but_fft_wdg) self.layHWin2.addWidget(self.qfft_win_select) self.layVWin = QVBoxLayout() self.layVWin.addLayout(self.layHWin1) self.layVWin.addLayout(self.layHWin2) self.layVWin.setContentsMargins(0, 0, 0, 0) # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits) self.wdg_fil = QWidget(self) self.wdg_fil.setObjectName('wdg_fil') self.wdg_fil.setLayout(self.layVWin) # ---------------------------------------------------------------------- # GLOBAL SIGNALS & SLOTs # ---------------------------------------------------------------------- # connect FFT widget to qfft_selector and vice versa and to signals upstream: self.fft_widget.sig_tx.connect(self.process_sig_rx) self.qfft_win_select.sig_tx.connect(self.process_sig_rx) # connect process_sig_rx output to both FFT widgets self.sig_tx_local.connect(self.fft_widget.sig_rx) self.sig_tx_local.connect(self.qfft_win_select.sig_rx) # ---------------------------------------------------------------------- # SIGNALS & SLOTs # ---------------------------------------------------------------------- self.cmb_firwin_alg.currentIndexChanged.connect( self._update_fft_window) self.but_fft_wdg.clicked.connect(self.toggle_fft_wdg) # ---------------------------------------------------------------------- # ============================================================================== def _update_fft_window(self): """ Update window type for FirWin - unneeded at the moment """ self.alg = str(self.cmb_firwin_alg.currentText()) self.emit({'filt_changed': 'firwin'}) # -------------------------------------------------------------------------- def _load_dict(self): """ Reload window selection and parameters from filter dictionary and set UI elements accordingly. load_dict() is called upon initialization and when the filter is loaded from disk. """ self.N = fb.fil[0]['N'] # alg_idx = 0 if 'wdg_fil' in fb.fil[0] and 'firwin' in fb.fil[0]['wdg_fil']\ and type(fb.fil[0]['wdg_fil']['firwin']) is dict: self.win_dict = fb.fil[0]['wdg_fil']['firwin'] self.emit({'view_changed': 'fft_win_type'}, sig_name='sig_tx_local') # -------------------------------------------------------------------------- def _store_dict(self): """ Store window and parameter settings using `self.win_dict` in filter dictionary. """ if 'wdg_fil' not in fb.fil[0]: fb.fil[0].update({'wdg_fil': {}}) fb.fil[0]['wdg_fil'].update({'firwin': self.win_dict}) # -------------------------------------------------------------------------- def _get_params(self, fil_dict): """ Translate parameters from the passed dictionary to instance parameters, scaling / transforming them if needed. """ self.N = fil_dict['N'] self.F_PB = fil_dict['F_PB'] self.F_SB = fil_dict['F_SB'] self.F_PB2 = fil_dict['F_PB2'] self.F_SB2 = fil_dict['F_SB2'] self.F_C = fil_dict['F_C'] self.F_C2 = fil_dict['F_C2'] # firwin amplitude specs are linear (not in dBs) self.A_PB = fil_dict['A_PB'] self.A_PB2 = fil_dict['A_PB2'] self.A_SB = fil_dict['A_SB'] self.A_SB2 = fil_dict['A_SB2'] # self.alg = 'ichige' # algorithm for determining the minimum order # self.alg = self.cmb_firwin_alg.currentText() def _test_N(self): """ Warn the user if the calculated order is too high for a reasonable filter design. """ if self.N > 1000: return qfilter_warning(self, self.N, "FirWin") else: return True def _save(self, fil_dict, arg): """ Convert between poles / zeros / gain, filter coefficients (polynomes) and second-order sections and store all available formats in the passed dictionary 'fil_dict'. """ fil_save(fil_dict, arg, self.FRMT, __name__) try: # has the order been calculated by a "min" filter design? fil_dict['N'] = self.N # yes, update filterbroker except AttributeError: pass self._store_dict() # ------------------------------------------------------------------------------ def firwin(self, numtaps, cutoff, window=None, pass_zero=True, scale=True, nyq=1.0, fs=None): """ FIR filter design using the window method. This is more or less the same as `scipy.signal.firwin` with the exception that an ndarray with the window values can be passed as an alternative to the window name. The parameters "width" (specifying a Kaiser window) and "fs" have been omitted, they are not needed here. This function computes the coefficients of a finite impulse response filter. The filter will have linear phase; it will be Type I if `numtaps` is odd and Type II if `numtaps` is even. Type II filters always have zero response at the Nyquist rate, so a ValueError exception is raised if firwin is called with `numtaps` even and having a passband whose right end is at the Nyquist rate. Parameters ---------- numtaps : int Length of the filter (number of coefficients, i.e. the filter order + 1). `numtaps` must be even if a passband includes the Nyquist frequency. cutoff : float or 1D array_like Cutoff frequency of filter (expressed in the same units as `nyq`) OR an array of cutoff frequencies (that is, band edges). In the latter case, the frequencies in `cutoff` should be positive and monotonically increasing between 0 and `nyq`. The values 0 and `nyq` must not be included in `cutoff`. window : ndarray or string string: use the window with the passed name from scipy.signal.windows ndarray: The window values - this is an addition to the original firwin routine. pass_zero : bool, optional If True, the gain at the frequency 0 (i.e. the "DC gain") is 1. Otherwise the DC gain is 0. scale : bool, optional Set to True to scale the coefficients so that the frequency response is exactly unity at a certain frequency. That frequency is either: - 0 (DC) if the first passband starts at 0 (i.e. pass_zero is True) - `nyq` (the Nyquist rate) if the first passband ends at `nyq` (i.e the filter is a single band highpass filter); center of first passband otherwise nyq : float, optional Nyquist frequency. Each frequency in `cutoff` must be between 0 and `nyq`. Returns ------- h : (numtaps,) ndarray Coefficients of length `numtaps` FIR filter. Raises ------ ValueError If any value in `cutoff` is less than or equal to 0 or greater than or equal to `nyq`, if the values in `cutoff` are not strictly monotonically increasing, or if `numtaps` is even but a passband includes the Nyquist frequency. See also -------- scipy.firwin """ cutoff = np.atleast_1d(cutoff) / float(nyq) # Check for invalid input. if cutoff.ndim > 1: raise ValueError("The cutoff argument must be at most " "one-dimensional.") if cutoff.size == 0: raise ValueError("At least one cutoff frequency must be given.") if cutoff.min() <= 0 or cutoff.max() >= 1: raise ValueError( "Invalid cutoff frequency {0}: frequencies must be " "greater than 0 and less than nyq.".format(cutoff)) if np.any(np.diff(cutoff) <= 0): raise ValueError("Invalid cutoff frequencies: the frequencies " "must be strictly increasing.") pass_nyquist = bool(cutoff.size & 1) ^ pass_zero if pass_nyquist and numtaps % 2 == 0: raise ValueError( "A filter with an even number of coefficients must " "have zero response at the Nyquist rate.") # Insert 0 and/or 1 at the ends of cutoff so that the length of cutoff # is even, and each pair in cutoff corresponds to passband. cutoff = np.hstack(([0.0] * pass_zero, cutoff, [1.0] * pass_nyquist)) # `bands` is a 2D array; each row gives the left and right edges of # a passband. bands = cutoff.reshape(-1, 2) # Build up the coefficients. alpha = 0.5 * (numtaps - 1) m = np.arange(0, numtaps) - alpha h = 0 for left, right in bands: h += right * sinc(right * m) h -= left * sinc(left * m) if type(window) == str: # Get and apply the window function. # from scipy.signal.signaltools import get_window win = signaltools.get_window(window, numtaps, fftbins=False) elif type(window) == np.ndarray: win = window else: logger.error( "The 'window' was neither a string nor a numpy array, " "it could not be evaluated.") return None # apply the window function. h *= win # Now handle scaling if desired. if scale: # Get the first passband. left, right = bands[0] if left == 0: scale_frequency = 0.0 elif right == 1: scale_frequency = 1.0 else: scale_frequency = 0.5 * (left + right) c = np.cos(np.pi * m * scale_frequency) s = np.sum(h * c) h /= s return h def _firwin_ord(self, F, W, A, alg): # http://www.mikroe.com/chapters/view/72/chapter-2-fir-filters/ delta_f = abs(F[1] - F[0]) * 2 # referred to f_Ny # delta_A = np.sqrt(A[0] * A[1]) if "Kaiser" in self.win_dict and self.win_dict[ 'cur_win_name'] == "Kaiser": N, beta = sig.kaiserord(20 * np.log10(np.abs(fb.fil[0]['A_SB'])), delta_f) # logger.warning(f"N={N}, beta={beta}, A_SB={fb.fil[0]['A_SB']}") self.win_dict["Kaiser"]["par"][0]["val"] = beta self.qfft_win_select.led_win_par_0.setText(str(beta)) self.qfft_win_select.ui2dict_params( ) # pass changed parameter to other widgets else: N = remezord(F, W, A, fs=1, alg=alg)[0] self.emit({'view_changed': 'fft_win_type'}, sig_name='sig_tx_local') return N def LPmin(self, fil_dict): self._get_params(fil_dict) self.N = self._firwin_ord([self.F_PB, self.F_SB], [1, 0], [self.A_PB, self.A_SB], alg=self.alg) if not self._test_N(): return -1 fil_dict['F_C'] = (self.F_SB + self.F_PB) / 2 # average calculated F_PB and F_SB self._save( fil_dict, self.firwin(self.N, fil_dict['F_C'], nyq=0.5, window=self.qfft_win_select.get_window(self.N, sym=True))) def LPman(self, fil_dict): self._get_params(fil_dict) if not self._test_N(): return -1 logger.warning(self.win_dict["cur_win_name"]) self._save( fil_dict, self.firwin(self.N, fil_dict['F_C'], nyq=0.5, window=self.qfft_win_select.get_window(self.N, sym=True))) def HPmin(self, fil_dict): self._get_params(fil_dict) N = self._firwin_ord([self.F_SB, self.F_PB], [0, 1], [self.A_SB, self.A_PB], alg=self.alg) self.N = round_odd(N) # enforce odd order if not self._test_N(): return -1 fil_dict['F_C'] = (self.F_SB + self.F_PB) / 2 # average calculated F_PB and F_SB self._save( fil_dict, self.firwin(self.N, fil_dict['F_C'], pass_zero=False, nyq=0.5, window=self.qfft_win_select.get_window(self.N, sym=True))) def HPman(self, fil_dict): self._get_params(fil_dict) self.N = round_odd(self.N) # enforce odd order if not self._test_N(): return -1 self._save( fil_dict, self.firwin(self.N, fil_dict['F_C'], pass_zero=False, nyq=0.5, window=self.qfft_win_select.get_window(self.N, sym=True))) # For BP and BS, F_PB and F_SB have two elements each def BPmin(self, fil_dict): self._get_params(fil_dict) self.N = remezord([self.F_SB, self.F_PB, self.F_PB2, self.F_SB2], [0, 1, 0], [self.A_SB, self.A_PB, self.A_SB2], fs=1, alg=self.alg)[0] if not self._test_N(): return -1 fil_dict['F_C'] = (self.F_SB + self.F_PB) / 2 # average calculated F_PB and F_SB fil_dict['F_C2'] = (self.F_SB2 + self.F_PB2) / 2 self._save( fil_dict, self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']], nyq=0.5, pass_zero=False, window=self.qfft_win_select.get_window(self.N, sym=True))) def BPman(self, fil_dict): self._get_params(fil_dict) if not self._test_N(): return -1 self._save( fil_dict, self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']], nyq=0.5, pass_zero=False, window=self.qfft_win_select.get_window(self.N, sym=True))) def BSmin(self, fil_dict): self._get_params(fil_dict) N = remezord([self.F_PB, self.F_SB, self.F_SB2, self.F_PB2], [1, 0, 1], [self.A_PB, self.A_SB, self.A_PB2], fs=1, alg=self.alg)[0] self.N = round_odd(N) # enforce odd order if not self._test_N(): return -1 fil_dict['F_C'] = (self.F_SB + self.F_PB) / 2 # average calculated F_PB and F_SB fil_dict['F_C2'] = (self.F_SB2 + self.F_PB2) / 2 self._save( fil_dict, self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']], window=self.qfft_win_select.get_window(self.N, sym=True), pass_zero=True, nyq=0.5)) def BSman(self, fil_dict): self._get_params(fil_dict) self.N = round_odd(self.N) # enforce odd order if not self._test_N(): return -1 self._save( fil_dict, self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']], window=self.qfft_win_select.get_window(self.N, sym=True), pass_zero=True, nyq=0.5)) # ------------------------------------------------------------------------------ def toggle_fft_wdg(self): """ Show / hide FFT widget depending on the state of the corresponding button When widget is shown, trigger an update of the window function. """ if self.but_fft_wdg.isChecked(): self.fft_widget.show() self.emit({'view_changed': 'fft_win_type'}, sig_name='sig_tx_local') else: self.fft_widget.hide() # -------------------------------------------------------------------------- def hide_fft_wdg(self): """ The closeEvent caused by clicking the "x" in the FFT widget is caught there and routed here to only hide the window """ self.but_fft_wdg.setChecked(False) self.fft_widget.hide()
def _construct_UI(self): """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ self.bfont = QFont() self.bfont.setBold(True) self.bifont = QFont() self.bifont.setBold(True) self.bifont.setItalic(True) # q_icon_size = QSize(20, 20) # optional, size is derived from butEnable ####################################################################### # frmMain # # This frame contains all the buttons ####################################################################### # --------------------------------------------- # layHDisplay # # UI Elements for controlling the display # --------------------------------------------- self.butEnable = PushButton(self, icon=QIcon(':/circle-check.svg'), checked=True) q_icon_size = self.butEnable.iconSize() # <- uncomment this for manual sizing self.butEnable.setToolTip( "<span>Show / hide filter coefficients in an editable table." " For high order systems, table display might be slow.</span>") fix_formats = ['Dec', 'Hex', 'Bin', 'CSD'] self.cmbFormat = QComboBox(self) model = self.cmbFormat.model() item = QtGui.QStandardItem('Float') item.setData('child', Qt.AccessibleDescriptionRole) model.appendRow(item) item = QtGui.QStandardItem('Fixp.:') item.setData('parent', Qt.AccessibleDescriptionRole) item.setData(0, QtGui.QFont.Bold) item.setFlags(item.flags() & ~Qt.ItemIsEnabled) # | Qt.ItemIsSelectable)) model.appendRow(item) for idx in range(len(fix_formats)): item = QtGui.QStandardItem(fix_formats[idx]) # item.setForeground(QtGui.QColor('red')) model.appendRow(item) self.cmbFormat.insertSeparator(1) qset_cmb_box(self.cmbFormat, 'float') self.cmbFormat.setToolTip('Set the display format.') self.cmbFormat.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.spnDigits = QSpinBox(self) self.spnDigits.setRange(0, 16) self.spnDigits.setValue(params['FMT_ba']) self.spnDigits.setToolTip("Number of digits to display.") self.lblDigits = QLabel("Digits", self) self.lblDigits.setFont(self.bifont) self.cmbQFrmt = QComboBox(self) q_formats = [('Norm. Frac.', 'qnfrac'), ('Integer', 'qint'), ('Fractional', 'qfrac')] for q in q_formats: self.cmbQFrmt.addItem(*q) self.lbl_W = QLabel("W = ", self) self.lbl_W.setFont(self.bifont) self.ledW = QLineEdit(self) self.ledW.setToolTip("Specify total wordlength.") self.ledW.setText("16") self.ledW.setMaxLength(2) # maximum of 2 digits self.ledW.setFixedWidth(30) # width of lineedit in points(?) layHDisplay = QHBoxLayout() layHDisplay.setAlignment(Qt.AlignLeft) layHDisplay.addWidget(self.butEnable) layHDisplay.addWidget(self.cmbFormat) layHDisplay.addWidget(self.spnDigits) layHDisplay.addWidget(self.lblDigits) layHDisplay.addWidget(self.cmbQFrmt) layHDisplay.addWidget(self.lbl_W) layHDisplay.addWidget(self.ledW) layHDisplay.addStretch() ####################################################################### # frmButtonsCoeffs # # This frame contains all buttons for manipulating coefficients ####################################################################### # ----------------------------------------------------------------- # layHButtonsCoeffs1 # # UI Elements for loading / storing / manipulating cells and rows # ----------------------------------------------------------------- self.cmbFilterType = QComboBox(self) self.cmbFilterType.setObjectName("comboFilterType") self.cmbFilterType.setToolTip( "<span>Select between IIR and FIR filter for manual entry." "Changing the type reloads the filter from the filter dict.</span>") self.cmbFilterType.addItems(["FIR", "IIR"]) self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.butAddCells = QPushButton(self) self.butAddCells.setIcon(QIcon(':/row_insert_above.svg')) self.butAddCells.setIconSize(q_icon_size) self.butAddCells.setToolTip( "<span>Select cells to insert a new cell above each selected cell. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, add a row at the end.</span>") self.butDelCells = QPushButton(self) self.butDelCells.setIcon(QIcon(':/row_delete.svg')) self.butDelCells.setIconSize(q_icon_size) self.butDelCells.setToolTip( "<span>Delete selected cell(s) from the table. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, delete the last row.</span>") self.butSave = QPushButton(self) self.butSave.setIcon(QIcon(':/upload.svg')) self.butSave.setIconSize(q_icon_size) self.butSave.setToolTip( "<span>Copy coefficient table to filter dict and update all plots" "and widgets.</span>") self.butLoad = QPushButton(self) self.butLoad.setIcon(QIcon(':/download.svg')) self.butLoad.setIconSize(q_icon_size) self.butLoad.setToolTip("Reload coefficient table from filter dict.") self.butClear = QPushButton(self) self.butClear.setIcon(QIcon(':/trash.svg')) self.butClear.setIconSize(q_icon_size) self.butClear.setToolTip("Clear all table entries.") self.butFromTable = QPushButton(self) self.butFromTable.setIconSize(q_icon_size) self.butToTable = QPushButton(self) self.butToTable.setIconSize(q_icon_size) self.but_csv_options = PushButton(self, icon=QIcon(':/settings.svg'), checked=False) self.but_csv_options.setIconSize(q_icon_size) self.but_csv_options.setToolTip( "<span>Select CSV format and whether " "to copy to/from clipboard or file.</span>") self._set_load_save_icons() # initialize icon / button settings layHButtonsCoeffs1 = QHBoxLayout() layHButtonsCoeffs1.addWidget(self.cmbFilterType) layHButtonsCoeffs1.addWidget(self.butAddCells) layHButtonsCoeffs1.addWidget(self.butDelCells) layHButtonsCoeffs1.addWidget(self.butClear) layHButtonsCoeffs1.addWidget(self.butSave) layHButtonsCoeffs1.addWidget(self.butLoad) layHButtonsCoeffs1.addWidget(self.butFromTable) layHButtonsCoeffs1.addWidget(self.butToTable) layHButtonsCoeffs1.addWidget(self.but_csv_options) layHButtonsCoeffs1.addStretch() # ---------------------------------------------------------------------- # layHButtonsCoeffs2 # # Eps / set zero settings # --------------------------------------------------------------------- self.butSetZero = QPushButton("= 0", self) self.butSetZero.setToolTip( "<span>Set selected coefficients = 0 with a magnitude < ε. " "When nothing is selected, test the whole table.</span>") self.butSetZero.setIconSize(q_icon_size) lblEps = QLabel(self) lblEps.setText("<b><i>for b, a</i> <</b>") self.ledEps = QLineEdit(self) self.ledEps.setToolTip("Specify tolerance value.") layHButtonsCoeffs2 = QHBoxLayout() layHButtonsCoeffs2.addWidget(self.butSetZero) layHButtonsCoeffs2.addWidget(lblEps) layHButtonsCoeffs2.addWidget(self.ledEps) layHButtonsCoeffs2.addStretch() # ------------------------------------------------------------------- # Now put the ButtonsCoeffs HBoxes into frmButtonsCoeffs # --------------------------------------------------------------------- layVButtonsCoeffs = QVBoxLayout() layVButtonsCoeffs.addLayout(layHButtonsCoeffs1) layVButtonsCoeffs.addLayout(layHButtonsCoeffs2) layVButtonsCoeffs.setContentsMargins(0, 5, 0, 0) # This frame encompasses all Quantization Settings self.frmButtonsCoeffs = QFrame(self) self.frmButtonsCoeffs.setLayout(layVButtonsCoeffs) # ###################################################################### # frmQSettings # # This frame contains all quantization settings # ###################################################################### # ------------------------------------------------------------------- # layHW_Scale # # QFormat and scale settings # --------------------------------------------------------------------- lbl_Q = QLabel("Q =", self) lbl_Q.setFont(self.bifont) self.ledWI = QLineEdit(self) self.ledWI.setToolTip("Specify number of integer bits.") self.ledWI.setText("0") self.ledWI.setMaxLength(2) # maximum of 2 digits self.ledWI.setFixedWidth(30) # width of lineedit in points(?) self.lblDot = QLabel(".", self) # class attribute, visibility is toggled self.lblDot.setFont(self.bfont) self.ledWF = QLineEdit(self) self.ledWF.setToolTip("Specify number of fractional bits.") self.ledWF.setText("15") self.ledWF.setMaxLength(2) # maximum of 2 digits # self.ledWF.setFixedWidth(30) # width of lineedit in points(?) self.ledWF.setMaximumWidth(30) self.lblScale = QLabel("<b><i>Scale</i> =</b>", self) self.ledScale = QLineEdit(self) self.ledScale.setToolTip( "Set the scale for converting float to fixpoint representation.") self.ledScale.setText(str(1)) self.ledScale.setEnabled(False) layHWI_WF = QHBoxLayout() layHWI_WF.addWidget(lbl_Q) layHWI_WF.addWidget(self.ledWI) layHWI_WF.addWidget(self.lblDot) layHWI_WF.addWidget(self.ledWF) layHWI_WF.addStretch() layHScale = QHBoxLayout() layHScale.addWidget(self.lblScale) layHScale.addWidget(self.ledScale) layHScale.addStretch() layHW_Scale = QHBoxLayout() layHW_Scale.addLayout(layHWI_WF) layHW_Scale.addLayout(layHScale) # ------------------------------------------------------------------- # layGQOpt # # Quantization / Overflow / MSB / LSB settings # --------------------------------------------------------------------- lblQOvfl = QLabel("Ovfl.:", self) lblQOvfl.setFont(self.bifont) lblQuant = QLabel("Quant.:", self) lblQuant.setFont(self.bifont) self.cmbQOvfl = QComboBox(self) qOvfl = ['wrap', 'sat'] self.cmbQOvfl.addItems(qOvfl) qset_cmb_box(self.cmbQOvfl, 'sat') self.cmbQOvfl.setToolTip("Select overflow behaviour.") # ComboBox size is adjusted automatically to fit the longest element self.cmbQOvfl.setSizeAdjustPolicy(QComboBox.AdjustToContents) layHQOvflOpt = QHBoxLayout() layHQOvflOpt.addWidget(lblQOvfl) layHQOvflOpt.addWidget(self.cmbQOvfl) layHQOvflOpt.addStretch() self.cmbQuant = QComboBox(self) qQuant = ['none', 'round', 'fix', 'floor'] self.cmbQuant.addItems(qQuant) qset_cmb_box(self.cmbQuant, 'round') self.cmbQuant.setToolTip("Select the kind of quantization.") self.cmbQuant.setSizeAdjustPolicy(QComboBox.AdjustToContents) layHQuantOpt = QHBoxLayout() layHQuantOpt.addWidget(lblQuant) layHQuantOpt.addWidget(self.cmbQuant) layHQuantOpt.addStretch() self.butQuant = QPushButton(self) self.butQuant.setToolTip( "<span>Quantize selected coefficients / " "whole table with specified settings.</span>") self.butQuant.setIcon(QIcon(':/quantize.svg')) self.butQuant.setIconSize(q_icon_size) self.butQuant.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) lblMSBtxt = QLabel(self) lblMSBtxt.setText("<b><i>MSB</i><sub>10</sub> =</b>") self.lblMSB = QLabel(self) layHMSB = QHBoxLayout() layHMSB.addWidget(lblMSBtxt) layHMSB.addWidget(self.lblMSB) layHMSB.addStretch() lblLSBtxt = QLabel(self) lblLSBtxt.setText("<b><i>LSB</i><sub>10</sub> =</b>") self.lblLSB = QLabel(self) layHLSB = QHBoxLayout() layHLSB.addWidget(lblLSBtxt) layHLSB.addWidget(self.lblLSB) layHLSB.addStretch() layGQOpt = QGridLayout() layGQOpt.addLayout(layHQOvflOpt, 0, 0) layGQOpt.addLayout(layHQuantOpt, 0, 1) layGQOpt.addWidget(self.butQuant, 0, 2, Qt.AlignCenter) layGQOpt.addLayout(layHMSB, 1, 0) layGQOpt.addLayout(layHLSB, 1, 1) # ------------------------------------------------------------------- # Display MAX # --------------------------------------------------------------------- lblMAXtxt = QLabel(self) lblMAXtxt.setText("<b><i>Max =</i></b>") self.lblMAX = QLabel(self) layHCoeffs_MAX = QHBoxLayout() layHCoeffs_MAX.addWidget(lblMAXtxt) layHCoeffs_MAX.addWidget(self.lblMAX) layHCoeffs_MAX.addStretch() ####################################################################### # Now put all the coefficient HBoxes into frmQSettings # --------------------------------------------------------------------- layVButtonsQ = QVBoxLayout() layVButtonsQ.addLayout(layHW_Scale) layVButtonsQ.addLayout(layGQOpt) layVButtonsQ.addLayout(layHCoeffs_MAX) layVButtonsQ.setContentsMargins(0, 0, 0, 0) # This frame encompasses all Quantization Settings self.frmQSettings = QFrame(self) self.frmQSettings.setLayout(layVButtonsQ) ####################################################################### # ######################## Main UI Layout ############################ ####################################################################### # layout for frame (UI widget) layVMainF = QVBoxLayout() layVMainF.addLayout(layHDisplay) layVMainF.addWidget(self.frmQSettings) layVMainF.addWidget(QHLine()) layVMainF.addWidget(self.frmButtonsCoeffs) # This frame encompasses all UI elements frmMain = QFrame(self) frmMain.setLayout(layVMainF) layVMain = QVBoxLayout() # the following affects only the first widget (intended here) layVMain.setAlignment(Qt.AlignTop) layVMain.addWidget(frmMain) layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(layVMain) ####################################################################### # --- set initial values from dict ------------ self.spnDigits.setValue(params['FMT_ba']) self.ledEps.setText(str(self.eps)) # ---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.but_csv_options.clicked.connect(self._open_csv_win)
def _construct_UI(self): """ initialize the User Interface """ butClipboard = QPushButton(self) butClipboard.setIcon(QIcon(':/clipboard.svg')) butClipboard.setToolTip("Copy text to clipboard.") butAbout = QPushButton(self) butAbout.setText("About") butAbout.setToolTip("Display 'About' info") butChangelog = QPushButton(self) butChangelog.setText("Changelog") butChangelog.setToolTip("Display changelog") butLicMIT = QPushButton(self) butLicMIT.setText("MIT License") butLicMIT.setToolTip("MIT License for pyFDA source code") butLicGPLv3 = QPushButton(self) butLicGPLv3.setText("GPLv3 License") butLicGPLv3.setToolTip("GPLv3 License for bundled distribution") butClose = QPushButton(self) butClose.setIcon(QIcon(':/circle-x.svg')) butClose.setToolTip("Close Window.") layGButtons = QGridLayout() layGButtons.addWidget(butClipboard, 0, 0) layGButtons.addWidget(butAbout, 0, 1) layGButtons.addWidget(butChangelog, 0, 2) layGButtons.addWidget(butLicMIT, 0, 3) layGButtons.addWidget(butLicGPLv3, 0, 4) layGButtons.addWidget(butClose, 0, 5) lblInfo = QLabel(self) lblInfo.setText(self.info_str) lblInfo.setFixedHeight(lblInfo.height() * 1.2) #lblInfo.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) lblInfo.setOpenExternalLinks(True) lblIcon = QLabel(self) lblIcon.setPixmap( QPixmap(':/pyfda_icon.svg').scaledToHeight( lblInfo.height(), Qt.SmoothTransformation)) butClipboard.setFixedWidth(lblInfo.height()) butClose.setFixedWidth(lblInfo.height()) layHInfo = QHBoxLayout() layHInfo.addWidget(lblIcon) layHInfo.addWidget(lblInfo) self.txtDisplay = QTextBrowser(self) self.txtDisplay.setOpenExternalLinks(True) self.display_about_str() self.txtDisplay.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) #self.txtDisplay.setFixedHeight(self.txtDisplay.width() * 2) layVMain = QVBoxLayout() # layVMain.setAlignment(Qt.AlignTop) # this affects only the first widget (intended here) layVMain.addLayout(layGButtons) layVMain.addLayout(layHInfo) layVMain.addWidget(self.txtDisplay) layVMain.setContentsMargins(*params['wdg_margins_spc']) self.setLayout(layVMain) #self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) #self.resize(0,0) #self.adjustSize() #QApplication.processEvents() butClipboard.clicked.connect( lambda: self.to_clipboard(self.info_str + self.about_str)) butAbout.clicked.connect(self.display_about_str) butChangelog.clicked.connect(self.display_changelog) butLicMIT.clicked.connect(self.display_MIT_lic) butLicGPLv3.clicked.connect(self.display_GPL_lic) butClose.clicked.connect(self.close)
def _construct_UI(self): # ===================================================================== # Controls for stimuli # ===================================================================== self.cmbStimulus = QComboBox(self) qcmb_box_populate(self.cmbStimulus, self.cmb_stim_items, self.cmb_stim_item) self.lblStimPar1 = QLabel(to_html("α =", frmt='b'), self) self.ledStimPar1 = QLineEdit(self) self.ledStimPar1.setText("0.5") self.ledStimPar1.setToolTip("Duty Cycle, 0 ... 1") self.ledStimPar1.setObjectName("ledStimPar1") self.but_stim_bl = QPushButton(self) self.but_stim_bl.setText("BL") self.but_stim_bl.setToolTip( "<span>Bandlimit the signal to the Nyquist " "frequency to avoid aliasing. However, this is much slower " "to calculate especially for a large number of points.</span>") self.but_stim_bl.setMaximumWidth(qtext_width(text="BL ")) self.but_stim_bl.setCheckable(True) self.but_stim_bl.setChecked(True) self.but_stim_bl.setObjectName("stim_bl") # ------------------------------------- self.cmbChirpType = QComboBox(self) qcmb_box_populate(self.cmbChirpType, self.cmb_stim_chirp_items, self.chirp_type) self.cmbImpulseType = QComboBox(self) qcmb_box_populate(self.cmbImpulseType, self.cmb_stim_impulse_items, self.impulse_type) self.cmbSinusoidType = QComboBox(self) qcmb_box_populate(self.cmbSinusoidType, self.cmb_stim_sinusoid_items, self.sinusoid_type) self.cmbPeriodicType = QComboBox(self) qcmb_box_populate(self.cmbPeriodicType, self.cmb_stim_periodic_items, self.cmb_stim_periodic_item) self.cmbModulationType = QComboBox(self) for t in [("AM", "am"), ("PM / FM", "pmfm")]: # text, data self.cmbModulationType.addItem(*t) qset_cmb_box(self.cmbModulationType, self.modulation_type, data=True) # ------------------------------------- self.chk_step_err = QPushButton("Error", self) self.chk_step_err.setToolTip( "<span>Display the step response error.</span>") self.chk_step_err.setMaximumWidth(qtext_width(text="Error ")) self.chk_step_err.setCheckable(True) self.chk_step_err.setChecked(False) self.chk_step_err.setObjectName("stim_step_err") layHCmbStim = QHBoxLayout() layHCmbStim.addWidget(self.cmbStimulus) layHCmbStim.addWidget(self.cmbImpulseType) layHCmbStim.addWidget(self.cmbSinusoidType) layHCmbStim.addWidget(self.cmbChirpType) layHCmbStim.addWidget(self.cmbPeriodicType) layHCmbStim.addWidget(self.cmbModulationType) layHCmbStim.addWidget(self.but_stim_bl) layHCmbStim.addWidget(self.lblStimPar1) layHCmbStim.addWidget(self.ledStimPar1) layHCmbStim.addWidget(self.chk_step_err) self.lblDC = QLabel(to_html("DC =", frmt='bi'), self) self.ledDC = QLineEdit(self) self.ledDC.setText(str(self.DC)) self.ledDC.setToolTip("DC Level") self.ledDC.setObjectName("stimDC") layHStimDC = QHBoxLayout() layHStimDC.addWidget(self.lblDC) layHStimDC.addWidget(self.ledDC) # ====================================================================== self.lblAmp1 = QLabel(to_html(" A_1", frmt='bi') + " =", self) self.ledAmp1 = QLineEdit(self) self.ledAmp1.setText(str(self.A1)) self.ledAmp1.setToolTip( "Stimulus amplitude, complex values like 3j - 1 are allowed") self.ledAmp1.setObjectName("stimAmp1") self.lblAmp2 = QLabel(to_html(" A_2", frmt='bi') + " =", self) self.ledAmp2 = QLineEdit(self) self.ledAmp2.setText(str(self.A2)) self.ledAmp2.setToolTip( "Stimulus amplitude 2, complex values like 3j - 1 are allowed") self.ledAmp2.setObjectName("stimAmp2") # ---------------------------------------------- self.lblPhi1 = QLabel(to_html(" φ_1", frmt='bi') + " =", self) self.ledPhi1 = QLineEdit(self) self.ledPhi1.setText(str(self.phi1)) self.ledPhi1.setToolTip("Stimulus phase") self.ledPhi1.setObjectName("stimPhi1") self.lblPhU1 = QLabel(to_html("°", frmt='b'), self) self.lblPhi2 = QLabel(to_html(" φ_2", frmt='bi') + " =", self) self.ledPhi2 = QLineEdit(self) self.ledPhi2.setText(str(self.phi2)) self.ledPhi2.setToolTip("Stimulus phase 2") self.ledPhi2.setObjectName("stimPhi2") self.lblPhU2 = QLabel(to_html("°", frmt='b'), self) # ---------------------------------------------- self.lbl_T1 = QLabel(to_html(" T_1", frmt='bi') + " =", self) self.led_T1 = QLineEdit(self) self.led_T1.setText(str(self.T1)) self.led_T1.setToolTip("Time shift") self.led_T1.setObjectName("stimT1") self.lbl_TU1 = QLabel(to_html("T_S", frmt='b'), self) self.lbl_T2 = QLabel(to_html(" T_2", frmt='bi') + " =", self) self.led_T2 = QLineEdit(self) self.led_T2.setText(str(self.T2)) self.led_T2.setToolTip("Time shift 2") self.led_T2.setObjectName("stimT2") self.lbl_TU2 = QLabel(to_html("T_S", frmt='b'), self) # --------------------------------------------- self.lbl_TW1 = QLabel( to_html(" ΔT_1", frmt='bi') + " =", self) self.led_TW1 = QLineEdit(self) self.led_TW1.setText(str(self.TW1)) self.led_TW1.setToolTip("Time width") self.led_TW1.setObjectName("stimTW1") self.lbl_TWU1 = QLabel(to_html("T_S", frmt='b'), self) self.lbl_TW2 = QLabel( to_html(" ΔT_2", frmt='bi') + " =", self) self.led_TW2 = QLineEdit(self) self.led_TW2.setText(str(self.TW2)) self.led_TW2.setToolTip("Time width 2") self.led_TW2.setObjectName("stimTW2") self.lbl_TWU2 = QLabel(to_html("T_S", frmt='b'), self) # ---------------------------------------------- self.txtFreq1_f = to_html(" f_1", frmt='bi') + " =" self.txtFreq1_k = to_html(" k_1", frmt='bi') + " =" self.lblFreq1 = QLabel(self.txtFreq1_f, self) self.ledFreq1 = QLineEdit(self) self.ledFreq1.setText(str(self.f1)) self.ledFreq1.setToolTip("Stimulus frequency") self.ledFreq1.setObjectName("stimFreq1") self.lblFreqUnit1 = QLabel("f_S", self) self.txtFreq2_f = to_html(" f_2", frmt='bi') + " =" self.txtFreq2_k = to_html(" k_2", frmt='bi') + " =" self.lblFreq2 = QLabel(self.txtFreq2_f, self) self.ledFreq2 = QLineEdit(self) self.ledFreq2.setText(str(self.f2)) self.ledFreq2.setToolTip("Stimulus frequency 2") self.ledFreq2.setObjectName("stimFreq2") self.lblFreqUnit2 = QLabel("f_S", self) # ---------------------------------------------- self.lbl_BW1 = QLabel( to_html(self.tr(" BW_1"), frmt='bi') + " =", self) self.led_BW1 = QLineEdit(self) self.led_BW1.setText(str(self.BW1)) self.led_BW1.setToolTip(self.tr("Relative bandwidth")) self.led_BW1.setObjectName("stimBW1") self.lbl_BW2 = QLabel( to_html(self.tr(" BW_2"), frmt='bi') + " =", self) self.led_BW2 = QLineEdit(self) self.led_BW2.setText(str(self.BW2)) self.led_BW2.setToolTip(self.tr("Relative bandwidth 2")) self.led_BW2.setObjectName("stimBW2") # ---------------------------------------------- self.lblNoise = QLabel(to_html(" Noise", frmt='bi'), self) self.cmbNoise = QComboBox(self) qcmb_box_populate(self.cmbNoise, self.cmb_stim_noise_items, self.noise) self.lblNoi = QLabel("not initialized", self) self.ledNoi = QLineEdit(self) self.ledNoi.setText(str(self.noi)) self.ledNoi.setToolTip("not initialized") self.ledNoi.setObjectName("stimNoi") layGStim = QGridLayout() layGStim.addLayout(layHCmbStim, 0, 1) layGStim.addLayout(layHStimDC, 1, 1) layGStim.addWidget(self.lblAmp1, 0, 2) layGStim.addWidget(self.lblAmp2, 1, 2) layGStim.addWidget(self.ledAmp1, 0, 3) layGStim.addWidget(self.ledAmp2, 1, 3) layGStim.addWidget(self.lblPhi1, 0, 4) layGStim.addWidget(self.lblPhi2, 1, 4) layGStim.addWidget(self.ledPhi1, 0, 5) layGStim.addWidget(self.ledPhi2, 1, 5) layGStim.addWidget(self.lblPhU1, 0, 6) layGStim.addWidget(self.lblPhU2, 1, 6) layGStim.addWidget(self.lbl_T1, 0, 7) layGStim.addWidget(self.lbl_T2, 1, 7) layGStim.addWidget(self.led_T1, 0, 8) layGStim.addWidget(self.led_T2, 1, 8) layGStim.addWidget(self.lbl_TU1, 0, 9) layGStim.addWidget(self.lbl_TU2, 1, 9) layGStim.addWidget(self.lbl_TW1, 0, 10) layGStim.addWidget(self.lbl_TW2, 1, 10) layGStim.addWidget(self.led_TW1, 0, 11) layGStim.addWidget(self.led_TW2, 1, 11) layGStim.addWidget(self.lbl_TWU1, 0, 12) layGStim.addWidget(self.lbl_TWU2, 1, 12) layGStim.addWidget(self.lblFreq1, 0, 13) layGStim.addWidget(self.lblFreq2, 1, 13) layGStim.addWidget(self.ledFreq1, 0, 14) layGStim.addWidget(self.ledFreq2, 1, 14) layGStim.addWidget(self.lblFreqUnit1, 0, 15) layGStim.addWidget(self.lblFreqUnit2, 1, 15) layGStim.addWidget(self.lbl_BW1, 0, 16) layGStim.addWidget(self.lbl_BW2, 1, 16) layGStim.addWidget(self.led_BW1, 0, 17) layGStim.addWidget(self.led_BW2, 1, 17) layGStim.addWidget(self.lblNoise, 0, 18) layGStim.addWidget(self.lblNoi, 1, 18) layGStim.addWidget(self.cmbNoise, 0, 19) layGStim.addWidget(self.ledNoi, 1, 19) # ---------------------------------------------- self.lblStimFormula = QLabel(to_html("x =", frmt='bi'), self) self.ledStimFormula = QLineEdit(self) self.ledStimFormula.setText(str(self.stim_formula)) self.ledStimFormula.setToolTip( "<span>Enter formula for stimulus in numexpr syntax.</span>") self.ledStimFormula.setObjectName("stimFormula") layH_stim_formula = QHBoxLayout() layH_stim_formula.addWidget(self.lblStimFormula) layH_stim_formula.addWidget(self.ledStimFormula, 10) # ---------------------------------------------------------------------- # Main Widget # ---------------------------------------------------------------------- layH_stim_par = QHBoxLayout() layH_stim_par.addLayout(layGStim) layV_stim = QVBoxLayout() layV_stim.addLayout(layH_stim_par) layV_stim.addLayout(layH_stim_formula) layH_stim = QHBoxLayout() layH_stim.addLayout(layV_stim) layH_stim.addStretch(10) self.wdg_stim = QWidget(self) self.wdg_stim.setLayout(layH_stim) self.wdg_stim.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) # ---------------------------------------------------------------------- # Event Filter # ---------------------------------------------------------------------- # frequency related widgets are scaled with f_s, requiring special handling self.ledFreq1.installEventFilter(self) self.ledFreq2.installEventFilter(self) self.led_T1.installEventFilter(self) self.led_T2.installEventFilter(self) self.led_TW1.installEventFilter(self) self.led_TW2.installEventFilter(self) # ---------------------------------------------------------------------- # GLOBAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.sig_rx.connect(self.process_sig_rx) # ---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs # ---------------------------------------------------------------------- # --- stimulus control --- self.but_stim_bl.clicked.connect(self._enable_stim_widgets) self.chk_step_err.clicked.connect(self._enable_stim_widgets) self.cmbStimulus.currentIndexChanged.connect(self._enable_stim_widgets) self.cmbNoise.currentIndexChanged.connect(self._update_noi) self.ledNoi.editingFinished.connect(self._update_noi) self.ledAmp1.editingFinished.connect(self._update_amp1) self.ledAmp2.editingFinished.connect(self._update_amp2) self.ledPhi1.editingFinished.connect(self._update_phi1) self.ledPhi2.editingFinished.connect(self._update_phi2) self.led_BW1.editingFinished.connect(self._update_BW1) self.led_BW2.editingFinished.connect(self._update_BW2) self.cmbImpulseType.currentIndexChanged.connect( self._update_impulse_type) self.cmbSinusoidType.currentIndexChanged.connect( self._update_sinusoid_type) self.cmbChirpType.currentIndexChanged.connect(self._update_chirp_type) self.cmbPeriodicType.currentIndexChanged.connect( self._update_periodic_type) self.cmbModulationType.currentIndexChanged.connect( self._update_modulation_type) self.ledDC.editingFinished.connect(self._update_DC) self.ledStimFormula.editingFinished.connect(self._update_stim_formula) self.ledStimPar1.editingFinished.connect(self._update_stim_par1)
def _construct_UI(self): """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ self.bfont = QFont() self.bfont.setBold(True) self.bifont = QFont() self.bifont.setBold(True) self.bifont.setItalic(True) # q_icon_size = QSize(20, 20) # optional, size is derived from butEnable # --------------------------------------------- # UI Elements for controlling the display # --------------------------------------------- self.butEnable = QPushButton(self) self.butEnable.setIcon(QIcon(':/circle-x.svg')) q_icon_size = self.butEnable.iconSize() # <- set this for manual icon sizing self.butEnable.setIconSize(q_icon_size) self.butEnable.setCheckable(True) self.butEnable.setChecked(True) self.butEnable.setToolTip("<span>Show / hide poles and zeros in an editable table." " For high order systems, the table display might be slow.</span>") self.cmbPZFrmt = QComboBox(self) pz_formats = [('Cartesian', 'cartesian'), ('Polar (rad)', 'polar_rad'), ('Polar (pi)', 'polar_pi'), ('Polar (°)', 'polar_deg')] # display text, data # π: u'3C0, °: u'B0, ∠: u'2220 for pz in pz_formats: self.cmbPZFrmt.addItem(*pz) self.cmbPZFrmt.setSizeAdjustPolicy(QComboBox.AdjustToContents) # self.cmbPZFrmt.setEnabled(False) self.cmbPZFrmt.setToolTip("<span>Set display format for poles and zeros to" " either cartesian (x + jy) or polar (r * ∠ Ω)." " Type 'o' for '°', '<' for '∠' and 'pi' for 'π'.</span>") self.spnDigits = QSpinBox(self) self.spnDigits.setRange(0,16) self.spnDigits.setToolTip("Number of digits to display.") self.lblDigits = QLabel("Digits", self) self.lblDigits.setFont(self.bifont) self.cmbCausal = QComboBox(self) causal_types = ['Causal', 'Acausal', 'Anticausal'] for cs in causal_types: self.cmbCausal.addItem(cs) qset_cmb_box(self.cmbCausal, 'Causal') self.cmbCausal.setToolTip('<span>Set the system type. Not implemented yet.</span>') self.cmbCausal.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbCausal.setEnabled(False) layHDisplay = QHBoxLayout() layHDisplay.setAlignment(Qt.AlignLeft) layHDisplay.addWidget(self.butEnable) layHDisplay.addWidget(self.cmbPZFrmt) layHDisplay.addWidget(self.spnDigits) layHDisplay.addWidget(self.lblDigits) layHDisplay.addWidget(self.cmbCausal) layHDisplay.addStretch() # --------------------------------------------- # UI Elements for setting the gain # --------------------------------------------- self.lblNorm = QLabel(to_html("Normalize:", frmt='bi'), self) self.cmbNorm = QComboBox(self) self.cmbNorm.addItems(["None", "1", "Max"]) self.cmbNorm.setToolTip("<span>Set the gain <i>k</i> so that H(f)<sub>max</sub> is " "either 1 or the max. of the previous system.</span>") self.lblGain = QLabel(to_html("k =", frmt='bi'), self) self.ledGain = QLineEdit(self) self.ledGain.setToolTip("<span>Specify gain factor <i>k</i>" " (only possible for Normalize = 'None').</span>") self.ledGain.setText(str(1.)) self.ledGain.setObjectName("ledGain") layHGain = QHBoxLayout() layHGain.addWidget(self.lblNorm) layHGain.addWidget(self.cmbNorm) layHGain.addWidget(self.lblGain) layHGain.addWidget(self.ledGain) layHGain.addStretch() # --------------------------------------------- # UI Elements for loading / storing / manipulating cells and rows # --------------------------------------------- # self.cmbFilterType = QComboBox(self) # self.cmbFilterType.setObjectName("comboFilterType") # self.cmbFilterType.setToolTip("Select between IIR and FIR filte for manual entry.") # self.cmbFilterType.addItems(["FIR","IIR"]) # self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.butAddCells = QPushButton(self) self.butAddCells.setIcon(QIcon(':/row_insert_above.svg')) self.butAddCells.setIconSize(q_icon_size) self.butAddCells.setToolTip("<SPAN>Select cells to insert a new cell above each selected cell. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, add a row at the end.</SPAN>") self.butDelCells = QPushButton(self) self.butDelCells.setIcon(QIcon(':/row_delete.svg')) self.butDelCells.setIconSize(q_icon_size) self.butDelCells.setToolTip("<SPAN>Delete selected cell(s) from the table. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, delete the last row.</SPAN>") self.butSave = QPushButton(self) self.butSave.setIcon(QIcon(':/upload.svg')) self.butSave.setIconSize(q_icon_size) self.butSave.setToolTip("<span>Copy P/Z table to filter dict and update all plots and widgets.</span>") self.butLoad = QPushButton(self) self.butLoad.setIcon(QIcon(':/download.svg')) self.butLoad.setIconSize(q_icon_size) self.butLoad.setToolTip("Reload P/Z table from filter dict.") self.butClear = QPushButton(self) self.butClear.setIcon(QIcon(':/trash.svg')) self.butClear.setIconSize(q_icon_size) self.butClear.setToolTip("Clear all table entries.") self.butFromTable = QPushButton(self) self.butFromTable.setIconSize(q_icon_size) self.butToTable = QPushButton(self) self.butToTable.setIconSize(q_icon_size) self.but_csv_options = QPushButton(self) self.but_csv_options.setIcon(QIcon(':/settings.svg')) self.but_csv_options.setIconSize(q_icon_size) self.but_csv_options.setToolTip("<span>Select CSV format and whether " "to copy to/from clipboard or file.</span>") self.but_csv_options.setCheckable(True) self.but_csv_options.setChecked(False) self._set_load_save_icons() # initialize icon / button settings layHButtonsCoeffs1 = QHBoxLayout() # layHButtonsCoeffs1.addWidget(self.cmbFilterType) layHButtonsCoeffs1.addWidget(self.butAddCells) layHButtonsCoeffs1.addWidget(self.butDelCells) layHButtonsCoeffs1.addWidget(self.butClear) layHButtonsCoeffs1.addWidget(self.butSave) layHButtonsCoeffs1.addWidget(self.butLoad) layHButtonsCoeffs1.addWidget(self.butFromTable) layHButtonsCoeffs1.addWidget(self.butToTable) layHButtonsCoeffs1.addWidget(self.but_csv_options) layHButtonsCoeffs1.addStretch() #------------------------------------------------------------------- # Eps / set zero settings # --------------------------------------------------------------------- self.butSetZero = QPushButton("= 0", self) self.butSetZero.setToolTip("<span>Set selected poles / zeros = 0 with a magnitude < ε. " "When nothing is selected, test the whole table.</span>") self.butSetZero.setIconSize(q_icon_size) lblEps = QLabel(self) lblEps.setText("<b><i>for ε</i> <</b>") self.ledEps = QLineEdit(self) self.ledEps.setToolTip("Specify tolerance value.") layHButtonsCoeffs2 = QHBoxLayout() layHButtonsCoeffs2.addWidget(self.butSetZero) layHButtonsCoeffs2.addWidget(lblEps) layHButtonsCoeffs2.addWidget(self.ledEps) layHButtonsCoeffs2.addStretch() # ######################## Main UI Layout ############################ # layout for frame (UI widget) layVMainF = QVBoxLayout() layVMainF.addLayout(layHDisplay) layVMainF.addLayout(layHGain) layVMainF.addLayout(layHButtonsCoeffs1) layVMainF.addLayout(layHButtonsCoeffs2) # This frame encompasses all UI elements frmMain = QFrame(self) frmMain.setLayout(layVMainF) layVMain = QVBoxLayout() layVMain.setAlignment(Qt.AlignTop) # this affects only the first widget (intended here) layVMain.addWidget(frmMain) layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(layVMain) #--- set initial values from dict ------------ self.spnDigits.setValue(params['FMT_pz']) self.ledEps.setText(str(self.eps)) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- self.but_csv_options.clicked.connect(self._open_csv_win)
def _construct_UI(self): # ----------- --------------------------------------------------- # Run control widgets # --------------------------------------------------------------- self.chk_auto_run = QCheckBox("Auto", self) self.chk_auto_run.setObjectName("chk_auto_run") self.chk_auto_run.setToolTip( "<span>Update response automatically when " "parameters have been changed.</span>") self.chk_auto_run.setChecked(True) self.but_run = QPushButton(self) self.but_run.setText("RUN") self.but_run.setToolTip("Run simulation") self.but_run.setEnabled(not self.chk_auto_run.isChecked()) self.cmb_sim_select = QComboBox(self) self.cmb_sim_select.addItems(["Float", "Fixpoint"]) qset_cmb_box(self.cmb_sim_select, "Float") self.cmb_sim_select.setToolTip( "<span>Simulate floating-point or fixpoint response." "</span>") self.lbl_N_points = QLabel(to_html("N", frmt='bi') + " =", self) self.led_N_points = QLineEdit(self) self.led_N_points.setText(str(self.N)) self.led_N_points.setToolTip( "<span>Number of displayed data points. " "<i>N</i> = 0 tries to choose for you.</span>") self.lbl_N_start = QLabel(to_html("N_0", frmt='bi') + " =", self) self.led_N_start = QLineEdit(self) self.led_N_start.setText(str(self.N_start)) self.led_N_start.setToolTip("<span>First point to plot.</span>") self.chk_fx_scale = QCheckBox("Int. scale", self) self.chk_fx_scale.setObjectName("chk_fx_scale") self.chk_fx_scale.setToolTip( "<span>Display data with integer (fixpoint) scale.</span>") self.chk_fx_scale.setChecked(False) self.chk_stim_options = QCheckBox("Stim. Options", self) self.chk_stim_options.setObjectName("chk_stim_options") self.chk_stim_options.setToolTip("<span>Show stimulus options.</span>") self.chk_stim_options.setChecked(True) self.but_fft_win = QPushButton(self) self.but_fft_win.setText("WIN FFT") self.but_fft_win.setToolTip( "Show time and frequency response of FFT Window") self.but_fft_win.setCheckable(True) self.but_fft_win.setChecked(False) layH_ctrl_run = QHBoxLayout() layH_ctrl_run.addWidget(self.but_run) #layH_ctrl_run.addWidget(self.lbl_sim_select) layH_ctrl_run.addWidget(self.cmb_sim_select) layH_ctrl_run.addWidget(self.chk_auto_run) layH_ctrl_run.addStretch(1) layH_ctrl_run.addWidget(self.lbl_N_start) layH_ctrl_run.addWidget(self.led_N_start) layH_ctrl_run.addStretch(1) layH_ctrl_run.addWidget(self.lbl_N_points) layH_ctrl_run.addWidget(self.led_N_points) layH_ctrl_run.addStretch(2) layH_ctrl_run.addWidget(self.chk_fx_scale) layH_ctrl_run.addStretch(2) layH_ctrl_run.addWidget(self.chk_stim_options) layH_ctrl_run.addStretch(2) layH_ctrl_run.addWidget(self.but_fft_win) layH_ctrl_run.addStretch(10) #layH_ctrl_run.setContentsMargins(*params['wdg_margins']) self.wdg_ctrl_run = QWidget(self) self.wdg_ctrl_run.setLayout(layH_ctrl_run) # --- end of run control ---------------------------------------- # ----------- --------------------------------------------------- # Controls for time domain # --------------------------------------------------------------- plot_styles_list = [ "None", "Dots", "Line", "Line*", "Stem", "Stem*", "Step", "Step*" ] lbl_plt_time_title = QLabel("<b>View:</b>", self) lbl_plt_time_resp = QLabel("Response " + to_html("y", frmt='bi'), self) self.cmb_plt_time_resp = QComboBox(self) self.cmb_plt_time_resp.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_time_resp, self.plt_time_resp) self.cmb_plt_time_resp.setToolTip( "<span>Plot style for response.</span>") self.lbl_plt_time_stim = QLabel("Stimulus " + to_html("x", frmt='bi'), self) self.cmb_plt_time_stim = QComboBox(self) self.cmb_plt_time_stim.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_time_stim, self.plt_time_stim) self.cmb_plt_time_stim.setToolTip( "<span>Plot style for stimulus.</span>") self.lbl_plt_time_stmq = QLabel( "Fixp. Stim. " + to_html("x_Q", frmt='bi'), self) self.cmb_plt_time_stmq = QComboBox(self) self.cmb_plt_time_stmq.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_time_stmq, self.plt_time_stmq) self.cmb_plt_time_stmq.setToolTip( "<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>" ) self.chk_log_time = QCheckBox("dB : min.", self) self.chk_log_time.setObjectName("chk_log_time") self.chk_log_time.setToolTip( "<span>Logarithmic scale for y-axis.</span>") self.chk_log_time.setChecked(False) self.led_log_bottom_time = QLineEdit(self) self.led_log_bottom_time.setText(str(self.bottom_t)) self.led_log_bottom_time.setToolTip( "<span>Minimum display value for log. scale.</span>") self.led_log_bottom_time.setVisible(self.chk_log_time.isChecked()) if not self.chk_log_time.isChecked(): self.chk_log_time.setText("dB") self.bottom_t = 0 self.chk_win_time = QCheckBox("FFT Window", self) self.chk_win_time.setObjectName("chk_win_time") self.chk_win_time.setToolTip( "<span>Show FFT windowing function.</span>") self.chk_win_time.setChecked(False) self.chk_fx_limits = QCheckBox("Min/max.", self) self.chk_fx_limits.setObjectName("chk_fx_limits") self.chk_fx_limits.setToolTip( "<span>Display limits of fixpoint range.</span>") self.chk_fx_limits.setChecked(False) layH_ctrl_time = QHBoxLayout() layH_ctrl_time.addWidget(lbl_plt_time_title) layH_ctrl_time.addStretch(1) layH_ctrl_time.addWidget(lbl_plt_time_resp) layH_ctrl_time.addWidget(self.cmb_plt_time_resp) layH_ctrl_time.addStretch(1) layH_ctrl_time.addWidget(self.lbl_plt_time_stim) layH_ctrl_time.addWidget(self.cmb_plt_time_stim) layH_ctrl_time.addStretch(1) layH_ctrl_time.addWidget(self.lbl_plt_time_stmq) layH_ctrl_time.addWidget(self.cmb_plt_time_stmq) layH_ctrl_time.addStretch(2) layH_ctrl_time.addWidget(self.chk_log_time) layH_ctrl_time.addWidget(self.led_log_bottom_time) layH_ctrl_time.addStretch(1) layH_ctrl_time.addWidget(self.chk_win_time) layH_ctrl_time.addStretch(2) layH_ctrl_time.addWidget(self.chk_fx_limits) layH_ctrl_time.addStretch(10) #layH_ctrl_time.setContentsMargins(*params['wdg_margins']) self.wdg_ctrl_time = QWidget(self) self.wdg_ctrl_time.setLayout(layH_ctrl_time) # ---- end time domain ------------------ # --------------------------------------------------------------- # Controls for frequency domain # --------------------------------------------------------------- lbl_plt_freq_title = QLabel("<b>View:</b>", self) lbl_plt_freq_resp = QLabel("Response " + to_html("Y", frmt='bi'), self) self.cmb_plt_freq_resp = QComboBox(self) self.cmb_plt_freq_resp.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_freq_resp, self.plt_freq_resp) self.cmb_plt_freq_resp.setToolTip( "<span>Plot style for response.</span>") self.lbl_plt_freq_stim = QLabel("Stimulus " + to_html("X", frmt='bi'), self) self.cmb_plt_freq_stim = QComboBox(self) self.cmb_plt_freq_stim.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_freq_stim, self.plt_freq_stim) self.cmb_plt_freq_stim.setToolTip( "<span>Plot style for stimulus.</span>") self.lbl_plt_freq_stmq = QLabel( "Fixp. Stim. " + to_html("X_Q", frmt='bi'), self) self.cmb_plt_freq_stmq = QComboBox(self) self.cmb_plt_freq_stmq.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_freq_stmq, self.plt_freq_stmq) self.cmb_plt_freq_stmq.setToolTip( "<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>" ) self.chk_log_freq = QCheckBox("dB : min.", self) self.chk_log_freq.setObjectName("chk_log_freq") self.chk_log_freq.setToolTip( "<span>Logarithmic scale for y-axis.</span>") self.chk_log_freq.setChecked(True) self.led_log_bottom_freq = QLineEdit(self) self.led_log_bottom_freq.setText(str(self.bottom_f)) self.led_log_bottom_freq.setToolTip( "<span>Minimum display value for log. scale.</span>") self.led_log_bottom_freq.setVisible(self.chk_log_freq.isChecked()) if not self.chk_log_freq.isChecked(): self.chk_log_freq.setText("dB") self.bottom_f = 0 self.lbl_win_fft = QLabel("Window: ", self) self.cmb_win_fft = QComboBox(self) self.cmb_win_fft.addItems(get_window_names()) self.cmb_win_fft.setToolTip("FFT window type.") qset_cmb_box(self.cmb_win_fft, self.window_name) self.cmb_win_fft_variant = QComboBox(self) self.cmb_win_fft_variant.setToolTip("FFT window variant.") self.cmb_win_fft_variant.setVisible(False) self.lblWinPar1 = QLabel("Param1") self.ledWinPar1 = QLineEdit(self) self.ledWinPar1.setText("1") self.ledWinPar1.setObjectName("ledWinPar1") self.lblWinPar2 = QLabel("Param2") self.ledWinPar2 = QLineEdit(self) self.ledWinPar2.setText("2") self.ledWinPar2.setObjectName("ledWinPar2") self.chk_Hf = QCheckBox(self) self.chk_Hf.setObjectName("chk_Hf") self.chk_Hf.setToolTip( "<span>Show ideal frequency response, calculated " "from the filter coefficients.</span>") self.chk_Hf.setChecked(False) self.chk_Hf_lbl = QLabel(to_html("H_id (f)", frmt="bi"), self) layH_ctrl_freq = QHBoxLayout() layH_ctrl_freq.addWidget(lbl_plt_freq_title) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(lbl_plt_freq_resp) layH_ctrl_freq.addWidget(self.cmb_plt_freq_resp) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(self.lbl_plt_freq_stim) layH_ctrl_freq.addWidget(self.cmb_plt_freq_stim) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(self.lbl_plt_freq_stmq) layH_ctrl_freq.addWidget(self.cmb_plt_freq_stmq) layH_ctrl_freq.addStretch(2) layH_ctrl_freq.addWidget(self.chk_log_freq) layH_ctrl_freq.addWidget(self.led_log_bottom_freq) layH_ctrl_freq.addStretch(2) layH_ctrl_freq.addWidget(self.lbl_win_fft) layH_ctrl_freq.addWidget(self.cmb_win_fft) layH_ctrl_freq.addWidget(self.cmb_win_fft_variant) layH_ctrl_freq.addWidget(self.lblWinPar1) layH_ctrl_freq.addWidget(self.ledWinPar1) layH_ctrl_freq.addWidget(self.lblWinPar2) layH_ctrl_freq.addWidget(self.ledWinPar2) layH_ctrl_freq.addStretch(2) layH_ctrl_freq.addWidget(self.chk_Hf) layH_ctrl_freq.addWidget(self.chk_Hf_lbl) layH_ctrl_freq.addStretch(10) #layH_ctrl_freq.setContentsMargins(*params['wdg_margins']) self.wdg_ctrl_freq = QWidget(self) self.wdg_ctrl_freq.setLayout(layH_ctrl_freq) # ---- end Frequency Domain ------------------ # --------------------------------------------------------------- # Controls for stimuli # --------------------------------------------------------------- lbl_title_stim = QLabel("<b>Stimulus:</b>", self) self.lblStimulus = QLabel(to_html("Shape ", frmt='bi'), self) self.cmbStimulus = QComboBox(self) self.cmbStimulus.addItems([ "None", "Pulse", "Step", "StepErr", "Cos", "Sine", "Triang", "Saw", "Rect", "Comb", "AM", "FM", "PM" ]) self.cmbStimulus.setToolTip("Stimulus type.") qset_cmb_box(self.cmbStimulus, self.stim) self.chk_stim_bl = QCheckBox("BL", self) self.chk_stim_bl.setToolTip( "<span>The signal is bandlimited to the Nyquist frequency " "to avoid aliasing. However, it is much slower to generate " "than the regular version.</span>") self.chk_stim_bl.setChecked(True) self.chk_stim_bl.setObjectName("stim_bl") self.chk_scale_impz_f = QCheckBox("Scale", self) self.chk_scale_impz_f.setToolTip( "<span>Scale the FFT of the impulse response with <i>N<sub>FFT</sub></i> " "so that it has the same magnitude as |H(f)|. DC and Noise need to be " "turned off.</span>") self.chk_scale_impz_f.setChecked(True) self.chk_scale_impz_f.setObjectName("scale_impz_f") self.lblDC = QLabel(to_html("DC =", frmt='bi'), self) self.ledDC = QLineEdit(self) self.ledDC.setText(str(self.DC)) self.ledDC.setToolTip("DC Level") self.ledDC.setObjectName("stimDC") layHCmbStim = QHBoxLayout() layHCmbStim.addWidget(self.cmbStimulus) layHCmbStim.addWidget(self.chk_stim_bl) layHCmbStim.addWidget(self.chk_scale_impz_f) layVlblCmbDC = QVBoxLayout() layVlblCmbDC.addWidget(self.lblStimulus) layVlblCmbDC.addWidget(self.lblDC) #layVlblAmp.setAlignment(Qt.AlignTop) layVCmbDC = QVBoxLayout() layVCmbDC.addLayout(layHCmbStim) layVCmbDC.addWidget(self.ledDC) #---------------------------------------------- self.lblAmp1 = QLabel(to_html("A_1", frmt='bi') + " =", self) self.ledAmp1 = QLineEdit(self) self.ledAmp1.setText(str(self.A1)) self.ledAmp1.setToolTip("Stimulus amplitude") self.ledAmp1.setObjectName("stimAmp1") self.lblAmp2 = QLabel(to_html("A_2", frmt='bi') + " =", self) self.ledAmp2 = QLineEdit(self) self.ledAmp2.setText(str(self.A2)) self.ledAmp2.setToolTip("Stimulus amplitude 2") self.ledAmp2.setObjectName("stimAmp2") layVlblAmp = QVBoxLayout() layVlblAmp.addWidget(self.lblAmp1) layVlblAmp.addWidget(self.lblAmp2) #layVlblAmp.setAlignment(Qt.AlignTop) # labels are aligned incorrectly layVledAmp = QVBoxLayout() layVledAmp.addWidget(self.ledAmp1) layVledAmp.addWidget(self.ledAmp2) #layVledAmp.setAlignment(Qt.AlignTop) #---------------------------------------------- self.lblPhi1 = QLabel(to_html("φ_1", frmt='bi') + " =", self) self.ledPhi1 = QLineEdit(self) self.ledPhi1.setText(str(self.phi1)) self.ledPhi1.setToolTip("Stimulus phase") self.ledPhi1.setObjectName("stimPhi1") self.lblPhU1 = QLabel(to_html("°", frmt='b'), self) self.lblPhi2 = QLabel(to_html("φ_2", frmt='bi') + " =", self) self.ledPhi2 = QLineEdit(self) self.ledPhi2.setText(str(self.phi2)) self.ledPhi2.setToolTip("Stimulus phase 2") self.ledPhi2.setObjectName("stimPhi2") self.lblPhU2 = QLabel(to_html("°", frmt='b'), self) layVlblPhi = QVBoxLayout() layVlblPhi.addWidget(self.lblPhi1) layVlblPhi.addWidget(self.lblPhi2) layVledPhi = QVBoxLayout() layVledPhi.addWidget(self.ledPhi1) layVledPhi.addWidget(self.ledPhi2) layVlblPhU = QVBoxLayout() layVlblPhU.addWidget(self.lblPhU1) layVlblPhU.addWidget(self.lblPhU2) #---------------------------------------------- self.lblFreq1 = QLabel(to_html("f_1", frmt='bi') + " =", self) self.ledFreq1 = QLineEdit(self) self.ledFreq1.setText(str(self.f1)) self.ledFreq1.setToolTip("Stimulus frequency 1") self.ledFreq1.setObjectName("stimFreq1") self.lblFreqUnit1 = QLabel("f_S", self) self.lblFreq2 = QLabel(to_html("f_2", frmt='bi') + " =", self) self.ledFreq2 = QLineEdit(self) self.ledFreq2.setText(str(self.f2)) self.ledFreq2.setToolTip("Stimulus frequency 2") self.ledFreq2.setObjectName("stimFreq2") self.lblFreqUnit2 = QLabel("f_S", self) layVlblfreq = QVBoxLayout() layVlblfreq.addWidget(self.lblFreq1) layVlblfreq.addWidget(self.lblFreq2) layVledfreq = QVBoxLayout() layVledfreq.addWidget(self.ledFreq1) layVledfreq.addWidget(self.ledFreq2) layVlblfreqU = QVBoxLayout() layVlblfreqU.addWidget(self.lblFreqUnit1) layVlblfreqU.addWidget(self.lblFreqUnit2) #---------------------------------------------- self.lblNoise = QLabel(to_html("Noise: ", frmt='bi'), self) self.cmbNoise = QComboBox(self) self.cmbNoise.addItems(["None", "Gauss", "Uniform", "PRBS"]) self.cmbNoise.setToolTip("Type of additive noise.") qset_cmb_box(self.cmbNoise, self.noise) self.lblNoi = QLabel("not initialized", self) self.ledNoi = QLineEdit(self) self.ledNoi.setText(str(self.noi)) self.ledNoi.setToolTip("not initialized") self.ledNoi.setObjectName("stimNoi") layVlblNoi = QVBoxLayout() layVlblNoi.addWidget(self.lblNoise) layVlblNoi.addWidget(self.lblNoi) layVcmbledNoi = QVBoxLayout() layVcmbledNoi.addWidget(self.cmbNoise) layVcmbledNoi.addWidget(self.ledNoi) #---------------------------------------------- #layG_ctrl_stim = QGridLayout() layH_ctrl_stim = QHBoxLayout() layH_ctrl_stim.addWidget(lbl_title_stim) layH_ctrl_stim.addStretch(1) layH_ctrl_stim.addLayout(layVlblCmbDC) layH_ctrl_stim.addLayout(layVCmbDC) layH_ctrl_stim.addStretch(1) layH_ctrl_stim.addLayout(layVlblAmp) layH_ctrl_stim.addLayout(layVledAmp) layH_ctrl_stim.addLayout(layVlblPhi) layH_ctrl_stim.addLayout(layVledPhi) layH_ctrl_stim.addLayout(layVlblPhU) layH_ctrl_stim.addStretch(1) layH_ctrl_stim.addLayout(layVlblfreq) layH_ctrl_stim.addLayout(layVledfreq) layH_ctrl_stim.addLayout(layVlblfreqU) layH_ctrl_stim.addStretch(1) layH_ctrl_stim.addLayout(layVlblNoi) layH_ctrl_stim.addLayout(layVcmbledNoi) layH_ctrl_stim.addStretch(10) self.wdg_ctrl_stim = QWidget(self) self.wdg_ctrl_stim.setLayout(layH_ctrl_stim) # --------- end stimuli --------------------------------- # frequency widgets require special handling as they are scaled with f_s self.ledFreq1.installEventFilter(self) self.ledFreq2.installEventFilter(self) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- # --- run control --- self.led_N_start.editingFinished.connect(self.update_N) self.led_N_points.editingFinished.connect(self.update_N) # --- frequency control --- # careful! currentIndexChanged passes the current index to _update_win_fft self.cmb_win_fft.currentIndexChanged.connect(self._update_win_fft) self.ledWinPar1.editingFinished.connect(self._read_param1) self.ledWinPar2.editingFinished.connect(self._read_param2) # --- stimulus control --- self.chk_stim_options.clicked.connect(self._show_stim_options) self.chk_stim_bl.clicked.connect(self._enable_stim_widgets) self.cmbStimulus.currentIndexChanged.connect(self._enable_stim_widgets) self.cmbNoise.currentIndexChanged.connect(self._update_noi) self.ledNoi.editingFinished.connect(self._update_noi) self.ledAmp1.editingFinished.connect(self._update_amp1) self.ledAmp2.editingFinished.connect(self._update_amp2) self.ledPhi1.editingFinished.connect(self._update_phi1) self.ledPhi2.editingFinished.connect(self._update_phi2) self.ledDC.editingFinished.connect(self._update_DC)
class SelectFilter(QWidget): """ Construct and read combo boxes for selecting the filter, consisting of the following hierarchy: 1. Response Type rt (LP, HP, Hilbert, ...) 2. Filter Type ft (IIR, FIR, CIC ...) 3. Filter Class (Butterworth, ...) Every time a combo box is changed manually, the filter tree for the selected response resp. filter type is read and the combo box(es) further down in the hierarchy are populated according to the available combinations. sig_tx({'filt_changed'}) is emitted and propagated to input_filter_specs.py where it triggers the recreation of all subwidgets. """ sig_tx = pyqtSignal(object) # outgoing from pyfda.libs.pyfda_qt_lib import emit def __init__(self, parent=None): super(SelectFilter, self).__init__(parent) self.fc_last = '' # previous filter class self._construct_UI() self._set_response_type() # first time initialization 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 (Chebyshev, ...) 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>Choose filter type, either recursive (Infinite Impulse Response) " "or transversal (Finite Impulse Response).</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( f"KeyError: {e} has no corresponding full name in rc.rt_names." ) 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.filter_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 emit 'filt_changed' 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 load_dict(self): """ Reload comboboxes from filter dictionary to update changed settings after loading a filter design from disk. `load_dict` uses the automatism of _set_response_type etc. of checking whether the previously selected filter design method is also available for the new combination. """ # find index for response type: rt_idx = self.cmbResponseType.findData(fb.fil[0]['rt']) self.cmbResponseType.setCurrentIndex(rt_idx) self._set_response_type() # ------------------------------------------------------------------------------ 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_filter_type(self, enb_signal=False): """" Triggered when cmbFilterType (IIR, FIR, ...) is changed: - read filter type ft and copy it to fb.fil[0]['ft'] and self.ft - (re)construct design method combo, adding displayed text (e.g. "Chebyshev 1") and hidden data (e.g. "cheby1") """ # Read out current setting of comboBox and convert to string fb.fil[0]['ft'] = self.ft = qget_cmb_box(self.cmbFilterType) # logger.debug("InputFilter.set_filter_type triggered: {0}".format( self.ft)) # --------------------------------------------------------------- # Get all available design methods for new ft from fil_tree and # - Collect them in fc_list # - Rebuild design method combobox entries for new ft setting: # The combobox is populated with the "long name", # the internal name is stored in comboBox.itemData self.cmbFilterClass.blockSignals(True) self.cmbFilterClass.clear() fc_list = [] for fc in sorted(fb.fil_tree[self.rt][self.ft]): self.cmbFilterClass.addItem(fb.filter_classes[fc]['name'], fc) fc_list.append(fc) logger.debug("fc_list: {0}\n{1}".format(fc_list, fb.fil[0]['fc'])) # Does new ft also provide the previous design method (e.g. ellip)? # Has filter been instantiated? if fb.fil[0]['fc'] in fc_list and ff.fil_inst: # yes, set same fc as before fc_idx = self.cmbFilterClass.findText( fb.filter_classes[fb.fil[0]['fc']]['name']) logger.debug("fc_idx : %s", fc_idx) self.cmbFilterClass.setCurrentIndex(fc_idx) else: self.cmbFilterClass.setCurrentIndex(0) # no, set index 0 self.cmbFilterClass.blockSignals(False) self._set_design_method(enb_signal) # ------------------------------------------------------------------------------ def _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(f"InputFilter.set_design_method triggered: {fc}\n" f"Returned error code {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() # )) # # ============================================================================= # construct dyn. subwidgets if available if hasattr(ff.fil_inst, 'construct_UI'): self._construct_dyn_widgets() self.fc_last = fb.fil[0]['fc'] self.load_filter_order(enb_signal) # ------------------------------------------------------------------------------ def load_filter_order(self, enb_signal=False): """ Called by set_design_method or from InputSpecs (with enb_signal = False), load filter order setting from fb.fil[0] and update widgets """ # collect dict_keys of available filter order [fo] methods for selected # design method [fc] from fil_tree (explicit list() needed for Python 3) fo_dict = fb.fil_tree[fb.fil[0]['rt']][fb.fil[0]['ft']][fb.fil[0] ['fc']] fo_list = list(fo_dict.keys()) # is currently selected fo setting available for (new) fc ? if fb.fil[0]['fo'] in fo_list: self.fo = fb.fil[0]['fo'] # keep current setting else: self.fo = fo_list[0] # use first list entry from filterTree fb.fil[0]['fo'] = self.fo # and update fo method # check whether fo widget is active, disabled or invisible if 'fo' in fo_dict[self.fo] and len(fo_dict[self.fo]['fo']) > 1: status = fo_dict[self.fo]['fo'][0] else: status = 'i' # Determine which subwidgets are __visible__ self.chkMinOrder.setVisible('min' in fo_list) self.ledOrderN.setVisible(status in {'a', 'd'}) self.lblOrderN.setVisible(status in {'a', 'd'}) # Determine which subwidgets are __enabled__ self.chkMinOrder.setChecked(fb.fil[0]['fo'] == 'min') self.ledOrderN.setText(str(fb.fil[0]['N'])) self.ledOrderN.setEnabled(not self.chkMinOrder.isChecked() and status == 'a') self.lblOrderN.setEnabled(not self.chkMinOrder.isChecked() and status == 'a') if enb_signal: logger.debug("Emit 'filt_changed'") self.emit({'filt_changed': 'filter_type'}) # ------------------------------------------------------------------------------ def _set_filter_order(self, enb_signal=False): """ Triggered when either ledOrderN or chkMinOrder are edited: - copy settings to fb.fil[0] - emit 'filt_changed' if enb_signal is True """ # Determine which subwidgets are _enabled_ if self.chkMinOrder.isVisible(): self.ledOrderN.setEnabled(not self.chkMinOrder.isChecked()) self.lblOrderN.setEnabled(not self.chkMinOrder.isChecked()) if self.chkMinOrder.isChecked() is True: # update in case N has been changed outside this class self.ledOrderN.setText(str(fb.fil[0]['N'])) fb.fil[0].update({'fo': 'min'}) else: fb.fil[0].update({'fo': 'man'}) else: self.lblOrderN.setEnabled(self.fo == 'man') self.ledOrderN.setEnabled(self.fo == 'man') # read manual filter order, convert to positive integer and store it # in filter dictionary. ordn = safe_eval(self.ledOrderN.text(), fb.fil[0]['N'], return_type='int', sign='pos') ordn = ordn if ordn > 0 else 1 self.ledOrderN.setText(str(ordn)) fb.fil[0].update({'N': ordn}) if enb_signal: logger.debug("Emit 'filt_changed'") self.emit({'filt_changed': 'filter_order_widget'}) # ------------------------------------------------------------------------------ def _destruct_dyn_widgets(self): """ Delete the dynamically created filter design subwidget (if there is one) see http://stackoverflow.com/questions/13827798/proper-way-to-cleanup- widgets-in-pyqt This does NOT work when the subwidgets to be deleted and created are identical, as the deletion is only performed when the current scope has been left (?)! Hence, it is necessary to skip this method when the new design method is the same as the old one. """ if hasattr(ff.fil_inst, 'wdg_fil'): # not needed, connection is destroyed automatically # ff.fil_inst.sig_tx.disconnect() try: # remove widget from layout self.layHDynWdg.removeWidget(self.dyn_wdg_fil) # delete UI widget when scope has been left self.dyn_wdg_fil.deleteLater() except AttributeError as e: logger.error("Could not destruct_UI!\n{0}".format(e)) ff.fil_inst.deleteLater( ) # delete QWidget when scope has been left # ------------------------------------------------------------------------------ def _construct_dyn_widgets(self): """ Create filter widget UI dynamically (if the filter routine has one) and connect its sig_tx signal to sig_tx in this scope. """ ff.fil_inst.construct_UI() if hasattr(ff.fil_inst, 'wdg_fil'): try: self.dyn_wdg_fil = getattr(ff.fil_inst, 'wdg_fil') self.layHDynWdg.addWidget(self.dyn_wdg_fil, stretch=1) except AttributeError as e: logger.warning(e) if hasattr(ff.fil_inst, 'sig_tx'): ff.fil_inst.sig_tx.connect(self.sig_tx)
def _construct_UI(self): """ Intitialize the widget, consisting of: - Matplotlib widget with NavigationToolbar - Frame with control elements """ self.bfont = QFont() self.bfont.setBold(True) self.qfft_win_select = QFFTWinSelector(self, self.win_dict) self.lbl_N = QLabel(to_html("N =", frmt='bi')) self.led_N = QLineEdit(self) self.led_N.setText(str(self.N_view)) self.led_N.setMaximumWidth(qtext_width(N_x=8)) self.led_N.setToolTip( "<span>Number of window data points to display.</span>") # By default, the enter key triggers the default 'dialog action' in QDialog # widgets. This activates one of the pushbuttons. self.but_log_t = QPushButton("dB", default=False, autoDefault=False) self.but_log_t.setMaximumWidth(qtext_width(" dB ")) self.but_log_t.setObjectName("chk_log_time") self.but_log_t.setCheckable(True) self.but_log_t.setChecked(False) self.but_log_t.setToolTip("Display in dB") self.led_log_bottom_t = QLineEdit(self) self.led_log_bottom_t.setVisible(self.but_log_t.isChecked()) self.led_log_bottom_t.setText(str(self.bottom_t)) self.led_log_bottom_t.setMaximumWidth(qtext_width(N_x=6)) self.led_log_bottom_t.setToolTip( "<span>Minimum display value for log. scale.</span>") self.lbl_log_bottom_t = QLabel(to_html("min =", frmt='bi'), self) self.lbl_log_bottom_t.setVisible(self.but_log_t.isChecked()) self.but_norm_f = QPushButton("Max=1", default=False, autoDefault=False) self.but_norm_f.setCheckable(True) self.but_norm_f.setChecked(True) self.but_norm_f.setMaximumWidth(qtext_width(text=" Max=1 ")) self.but_norm_f.setToolTip( "Normalize window spectrum for a maximum of 1.") self.but_half_f = QPushButton("0...½", default=False, autoDefault=False) self.but_half_f.setCheckable(True) self.but_half_f.setChecked(True) self.but_half_f.setMaximumWidth(qtext_width(text=" 0...½ ")) self.but_half_f.setToolTip( "Display window spectrum in the range 0 ... 0.5 f_S.") # By default, the enter key triggers the default 'dialog action' in QDialog # widgets. This activates one of the pushbuttons. self.but_log_f = QPushButton("dB", default=False, autoDefault=False) self.but_log_f.setMaximumWidth(qtext_width(" dB ")) self.but_log_f.setObjectName("chk_log_freq") self.but_log_f.setToolTip("<span>Display in dB.</span>") self.but_log_f.setCheckable(True) self.but_log_f.setChecked(True) self.lbl_log_bottom_f = QLabel(to_html("min =", frmt='bi'), self) self.lbl_log_bottom_f.setVisible(self.but_log_f.isChecked()) self.led_log_bottom_f = QLineEdit(self) self.led_log_bottom_f.setVisible(self.but_log_t.isChecked()) self.led_log_bottom_f.setText(str(self.bottom_f)) self.led_log_bottom_f.setMaximumWidth(qtext_width(N_x=6)) self.led_log_bottom_f.setToolTip( "<span>Minimum display value for log. scale.</span>") # ---------------------------------------------------------------------- # ### frmControls ### # # This widget encompasses all control subwidgets # ---------------------------------------------------------------------- layH_win_select = QHBoxLayout() layH_win_select.addWidget(self.qfft_win_select) layH_win_select.setContentsMargins(0, 0, 0, 0) layH_win_select.addStretch(1) frmQFFT = QFrame(self) frmQFFT.setObjectName("frmQFFT") frmQFFT.setLayout(layH_win_select) hline = QHLine() layHControls = QHBoxLayout() layHControls.addWidget(self.lbl_N) layHControls.addWidget(self.led_N) layHControls.addStretch(1) layHControls.addWidget(self.lbl_log_bottom_t) layHControls.addWidget(self.led_log_bottom_t) layHControls.addWidget(self.but_log_t) layHControls.addStretch(5) layHControls.addWidget(QVLine(width=2)) layHControls.addStretch(5) layHControls.addWidget(self.but_norm_f) layHControls.addStretch(1) layHControls.addWidget(self.but_half_f) layHControls.addStretch(1) layHControls.addWidget(self.lbl_log_bottom_f) layHControls.addWidget(self.led_log_bottom_f) layHControls.addWidget(self.but_log_f) layVControls = QVBoxLayout() layVControls.addWidget(frmQFFT) layVControls.addWidget(hline) layVControls.addLayout(layHControls) frmControls = QFrame(self) frmControls.setObjectName("frmControls") frmControls.setLayout(layVControls) # ---------------------------------------------------------------------- # ### mplwidget ### # # Layout layVMainMpl (VBox) is defined within MplWidget, additional # widgets can be added below the matplotlib widget (here: frmControls) # # ---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(frmControls) self.mplwidget.layVMainMpl.setContentsMargins(0, 0, 0, 0) # ---------------------------------------------------------------------- # ### frmInfo ### # # This widget encompasses the text info box and the table with window # parameters. # ---------------------------------------------------------------------- self.tbl_win_props = QTableWidget(self.tbl_rows, self.tbl_cols, self) self.tbl_win_props.setAlternatingRowColors(True) # Auto-resize of table can be set using the header (although it is invisible) self.tbl_win_props.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) # Only the columns with data are stretched, the others are minimum size self.tbl_win_props.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch) self.tbl_win_props.horizontalHeader().setSectionResizeMode( 4, QHeaderView.Stretch) self.tbl_win_props.verticalHeader().setVisible(False) self.tbl_win_props.horizontalHeader().setVisible(False) self.tbl_win_props.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.tbl_win_props.setFixedHeight( self.tbl_win_props.rowHeight(0) * self.tbl_rows + self.tbl_win_props.frameWidth() * 2) # self.tbl_win_props.setVerticalScrollBarPolicy( # Qt.ScrollBarAlwaysOff) # self.tbl_win_props.setHorizontalScrollBarPolicy( # Qt.ScrollBarAlwaysOff) self._construct_table(self.tbl_rows, self.tbl_cols, " ") self.txtInfoBox = QTextBrowser(self) layVInfo = QVBoxLayout(self) layVInfo.addWidget(self.tbl_win_props) layVInfo.addWidget(self.txtInfoBox) frmInfo = QFrame(self) frmInfo.setObjectName("frmInfo") frmInfo.setLayout(layVInfo) # ---------------------------------------------------------------------- # ### splitter ### # # This widget encompasses all subwidgets # ---------------------------------------------------------------------- splitter = QSplitter(self) splitter.setOrientation(Qt.Vertical) splitter.addWidget(self.mplwidget) splitter.addWidget(frmInfo) # 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, 800]) layVMain = QVBoxLayout() layVMain.addWidget(splitter) self.setLayout(layVMain) # ---------------------------------------------------------------------- # Set subplots # self.ax = self.mplwidget.fig.subplots(nrows=1, ncols=2) self.ax_t = self.ax[0] self.ax_f = self.ax[1] self.calc_win_draw() # initial calculation and drawing # ---------------------------------------------------------------------- # GLOBAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.sig_rx.connect(self.process_sig_rx) self.sig_rx.connect(self.qfft_win_select.sig_rx) # ---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.but_log_f.clicked.connect(self.update_view) self.but_log_t.clicked.connect(self.update_view) self.led_log_bottom_t.editingFinished.connect(self.update_bottom) self.led_log_bottom_f.editingFinished.connect(self.update_bottom) self.led_N.editingFinished.connect(self.calc_win_draw) self.but_norm_f.clicked.connect(self.calc_win_draw) self.but_half_f.clicked.connect(self.update_view) self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx) self.tbl_win_props.itemClicked.connect(self._handle_item_clicked) self.qfft_win_select.sig_tx.connect(self.update_fft_win)