Example #1
0
class Input_Info(QWidget):
    """
    Create widget for displaying infos about filter specs and filter design method
    """
    sig_rx = pyqtSignal(object)  # incoming signals from input_tab_widgets
    sig_tx = pyqtSignal(object)
    from pyfda.libs.pyfda_qt_lib import emit

    def __init__(self, parent=None):
        super(Input_Info, self).__init__(parent)

        self.tab_label = 'Info'
        self.tool_tip = (
            "<span>Display the achieved filter specifications"
            " and more info about the filter design algorithm.</span>")

        self._construct_UI()
        self.load_dict()

    def process_sig_rx(self, dict_sig=None):
        """
        Process signals coming from sig_rx
        """
        # logger.debug("Processing {0}: {1}".format(type(dict_sig).__name__, dict_sig))
        if 'data_changed' in dict_sig or 'view_changed' in dict_sig\
                or 'specs_changed' in dict_sig:
            self.load_dict()

    def _construct_UI(self):
        """
        Intitialize the widget, consisting of:
        - Checkboxes for selecting the info to be displayed
        - A large text window for displaying infos about the filter design
          algorithm
        """
        bfont = QFont()
        bfont.setBold(True)

        # ============== UI Layout =====================================
        # widget / subwindow for filter infos
#        self.butFiltPerf = QToolButton("H(f)", self)
        self.butFiltPerf = QPushButton(self)
        self.butFiltPerf.setText("H(f)")
        self.butFiltPerf.setCheckable(True)
        self.butFiltPerf.setChecked(True)
        self.butFiltPerf.setToolTip("Display frequency response at test frequencies.")

        self.butDebug = QPushButton(self)
        self.butDebug.setText("Debug")
        self.butDebug.setCheckable(True)
        self.butDebug.setChecked(False)
        self.butDebug.setToolTip("Show debugging options.")

        self.butAbout = QPushButton("About", self)  # pop-up "About" window

        self.butSettings = QPushButton("Settings", self)  #
        self.butSettings.setCheckable(True)
        self.butSettings.setChecked(False)
        self.butSettings.setToolTip("Display and set some settings")

        layHControls1 = QHBoxLayout()
        layHControls1.addWidget(self.butFiltPerf)
        layHControls1.addWidget(self.butAbout)
        layHControls1.addWidget(self.butSettings)
        layHControls1.addWidget(self.butDebug)

        self.butDocstring = QPushButton("Doc$", self)
        self.butDocstring.setCheckable(True)
        self.butDocstring.setChecked(False)
        self.butDocstring.setToolTip("Display docstring from python filter method.")

        self.butRichText = QPushButton("RTF", self)
        self.butRichText.setCheckable(HAS_DOCUTILS)
        self.butRichText.setChecked(HAS_DOCUTILS)
        self.butRichText.setEnabled(HAS_DOCUTILS)
        self.butRichText.setToolTip("Render documentation in Rich Text Format.")

        self.butFiltDict = QPushButton("FiltDict", self)
        self.butFiltDict.setToolTip("Show filter dictionary for debugging.")
        self.butFiltDict.setCheckable(True)
        self.butFiltDict.setChecked(False)

        self.butFiltTree = QPushButton("FiltTree", self)
        self.butFiltTree.setToolTip("Show filter tree for debugging.")
        self.butFiltTree.setCheckable(True)
        self.butFiltTree.setChecked(False)

        layHControls2 = QHBoxLayout()
        layHControls2.addWidget(self.butDocstring)
        # layHControls2.addStretch(1)
        layHControls2.addWidget(self.butRichText)
        # layHControls2.addStretch(1)
        layHControls2.addWidget(self.butFiltDict)
        # layHControls2.addStretch(1)
        layHControls2.addWidget(self.butFiltTree)

        self.frmControls2 = QFrame(self)
        self.frmControls2.setLayout(layHControls2)
        self.frmControls2.setVisible(self.butDebug.isChecked())
        self.frmControls2.setContentsMargins(0, 0, 0, 0)

        lbl_settings_NFFT = QLabel(to_html("N_FFT =", frmt='bi'), self)
        self.led_settings_NFFT = QLineEdit(self)
        self.led_settings_NFFT.setText(str(params['N_FFT']))
        self.led_settings_NFFT.setToolTip("<span>Number of FFT points for frequency "
                                          "domain widgets.</span>")

        layGSettings = QGridLayout()
        layGSettings.addWidget(lbl_settings_NFFT, 1, 0)
        layGSettings.addWidget(self.led_settings_NFFT, 1, 1)

        self.frmSettings = QFrame(self)
        self.frmSettings.setLayout(layGSettings)
        self.frmSettings.setVisible(self.butSettings.isChecked())
        self.frmSettings.setContentsMargins(0, 0, 0, 0)

        layVControls = QVBoxLayout()
        layVControls.addLayout(layHControls1)
        layVControls.addWidget(self.frmControls2)
        layVControls.addWidget(self.frmSettings)

        self.frmMain = QFrame(self)
        self.frmMain.setLayout(layVControls)

        self.tblFiltPerf = QTableWidget(self)
        self.tblFiltPerf.setAlternatingRowColors(True)
#        self.tblFiltPerf.verticalHeader().setVisible(False)
        self.tblFiltPerf.horizontalHeader().setHighlightSections(False)
        self.tblFiltPerf.horizontalHeader().setFont(bfont)
        self.tblFiltPerf.verticalHeader().setHighlightSections(False)
        self.tblFiltPerf.verticalHeader().setFont(bfont)

        self.txtFiltInfoBox = QTextBrowser(self)
        self.txtFiltDict = QTextBrowser(self)
        self.txtFiltTree = QTextBrowser(self)

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmMain)

#        layVMain.addLayout(self.layHControls)
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(self.tblFiltPerf)
        splitter.addWidget(self.txtFiltInfoBox)
        splitter.addWidget(self.txtFiltDict)
        splitter.addWidget(self.txtFiltTree)
        # setSizes uses absolute pixel values, but can be "misused" by specifying values
        # that are way too large: in this case, the space is distributed according
        # to the _ratio_ of the values:
        splitter.setSizes([3000, 10000, 1000, 1000])
        layVMain.addWidget(splitter)

        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.butFiltPerf.clicked.connect(self._show_filt_perf)
        self.butAbout.clicked.connect(self._about_window)
        self.butSettings.clicked.connect(self._show_settings)
        self.led_settings_NFFT.editingFinished.connect(self._update_settings_nfft)
        self.butDebug.clicked.connect(self._show_debug)

        self.butFiltDict.clicked.connect(self._show_filt_dict)
        self.butFiltTree.clicked.connect(self._show_filt_tree)
        self.butDocstring.clicked.connect(self._show_doc)
        self.butRichText.clicked.connect(self._show_doc)

    def _about_window(self):
        self.about_widget = AboutWindow(self)  # important: Handle must be class attribute
        # self.opt_widget.show() # modeless dialog, i.e. non-blocking
        self.about_widget.exec_()  # modal dialog (blocking)

# ------------------------------------------------------------------------------
    def _show_debug(self):
        """
        Show / hide debug options depending on the state of the debug button
        """
        self.frmControls2.setVisible(self.butDebug.isChecked())

# ------------------------------------------------------------------------------
    def _show_settings(self):
        """
        Show / hide settings options depending on the state of the settings button
        """
        self.frmSettings.setVisible(self.butSettings.isChecked())

    def _update_settings_nfft(self):
        """ Update value for self.par1 from QLineEdit Widget"""
        params['N_FFT'] = safe_eval(self.led_settings_NFFT.text(), params['N_FFT'],
                                    sign='pos', return_type='int')
        self.led_settings_NFFT.setText(str(params['N_FFT']))
        self.emit({'data_changed': 'n_fft'})

# ------------------------------------------------------------------------------
    def load_dict(self):
        """
        update docs and filter performance
        """
        self._show_doc()
        self._show_filt_perf()
        self._show_filt_dict()
        self._show_filt_tree()

# ------------------------------------------------------------------------------
    def _show_doc(self):
        """
        Display info from filter design file and docstring
        """
        if hasattr(ff.fil_inst, 'info'):
            if self.butRichText.isChecked():
                self.txtFiltInfoBox.setText(publish_string(
                    self._clean_doc(ff.fil_inst.info), writer_name='html',
                    settings_overrides={'output_encoding': 'unicode'}))
            else:
                self.txtFiltInfoBox.setText(textwrap.dedent(ff.fil_inst.info))
        else:
            self.txtFiltInfoBox.setText("")

        if self.butDocstring.isChecked() and hasattr(ff.fil_inst, 'info_doc'):
            if self.butRichText.isChecked():
                self.txtFiltInfoBox.append(
                    '<hr /><b>Python module docstring:</b>\n')
                for doc in ff.fil_inst.info_doc:
                    self.txtFiltInfoBox.append(publish_string(
                     self._clean_doc(doc), writer_name='html',
                     settings_overrides={'output_encoding': 'unicode'}))
            else:
                self.txtFiltInfoBox.append('\nPython module docstring:\n')
                for doc in ff.fil_inst.info_doc:
                    self.txtFiltInfoBox.append(self._clean_doc(doc))

        self.txtFiltInfoBox.moveCursor(QTextCursor.Start)

    def _clean_doc(self, doc):
        """
        Remove uniform number of leading blanks from docstrings for subsequent
        processing of rich text. The first line is treated differently, _all_
        leading blanks are removed (if any). This allows for different formats
        of docstrings.
        """
        lines = doc.splitlines()
        result = lines[0].lstrip() + "\n" + textwrap.dedent("\n".join(lines[1:]))
        return result

# ------------------------------------------------------------------------------
    def _show_filt_perf(self):
        """
        Print filter properties in a table at frequencies of interest. When
        specs are violated, colour the table entry in red.
        """

        antiC = False

        def _find_min_max(self, f_start, f_stop, unit='dB'):
            """
            Find minimum and maximum magnitude and the corresponding frequencies
            for the filter defined in the filter dict in a given frequency band
            [f_start, f_stop].
            """
            w = np.linspace(f_start, f_stop, params['N_FFT'])*2*np.pi
            [w, H] = sig.freqz(bb, aa, worN=w)

            # add antiCausals if we have them
            if (antiC):
               #
               # Evaluate transfer function of anticausal half on the same freq grid.
               #
               wa, ha = sig.freqz(bbA, aaA, worN=w)
               ha = ha.conjugate()
               #
               # Total transfer function is the product
               #
               H = H*ha

            f = w / (2.0 * pi)  # frequency normalized to f_S
            H_abs = abs(H)
            H_max = max(H_abs)
            H_min = min(H_abs)
            F_max = f[np.argmax(H_abs)]  # find the frequency where H_abs
            F_min = f[np.argmin(H_abs)]  # becomes max resp. min
            if unit == 'dB':
                H_max = 20*log10(H_max)
                H_min = 20*log10(H_min)
            return F_min, H_min, F_max, H_max
        # ------------------------------------------------------------------

        self.tblFiltPerf.setVisible(self.butFiltPerf.isChecked())
        if self.butFiltPerf.isChecked():

            bb = fb.fil[0]['ba'][0]
            aa = fb.fil[0]['ba'][1]

            # 'rpk' means nonCausal filter
            if 'rpk' in fb.fil[0]:
                antiC = True
                bbA = fb.fil[0]['baA'][0]
                aaA = fb.fil[0]['baA'][1]
                bbA = bbA.conjugate()
                aaA = aaA.conjugate()

            f_S = fb.fil[0]['f_S']

            f_lbls = []
            f_vals = []
            a_lbls = []
            a_targs = []
            a_targs_dB = []
            a_test = []
            ft = fb.fil[0]['ft']  # get filter type ('IIR', 'FIR')
            unit = fb.fil[0]['amp_specs_unit']
            unit = 'dB'  # fix this for the moment
            # construct pairs of corner frequency and corresponding amplitude
            # labels in ascending frequency for each response type
            if fb.fil[0]['rt'] in {'LP', 'HP', 'BP', 'BS', 'HIL'}:
                if fb.fil[0]['rt'] == 'LP':
                    f_lbls = ['F_PB', 'F_SB']
                    a_lbls = ['A_PB', 'A_SB']
                elif fb.fil[0]['rt'] == 'HP':
                    f_lbls = ['F_SB', 'F_PB']
                    a_lbls = ['A_SB', 'A_PB']
                elif fb.fil[0]['rt'] == 'BP':
                    f_lbls = ['F_SB', 'F_PB', 'F_PB2', 'F_SB2']
                    a_lbls = ['A_SB', 'A_PB', 'A_PB', 'A_SB2']
                elif fb.fil[0]['rt'] == 'BS':
                    f_lbls = ['F_PB', 'F_SB', 'F_SB2', 'F_PB2']
                    a_lbls = ['A_PB', 'A_SB', 'A_SB', 'A_PB2']
                elif fb.fil[0]['rt'] == 'HIL':
                    f_lbls = ['F_PB', 'F_PB2']
                    a_lbls = ['A_PB', 'A_PB']

            # Try to get lists of frequency / amplitude specs from the filter dict
            # that correspond to the f_lbls / a_lbls pairs defined above
            # When one of the labels doesn't exist in the filter dict, delete
            # all corresponding amplitude and frequency entries.
                err = [False] * len(f_lbls)  # initialize error list
                f_vals = []
                a_targs = []
                for i in range(len(f_lbls)):
                    try:
                        f = fb.fil[0][f_lbls[i]]
                        f_vals.append(f)
                    except KeyError as e:
                        f_vals.append('')
                        err[i] = True
                        logger.debug(e)
                    try:
                        a = fb.fil[0][a_lbls[i]]
                        a_dB = lin2unit(fb.fil[0][a_lbls[i]], ft, a_lbls[i], unit)
                        a_targs.append(a)
                        a_targs_dB.append(a_dB)
                    except KeyError as e:
                        a_targs.append('')
                        a_targs_dB.append('')
                        err[i] = True
                        logger.debug(e)

                for i in range(len(f_lbls)):
                    if err[i]:
                        del f_lbls[i]
                        del f_vals[i]
                        del a_lbls[i]
                        del a_targs[i]
                        del a_targs_dB[i]

                f_vals = np.asarray(f_vals)  # convert to numpy array

                logger.debug("F_test_labels = %s" % f_lbls)

                # Calculate frequency response at test frequencies
                [w_test, a_test] = sig.freqz(bb, aa, 2.0 * pi * f_vals.astype(float))
                # add antiCausals if we have them
                if (antiC):
                   wa, ha = sig.freqz(bbA, aaA, 2.0 * pi * f_vals.astype(float))
                   ha = ha.conjugate()
                   a_test = a_test*ha

            (F_min, H_min, F_max, H_max) = _find_min_max(self, 0, 1, unit='V')
            # append frequencies and values for min. and max. filter reponse to
            # test vector

            f_lbls += ['Min.', 'Max.']
            # QTableView does not support direct formatting, use QLabel

            f_vals = np.append(f_vals, [F_min, F_max])
            a_targs = np.append(a_targs, [np.nan, np.nan])
            a_targs_dB = np.append(a_targs_dB, [np.nan, np.nan])
            a_test = np.append(a_test, [H_min, H_max])
            # calculate response of test frequencies in dB
            a_test_dB = -20*log10(abs(a_test))

            # get filter type ('IIR', 'FIR') for dB <-> lin conversion
            ft = fb.fil[0]['ft']
#            unit = fb.fil[0]['amp_specs_unit']
            unit = 'dB'  # make this fixed for the moment

            # build a list with the corresponding target specs:
            a_targs_pass = []
            eps = 1e-3
            for i in range(len(f_lbls)):
                if 'PB' in f_lbls[i]:
                    a_targs_pass.append((a_test_dB[i] - a_targs_dB[i]) < eps)
                    a_test[i] = 1 - abs(a_test[i])
                elif 'SB' in f_lbls[i]:
                    a_targs_pass.append(a_test_dB[i] >= a_targs_dB[i])
                else:
                    a_targs_pass.append(True)

            self.targs_spec_passed = np.all(a_targs_pass)

            logger.debug(
                "H_targ = {0}\n"
                "H_test = {1}\n"
                "H_test_dB = {2}\n"
                "F_test = {3}\n"
                "H_targ_pass = {4}\n"
                "passed: {5}\n".format(a_targs,  a_test,  a_test_dB, f_vals,
                                       a_targs_pass, self.targs_spec_passed))

            self.tblFiltPerf.setRowCount(len(a_test))  # number of table rows
            self.tblFiltPerf.setColumnCount(5)  # number of table columns

            self.tblFiltPerf.setHorizontalHeaderLabels([
                'f/{0:s}'.format(fb.fil[0]['freq_specs_unit']), 'Spec\n(dB)',
                '|H(f)|\n(dB)', 'Spec', '|H(f)|'])
            self.tblFiltPerf.setVerticalHeaderLabels(f_lbls)
            for row in range(len(a_test)):
                self.tblFiltPerf.setItem(
                    row, 0, QTableWidgetItem(str('{0:.4g}'.format(f_vals[row]*f_S))))
                self.tblFiltPerf.setItem(
                    row, 1, QTableWidgetItem(str('%2.3g'%(-a_targs_dB[row]))))
                self.tblFiltPerf.setItem(
                    row, 2, QTableWidgetItem(str('%2.3f'%(-a_test_dB[row]))))
                if a_targs[row] < 0.01:
                    self.tblFiltPerf.setItem(
                        row, 3, QTableWidgetItem(str('%.3e'%(a_targs[row]))))
                else:
                    self.tblFiltPerf.setItem(
                        row, 3, QTableWidgetItem(str('%2.4f'%(a_targs[row]))))
                if a_test[row] < 0.01:
                    self.tblFiltPerf.setItem(
                        row, 4, QTableWidgetItem(str('%.3e'%(abs(a_test[row])))))
                else:
                    self.tblFiltPerf.setItem(
                        row, 4, QTableWidgetItem(str('%.4f'%(abs(a_test[row])))))

                if not a_targs_pass[row]:
                    self.tblFiltPerf.item(row, 1).setBackground(QtGui.QColor('red'))
                    self.tblFiltPerf.item(row, 3).setBackground(QtGui.QColor('red'))

            self.tblFiltPerf.resizeColumnsToContents()
            self.tblFiltPerf.resizeRowsToContents()

# ------------------------------------------------------------------------------
    def _show_filt_dict(self):
        """
        Print filter dict for debugging
        """
        self.txtFiltDict.setVisible(self.butFiltDict.isChecked())

        fb_sorted = [str(key) + ' : ' + str(fb.fil[0][key])
                     for key in sorted(fb.fil[0].keys())]
        dictstr = pprint.pformat(fb_sorted)
#        dictstr = pprint.pformat(fb.fil[0])
        self.txtFiltDict.setText(dictstr)

# ------------------------------------------------------------------------------
    def _show_filt_tree(self):
        """
        Print filter tree for debugging
        """
        self.txtFiltTree.setVisible(self.butFiltTree.isChecked())

        ftree_sorted = ['<b>' + str(key) + ' : ' + '</b>' + str(fb.fil_tree[key])
                        for key in sorted(fb.fil_tree.keys())]
        dictstr = pprint.pformat(ftree_sorted, indent=4)
#        dictstr = pprint.pformat(fb.fil[0])
        self.txtFiltTree.setText(dictstr)
Example #2
0
    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)
Example #3
0
class AboutWindow(QDialog):
    """
    Create a pop-up widget for the About Window.
    """

    # sig_tx = pyqtSignal(dict) # outgoing

    def __init__(self, parent):
        super(AboutWindow, self).__init__(parent)
        self.setWindowTitle("About pyFDA")
        self.collect_info()
        self._construct_UI()
        qwindow_stay_on_top(self, True)

#------------------------------------------------------------------------------

    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 to_clipboard(self, my_string):
        """
        Copy version info to clipboard
        """
        mapping = [
            ('<br>', '\n'),
            ('<br />', '\n'),
            ('</tr>', '\n'),
            ('</th>', '\n==============\n'),
            ('</table>', '\n'),
            ('<hr>', '\n---------\n'),
            ('<b>', ''),
            ('</b>', ''),
            ('<tr>', ''),
            ('<td>', ''),
            ('</td>', '\t'),
            ('<th>', ''),
            ('&emsp;', ' '),
            ('<table>', ''),  # ('</a>',''),
            ("<th style='font-size:large;'>", "\n")
        ]
        # =============================================================================
        #         """ Map some HTML tags to control codes"""
        #         for k, v in mapping:
        #             my_string = my_string.replace(k, v)
        #
        #         """Remove html tags from a string"""
        #         clean = re.compile('<.*?>')
        #         fb.clipboard.setText(re.sub(clean, '', my_string))
        # =============================================================================
        fb.clipboard.setText(my_string)
#------------------------------------------------------------------------------

    def collect_info(self):
        """
        Collect information about version, imported modules in strings:

        `self.info_str` : General info, copyright, version, link to readthedocs
                          This info is always visible.

        `self.about_str`: OS, user name, directories, versions of installed software
        """


        self.info_str = ("<b><a href=https://www.github.com/chipmuenk/pyfda>pyfda</a> "
        "Version {0} (c) 2013 - 2020 Christian Münker</b><br />"
        "Design, analyze and synthesize digital filters. Docs @ "
        "<a href=https://pyfda.rtfd.org>pyfda.rtfd.org</a>"
        " (<a href=https://media.readthedocs.org/pdf/pyfda/latest/pyfda.pdf>pdf</a>)<br />"\
        .format(version.__version__))

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        user_dirs_str = ""
        if dirs.USER_DIRS:
            for d in dirs.USER_DIRS:
                user_dirs_str += d + '<br />'
        else:
            user_dirs_str = "None<br />"

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        os_str = ("<b>OS:</b> {0} {1}<br><b>User Name:</b> {2}".format(
            dirs.OS, dirs.OS_VER, dirs.USER_NAME))

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        dirs_md = ("### Directories ###\n"
                      "| *Function*    | *Path*|\n"#"|  <!-- -->     |  <!-- -->  |\n"
                      "|:  ----        |:  ----     |\n"
                      "| **Install Dir **  | `{install_dir}` |\n"
                      "| **User Module Dir ** | `{user_dir}` |\n"
                      "| **Home Dir**  |   `{home_dir}` |\n"
                      "| **Temp Dir ** | `{temp_dir}` |\n"
                      "| **Config Dir ** | `{conf_dir}` |\n"
                      "| - - - - - - -  | - - - - - - - - -|\n"
                      "| **pyFDA Config ** | `{pyfda_conf}` |\n"
                      "| **Log. Config ** | `{log_conf}` |\n"
                      "| **Logfile **  | `{log_file}` |"\
                      .format(home_dir=dirs.HOME_DIR, install_dir=dirs.INSTALL_DIR,
                              conf_dir=dirs.CONF_DIR, user_dir=user_dirs_str[:-6],
                              temp_dir=dirs.TEMP_DIR, pyfda_conf=dirs.USER_CONF_DIR_FILE,
                              log_conf=dirs.USER_LOG_CONF_DIR_FILE, log_file=dirs.LOG_DIR_FILE))

        dirs_str = markdown.markdown(dirs_md,
                                     output_format='html5',
                                     extensions=['tables'])

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        ver_str = pyfda_lib.mod_version()
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        if False:  #dirs.PYINSTALLER:
            self.lic_str = ""
        else:
            with open(os.path.join(dirs.INSTALL_DIR, "license_info.md"),
                      'r') as f:
                self.lic_str = markdown.markdown(f.read(),
                                                 output_format='html5',
                                                 extensions=['tables'])

        self.about_str = os_str + dirs_str + ver_str

#------------------------------------------------------------------------------

    def display_about_str(self):
        """ Display general "About" info """

        self.txtDisplay.setText(self.about_str + self.lic_str)

#------------------------------------------------------------------------------

    def display_changelog(self):
        """ Display changelog """
        with open(os.path.join(dirs.INSTALL_DIR, "..", "CHANGELOG.md"),
                  'r') as f:
            lic_str = markdown.markdown(f.read(), output_format='html5')
        self.txtDisplay.setText(lic_str)

#------------------------------------------------------------------------------

    def display_MIT_lic(self):
        """ Display MIT license """
        with open(os.path.join(dirs.INSTALL_DIR, "..", "LICENSE.md"),
                  'r') as f:
            lic_str = markdown.markdown(f.read(), output_format='html5')
        self.txtDisplay.setText(lic_str)

#------------------------------------------------------------------------------

    def display_GPL_lic(self):
        """ Display MIT license """
        with open(os.path.join(dirs.INSTALL_DIR, "..", "LICENSE_GPL3_0.html"),
                  'r') as f:
            lic_str = f.read()
        self.txtDisplay.setText(lic_str)
Example #4
0
    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)
Example #5
0
class Plot_FFT_win(QDialog):
    """
    Create a pop-up widget for displaying time and frequency view of an FFT
    window.

    Data is passed via the dictionary `win_dict` that is passed during construction.
    """
    # incoming
    sig_rx = pyqtSignal(object)
    # outgoing
    sig_tx = pyqtSignal(object)

    def __init__(self,
                 parent,
                 win_dict=fb.fil[0]['win_fft'],
                 sym=True,
                 title='pyFDA Window Viewer'):
        super(Plot_FFT_win, self).__init__(parent)

        self.needs_calc = True
        self.needs_draw = True
        self.needs_redraw = True

        self.bottom_f = -80  # min. value for dB display
        self.bottom_t = -60
        self.N = 32  # initial number of data points
        self.N_auto = win_dict['win_len']

        self.pad = 16  # amount of zero padding

        self.win_dict = win_dict
        self.sym = sym

        self.tbl_rows = 2
        self.tbl_cols = 6
        # initial settings for checkboxes
        self.tbl_sel = [True, True, False, False]

        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowTitle(title)
        self._construct_UI()

        qwindow_stay_on_top(self, True)

#------------------------------------------------------------------------------

    def closeEvent(self, event):
        """
        Catch closeEvent (user has tried to close the window) and send a
        signal to parent where window closing is registered before actually
        closing the window.
        """
        self.sig_tx.emit({'sender': __name__, 'closeEvent': ''})
        event.accept()

#------------------------------------------------------------------------------

    def process_sig_rx(self, dict_sig=None):
        """
        Process signals coming from the navigation toolbar and from sig_rx
        """
        logger.debug("PROCESS_SIG_RX - vis: {0}\n{1}"\
                     .format(self.isVisible(), pprint_log(dict_sig)))
        if ('view_changed' in dict_sig and dict_sig['view_changed'] == 'win')\
            or ('filt_changed' in dict_sig and dict_sig['filt_changed'] == 'firwin')\
            or self.needs_calc:
            # logger.warning("Auto: {0} - WinLen: {1}".format(self.N_auto, self.win_dict['win_len']))
            self.N_auto = self.win_dict['win_len']
            self.calc_N()

            if self.isVisible():
                self.draw()
                self.needs_calc = False
            else:
                self.needs_calc = True

        elif 'home' in dict_sig:
            self.update_view()

        else:
            logger.error("Unknown content of dict_sig: {0}".format(dict_sig))

#------------------------------------------------------------------------------

    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.chk_auto_N = QCheckBox(self)
        self.chk_auto_N.setChecked(False)
        self.chk_auto_N.setToolTip(
            "Use number of points from calling routine.")

        self.lbl_auto_N = QLabel("Auto " + to_html("N", frmt='i'))

        self.led_N = QLineEdit(self)
        self.led_N.setText(str(self.N))
        self.led_N.setMaximumWidth(70)
        self.led_N.setToolTip("<span>Number of window data points.</span>")

        self.chk_log_t = QCheckBox("Log", self)
        self.chk_log_t.setChecked(False)
        self.chk_log_t.setToolTip("Display in dB")

        self.led_log_bottom_t = QLineEdit(self)
        self.led_log_bottom_t.setText(str(self.bottom_t))
        self.led_log_bottom_t.setMaximumWidth(50)
        self.led_log_bottom_t.setEnabled(self.chk_log_t.isChecked())
        self.led_log_bottom_t.setToolTip(
            "<span>Minimum display value for log. scale.</span>")

        self.lbl_log_bottom_t = QLabel("dB", self)
        self.lbl_log_bottom_t.setEnabled(self.chk_log_t.isChecked())

        self.chk_norm_f = QCheckBox("Norm", self)
        self.chk_norm_f.setChecked(True)
        self.chk_norm_f.setToolTip(
            "Normalize window spectrum for a maximum of 1.")

        self.chk_half_f = QCheckBox("Half", self)
        self.chk_half_f.setChecked(True)
        self.chk_half_f.setToolTip(
            "Display window spectrum in the range 0 ... 0.5 f_S.")

        self.chk_log_f = QCheckBox("Log", self)
        self.chk_log_f.setChecked(True)
        self.chk_log_f.setToolTip("Display in dB")

        self.led_log_bottom_f = QLineEdit(self)
        self.led_log_bottom_f.setText(str(self.bottom_f))
        self.led_log_bottom_f.setMaximumWidth(50)
        self.led_log_bottom_f.setEnabled(self.chk_log_f.isChecked())
        self.led_log_bottom_f.setToolTip(
            "<span>Minimum display value for log. scale.</span>")

        self.lbl_log_bottom_f = QLabel("dB", self)
        self.lbl_log_bottom_f.setEnabled(self.chk_log_f.isChecked())

        layHControls = QHBoxLayout()
        layHControls.addWidget(self.chk_auto_N)
        layHControls.addWidget(self.lbl_auto_N)
        layHControls.addWidget(self.led_N)
        layHControls.addStretch(1)
        layHControls.addWidget(self.chk_log_t)
        layHControls.addWidget(self.led_log_bottom_t)
        layHControls.addWidget(self.lbl_log_bottom_t)
        layHControls.addStretch(10)
        layHControls.addWidget(self.chk_norm_f)
        layHControls.addStretch(1)
        layHControls.addWidget(self.chk_half_f)
        layHControls.addStretch(1)
        layHControls.addWidget(self.chk_log_f)
        layHControls.addWidget(self.led_log_bottom_f)
        layHControls.addWidget(self.lbl_log_bottom_f)

        self.tblWinProperties = QTableWidget(self.tbl_rows, self.tbl_cols,
                                             self)
        self.tblWinProperties.setAlternatingRowColors(True)
        self.tblWinProperties.verticalHeader().setVisible(False)
        self.tblWinProperties.horizontalHeader().setVisible(False)
        self._init_table(self.tbl_rows, self.tbl_cols, " ")

        self.txtInfoBox = QTextBrowser(self)

        #----------------------------------------------------------------------
        #               ### frmControls ###
        #
        # This widget encompasses all control subwidgets
        #----------------------------------------------------------------------
        self.frmControls = QFrame(self)
        self.frmControls.setObjectName("frmControls")
        self.frmControls.setLayout(layHControls)

        #----------------------------------------------------------------------
        #               ### mplwidget ###
        #
        # main widget: Layout layVMainMpl (VBox) is defined with MplWidget,
        #              additional widgets can be added (like self.frmControls)
        #              The widget encompasses all other widgets.
        #----------------------------------------------------------------------
        self.mplwidget = MplWidget(self)
        self.mplwidget.layVMainMpl.addWidget(self.frmControls)
        self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins'])

        #----------------------------------------------------------------------
        #               ### frmInfo ###
        #
        # This widget encompasses the text info box and the table with window
        # parameters.
        #----------------------------------------------------------------------
        layVInfo = QVBoxLayout(self)
        layVInfo.addWidget(self.tblWinProperties)
        layVInfo.addWidget(self.txtInfoBox)

        self.frmInfo = QFrame(self)
        self.frmInfo.setObjectName("frmInfo")
        self.frmInfo.setLayout(layVInfo)

        #----------------------------------------------------------------------
        #               ### splitter ###
        #
        # This widget encompasses all control subwidgets
        #----------------------------------------------------------------------

        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(self.mplwidget)
        splitter.addWidget(self.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, 1000])

        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.draw()  # initial drawing

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.chk_log_f.clicked.connect(self.update_view)
        self.chk_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.chk_auto_N.clicked.connect(self.calc_N)
        self.led_N.editingFinished.connect(self.draw)

        self.chk_norm_f.clicked.connect(self.draw)
        self.chk_half_f.clicked.connect(self.update_view)

        self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx)
        self.tblWinProperties.itemClicked.connect(self._handle_item_clicked)
#------------------------------------------------------------------------------

    def _init_table(self, rows, cols, val):
        for r in range(rows):
            for c in range(cols):
                item = QTableWidgetItem(val)
                if c % 3 == 0:
                    item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
                    if self.tbl_sel[r * 2 + c % 3]:
                        item.setCheckState(Qt.Checked)
                    else:
                        item.setCheckState(Qt.Unchecked)
                self.tblWinProperties.setItem(r, c, item)
#   https://stackoverflow.com/questions/12366521/pyqt-checkbox-in-qtablewidget

#------------------------------------------------------------------------------

    def _set_table_item(self, row, col, val, font=None, sel=None):
        """
        Set the table item with the index `row, col` and the value val
        """
        item = self.tblWinProperties.item(row, col)
        item.setText(str(val))

        if font:
            self.tblWinProperties.item(row, col).setFont(font)

        if sel == True:
            item.setCheckState(Qt.Checked)
        if sel == False:
            item.setCheckState(Qt.Unchecked)
        # when sel is not specified, don't change anything

#------------------------------------------------------------------------------

    def _handle_item_clicked(self, item):
        if item.column() % 3 == 0:  # clicked on checkbox
            num = item.row() * 2 + item.column() // 3
            if item.checkState() == Qt.Checked:
                self.tbl_sel[num] = True
                logger.debug('"{0}:{1}" Checked'.format(item.text(), num))
            else:
                self.tbl_sel[num] = False
                logger.debug('"{0}:{1}" Unchecked'.format(item.text(), num))

        elif item.column() % 3 == 1:  # clicked on value field
            logger.info("{0:s} copied to clipboard.".format(item.text()))
            fb.clipboard.setText(item.text())

        self.update_view()

#------------------------------------------------------------------------------

    def update_bottom(self):
        """
        Update log bottom settings
        """
        self.bottom_t = safe_eval(self.led_log_bottom_t.text(),
                                  self.bottom_t,
                                  sign='neg',
                                  return_type='float')
        self.led_log_bottom_t.setText(str(self.bottom_t))

        self.bottom_f = safe_eval(self.led_log_bottom_f.text(),
                                  self.bottom_f,
                                  sign='neg',
                                  return_type='float')
        self.led_log_bottom_f.setText(str(self.bottom_f))

        self.update_view()

#------------------------------------------------------------------------------

    def calc_N(self):
        """
        (Re-)Calculate the number of data points when Auto N chkbox has been
        clicked or when the number of data points has been updated outside this
        class
        """
        if self.chk_auto_N.isChecked():
            self.N = self.N_auto

        self.draw()

#------------------------------------------------------------------------------

    def calc_win(self):
        """
        (Re-)Calculate the window and its FFT
        """
        self.led_N.setEnabled(not self.chk_auto_N.isChecked())

        if not self.chk_auto_N.isChecked():
            self.N = safe_eval(self.led_N.text(),
                               self.N,
                               sign='pos',
                               return_type='int')

    # else:
    #self.N = self.win_dict['win_len']

        self.led_N.setText(str(self.N))

        self.n = np.arange(self.N)

        self.win = calc_window_function(self.win_dict,
                                        self.win_dict['name'],
                                        self.N,
                                        sym=self.sym)

        self.nenbw = self.N * np.sum(np.square(self.win)) / (np.square(
            np.sum(self.win)))
        self.cgain = np.sum(self.win) / self.N

        self.F = fftfreq(self.N * self.pad,
                         d=1. / fb.fil[0]['f_S'])  # use zero padding
        self.Win = np.abs(fft(self.win, self.N * self.pad))
        if self.chk_norm_f.isChecked():
            self.Win /= (self.N * self.cgain
                         )  # correct gain for periodic signals (coherent gain)

        first_zero = argrelextrema(self.Win[:(self.N * self.pad) // 2],
                                   np.less)
        if np.shape(first_zero)[1] > 0:
            first_zero = first_zero[0][0]
            self.first_zero_f = self.F[first_zero]
            self.sidelobe_level = np.max(
                self.Win[first_zero:(self.N * self.pad) // 2])
        else:
            self.first_zero_f = np.nan
            self.sidelobe_level = 0

#------------------------------------------------------------------------------

    def draw(self):
        """
        Main entry point:
        Re-calculate \|H(f)\| and draw the figure
        """
        self.calc_win()
        self.update_view()

#------------------------------------------------------------------------------

    def update_view(self):
        """
        Draw the figure with new limits, scale etc without recalculating the
        window.
        """
        # suppress "divide by zero in log10" warnings
        old_settings_seterr = np.seterr()
        np.seterr(divide='ignore')

        self.ax_t.cla()
        self.ax_f.cla()

        self.ax_t.set_xlabel(fb.fil[0]['plt_tLabel'])
        self.ax_t.set_ylabel(r'$w[n] \; \rightarrow$')

        self.ax_f.set_xlabel(fb.fil[0]['plt_fLabel'])
        self.ax_f.set_ylabel(r'$W(f) \; \rightarrow$')

        if self.chk_log_t.isChecked():
            self.ax_t.plot(
                self.n,
                np.maximum(20 * np.log10(np.abs(self.win)), self.bottom_t))
        else:
            self.ax_t.plot(self.n, self.win)

        if self.chk_half_f.isChecked():
            F = self.F[:len(self.F * self.pad) // 2]
            Win = self.Win[:len(self.F * self.pad) // 2]
        else:
            F = fftshift(self.F)
            Win = fftshift(self.Win)

        if self.chk_log_f.isChecked():
            self.ax_f.plot(
                F, np.maximum(20 * np.log10(np.abs(Win)), self.bottom_f))
            self.nenbw_disp = 10 * np.log10(self.nenbw)
            self.cgain_disp = 20 * np.log10(self.cgain)
            self.sidelobe_level_disp = 20 * np.log10(self.sidelobe_level)
            self.unit_nenbw = "dB"
            self.unit_scale = "dB"
        else:
            self.ax_f.plot(F, Win)
            self.nenbw_disp = self.nenbw
            self.cgain_disp = self.cgain
            self.sidelobe_level_disp = self.sidelobe_level
            self.unit_nenbw = "bins"
            self.unit_scale = ""

        self.led_log_bottom_t.setEnabled(self.chk_log_t.isChecked())
        self.lbl_log_bottom_t.setEnabled(self.chk_log_t.isChecked())
        self.led_log_bottom_f.setEnabled(self.chk_log_f.isChecked())
        self.lbl_log_bottom_f.setEnabled(self.chk_log_f.isChecked())

        window_name = self.win_dict['name']
        param_txt = ""
        if self.win_dict['n_par'] > 0:
            param_txt = " (" + self.win_dict['par'][0][
                'name_tex'] + " = {0:.3g})".format(
                    self.win_dict['par'][0]['val'])
        if self.win_dict['n_par'] > 1:
            param_txt = param_txt[:-1]\
                + ", {0:s} = {1:.3g})".format(self.win_dict['par'][1]['name_tex'], self.win_dict['par'][1]['val'])

        self.mplwidget.fig.suptitle(r'{0} Window'.format(window_name) +
                                    param_txt)

        # plot a line at the max. sidelobe level
        if self.tbl_sel[3]:
            self.ax_f.axhline(self.sidelobe_level_disp, ls='dotted', c='b')

        patch = mpl_patches.Rectangle((0, 0),
                                      1,
                                      1,
                                      fc="white",
                                      ec="white",
                                      lw=0,
                                      alpha=0)
        # Info legend for time domain window
        labels_t = []
        labels_t.append("$N$ = {0:d}".format(self.N))
        self.ax_t.legend([patch],
                         labels_t,
                         loc='best',
                         fontsize='small',
                         fancybox=True,
                         framealpha=0.7,
                         handlelength=0,
                         handletextpad=0)

        # Info legend for frequency domain window
        labels_f = []
        N_patches = 0
        if self.tbl_sel[0]:
            labels_f.append("$NENBW$ = {0:.4g} {1}".format(
                self.nenbw_disp, self.unit_nenbw))
            N_patches += 1
        if self.tbl_sel[1]:
            labels_f.append("$CGAIN$ = {0:.4g} {1}".format(
                self.cgain_disp, self.unit_scale))
            N_patches += 1
        if self.tbl_sel[2]:
            labels_f.append("1st Zero = {0:.4g}".format(self.first_zero_f))
            N_patches += 1
        if N_patches > 0:
            self.ax_f.legend([patch] * N_patches,
                             labels_f,
                             loc='best',
                             fontsize='small',
                             fancybox=True,
                             framealpha=0.7,
                             handlelength=0,
                             handletextpad=0)
        np.seterr(**old_settings_seterr)

        self.update_info()
        self.redraw()

#------------------------------------------------------------------------------

    def update_info(self):
        """
        Update the text info box for the window
        """
        if 'info' in self.win_dict:
            self.txtInfoBox.setText(self.win_dict['info'])

        self._set_table_item(0, 0, "ENBW", font=self.bfont)  #, sel=True)
        self._set_table_item(0, 1, "{0:.5g}".format(self.nenbw_disp))
        self._set_table_item(0, 2, self.unit_nenbw)
        self._set_table_item(0, 3, "Scale", font=self.bfont)  #, sel=True)
        self._set_table_item(0, 4, "{0:.5g}".format(self.cgain_disp))
        self._set_table_item(0, 5, self.unit_scale)

        self._set_table_item(1, 0, "1st Zero", font=self.bfont)  #, sel=True)
        self._set_table_item(1, 1, "{0:.5g}".format(self.first_zero_f))
        self._set_table_item(1, 2, "f_S")

        self._set_table_item(1, 3, "Sidelobes", font=self.bfont)  #, sel=True)
        self._set_table_item(1, 4, "{0:.5g}".format(self.sidelobe_level_disp))
        self._set_table_item(1, 5, self.unit_scale)

        self.tblWinProperties.resizeColumnsToContents()
        self.tblWinProperties.resizeRowsToContents()
#------------------------------------------------------------------------------

    def redraw(self):
        """
        Redraw the canvas when e.g. the canvas size has changed
        """
        self.mplwidget.redraw()
        self.needs_redraw = False
Example #6
0
    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.chk_auto_N = QCheckBox(self)
        self.chk_auto_N.setChecked(False)
        self.chk_auto_N.setToolTip(
            "Use number of points from calling routine.")

        self.lbl_auto_N = QLabel("Auto " + to_html("N", frmt='i'))

        self.led_N = QLineEdit(self)
        self.led_N.setText(str(self.N))
        self.led_N.setMaximumWidth(70)
        self.led_N.setToolTip("<span>Number of window data points.</span>")

        self.chk_log_t = QCheckBox("Log", self)
        self.chk_log_t.setChecked(False)
        self.chk_log_t.setToolTip("Display in dB")

        self.led_log_bottom_t = QLineEdit(self)
        self.led_log_bottom_t.setText(str(self.bottom_t))
        self.led_log_bottom_t.setMaximumWidth(50)
        self.led_log_bottom_t.setEnabled(self.chk_log_t.isChecked())
        self.led_log_bottom_t.setToolTip(
            "<span>Minimum display value for log. scale.</span>")

        self.lbl_log_bottom_t = QLabel("dB", self)
        self.lbl_log_bottom_t.setEnabled(self.chk_log_t.isChecked())

        self.chk_norm_f = QCheckBox("Norm", self)
        self.chk_norm_f.setChecked(True)
        self.chk_norm_f.setToolTip(
            "Normalize window spectrum for a maximum of 1.")

        self.chk_half_f = QCheckBox("Half", self)
        self.chk_half_f.setChecked(True)
        self.chk_half_f.setToolTip(
            "Display window spectrum in the range 0 ... 0.5 f_S.")

        self.chk_log_f = QCheckBox("Log", self)
        self.chk_log_f.setChecked(True)
        self.chk_log_f.setToolTip("Display in dB")

        self.led_log_bottom_f = QLineEdit(self)
        self.led_log_bottom_f.setText(str(self.bottom_f))
        self.led_log_bottom_f.setMaximumWidth(50)
        self.led_log_bottom_f.setEnabled(self.chk_log_f.isChecked())
        self.led_log_bottom_f.setToolTip(
            "<span>Minimum display value for log. scale.</span>")

        self.lbl_log_bottom_f = QLabel("dB", self)
        self.lbl_log_bottom_f.setEnabled(self.chk_log_f.isChecked())

        layHControls = QHBoxLayout()
        layHControls.addWidget(self.chk_auto_N)
        layHControls.addWidget(self.lbl_auto_N)
        layHControls.addWidget(self.led_N)
        layHControls.addStretch(1)
        layHControls.addWidget(self.chk_log_t)
        layHControls.addWidget(self.led_log_bottom_t)
        layHControls.addWidget(self.lbl_log_bottom_t)
        layHControls.addStretch(10)
        layHControls.addWidget(self.chk_norm_f)
        layHControls.addStretch(1)
        layHControls.addWidget(self.chk_half_f)
        layHControls.addStretch(1)
        layHControls.addWidget(self.chk_log_f)
        layHControls.addWidget(self.led_log_bottom_f)
        layHControls.addWidget(self.lbl_log_bottom_f)

        self.tblWinProperties = QTableWidget(self.tbl_rows, self.tbl_cols,
                                             self)
        self.tblWinProperties.setAlternatingRowColors(True)
        self.tblWinProperties.verticalHeader().setVisible(False)
        self.tblWinProperties.horizontalHeader().setVisible(False)
        self._init_table(self.tbl_rows, self.tbl_cols, " ")

        self.txtInfoBox = QTextBrowser(self)

        #----------------------------------------------------------------------
        #               ### frmControls ###
        #
        # This widget encompasses all control subwidgets
        #----------------------------------------------------------------------
        self.frmControls = QFrame(self)
        self.frmControls.setObjectName("frmControls")
        self.frmControls.setLayout(layHControls)

        #----------------------------------------------------------------------
        #               ### mplwidget ###
        #
        # main widget: Layout layVMainMpl (VBox) is defined with MplWidget,
        #              additional widgets can be added (like self.frmControls)
        #              The widget encompasses all other widgets.
        #----------------------------------------------------------------------
        self.mplwidget = MplWidget(self)
        self.mplwidget.layVMainMpl.addWidget(self.frmControls)
        self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins'])

        #----------------------------------------------------------------------
        #               ### frmInfo ###
        #
        # This widget encompasses the text info box and the table with window
        # parameters.
        #----------------------------------------------------------------------
        layVInfo = QVBoxLayout(self)
        layVInfo.addWidget(self.tblWinProperties)
        layVInfo.addWidget(self.txtInfoBox)

        self.frmInfo = QFrame(self)
        self.frmInfo.setObjectName("frmInfo")
        self.frmInfo.setLayout(layVInfo)

        #----------------------------------------------------------------------
        #               ### splitter ###
        #
        # This widget encompasses all control subwidgets
        #----------------------------------------------------------------------

        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(self.mplwidget)
        splitter.addWidget(self.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, 1000])

        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.draw()  # initial drawing

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.chk_log_f.clicked.connect(self.update_view)
        self.chk_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.chk_auto_N.clicked.connect(self.calc_N)
        self.led_N.editingFinished.connect(self.draw)

        self.chk_norm_f.clicked.connect(self.draw)
        self.chk_half_f.clicked.connect(self.update_view)

        self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx)
        self.tblWinProperties.itemClicked.connect(self._handle_item_clicked)
Example #7
0
class Plot_FFT_win(QDialog):
    """
    Create a pop-up widget for displaying time and frequency view of an FFT
    window.

    Data is passed via the dictionary `win_dict` that is specified during
    construction. Available windows, parameters, tooltipps etc are provided
    by the widget `pyfda_fft_windows_lib.QFFTWinSelection`

    Parameters
    ----------

    parent : class instance
        reference to parent

    win_dict : dict
        dictionary derived from `pyfda_fft_windows_lib.all_windows_dict`
        with valid and available windows and their current settings (if applicable)

    sym : bool
        Passed to `get_window()`:
        When True, generate a symmetric window for use in filter design.
        When False (default), generate a periodic window for use in spectral analysis.

    title : str
        Title text for Qt Window

    ignore_close_event : bool
        Disable close event when True (Default)

    Methods
    -------

    - `self.calc_N()`
    - `self.update_view()`:
    - `self.draw()`: calculate window and FFT and draw both
    - `get_win(N)` : Get the window array
    """
    sig_rx = pyqtSignal(object)  # incoming
    sig_tx = pyqtSignal(object)  # outgoing
    from pyfda.libs.pyfda_qt_lib import emit

    def __init__(self,
                 parent,
                 win_dict,
                 sym=False,
                 title='pyFDA Window Viewer',
                 ignore_close_event=True):
        super(Plot_FFT_win, self).__init__(parent)
        # make window stay on top
        qwindow_stay_on_top(self, True)

        self.win_dict = win_dict
        self.sym = sym
        self.ignore_close_event = ignore_close_event
        self.setWindowTitle(title)

        self.needs_calc = True

        self.bottom_f = -80  # min. value for dB display
        self.bottom_t = -60
        # initial number of data points for visualization
        self.N_view = 32

        self.pad = 16  # zero padding factor for smooth FFT plot

        # initial settings for checkboxes
        self.tbl_sel = [True, True, False, False]
        #    False, False, False, False]
        self.tbl_cols = 6
        self.tbl_rows = len(self.tbl_sel) // (self.tbl_cols // 3)

        self._construct_UI()
        self.calc_win_draw()

# ------------------------------------------------------------------------------

    def closeEvent(self, event):
        """
        Catch `closeEvent` (user has tried to close the FFT window) and send a
        signal to parent to decide how to proceed.

        This can be disabled by setting `self.ignore_close_event = False` e.g.
        for instantiating the widget as a standalone window.
        """
        if self.ignore_close_event:
            event.ignore()
            self.emit({'closeEvent': ''})

# ------------------------------------------------------------------------------

    def process_sig_rx(self, dict_sig=None):
        """
        Process signals coming from the navigation toolbar and from sig_rx:

        - `self.calc_N`
        - `self.update_view`:
        - `self.draw`: calculate window and FFT and draw both
        """
        # logger.debug("PROCESS_SIG_RX - vis={0}, needs_calc={1}\n{2}"
        #              .format(self.isVisible(), self.needs_calc, pprint_log(dict_sig)))

        if dict_sig['id'] == id(self):
            logger.warning("Stopped infinite loop:\n{0}".format(
                pprint_log(dict_sig)))
            return

        elif not self.isVisible():
            self.needs_calc = True

        elif 'view_changed' in dict_sig and 'fft_win' in dict_sig['view_changed']\
                or self.needs_calc:

            self.calc_win_draw()
            self.needs_calc = False

        elif 'home' in dict_sig:
            self.update_view()

        else:
            logger.error("Unknown content of dict_sig: {0}".format(dict_sig))

# ------------------------------------------------------------------------------

    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)

# ------------------------------------------------------------------------------

    def _construct_table(self, rows, cols, val):
        """
        Create a table with `rows` and `cols`, organized in sets of 3:
        Name (with a checkbox) - value - unit
        each item.

        Parameters
        ----------

        rows : int
            number of rows

        cols : int
            number of columns (must be multiple of 3)

        val : str
            initialization value for the table

        Returns
        -------
        None
        """
        for r in range(rows):
            for c in range(cols):
                item = QTableWidgetItem(val)
                if c % 3 == 0:
                    item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
                    if self.tbl_sel[r * 2 + c % 3]:
                        item.setCheckState(Qt.Checked)
                    else:
                        item.setCheckState(Qt.Unchecked)
                self.tbl_win_props.setItem(r, c, item)

    # https://stackoverflow.com/questions/12366521/pyqt-checkbox-in-qtablewidget

# ------------------------------------------------------------------------------

    def update_fft_win(self, dict_sig=None):
        """
        Update FFT window when window or parameters have changed and
        pass thru 'view_changed':'fft_win_type' or 'fft_win_par'
        """
        self.calc_win_draw()
        self.emit(dict_sig)

# ------------------------------------------------------------------------------

    def calc_win_draw(self):
        """
        (Re-)Calculate the window, its FFT and some characteristic values and update
        the plot of the window and its FFT. This should be triggered when the
        window type or length or a parameters has been changed.

        Returns
        -------
        None

        Attributes
        ----------

        """
        self.N_view = safe_eval(self.led_N.text(),
                                self.N_view,
                                sign='pos',
                                return_type='int')
        self.led_N.setText(str(self.N_view))
        self.n = np.arange(self.N_view)
        self.win_view = self.qfft_win_select.get_window(self.N_view,
                                                        sym=self.sym)

        if self.qfft_win_select.err:
            self.qfft_win_select.dict2ui()

        self.nenbw = self.N_view * np.sum(np.square(self.win_view))\
            / np.square(np.sum(self.win_view))
        self.cgain = np.sum(self.win_view) / self.N_view  # coherent gain

        # calculate the FFT of the window with a zero padding factor
        # of `self.pad`
        self.F = fftfreq(self.N_view * self.pad, d=1. / fb.fil[0]['f_S'])
        self.Win = np.abs(fft(self.win_view, self.N_view * self.pad))

        # Correct gain for periodic signals (coherent gain)
        if self.but_norm_f.isChecked():
            self.Win /= (self.N_view * self.cgain)

        # calculate frequency of first zero and maximum sidelobe level
        first_zero = argrelextrema(self.Win[:(self.N_view * self.pad) // 2],
                                   np.less)
        if np.shape(first_zero)[1] > 0:
            first_zero = first_zero[0][0]
            self.first_zero_f = self.F[first_zero]
            self.sidelobe_level = np.max(
                self.Win[first_zero:(self.N_view * self.pad) // 2])
        else:
            self.first_zero_f = np.nan
            self.sidelobe_level = 0

        self.update_view()

# ------------------------------------------------------------------------------

    def _set_table_item(self, row, col, val, font=None, sel=None):
        """
        Set the table item with the index `row, col` and the value val
        """
        item = self.tbl_win_props.item(row, col)
        item.setText(str(val))

        if font:
            self.tbl_win_props.item(row, col).setFont(font)

        if sel is True:
            item.setCheckState(Qt.Checked)
        if sel is False:
            item.setCheckState(Qt.Unchecked)
        # when sel is not specified, don't change anything

# ------------------------------------------------------------------------------

    def _handle_item_clicked(self, item):
        if item.column() % 3 == 0:  # clicked on checkbox
            num = item.row() * 2 + item.column() // 3
            if item.checkState() == Qt.Checked:
                self.tbl_sel[num] = True
                logger.debug('"{0}:{1}" Checked'.format(item.text(), num))
            else:
                self.tbl_sel[num] = False
                logger.debug('"{0}:{1}" Unchecked'.format(item.text(), num))

        elif item.column() % 3 == 1:  # clicked on value field
            logger.info("{0:s} copied to clipboard.".format(item.text()))
            fb.clipboard.setText(item.text())

        self.update_view()

# ------------------------------------------------------------------------------

    def update_bottom(self):
        """
        Update log bottom settings
        """
        self.bottom_t = safe_eval(self.led_log_bottom_t.text(),
                                  self.bottom_t,
                                  sign='neg',
                                  return_type='float')
        self.led_log_bottom_t.setText(str(self.bottom_t))

        self.bottom_f = safe_eval(self.led_log_bottom_f.text(),
                                  self.bottom_f,
                                  sign='neg',
                                  return_type='float')
        self.led_log_bottom_f.setText(str(self.bottom_f))

        self.update_view()

# ------------------------------------------------------------------------------

    def update_view(self):
        """
        Draw the figure with new limits, scale, lin/log  etc without
        recalculating the window or its FFT.
        """
        # suppress "divide by zero in log10" warnings
        old_settings_seterr = np.seterr()
        np.seterr(divide='ignore')

        self.ax_t.cla()
        self.ax_f.cla()

        self.ax_t.set_xlabel(fb.fil[0]['plt_tLabel'])
        self.ax_t.set_ylabel(r'$w[n] \; \rightarrow$')

        self.ax_f.set_xlabel(fb.fil[0]['plt_fLabel'])
        self.ax_f.set_ylabel(r'$W(f) \; \rightarrow$')

        if self.but_log_t.isChecked():
            self.ax_t.plot(
                self.n,
                np.maximum(20 * np.log10(np.abs(self.win_view)),
                           self.bottom_t))
        else:
            self.ax_t.plot(self.n, self.win_view)

        if self.but_half_f.isChecked():
            F = self.F[:len(self.F * self.pad) // 2]
            Win = self.Win[:len(self.F * self.pad) // 2]
        else:
            F = fftshift(self.F)
            Win = fftshift(self.Win)

        if self.but_log_f.isChecked():
            self.ax_f.plot(
                F, np.maximum(20 * np.log10(np.abs(Win)), self.bottom_f))
            self.nenbw_disp = 10 * np.log10(self.nenbw)
            self.cgain_disp = 20 * np.log10(self.cgain)
            self.sidelobe_level_disp = 20 * np.log10(self.sidelobe_level)
            self.nenbw_unit = "dB"
            self.cgain_unit = "dB"
        else:
            self.ax_f.plot(F, Win)
            self.nenbw_disp = self.nenbw
            self.cgain_disp = self.cgain
            self.sidelobe_level_disp = self.sidelobe_level
            self.nenbw_unit = "bins"
            self.cgain_unit = ""

        self.led_log_bottom_t.setVisible(self.but_log_t.isChecked())
        self.lbl_log_bottom_t.setVisible(self.but_log_t.isChecked())
        self.led_log_bottom_f.setVisible(self.but_log_f.isChecked())
        self.lbl_log_bottom_f.setVisible(self.but_log_f.isChecked())

        cur = self.win_dict['cur_win_name']
        cur_win_d = self.win_dict[cur]
        param_txt = ""
        if cur_win_d['n_par'] > 0:
            if type(cur_win_d['par'][0]['val']) in {str}:
                p1 = cur_win_d['par'][0]['val']
            else:
                p1 = "{0:.3g}".format(cur_win_d['par'][0]['val'])
            param_txt = " ({0:s} = {1:s})".format(
                cur_win_d['par'][0]['name_tex'], p1)

        if self.win_dict[cur]['n_par'] > 1:
            if type(cur_win_d['par'][1]['val']) in {str}:
                p2 = cur_win_d['par'][1]['val']
            else:
                p2 = "{0:.3g}".format(cur_win_d['par'][1]['val'])
            param_txt = param_txt[:-1]\
                + ", {0:s} = {1:s})".format(cur_win_d['par'][1]['name_tex'], p2)

        self.mplwidget.fig.suptitle(r'{0} Window'.format(cur) + param_txt)

        # plot a line at the max. sidelobe level
        if self.tbl_sel[3]:
            self.ax_f.axhline(self.sidelobe_level_disp, ls='dotted', c='b')

        patch = mpl_patches.Rectangle((0, 0),
                                      1,
                                      1,
                                      fc="white",
                                      ec="white",
                                      lw=0,
                                      alpha=0)
        # Info legend for time domain window
        labels_t = []
        labels_t.append("$N$ = {0:d}".format(self.N_view))
        self.ax_t.legend([patch],
                         labels_t,
                         loc='best',
                         fontsize='small',
                         fancybox=True,
                         framealpha=0.7,
                         handlelength=0,
                         handletextpad=0)

        # Info legend for frequency domain window
        labels_f = []
        N_patches = 0
        if self.tbl_sel[0]:
            labels_f.append("$NENBW$ = {0:.4g} {1}".format(
                self.nenbw_disp, self.nenbw_unit))
            N_patches += 1
        if self.tbl_sel[1]:
            labels_f.append("$CGAIN$ = {0:.4g} {1}".format(
                self.cgain_disp, self.cgain_unit))
            N_patches += 1
        if self.tbl_sel[2]:
            labels_f.append("1st Zero = {0:.4g}".format(self.first_zero_f))
            N_patches += 1

        if N_patches > 0:
            self.ax_f.legend([patch] * N_patches,
                             labels_f,
                             loc='best',
                             fontsize='small',
                             fancybox=True,
                             framealpha=0.7,
                             handlelength=0,
                             handletextpad=0)
        np.seterr(**old_settings_seterr)

        self.update_info()
        self.redraw()

# ------------------------------------------------------------------------------

    def update_info(self):
        """
        Update the text info box for the window
        """
        cur = self.win_dict['cur_win_name']
        if 'info' in self.win_dict[cur]:
            self.txtInfoBox.setText(self.win_dict[cur]['info'])
        else:
            self.txtInfoBox.clear()

        self._set_table_item(0, 0, "NENBW", font=self.bfont)  # , sel=True)
        self._set_table_item(0, 1, "{0:.5g}".format(self.nenbw_disp))
        self._set_table_item(0, 2, self.nenbw_unit)
        self._set_table_item(0, 3, "Scale", font=self.bfont)  # , sel=True)
        self._set_table_item(0, 4, "{0:.5g}".format(self.cgain_disp))
        self._set_table_item(0, 5, self.cgain_unit)

        self._set_table_item(1, 0, "1st Zero", font=self.bfont)  # , sel=True)
        self._set_table_item(1, 1, "{0:.5g}".format(self.first_zero_f))
        self._set_table_item(1, 2, "f_S")

        self._set_table_item(1, 3, "Sidelobes", font=self.bfont)  # , sel=True)
        self._set_table_item(1, 4, "{0:.5g}".format(self.sidelobe_level_disp))
        self._set_table_item(1, 5, self.cgain_unit)

        self.tbl_win_props.resizeColumnsToContents()
        self.tbl_win_props.resizeRowsToContents()

# -----------------------------------------------------------------------------

    def redraw(self):
        """
        Redraw the canvas when e.g. the canvas size has changed
        """
        self.mplwidget.redraw()
Example #8
0
    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)
Example #9
0
    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.chkFiltPerf = QCheckBox("H(f)", self)
        self.chkFiltPerf.setChecked(True)
        self.chkFiltPerf.setToolTip(
            "Display frequency response at test frequencies.")

        self.chkDocstring = QCheckBox("Doc$", self)
        self.chkDocstring.setChecked(False)
        self.chkDocstring.setToolTip(
            "Display docstring from python filter method.")

        self.chkRichText = QCheckBox("RTF", self)
        self.chkRichText.setChecked(HAS_DOCUTILS)
        self.chkRichText.setEnabled(HAS_DOCUTILS)
        self.chkRichText.setToolTip(
            "Render documentation in Rich Text Format.")

        self.chkFiltDict = QCheckBox("FiltDict", self)
        self.chkFiltDict.setToolTip("Show filter dictionary for debugging.")

        self.chkFiltTree = QCheckBox("FiltTree", self)
        self.chkFiltTree.setToolTip("Show filter tree for debugging.")

        self.layHChkBoxes = QHBoxLayout()
        self.layHChkBoxes.addWidget(self.chkFiltPerf)
        self.layHChkBoxes.addStretch(1)
        self.layHChkBoxes.addWidget(self.chkDocstring)
        self.layHChkBoxes.addStretch(1)
        self.layHChkBoxes.addWidget(self.chkRichText)
        self.layHChkBoxes.addStretch(1)
        self.layHChkBoxes.addWidget(self.chkFiltDict)
        self.layHChkBoxes.addStretch(1)
        self.layHChkBoxes.addWidget(self.chkFiltTree)
        self.frmMain = QFrame(self)
        self.frmMain.setLayout(self.layHChkBoxes)

        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.layHChkBoxes)
        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.chkFiltPerf.clicked.connect(self._show_filt_perf)
        self.chkFiltDict.clicked.connect(self._show_filt_dict)
        self.chkFiltTree.clicked.connect(self._show_filt_tree)
        self.chkDocstring.clicked.connect(self._show_doc)
        self.chkRichText.clicked.connect(self._show_doc)