Ejemplo n.º 1
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)
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
class Firwin(QWidget):

    FRMT = 'ba'  # output format(s) of filter design routines 'zpk' / 'ba' / 'sos'
    # currently, only 'ba' is supported for firwin routines

    sig_tx = pyqtSignal(
        object)  # local signal between FFT widget and FFTWin_Selector
    sig_tx_local = pyqtSignal(object)
    from pyfda.libs.pyfda_qt_lib import emit

    def __init__(self):
        QWidget.__init__(self)

        self.ft = 'FIR'

        win_names_list = [
            "Boxcar", "Rectangular", "Barthann", "Bartlett", "Blackman",
            "Blackmanharris", "Bohman", "Cosine", "Dolph-Chebyshev", "Flattop",
            "General Gaussian", "Gauss", "Hamming", "Hann", "Kaiser",
            "Nuttall", "Parzen", "Slepian", "Triangular", "Tukey"
        ]
        self.cur_win_name = "Kaiser"  # set initial window type
        self.alg = "ichige"

        # initialize windows dict with the list above for firwin window settings
        self.win_dict = get_windows_dict(win_names_list=win_names_list,
                                         cur_win_name=self.cur_win_name)

        # get initial / last setting from dictionary, updating self.win_dict
        self._load_dict()

        # instantiate FFT window with windows dict
        self.fft_widget = Plot_FFT_win(self,
                                       win_dict=self.win_dict,
                                       sym=True,
                                       title="pyFDA FIR Window Viewer")
        # hide window initially, this is modeless i.e. a non-blocking popup window
        self.fft_widget.hide()

        c = Common()
        self.rt_dict = c.rt_base_iir

        self.rt_dict_add = {
            'COM': {
                'min': {
                    'msg':
                    ('a', "<br /><b>Note:</b> Filter order is only a rough "
                     "approximation and most likely far too low!")
                },
                'man': {
                    'msg':
                    ('a', "Enter desired filter order <b><i>N</i></b> and "
                     "<b>-6 dB</b> pass band corner "
                     "frequency(ies) <b><i>F<sub>C</sub></i></b> .")
                },
            },
            'LP': {
                'man': {},
                'min': {}
            },
            'HP': {
                'man': {
                    'msg': ('a', r"<br /><b>Note:</b> Order needs to be odd!")
                },
                'min': {}
            },
            'BS': {
                'man': {
                    'msg': ('a', r"<br /><b>Note:</b> Order needs to be odd!")
                },
                'min': {}
            },
            'BP': {
                'man': {},
                'min': {}
            },
        }

        self.info = """**Windowed FIR filters**

        are designed by truncating the
        infinite impulse response of an ideal filter with a window function.
        The kind of used window has strong influence on ripple etc. of the
        resulting filter.

        **Design routines:**

        ``scipy.signal.firwin()``

        """
        # self.info_doc = [] is set in self._update_UI()

        # ------------------- end of static info for filter tree ---------------

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

    def process_sig_rx(self, dict_sig=None):
        """
        Process local signals from / for
        - FFT window widget
        - qfft_win_select
        """

        logger.debug("SIG_RX - vis: {0}\n{1}".format(self.isVisible(),
                                                     pprint_log(dict_sig)))

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

        # --- signals coming from the FFT window widget or the qfft_win_select
        if dict_sig['class'] in {'Plot_FFT_win', 'QFFTWinSelector'}:
            if 'closeEvent' in dict_sig:  # hide FFT window windget and return
                self.hide_fft_wdg()
                return
            else:
                if 'view_changed' in dict_sig and 'fft_win' in dict_sig[
                        'view_changed']:
                    # self._update_fft_window()  # TODO: needed?
                    # local connection to FFT window widget and qfft_win_select
                    self.emit(dict_sig, sig_name='sig_tx_local')
                    # global connection to upper hierachies
                    # send notification that filter design has changed
                    self.emit({'filt_changed': 'firwin'})

    # --------------------------------------------------------------------------
    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in
        select_filter.py using the handle to the filter object, fb.filObj .
        """
        # Combobox for selecting the algorithm to estimate minimum filter order
        self.cmb_firwin_alg = QComboBox(self)
        self.cmb_firwin_alg.setObjectName('wdg_cmb_firwin_alg')
        self.cmb_firwin_alg.addItems(['ichige', 'kaiser', 'herrmann'])
        # Minimum size, can be changed in the upper hierarchy levels using layouts:
        self.cmb_firwin_alg.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmb_firwin_alg.hide()

        self.qfft_win_select = QFFTWinSelector(self, self.win_dict)
        # Minimum size, can be changed in the upper hierarchy levels using layouts:
        # self.qfft_win_select.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.but_fft_wdg = QPushButton(self)
        self.but_fft_wdg.setIcon(QIcon(":/fft.svg"))
        but_height = self.qfft_win_select.sizeHint().height()
        self.but_fft_wdg.setIconSize(QSize(but_height, but_height))
        self.but_fft_wdg.setFixedSize(QSize(but_height, but_height))
        self.but_fft_wdg.setToolTip(
            '<span>Show / hide FFT widget (select window type '
            ' and display its properties).</span>')
        self.but_fft_wdg.setCheckable(True)
        self.but_fft_wdg.setChecked(False)

        self.layHWin1 = QHBoxLayout()
        # self.layHWin1.addWidget(self.cmb_firwin_win)
        # self.layHWin1.addWidget(self.but_fft_wdg)
        self.layHWin1.addWidget(self.cmb_firwin_alg)
        self.layHWin2 = QHBoxLayout()
        self.layHWin2.addWidget(self.but_fft_wdg)
        self.layHWin2.addWidget(self.qfft_win_select)

        self.layVWin = QVBoxLayout()
        self.layVWin.addLayout(self.layHWin1)
        self.layVWin.addLayout(self.layHWin2)
        self.layVWin.setContentsMargins(0, 0, 0, 0)

        # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits)
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layVWin)

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        # connect FFT widget to qfft_selector and vice versa and to signals upstream:
        self.fft_widget.sig_tx.connect(self.process_sig_rx)
        self.qfft_win_select.sig_tx.connect(self.process_sig_rx)
        # connect process_sig_rx output to both FFT widgets
        self.sig_tx_local.connect(self.fft_widget.sig_rx)
        self.sig_tx_local.connect(self.qfft_win_select.sig_rx)

        # ----------------------------------------------------------------------
        # SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.cmb_firwin_alg.currentIndexChanged.connect(
            self._update_fft_window)
        self.but_fft_wdg.clicked.connect(self.toggle_fft_wdg)
        # ----------------------------------------------------------------------

# ==============================================================================

    def _update_fft_window(self):
        """ Update window type for FirWin - unneeded at the moment """
        self.alg = str(self.cmb_firwin_alg.currentText())
        self.emit({'filt_changed': 'firwin'})

    # --------------------------------------------------------------------------
    def _load_dict(self):
        """
        Reload window selection and parameters from filter dictionary
        and set UI elements accordingly. load_dict() is called upon
        initialization and when the filter is loaded from disk.
        """
        self.N = fb.fil[0]['N']
        # alg_idx = 0
        if 'wdg_fil' in fb.fil[0] and 'firwin' in fb.fil[0]['wdg_fil']\
                and type(fb.fil[0]['wdg_fil']['firwin']) is dict:
            self.win_dict = fb.fil[0]['wdg_fil']['firwin']

        self.emit({'view_changed': 'fft_win_type'}, sig_name='sig_tx_local')

    # --------------------------------------------------------------------------
    def _store_dict(self):
        """
        Store window and parameter settings using `self.win_dict` in filter dictionary.
        """
        if 'wdg_fil' not in fb.fil[0]:
            fb.fil[0].update({'wdg_fil': {}})
        fb.fil[0]['wdg_fil'].update({'firwin': self.win_dict})

    # --------------------------------------------------------------------------
    def _get_params(self, fil_dict):
        """
        Translate parameters from the passed dictionary to instance
        parameters, scaling / transforming them if needed.
        """
        self.N = fil_dict['N']
        self.F_PB = fil_dict['F_PB']
        self.F_SB = fil_dict['F_SB']
        self.F_PB2 = fil_dict['F_PB2']
        self.F_SB2 = fil_dict['F_SB2']
        self.F_C = fil_dict['F_C']
        self.F_C2 = fil_dict['F_C2']

        # firwin amplitude specs are linear (not in dBs)
        self.A_PB = fil_dict['A_PB']
        self.A_PB2 = fil_dict['A_PB2']
        self.A_SB = fil_dict['A_SB']
        self.A_SB2 = fil_dict['A_SB2']

#        self.alg = 'ichige' # algorithm for determining the minimum order
#        self.alg = self.cmb_firwin_alg.currentText()

    def _test_N(self):
        """
        Warn the user if the calculated order is too high for a reasonable filter
        design.
        """
        if self.N > 1000:
            return qfilter_warning(self, self.N, "FirWin")
        else:
            return True

    def _save(self, fil_dict, arg):
        """
        Convert between poles / zeros / gain, filter coefficients (polynomes)
        and second-order sections and store all available formats in the passed
        dictionary 'fil_dict'.
        """
        fil_save(fil_dict, arg, self.FRMT, __name__)

        try:  # has the order been calculated by a "min" filter design?
            fil_dict['N'] = self.N  # yes, update filterbroker
        except AttributeError:
            pass
        self._store_dict()

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

    def firwin(self,
               numtaps,
               cutoff,
               window=None,
               pass_zero=True,
               scale=True,
               nyq=1.0,
               fs=None):
        """
        FIR filter design using the window method. This is more or less the
        same as `scipy.signal.firwin` with the exception that an ndarray with
        the window values can be passed as an alternative to the window name.

        The parameters "width" (specifying a Kaiser window) and "fs" have been
        omitted, they are not needed here.

        This function computes the coefficients of a finite impulse response
        filter.  The filter will have linear phase; it will be Type I if
        `numtaps` is odd and Type II if `numtaps` is even.
        Type II filters always have zero response at the Nyquist rate, so a
        ValueError exception is raised if firwin is called with `numtaps` even and
        having a passband whose right end is at the Nyquist rate.

        Parameters
        ----------
        numtaps : int
            Length of the filter (number of coefficients, i.e. the filter
            order + 1).  `numtaps` must be even if a passband includes the
            Nyquist frequency.
        cutoff : float or 1D array_like
            Cutoff frequency of filter (expressed in the same units as `nyq`)
            OR an array of cutoff frequencies (that is, band edges). In the
            latter case, the frequencies in `cutoff` should be positive and
            monotonically increasing between 0 and `nyq`.  The values 0 and
            `nyq` must not be included in `cutoff`.
        window : ndarray or string
            string: use the window with the passed name from scipy.signal.windows

            ndarray: The window values - this is an addition to the original
            firwin routine.
        pass_zero : bool, optional
            If True, the gain at the frequency 0 (i.e. the "DC gain") is 1.
            Otherwise the DC gain is 0.
        scale : bool, optional
            Set to True to scale the coefficients so that the frequency
            response is exactly unity at a certain frequency.
            That frequency is either:
            - 0 (DC) if the first passband starts at 0 (i.e. pass_zero
              is True)
            - `nyq` (the Nyquist rate) if the first passband ends at
              `nyq` (i.e the filter is a single band highpass filter);
              center of first passband otherwise
        nyq : float, optional
            Nyquist frequency.  Each frequency in `cutoff` must be between 0
            and `nyq`.

        Returns
        -------
        h : (numtaps,) ndarray
            Coefficients of length `numtaps` FIR filter.
        Raises
        ------
        ValueError
            If any value in `cutoff` is less than or equal to 0 or greater
            than or equal to `nyq`, if the values in `cutoff` are not strictly
            monotonically increasing, or if `numtaps` is even but a passband
            includes the Nyquist frequency.
        See also
        --------
        scipy.firwin
        """
        cutoff = np.atleast_1d(cutoff) / float(nyq)

        # Check for invalid input.
        if cutoff.ndim > 1:
            raise ValueError("The cutoff argument must be at most "
                             "one-dimensional.")
        if cutoff.size == 0:
            raise ValueError("At least one cutoff frequency must be given.")
        if cutoff.min() <= 0 or cutoff.max() >= 1:
            raise ValueError(
                "Invalid cutoff frequency {0}: frequencies must be "
                "greater than 0 and less than nyq.".format(cutoff))
        if np.any(np.diff(cutoff) <= 0):
            raise ValueError("Invalid cutoff frequencies: the frequencies "
                             "must be strictly increasing.")

        pass_nyquist = bool(cutoff.size & 1) ^ pass_zero
        if pass_nyquist and numtaps % 2 == 0:
            raise ValueError(
                "A filter with an even number of coefficients must "
                "have zero response at the Nyquist rate.")

        # Insert 0 and/or 1 at the ends of cutoff so that the length of cutoff
        # is even, and each pair in cutoff corresponds to passband.
        cutoff = np.hstack(([0.0] * pass_zero, cutoff, [1.0] * pass_nyquist))

        # `bands` is a 2D array; each row gives the left and right edges of
        # a passband.
        bands = cutoff.reshape(-1, 2)

        # Build up the coefficients.
        alpha = 0.5 * (numtaps - 1)
        m = np.arange(0, numtaps) - alpha
        h = 0
        for left, right in bands:
            h += right * sinc(right * m)
            h -= left * sinc(left * m)

        if type(window) == str:
            # Get and apply the window function.
            # from scipy.signal.signaltools import get_window
            win = signaltools.get_window(window, numtaps, fftbins=False)
        elif type(window) == np.ndarray:
            win = window
        else:
            logger.error(
                "The 'window' was neither a string nor a numpy array, "
                "it could not be evaluated.")
            return None
        # apply the window function.
        h *= win

        # Now handle scaling if desired.
        if scale:
            # Get the first passband.
            left, right = bands[0]
            if left == 0:
                scale_frequency = 0.0
            elif right == 1:
                scale_frequency = 1.0
            else:
                scale_frequency = 0.5 * (left + right)
            c = np.cos(np.pi * m * scale_frequency)
            s = np.sum(h * c)
            h /= s
        return h

    def _firwin_ord(self, F, W, A, alg):
        # http://www.mikroe.com/chapters/view/72/chapter-2-fir-filters/
        delta_f = abs(F[1] - F[0]) * 2  # referred to f_Ny
        # delta_A = np.sqrt(A[0] * A[1])
        if "Kaiser" in self.win_dict and self.win_dict[
                'cur_win_name'] == "Kaiser":
            N, beta = sig.kaiserord(20 * np.log10(np.abs(fb.fil[0]['A_SB'])),
                                    delta_f)
            # logger.warning(f"N={N}, beta={beta}, A_SB={fb.fil[0]['A_SB']}")
            self.win_dict["Kaiser"]["par"][0]["val"] = beta
            self.qfft_win_select.led_win_par_0.setText(str(beta))
            self.qfft_win_select.ui2dict_params(
            )  # pass changed parameter to other widgets
        else:
            N = remezord(F, W, A, fs=1, alg=alg)[0]
        self.emit({'view_changed': 'fft_win_type'}, sig_name='sig_tx_local')
        return N

    def LPmin(self, fil_dict):
        self._get_params(fil_dict)
        self.N = self._firwin_ord([self.F_PB, self.F_SB], [1, 0],
                                  [self.A_PB, self.A_SB],
                                  alg=self.alg)
        if not self._test_N():
            return -1

        fil_dict['F_C'] = (self.F_SB +
                           self.F_PB) / 2  # average calculated F_PB and F_SB
        self._save(
            fil_dict,
            self.firwin(self.N,
                        fil_dict['F_C'],
                        nyq=0.5,
                        window=self.qfft_win_select.get_window(self.N,
                                                               sym=True)))

    def LPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        logger.warning(self.win_dict["cur_win_name"])
        self._save(
            fil_dict,
            self.firwin(self.N,
                        fil_dict['F_C'],
                        nyq=0.5,
                        window=self.qfft_win_select.get_window(self.N,
                                                               sym=True)))

    def HPmin(self, fil_dict):
        self._get_params(fil_dict)
        N = self._firwin_ord([self.F_SB, self.F_PB], [0, 1],
                             [self.A_SB, self.A_PB],
                             alg=self.alg)
        self.N = round_odd(N)  # enforce odd order
        if not self._test_N():
            return -1
        fil_dict['F_C'] = (self.F_SB +
                           self.F_PB) / 2  # average calculated F_PB and F_SB
        self._save(
            fil_dict,
            self.firwin(self.N,
                        fil_dict['F_C'],
                        pass_zero=False,
                        nyq=0.5,
                        window=self.qfft_win_select.get_window(self.N,
                                                               sym=True)))

    def HPman(self, fil_dict):
        self._get_params(fil_dict)
        self.N = round_odd(self.N)  # enforce odd order
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            self.firwin(self.N,
                        fil_dict['F_C'],
                        pass_zero=False,
                        nyq=0.5,
                        window=self.qfft_win_select.get_window(self.N,
                                                               sym=True)))

    # For BP and BS, F_PB and F_SB have two elements each
    def BPmin(self, fil_dict):
        self._get_params(fil_dict)
        self.N = remezord([self.F_SB, self.F_PB, self.F_PB2, self.F_SB2],
                          [0, 1, 0], [self.A_SB, self.A_PB, self.A_SB2],
                          fs=1,
                          alg=self.alg)[0]
        if not self._test_N():
            return -1

        fil_dict['F_C'] = (self.F_SB +
                           self.F_PB) / 2  # average calculated F_PB and F_SB
        fil_dict['F_C2'] = (self.F_SB2 + self.F_PB2) / 2
        self._save(
            fil_dict,
            self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']],
                        nyq=0.5,
                        pass_zero=False,
                        window=self.qfft_win_select.get_window(self.N,
                                                               sym=True)))

    def BPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']],
                        nyq=0.5,
                        pass_zero=False,
                        window=self.qfft_win_select.get_window(self.N,
                                                               sym=True)))

    def BSmin(self, fil_dict):
        self._get_params(fil_dict)
        N = remezord([self.F_PB, self.F_SB, self.F_SB2, self.F_PB2], [1, 0, 1],
                     [self.A_PB, self.A_SB, self.A_PB2],
                     fs=1,
                     alg=self.alg)[0]
        self.N = round_odd(N)  # enforce odd order
        if not self._test_N():
            return -1
        fil_dict['F_C'] = (self.F_SB +
                           self.F_PB) / 2  # average calculated F_PB and F_SB
        fil_dict['F_C2'] = (self.F_SB2 + self.F_PB2) / 2
        self._save(
            fil_dict,
            self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']],
                        window=self.qfft_win_select.get_window(self.N,
                                                               sym=True),
                        pass_zero=True,
                        nyq=0.5))

    def BSman(self, fil_dict):
        self._get_params(fil_dict)
        self.N = round_odd(self.N)  # enforce odd order
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']],
                        window=self.qfft_win_select.get_window(self.N,
                                                               sym=True),
                        pass_zero=True,
                        nyq=0.5))

    # ------------------------------------------------------------------------------
    def toggle_fft_wdg(self):
        """
        Show / hide FFT widget depending on the state of the corresponding button
        When widget is shown, trigger an update of the window function.
        """
        if self.but_fft_wdg.isChecked():
            self.fft_widget.show()
            self.emit({'view_changed': 'fft_win_type'},
                      sig_name='sig_tx_local')
        else:
            self.fft_widget.hide()

    # --------------------------------------------------------------------------
    def hide_fft_wdg(self):
        """
        The closeEvent caused by clicking the "x" in the FFT widget is caught
        there and routed here to only hide the window
        """
        self.but_fft_wdg.setChecked(False)
        self.fft_widget.hide()
Ejemplo n.º 4
0
    def _construct_UI(self):
        """
        Construct User Interface
        """
        amp_units = ["dB", "V", "W"]

        bfont = QFont()
        bfont.setBold(True)
        lblTitle = QLabel(str(self.title), self)  # field for widget title
        lblTitle.setFont(bfont)
        lblTitle.setWordWrap(True)

        lblUnits = QLabel("in", self)

        self.cmbUnitsA = QComboBox(self)
        self.cmbUnitsA.addItems(amp_units)
        self.cmbUnitsA.setObjectName("cmbUnitsA")
        self.cmbUnitsA.setToolTip(
            "<span>Unit for amplitude specifications:"
            " dB is attenuation (&gt; 0); levels in V and W have to be &lt; 1.</span>"
        )

        # fit size dynamically to largest element:
        self.cmbUnitsA.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        # find index for default unit from dictionary and set the unit
        amp_idx = self.cmbUnitsA.findData(fb.fil[0]['amp_specs_unit'])
        if amp_idx < 0:
            amp_idx = 0
        self.cmbUnitsA.setCurrentIndex(amp_idx)  # initialize for dBs

        layHTitle = QHBoxLayout()  # layout for title and unit
        layHTitle.addWidget(lblTitle)
        layHTitle.addWidget(lblUnits, Qt.AlignLeft)
        layHTitle.addWidget(self.cmbUnitsA, Qt.AlignLeft)
        layHTitle.addStretch(1)

        self.layGSpecs = QGridLayout()  # sublayout for spec fields
        # set the title as the first (fixed) entry in grid layout. The other
        # fields are added and hidden dynamically in _show_entries and _hide_entries()
        self.layGSpecs.addLayout(layHTitle, 0, 0, 1, 2)
        self.layGSpecs.setAlignment(Qt.AlignLeft)

        # This is the top level widget, encompassing the other widgets
        self.frmMain = QFrame(self)
        self.frmMain.setLayout(self.layGSpecs)

        self.layVMain = QVBoxLayout()  # Widget main layout
        self.layVMain.addWidget(self.frmMain)
        self.layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(self.layVMain)

        self.n_cur_labels = 0  # number of currently visible labels / qlineedits

        # - Build a list from all entries in the fil_dict dictionary starting
        #   with "A" (= amplitude specifications of the current filter)
        # - Pass the list to update_UI which recreates the widget
        # ATTENTION: Entries need to be converted from QString to str for Py 2
        new_labels = [str(l) for l in fb.fil[0] if l[0] == 'A']
        self.update_UI(new_labels=new_labels)

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs / EVENT MONITORING
        #----------------------------------------------------------------------
        self.cmbUnitsA.currentIndexChanged.connect(self._set_amp_unit)
Ejemplo n.º 5
0
class AllpPZ(QWidget):

    FRMT = 'zpk'  # output format of delay filter widget

    info = """
**Allpass widget**

allows entering the two **poles** :math:`p`. **zeros** are calculated from the 
reciprocal values of the poles. There is no minimum algorithm, only the two 
poles can be entered manually.

    """

    sig_tx = pyqtSignal(object)

    def __init__(self):
        QWidget.__init__(self)

        self.p = [0.5, 0.5j]

        self.ft = 'IIR'

        # the following defines which subwidgets are "a"ctive, "i"nvisible or "d"eactivated
        self.rt_dicts = ('com', )
        self.rt_dict = {
            'COM': {
                'man': {
                    'fo': ('d', 'N'),
                    'msg':
                    ('a',
                     "<span>Enter poles  <b><i>p</i></b> for allpass function,"
                     "zeros will be calculated.</span>")
                },
            },
            'AP': {
                'man': {}
            }
        }

        self.info_doc = []

    #--------------------------------------------------------------------------
    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in 
        select_filter.py using the handle to the filter instance, fb.fil_inst.
        """
        self.lbl_pole1 = QLabel("Pole 1", self)
        self.lbl_pole1.setObjectName('wdg_lbl_pole1')
        self.led_pole1 = QLineEdit(self)
        self.led_pole1.setText(str(self.p[0]))
        self.led_pole1.setObjectName('wdg_led_pole1')
        self.led_pole1.setToolTip("Pole 1 for allpass filter")

        self.lbl_pole2 = QLabel("Pole 2", self)
        self.lbl_pole2.setObjectName('wdg_lbl_pole2')
        self.led_pole2 = QLineEdit(self)
        self.led_pole2.setText(str(self.p[1]))
        self.led_pole2.setObjectName('wdg_led_pole2')
        self.led_pole2.setToolTip("Pole 2 for allpass filter")

        self.layHWin = QHBoxLayout()
        self.layHWin.setObjectName('wdg_layGWin')
        self.layHWin.addWidget(self.lbl_pole1)
        self.layHWin.addWidget(self.led_pole1)
        self.layHWin.addWidget(self.lbl_pole2)
        self.layHWin.addWidget(self.led_pole2)
        self.layHWin.setContentsMargins(0, 0, 0, 0)
        # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits)
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layHWin)

        #----------------------------------------------------------------------
        # SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.led_pole1.editingFinished.connect(self._update_UI)
        self.led_pole2.editingFinished.connect(self._update_UI)
        # fires when edited line looses focus or when RETURN is pressed
        #----------------------------------------------------------------------

        self._load_dict()  # get initial / last setting from dictionary
        self._update_UI()

    def _update_UI(self):
        """
        Update UI when line edit field is changed (here, only the text is read
        and converted to integer) and store parameter settings in filter 
        dictionary
        """
        self.p[0] = safe_eval(self.led_pole1.text(),
                              self.p[0],
                              return_type='cmplx')
        self.led_pole1.setText(str(self.p[0]))

        self.p[1] = safe_eval(self.led_pole2.text(),
                              self.p[1],
                              return_type='cmplx')
        self.led_pole2.setText(str(self.p[1]))

        if not 'wdg_fil' in fb.fil[0]:
            fb.fil[0].update({'wdg_fil': {}})
        fb.fil[0]['wdg_fil'].update(
            {'poles': {
                'p1': self.p[0],
                'p2': self.p[1]
            }})

        # sig_tx -> select_filter -> filter_specs
        self.sig_tx.emit({'sender': __name__, 'filt_changed': 'pole_1_2'})

    def _load_dict(self):
        """
        Reload parameter(s) from filter dictionary (if they exist) and set 
        corresponding UI elements. _load_dict() is called upon initialization
        and when the filter is loaded from disk.
        """
        if 'wdg_fil' in fb.fil[0] and 'poles' in fb.fil[0]['wdg_fil']:
            wdg_fil_par = fb.fil[0]['wdg_fil']['poles']
            if 'p1' in wdg_fil_par:
                self.p1 = wdg_fil_par['p1']
                self.led_pole1.setText(str(self.p1))
            if 'p2' in wdg_fil_par:
                self.p2 = wdg_fil_par['p2']
                self.led_pole2.setText(str(self.p2))

    def _get_params(self, fil_dict):
        """
        Get parameters needed for filter design from the passed dictionary and 
        translate them to instance parameters, scaling / transforming them if needed.
        """
        #self.p1     = fil_dict['zpk'][1][0]  # get the first and second pole
        #self.p2     = fil_dict['zpk'][1][1]  # from central filter dect
        logger.info(fil_dict['zpk'])

    def _test_poles(self):
        """
        Warn the user if one of the poles is outside the unit circle
        """
        if abs(self.p[0]) >= 1 or abs(self.p[1]) >= 1:
            return qfilter_warning(self, self.p[0], "Delay")
        else:
            return True

    def _save(self, fil_dict, arg=None):
        """
        Convert between poles / zeros / gain, filter coefficients (polynomes)
        and second-order sections and store all available formats in the passed
        dictionary 'fil_dict'.
        """
        if arg is None:
            logger.error("Passed empty filter dict")
        logger.info(arg)
        fil_save(fil_dict, arg, self.FRMT, __name__)

        fil_dict['N'] = len(self.p)

#------------------------------------------------------------------------------
# Filter design routines
#------------------------------------------------------------------------------
# The method name MUST be "FilterType"+"MinMan", e.g. LPmin or BPman

    def APman(self, fil_dict):
        """
        Calculate z =1/p* for a given set of poles p. If p=0, set z=0.
        The gain factor k is calculated from z and p at z = 1.
        """
        self._get_params(fil_dict)  # not needed here
        if not self._test_poles():
            return -1
        self.z = [0, 0]
        if self.p[0] != 0:
            self.z[0] = np.conj(1 / self.p[0])
        if type(self.p[0]) == complex:
            pass
        if self.p[1] != 0:
            self.z[1] = np.conj(1 / self.p[1])

        k = np.abs(
            np.polyval(np.poly(self.p), 1) / np.polyval(np.poly(self.z), 1))
        zpk_list = [self.z, self.p, k]

        self._save(fil_dict, zpk_list)
Ejemplo n.º 6
0
class FreqUnits(QWidget):
    """
    Build and update widget for entering the frequency units
    
    The following key-value pairs of the `fb.fil[0]` dict are modified:

        - `'freq_specs_unit'` : The unit ('k', 'f_S', 'f_Ny', 'Hz' etc.) as a string
        - `'freqSpecsRange'` : A list with two entries for minimum and maximum frequency
                               values for labelling the frequency axis
        - `'f_S'` : The sampling frequency for referring frequency values to as a float
        - `'f_max'` : maximum frequency for scaling frequency axis              
        - `'plt_fUnit'`: frequency unit as string
        - `'plt_tUnit'`: time unit as string
        - `'plt_fLabel'`: label for frequency axis
        - `'plt_tLabel'`: label for time axis

    """

    # class variables (shared between instances if more than one exists)
    sig_tx = pyqtSignal(object)  # outgoing

    def __init__(self, parent, title="Frequency Units"):

        super(FreqUnits, self).__init__(parent)
        self.title = title
        self.spec_edited = False  # flag whether QLineEdit field has been edited

        self._construct_UI()

    def _construct_UI(self):
        """
        Construct the User Interface
        """
        self.layVMain = QVBoxLayout()  # Widget main layout

        f_units = ['k', 'f_S', 'f_Ny', 'Hz', 'kHz', 'MHz', 'GHz']
        self.t_units = ['', '', '', 's', 'ms', r'$\mu$s', 'ns']

        bfont = QFont()
        bfont.setBold(True)

        self.lblUnits = QLabel(self)
        self.lblUnits.setText("Freq. Unit:")
        self.lblUnits.setFont(bfont)

        self.fs_old = fb.fil[0]['f_S']  # store current sampling frequency
        self.ledF_S = QLineEdit()
        self.ledF_S.setText(str(fb.fil[0]["f_S"]))
        self.ledF_S.setObjectName("f_S")
        self.ledF_S.installEventFilter(self)  # filter events

        self.lblF_S = QLabel(self)
        self.lblF_S.setText(to_html("f_S", frmt='bi'))

        self.cmbUnits = QComboBox(self)
        self.cmbUnits.setObjectName("cmbUnits")
        self.cmbUnits.addItems(f_units)
        self.cmbUnits.setToolTip(
            'Select whether frequencies are specified w.r.t. \n'
            'the sampling frequency "f_S", to the Nyquist frequency \n'
            'f_Ny = f_S/2 or as absolute values. "k" specifies frequencies w.r.t. f_S '
            'but plots graphs over the frequency index k.')
        self.cmbUnits.setCurrentIndex(1)
        #        self.cmbUnits.setItemData(0, (0,QColor("#FF333D"),Qt.BackgroundColorRole))#
        #        self.cmbUnits.setItemData(0, (QFont('Verdana', bold=True), Qt.FontRole)

        fRanges = [("0...½", "half"), ("0...1", "whole"), ("-½...½", "sym")]
        self.cmbFRange = QComboBox(self)
        self.cmbFRange.setObjectName("cmbFRange")
        for f in fRanges:
            self.cmbFRange.addItem(f[0], f[1])
        self.cmbFRange.setToolTip("Select frequency range (whole or half).")
        self.cmbFRange.setCurrentIndex(0)

        # Combobox resizes with longest entry
        self.cmbUnits.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbFRange.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.butSort = QToolButton(self)
        self.butSort.setText("Sort")
        self.butSort.setCheckable(True)
        self.butSort.setChecked(True)
        self.butSort.setToolTip(
            "Sort frequencies in ascending order when pushed.")
        self.butSort.setStyleSheet("QToolButton:checked {font-weight:bold}")

        self.layHUnits = QHBoxLayout()
        self.layHUnits.addWidget(self.cmbUnits)
        self.layHUnits.addWidget(self.cmbFRange)
        self.layHUnits.addWidget(self.butSort)

        # Create a gridLayout consisting of QLabel and QLineEdit fields
        # for setting f_S, the units and the actual frequency specs:
        self.layGSpecWdg = QGridLayout()  # sublayout for spec fields
        self.layGSpecWdg.addWidget(self.lblF_S, 1, 0)
        self.layGSpecWdg.addWidget(self.ledF_S, 1, 1)
        self.layGSpecWdg.addWidget(self.lblUnits, 0, 0)
        self.layGSpecWdg.addLayout(self.layHUnits, 0, 1)

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

        self.layVMain.addWidget(frmMain)
        self.layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(self.layVMain)

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.cmbUnits.currentIndexChanged.connect(self.update_UI)
        self.cmbFRange.currentIndexChanged.connect(self._freq_range)
        self.butSort.clicked.connect(self._store_sort_flag)
        #----------------------------------------------------------------------

        self.update_UI()  # first-time initialization

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

    def update_UI(self):
        """
        Transform the displayed frequency spec input fields according to the units
        setting. Spec entries are always stored normalized w.r.t. f_S in the
        dictionary; when f_S or the unit are changed, only the displayed values
        of the frequency entries are updated, not the dictionary!
        Signals are blocked before changing the value for f_S programmatically

        update_UI is called
        - during init
        - when the unit combobox is changed

        Finally, store freqSpecsRange and emit 'view_changed' signal via _freq_range
        """
        idx = self.cmbUnits.currentIndex()  # read index of units combobox
        f_unit = str(self.cmbUnits.currentText())  # and the label

        self.ledF_S.setVisible(f_unit not in {"f_S", "f_Ny",
                                              "k"})  # only vis. when
        self.lblF_S.setVisible(f_unit not in {"f_S", "f_Ny",
                                              "k"})  # not normalized
        f_S_scale = 1  # default setting for f_S scale

        if f_unit in {"f_S", "f_Ny", "k"}:  # normalized frequency
            self.fs_old = fb.fil[0]['f_S']  # store current sampling frequency

            if f_unit == "f_S":  # normalized to f_S
                fb.fil[0]['f_S'] = fb.fil[0]['f_max'] = 1.
                f_label = r"$F = f\, /\, f_S = \Omega \, /\,  2 \mathrm{\pi} \; \rightarrow$"
            elif f_unit == "f_Ny":  # idx == 1: normalized to f_nyq = f_S / 2
                fb.fil[0]['f_S'] = fb.fil[0]['f_max'] = 2.
                f_label = r"$F = 2f \, / \, f_S = \Omega \, / \, \mathrm{\pi} \; \rightarrow$"
            else:
                fb.fil[0]['f_S'] = 1
                fb.fil[0]['f_max'] = params['N_FFT']
                f_label = r"$k \; \rightarrow$"
            t_label = r"$n \; \rightarrow$"

            self.ledF_S.setText(params['FMT'].format(fb.fil[0]['f_S']))

        else:  # Hz, kHz, ...
            if fb.fil[0]['freq_specs_unit'] in {"f_S", "f_Ny",
                                                "k"}:  # previous setting
                fb.fil[0]['f_S'] = fb.fil[0][
                    'f_max'] = self.fs_old  # restore prev. sampling frequency
                self.ledF_S.setText(params['FMT'].format(fb.fil[0]['f_S']))

            if f_unit == "Hz":
                f_S_scale = 1.
            elif f_unit == "kHz":
                f_S_scale = 1.e3
            elif f_unit == "MHz":
                f_S_scale = 1.e6
            elif f_unit == "GHz":
                f_S_scale = 1.e9
            else:
                logger.warning("Unknown frequency unit {0}".format(f_unit))

            f_label = r"$f$ in " + f_unit + r"$\; \rightarrow$"
            t_label = r"$t$ in " + self.t_units[idx] + r"$\; \rightarrow$"

        if f_unit == "k":
            plt_f_unit = "f_S"
        else:
            plt_f_unit = f_unit
        fb.fil[0].update({'f_S_scale': f_S_scale})  # scale factor for f_S
        fb.fil[0].update({'freq_specs_unit': f_unit})  # frequency unit
        fb.fil[0].update({"plt_fLabel": f_label})  # label for freq. axis
        fb.fil[0].update({"plt_tLabel": t_label})  # label for time axis
        fb.fil[0].update({"plt_fUnit": plt_f_unit})  # frequency unit as string
        fb.fil[0].update({"plt_tUnit":
                          self.t_units[idx]})  # time unit as string

        self._freq_range(
        )  # update f_lim setting and emit sigUnitChanged signal

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

    def eventFilter(self, source, event):
        """
        Filter all events generated by the QLineEdit widgets. Source and type
        of all events generated by monitored objects are passed to this eventFilter,
        evaluated and passed on to the next hierarchy level.

        - When a QLineEdit widget gains input focus (QEvent.FocusIn`), display
          the stored value from filter dict with full precision
        - When a key is pressed inside the text field, set the `spec_edited` flag
          to True.
        - When a QLineEdit widget loses input focus (QEvent.FocusOut`), store
          current value with full precision (only if `spec_edited`== True) and
          display the stored value in selected format. Emit 'view_changed':'f_S'
        """
        def _store_entry():
            """
            Update filter dictionary, set line edit entry with reduced precision
            again.
            """
            if self.spec_edited:
                fb.fil[0].update({
                    'f_S':
                    safe_eval(source.text(), fb.fil[0]['f_S'], sign='pos')
                })
                # TODO: ?!
                self._freq_range(emit_sig_range=False)  # update plotting range
                self.sig_tx.emit({'sender': __name__, 'view_changed': 'f_S'})
                self.spec_edited = False  # reset flag, changed entry has been saved

        if source.objectName() == 'f_S':
            if event.type() == QEvent.FocusIn:
                self.spec_edited = False
                source.setText(str(fb.fil[0]['f_S']))  # full precision
            elif event.type() == QEvent.KeyPress:
                self.spec_edited = True  # entry has been changed
                key = event.key()
                if key in {QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter}:
                    _store_entry()
                elif key == QtCore.Qt.Key_Escape:  # revert changes
                    self.spec_edited = False
                    source.setText(str(fb.fil[0]['f_S']))  # full precision

            elif event.type() == QEvent.FocusOut:
                _store_entry()
                source.setText(params['FMT'].format(
                    fb.fil[0]['f_S']))  # reduced precision
        # Call base class method to continue normal event processing:
        return super(FreqUnits, self).eventFilter(source, event)

    #-------------------------------------------------------------
    def _freq_range(self, emit_sig_range=True):
        """
        Set frequency plotting range for single-sided spectrum up to f_S/2 or f_S
        or for double-sided spectrum between -f_S/2 and f_S/2 and emit
        'view_changed':'f_range'.
        """
        rangeType = qget_cmb_box(self.cmbFRange)

        fb.fil[0].update({'freqSpecsRangeType': rangeType})
        f_max = fb.fil[0]["f_max"]

        if rangeType == 'whole':
            f_lim = [0, f_max]
        elif rangeType == 'sym':
            f_lim = [-f_max / 2., f_max / 2.]
        else:
            f_lim = [0, f_max / 2.]

        fb.fil[0]['freqSpecsRange'] = f_lim  # store settings in dict

        self.sig_tx.emit({'sender': __name__, 'view_changed': 'f_range'})

    #-------------------------------------------------------------
    def load_dict(self):
        """
        Reload comboBox settings and textfields from filter dictionary
        Block signals during update of combobox / lineedit widgets
        """
        self.ledF_S.setText(params['FMT'].format(fb.fil[0]['f_S']))

        self.cmbUnits.blockSignals(True)
        idx = self.cmbUnits.findText(
            fb.fil[0]['freq_specs_unit'])  # get and set
        self.cmbUnits.setCurrentIndex(idx)  # index for freq. unit combo box
        self.cmbUnits.blockSignals(False)

        self.cmbFRange.blockSignals(True)
        idx = self.cmbFRange.findData(fb.fil[0]['freqSpecsRangeType'])
        self.cmbFRange.setCurrentIndex(idx)  # set frequency range
        self.cmbFRange.blockSignals(False)

        self.butSort.blockSignals(True)
        self.butSort.setChecked(fb.fil[0]['freq_specs_sort'])
        self.butSort.blockSignals(False)

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

    def _store_sort_flag(self):
        """
        Store sort flag in filter dict and emit 'specs_changed':'f_sort'
        when sort button is checked.
        """
        fb.fil[0]['freq_specs_sort'] = self.butSort.isChecked()
        if self.butSort.isChecked():
            self.sig_tx.emit({'sender': __name__, 'specs_changed': 'f_sort'})
Ejemplo n.º 7
0
class Input_Fixpoint_Specs(QWidget):
    """
    Create the widget that holds the dynamically loaded fixpoint filter ui
    """

    # sig_resize = pyqtSignal()  # emit a signal when the image has been resized
    sig_rx_local = pyqtSignal(object)  # incoming from subwidgets -> process_sig_rx_local
    sig_rx = pyqtSignal(object)  # incoming, connected to input_tab_widget.sig_rx
    sig_tx = pyqtSignal(object)  # outcgoing
    from pyfda.libs.pyfda_qt_lib import emit

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

        self.tab_label = 'Fixpoint'
        self.tool_tip = ("<span>Select a fixpoint implementation for the filter,"
                         " simulate it or generate a Verilog netlist.</span>")
        self.parent = parent
        self.fx_path = os.path.realpath(
            os.path.join(dirs.INSTALL_DIR, 'fixpoint_widgets'))

        self.no_fx_filter_img = os.path.join(self.fx_path, "no_fx_filter.png")
        if not os.path.isfile(self.no_fx_filter_img):
            logger.error("Image {0:s} not found!".format(self.no_fx_filter_img))

        self.default_fx_img = os.path.join(self.fx_path, "default_fx_img.png")
        if not os.path.isfile(self.default_fx_img):
            logger.error("Image {0:s} not found!".format(self.default_fx_img))

        self._construct_UI()
        inst_wdg_list = self._update_filter_cmb()
        if len(inst_wdg_list) == 0:
            logger.warning("No fixpoint filter found for this type of filter!")
        else:
            logger.debug("Imported {0:d} fixpoint filters:\n{1}"
                         .format(len(inst_wdg_list.split("\n"))-1, inst_wdg_list))
        self._update_fixp_widget()

# ------------------------------------------------------------------------------
    def process_sig_rx_local(self, dict_sig: dict = None) -> None:
        """
        Process signals coming in from input and output quantizer subwidget and the
        dynamically instantiated subwidget and emit {'fx_sim': 'specs_changed'} in
        the end.
        """
        if dict_sig['id'] == id(self):
            logger.warning(f'RX_LOCAL - Stopped infinite loop: "{first_item(dict_sig)}"')
            return

        elif 'fx_sim' in dict_sig and dict_sig['fx_sim'] == 'specs_changed':
            self.wdg_dict2ui()  # update wordlengths in UI and set RUN button to 'changed'
            dict_sig.update({'id': id(self)})  # propagate 'specs_changed' with self 'id'
            self.emit(dict_sig)
            return

        # ---- Process input and output quantizer settings ('ui' in dict_sig) --
        elif 'ui' in dict_sig:
            if 'wdg_name' not in dict_sig:
                logger.warning(f"No key 'wdg_name' in dict_sig:\n{pprint_log(dict_sig)}")
                return

            elif dict_sig['wdg_name'] == 'w_input':
                """
                Input fixpoint format has been changed or butLock has been clicked.
                When I/O lock is active, copy input fixpoint word format to output
                word format.
                """
                if dict_sig['ui'] == 'butLock'\
                        and not self.wdg_w_input.butLock.isChecked():
                    # butLock was deactivitated, don't do anything
                    return
                elif self.wdg_w_input.butLock.isChecked():
                    # but lock was activated or wordlength setting have been changed
                    fb.fil[0]['fxqc']['QO']['WI'] = fb.fil[0]['fxqc']['QI']['WI']
                    fb.fil[0]['fxqc']['QO']['WF'] = fb.fil[0]['fxqc']['QI']['WF']
                    fb.fil[0]['fxqc']['QO']['W'] = fb.fil[0]['fxqc']['QI']['W']

            elif dict_sig['wdg_name'] == 'w_output':
                """
                Output fixpoint format has been changed. When I/O lock is active, copy
                output fixpoint word format to input word format.
                """
                if self.wdg_w_input.butLock.isChecked():
                    fb.fil[0]['fxqc']['QI']['WI'] = fb.fil[0]['fxqc']['QO']['WI']
                    fb.fil[0]['fxqc']['QI']['WF'] = fb.fil[0]['fxqc']['QO']['WF']
                    fb.fil[0]['fxqc']['QI']['W'] = fb.fil[0]['fxqc']['QO']['W']

            elif dict_sig['wdg_name'] in {'q_output', 'q_input'}:
                pass
            else:
                logger.error("Unknown wdg_name '{0}' in dict_sig:\n{1}"
                             .format(dict_sig['wdg_name'], pprint_log(dict_sig)))
                return

            if dict_sig['ui'] not in {'WI', 'WF', 'ovfl', 'quant', 'cmbW', 'butLock'}:
                logger.warning("Unknown value '{0}' for key 'ui'".format(dict_sig['ui']))

            self.wdg_dict2ui()  # update wordlengths in UI and set RUN button to 'changed'
            self.emit({'fx_sim': 'specs_changed'})  # propagate 'specs_changed'

        else:
            logger.error(f"Unknown key/value in 'dict_sig':\n{pprint_log(dict_sig)}")

# ------------------------------------------------------------------------------
    def process_sig_rx(self, dict_sig: dict = None) -> None:
        """
        Process signals coming in via `sig_rx` from other widgets.

        Trigger fx simulation:

        1. ``fx_sim': 'init'``: Start fixpoint simulation by sending
           'fx_sim':'start_fx_response_calculation'

        2. ``fx_sim_calc_response()``: Receive stimulus from widget in
            'fx_sim':'calc_frame_fx_response' and pass it to fixpoint simulation method

        3. Store fixpoint response in `fb.fx_result` and return to initiating routine
        """

        # logger.info(
        #     "SIG_RX(): vis={0}\n{1}".format(self.isVisible(), pprint_log(dict_sig)))
        # logger.debug(f'SIG_RX():  "{first_item(dict_sig)}"')

        if dict_sig['id'] == id(self):
            # logger.warning(f'Stopped infinite loop: "{first_item(dict_sig)}"')
            return

        elif 'data_changed' in dict_sig and dict_sig['data_changed'] == "filter_designed":
            # New filter has been designed, update list of available filter topologies
            self._update_filter_cmb()
            return

        elif 'data_changed' in dict_sig or\
             ('view_changed' in dict_sig and dict_sig['view_changed'] == 'q_coeff'):
            # Filter data has changed (but not the filter type) or the coefficient
            # format / wordlength have been changed in `input_coeffs`. The latter means
            # the view / display has been changed (wordlength) but not the actual
            # coefficients in the `input_coeffs` widget. However, the wordlength setting
            # is copied to the fxqc dict and from there to the fixpoint widget.
            # - update fields in the fixpoint filter widget - wordlength may have
            #   been changed.
            # - Set RUN button to "changed" in wdg_dict2ui()
            self.wdg_dict2ui()

        # --------------- FX Simulation -------------------------------------------
        elif 'fx_sim' in dict_sig:
            if dict_sig['fx_sim'] == 'init':
                # fixpoint simulation has been started externally, e.g. by
                # `impz.impz_init()`, return a handle to the fixpoint filter function
                # via signal-slot connection
                if not self.fx_wdg_found:
                    logger.error("No fixpoint widget found!")
                    qstyle_widget(self.butSimFx, "error")
                    self.emit({'fx_sim': 'error'})
                elif self.fx_sim_init() != 0:  # returned an error
                    qstyle_widget(self.butSimFx, "error")
                    self.emit({'fx_sim': 'error'})
                else:
                    self.emit({'fx_sim': 'start_fx_response_calculation',
                               'fxfilter_func': self.fx_filt_ui.fxfilter})

            elif dict_sig['fx_sim'] == 'calc_frame_fx_response':
                self.fx_sim_calc_response(dict_sig)
                # return to the routine collecting the response frame by frame
                return

            elif dict_sig['fx_sim'] == 'specs_changed':
                # fixpoint specification have been changed somewhere, update ui
                # and set run button to "changed" in wdg_dict2ui()
                self.wdg_dict2ui()
            elif dict_sig['fx_sim'] == 'finish':
                qstyle_widget(self.butSimFx, "normal")
            else:
                logger.error('Unknown "fx_sim" command option "{0}"\n'
                             '\treceived from "{1}".'
                             .format(dict_sig['fx_sim'], dict_sig['class']))

        # ---- resize image when "Fixpoint" tab is selected or widget size is changed:
        elif 'ui_changed' in dict_sig and dict_sig['ui_changed'] in {'resized', 'tab'}\
                and self.isVisible():
            self.resize_img()

# ------------------------------------------------------------------------------
    def _construct_UI(self) -> None:
        """
        Intitialize the main GUI, consisting of:

        - A combo box to select the filter topology and an image of the topology

        - The input quantizer

        - The UI of the fixpoint filter widget

        - Simulation and export buttons
        """
# ------------------------------------------------------------------------------
        # Define frame and layout for the dynamically updated filter widget
        # The actual filter widget is instantiated in self.set_fixp_widget() later on

        self.layH_fx_wdg = QHBoxLayout()
        # self.layH_fx_wdg.setContentsMargins(*params['wdg_margins'])
        frmHDL_wdg = QFrame(self)
        frmHDL_wdg.setLayout(self.layH_fx_wdg)
        # frmHDL_wdg.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

# ------------------------------------------------------------------------------
#       Initialize fixpoint filter combobox, title and description
# ------------------------------------------------------------------------------
        self.cmb_fx_wdg = QComboBox(self)
        self.cmb_fx_wdg.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.lblTitle = QLabel("not set", self)
        self.lblTitle.setWordWrap(True)
        self.lblTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        layHTitle = QHBoxLayout()
        layHTitle.addWidget(self.cmb_fx_wdg)
        layHTitle.addWidget(self.lblTitle)

        self.frmTitle = QFrame(self)
        self.frmTitle.setLayout(layHTitle)
        self.frmTitle.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Input and Output Quantizer
# ------------------------------------------------------------------------------
#       - instantiate widgets for input and output quantizer
#       - pass the quantization (sub-?) dictionary to the constructor
# ------------------------------------------------------------------------------

        self.wdg_w_input = UI_W(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='w_input', label='', lock_visible=True)
        self.wdg_w_input.sig_tx.connect(self.process_sig_rx_local)

        cmb_q = ['round', 'floor', 'fix']

        self.wdg_w_output = UI_W(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='w_output', label='')
        self.wdg_w_output.sig_tx.connect(self.process_sig_rx_local)

        self.wdg_q_output = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='q_output',
                                 label='Output Format <i>Q<sub>Y&nbsp;</sub></i>:',
                                 cmb_q=cmb_q, cmb_ov=['wrap', 'sat'])
        self.wdg_q_output.sig_tx.connect(self.sig_rx_local)

        if HAS_DS:
            cmb_q.append('dsm')
        self.wdg_q_input = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='q_input',
                                label='Input Format <i>Q<sub>X&nbsp;</sub></i>:',
                                cmb_q=cmb_q)
        self.wdg_q_input.sig_tx.connect(self.sig_rx_local)

        # Layout and frame for input quantization
        layVQiWdg = QVBoxLayout()
        layVQiWdg.addWidget(self.wdg_q_input)
        layVQiWdg.addWidget(self.wdg_w_input)
        frmQiWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQiWdg.setLayout(layVQiWdg)
        frmQiWdg.setContentsMargins(*params['wdg_margins'])

        # Layout and frame for output quantization
        layVQoWdg = QVBoxLayout()
        layVQoWdg.addWidget(self.wdg_q_output)
        layVQoWdg.addWidget(self.wdg_w_output)
        frmQoWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQoWdg.setLayout(layVQoWdg)
        frmQoWdg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Dynamically updated image of filter topology (label as placeholder)
# ------------------------------------------------------------------------------
        # allow setting background color
        # lbl_fixp_img_palette = QPalette()
        # lbl_fixp_img_palette.setColor(QPalette(window, Qt: white))
        # lbl_fixp_img_palette.setBrush(self.backgroundRole(), QColor(150, 0, 0))
        # lbl_fixp_img_palette.setColor(QPalette: WindowText, Qt: blue)

        self.lbl_fixp_img = QLabel("img not set", self)
        self.lbl_fixp_img.setAutoFillBackground(True)
        # self.lbl_fixp_img.setPalette(lbl_fixp_img_palette)
        # self.lbl_fixp_img.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.embed_fixp_img(self.no_fx_filter_img)
        layHImg = QHBoxLayout()
        layHImg.setContentsMargins(0, 0, 0, 0)
        layHImg.addWidget(self.lbl_fixp_img)  # , Qt.AlignCenter)
        self.frmImg = QFrame(self)
        self.frmImg.setLayout(layHImg)
        self.frmImg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Simulation and export Buttons
# ------------------------------------------------------------------------------
        self.butExportHDL = QPushButton(self)
        self.butExportHDL.setToolTip(
            "Create Verilog or VHDL netlist for fixpoint filter.")
        self.butExportHDL.setText("Create HDL")

        self.butSimFx = QPushButton(self)
        self.butSimFx.setToolTip("Start fixpoint simulation.")
        self.butSimFx.setText("Sim. FX")

        self.layHHdlBtns = QHBoxLayout()
        self.layHHdlBtns.addWidget(self.butSimFx)
        self.layHHdlBtns.addWidget(self.butExportHDL)
        # This frame encompasses the HDL buttons sim and convert
        frmHdlBtns = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmHdlBtns.setLayout(self.layHHdlBtns)
        frmHdlBtns.setContentsMargins(*params['wdg_margins'])

# -------------------------------------------------------------------
#       Top level layout
# -------------------------------------------------------------------
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(frmHDL_wdg)
        splitter.addWidget(frmQoWdg)
        splitter.addWidget(self.frmImg)

        # 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, 3000, 5000])

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmTitle)
        layVMain.addWidget(frmHdlBtns)
        layVMain.addWidget(frmQiWdg)
        layVMain.addWidget(splitter)
        layVMain.addStretch()
        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        self.sig_rx_local.connect(self.process_sig_rx_local)
        # dynamic connection in `self._update_fixp_widget()`:
        # -----
        # if hasattr(self.fx_filt_ui, "sig_rx"):
        #     self.sig_rx.connect(self.fx_filt_ui.sig_rx)
        # if hasattr(self.fx_filt_ui, "sig_tx"):
        #     self.fx_filt_ui.sig_tx.connect(self.sig_rx_local)
        # ----
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.cmb_fx_wdg.currentIndexChanged.connect(self._update_fixp_widget)
        self.butExportHDL.clicked.connect(self.exportHDL)
        self.butSimFx.clicked.connect(lambda x: self.emit({'fx_sim': 'start'}))
        # ----------------------------------------------------------------------
        # EVENT FILTER
        # ----------------------------------------------------------------------
        # # monitor events and generate sig_resize event when resized
        # self.lbl_fixp_img.installEventFilter(self)
        # # ... then redraw image when resized
        # self.sig_resize.connect(self.resize_img)

# ------------------------------------------------------------------------------
    def _update_filter_cmb(self) -> str:
        """
        (Re-)Read list of available fixpoint filters for a given filter design
        every time a new filter design is selected.

        Then try to import the fixpoint designs in the list and populate the
        fixpoint implementation combo box `self.cmb_fx_wdg` when successfull.

        Returns
        -------
        inst_wdg_str: str
          string with all fixpoint widgets that could be instantiated successfully
        """
        inst_wdg_str = ""  # full names of successfully instantiated widgets for logging
        # remember last fx widget setting:
        last_fx_wdg = qget_cmb_box(self.cmb_fx_wdg, data=False)
        self.cmb_fx_wdg.clear()
        fc = fb.fil[0]['fc']

        if 'fix' in fb.filter_classes[fc]:
            self.cmb_fx_wdg.blockSignals(True)
            for class_name in fb.filter_classes[fc]['fix']:  # get class name
                try:   # construct module + class name ...
                    mod_class_name = fb.fixpoint_classes[class_name]['mod'] + '.'\
                        + class_name
                    # ... and display name
                    disp_name = fb.fixpoint_classes[class_name]['name']
                    self.cmb_fx_wdg.addItem(disp_name, mod_class_name)
                    inst_wdg_str += '\t' + class_name + ' : ' + mod_class_name + '\n'
                except AttributeError as e:
                    logger.warning('Widget "{0}":\n{1}'.format(class_name, e))
                    self.embed_fixp_img(self.no_fx_filter_img)
                    continue  # with next `class_name` of for loop
                except KeyError as e:
                    logger.warning("No fixpoint filter for filter type {0} available."
                                   .format(e))
                    self.embed_fixp_img(self.no_fx_filter_img)
                    continue  # with next `class_name` of for loop

            # restore last fx widget if possible
            idx = self.cmb_fx_wdg.findText(last_fx_wdg)
            # set to idx 0 if not found (returned -1)
            self.cmb_fx_wdg.setCurrentIndex(max(idx, 0))
            self.cmb_fx_wdg.blockSignals(False)
        else:  # no fixpoint widget
            self.embed_fixp_img(self.no_fx_filter_img)
        self._update_fixp_widget()
        return inst_wdg_str

# # ------------------------------------------------------------------------------
#     def eventFilter(self, source, event):
#         """
#         Filter all events generated by monitored QLabel, only resize events are
#         processed here, generating a `sig_resize` signal. All other events
#         are passed on to the next hierarchy level.
#         """
#         if event.type() == QEvent.Resize:
#             logger.warning("resize event!")
#             self.sig_resize.emit()

#         # Call base class method to continue normal event processing:
#         return super(Input_Fixpoint_Specs, self).eventFilter(source, event)

# ------------------------------------------------------------------------------
    def embed_fixp_img(self, img_file: str) -> QPixmap:
        """
        Embed `img_file` in png format as `self.img_fixp`

        Parameters
        ----------
        img_file: str
            path and file name to image file

        Returns
        -------
        self.img_fixp: QPixmap object
            pixmap containing the passed img_file
        """
        if not os.path.isfile(img_file):
            logger.warning("Image file {0} doesn't exist.".format(img_file))
            img_file = self.default_fx_img

        _, file_extension = os.path.splitext(img_file)
        if file_extension != '.png':
            logger.error('Unknown file extension "{0}"!'.format(file_extension))
            img_file = self.default_fx_img

        self.img_fixp = QPixmap(img_file)
        # logger.warning(f"img_fixp = {img_file}")
        # logger.warning(f"_embed_fixp_img(): {self.img_fixp.__class__.__name__}")
        return self.img_fixp

# ------------------------------------------------------------------------------
    def resize_img(self) -> None:
        """
        Triggered when `self` (the widget) is selected or resized. The method resizes
        the image inside QLabel to completely fill the label while keeping
        the aspect ratio. An offset of some pixels is needed, otherwise the image
        is clipped.
        """
        # logger.warning(f"resize_img(): img_fixp = {self.img_fixp.__class__.__name__}")

        if self.parent is None:  # parent is QApplication, has no width or height
            par_w, par_h = 300, 700  # fixed size for module level test
        else:  # widget parent is InputTabWidget()
            par_w, par_h = self.parent.width(), self.parent.height()

        img_w, img_h = self.img_fixp.width(), self.img_fixp.height()

        if img_w > 10:
            max_h = int(max(np.floor(img_h * par_w/img_w) - 5, 20))
        else:
            max_h = 200
        logger.debug("img size: {0},{1}, frm size: {2},{3}, max_h: {4}"
                     .format(img_w, img_h, par_w, par_h, max_h))

        # The following doesn't work because the width of the parent widget can grow
        # with the image size
        # img_scaled = self.img_fixp.scaled(self.lbl_fixp_img.size(),
        # Qt.KeepAspectRatio, Qt.SmoothTransformation)
        img_scaled = self.img_fixp.scaledToHeight(max_h, Qt.SmoothTransformation)

        self.lbl_fixp_img.setPixmap(img_scaled)

# ------------------------------------------------------------------------------
    def _update_fixp_widget(self):
        """
        This method is called at the initialization of the widget and when
        a new fixpoint filter implementation is selected from the combo box:

        - Destruct old instance of fixpoint filter widget `self.fx_filt_ui`
        - Import and instantiate new fixpoint filter widget e.g. after changing the
          filter topology as
        - Try to load image for filter topology
        - Update the UI of the widget
        - Try to instantiate HDL filter as `self.fx_filt_ui.fixp_filter` with
            dummy data
        - emit {'fx_sim': 'specs_changed'} when successful
        """
        def _disable_fx_wdg(self) -> None:

            if hasattr(self, "fx_filt_ui") and self.fx_filt_ui is not None:
                # is a fixpoint widget loaded?
                try:
                    # try to remove widget from layout
                    self.layH_fx_wdg.removeWidget(self.fx_filt_ui)
                    # delete QWidget when scope has been left
                    self.fx_filt_ui.deleteLater()
                except AttributeError as e:
                    logger.error("Destructing UI failed!\n{0}".format(e))

            self.fx_wdg_found = False
            self.butSimFx.setEnabled(False)
            self.butExportHDL.setVisible(False)
            # self.layH_fx_wdg.setVisible(False)
            self.img_fixp = self.embed_fixp_img(self.no_fx_filter_img)
            self.resize_img()
            self.lblTitle.setText("")

            self.fx_filt_ui = None
        # -----------------------------------------------------------
        _disable_fx_wdg(self)  # destruct old fixpoint widget instance:

        # instantiate new fixpoint widget class as self.fx_filt_ui
        cmb_wdg_fx_cur = qget_cmb_box(self.cmb_fx_wdg, data=False)
        if cmb_wdg_fx_cur:  # at least one valid fixpoint widget found
            self.fx_wdg_found = True
            # get list [module name and path, class name]
            fx_mod_class_name = qget_cmb_box(self.cmb_fx_wdg, data=True).rsplit('.', 1)
            fx_mod = importlib.import_module(fx_mod_class_name[0])  # get module
            fx_filt_ui_class = getattr(fx_mod, fx_mod_class_name[1])  # get class
            logger.info("Instantiating new FX widget\n\t"
                        f"{fx_mod.__name__}.{fx_filt_ui_class.__name__}")
            # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            self.fx_filt_ui = fx_filt_ui_class()  # instantiate the fixpoint widget
            # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            # and add it to layout:
            self.layH_fx_wdg.addWidget(self.fx_filt_ui, stretch=1)
            self.fx_filt_ui.setVisible(True)
            self.wdg_dict2ui()  # initialize the fixpoint subwidgets from the fxqc_dict

            # ---- connect signals to fx_filt_ui ----
            if hasattr(self.fx_filt_ui, "sig_rx"):
                self.sig_rx.connect(self.fx_filt_ui.sig_rx)
            if hasattr(self.fx_filt_ui, "sig_tx"):
                self.fx_filt_ui.sig_tx.connect(self.sig_rx_local)

            # ---- get name of new fixpoint filter image ----
            if not (hasattr(self.fx_filt_ui, "img_name") and self.fx_filt_ui.img_name):
                # no image name defined, use default image
                img_file = self.default_fx_img
            else:
                # get path of imported fixpoint widget ...
                file_path = os.path.dirname(fx_mod.__file__)
                # ... and construct full image name from it
                img_file = os.path.join(file_path, self.fx_filt_ui.img_name)

            # ---- instantiate and scale graphic of filter topology ----
            self.embed_fixp_img(img_file)
            self.resize_img()

            # ---- set title and description for filter
            self.lblTitle.setText(self.fx_filt_ui.title)

            # Check which methods the fixpoint widget provides and enable
            # corresponding buttons:
            self.butExportHDL.setVisible(hasattr(self.fx_filt_ui, "to_hdl"))
            self.butSimFx.setEnabled(hasattr(self.fx_filt_ui, "fxfilter"))
            self.update_fxqc_dict()
            self.emit({'fx_sim': 'specs_changed'})

# ------------------------------------------------------------------------------
    def wdg_dict2ui(self):
        """
        Trigger an update of the fixpoint widget UI when view (i.e. fixpoint
        coefficient format) or data have been changed outside this class. Additionally,
        pass the fixpoint quantization widget to update / restore other subwidget
        settings.

        Set the RUN button to "changed".
        """
#        fb.fil[0]['fxqc']['QCB'].update({'scale':(1 << fb.fil[0]['fxqc']['QCB']['W'])})
        self.wdg_q_input.dict2ui(fb.fil[0]['fxqc']['QI'])
        self.wdg_q_output.dict2ui(fb.fil[0]['fxqc']['QO'])
        self.wdg_w_input.dict2ui(fb.fil[0]['fxqc']['QI'])
        self.wdg_w_output.dict2ui(fb.fil[0]['fxqc']['QO'])
        if self.fx_wdg_found and hasattr(self.fx_filt_ui, "dict2ui"):
            self.fx_filt_ui.dict2ui()
#            dict_sig = {'fx_sim':'specs_changed'}
#            self.emit(dict_sig)

        qstyle_widget(self.butSimFx, "changed")

# ------------------------------------------------------------------------------
    def update_fxqc_dict(self):
        """
        Update the fxqc dictionary before simulation / HDL generation starts.
        """
        if self.fx_wdg_found:
            # get a dict with the coefficients and fixpoint settings from fixpoint widget
            if hasattr(self.fx_filt_ui, "ui2dict"):
                fb.fil[0]['fxqc'].update(self.fx_filt_ui.ui2dict())
                logger.debug("update fxqc: \n{0}".format(pprint_log(fb.fil[0]['fxqc'])))
        else:
            logger.error("No fixpoint widget found!")

# ------------------------------------------------------------------------------
    def exportHDL(self):
        """
        Synthesize HDL description of filter
        """
        dlg = QFD(self)  # instantiate file dialog object

        file_types = "Verilog (*.v)"
        # needed for overwrite confirmation when name is entered without suffix:
        dlg.setDefaultSuffix('v')
        dlg.setWindowTitle('Export Verilog')
        dlg.setNameFilter(file_types)
        dlg.setDirectory(dirs.save_dir)
        # set mode "save file" instead "open file":
        dlg.setAcceptMode(QFD.AcceptSave)
        dlg.setOption(QFD.DontConfirmOverwrite, False)
        if dlg.exec_() == QFD.Accepted:
            hdl_file = qstr(dlg.selectedFiles()[0])
            # hdl_type = extract_file_ext(qstr(dlg.selectedNameFilter()))[0]

# =============================================================================
#       # static method getSaveFileName_() is simple but unflexible
#         hdl_file, hdl_filter = dlg.getSaveFileName_(
#                 caption="Save Verilog netlist as (this also defines the module name)",
#                 directory=dirs.save_dir, filter=file_types)
#         hdl_file = qstr(hdl_file)
#         if hdl_file != "": # "operation cancelled" returns an empty string
#             # return '.v' or '.vhd' depending on filetype selection:
#             # hdl_type = extract_file_ext(qstr(hdl_filter))[0]
#             # sanitized dir + filename + suffix. The filename suffix is replaced
#             # by `v` later.
#             hdl_file = os.path.normpath(hdl_file) # complete path + file name
# =============================================================================
            hdl_dir_name = os.path.dirname(hdl_file)  # extract the directory path
            if not os.path.isdir(hdl_dir_name):  # create directory if it doesn't exist
                os.mkdir(hdl_dir_name)
            dirs.save_dir = hdl_dir_name  # make this directory the new default / base dir
            hdl_file_name = os.path.splitext(os.path.basename(hdl_file))[0]
            hdl_full_name = os.path.join(hdl_dir_name, hdl_file_name + ".v")
            # remove all non-alphanumeric chars:
            vlog_mod_name = re.sub(r'\W+', '', hdl_file_name).lower()

            logger.info('Creating hdl_file "{0}"\n\twith top level module "{1}"'
                        .format(hdl_full_name, vlog_mod_name))
            try:
                self.update_fxqc_dict()
                self.fx_filt_ui.construct_fixp_filter()
                code = self.fx_filt_ui.to_hdl(name=vlog_mod_name)
                # logger.info(str(code)) # print verilog code to console
                with io.open(hdl_full_name, 'w', encoding="utf8") as f:
                    f.write(str(code))

                logger.info("HDL conversion finished!")
            except (IOError, TypeError) as e:
                logger.warning(e)

    # --------------------------------------------------------------------------
    def fx_sim_init(self):
        """
        Initialize fix-point simulation:

        - Update the `fxqc_dict` containing all quantization information
        - Setup a filter instance for fixpoint simulation
        - Request a stimulus signal

        Returns
        -------
        error: int
            0 for sucessful fx widget construction, -1 for error
        """
        try:
            self.update_fxqc_dict()
            self.fx_filt_ui.init_filter()   # setup filter instance
            return 0

        except ValueError as e:
            logger.error('Fixpoint stimulus generation failed during "init"'
                         '\nwith "{0} "'.format(e))
        return -1

# ------------------------------------------------------------------------------
    def fx_sim_calc_response(self, dict_sig) -> None:
        """
        - Read fixpoint stimulus from `dict_sig` in integer format
        - Pass it to the fixpoint filter which calculates the fixpoint response
        - Store the result in `fb.fx_results` and return. In case of an error,
          `fb.fx_results == None`

        Returns
        -------
        None
        """
        try:
            # logger.info(
            #     'Simulate fixpoint frame with "{0}" stimulus:\n\t{1}'.format(
            #         dict_sig['class'],
            #         pprint_log(dict_sig['fx_stimulus'], tab=" "),
            #         ))

            # Run fixpoint simulation and store the results as integer values:
            fb.fx_results = self.fx_filt_ui.fxfilter(dict_sig['fx_stimulus'])

            if len(fb.fx_results) == 0:
                logger.error("Fixpoint simulation returned empty results!")
            # else:
            #     # logger.debug("fx_results: {0}"\
            #     #            .format(pprint_log(fb.fx_results, tab= " ")))
            #     logger.info(
            #         f'Fixpoint simulation successful for dict\n{pprint_log(dict_sig)}'
            #         f'\tStimuli: Shape {np.shape(dict_sig["fx_stimulus"])}'
            #         f' of type "{dict_sig["fx_stimulus"].dtype}"'
            #         f'\n\tResponse: Shape {np.shape(fb.fx_results)}'
            #         f' of type "{type(fb.fx_results).__name__} "'
            #         f' ("{type(fb.fx_results[0]).__name__}")'
            #     )

        except ValueError as e:
            logger.error("Simulator error {0}".format(e))
            fb.fx_results = None

        except AssertionError as e:
            logger.error('Fixpoint simulation failed for dict\n{0}'
                         '\twith msg. "{1}"\n\tStimuli: Shape {2} of type "{3}"'
                         '\n\tResponse: Shape {4} of type "{5}"'.format(
                            pprint_log(dict_sig), e,
                            np.shape(dict_sig['fx_stimulus']),
                            dict_sig['fx_stimulus'].dtype,
                            np.shape(fb.fx_results),
                            type(fb.fx_results)
                                ))
            fb.fx_results = None

        if fb.fx_results is None:
            qstyle_widget(self.butSimFx, "error")
        else:
            pass # everything ok, return 
            # logger.debug("Sending fixpoint results")
        return
Ejemplo n.º 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.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)
Ejemplo n.º 9
0
class FreqUnits(QWidget):
    """
    Build and update widget for entering frequency unit, frequency range and
    sampling frequency f_S

    The following key-value pairs of the `fb.fil[0]` dict are modified:

        - `'freq_specs_unit'` : The unit ('k', 'f_S', 'f_Ny', 'Hz' etc.) as a string
        - `'freqSpecsRange'` : A list with two entries for minimum and maximum frequency
                               values for labelling the frequency axis
        - `'f_S'` : The sampling frequency for referring frequency values to as a float
        - `'f_max'` : maximum frequency for scaling frequency axis
        - `'plt_fUnit'`: frequency unit as string
        - `'plt_tUnit'`: time unit as string
        - `'plt_fLabel'`: label for frequency axis
        - `'plt_tLabel'`: label for time axis

    """

    # class variables (shared between instances if more than one exists)
    sig_tx = pyqtSignal(object)  # outgoing
    from pyfda.libs.pyfda_qt_lib import emit

    def __init__(self, parent=None, title="Frequency Units"):

        super(FreqUnits, self).__init__(parent)
        self.title = title
        self.spec_edited = False  # flag whether QLineEdit field has been edited

        self._construct_UI()

    def _construct_UI(self):
        """
        Construct the User Interface
        """
        self.layVMain = QVBoxLayout() # Widget main layout

        f_units = ['k','f_S', 'f_Ny', 'Hz', 'kHz', 'MHz', 'GHz']
        self.t_units = ['', 'T_S', 'T_S', 's', 'ms', r'$\mu$s', 'ns']

        bfont = QFont()
        bfont.setBold(True)

        self.lblUnits = QLabel(self)
        self.lblUnits.setText("Freq. Unit")
        self.lblUnits.setFont(bfont)

        self.fs_old = fb.fil[0]['f_S']  # store current sampling frequency

        self.lblF_S = QLabel(self)
        self.lblF_S.setText(to_html("f_S =", frmt='bi'))

        self.ledF_S = QLineEdit()
        self.ledF_S.setText(str(fb.fil[0]["f_S"]))
        self.ledF_S.setObjectName("f_S")
        self.ledF_S.installEventFilter(self)  # filter events

        self.butLock = QToolButton(self)
        self.butLock.setIcon(QIcon(':/lock-unlocked.svg'))
        self.butLock.setCheckable(True)
        self.butLock.setChecked(False)
        self.butLock.setToolTip(
            "<span><b>Unlocked:</b> When f_S is changed, all frequency related "
            "widgets are updated, normalized frequencies stay the same.<br />"
            "<b>Locked:</b> When f_S is changed, displayed absolute frequency "
            "values don't change but normalized frequencies do.</span>")
        # self.butLock.setStyleSheet("QToolButton:checked {font-weight:bold}")

        layHF_S = QHBoxLayout()
        layHF_S.addWidget(self.ledF_S)
        layHF_S.addWidget(self.butLock)

        self.cmbUnits = QComboBox(self)
        self.cmbUnits.setObjectName("cmbUnits")
        self.cmbUnits.addItems(f_units)
        self.cmbUnits.setToolTip(
        'Select whether frequencies are specified w.r.t. \n'
        'the sampling frequency "f_S", to the Nyquist frequency \n'
        'f_Ny = f_S/2 or as absolute values. "k" specifies frequencies w.r.t. f_S '
        'but plots graphs over the frequency index k.')
        self.cmbUnits.setCurrentIndex(1)
#        self.cmbUnits.setItemData(0, (0,QColor("#FF333D"),Qt.BackgroundColorRole))#
#        self.cmbUnits.setItemData(0, (QFont('Verdana', bold=True), Qt.FontRole)

        fRanges = [("0...½", "half"), ("0...1","whole"), ("-½...½", "sym")]
        self.cmbFRange = QComboBox(self)
        self.cmbFRange.setObjectName("cmbFRange")
        for f in fRanges:
            self.cmbFRange.addItem(f[0],f[1])
        self.cmbFRange.setToolTip("Select frequency range (whole or half).")
        self.cmbFRange.setCurrentIndex(0)

        # Combobox resizes with longest entry
        self.cmbUnits.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbFRange.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.butSort = QToolButton(self)
        self.butSort.setText("Sort")
        self.butSort.setIcon(QIcon(':/sort-ascending.svg'))
        #self.butDelCells.setIconSize(q_icon_size)
        self.butSort.setCheckable(True)
        self.butSort.setChecked(True)
        self.butSort.setToolTip("Sort frequencies in ascending order when pushed.")
        self.butSort.setStyleSheet("QToolButton:checked {font-weight:bold}")

        self.layHUnits = QHBoxLayout()
        self.layHUnits.addWidget(self.cmbUnits)
        self.layHUnits.addWidget(self.cmbFRange)
        self.layHUnits.addWidget(self.butSort)

        # Create a gridLayout consisting of QLabel and QLineEdit fields
        # for setting f_S, the units and the actual frequency specs:
        self.layGSpecWdg = QGridLayout()  # sublayout for spec fields
        self.layGSpecWdg.addWidget(self.lblF_S, 1, 0)
        # self.layGSpecWdg.addWidget(self.ledF_S,1,1)
        self.layGSpecWdg.addLayout(layHF_S, 1, 1)
        self.layGSpecWdg.addWidget(self.lblUnits, 0, 0)
        self.layGSpecWdg.addLayout(self.layHUnits, 0, 1)

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

        self.layVMain.addWidget(frmMain)
        self.layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(self.layVMain)

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.cmbUnits.currentIndexChanged.connect(self.update_UI)
        self.butLock.clicked.connect(self._lock_freqs)
        self.cmbFRange.currentIndexChanged.connect(self._freq_range)
        self.butSort.clicked.connect(self._store_sort_flag)
        # ----------------------------------------------------------------------

        self.update_UI()  # first-time initialization

# -------------------------------------------------------------
    def _lock_freqs(self):
        """
        Lock / unlock frequency entries: The values of frequency related widgets
        are stored in normalized form (w.r.t. sampling frequency)`fb.fil[0]['f_S']`.

        When the sampling frequency changes, absolute frequencies displayed in the
        widgets change their values. Most of the time, this is the desired behaviour,
        the properties of discrete time systems or signals are usually defined
        by the normalized frequencies.

        When the effect of varying the sampling frequency is to be analyzed, the
        displayed values in the widgets can be locked by pressing the Lock button.
        After changing the sampling frequency, normalized frequencies have to be
        rescaled like `f_a *= fb.fil[0]['f_S_prev'] / fb.fil[0]['f_S']` to maintain
        the displayed value `f_a * f_S`.

        This has to be accomplished by each frequency widget.

        The setting is stored as bool in the global dict entry `fb.fil[0]['freq_locked'`
        the signal 'view_changed':'f_S' is emitted.
        """

        if self.butLock.isChecked():
            # Lock has been activated, keep displayed frequencies locked
            fb.fil[0]['freq_locked'] = True
            self.butLock.setIcon(QIcon(':/lock-locked.svg'))
        else:
            # Lock has been unlocked, scale displayed frequencies with f_S
            fb.fil[0]['freq_locked'] = False
            self.butLock.setIcon(QIcon(':/lock-unlocked.svg'))

        self.emit({'view_changed': 'f_S'})

# -------------------------------------------------------------
    def update_UI(self):
        """
        update_UI is called
        - during init
        - when the unit combobox is changed

        Set various scale factors and labels depending on the setting of the unit
        combobox.

        Update the freqSpecsRange and finally, emit 'view_changed':'f_S' signal
        """
        f_unit = str(self.cmbUnits.currentText())  # selected frequency unit
        idx = self.cmbUnits.currentIndex()  # and its index

        is_normalized_freq = f_unit in {"f_S", "f_Ny", "k"}

        self.ledF_S.setVisible(not is_normalized_freq)  # only vis. when
        self.lblF_S.setVisible(not is_normalized_freq)  # not normalized
        self.butLock.setVisible(not is_normalized_freq)
        f_S_scale = 1  # default setting for f_S scale

        if is_normalized_freq:
            # store current sampling frequency to restore it when returning to
            # unnormalized frequencies
            self.fs_old = fb.fil[0]['f_S']

            if f_unit == "f_S":  # normalized to f_S
                fb.fil[0]['f_S'] = fb.fil[0]['f_max'] = 1.
                fb.fil[0]['T_S'] = 1.
                f_label = r"$F = f\, /\, f_S = \Omega \, /\,  2 \mathrm{\pi} \; \rightarrow$"
                t_label = r"$n = t\, /\, T_S \; \rightarrow$"
            elif f_unit == "f_Ny":  # normalized to f_nyq = f_S / 2
                fb.fil[0]['f_S'] = fb.fil[0]['f_max'] = 2.
                fb.fil[0]['T_S'] = 1.
                f_label = r"$F = 2f \, / \, f_S = \Omega \, / \, \mathrm{\pi} \; \rightarrow$"
                t_label = r"$n = t\, /\, T_S \; \rightarrow$"
            else: # frequency index k,
                fb.fil[0]['f_S'] = 1.
                fb.fil[0]['T_S'] = 1.
                fb.fil[0]['f_max'] = params['N_FFT']
                f_label = r"$k \; \rightarrow$"
                t_label = r"$n\; \rightarrow$"

            self.ledF_S.setText(params['FMT'].format(fb.fil[0]['f_S']))

        else:  # Hz, kHz, ...
            # Restore sampling frequency when returning from f_S / f_Ny / k
            if fb.fil[0]['freq_specs_unit'] in {"f_S", "f_Ny", "k"}:  # previous setting normalized?
                fb.fil[0]['f_S'] = fb.fil[0]['f_max'] = self.fs_old  # yes, restore prev.
                fb.fil[0]['T_S'] = 1./self.fs_old  # settings for sampling frequency
                self.ledF_S.setText(params['FMT'].format(fb.fil[0]['f_S']))

            if f_unit == "Hz":
                f_S_scale = 1.
            elif f_unit == "kHz":
                f_S_scale = 1.e3
            elif f_unit == "MHz":
                f_S_scale = 1.e6
            elif f_unit == "GHz":
                f_S_scale = 1.e9
            else:
                logger.warning("Unknown frequency unit {0}".format(f_unit))

            f_label = r"$f$ in " + f_unit + r"$\; \rightarrow$"
            t_label = r"$t$ in " + self.t_units[idx] + r"$\; \rightarrow$"

        if f_unit == "k":
            plt_f_unit = "f_S"
        else:
            plt_f_unit = f_unit

        fb.fil[0].update({'f_S_scale': f_S_scale})  # scale factor for f_S (Hz, kHz, ...)
        fb.fil[0].update({'freq_specs_unit': f_unit})  # frequency unit
        # time and frequency unit as string e.g. for plot axis labeling
        fb.fil[0].update({"plt_fUnit": plt_f_unit})
        fb.fil[0].update({"plt_tUnit": self.t_units[idx]})
        # complete plot axis labels including unit and arrow
        fb.fil[0].update({"plt_fLabel": f_label})
        fb.fil[0].update({"plt_tLabel": t_label})

        self._freq_range(emit=False)  # update f_lim setting without emitting signal

        self.emit({'view_changed': 'f_S'})

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

    def eventFilter(self, source, event):

        """
        Filter all events generated by the QLineEdit `f_S` widget. Source and type
        of all events generated by monitored objects are passed to this eventFilter,
        evaluated and passed on to the next hierarchy level.

        - When a QLineEdit widget gains input focus (QEvent.FocusIn`), display
          the stored value from filter dict with full precision
        - When a key is pressed inside the text field, set the `spec_edited` flag
          to True.
        - When a QLineEdit widget loses input focus (QEvent.FocusOut`), store
          current value with full precision (only if `spec_edited`== True) and
          display the stored value in selected format. Emit 'view_changed':'f_S'
        """
        def _store_entry():
            """
            Update filter dictionary, set line edit entry with reduced precision
            again.
            """
            if self.spec_edited:
                fb.fil[0].update({'f_S_prev': fb.fil[0]['f_S']})
                fb.fil[0].update({'f_S': safe_eval(source.text(), fb.fil[0]['f_S'],
                                                   sign='pos')})
                fb.fil[0].update({'T_S': 1./fb.fil[0]['f_S']})
                fb.fil[0].update({'f_max': fb.fil[0]['f_S']})

                self._freq_range(emit = False)  # update plotting range
                self.emit({'view_changed': 'f_S'})
                self.spec_edited = False  # reset flag, changed entry has been saved

        if source.objectName() == 'f_S':
            if event.type() == QEvent.FocusIn:
                self.spec_edited = False
                source.setText(str(fb.fil[0]['f_S']))  # full precision
            elif event.type() == QEvent.KeyPress:
                self.spec_edited = True  # entry has been changed
                key = event.key()
                if key in {QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter}:
                    _store_entry()
                elif key == QtCore.Qt.Key_Escape:  # revert changes
                    self.spec_edited = False
                    source.setText(str(fb.fil[0]['f_S']))  # full precision

            elif event.type() == QEvent.FocusOut:
                _store_entry()
                source.setText(params['FMT'].format(fb.fil[0]['f_S']))  # reduced precision
        # Call base class method to continue normal event processing:
        return super(FreqUnits, self).eventFilter(source, event)

    # -------------------------------------------------------------
    def _freq_range(self, emit=True):
        """
        Set frequency plotting range for single-sided spectrum up to f_S/2 or f_S
        or for double-sided spectrum between -f_S/2 and f_S/2

        Emit 'view_changed':'f_range' when `emit=True`
        """
        if type(emit) == int:  # signal was emitted by combobox
            emit = True

        rangeType = qget_cmb_box(self.cmbFRange)

        fb.fil[0].update({'freqSpecsRangeType': rangeType})
        f_max = fb.fil[0]["f_max"]

        if rangeType == 'whole':
            f_lim = [0, f_max]
        elif rangeType == 'sym':
            f_lim = [-f_max/2., f_max/2.]
        else:
            f_lim = [0, f_max/2.]

        fb.fil[0]['freqSpecsRange'] = f_lim  # store settings in dict

        if emit:
            self.emit({'view_changed': 'f_range'})

    # -------------------------------------------------------------
    def load_dict(self):
        """
        Reload comboBox settings and textfields from filter dictionary
        Block signals during update of combobox / lineedit widgets
        """
        self.ledF_S.setText(params['FMT'].format(fb.fil[0]['f_S']))

        self.cmbUnits.blockSignals(True)
        idx = self.cmbUnits.findText(fb.fil[0]['freq_specs_unit'])  # get and set
        self.cmbUnits.setCurrentIndex(idx)  # index for freq. unit combo box
        self.cmbUnits.blockSignals(False)

        self.cmbFRange.blockSignals(True)
        idx = self.cmbFRange.findData(fb.fil[0]['freqSpecsRangeType'])
        self.cmbFRange.setCurrentIndex(idx)  # set frequency range
        self.cmbFRange.blockSignals(False)

        self.butSort.blockSignals(True)
        self.butSort.setChecked(fb.fil[0]['freq_specs_sort'])
        self.butSort.blockSignals(False)

# -------------------------------------------------------------
    def _store_sort_flag(self):
        """
        Store sort flag in filter dict and emit 'specs_changed':'f_sort'
        when sort button is checked.
        """
        fb.fil[0]['freq_specs_sort'] = self.butSort.isChecked()
        if self.butSort.isChecked():
            self.emit({'specs_changed': 'f_sort'})
Ejemplo n.º 10
0
class Equiripple(QWidget):

    FRMT = 'ba'  # output format of filter design routines 'zpk' / 'ba' / 'sos'
    # currently, only 'ba' is supported for equiripple routines

    info = """
**Equiripple filters**

have the steepest rate of transition between the frequency response’s passband
and stopband of all FIR filters. This comes at the expense of a constant ripple
(equiripple) :math:`A_PB` and :math:`A_SB` in both pass and stop band.

The filter-coefficients are calculated in such a way that the transfer function
minimizes the maximum error (**Minimax** design) between the desired gain and the
realized gain in the specified frequency bands using the **Remez** exchange algorithm.
The filter design algorithm is known as **Parks-McClellan** algorithm, in
Matlab (R) it is called ``firpm``.

Manual filter order design requires specifying the frequency bands (:math:`F_PB`,
:math:`f_SB` etc.), the filter order :math:`N` and weight factors :math:`W_PB`,
:math:`W_SB` etc.) for individual bands.

The minimum order and the weight factors needed to fulfill the target specifications
is estimated from frequency and amplitude specifications using Ichige's algorithm.

**Design routines:**

``scipy.signal.remez()``, ``pyfda_lib.remezord()``
    """

    sig_tx = pyqtSignal(object)

    def __init__(self):
        QWidget.__init__(self)

        self.grid_density = 16

        self.ft = 'FIR'

        self.rt_dicts = ('com', )

        self.rt_dict = {
            'COM': {
                'man': {
                    'fo': ('a', 'N'),
                    'msg':
                    ('a',
                     "<span>Enter desired filter order <b><i>N</i></b>, corner "
                     "frequencies of pass and stop band(s), <b><i>F<sub>PB</sub></i></b>"
                     "&nbsp; and <b><i>F<sub>SB</sub></i></b>&nbsp;, and relative weight "
                     "values <b><i>W&nbsp; </i></b> (1 ... 10<sup>6</sup>) to specify how well "
                     "the bands are approximated.</span>")
                },
                'min': {
                    'fo': ('d', 'N'),
                    'msg':
                    ('a',
                     "<span>Enter the maximum pass band ripple <b><i>A<sub>PB</sub></i></b>, "
                     "minimum stop band attenuation <b><i>A<sub>SB</sub></i></b> "
                     "and the corresponding corner frequencies of pass and "
                     "stop band(s), <b><i>F<sub>PB</sub></i></b>&nbsp; and "
                     "<b><i>F<sub>SB</sub></i></b> .</span>")
                }
            },
            'LP': {
                'man': {
                    'wspecs': ('a', 'W_PB', 'W_SB'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_PB', 'F_SB'),
                        'amp': ('u', 'A_PB', 'A_SB')
                    })
                },
                'min': {
                    'wspecs': ('d', 'W_PB', 'W_SB'),
                    'tspecs': ('a', {
                        'frq': ('a', 'F_PB', 'F_SB'),
                        'amp': ('a', 'A_PB', 'A_SB')
                    })
                }
            },
            'HP': {
                'man': {
                    'wspecs': ('a', 'W_SB', 'W_PB'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_SB', 'F_PB'),
                        'amp': ('u', 'A_SB', 'A_PB')
                    })
                },
                'min': {
                    'wspecs': ('d', 'W_SB', 'W_PB'),
                    'tspecs': ('a', {
                        'frq': ('a', 'F_SB', 'F_PB'),
                        'amp': ('a', 'A_SB', 'A_PB')
                    })
                }
            },
            'BP': {
                'man': {
                    'wspecs': ('a', 'W_SB', 'W_PB', 'W_SB2'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_SB', 'F_PB', 'F_PB2', 'F_SB2'),
                        'amp': ('u', 'A_SB', 'A_PB', 'A_SB2')
                    })
                },
                'min': {
                    'wspecs': ('d', 'W_SB', 'W_PB', 'W_SB2'),
                    'tspecs': ('a', {
                        'frq': ('a', 'F_SB', 'F_PB', 'F_PB2', 'F_SB2'),
                        'amp': ('a', 'A_SB', 'A_PB', 'A_SB2')
                    })
                },
            },
            'BS': {
                'man': {
                    'wspecs': ('a', 'W_PB', 'W_SB', 'W_PB2'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_PB', 'F_SB', 'F_SB2', 'F_PB2'),
                        'amp': ('u', 'A_PB', 'A_SB', 'A_PB2')
                    })
                },
                'min': {
                    'wspecs': ('d', 'W_PB', 'W_SB', 'W_PB2'),
                    'tspecs': ('a', {
                        'frq': ('a', 'F_PB', 'F_SB', 'F_SB2', 'F_PB2'),
                        'amp': ('a', 'A_PB', 'A_SB', 'A_PB2')
                    })
                }
            },
            'HIL': {
                'man': {
                    'wspecs': ('a', 'W_SB', 'W_PB', 'W_SB2'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_SB', 'F_PB', 'F_PB2', 'F_SB2'),
                        'amp': ('u', 'A_SB', 'A_PB', 'A_SB2')
                    })
                }
            },
            'DIFF': {
                'man': {
                    'wspecs': ('a', 'W_PB'),
                    'tspecs': ('u', {
                        'frq': ('a', 'F_PB'),
                        'amp': ('i', )
                    }),
                    'msg':
                    ('a',
                     "Enter the max. frequency up to where the differentiator "
                     "works.")
                }
            }
        }

        self.info_doc = []
        self.info_doc.append('remez()\n=======')
        self.info_doc.append(sig.remez.__doc__)
        self.info_doc.append('remezord()\n==========')
        self.info_doc.append(remezord.__doc__)

    #--------------------------------------------------------------------------
    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in 
        select_filter.py using the handle to the filter instance, fb.fil_inst.
        """
        self.lbl_remez_1 = QLabel("Grid Density", self)
        self.lbl_remez_1.setObjectName('wdg_lbl_remez_1')
        self.led_remez_1 = QLineEdit(self)
        self.led_remez_1.setText(str(self.grid_density))
        self.led_remez_1.setObjectName('wdg_led_remez_1')
        self.led_remez_1.setToolTip(
            "Number of frequency points for Remez algorithm. Increase the\n"
            "number to reduce frequency overshoot in the transition region.")

        self.layHWin = QHBoxLayout()
        self.layHWin.setObjectName('wdg_layGWin')
        self.layHWin.addWidget(self.lbl_remez_1)
        self.layHWin.addWidget(self.led_remez_1)
        self.layHWin.setContentsMargins(0, 0, 0, 0)
        # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits)
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layHWin)

        #----------------------------------------------------------------------
        # SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.led_remez_1.editingFinished.connect(self._update_UI)
        # fires when edited line looses focus or when RETURN is pressed
        #----------------------------------------------------------------------

        self._load_dict()  # get initial / last setting from dictionary
        self._update_UI()

    def _update_UI(self):
        """
        Update UI when line edit field is changed (here, only the text is read
        and converted to integer) and store parameter settings in filter 
        dictionary
        """
        self.grid_density = safe_eval(self.led_remez_1.text(),
                                      self.grid_density,
                                      return_type='int',
                                      sign='pos')
        self.led_remez_1.setText(str(self.grid_density))

        if not 'wdg_fil' in fb.fil[0]:
            fb.fil[0].update({'wdg_fil': {}})
        fb.fil[0]['wdg_fil'].update(
            {'equiripple': {
                'grid_density': self.grid_density
            }})

        # sig_tx -> select_filter -> filter_specs
        self.sig_tx.emit({'sender': __name__, 'filt_changed': 'equiripple'})

    def _load_dict(self):
        """
        Reload parameter(s) from filter dictionary (if they exist) and set 
        corresponding UI elements. _load_dict() is called upon initialization
        and when the filter is loaded from disk.
        """
        if 'wdg_fil' in fb.fil[0] and 'equiripple' in fb.fil[0]['wdg_fil']:
            wdg_fil_par = fb.fil[0]['wdg_fil']['equiripple']
            if 'grid_density' in wdg_fil_par:
                self.grid_density = wdg_fil_par['grid_density']
                self.led_remez_1.setText(str(self.grid_density))

    def _get_params(self, fil_dict):
        """
        Translate parameters from the passed dictionary to instance
        parameters, scaling / transforming them if needed.
        """
        self.N = fil_dict['N'] + 1  # remez algorithms expects number of taps
        # which is larger by one than the order!!
        self.F_PB = fil_dict['F_PB']
        self.F_SB = fil_dict['F_SB']
        self.F_PB2 = fil_dict['F_PB2']
        self.F_SB2 = fil_dict['F_SB2']
        # remez amplitude specs are linear (not in dBs)
        self.A_PB = fil_dict['A_PB']
        self.A_PB2 = fil_dict['A_PB2']
        self.A_SB = fil_dict['A_SB']
        self.A_SB2 = fil_dict['A_SB2']

        self.alg = 'ichige'

    def _test_N(self):
        """
        Warn the user if the calculated order is too high for a reasonable filter
        design.
        """
        if self.N > 2000:
            return qfilter_warning(self, self.N, "Equiripple")
        else:
            return True

    def _save(self, fil_dict, arg):
        """
        Convert between poles / zeros / gain, filter coefficients (polynomes)
        and second-order sections and store all available formats in the passed
        dictionary 'fil_dict'.
        """
        try:
            fil_save(fil_dict, arg, self.FRMT, __name__)
        except Exception as e:
            # catch exception due to malformatted coefficients:
            logger.error("While saving the equiripple filter design, "
                         "the following error occurred:\n{0}".format(e))
            return -1

        if str(fil_dict['fo']) == 'min':
            fil_dict['N'] = self.N - 1  # yes, update filterbroker

    def LPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.remez(self.N, [0, self.F_PB, self.F_SB, 0.5], [1, 0],
                      weight=[fil_dict['W_PB'], fil_dict['W_SB']],
                      fs=1,
                      grid_density=self.grid_density))

    def LPmin(self, fil_dict):
        self._get_params(fil_dict)
        (self.N, F, A, W) = remezord([self.F_PB, self.F_SB], [1, 0],
                                     [self.A_PB, self.A_SB],
                                     fs=1,
                                     alg=self.alg)
        if not self._test_N():
            return -1
        fil_dict['W_PB'] = W[0]
        fil_dict['W_SB'] = W[1]
        self._save(
            fil_dict,
            sig.remez(self.N,
                      F, [1, 0],
                      weight=W,
                      fs=1,
                      grid_density=self.grid_density))

    def HPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        if (self.N % 2 == 0):  # even order, use odd symmetry (type III)
            self._save(
                fil_dict,
                sig.remez(self.N, [0, self.F_SB, self.F_PB, 0.5], [0, 1],
                          weight=[fil_dict['W_SB'], fil_dict['W_PB']],
                          fs=1,
                          type='hilbert',
                          grid_density=self.grid_density))
        else:  # odd order,
            self._save(
                fil_dict,
                sig.remez(self.N, [0, self.F_SB, self.F_PB, 0.5], [0, 1],
                          weight=[fil_dict['W_SB'], fil_dict['W_PB']],
                          fs=1,
                          type='bandpass',
                          grid_density=self.grid_density))

    def HPmin(self, fil_dict):
        self._get_params(fil_dict)
        (self.N, F, A, W) = remezord([self.F_SB, self.F_PB], [0, 1],
                                     [self.A_SB, self.A_PB],
                                     fs=1,
                                     alg=self.alg)
        if not self._test_N():
            return -1
#        self.N = ceil_odd(N)  # enforce odd order
        fil_dict['W_SB'] = W[0]
        fil_dict['W_PB'] = W[1]
        if (self.N % 2 == 0):  # even order
            self._save(
                fil_dict,
                sig.remez(self.N,
                          F, [0, 1],
                          weight=W,
                          fs=1,
                          type='hilbert',
                          grid_density=self.grid_density))
        else:
            self._save(
                fil_dict,
                sig.remez(self.N,
                          F, [0, 1],
                          weight=W,
                          fs=1,
                          type='bandpass',
                          grid_density=self.grid_density))

    # For BP and BS, F_PB and F_SB have two elements each
    def BPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.remez(
                self.N, [0, self.F_SB, self.F_PB, self.F_PB2, self.F_SB2, 0.5],
                [0, 1, 0],
                weight=[fil_dict['W_SB'], fil_dict['W_PB'], fil_dict['W_SB2']],
                fs=1,
                grid_density=self.grid_density))

    def BPmin(self, fil_dict):
        self._get_params(fil_dict)
        (self.N, F, A,
         W) = remezord([self.F_SB, self.F_PB, self.F_PB2, self.F_SB2],
                       [0, 1, 0], [self.A_SB, self.A_PB, self.A_SB2],
                       fs=1,
                       alg=self.alg)
        if not self._test_N():
            return -1
        fil_dict['W_SB'] = W[0]
        fil_dict['W_PB'] = W[1]
        fil_dict['W_SB2'] = W[2]
        self._save(
            fil_dict,
            sig.remez(self.N,
                      F, [0, 1, 0],
                      weight=W,
                      fs=1,
                      grid_density=self.grid_density))

    def BSman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self.N = round_odd(self.N)  # enforce odd order
        self._save(
            fil_dict,
            sig.remez(
                self.N, [0, self.F_PB, self.F_SB, self.F_SB2, self.F_PB2, 0.5],
                [1, 0, 1],
                weight=[fil_dict['W_PB'], fil_dict['W_SB'], fil_dict['W_PB2']],
                fs=1,
                grid_density=self.grid_density))

    def BSmin(self, fil_dict):
        self._get_params(fil_dict)
        (N, F, A, W) = remezord([self.F_PB, self.F_SB, self.F_SB2, self.F_PB2],
                                [1, 0, 1], [self.A_PB, self.A_SB, self.A_PB2],
                                fs=1,
                                alg=self.alg)
        self.N = round_odd(N)  # enforce odd order
        if not self._test_N():
            return -1
        fil_dict['W_PB'] = W[0]
        fil_dict['W_SB'] = W[1]
        fil_dict['W_PB2'] = W[2]
        self._save(
            fil_dict,
            sig.remez(self.N,
                      F, [1, 0, 1],
                      weight=W,
                      fs=1,
                      grid_density=self.grid_density))

    def HILman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self._save(
            fil_dict,
            sig.remez(
                self.N, [0, self.F_SB, self.F_PB, self.F_PB2, self.F_SB2, 0.5],
                [0, 1, 0],
                weight=[fil_dict['W_SB'], fil_dict['W_PB'], fil_dict['W_SB2']],
                fs=1,
                type='hilbert',
                grid_density=self.grid_density))

    def DIFFman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self.N = ceil_even(self.N)  # enforce even order
        if self.F_PB < 0.1:
            logger.warning(
                "Bandwidth for pass band ({0}) is too low, inreasing to 0.1".
                format(self.F_PB))
            self.F_PB = 0.1
            fil_dict['F_PB'] = self.F_PB
            self.sig_tx.emit({
                'sender': __name__,
                'specs_changed': 'equiripple'
            })

        self._save(
            fil_dict,
            sig.remez(self.N, [0, self.F_PB], [np.pi * fil_dict['W_PB']],
                      fs=1,
                      type='differentiator',
                      grid_density=self.grid_density))
Ejemplo n.º 11
0
class MA(QWidget):

    FRMT = (
        'zpk', 'ba'
    )  # output format(s) of filter design routines 'zpk' / 'ba' / 'sos'

    info = """
**Moving average filters**

can only be specified via their length and the number of cascaded sections.

The minimum order to obtain a certain attenuation at a given frequency is
calculated via the si function.

Moving average filters can be implemented very efficiently in hard- and software
as they require no multiplications but only addition and subtractions. Probably
only the lowpass is really useful, as the other response types only filter out resp.
leave components at ``f_S/4`` (bandstop resp. bandpass) resp. leave components
near ``f_S/2`` (highpass).

**Design routines:**

``ma.calc_ma()``
    """
    sig_tx = pyqtSignal(object)
    from pyfda.libs.pyfda_qt_lib import emit

    def __init__(self):
        QWidget.__init__(self)

        self.delays = 12  # number of delays per stage
        self.stages = 1  # number of stages

        self.ft = 'FIR'

        self.rt_dicts = ()
        # Common data for all filter response types:
        # This data is merged with the entries for individual response types
        # (common data comes first):

        self.rt_dict = {
            'COM': {
                'man': {
                    'fo': ('d', 'N'),
                    'msg':
                    ('a',
                     "Enter desired order (= delays) <b><i>M</i></b> per stage and"
                     " the number of <b>stages</b>. Target frequencies and amplitudes"
                     " are only used for comparison, not for the design itself."
                     )
                },
                'min': {
                    'fo': ('d', 'N'),
                    'msg':
                    ('a',
                     "Enter desired attenuation <b><i>A<sub>SB</sub></i></b> at "
                     "the corner of the stop band <b><i>F<sub>SB</sub></i></b>. "
                     "Choose the number of <b>stages</b>, the minimum order <b><i>M</i></b> "
                     "per stage will be determined. Passband specs are not regarded."
                     )
                }
            },
            'LP': {
                'man': {
                    'tspecs': ('u', {
                        'frq': ('u', 'F_PB', 'F_SB'),
                        'amp': ('u', 'A_PB', 'A_SB')
                    })
                },
                'min': {
                    'tspecs': ('a', {
                        'frq': ('a', 'F_PB', 'F_SB'),
                        'amp': ('a', 'A_PB', 'A_SB')
                    })
                }
            },
            'HP': {
                'man': {
                    'tspecs': ('u', {
                        'frq': ('u', 'F_SB', 'F_PB'),
                        'amp': ('u', 'A_SB', 'A_PB')
                    })
                },
                'min': {
                    'tspecs': ('a', {
                        'frq': ('a', 'F_SB', 'F_PB'),
                        'amp': ('a', 'A_SB', 'A_PB')
                    })
                },
            },
            'BS': {
                'man': {
                    'tspecs': ('u', {
                        'frq': ('u', 'F_PB', 'F_SB', 'F_SB2', 'F_PB2'),
                        'amp': ('u', 'A_PB', 'A_SB', 'A_PB2')
                    }),
                    'msg':
                    ('a', "\nThis is not a proper band stop, it only lets pass"
                     " frequency components around DC and <i>f<sub>S</sub></i>/2."
                     " The order needs to be odd."),
                }
            },
            'BP': {
                'man': {
                    'tspecs': ('u', {
                        'frq': (
                            'u',
                            'F_SB',
                            'F_PB',
                            'F_PB2',
                            'F_SB2',
                        ),
                        'amp': ('u', 'A_SB', 'A_PB', 'A_SB2')
                    }),
                    'msg':
                    ('a', "\nThis is not a proper band pass, it only lets pass"
                     " frequency components around <i>f<sub>S</sub></i>/4."
                     " The order needs to be odd."),
                }
            },
        }

        self.info_doc = []
#        self.info_doc.append('remez()\n=======')
#        self.info_doc.append(sig.remez.__doc__)
#        self.info_doc.append('remezord()\n==========')
#        self.info_doc.append(remezord.__doc__)

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

    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in
        select_filter.py using the handle to the filter instance, fb.fil_inst.
        """

        self.lbl_delays = QLabel("<b><i>M =</ i></ b>", self)
        self.lbl_delays.setObjectName('wdg_lbl_ma_0')
        self.led_delays = QLineEdit(self)
        try:
            self.led_delays.setText(str(fb.fil[0]['N']))
        except KeyError:
            self.led_delays.setText(str(self.delays))
        self.led_delays.setObjectName('wdg_led_ma_0')
        self.led_delays.setToolTip("Set number of delays per stage")

        self.lbl_stages = QLabel("<b>Stages =</ b>", self)
        self.lbl_stages.setObjectName('wdg_lbl_ma_1')
        self.led_stages = QLineEdit(self)
        self.led_stages.setText(str(self.stages))

        self.led_stages.setObjectName('wdg_led_ma_1')
        self.led_stages.setToolTip("Set number of stages ")

        self.chk_norm = QCheckBox("Normalize", self)
        self.chk_norm.setChecked(True)
        self.chk_norm.setObjectName('wdg_chk_ma_2')
        self.chk_norm.setToolTip("Normalize to| H_max = 1|")

        self.layHWin = QHBoxLayout()
        self.layHWin.setObjectName('wdg_layGWin')
        self.layHWin.addWidget(self.lbl_delays)
        self.layHWin.addWidget(self.led_delays)
        self.layHWin.addStretch(1)
        self.layHWin.addWidget(self.lbl_stages)
        self.layHWin.addWidget(self.led_stages)
        self.layHWin.addStretch(1)
        self.layHWin.addWidget(self.chk_norm)
        self.layHWin.setContentsMargins(0, 0, 0, 0)
        # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits)
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layHWin)

        #----------------------------------------------------------------------
        # SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.led_delays.editingFinished.connect(self._update_UI)
        self.led_stages.editingFinished.connect(self._update_UI)
        # fires when edited line looses focus or when RETURN is pressed
        self.chk_norm.clicked.connect(self._update_UI)
        #----------------------------------------------------------------------

        self._load_dict()  # get initial / last setting from dictionary
        self._update_UI()

    def _load_dict(self):
        """
        Reload parameter(s) from filter dictionary (if they exist) and set
        corresponding UI elements. load_dict() is called upon initialization
        and when the filter is loaded from disk.
        """
        if 'wdg_fil' in fb.fil[0] and 'ma' in fb.fil[0]['wdg_fil']:
            wdg_fil_par = fb.fil[0]['wdg_fil']['ma']
            if 'delays' in wdg_fil_par:
                self.delays = wdg_fil_par['delays']
                self.led_delays.setText(str(self.delays))
            if 'stages' in wdg_fil_par:
                self.stages = wdg_fil_par['stages']
                self.led_stages.setText(str(self.stages))
            if 'normalize' in wdg_fil_par:
                self.chk_norm.setChecked(wdg_fil_par['normalize'])

    def _update_UI(self):
        """
        Update UI when line edit field is changed (here, only the text is read
        and converted to integer) and resize the textfields according to content.
        """
        self.delays = safe_eval(self.led_delays.text(),
                                self.delays,
                                return_type='int',
                                sign='pos')
        self.led_delays.setText(str(self.delays))
        self.stages = safe_eval(self.led_stages.text(),
                                self.stages,
                                return_type='int',
                                sign='pos')
        self.led_stages.setText(str(self.stages))

        self._store_entries()

    def _store_entries(self):
        """
        Store parameter settings in filter dictionary. Called from _update_UI()
        and _save()
        """
        if not 'wdg_fil' in fb.fil[0]:
            fb.fil[0].update({'wdg_fil': {}})
        fb.fil[0]['wdg_fil'].update({
            'ma': {
                'delays': self.delays,
                'stages': self.stages,
                'normalize': self.chk_norm.isChecked()
            }
        })
        # sig_tx -> select_filter -> filter_specs
        self.emit({'filt_changed': 'ma'})

    def _get_params(self, fil_dict):
        """
        Translate parameters from the passed dictionary to instance
        parameters, scaling / transforming them if needed.
        """
        # N is total order, L is number of taps per stage
        self.F_SB = fil_dict['F_SB']
        self.A_SB = fil_dict['A_SB']

    def _save(self, fil_dict):
        """
        Save MA-filters both in 'zpk' and 'ba' format; no conversion has to be
        performed except maybe deleting an 'sos' entry from an earlier
        filter design.
        """
        if 'zpk' in self.FRMT:
            fil_save(fil_dict, self.zpk, 'zpk', __name__, convert=False)

        if 'ba' in self.FRMT:
            fil_save(fil_dict, self.b, 'ba', __name__, convert=False)

        fil_convert(fil_dict, self.FRMT)

        # always update filter dict and LineEdit, in case the design algorithm
        # has changed the number of delays:
        fil_dict['N'] = self.delays * self.stages  # updated filter order
        self.led_delays.setText(str(self.delays))  # updated number of delays

        self._store_entries()

    def calc_ma(self, fil_dict, rt='LP'):
        """
        Calculate coefficients and P/Z for moving average filter based on
        filter length L = N + 1 and number of cascaded stages and save the
        result in the filter dictionary.
        """
        b = 1.
        k = 1.
        L = self.delays + 1

        if rt == 'LP':
            b0 = np.ones(L)  #  h[n] = {1; 1; 1; ...}
            i = np.arange(1, L)

            norm = L

        elif rt == 'HP':
            b0 = np.ones(L)
            b0[::2] = -1.  # h[n] = {1; -1; 1; -1; ...}

            i = np.arange(L)
            if (L % 2 == 0):  # even order, remove middle element
                i = np.delete(i, round(L / 2.))
            else:  # odd order, shift by 0.5 and remove middle element
                i = np.delete(i, int(L / 2.)) + 0.5

            norm = L

        elif rt == 'BP':
            # N is even, L is odd
            b0 = np.ones(L)
            b0[1::2] = 0
            b0[::4] = -1  # h[n] = {1; 0; -1; 0; 1; ... }

            L = L + 1
            i = np.arange(L)  # create N + 2 zeros around the unit circle, ...
            # ... remove first and middle element and rotate by L / 4
            i = np.delete(i, [0, L // 2]) + L / 4

            norm = np.sum(abs(b0))

        elif rt == 'BS':
            # N is even, L is odd
            b0 = np.ones(L)
            b0[1::2] = 0

            L = L + 1
            i = np.arange(
                L)  # create N + 2 zeros around the unit circle and ...
            i = np.delete(i,
                          [0, L // 2])  # ... remove first and middle element

            norm = np.sum(b0)

        if self.delays > 1000:
            if not qfilter_warning(None, self.delays * self.stages,
                                   "Moving Average"):
                return -1

        z0 = np.exp(-2j * np.pi * i / L)
        # calculate filter for multiple cascaded stages
        for i in range(self.stages):
            b = np.convolve(b0, b)
        z = np.repeat(z0, self.stages)

        # normalize filter to |H_max| = 1 if checked:
        if self.chk_norm.isChecked():
            b = b / (norm**self.stages)
            k = 1. / norm**self.stages
        p = np.zeros(len(z))

        # store in class attributes for the _save method
        self.zpk = [z, p, k]
        self.b = b
        self._save(fil_dict)

    def LPman(self, fil_dict):
        self._get_params(fil_dict)
        self.calc_ma(fil_dict, rt='LP')

    def LPmin(self, fil_dict):
        self._get_params(fil_dict)
        self.delays = int(
            np.ceil(
                1 /
                (self.A_SB**(1 / self.stages) * np.sin(self.F_SB * np.pi))))
        self.calc_ma(fil_dict, rt='LP')

    def HPman(self, fil_dict):
        self._get_params(fil_dict)
        self.calc_ma(fil_dict, rt='HP')

    def HPmin(self, fil_dict):
        self._get_params(fil_dict)
        self.delays = int(
            np.ceil(1 / (self.A_SB**(1 / self.stages) * np.sin(
                (0.5 - self.F_SB) * np.pi))))
        self.calc_ma(fil_dict, rt='HP')

    def BSman(self, fil_dict):
        self._get_params(fil_dict)
        self.delays = ceil_odd(self.delays)  # enforce odd order
        self.calc_ma(fil_dict, rt='BS')

    def BPman(self, fil_dict):
        self._get_params(fil_dict)
        self.delays = ceil_odd(self.delays)  # enforce odd order
        self.calc_ma(fil_dict, rt='BP')
Ejemplo n.º 12
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)
Ejemplo n.º 13
0
    def _construct_UI(self):
        """
        Intitialize the widget, consisting of:
        - Matplotlib widget with NavigationToolbar
        - Frame with control elements
        """
        self.chkWarnings = QCheckBox(self.tr("Verbose"), self)
        self.chkWarnings.setChecked(self.verbose)
        self.chkWarnings.setToolTip(self.tr("<span>Print messages about singular group delay"
                                    "and calculation times."))
 
        self.cmbAlgorithm = QComboBox(self)
        for t in [(self.tr("Auto"),"auto"),(self.tr("Scipy"),"scipy"), (self.tr("JOS"), "jos"),
                  (self.tr("Diff"), "diff"), (self.tr("Shpak"), "shpak")]: # text, data
            self.cmbAlgorithm.addItem(*t)
        qset_cmb_box(self.cmbAlgorithm, self.algorithm,data=True)
        self.cmbAlgorithm.setToolTip(self.tr("<span>Select algorithm for calculating "
                                     "the group delay.</span>"))
        self.cmbAlgorithm.setItemData(0, self.tr("<span>Try to find best-suited algorithm."
                                      "</span>"),Qt.ToolTipRole)
        self.cmbAlgorithm.setItemData(1, self.tr("<span>Scipy algorithm.</span>"),Qt.ToolTipRole)
        self.cmbAlgorithm.setItemData(2, self.tr("<span>J.O. Smith's algorithm.</span>"),Qt.ToolTipRole)
        self.cmbAlgorithm.setItemData(3, self.tr("<span>Textbook-style, differentiate "
                                      "the phase.</span>"),Qt.ToolTipRole)
        self.cmbAlgorithm.setItemData(4, self.tr("<span>Shpak's algorithm for SOS and other "
                                      "IIR filters.</span>"),Qt.ToolTipRole)        

        #self.chkScipy = QCheckBox("Scipy", self)
        #self.chkScipy.setChecked(False)
        #self.chkScipy.setToolTip("Use scipy group delay routine")
 
        layHControls = QHBoxLayout()
        layHControls.addStretch(10)
        layHControls.addWidget(self.chkWarnings)
        #layHControls.addWidget(self.chkScipy)
        layHControls.addWidget(self.cmbAlgorithm)
 
        # This widget encompasses all control subwidgets:
        self.frmControls = QFrame(self)
        self.frmControls.setObjectName("frmControls")
        self.frmControls.setLayout(layHControls)

        self.mplwidget = MplWidget(self)
        self.mplwidget.layVMainMpl.addWidget(self.frmControls)
        self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins'])
        self.mplwidget.mplToolbar.a_he.setEnabled(True)
        self.mplwidget.mplToolbar.a_he.info = "manual/plot_tau_g.html"
        self.setLayout(self.mplwidget.layVMainMpl)

        self.init_axes()
        self.draw() # initial drawing of tau_g

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx)
        self.cmbAlgorithm.currentIndexChanged.connect(self.draw)
Ejemplo n.º 14
0
    def _construct_UI(self):
        # ----------- ---------------------------------------------------
        # Run control widgets
        # ---------------------------------------------------------------
        self.chk_auto_run = QCheckBox("Auto", self)
        self.chk_auto_run.setObjectName("chk_auto_run")
        self.chk_auto_run.setToolTip("<span>Update response automatically when "
                                     "parameters have been changed.</span>")
        self.chk_auto_run.setChecked(True)

        self.but_run = QPushButton(self)
        self.but_run.setText("RUN")
        self.but_run.setToolTip("Run simulation")
        self.but_run.setEnabled(not self.chk_auto_run.isChecked())

        self.cmb_sim_select = QComboBox(self)
        self.cmb_sim_select.addItems(["Float","Fixpoint"])
        qset_cmb_box(self.cmb_sim_select, "Float")
        self.cmb_sim_select.setToolTip("<span>Simulate floating-point or fixpoint response."
                                 "</span>")

        self.lbl_N_points = QLabel(to_html("N", frmt='bi')  + " =", self)
        self.led_N_points = QLineEdit(self)
        self.led_N_points.setText(str(self.N))
        self.led_N_points.setToolTip("<span>Number of displayed data points. "
                                   "<i>N</i> = 0 tries to choose for you.</span>")

        self.lbl_N_start = QLabel(to_html("N_0", frmt='bi') + " =", self)
        self.led_N_start = QLineEdit(self)
        self.led_N_start.setText(str(self.N_start))
        self.led_N_start.setToolTip("<span>First point to plot.</span>")

        self.chk_fx_scale = QCheckBox("Int. scale", self)
        self.chk_fx_scale.setObjectName("chk_fx_scale")
        self.chk_fx_scale.setToolTip("<span>Display data with integer (fixpoint) scale.</span>")
        self.chk_fx_scale.setChecked(False)

        self.chk_stim_options = QCheckBox("Stim. Options", self)
        self.chk_stim_options.setObjectName("chk_stim_options")
        self.chk_stim_options.setToolTip("<span>Show stimulus options.</span>")
        self.chk_stim_options.setChecked(True)

        self.but_fft_win = QPushButton(self)
        self.but_fft_win.setText("WIN FFT")
        self.but_fft_win.setToolTip('<span> time and frequency response of FFT Window '
                                    '(can be modified in the "Frequency" tab)</span>')
        self.but_fft_win.setCheckable(True)
        self.but_fft_win.setChecked(False)

        layH_ctrl_run = QHBoxLayout()
        layH_ctrl_run.addWidget(self.but_run)
        #layH_ctrl_run.addWidget(self.lbl_sim_select)
        layH_ctrl_run.addWidget(self.cmb_sim_select)
        layH_ctrl_run.addWidget(self.chk_auto_run)
        layH_ctrl_run.addStretch(1)
        layH_ctrl_run.addWidget(self.lbl_N_start)
        layH_ctrl_run.addWidget(self.led_N_start)
        layH_ctrl_run.addStretch(1)
        layH_ctrl_run.addWidget(self.lbl_N_points)
        layH_ctrl_run.addWidget(self.led_N_points)
        layH_ctrl_run.addStretch(2)
        layH_ctrl_run.addWidget(self.chk_fx_scale)
        layH_ctrl_run.addStretch(2)
        layH_ctrl_run.addWidget(self.chk_stim_options)
        layH_ctrl_run.addStretch(2)
        layH_ctrl_run.addWidget(self.but_fft_win)
        layH_ctrl_run.addStretch(10)

        #layH_ctrl_run.setContentsMargins(*params['wdg_margins'])

        self.wdg_ctrl_run = QWidget(self)
        self.wdg_ctrl_run.setLayout(layH_ctrl_run)
        # --- end of run control ----------------------------------------

        # ----------- ---------------------------------------------------
        # Controls for time domain
        # ---------------------------------------------------------------
        plot_styles_list = ["None","Dots","Line","Line*","Stem","Stem*","Step","Step*"]

        lbl_plt_time_title = QLabel("<b>View:</b>", self)

        self.lbl_plt_time_stim = QLabel(to_html("Stimulus x", frmt='bi'), self)
        self.cmb_plt_time_stim = QComboBox(self)
        self.cmb_plt_time_stim.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_time_stim, self.plt_time_stim)
        self.cmb_plt_time_stim.setToolTip("<span>Plot style for stimulus.</span>")

        self.lbl_plt_time_stmq = QLabel(to_html("&nbsp;&nbsp;Fixp. Stim. x_Q", frmt='bi'), self)
        self.cmb_plt_time_stmq = QComboBox(self)
        self.cmb_plt_time_stmq.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_time_stmq, self.plt_time_stmq)
        self.cmb_plt_time_stmq.setToolTip("<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>")

        lbl_plt_time_resp = QLabel(to_html("&nbsp;&nbsp;Response y", frmt='bi'), self)
        self.cmb_plt_time_resp = QComboBox(self)
        self.cmb_plt_time_resp.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_time_resp, self.plt_time_resp)
        self.cmb_plt_time_resp.setToolTip("<span>Plot style for response.</span>")

        lbl_win_time = QLabel(to_html("&nbsp;&nbsp;FFT Window", frmt='bi'), self)
        self.chk_win_time = QCheckBox(self)
        self.chk_win_time.setObjectName("chk_win_time")
        self.chk_win_time.setToolTip('<span>Show FFT windowing function (can be modified in the "Frequency" tab).</span>')
        self.chk_win_time.setChecked(False)

        lbl_log_time = QLabel(to_html("dB", frmt='b'), self)
        self.chk_log_time = QCheckBox(self)
        self.chk_log_time.setObjectName("chk_log_time")
        self.chk_log_time.setToolTip("<span>Logarithmic scale for y-axis.</span>")
        self.chk_log_time.setChecked(False)

        self.lbl_log_bottom_time = QLabel(to_html("min =", frmt='bi'), self)
        self.lbl_log_bottom_time.setVisible(True)
        self.led_log_bottom_time = QLineEdit(self)
        self.led_log_bottom_time.setText(str(self.bottom_t))
        self.led_log_bottom_time.setToolTip("<span>Minimum display value for time "
                                            "and spectrogram plots with log. scale.</span>")
        self.led_log_bottom_time.setVisible(True)

        lbl_plt_time_spgr = QLabel(to_html("&nbsp;&nbsp;Spectrogram", frmt='bi'), self)
        self.cmb_plt_time_spgr = QComboBox(self)
        self.cmb_plt_time_spgr.addItems(["None", "x[n]", "x_q[n]", "y[n]"])
        qset_cmb_box(self.cmb_plt_time_spgr, self.plt_time_spgr)
        self.cmb_plt_time_spgr.setToolTip("<span>Show Spectrogram for selected signal.</span>")
        spgr_en = self.plt_time_spgr != "None"

        self.lbl_log_spgr_time = QLabel(to_html("&nbsp;dB", frmt='b'), self)
        self.lbl_log_spgr_time.setVisible(spgr_en)
        self.chk_log_spgr_time = QCheckBox(self)
        self.chk_log_spgr_time.setObjectName("chk_log_spgr")
        self.chk_log_spgr_time.setToolTip("<span>Logarithmic scale for spectrogram.</span>")
        self.chk_log_spgr_time.setChecked(True)
        self.chk_log_spgr_time.setVisible(spgr_en)

        self.lbl_nfft_spgr_time = QLabel(to_html("&nbsp;N_FFT =", frmt='bi'), self)
        self.lbl_nfft_spgr_time.setVisible(spgr_en)
        self.led_nfft_spgr_time = QLineEdit(self)
        self.led_nfft_spgr_time.setText(str(self.nfft_spgr_time))
        self.led_nfft_spgr_time.setToolTip("<span>Number of FFT points per spectrogram segment.</span>")
        self.led_nfft_spgr_time.setVisible(spgr_en)

        self.lbl_ovlp_spgr_time = QLabel(to_html("&nbsp;N_OVLP =", frmt='bi'), self)
        self.lbl_ovlp_spgr_time.setVisible(spgr_en)
        self.led_ovlp_spgr_time = QLineEdit(self)
        self.led_ovlp_spgr_time.setText(str(self.ovlp_spgr_time))
        self.led_ovlp_spgr_time.setToolTip("<span>Number of overlap data points between spectrogram segments.</span>")
        self.led_ovlp_spgr_time.setVisible(spgr_en)

        self.lbl_mode_spgr_time = QLabel(to_html("&nbsp;Mode", frmt='bi'), self)
        self.lbl_mode_spgr_time.setVisible(spgr_en)
        self.cmb_mode_spgr_time = QComboBox(self)
        spgr_modes = [("PSD","psd"), ("Mag.","magnitude"),\
                      ("Angle","angle"), ("Phase","phase")]
        for i in spgr_modes:
            self.cmb_mode_spgr_time.addItem(*i)
        qset_cmb_box(self.cmb_mode_spgr_time, self.mode_spgr_time, data=True)
        self.cmb_mode_spgr_time.setToolTip("<span>Spectrogram display mode.</span>")
        self.cmb_mode_spgr_time.setVisible(spgr_en)

        self.lbl_byfs_spgr_time = QLabel(to_html("&nbsp;per f_S", frmt='b'), self)
        self.lbl_byfs_spgr_time.setVisible(spgr_en)
        self.chk_byfs_spgr_time = QCheckBox(self)
        self.chk_byfs_spgr_time.setObjectName("chk_log_spgr")
        self.chk_byfs_spgr_time.setToolTip("<span>Display spectral density i.e. scale by f_S</span>")
        self.chk_byfs_spgr_time.setChecked(True)
        self.chk_byfs_spgr_time.setVisible(spgr_en)


        # self.lbl_colorbar_time = QLabel(to_html("&nbsp;Col.bar", frmt='b'), self)
        # self.lbl_colorbar_time.setVisible(spgr_en)
        # self.chk_colorbar_time = QCheckBox(self)
        # self.chk_colorbar_time.setObjectName("chk_colorbar_time")
        # self.chk_colorbar_time.setToolTip("<span>Enable colorbar</span>")
        # self.chk_colorbar_time.setChecked(True)
        # self.chk_colorbar_time.setVisible(spgr_en)

        self.chk_fx_limits = QCheckBox("Min/max.", self)
        self.chk_fx_limits.setObjectName("chk_fx_limits")
        self.chk_fx_limits.setToolTip("<span>Display limits of fixpoint range.</span>")
        self.chk_fx_limits.setChecked(False)

        layH_ctrl_time = QHBoxLayout()
        layH_ctrl_time.addWidget(lbl_plt_time_title)
        layH_ctrl_time.addStretch(1)
        layH_ctrl_time.addWidget(self.lbl_plt_time_stim)
        layH_ctrl_time.addWidget(self.cmb_plt_time_stim)
        #
        layH_ctrl_time.addWidget(self.lbl_plt_time_stmq)
        layH_ctrl_time.addWidget(self.cmb_plt_time_stmq)
        #
        layH_ctrl_time.addWidget(lbl_plt_time_resp)
        layH_ctrl_time.addWidget(self.cmb_plt_time_resp)
        #
        layH_ctrl_time.addWidget(lbl_win_time)
        layH_ctrl_time.addWidget(self.chk_win_time)
        layH_ctrl_time.addStretch(1)
        layH_ctrl_time.addWidget(lbl_log_time)
        layH_ctrl_time.addWidget(self.chk_log_time)
        layH_ctrl_time.addWidget(self.lbl_log_bottom_time)
        layH_ctrl_time.addWidget(self.led_log_bottom_time)
        #
        layH_ctrl_time.addStretch(1)
        #
        layH_ctrl_time.addWidget(lbl_plt_time_spgr)
        layH_ctrl_time.addWidget(self.cmb_plt_time_spgr)
        layH_ctrl_time.addWidget(self.lbl_log_spgr_time)
        layH_ctrl_time.addWidget(self.chk_log_spgr_time)
        layH_ctrl_time.addWidget(self.lbl_nfft_spgr_time)
        layH_ctrl_time.addWidget(self.led_nfft_spgr_time)
        layH_ctrl_time.addWidget(self.lbl_ovlp_spgr_time)
        layH_ctrl_time.addWidget(self.led_ovlp_spgr_time)
        layH_ctrl_time.addWidget(self.lbl_mode_spgr_time)
        layH_ctrl_time.addWidget(self.cmb_mode_spgr_time)
        layH_ctrl_time.addWidget(self.lbl_byfs_spgr_time)
        layH_ctrl_time.addWidget(self.chk_byfs_spgr_time)

        layH_ctrl_time.addStretch(2)
        layH_ctrl_time.addWidget(self.chk_fx_limits)
        layH_ctrl_time.addStretch(10)

        #layH_ctrl_time.setContentsMargins(*params['wdg_margins'])

        self.wdg_ctrl_time = QWidget(self)
        self.wdg_ctrl_time.setLayout(layH_ctrl_time)
        # ---- end time domain ------------------

        # ---------------------------------------------------------------
        # Controls for frequency domain
        # ---------------------------------------------------------------
        lbl_plt_freq_title = QLabel("<b>View:</b>", self)

        self.lbl_plt_freq_stim = QLabel(to_html("Stimulus X", frmt='bi'), self)
        self.cmb_plt_freq_stim = QComboBox(self)
        self.cmb_plt_freq_stim.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_freq_stim, self.plt_freq_stim)
        self.cmb_plt_freq_stim.setToolTip("<span>Plot style for stimulus.</span>")

        self.lbl_plt_freq_stmq = QLabel(to_html("&nbsp;Fixp. Stim. X_Q", frmt='bi'), self)
        self.cmb_plt_freq_stmq = QComboBox(self)
        self.cmb_plt_freq_stmq.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_freq_stmq, self.plt_freq_stmq)
        self.cmb_plt_freq_stmq.setToolTip("<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>")

        lbl_plt_freq_resp = QLabel(to_html("&nbsp;Response Y", frmt='bi'), self)
        self.cmb_plt_freq_resp = QComboBox(self)
        self.cmb_plt_freq_resp.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_freq_resp, self.plt_freq_resp)
        self.cmb_plt_freq_resp.setToolTip("<span>Plot style for response.</span>")

        lbl_log_freq = QLabel(to_html("dB", frmt='b'), self)
        self.chk_log_freq = QCheckBox(self)
        self.chk_log_freq.setObjectName("chk_log_freq")
        self.chk_log_freq.setToolTip("<span>Logarithmic scale for y-axis.</span>")
        self.chk_log_freq.setChecked(True)

        self.lbl_log_bottom_freq = QLabel(to_html("min =", frmt='bi'), self)
        self.lbl_log_bottom_freq.setVisible(self.chk_log_freq.isChecked())
        self.led_log_bottom_freq = QLineEdit(self)
        self.led_log_bottom_freq.setText(str(self.bottom_f))
        self.led_log_bottom_freq.setToolTip("<span>Minimum display value for log. scale.</span>")
        self.led_log_bottom_freq.setVisible(self.chk_log_freq.isChecked())

        if not self.chk_log_freq.isChecked():
            self.bottom_f = 0
            
        lbl_re_im_freq = QLabel(to_html("Re / Im", frmt='b'), self)
        self.chk_re_im_freq = QCheckBox(self)
        self.chk_re_im_freq.setObjectName("chk_re_im_freq")
        self.chk_re_im_freq.setToolTip("<span>Show real and imaginary part of spectrum</span>")
        self.chk_re_im_freq.setChecked(False)

        self.lbl_win_fft = QLabel(to_html("Window", frmt='bi'), self)
        self.cmb_win_fft = QComboBox(self)
        self.cmb_win_fft.addItems(get_window_names())
        self.cmb_win_fft.setToolTip("FFT window type.")
        qset_cmb_box(self.cmb_win_fft, self.window_name)

        self.cmb_win_fft_variant = QComboBox(self)
        self.cmb_win_fft_variant.setToolTip("FFT window variant.")
        self.cmb_win_fft_variant.setVisible(False)

        self.lblWinPar1 = QLabel("Param1")
        self.ledWinPar1 = QLineEdit(self)
        self.ledWinPar1.setText("1")
        self.ledWinPar1.setObjectName("ledWinPar1")

        self.lblWinPar2 = QLabel("Param2")
        self.ledWinPar2 = QLineEdit(self)
        self.ledWinPar2.setText("2")
        self.ledWinPar2.setObjectName("ledWinPar2")

        self.chk_Hf = QCheckBox(self)
        self.chk_Hf.setObjectName("chk_Hf")
        self.chk_Hf.setToolTip("<span>Show ideal frequency response, calculated "
                               "from the filter coefficients.</span>")
        self.chk_Hf.setChecked(False)
        self.chk_Hf_lbl = QLabel(to_html("H_id (f)", frmt="bi"), self)

        lbl_show_info_freq = QLabel(to_html("Info", frmt='b'), self)
        self.chk_show_info_freq = QCheckBox(self)
        self.chk_show_info_freq.setObjectName("chk_show_info_freq")
        self.chk_show_info_freq.setToolTip("<span>Show infos about signal power "
                                           "and window properties.</span>")
        self.chk_show_info_freq.setChecked(False)

        layH_ctrl_freq = QHBoxLayout()
        layH_ctrl_freq.addWidget(lbl_plt_freq_title)
        layH_ctrl_freq.addStretch(1)
        layH_ctrl_freq.addWidget(self.lbl_plt_freq_stim)
        layH_ctrl_freq.addWidget(self.cmb_plt_freq_stim)
        #
        layH_ctrl_freq.addWidget(self.lbl_plt_freq_stmq)
        layH_ctrl_freq.addWidget(self.cmb_plt_freq_stmq)
        #
        layH_ctrl_freq.addWidget(lbl_plt_freq_resp)
        layH_ctrl_freq.addWidget(self.cmb_plt_freq_resp)
        #
        layH_ctrl_freq.addWidget(self.chk_Hf_lbl)
        layH_ctrl_freq.addWidget(self.chk_Hf)
        layH_ctrl_freq.addStretch(1)
        layH_ctrl_freq.addWidget(lbl_log_freq)
        layH_ctrl_freq.addWidget(self.chk_log_freq)
        layH_ctrl_freq.addWidget(self.lbl_log_bottom_freq)
        layH_ctrl_freq.addWidget(self.led_log_bottom_freq)
        layH_ctrl_freq.addStretch(1)
        layH_ctrl_freq.addWidget(lbl_re_im_freq)
        layH_ctrl_freq.addWidget(self.chk_re_im_freq)
        layH_ctrl_freq.addStretch(2)
        layH_ctrl_freq.addWidget(self.lbl_win_fft)
        layH_ctrl_freq.addWidget(self.cmb_win_fft)
        layH_ctrl_freq.addWidget(self.cmb_win_fft_variant)
        layH_ctrl_freq.addWidget(self.lblWinPar1)
        layH_ctrl_freq.addWidget(self.ledWinPar1)
        layH_ctrl_freq.addWidget(self.lblWinPar2)
        layH_ctrl_freq.addWidget(self.ledWinPar2)
        layH_ctrl_freq.addStretch(1)
        layH_ctrl_freq.addWidget(lbl_show_info_freq)
        layH_ctrl_freq.addWidget(self.chk_show_info_freq)
        layH_ctrl_freq.addStretch(10)

        #layH_ctrl_freq.setContentsMargins(*params['wdg_margins'])

        self.wdg_ctrl_freq = QWidget(self)
        self.wdg_ctrl_freq.setLayout(layH_ctrl_freq)
        # ---- end Frequency Domain ------------------

        # ---------------------------------------------------------------
        # Controls for stimuli
        # ---------------------------------------------------------------

        lbl_title_stim = QLabel("<b>Stimulus:</b>", self)

        self.lblStimulus = QLabel(to_html("Type", frmt='bi'), self)
        self.cmbStimulus = QComboBox(self)
        self.cmbStimulus.addItems(["None","Impulse","Step","StepErr","Cos","Sine", "Chirp",
                                   "Triang","Saw","Rect","Comb","AM","FM","PM","Formula"])
        self.cmbStimulus.setToolTip("Stimulus type.")
        qset_cmb_box(self.cmbStimulus, self.stim)

        self.chk_stim_bl = QCheckBox("BL", self)
        self.chk_stim_bl.setToolTip("<span>The signal is bandlimited to the Nyquist frequency "
                                    "to avoid aliasing. However, it is much slower to generate "
                                    "than the regular version.</span>")
        self.chk_stim_bl.setChecked(True)
        self.chk_stim_bl.setObjectName("stim_bl")

        self.cmbChirpMethod = QComboBox(self)
        for t in [("Lin","Linear"),("Square","Quadratic"),("Log", "Logarithmic"), ("Hyper", "Hyperbolic")]:
            self.cmbChirpMethod.addItem(*t)
        qset_cmb_box(self.cmbChirpMethod, self.chirp_method, data=False)

        self.chk_scale_impz_f = QCheckBox("Scale", self)
        self.chk_scale_impz_f.setToolTip("<span>Scale the FFT of the impulse response with <i>N<sub>FFT</sub></i> "
                                    "so that it has the same magnitude as |H(f)|. DC and Noise need to be "
                                    "turned off.</span>")
        self.chk_scale_impz_f.setChecked(True)
        self.chk_scale_impz_f.setObjectName("scale_impz_f")

        self.lblDC = QLabel(to_html("DC =", frmt='bi'), self)
        self.ledDC = QLineEdit(self)
        self.ledDC.setText(str(self.DC))
        self.ledDC.setToolTip("DC Level")
        self.ledDC.setObjectName("stimDC")

        layHCmbStim = QHBoxLayout()
        layHCmbStim.addWidget(self.cmbStimulus)
        layHCmbStim.addWidget(self.chk_stim_bl)
        layHCmbStim.addWidget(self.chk_scale_impz_f)
        layHCmbStim.addWidget(self.cmbChirpMethod)

        #----------------------------------------------
        self.lblAmp1 = QLabel(to_html("&nbsp;A_1", frmt='bi') + " =", self)
        self.ledAmp1 = QLineEdit(self)
        self.ledAmp1.setText(str(self.A1))
        self.ledAmp1.setToolTip("Stimulus amplitude, complex values like 3j - 1 are allowed")
        self.ledAmp1.setObjectName("stimAmp1")

        self.lblAmp2 = QLabel(to_html("&nbsp;A_2", frmt='bi') + " =", self)
        self.ledAmp2 = QLineEdit(self)
        self.ledAmp2.setText(str(self.A2))
        self.ledAmp2.setToolTip("Stimulus amplitude 2, complex values like 3j - 1 are allowed")
        self.ledAmp2.setObjectName("stimAmp2")

        #----------------------------------------------
        self.lblPhi1 = QLabel(to_html("&nbsp;&phi;_1", frmt='bi') + " =", self)
        self.ledPhi1 = QLineEdit(self)
        self.ledPhi1.setText(str(self.phi1))
        self.ledPhi1.setToolTip("Stimulus phase")
        self.ledPhi1.setObjectName("stimPhi1")
        self.lblPhU1 = QLabel(to_html("&deg;", frmt='b'), self)

        self.lblPhi2 = QLabel(to_html("&nbsp;&phi;_2", frmt='bi') + " =", self)
        self.ledPhi2 = QLineEdit(self)
        self.ledPhi2.setText(str(self.phi2))
        self.ledPhi2.setToolTip("Stimulus phase 2")
        self.ledPhi2.setObjectName("stimPhi2")
        self.lblPhU2 = QLabel(to_html("&deg;", frmt='b'), self)

        #----------------------------------------------
        self.lblFreq1 = QLabel(to_html("&nbsp;f_1", frmt='bi') + " =", self)
        self.ledFreq1 = QLineEdit(self)
        self.ledFreq1.setText(str(self.f1))
        self.ledFreq1.setToolTip("Stimulus frequency 1")
        self.ledFreq1.setObjectName("stimFreq1")
        self.lblFreqUnit1 = QLabel("f_S", self)

        self.lblFreq2 = QLabel(to_html("&nbsp;f_2", frmt='bi') + " =", self)
        self.ledFreq2 = QLineEdit(self)
        self.ledFreq2.setText(str(self.f2))
        self.ledFreq2.setToolTip("Stimulus frequency 2")
        self.ledFreq2.setObjectName("stimFreq2")
        self.lblFreqUnit2 = QLabel("f_S", self)
        
        #----------------------------------------------
        self.lblNoise = QLabel(to_html("&nbsp;Noise", frmt='bi'), self)
        self.cmbNoise = QComboBox(self)
        self.cmbNoise.addItems(["None","Gauss","Uniform","PRBS"])
        self.cmbNoise.setToolTip("Type of additive noise.")
        qset_cmb_box(self.cmbNoise, self.noise)

        self.lblNoi = QLabel("not initialized", self)
        self.ledNoi = QLineEdit(self)
        self.ledNoi.setText(str(self.noi))
        self.ledNoi.setToolTip("not initialized")
        self.ledNoi.setObjectName("stimNoi")
        
        layGStim = QGridLayout()
        
        layGStim.addWidget(self.lblStimulus, 0, 0)
        layGStim.addWidget(self.lblDC, 1, 0)

        layGStim.addLayout(layHCmbStim, 0, 1)
        layGStim.addWidget(self.ledDC,  1, 1)

        layGStim.addWidget(self.lblAmp1, 0, 2)
        layGStim.addWidget(self.lblAmp2, 1, 2)

        layGStim.addWidget(self.ledAmp1, 0, 3)
        layGStim.addWidget(self.ledAmp2, 1, 3)
        
        layGStim.addWidget(self.lblPhi1, 0, 4)
        layGStim.addWidget(self.lblPhi2, 1, 4)

        layGStim.addWidget(self.ledPhi1, 0, 5)
        layGStim.addWidget(self.ledPhi2, 1, 5)

        layGStim.addWidget(self.lblPhU1, 0, 6)
        layGStim.addWidget(self.lblPhU2, 1, 6)

        layGStim.addWidget(self.lblFreq1, 0, 7)
        layGStim.addWidget(self.lblFreq2, 1, 7)

        layGStim.addWidget(self.ledFreq1, 0, 8)
        layGStim.addWidget(self.ledFreq2, 1, 8)

        layGStim.addWidget(self.lblFreqUnit1, 0, 9)
        layGStim.addWidget(self.lblFreqUnit2, 1, 9)
        
        layGStim.addWidget(self.lblNoise, 0, 10)
        layGStim.addWidget(self.lblNoi, 1, 10)

        layGStim.addWidget(self.cmbNoise, 0, 11)
        layGStim.addWidget(self.ledNoi, 1, 11)

        #----------------------------------------------
        self.lblStimFormula = QLabel(to_html("x =", frmt='bi'), self)
        self.ledStimFormula = QLineEdit(self)
        self.ledStimFormula.setText(str(self.stim_formula))
        self.ledStimFormula.setToolTip("<span>Enter formula for stimulus in numexpr syntax"
                                  "</span>")
        self.ledStimFormula.setObjectName("stimFormula")

        layH_ctrl_stim_formula = QHBoxLayout()
        layH_ctrl_stim_formula.addWidget(self.lblStimFormula)
        layH_ctrl_stim_formula.addWidget(self.ledStimFormula,10)

        #----------------------------------------------
        #layG_ctrl_stim = QGridLayout()
        layH_ctrl_stim_par = QHBoxLayout()

        layH_ctrl_stim_par.addLayout(layGStim)

        layV_ctrl_stim = QVBoxLayout()
        layV_ctrl_stim.addLayout(layH_ctrl_stim_par)
        layV_ctrl_stim.addLayout(layH_ctrl_stim_formula)

        layH_ctrl_stim = QHBoxLayout()
        layH_ctrl_stim.addWidget(lbl_title_stim)
        layH_ctrl_stim.addStretch(1)
        layH_ctrl_stim.addLayout(layV_ctrl_stim)
        layH_ctrl_stim.addStretch(10)

        self.wdg_ctrl_stim = QWidget(self)
        self.wdg_ctrl_stim.setLayout(layH_ctrl_stim)
        # --------- end stimuli ---------------------------------

        # frequency widgets require special handling as they are scaled with f_s
        self.ledFreq1.installEventFilter(self)
        self.ledFreq2.installEventFilter(self)

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        # --- run control ---
        self.led_N_start.editingFinished.connect(self.update_N)
        self.led_N_points.editingFinished.connect(self.update_N)

        # --- frequency control ---
        # careful! currentIndexChanged passes the current index to _update_win_fft
        self.cmb_win_fft.currentIndexChanged.connect(self._update_win_fft)
        self.ledWinPar1.editingFinished.connect(self._read_param1)
        self.ledWinPar2.editingFinished.connect(self._read_param2)

        # --- stimulus control ---
        self.chk_stim_options.clicked.connect(self._show_stim_options)

        self.chk_stim_bl.clicked.connect(self._enable_stim_widgets)
        self.cmbStimulus.currentIndexChanged.connect(self._enable_stim_widgets)

        self.cmbNoise.currentIndexChanged.connect(self._update_noi)
        self.ledNoi.editingFinished.connect(self._update_noi)
        self.ledAmp1.editingFinished.connect(self._update_amp1)
        self.ledAmp2.editingFinished.connect(self._update_amp2)
        self.ledPhi1.editingFinished.connect(self._update_phi1)
        self.ledPhi2.editingFinished.connect(self._update_phi2)
        self.cmbChirpMethod.currentIndexChanged.connect(self._update_chirp_method)
        self.ledDC.editingFinished.connect(self._update_DC)
        self.ledStimFormula.editingFinished.connect(self._update_stim_formula)
Ejemplo n.º 15
0
    def _construct_UI(self):        
        """
        Intitialize the widget, consisting of:
        - top chkbox row
        - coefficient table
        - two bottom rows with action buttons
        """
        
        self.bfont = QFont()
        self.bfont.setBold(True)
        self.bifont = QFont()
        self.bifont.setBold(True)
        self.bifont.setItalic(True)
#        q_icon_size = QSize(20, 20) # optional, size is derived from butEnable

        # ---------------------------------------------
        # UI Elements for controlling the display
        # ---------------------------------------------
        
        self.butEnable = QPushButton(self)
        self.butEnable.setIcon(QIcon(':/circle-x.svg'))
        q_icon_size = self.butEnable.iconSize() # <- set this for manual icon sizing
        self.butEnable.setIconSize(q_icon_size)
        self.butEnable.setCheckable(True)
        self.butEnable.setChecked(True)
        self.butEnable.setToolTip("<span>Show / hide poles and zeros in an editable table."
                " For high order systems, the table display might be slow.</span>")

        self.cmbPZFrmt = QComboBox(self)
        pz_formats = [('Cartesian', 'cartesian'), ('Polar (rad)', 'polar_rad'),
                      ('Polar (pi)', 'polar_pi'), ('Polar (°)', 'polar_deg')] # display text, data
        # π: u'3C0, °: u'B0, ∠: u'2220
        for pz in pz_formats:
            self.cmbPZFrmt.addItem(*pz)
        self.cmbPZFrmt.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        # self.cmbPZFrmt.setEnabled(False)
        self.cmbPZFrmt.setToolTip("<span>Set display format for poles and zeros to"
                                  " either cartesian (x + jy) or polar (r * &ang; &Omega;)."
                                  " Type 'o' for '&deg;', '&lt;' for '&ang;' and 'pi' for '&pi;'.</span>")

        self.spnDigits = QSpinBox(self)
        self.spnDigits.setRange(0,16)
        self.spnDigits.setToolTip("Number of digits to display.")
        self.lblDigits = QLabel("Digits", self)
        self.lblDigits.setFont(self.bifont)
        
        self.cmbCausal = QComboBox(self)
        causal_types = ['Causal', 'Acausal', 'Anticausal']
        for cs in causal_types:
            self.cmbCausal.addItem(cs)

        qset_cmb_box(self.cmbCausal, 'Causal')
        self.cmbCausal.setToolTip('<span>Set the system type. Not implemented yet.</span>')
        self.cmbCausal.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbCausal.setEnabled(False)
            
        layHDisplay = QHBoxLayout()
        layHDisplay.setAlignment(Qt.AlignLeft)
        layHDisplay.addWidget(self.butEnable)
        layHDisplay.addWidget(self.cmbPZFrmt)
        layHDisplay.addWidget(self.spnDigits)
        layHDisplay.addWidget(self.lblDigits)
        layHDisplay.addWidget(self.cmbCausal)
        layHDisplay.addStretch()

        # ---------------------------------------------
        # UI Elements for setting the gain
        # ---------------------------------------------
        self.lblNorm = QLabel(to_html("Normalize:", frmt='bi'), self)
        self.cmbNorm = QComboBox(self)
        self.cmbNorm.addItems(["None", "1", "Max"])
        self.cmbNorm.setToolTip("<span>Set the gain <i>k</i> so that H(f)<sub>max</sub> is "
                                "either 1 or the max. of the previous system.</span>")

        self.lblGain = QLabel(to_html("k =", frmt='bi'), self)
        self.ledGain = QLineEdit(self)
        self.ledGain.setToolTip("<span>Specify gain factor <i>k</i>"
                                " (only possible for Normalize = 'None').</span>")
        self.ledGain.setText(str(1.))
        self.ledGain.setObjectName("ledGain")
        
        layHGain = QHBoxLayout()
        layHGain.addWidget(self.lblNorm)
        layHGain.addWidget(self.cmbNorm)
        layHGain.addWidget(self.lblGain)
        layHGain.addWidget(self.ledGain)
        layHGain.addStretch()

        # ---------------------------------------------
        # UI Elements for loading / storing / manipulating cells and rows
        # ---------------------------------------------

#        self.cmbFilterType = QComboBox(self)
#        self.cmbFilterType.setObjectName("comboFilterType")
#        self.cmbFilterType.setToolTip("Select between IIR and FIR filte for manual entry.")
#        self.cmbFilterType.addItems(["FIR","IIR"])
#        self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents)


        self.butAddCells = QPushButton(self)
        self.butAddCells.setIcon(QIcon(':/row_insert_above.svg'))
        self.butAddCells.setIconSize(q_icon_size)
        self.butAddCells.setToolTip("<SPAN>Select cells to insert a new cell above each selected cell. "
                                "Use &lt;SHIFT&gt; or &lt;CTRL&gt; to select multiple cells. "
                                "When nothing is selected, add a row at the end.</SPAN>")

        self.butDelCells = QPushButton(self)
        self.butDelCells.setIcon(QIcon(':/row_delete.svg'))
        self.butDelCells.setIconSize(q_icon_size)
        self.butDelCells.setToolTip("<SPAN>Delete selected cell(s) from the table. "
                "Use &lt;SHIFT&gt; or &lt;CTRL&gt; to select multiple cells. "
                "When nothing is selected, delete the last row.</SPAN>")

        self.butSave = QPushButton(self)
        self.butSave.setIcon(QIcon(':/upload.svg'))
        self.butSave.setIconSize(q_icon_size)
        self.butSave.setToolTip("<span>Copy P/Z table to filter dict and update all plots and widgets.</span>")

        self.butLoad = QPushButton(self)
        self.butLoad.setIcon(QIcon(':/download.svg'))
        self.butLoad.setIconSize(q_icon_size)
        self.butLoad.setToolTip("Reload P/Z table from filter dict.")

        self.butClear = QPushButton(self)
        self.butClear.setIcon(QIcon(':/trash.svg'))
        self.butClear.setIconSize(q_icon_size)
        self.butClear.setToolTip("Clear all table entries.")


        self.butFromTable = QPushButton(self)
        self.butFromTable.setIconSize(q_icon_size)
        
        self.butToTable = QPushButton(self)
        self.butToTable.setIconSize(q_icon_size)

        self.but_csv_options = QPushButton(self)
        self.but_csv_options.setIcon(QIcon(':/settings.svg'))
        self.but_csv_options.setIconSize(q_icon_size)
        self.but_csv_options.setToolTip("<span>Select CSV format and whether "
                                "to copy to/from clipboard or file.</span>")
        self.but_csv_options.setCheckable(True)
        self.but_csv_options.setChecked(False)
        
        self._set_load_save_icons() # initialize icon / button settings

        layHButtonsCoeffs1 = QHBoxLayout()
#        layHButtonsCoeffs1.addWidget(self.cmbFilterType)
        layHButtonsCoeffs1.addWidget(self.butAddCells)
        layHButtonsCoeffs1.addWidget(self.butDelCells)
        layHButtonsCoeffs1.addWidget(self.butClear)
        layHButtonsCoeffs1.addWidget(self.butSave)
        layHButtonsCoeffs1.addWidget(self.butLoad)
        layHButtonsCoeffs1.addWidget(self.butFromTable)
        layHButtonsCoeffs1.addWidget(self.butToTable)
        layHButtonsCoeffs1.addWidget(self.but_csv_options)
        layHButtonsCoeffs1.addStretch()

        #-------------------------------------------------------------------
        #   Eps / set zero settings
        # ---------------------------------------------------------------------
        self.butSetZero = QPushButton("= 0", self)
        self.butSetZero.setToolTip("<span>Set selected poles / zeros = 0 with a magnitude &lt; &epsilon;. "
        "When nothing is selected, test the whole table.</span>")
        self.butSetZero.setIconSize(q_icon_size)

        lblEps = QLabel(self)
        lblEps.setText("<b><i>for &epsilon;</i> &lt;</b>")

        self.ledEps = QLineEdit(self)
        self.ledEps.setToolTip("Specify tolerance value.")

        layHButtonsCoeffs2 = QHBoxLayout()
        layHButtonsCoeffs2.addWidget(self.butSetZero)
        layHButtonsCoeffs2.addWidget(lblEps)
        layHButtonsCoeffs2.addWidget(self.ledEps)
        layHButtonsCoeffs2.addStretch()

        # ########################  Main UI Layout ############################
        # layout for frame (UI widget)
        layVMainF = QVBoxLayout()
        layVMainF.addLayout(layHDisplay)
        layVMainF.addLayout(layHGain)
        layVMainF.addLayout(layHButtonsCoeffs1)
        layVMainF.addLayout(layHButtonsCoeffs2)
        # This frame encompasses all UI elements
        frmMain = QFrame(self)
        frmMain.setLayout(layVMainF)

        layVMain = QVBoxLayout()
        layVMain.setAlignment(Qt.AlignTop) # this affects only the first widget (intended here)
        layVMain.addWidget(frmMain)
        layVMain.setContentsMargins(*params['wdg_margins'])
        self.setLayout(layVMain)
        
        #--- set initial values from dict ------------
        self.spnDigits.setValue(params['FMT_pz'])
        self.ledEps.setText(str(self.eps))
        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------        
        self.but_csv_options.clicked.connect(self._open_csv_win)
Ejemplo n.º 16
0
    def _construct_UI(self):
        """
        Construct the User Interface
        """
        self.layVMain = QVBoxLayout() # Widget main layout

        f_units = ['k','f_S', 'f_Ny', 'Hz', 'kHz', 'MHz', 'GHz']
        self.t_units = ['', 'T_S', 'T_S', 's', 'ms', r'$\mu$s', 'ns']

        bfont = QFont()
        bfont.setBold(True)

        self.lblUnits = QLabel(self)
        self.lblUnits.setText("Freq. Unit")
        self.lblUnits.setFont(bfont)

        self.fs_old = fb.fil[0]['f_S']  # store current sampling frequency

        self.lblF_S = QLabel(self)
        self.lblF_S.setText(to_html("f_S =", frmt='bi'))

        self.ledF_S = QLineEdit()
        self.ledF_S.setText(str(fb.fil[0]["f_S"]))
        self.ledF_S.setObjectName("f_S")
        self.ledF_S.installEventFilter(self)  # filter events

        self.butLock = QToolButton(self)
        self.butLock.setIcon(QIcon(':/lock-unlocked.svg'))
        self.butLock.setCheckable(True)
        self.butLock.setChecked(False)
        self.butLock.setToolTip(
            "<span><b>Unlocked:</b> When f_S is changed, all frequency related "
            "widgets are updated, normalized frequencies stay the same.<br />"
            "<b>Locked:</b> When f_S is changed, displayed absolute frequency "
            "values don't change but normalized frequencies do.</span>")
        # self.butLock.setStyleSheet("QToolButton:checked {font-weight:bold}")

        layHF_S = QHBoxLayout()
        layHF_S.addWidget(self.ledF_S)
        layHF_S.addWidget(self.butLock)

        self.cmbUnits = QComboBox(self)
        self.cmbUnits.setObjectName("cmbUnits")
        self.cmbUnits.addItems(f_units)
        self.cmbUnits.setToolTip(
        'Select whether frequencies are specified w.r.t. \n'
        'the sampling frequency "f_S", to the Nyquist frequency \n'
        'f_Ny = f_S/2 or as absolute values. "k" specifies frequencies w.r.t. f_S '
        'but plots graphs over the frequency index k.')
        self.cmbUnits.setCurrentIndex(1)
#        self.cmbUnits.setItemData(0, (0,QColor("#FF333D"),Qt.BackgroundColorRole))#
#        self.cmbUnits.setItemData(0, (QFont('Verdana', bold=True), Qt.FontRole)

        fRanges = [("0...½", "half"), ("0...1","whole"), ("-½...½", "sym")]
        self.cmbFRange = QComboBox(self)
        self.cmbFRange.setObjectName("cmbFRange")
        for f in fRanges:
            self.cmbFRange.addItem(f[0],f[1])
        self.cmbFRange.setToolTip("Select frequency range (whole or half).")
        self.cmbFRange.setCurrentIndex(0)

        # Combobox resizes with longest entry
        self.cmbUnits.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbFRange.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.butSort = QToolButton(self)
        self.butSort.setText("Sort")
        self.butSort.setIcon(QIcon(':/sort-ascending.svg'))
        #self.butDelCells.setIconSize(q_icon_size)
        self.butSort.setCheckable(True)
        self.butSort.setChecked(True)
        self.butSort.setToolTip("Sort frequencies in ascending order when pushed.")
        self.butSort.setStyleSheet("QToolButton:checked {font-weight:bold}")

        self.layHUnits = QHBoxLayout()
        self.layHUnits.addWidget(self.cmbUnits)
        self.layHUnits.addWidget(self.cmbFRange)
        self.layHUnits.addWidget(self.butSort)

        # Create a gridLayout consisting of QLabel and QLineEdit fields
        # for setting f_S, the units and the actual frequency specs:
        self.layGSpecWdg = QGridLayout()  # sublayout for spec fields
        self.layGSpecWdg.addWidget(self.lblF_S, 1, 0)
        # self.layGSpecWdg.addWidget(self.ledF_S,1,1)
        self.layGSpecWdg.addLayout(layHF_S, 1, 1)
        self.layGSpecWdg.addWidget(self.lblUnits, 0, 0)
        self.layGSpecWdg.addLayout(self.layHUnits, 0, 1)

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

        self.layVMain.addWidget(frmMain)
        self.layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(self.layVMain)

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.cmbUnits.currentIndexChanged.connect(self.update_UI)
        self.butLock.clicked.connect(self._lock_freqs)
        self.cmbFRange.currentIndexChanged.connect(self._freq_range)
        self.butSort.clicked.connect(self._store_sort_flag)
        # ----------------------------------------------------------------------

        self.update_UI()  # first-time initialization
Ejemplo n.º 17
0
    def _construct_UI(self):
        # =====================================================================
        # Controls for stimuli
        # =====================================================================
        self.cmbStimulus = QComboBox(self)
        qcmb_box_populate(self.cmbStimulus, self.cmb_stim_items,
                          self.cmb_stim_item)

        self.lblStimPar1 = QLabel(to_html("&alpha; =", frmt='b'), self)
        self.ledStimPar1 = QLineEdit(self)
        self.ledStimPar1.setText("0.5")
        self.ledStimPar1.setToolTip("Duty Cycle, 0 ... 1")
        self.ledStimPar1.setObjectName("ledStimPar1")

        self.but_stim_bl = QPushButton(self)
        self.but_stim_bl.setText("BL")
        self.but_stim_bl.setToolTip(
            "<span>Bandlimit the signal to the Nyquist "
            "frequency to avoid aliasing. However, this is much slower "
            "to calculate especially for a large number of points.</span>")
        self.but_stim_bl.setMaximumWidth(qtext_width(text="BL "))
        self.but_stim_bl.setCheckable(True)
        self.but_stim_bl.setChecked(True)
        self.but_stim_bl.setObjectName("stim_bl")

        # -------------------------------------
        self.cmbChirpType = QComboBox(self)
        qcmb_box_populate(self.cmbChirpType, self.cmb_stim_chirp_items,
                          self.chirp_type)

        self.cmbImpulseType = QComboBox(self)
        qcmb_box_populate(self.cmbImpulseType, self.cmb_stim_impulse_items,
                          self.impulse_type)

        self.cmbSinusoidType = QComboBox(self)
        qcmb_box_populate(self.cmbSinusoidType, self.cmb_stim_sinusoid_items,
                          self.sinusoid_type)

        self.cmbPeriodicType = QComboBox(self)
        qcmb_box_populate(self.cmbPeriodicType, self.cmb_stim_periodic_items,
                          self.cmb_stim_periodic_item)

        self.cmbModulationType = QComboBox(self)
        for t in [("AM", "am"), ("PM / FM", "pmfm")]:  # text, data
            self.cmbModulationType.addItem(*t)
        qset_cmb_box(self.cmbModulationType, self.modulation_type, data=True)

        # -------------------------------------
        self.chk_step_err = QPushButton("Error", self)
        self.chk_step_err.setToolTip(
            "<span>Display the step response error.</span>")
        self.chk_step_err.setMaximumWidth(qtext_width(text="Error "))
        self.chk_step_err.setCheckable(True)
        self.chk_step_err.setChecked(False)
        self.chk_step_err.setObjectName("stim_step_err")

        layHCmbStim = QHBoxLayout()
        layHCmbStim.addWidget(self.cmbStimulus)
        layHCmbStim.addWidget(self.cmbImpulseType)
        layHCmbStim.addWidget(self.cmbSinusoidType)
        layHCmbStim.addWidget(self.cmbChirpType)
        layHCmbStim.addWidget(self.cmbPeriodicType)
        layHCmbStim.addWidget(self.cmbModulationType)
        layHCmbStim.addWidget(self.but_stim_bl)
        layHCmbStim.addWidget(self.lblStimPar1)
        layHCmbStim.addWidget(self.ledStimPar1)
        layHCmbStim.addWidget(self.chk_step_err)

        self.lblDC = QLabel(to_html("DC =", frmt='bi'), self)
        self.ledDC = QLineEdit(self)
        self.ledDC.setText(str(self.DC))
        self.ledDC.setToolTip("DC Level")
        self.ledDC.setObjectName("stimDC")

        layHStimDC = QHBoxLayout()
        layHStimDC.addWidget(self.lblDC)
        layHStimDC.addWidget(self.ledDC)

        # ======================================================================
        self.lblAmp1 = QLabel(to_html("&nbsp;A_1", frmt='bi') + " =", self)
        self.ledAmp1 = QLineEdit(self)
        self.ledAmp1.setText(str(self.A1))
        self.ledAmp1.setToolTip(
            "Stimulus amplitude, complex values like 3j - 1 are allowed")
        self.ledAmp1.setObjectName("stimAmp1")

        self.lblAmp2 = QLabel(to_html("&nbsp;A_2", frmt='bi') + " =", self)
        self.ledAmp2 = QLineEdit(self)
        self.ledAmp2.setText(str(self.A2))
        self.ledAmp2.setToolTip(
            "Stimulus amplitude 2, complex values like 3j - 1 are allowed")
        self.ledAmp2.setObjectName("stimAmp2")
        # ----------------------------------------------
        self.lblPhi1 = QLabel(to_html("&nbsp;&phi;_1", frmt='bi') + " =", self)
        self.ledPhi1 = QLineEdit(self)
        self.ledPhi1.setText(str(self.phi1))
        self.ledPhi1.setToolTip("Stimulus phase")
        self.ledPhi1.setObjectName("stimPhi1")
        self.lblPhU1 = QLabel(to_html("&deg;", frmt='b'), self)

        self.lblPhi2 = QLabel(to_html("&nbsp;&phi;_2", frmt='bi') + " =", self)
        self.ledPhi2 = QLineEdit(self)
        self.ledPhi2.setText(str(self.phi2))
        self.ledPhi2.setToolTip("Stimulus phase 2")
        self.ledPhi2.setObjectName("stimPhi2")
        self.lblPhU2 = QLabel(to_html("&deg;", frmt='b'), self)
        # ----------------------------------------------
        self.lbl_T1 = QLabel(to_html("&nbsp;T_1", frmt='bi') + " =", self)
        self.led_T1 = QLineEdit(self)
        self.led_T1.setText(str(self.T1))
        self.led_T1.setToolTip("Time shift")
        self.led_T1.setObjectName("stimT1")
        self.lbl_TU1 = QLabel(to_html("T_S", frmt='b'), self)

        self.lbl_T2 = QLabel(to_html("&nbsp;T_2", frmt='bi') + " =", self)
        self.led_T2 = QLineEdit(self)
        self.led_T2.setText(str(self.T2))
        self.led_T2.setToolTip("Time shift 2")
        self.led_T2.setObjectName("stimT2")
        self.lbl_TU2 = QLabel(to_html("T_S", frmt='b'), self)
        # ---------------------------------------------
        self.lbl_TW1 = QLabel(
            to_html("&nbsp;&Delta;T_1", frmt='bi') + " =", self)
        self.led_TW1 = QLineEdit(self)
        self.led_TW1.setText(str(self.TW1))
        self.led_TW1.setToolTip("Time width")
        self.led_TW1.setObjectName("stimTW1")
        self.lbl_TWU1 = QLabel(to_html("T_S", frmt='b'), self)

        self.lbl_TW2 = QLabel(
            to_html("&nbsp;&Delta;T_2", frmt='bi') + " =", self)
        self.led_TW2 = QLineEdit(self)
        self.led_TW2.setText(str(self.TW2))
        self.led_TW2.setToolTip("Time width 2")
        self.led_TW2.setObjectName("stimTW2")
        self.lbl_TWU2 = QLabel(to_html("T_S", frmt='b'), self)
        # ----------------------------------------------
        self.txtFreq1_f = to_html("&nbsp;f_1", frmt='bi') + " ="
        self.txtFreq1_k = to_html("&nbsp;k_1", frmt='bi') + " ="
        self.lblFreq1 = QLabel(self.txtFreq1_f, self)
        self.ledFreq1 = QLineEdit(self)
        self.ledFreq1.setText(str(self.f1))
        self.ledFreq1.setToolTip("Stimulus frequency")
        self.ledFreq1.setObjectName("stimFreq1")
        self.lblFreqUnit1 = QLabel("f_S", self)

        self.txtFreq2_f = to_html("&nbsp;f_2", frmt='bi') + " ="
        self.txtFreq2_k = to_html("&nbsp;k_2", frmt='bi') + " ="
        self.lblFreq2 = QLabel(self.txtFreq2_f, self)
        self.ledFreq2 = QLineEdit(self)
        self.ledFreq2.setText(str(self.f2))
        self.ledFreq2.setToolTip("Stimulus frequency 2")
        self.ledFreq2.setObjectName("stimFreq2")
        self.lblFreqUnit2 = QLabel("f_S", self)
        # ----------------------------------------------
        self.lbl_BW1 = QLabel(
            to_html(self.tr("&nbsp;BW_1"), frmt='bi') + " =", self)
        self.led_BW1 = QLineEdit(self)
        self.led_BW1.setText(str(self.BW1))
        self.led_BW1.setToolTip(self.tr("Relative bandwidth"))
        self.led_BW1.setObjectName("stimBW1")

        self.lbl_BW2 = QLabel(
            to_html(self.tr("&nbsp;BW_2"), frmt='bi') + " =", self)
        self.led_BW2 = QLineEdit(self)
        self.led_BW2.setText(str(self.BW2))
        self.led_BW2.setToolTip(self.tr("Relative bandwidth 2"))
        self.led_BW2.setObjectName("stimBW2")
        # ----------------------------------------------
        self.lblNoise = QLabel(to_html("&nbsp;Noise", frmt='bi'), self)
        self.cmbNoise = QComboBox(self)
        qcmb_box_populate(self.cmbNoise, self.cmb_stim_noise_items, self.noise)

        self.lblNoi = QLabel("not initialized", self)
        self.ledNoi = QLineEdit(self)
        self.ledNoi.setText(str(self.noi))
        self.ledNoi.setToolTip("not initialized")
        self.ledNoi.setObjectName("stimNoi")

        layGStim = QGridLayout()

        layGStim.addLayout(layHCmbStim, 0, 1)
        layGStim.addLayout(layHStimDC, 1, 1)

        layGStim.addWidget(self.lblAmp1, 0, 2)
        layGStim.addWidget(self.lblAmp2, 1, 2)

        layGStim.addWidget(self.ledAmp1, 0, 3)
        layGStim.addWidget(self.ledAmp2, 1, 3)

        layGStim.addWidget(self.lblPhi1, 0, 4)
        layGStim.addWidget(self.lblPhi2, 1, 4)

        layGStim.addWidget(self.ledPhi1, 0, 5)
        layGStim.addWidget(self.ledPhi2, 1, 5)

        layGStim.addWidget(self.lblPhU1, 0, 6)
        layGStim.addWidget(self.lblPhU2, 1, 6)

        layGStim.addWidget(self.lbl_T1, 0, 7)
        layGStim.addWidget(self.lbl_T2, 1, 7)

        layGStim.addWidget(self.led_T1, 0, 8)
        layGStim.addWidget(self.led_T2, 1, 8)

        layGStim.addWidget(self.lbl_TU1, 0, 9)
        layGStim.addWidget(self.lbl_TU2, 1, 9)

        layGStim.addWidget(self.lbl_TW1, 0, 10)
        layGStim.addWidget(self.lbl_TW2, 1, 10)

        layGStim.addWidget(self.led_TW1, 0, 11)
        layGStim.addWidget(self.led_TW2, 1, 11)

        layGStim.addWidget(self.lbl_TWU1, 0, 12)
        layGStim.addWidget(self.lbl_TWU2, 1, 12)

        layGStim.addWidget(self.lblFreq1, 0, 13)
        layGStim.addWidget(self.lblFreq2, 1, 13)

        layGStim.addWidget(self.ledFreq1, 0, 14)
        layGStim.addWidget(self.ledFreq2, 1, 14)

        layGStim.addWidget(self.lblFreqUnit1, 0, 15)
        layGStim.addWidget(self.lblFreqUnit2, 1, 15)

        layGStim.addWidget(self.lbl_BW1, 0, 16)
        layGStim.addWidget(self.lbl_BW2, 1, 16)

        layGStim.addWidget(self.led_BW1, 0, 17)
        layGStim.addWidget(self.led_BW2, 1, 17)

        layGStim.addWidget(self.lblNoise, 0, 18)
        layGStim.addWidget(self.lblNoi, 1, 18)

        layGStim.addWidget(self.cmbNoise, 0, 19)
        layGStim.addWidget(self.ledNoi, 1, 19)

        # ----------------------------------------------
        self.lblStimFormula = QLabel(to_html("x =", frmt='bi'), self)
        self.ledStimFormula = QLineEdit(self)
        self.ledStimFormula.setText(str(self.stim_formula))
        self.ledStimFormula.setToolTip(
            "<span>Enter formula for stimulus in numexpr syntax.</span>")
        self.ledStimFormula.setObjectName("stimFormula")

        layH_stim_formula = QHBoxLayout()
        layH_stim_formula.addWidget(self.lblStimFormula)
        layH_stim_formula.addWidget(self.ledStimFormula, 10)

        # ----------------------------------------------------------------------
        # Main Widget
        # ----------------------------------------------------------------------
        layH_stim_par = QHBoxLayout()
        layH_stim_par.addLayout(layGStim)

        layV_stim = QVBoxLayout()
        layV_stim.addLayout(layH_stim_par)
        layV_stim.addLayout(layH_stim_formula)

        layH_stim = QHBoxLayout()
        layH_stim.addLayout(layV_stim)
        layH_stim.addStretch(10)

        self.wdg_stim = QWidget(self)
        self.wdg_stim.setLayout(layH_stim)
        self.wdg_stim.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)

        # ----------------------------------------------------------------------
        # Event Filter
        # ----------------------------------------------------------------------
        # frequency related widgets are scaled with f_s, requiring special handling
        self.ledFreq1.installEventFilter(self)
        self.ledFreq2.installEventFilter(self)
        self.led_T1.installEventFilter(self)
        self.led_T2.installEventFilter(self)
        self.led_TW1.installEventFilter(self)
        self.led_TW2.installEventFilter(self)

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        # --- stimulus control ---
        self.but_stim_bl.clicked.connect(self._enable_stim_widgets)
        self.chk_step_err.clicked.connect(self._enable_stim_widgets)
        self.cmbStimulus.currentIndexChanged.connect(self._enable_stim_widgets)

        self.cmbNoise.currentIndexChanged.connect(self._update_noi)
        self.ledNoi.editingFinished.connect(self._update_noi)
        self.ledAmp1.editingFinished.connect(self._update_amp1)
        self.ledAmp2.editingFinished.connect(self._update_amp2)
        self.ledPhi1.editingFinished.connect(self._update_phi1)
        self.ledPhi2.editingFinished.connect(self._update_phi2)
        self.led_BW1.editingFinished.connect(self._update_BW1)
        self.led_BW2.editingFinished.connect(self._update_BW2)

        self.cmbImpulseType.currentIndexChanged.connect(
            self._update_impulse_type)
        self.cmbSinusoidType.currentIndexChanged.connect(
            self._update_sinusoid_type)
        self.cmbChirpType.currentIndexChanged.connect(self._update_chirp_type)
        self.cmbPeriodicType.currentIndexChanged.connect(
            self._update_periodic_type)
        self.cmbModulationType.currentIndexChanged.connect(
            self._update_modulation_type)

        self.ledDC.editingFinished.connect(self._update_DC)
        self.ledStimFormula.editingFinished.connect(self._update_stim_formula)
        self.ledStimPar1.editingFinished.connect(self._update_stim_par1)
Ejemplo n.º 18
0
    def _construct_UI(self, **kwargs):
        """ 
        Construct widget from quantization dict, individual settings and
        the default dict below """

        # default settings
        dict_ui = {'id':'ui_w', 'label':'WI.WF', 'lbl_sep':'.', 'max_led_width':30,
                   'WI':0, 'WI_len':2, 'tip_WI':'Number of integer bits',
                   'WF':15,'WF_len':2, 'tip_WF':'Number of fractional bits',
                   'enabled':True, 'visible':True, 'fractional':True,
                   'combo_visible':False, 'combo_items':['auto', 'full', 'man'],
                   'tip_combo':'Calculate Acc. width.',                  
                   'lock_visible':False, 'tip_lock':'Lock input/output quantization.'
                   } #: default values

        if self.q_dict:
            dict_ui.update(self.q_dict)
            
        for k, v in kwargs.items():
            if k not in dict_ui:
                logger.warning("Unknown key {0}".format(k))
            else:
                dict_ui.update({k:v})

        self.id = dict_ui['id']

        if not dict_ui['fractional']:
            dict_ui['WF'] = 0
        self.WI = dict_ui['WI']
        self.WF = dict_ui['WF']
        self.W = int(self.WI + self.WF + 1)
        if self.q_dict:
            self.q_dict.update({'WI':self.WI, 'WF':self.WF, 'W':self.W})
        else:
            self.q_dict = {'WI':self.WI, 'WF':self.WF, 'W':self.W}

        lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self)

        self.cmbW = QComboBox(self)
        self.cmbW.addItems(dict_ui['combo_items'])
        self.cmbW.setVisible(dict_ui['combo_visible'])
        self.cmbW.setToolTip(dict_ui['tip_combo'])
        self.cmbW.setObjectName("cmbW")
        
        self.butLock = QPushButton(self)
        self.butLock.setCheckable(True)
        self.butLock.setChecked(False)
        self.butLock.setVisible(dict_ui['lock_visible'])
        self.butLock.setToolTip(dict_ui['tip_lock'])

        self.ledWI = QLineEdit(self)
        self.ledWI.setToolTip(dict_ui['tip_WI'])
        self.ledWI.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits
        self.ledWI.setFixedWidth(dict_ui['max_led_width']) # width of lineedit in points(?)
        self.ledWI.setObjectName("WI")

        lblDot = QLabel(dict_ui['lbl_sep'], self)
        lblDot.setVisible(dict_ui['fractional'])

        self.ledWF = QLineEdit(self)
        self.ledWF.setToolTip(dict_ui['tip_WF'])
        self.ledWF.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits
        self.ledWF.setFixedWidth(dict_ui['max_led_width']) # width of lineedit in points(?)
        self.ledWF.setVisible(dict_ui['fractional'])
        self.ledWF.setObjectName("WF")
        
        layH = QHBoxLayout()
        layH.addWidget(lblW)
        layH.addStretch()
        layH.addWidget(self.cmbW)
        layH.addWidget(self.butLock)
        layH.addWidget(self.ledWI)
        layH.addWidget(lblDot)
        layH.addWidget(self.ledWF)
        layH.setContentsMargins(0,0,0,0)

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

        layVMain = QVBoxLayout() # Widget main layout
        layVMain.addWidget(frmMain)
        layVMain.setContentsMargins(0,5,0,0)#*params['wdg_margins'])

        self.setLayout(layVMain)
        
        #----------------------------------------------------------------------
        # INITIAL SETTINGS
        #----------------------------------------------------------------------
        self.ledWI.setText(qstr(dict_ui['WI']))
        self.ledWF.setText(qstr(dict_ui['WF']))

        frmMain.setEnabled(dict_ui['enabled'])
        frmMain.setVisible(dict_ui['visible'])

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.ledWI.editingFinished.connect(self.ui2dict)
        self.ledWF.editingFinished.connect(self.ui2dict)
        self.butLock.clicked.connect(self.butLock_clicked)
        self.cmbW.currentIndexChanged.connect(self.ui2dict)

        # initialize button icon        
        self.butLock_clicked(self.butLock.isChecked())
Ejemplo n.º 19
0
    def _construct_UI(self) -> None:
        """
        Intitialize the main GUI, consisting of:

        - A combo box to select the filter topology and an image of the topology

        - The input quantizer

        - The UI of the fixpoint filter widget

        - Simulation and export buttons
        """
# ------------------------------------------------------------------------------
        # Define frame and layout for the dynamically updated filter widget
        # The actual filter widget is instantiated in self.set_fixp_widget() later on

        self.layH_fx_wdg = QHBoxLayout()
        # self.layH_fx_wdg.setContentsMargins(*params['wdg_margins'])
        frmHDL_wdg = QFrame(self)
        frmHDL_wdg.setLayout(self.layH_fx_wdg)
        # frmHDL_wdg.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

# ------------------------------------------------------------------------------
#       Initialize fixpoint filter combobox, title and description
# ------------------------------------------------------------------------------
        self.cmb_fx_wdg = QComboBox(self)
        self.cmb_fx_wdg.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.lblTitle = QLabel("not set", self)
        self.lblTitle.setWordWrap(True)
        self.lblTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        layHTitle = QHBoxLayout()
        layHTitle.addWidget(self.cmb_fx_wdg)
        layHTitle.addWidget(self.lblTitle)

        self.frmTitle = QFrame(self)
        self.frmTitle.setLayout(layHTitle)
        self.frmTitle.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Input and Output Quantizer
# ------------------------------------------------------------------------------
#       - instantiate widgets for input and output quantizer
#       - pass the quantization (sub-?) dictionary to the constructor
# ------------------------------------------------------------------------------

        self.wdg_w_input = UI_W(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='w_input', label='', lock_visible=True)
        self.wdg_w_input.sig_tx.connect(self.process_sig_rx_local)

        cmb_q = ['round', 'floor', 'fix']

        self.wdg_w_output = UI_W(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='w_output', label='')
        self.wdg_w_output.sig_tx.connect(self.process_sig_rx_local)

        self.wdg_q_output = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='q_output',
                                 label='Output Format <i>Q<sub>Y&nbsp;</sub></i>:',
                                 cmb_q=cmb_q, cmb_ov=['wrap', 'sat'])
        self.wdg_q_output.sig_tx.connect(self.sig_rx_local)

        if HAS_DS:
            cmb_q.append('dsm')
        self.wdg_q_input = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='q_input',
                                label='Input Format <i>Q<sub>X&nbsp;</sub></i>:',
                                cmb_q=cmb_q)
        self.wdg_q_input.sig_tx.connect(self.sig_rx_local)

        # Layout and frame for input quantization
        layVQiWdg = QVBoxLayout()
        layVQiWdg.addWidget(self.wdg_q_input)
        layVQiWdg.addWidget(self.wdg_w_input)
        frmQiWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQiWdg.setLayout(layVQiWdg)
        frmQiWdg.setContentsMargins(*params['wdg_margins'])

        # Layout and frame for output quantization
        layVQoWdg = QVBoxLayout()
        layVQoWdg.addWidget(self.wdg_q_output)
        layVQoWdg.addWidget(self.wdg_w_output)
        frmQoWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQoWdg.setLayout(layVQoWdg)
        frmQoWdg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Dynamically updated image of filter topology (label as placeholder)
# ------------------------------------------------------------------------------
        # allow setting background color
        # lbl_fixp_img_palette = QPalette()
        # lbl_fixp_img_palette.setColor(QPalette(window, Qt: white))
        # lbl_fixp_img_palette.setBrush(self.backgroundRole(), QColor(150, 0, 0))
        # lbl_fixp_img_palette.setColor(QPalette: WindowText, Qt: blue)

        self.lbl_fixp_img = QLabel("img not set", self)
        self.lbl_fixp_img.setAutoFillBackground(True)
        # self.lbl_fixp_img.setPalette(lbl_fixp_img_palette)
        # self.lbl_fixp_img.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.embed_fixp_img(self.no_fx_filter_img)
        layHImg = QHBoxLayout()
        layHImg.setContentsMargins(0, 0, 0, 0)
        layHImg.addWidget(self.lbl_fixp_img)  # , Qt.AlignCenter)
        self.frmImg = QFrame(self)
        self.frmImg.setLayout(layHImg)
        self.frmImg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Simulation and export Buttons
# ------------------------------------------------------------------------------
        self.butExportHDL = QPushButton(self)
        self.butExportHDL.setToolTip(
            "Create Verilog or VHDL netlist for fixpoint filter.")
        self.butExportHDL.setText("Create HDL")

        self.butSimFx = QPushButton(self)
        self.butSimFx.setToolTip("Start fixpoint simulation.")
        self.butSimFx.setText("Sim. FX")

        self.layHHdlBtns = QHBoxLayout()
        self.layHHdlBtns.addWidget(self.butSimFx)
        self.layHHdlBtns.addWidget(self.butExportHDL)
        # This frame encompasses the HDL buttons sim and convert
        frmHdlBtns = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmHdlBtns.setLayout(self.layHHdlBtns)
        frmHdlBtns.setContentsMargins(*params['wdg_margins'])

# -------------------------------------------------------------------
#       Top level layout
# -------------------------------------------------------------------
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(frmHDL_wdg)
        splitter.addWidget(frmQoWdg)
        splitter.addWidget(self.frmImg)

        # 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, 3000, 5000])

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmTitle)
        layVMain.addWidget(frmHdlBtns)
        layVMain.addWidget(frmQiWdg)
        layVMain.addWidget(splitter)
        layVMain.addStretch()
        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        self.sig_rx_local.connect(self.process_sig_rx_local)
        # dynamic connection in `self._update_fixp_widget()`:
        # -----
        # if hasattr(self.fx_filt_ui, "sig_rx"):
        #     self.sig_rx.connect(self.fx_filt_ui.sig_rx)
        # if hasattr(self.fx_filt_ui, "sig_tx"):
        #     self.fx_filt_ui.sig_tx.connect(self.sig_rx_local)
        # ----
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.cmb_fx_wdg.currentIndexChanged.connect(self._update_fixp_widget)
        self.butExportHDL.clicked.connect(self.exportHDL)
        self.butSimFx.clicked.connect(lambda x: self.emit({'fx_sim': 'start'}))
Ejemplo n.º 20
0
    def _construct_UI(self, **kwargs):
        """ Construct widget """

        dict_ui = {'id':'ui_q', 'label':'',
                   'label_q':'Quant.', 'tip_q':'Select the kind of quantization.',
                   'cmb_q':['round', 'fix', 'floor'], 'cur_q':'round',
                   'label_ov':'Ovfl.', 'tip_ov':'Select overflow behaviour.',
                   'cmb_ov':['wrap', 'sat'], 'cur_ov':'wrap',
                   'enabled':True, 'visible':True
                   } #: default widget settings

        if 'quant' in self.q_dict and self.q_dict['quant'] in dict_ui['cmb_q']:
            dict_ui['cur_q'] = self.q_dict['quant']
        if 'ovfl' in self.q_dict and self.q_dict['ovfl'] in dict_ui['cmb_ov']:
            dict_ui['cur_ov'] = self.q_dict['ovfl']
            
        for key, val in kwargs.items():
            dict_ui.update({key:val})
        # dict_ui.update(map(kwargs)) # same as above?
            
        self.id = dict_ui['id']
        
        lblQuant = QLabel(dict_ui['label_q'], self)
        self.cmbQuant = QComboBox(self)
        self.cmbQuant.addItems(dict_ui['cmb_q'])
        qset_cmb_box(self.cmbQuant, dict_ui['cur_q'])
        self.cmbQuant.setToolTip(dict_ui['tip_q'])
        self.cmbQuant.setObjectName('quant')        

        lblOvfl = QLabel(dict_ui['label_ov'], self)
        self.cmbOvfl = QComboBox(self)
        self.cmbOvfl.addItems(dict_ui['cmb_ov'])
        qset_cmb_box(self.cmbOvfl, dict_ui['cur_ov'])
        self.cmbOvfl.setToolTip(dict_ui['tip_ov'])
        self.cmbOvfl.setObjectName('ovfl')
        
        # ComboBox size is adjusted automatically to fit the longest element
        self.cmbQuant.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbOvfl.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        layH = QHBoxLayout()
        if dict_ui['label'] != "":
            lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self)
            layH.addWidget(lblW)
        layH.addStretch()
        layH.addWidget(lblOvfl)
        layH.addWidget(self.cmbOvfl)
        #layH.addStretch(1)
        layH.addWidget(lblQuant)
        layH.addWidget(self.cmbQuant)
        layH.setContentsMargins(0,0,0,0)

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

        layVMain = QVBoxLayout() # Widget main layout
        layVMain.addWidget(frmMain)
        layVMain.setContentsMargins(0,0,0,0)#*params['wdg_margins'])

        self.setLayout(layVMain)

        #----------------------------------------------------------------------
        # INITIAL SETTINGS
        #----------------------------------------------------------------------
        self.ovfl = qget_cmb_box(self.cmbOvfl, data=False)
        self.quant = qget_cmb_box(self.cmbQuant, data=False)
        
        frmMain.setEnabled(dict_ui['enabled'])
        frmMain.setVisible(dict_ui['visible'])

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.cmbOvfl.currentIndexChanged.connect(self.ui2dict)
        self.cmbQuant.currentIndexChanged.connect(self.ui2dict)
Ejemplo n.º 21
0
    def _construct_ui(self):
        """
        Define and construct the subwidgets
        """
        modes = ['| H |', 're{H}', 'im{H}']
        self.cmbShowH = QComboBox(self)
        self.cmbShowH.addItems(modes)
        self.cmbShowH.setObjectName("cmbUnitsH")
        self.cmbShowH.setToolTip(
            "Show magnitude, real / imag. part of H or H \n"
            "without linear phase (acausal system).")
        self.cmbShowH.setCurrentIndex(0)

        self.lblIn = QLabel("in", self)

        units = ['dB', 'V', 'W', 'Auto']
        self.cmbUnitsA = QComboBox(self)
        self.cmbUnitsA.addItems(units)
        self.cmbUnitsA.setObjectName("cmbUnitsA")
        self.cmbUnitsA.setToolTip(
            "<span>Set unit for y-axis:\n"
            "dB is attenuation (positive values), V and W are gain (less than 1).</span>"
        )
        self.cmbUnitsA.setCurrentIndex(0)

        self.lbl_log_bottom = QLabel("Bottom", self)
        self.led_log_bottom = QLineEdit(self)
        self.led_log_bottom.setText(str(self.log_bottom))
        self.led_log_bottom.setToolTip(
            "<span>Minimum display value for dB. scale.</span>")
        self.lbl_log_unit = QLabel("dB", self)

        self.cmbShowH.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbUnitsA.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.chkZerophase = QCheckBox("Zero phase", self)
        self.chkZerophase.setToolTip(
            "<span>Remove linear phase calculated from filter order.\n"
            "Attention: This makes no sense for a non-linear phase system!</span>"
        )

        self.lblInset = QLabel("Inset", self)
        self.cmbInset = QComboBox(self)
        self.cmbInset.addItems(['off', 'edit', 'fixed'])
        self.cmbInset.setObjectName("cmbInset")
        self.cmbInset.setToolTip("Display/edit second inset plot")
        self.cmbInset.setCurrentIndex(0)
        self.inset_idx = 0  # store previous index for comparison

        self.chkSpecs = QCheckBox("Specs", self)
        self.chkSpecs.setChecked(False)
        self.chkSpecs.setToolTip("Display filter specs as hatched regions")

        self.chkPhase = QCheckBox("Phase", self)
        self.chkPhase.setToolTip("Overlay phase")
        self.chkPhase.setChecked(False)

        self.chkAlign = QCheckBox("Align", self)
        self.chkAlign.setToolTip(
            "<span>Try to align grids for magnitude and phase "
            "(doesn't work in all cases).</span>")
        self.chkAlign.setChecked(True)
        self.chkAlign.setVisible(self.chkPhase.isChecked())

        #----------------------------------------------------------------------
        #               ### frmControls ###
        #
        # This widget encompasses all control subwidgets
        #----------------------------------------------------------------------
        layHControls = QHBoxLayout()
        layHControls.addStretch(10)
        layHControls.addWidget(self.cmbShowH)
        layHControls.addWidget(self.lblIn)
        layHControls.addWidget(self.cmbUnitsA)
        layHControls.addStretch(1)
        layHControls.addWidget(self.lbl_log_bottom)
        layHControls.addWidget(self.led_log_bottom)
        layHControls.addWidget(self.lbl_log_unit)
        layHControls.addStretch(1)
        layHControls.addWidget(self.chkZerophase)
        layHControls.addStretch(1)
        layHControls.addWidget(self.lblInset)
        layHControls.addWidget(self.cmbInset)
        layHControls.addStretch(1)
        layHControls.addWidget(self.chkSpecs)
        layHControls.addStretch(1)
        layHControls.addWidget(self.chkPhase)
        layHControls.addWidget(self.chkAlign)
        layHControls.addStretch(10)

        self.frmControls = QFrame(self)
        self.frmControls.setObjectName("frmControls")
        self.frmControls.setLayout(layHControls)

        #----------------------------------------------------------------------
        #               ### mplwidget ###
        #
        # main widget, encompassing the other widgets
        #----------------------------------------------------------------------
        self.mplwidget = MplWidget(self)
        self.mplwidget.layVMainMpl.addWidget(self.frmControls)
        self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins'])
        self.mplwidget.mplToolbar.a_he.setEnabled(True)
        self.mplwidget.mplToolbar.a_he.info = "manual/plot_hf.html"
        self.setLayout(self.mplwidget.layVMainMpl)

        self.init_axes()

        self.draw()  # calculate and draw |H(f)|

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.cmbUnitsA.currentIndexChanged.connect(self.draw)
        self.led_log_bottom.editingFinished.connect(self.update_view)
        self.cmbShowH.currentIndexChanged.connect(self.draw)

        self.chkZerophase.clicked.connect(self.draw)
        self.cmbInset.currentIndexChanged.connect(self.draw_inset)

        self.chkSpecs.clicked.connect(self.draw)
        self.chkPhase.clicked.connect(self.draw)
        self.chkAlign.clicked.connect(self.draw)

        self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx)
Ejemplo n.º 22
0
    def _construct_ui(self):
        """
        Define and construct the subwidgets
        """
        modes = ['| H |', 're{H}', 'im{H}']
        self.cmbShowH = QComboBox(self)
        self.cmbShowH.addItems(modes)
        self.cmbShowH.setObjectName("cmbUnitsH")
        self.cmbShowH.setToolTip("Show magnitude, real / imag. part of H or H \n"
                                 "without linear phase (acausal system).")
        self.cmbShowH.setCurrentIndex(0)

        self.lblIn = QLabel(to_html("Unit:", frmt="b"), self)

        self.cmb_units_a = QComboBox(self)
        qcmb_box_populate(self.cmb_units_a, self.cmb_units_a_items,
                          self.cmb_units_a_default)
        self.cmb_units_a.setObjectName("cmbUnitsA")

        self.lbl_log_bottom = QLabel(to_html("min =", 'bi'), self)
        self.led_log_bottom = QLineEdit(self)
        self.led_log_bottom.setText(str(self.log_bottom))
        self.led_log_bottom.setMaximumWidth(qtext_width(N_x=8))
        self.led_log_bottom.setToolTip(
            "<span>Minimum display value for dB. scale.</span>")
        self.lbl_log_unit = QLabel("dB", self)

        self.cmbShowH.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmb_units_a.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.but_zerophase = PushButton(" Zero phase ", checked=False)
        self.but_zerophase.setToolTip(
            "<span>Remove linear phase calculated from filter order.\n"
            "Attention: This makes no sense for a non-linear phase system!</span>")

        self.lblInset = QLabel(to_html("Inset", "bi"), self)
        self.cmbInset = QComboBox(self)
        self.cmbInset.addItems(['off', 'edit', 'fixed'])
        self.cmbInset.setObjectName("cmbInset")
        self.cmbInset.setToolTip("Display/edit second inset plot")
        self.cmbInset.setCurrentIndex(0)
        self.inset_idx = 0  # store previous index for comparison

        self.but_specs = PushButton("Specs ", checked=False)
        self.but_specs.setToolTip("Display filter specs as hatched regions")

        self.but_phase = PushButton("Phase ", checked=False)
        self.but_phase.setToolTip("Overlay phase")

        self.but_align = PushButton("Align", checked=True)
        self.but_align.setToolTip(
            "<span>Try to align gridlines for magnitude and phase "
            "(doesn't work in all cases).</span>")
        self.but_align.setVisible(self.but_phase.isChecked())

        # ----------------------------------------------------------------------
        #               ### frmControls ###
        #
        # This widget encompasses all control subwidgets
        # ----------------------------------------------------------------------
        layHControls = QHBoxLayout()
        layHControls.addWidget(self.cmbShowH)
        layHControls.addWidget(self.lblIn)
        layHControls.addWidget(self.cmb_units_a)
        layHControls.addStretch(1)
        layHControls.addWidget(self.lbl_log_bottom)
        layHControls.addWidget(self.led_log_bottom)
        layHControls.addWidget(self.lbl_log_unit)
        layHControls.addStretch(1)
        layHControls.addWidget(self.but_zerophase)
        layHControls.addStretch(1)
        layHControls.addWidget(self.lblInset)
        layHControls.addWidget(self.cmbInset)
        layHControls.addStretch(1)
        layHControls.addWidget(self.but_specs)
        layHControls.addStretch(1)
        layHControls.addWidget(self.but_phase)
        layHControls.addWidget(self.but_align)
        layHControls.addStretch(10)

        self.frmControls = QFrame(self)
        self.frmControls.setObjectName("frmControls")
        self.frmControls.setLayout(layHControls)

        # ----------------------------------------------------------------------
        #               ### mplwidget ###
        #
        # main widget, encompassing the other widgets
        # ----------------------------------------------------------------------
        self.mplwidget = MplWidget(self)
        self.mplwidget.layVMainMpl.addWidget(self.frmControls)
        self.mplwidget.layVMainMpl.setContentsMargins(*params['mpl_margins'])
        self.mplwidget.mplToolbar.a_he.setEnabled(True)
        self.mplwidget.mplToolbar.a_he.info = "manual/plot_hf.html"
        self.setLayout(self.mplwidget.layVMainMpl)

        self.init_axes()

        self.draw()  # calculate and draw |H(f)|

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.cmb_units_a.currentIndexChanged.connect(self.draw)
        self.led_log_bottom.editingFinished.connect(self.update_view)
        self.cmbShowH.currentIndexChanged.connect(self.draw)

        self.but_zerophase.clicked.connect(self.draw)
        self.cmbInset.currentIndexChanged.connect(self.draw_inset)

        self.but_specs.clicked.connect(self.draw)
        self.but_phase.clicked.connect(self.draw)
        self.but_align.clicked.connect(self.draw)

        self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx)
Ejemplo n.º 23
0
    def _construct_UI(self):
        """
        Construct User Interface from all input subwidgets
        """
        # Subwidget for selecting filter with response type rt (LP, ...),
        #    filter type ft (IIR, ...) and filter class fc (cheby1, ...)
        self.sel_fil = select_filter.SelectFilter(self)
        self.sel_fil.setObjectName("select_filter")
        self.sel_fil.sig_tx.connect(self.sig_rx_local)

        # Subwidget for selecting the frequency unit and range
        self.f_units = freq_units.FreqUnits(self)
        self.f_units.setObjectName("freq_units")
        self.f_units.sig_tx.connect(self.sig_rx_local)

        # Changing the frequency unit requires re-display of frequency specs
        # but it does not influence the actual specs (no specsChanged )
        # Activating the "Sort" button emits 'view_changed'?specs_changed'?, requiring
        # sorting and storing the frequency entries

        # Changing filter parameters / specs requires reloading of parameters
        # in other hierarchy levels, e.g. in the plot tabs

        # Subwidget for Frequency Specs
        self.f_specs = freq_specs.FreqSpecs(self)
        self.f_specs.setObjectName("freq_specs")
        self.f_specs.sig_tx.connect(self.sig_rx_local)
        # Subwidget for Amplitude Specs
        self.a_specs = amplitude_specs.AmplitudeSpecs(self)
        self.a_specs.setObjectName("amplitude_specs")
        self.a_specs.sig_tx.connect(self.sig_rx_local)
        # Subwidget for Weight Specs
        self.w_specs = weight_specs.WeightSpecs(self)
        self.w_specs.setObjectName("weight_specs")
        self.w_specs.sig_tx.connect(self.sig_rx_local)
        # Subwidget for target specs (frequency and amplitude)
        self.t_specs = target_specs.TargetSpecs(self,
                                                title="Target Specifications")
        self.t_specs.setObjectName("target_specs")
        self.t_specs.sig_tx.connect(self.sig_rx_local)
        # self.sig_tx.connect(self.t_specs.sig_rx)
        # Subwidget for displaying infos on the design method
        self.lblMsg = QLabel(self)
        self.lblMsg.setWordWrap(True)
        layVMsg = QVBoxLayout()
        layVMsg.addWidget(self.lblMsg)

        self.frmMsg = QFrame(self)
        self.frmMsg.setLayout(layVMsg)
        layVFrm = QVBoxLayout()
        layVFrm.addWidget(self.frmMsg)
        layVFrm.setContentsMargins(*params['wdg_margins'])

        self.butDesignFilt = QPushButton("DESIGN FILTER", self)
        self.butDesignFilt.setToolTip("Design filter with chosen specs")
        self.butQuit = QPushButton("Quit", self)
        self.butQuit.setToolTip("Exit pyfda tool")
        layHButtons = QHBoxLayout()
        layHButtons.addWidget(self.butDesignFilt)  # <Design Filter> button
        layHButtons.addWidget(self.butQuit)  # <Quit> button
        layHButtons.setContentsMargins(*params['wdg_margins'])

        #----------------------------------------------------------------------
        # LAYOUT for input specifications and buttons
        #----------------------------------------------------------------------
        layVMain = QVBoxLayout(self)
        layVMain.addWidget(self.sel_fil)  # Design method (IIR - ellip, ...)
        layVMain.addLayout(layHButtons)  # <Design> & <Quit> buttons
        layVMain.addWidget(self.f_units)  # Frequency units
        layVMain.addWidget(self.t_specs)  # Target specs
        layVMain.addWidget(self.f_specs)  # Freq. specifications
        layVMain.addWidget(self.a_specs)  # Amplitude specs
        layVMain.addWidget(self.w_specs)  # Weight specs
        layVMain.addLayout(layVFrm)  # Text message

        layVMain.addStretch()

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

        self.setLayout(layVMain)  # main layout of widget

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx_local.connect(self.process_sig_rx_local)
        self.butDesignFilt.clicked.connect(self.start_design_filt)
        self.butQuit.clicked.connect(self.quit_program)  # emit 'quit_program'
        #----------------------------------------------------------------------

        self.update_UI()  # first time initialization
        self.start_design_filt()  # design first filter using default values
Ejemplo n.º 24
0
class SelectFilter(QWidget):
    """
    Construct and read combo boxes for selecting the filter, consisting of the
    following hierarchy:

    1. Response Type rt (LP, HP, Hilbert, ...)
    2. Filter Type ft (IIR, FIR, CIC ...)
    3. Filter Class (Butterworth, ...)

    Every time a combo box is changed manually, the filter tree for the selected
    response resp. filter type is read and the combo box(es) further down in
    the hierarchy are populated according to the available combinations.

    sig_tx({'filt_changed'}) is emitted and propagated to input_filter_specs.py
    where it triggers the recreation of all subwidgets.
    """
    sig_tx = pyqtSignal(object)  # outgoing
    from pyfda.libs.pyfda_qt_lib import emit

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

        self.fc_last = ''  # previous filter class

        self._construct_UI()

        self._set_response_type()  # first time initialization

    def _construct_UI(self):
        """
        Construct UI with comboboxes for selecting filter:

        - cmbResponseType for selecting response type rt (LP, HP, ...)

        - cmbFilterType for selection of filter type (IIR, FIR, ...)

        - cmbFilterClass for selection of design design class (Chebyshev, ...)

        and populate them from the "filterTree" dict during the initial run.
        Later, calling _set_response_type() updates the three combo boxes.

        See filterbroker.py for structure and content of "filterTree" dict

        """
        # ----------------------------------------------------------------------
        # Combo boxes for filter selection
        # ----------------------------------------------------------------------
        self.cmbResponseType = QComboBox(self)
        self.cmbResponseType.setObjectName("comboResponseType")
        self.cmbResponseType.setToolTip("Select filter response type.")
        self.cmbFilterType = QComboBox(self)
        self.cmbFilterType.setObjectName("comboFilterType")
        self.cmbFilterType.setToolTip(
            "<span>Choose filter type, either recursive (Infinite Impulse Response) "
            "or transversal (Finite Impulse Response).</span>")
        self.cmbFilterClass = QComboBox(self)
        self.cmbFilterClass.setObjectName("comboFilterClass")
        self.cmbFilterClass.setToolTip("Select the filter design class.")

        # Adapt comboboxes size dynamically to largest element
        self.cmbResponseType.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbFilterClass.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        # ----------------------------------------------------------------------
        # Populate combo box with initial settings from fb.fil_tree
        # ----------------------------------------------------------------------
        # Translate short response type ("LP") to displayed names ("Lowpass")
        # (correspondence is defined in pyfda_rc.py) and populate rt combo box
        #
        rt_list = sorted(list(fb.fil_tree.keys()))

        for rt in rt_list:
            try:
                self.cmbResponseType.addItem(rc.rt_names[rt], rt)
            except KeyError as e:
                logger.warning(
                    f"KeyError: {e} has no corresponding full name in rc.rt_names."
                )
        idx = self.cmbResponseType.findData('LP')  # find index for 'LP'

        if idx == -1:  # Key 'LP' does not exist, use first entry instead
            idx = 0

        self.cmbResponseType.setCurrentIndex(idx)  # set initial index
        rt = qget_cmb_box(self.cmbResponseType)

        for ft in fb.fil_tree[rt]:
            self.cmbFilterType.addItem(rc.ft_names[ft], ft)
        self.cmbFilterType.setCurrentIndex(0)  # set initial index
        ft = qget_cmb_box(self.cmbFilterType)

        for fc in fb.fil_tree[rt][ft]:
            self.cmbFilterClass.addItem(fb.filter_classes[fc]['name'], fc)
        self.cmbFilterClass.setCurrentIndex(0)  # set initial index

        # ----------------------------------------------------------------------
        # Layout for Filter Type Subwidgets
        # ----------------------------------------------------------------------

        layHFilWdg = QHBoxLayout()  # container for filter subwidgets
        layHFilWdg.addWidget(self.cmbResponseType)  # LP, HP, BP, etc.
        layHFilWdg.addStretch()
        layHFilWdg.addWidget(self.cmbFilterType)  # FIR, IIR
        layHFilWdg.addStretch()
        layHFilWdg.addWidget(self.cmbFilterClass)  # bessel, elliptic, etc.

        # ----------------------------------------------------------------------
        # Layout for dynamic filter subwidgets (empty frame)
        # ----------------------------------------------------------------------
        # see Summerfield p. 278
        self.layHDynWdg = QHBoxLayout()  # for additional dynamic subwidgets

        # ----------------------------------------------------------------------
        # Filter Order Subwidgets
        # ----------------------------------------------------------------------
        self.lblOrder = QLabel("<b>Order:</b>")
        self.chkMinOrder = QCheckBox("Minimum", self)
        self.chkMinOrder.setToolTip(
            "<span>Minimum filter order / # of taps is determined automatically.</span>"
        )
        self.lblOrderN = QLabel("<b><i>N =</i></b>")
        self.ledOrderN = QLineEdit(str(fb.fil[0]['N']), self)
        self.ledOrderN.setToolTip("Filter order (# of taps - 1).")

        # --------------------------------------------------
        #  Layout for filter order subwidgets
        # --------------------------------------------------
        layHOrdWdg = QHBoxLayout()
        layHOrdWdg.addWidget(self.lblOrder)
        layHOrdWdg.addWidget(self.chkMinOrder)
        layHOrdWdg.addStretch()
        layHOrdWdg.addWidget(self.lblOrderN)
        layHOrdWdg.addWidget(self.ledOrderN)

        # ----------------------------------------------------------------------
        # OVERALL LAYOUT (stack standard + dynamic subwidgets vertically)
        # ----------------------------------------------------------------------
        self.layVAllWdg = QVBoxLayout()
        self.layVAllWdg.addLayout(layHFilWdg)
        self.layVAllWdg.addLayout(self.layHDynWdg)
        self.layVAllWdg.addLayout(layHOrdWdg)

        # ==============================================================================
        frmMain = QFrame(self)
        frmMain.setLayout(self.layVAllWdg)

        layHMain = QHBoxLayout()
        layHMain.addWidget(frmMain)
        layHMain.setContentsMargins(*rc.params['wdg_margins'])

        self.setLayout(layHMain)

        # ==============================================================================

        # ------------------------------------------------------------
        # SIGNALS & SLOTS
        # ------------------------------------------------------------
        # Connect comboBoxes and setters, propgate change events hierarchically
        #  through all widget methods and emit 'filt_changed' in the end.
        self.cmbResponseType.currentIndexChanged.connect(
            lambda: self._set_response_type(enb_signal=True))  # 'LP'
        self.cmbFilterType.currentIndexChanged.connect(
            lambda: self._set_filter_type(enb_signal=True))  # 'IIR'
        self.cmbFilterClass.currentIndexChanged.connect(
            lambda: self._set_design_method(enb_signal=True))  # 'cheby1'
        self.chkMinOrder.clicked.connect(
            lambda: self._set_filter_order(enb_signal=True))  # Min. Order
        self.ledOrderN.editingFinished.connect(
            lambda: self._set_filter_order(enb_signal=True))  # Manual Order
        # ------------------------------------------------------------

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

    def load_dict(self):
        """
        Reload comboboxes from filter dictionary to update changed settings
        after loading a filter design from disk.
        `load_dict` uses the automatism of _set_response_type etc.
        of checking whether the previously selected filter design method is
        also available for the new combination.
        """
        # find index for response type:
        rt_idx = self.cmbResponseType.findData(fb.fil[0]['rt'])
        self.cmbResponseType.setCurrentIndex(rt_idx)
        self._set_response_type()

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

    def _set_response_type(self, enb_signal=False):
        """
        Triggered when cmbResponseType (LP, HP, ...) is changed:
        Copy selection to self.rt and fb.fil[0] and reconstruct filter type combo

        If previous filter type (FIR, IIR, ...) exists for new rt, set the
        filter type combo box to the old setting
        """
        # Read current setting of comboBox as string and store it in the filter dict
        fb.fil[0]['rt'] = self.rt = qget_cmb_box(self.cmbResponseType)

        # Get list of available filter types for new rt
        ft_list = list(
            fb.fil_tree[self.rt].keys())  # explicit list() needed for Py3
        # ---------------------------------------------------------------
        # Rebuild filter type combobox entries for new rt setting
        self.cmbFilterType.blockSignals(
            True)  # don't fire when changed programmatically
        self.cmbFilterType.clear()
        for ft in fb.fil_tree[self.rt]:
            self.cmbFilterType.addItem(rc.ft_names[ft], ft)

        # Is current filter type (e.g. IIR) in list for new rt?
        if fb.fil[0]['ft'] in ft_list:
            ft_idx = self.cmbFilterType.findText(fb.fil[0]['ft'])
            self.cmbFilterType.setCurrentIndex(
                ft_idx)  # yes, set same ft as before
        else:
            self.cmbFilterType.setCurrentIndex(0)  # no, set index 0

        self.cmbFilterType.blockSignals(False)
        # ---------------------------------------------------------------

        self._set_filter_type(enb_signal)

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

    def _set_filter_type(self, enb_signal=False):
        """"
        Triggered when cmbFilterType (IIR, FIR, ...) is changed:
        - read filter type ft and copy it to fb.fil[0]['ft'] and self.ft
        - (re)construct design method combo, adding
          displayed text (e.g. "Chebyshev 1") and hidden data (e.g. "cheby1")
        """
        # Read out current setting of comboBox and convert to string
        fb.fil[0]['ft'] = self.ft = qget_cmb_box(self.cmbFilterType)
        #
        logger.debug("InputFilter.set_filter_type triggered: {0}".format(
            self.ft))

        # ---------------------------------------------------------------
        # Get all available design methods for new ft from fil_tree and
        # - Collect them in fc_list
        # - Rebuild design method combobox entries for new ft setting:
        #    The combobox is populated with the "long name",
        #    the internal name is stored in comboBox.itemData
        self.cmbFilterClass.blockSignals(True)
        self.cmbFilterClass.clear()
        fc_list = []

        for fc in sorted(fb.fil_tree[self.rt][self.ft]):
            self.cmbFilterClass.addItem(fb.filter_classes[fc]['name'], fc)
            fc_list.append(fc)

        logger.debug("fc_list: {0}\n{1}".format(fc_list, fb.fil[0]['fc']))

        # Does new ft also provide the previous design method (e.g. ellip)?
        # Has filter been instantiated?
        if fb.fil[0]['fc'] in fc_list and ff.fil_inst:
            # yes, set same fc as before
            fc_idx = self.cmbFilterClass.findText(
                fb.filter_classes[fb.fil[0]['fc']]['name'])
            logger.debug("fc_idx : %s", fc_idx)
            self.cmbFilterClass.setCurrentIndex(fc_idx)
        else:
            self.cmbFilterClass.setCurrentIndex(0)  # no, set index 0

        self.cmbFilterClass.blockSignals(False)

        self._set_design_method(enb_signal)

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

    def _set_design_method(self, enb_signal=False):
        """
        Triggered when cmbFilterClass (cheby1, ...) is changed:
        - read design method fc and copy it to fb.fil[0]
        - create / update global filter instance fb.fil_inst of fc class
        - update dynamic widgets (if fc has changed and if there are any)
        - call load filter order
        """
        fb.fil[0]['fc'] = fc = qget_cmb_box(self.cmbFilterClass)

        if fc != self.fc_last:  # fc has changed:

            # when filter has been changed, try to destroy dynamic widgets of last fc:
            if self.fc_last:
                self._destruct_dyn_widgets()

            # ==================================================================
            """
            Create new instance of the selected filter class, accessible via
            its handle fb.fil_inst
            """
            err = ff.fil_factory.create_fil_inst(fc)
            logger.debug(f"InputFilter.set_design_method triggered: {fc}\n"
                         f"Returned error code {err}")
            # ==================================================================

            # Check whether new design method also provides the old filter order
            # method. If yes, don't change it, else set first available
            # filter order method
            if fb.fil[0]['fo'] not in fb.fil_tree[self.rt][self.ft][fc].keys():
                fb.fil[0].update({'fo': {}})
                # explicit list(dict.keys()) needed for Python 3
                fb.fil[0]['fo'] = list(
                    fb.fil_tree[self.rt][self.ft][fc].keys())[0]

# =============================================================================
#             logger.debug("selFilter = %s"
#                    "filterTree[fc] = %s"
#                    "filterTree[fc].keys() = %s"
#                   %(fb.fil[0], fb.fil_tree[self.rt][self.ft][fc],\
#                     fb.fil_tree[self.rt][self.ft][fc].keys()
#                     ))
#
# =============================================================================
# construct dyn. subwidgets if available
            if hasattr(ff.fil_inst, 'construct_UI'):
                self._construct_dyn_widgets()

            self.fc_last = fb.fil[0]['fc']

        self.load_filter_order(enb_signal)

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

    def load_filter_order(self, enb_signal=False):
        """
        Called by set_design_method or from InputSpecs (with enb_signal = False),
          load filter order setting from fb.fil[0] and update widgets

        """
        # collect dict_keys of available filter order [fo] methods for selected
        # design method [fc] from fil_tree (explicit list() needed for Python 3)
        fo_dict = fb.fil_tree[fb.fil[0]['rt']][fb.fil[0]['ft']][fb.fil[0]
                                                                ['fc']]
        fo_list = list(fo_dict.keys())

        # is currently selected fo setting available for (new) fc ?
        if fb.fil[0]['fo'] in fo_list:
            self.fo = fb.fil[0]['fo']  # keep current setting
        else:
            self.fo = fo_list[0]  # use first list entry from filterTree
            fb.fil[0]['fo'] = self.fo  # and update fo method

        # check whether fo widget is active, disabled or invisible
        if 'fo' in fo_dict[self.fo] and len(fo_dict[self.fo]['fo']) > 1:
            status = fo_dict[self.fo]['fo'][0]
        else:
            status = 'i'

        # Determine which subwidgets are __visible__
        self.chkMinOrder.setVisible('min' in fo_list)
        self.ledOrderN.setVisible(status in {'a', 'd'})
        self.lblOrderN.setVisible(status in {'a', 'd'})

        # Determine which subwidgets are __enabled__
        self.chkMinOrder.setChecked(fb.fil[0]['fo'] == 'min')
        self.ledOrderN.setText(str(fb.fil[0]['N']))
        self.ledOrderN.setEnabled(not self.chkMinOrder.isChecked()
                                  and status == 'a')
        self.lblOrderN.setEnabled(not self.chkMinOrder.isChecked()
                                  and status == 'a')

        if enb_signal:
            logger.debug("Emit 'filt_changed'")
            self.emit({'filt_changed': 'filter_type'})

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

    def _set_filter_order(self, enb_signal=False):
        """
        Triggered when either ledOrderN or chkMinOrder are edited:
        - copy settings to fb.fil[0]
        - emit 'filt_changed' if enb_signal is True
        """
        # Determine which subwidgets are _enabled_
        if self.chkMinOrder.isVisible():
            self.ledOrderN.setEnabled(not self.chkMinOrder.isChecked())
            self.lblOrderN.setEnabled(not self.chkMinOrder.isChecked())

            if self.chkMinOrder.isChecked() is True:
                # update in case N has been changed outside this class
                self.ledOrderN.setText(str(fb.fil[0]['N']))
                fb.fil[0].update({'fo': 'min'})

            else:
                fb.fil[0].update({'fo': 'man'})

        else:
            self.lblOrderN.setEnabled(self.fo == 'man')
            self.ledOrderN.setEnabled(self.fo == 'man')

        # read manual filter order, convert to positive integer and store it
        # in filter dictionary.
        ordn = safe_eval(self.ledOrderN.text(),
                         fb.fil[0]['N'],
                         return_type='int',
                         sign='pos')
        ordn = ordn if ordn > 0 else 1
        self.ledOrderN.setText(str(ordn))
        fb.fil[0].update({'N': ordn})

        if enb_signal:
            logger.debug("Emit 'filt_changed'")
            self.emit({'filt_changed': 'filter_order_widget'})

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

    def _destruct_dyn_widgets(self):
        """
        Delete the dynamically created filter design subwidget (if there is one)

        see http://stackoverflow.com/questions/13827798/proper-way-to-cleanup-
        widgets-in-pyqt

        This does NOT work when the subwidgets to be deleted and created are
        identical, as the deletion is only performed when the current scope has
        been left (?)! Hence, it is necessary to skip this method when the new
        design method is the same as the old one.
        """

        if hasattr(ff.fil_inst, 'wdg_fil'):
            # not needed, connection is destroyed automatically
            # ff.fil_inst.sig_tx.disconnect()
            try:
                # remove widget from layout
                self.layHDynWdg.removeWidget(self.dyn_wdg_fil)
                # delete UI widget when scope has been left
                self.dyn_wdg_fil.deleteLater()

            except AttributeError as e:
                logger.error("Could not destruct_UI!\n{0}".format(e))

            ff.fil_inst.deleteLater(
            )  # delete QWidget when scope has been left

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

    def _construct_dyn_widgets(self):
        """
        Create filter widget UI dynamically (if the filter routine has one) and
        connect its sig_tx signal to sig_tx in this scope.
        """
        ff.fil_inst.construct_UI()
        if hasattr(ff.fil_inst, 'wdg_fil'):
            try:
                self.dyn_wdg_fil = getattr(ff.fil_inst, 'wdg_fil')
                self.layHDynWdg.addWidget(self.dyn_wdg_fil, stretch=1)
            except AttributeError as e:
                logger.warning(e)

        if hasattr(ff.fil_inst, 'sig_tx'):
            ff.fil_inst.sig_tx.connect(self.sig_tx)
Ejemplo n.º 25
0
    def _construct_UI(self):
        """
        Intitialize the widget, consisting of:
        - Matplotlib widget with NavigationToolbar
        - Frame with control elements
        """
        self.chkHf = QCheckBox("Show |H(f)|", self)
        self.chkHf.setToolTip(
            "<span>Display |H(f)| around unit circle.</span>")
        self.chkHf.setEnabled(True)

        self.chkHfLog = QCheckBox("Log. Scale", self)
        self.chkHfLog.setToolTip("<span>Log. scale for |H(f)|.</span>")
        self.chkHfLog.setEnabled(True)

        self.diaRad_Hf = QDial(self)
        self.diaRad_Hf.setRange(2., 10.)
        self.diaRad_Hf.setValue(2)
        self.diaRad_Hf.setTracking(False)  # produce less events when turning
        self.diaRad_Hf.setFixedHeight(30)
        self.diaRad_Hf.setFixedWidth(30)
        self.diaRad_Hf.setWrapping(False)
        self.diaRad_Hf.setToolTip(
            "<span>Set max. radius for |H(f)| plot.</span>")

        self.lblRad_Hf = QLabel("Radius", self)

        self.chkFIR_P = QCheckBox("Plot FIR Poles", self)
        self.chkFIR_P.setToolTip("<span>Show FIR poles at the origin.</span>")
        self.chkFIR_P.setChecked(True)

        layHControls = QHBoxLayout()
        layHControls.addWidget(self.chkHf)
        layHControls.addWidget(self.chkHfLog)
        layHControls.addWidget(self.diaRad_Hf)
        layHControls.addWidget(self.lblRad_Hf)
        layHControls.addStretch(10)
        layHControls.addWidget(self.chkFIR_P)

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

        #----------------------------------------------------------------------
        #               ### mplwidget ###
        #
        # main widget, encompassing the other widgets
        #----------------------------------------------------------------------
        self.mplwidget = MplWidget(self)
        self.mplwidget.layVMainMpl.addWidget(self.frmControls)
        self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins'])
        self.setLayout(self.mplwidget.layVMainMpl)

        self.init_axes()

        self.draw()  # calculate and draw poles and zeros

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx)
        self.chkHf.clicked.connect(self.draw)
        self.chkHfLog.clicked.connect(self.draw)
        self.diaRad_Hf.valueChanged.connect(self.draw)
        self.chkFIR_P.clicked.connect(self.draw)
Ejemplo n.º 26
0
    def _construct_UI(self):
        """
        Construct UI with comboboxes for selecting filter:

        - cmbResponseType for selecting response type rt (LP, HP, ...)

        - cmbFilterType for selection of filter type (IIR, FIR, ...)

        - cmbFilterClass for selection of design design class (Chebyshev, ...)

        and populate them from the "filterTree" dict during the initial run.
        Later, calling _set_response_type() updates the three combo boxes.

        See filterbroker.py for structure and content of "filterTree" dict

        """
        # ----------------------------------------------------------------------
        # Combo boxes for filter selection
        # ----------------------------------------------------------------------
        self.cmbResponseType = QComboBox(self)
        self.cmbResponseType.setObjectName("comboResponseType")
        self.cmbResponseType.setToolTip("Select filter response type.")
        self.cmbFilterType = QComboBox(self)
        self.cmbFilterType.setObjectName("comboFilterType")
        self.cmbFilterType.setToolTip(
            "<span>Choose filter type, either recursive (Infinite Impulse Response) "
            "or transversal (Finite Impulse Response).</span>")
        self.cmbFilterClass = QComboBox(self)
        self.cmbFilterClass.setObjectName("comboFilterClass")
        self.cmbFilterClass.setToolTip("Select the filter design class.")

        # Adapt comboboxes size dynamically to largest element
        self.cmbResponseType.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmbFilterClass.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        # ----------------------------------------------------------------------
        # Populate combo box with initial settings from fb.fil_tree
        # ----------------------------------------------------------------------
        # Translate short response type ("LP") to displayed names ("Lowpass")
        # (correspondence is defined in pyfda_rc.py) and populate rt combo box
        #
        rt_list = sorted(list(fb.fil_tree.keys()))

        for rt in rt_list:
            try:
                self.cmbResponseType.addItem(rc.rt_names[rt], rt)
            except KeyError as e:
                logger.warning(
                    f"KeyError: {e} has no corresponding full name in rc.rt_names."
                )
        idx = self.cmbResponseType.findData('LP')  # find index for 'LP'

        if idx == -1:  # Key 'LP' does not exist, use first entry instead
            idx = 0

        self.cmbResponseType.setCurrentIndex(idx)  # set initial index
        rt = qget_cmb_box(self.cmbResponseType)

        for ft in fb.fil_tree[rt]:
            self.cmbFilterType.addItem(rc.ft_names[ft], ft)
        self.cmbFilterType.setCurrentIndex(0)  # set initial index
        ft = qget_cmb_box(self.cmbFilterType)

        for fc in fb.fil_tree[rt][ft]:
            self.cmbFilterClass.addItem(fb.filter_classes[fc]['name'], fc)
        self.cmbFilterClass.setCurrentIndex(0)  # set initial index

        # ----------------------------------------------------------------------
        # Layout for Filter Type Subwidgets
        # ----------------------------------------------------------------------

        layHFilWdg = QHBoxLayout()  # container for filter subwidgets
        layHFilWdg.addWidget(self.cmbResponseType)  # LP, HP, BP, etc.
        layHFilWdg.addStretch()
        layHFilWdg.addWidget(self.cmbFilterType)  # FIR, IIR
        layHFilWdg.addStretch()
        layHFilWdg.addWidget(self.cmbFilterClass)  # bessel, elliptic, etc.

        # ----------------------------------------------------------------------
        # Layout for dynamic filter subwidgets (empty frame)
        # ----------------------------------------------------------------------
        # see Summerfield p. 278
        self.layHDynWdg = QHBoxLayout()  # for additional dynamic subwidgets

        # ----------------------------------------------------------------------
        # Filter Order Subwidgets
        # ----------------------------------------------------------------------
        self.lblOrder = QLabel("<b>Order:</b>")
        self.chkMinOrder = QCheckBox("Minimum", self)
        self.chkMinOrder.setToolTip(
            "<span>Minimum filter order / # of taps is determined automatically.</span>"
        )
        self.lblOrderN = QLabel("<b><i>N =</i></b>")
        self.ledOrderN = QLineEdit(str(fb.fil[0]['N']), self)
        self.ledOrderN.setToolTip("Filter order (# of taps - 1).")

        # --------------------------------------------------
        #  Layout for filter order subwidgets
        # --------------------------------------------------
        layHOrdWdg = QHBoxLayout()
        layHOrdWdg.addWidget(self.lblOrder)
        layHOrdWdg.addWidget(self.chkMinOrder)
        layHOrdWdg.addStretch()
        layHOrdWdg.addWidget(self.lblOrderN)
        layHOrdWdg.addWidget(self.ledOrderN)

        # ----------------------------------------------------------------------
        # OVERALL LAYOUT (stack standard + dynamic subwidgets vertically)
        # ----------------------------------------------------------------------
        self.layVAllWdg = QVBoxLayout()
        self.layVAllWdg.addLayout(layHFilWdg)
        self.layVAllWdg.addLayout(self.layHDynWdg)
        self.layVAllWdg.addLayout(layHOrdWdg)

        # ==============================================================================
        frmMain = QFrame(self)
        frmMain.setLayout(self.layVAllWdg)

        layHMain = QHBoxLayout()
        layHMain.addWidget(frmMain)
        layHMain.setContentsMargins(*rc.params['wdg_margins'])

        self.setLayout(layHMain)

        # ==============================================================================

        # ------------------------------------------------------------
        # SIGNALS & SLOTS
        # ------------------------------------------------------------
        # Connect comboBoxes and setters, propgate change events hierarchically
        #  through all widget methods and emit 'filt_changed' in the end.
        self.cmbResponseType.currentIndexChanged.connect(
            lambda: self._set_response_type(enb_signal=True))  # 'LP'
        self.cmbFilterType.currentIndexChanged.connect(
            lambda: self._set_filter_type(enb_signal=True))  # 'IIR'
        self.cmbFilterClass.currentIndexChanged.connect(
            lambda: self._set_design_method(enb_signal=True))  # 'cheby1'
        self.chkMinOrder.clicked.connect(
            lambda: self._set_filter_order(enb_signal=True))  # Min. Order
        self.ledOrderN.editingFinished.connect(
            lambda: self._set_filter_order(enb_signal=True))  # Manual Order
Ejemplo n.º 27
0
    def _construct_UI(self):
        """
        Intitialize the widget, consisting of:
        - top chkbox row
        - coefficient table
        - two bottom rows with action buttons
        """
        self.bfont = QFont()
        self.bfont.setBold(True)
        self.bifont = QFont()
        self.bifont.setBold(True)
        self.bifont.setItalic(True)
#        q_icon_size = QSize(20, 20) # optional, size is derived from butEnable

        #######################################################################
        # frmMain
        #
        # This frame contains all the buttons
        #######################################################################
        # ---------------------------------------------
        # layHDisplay
        #
        # UI Elements for controlling the display
        # ---------------------------------------------
        self.butEnable = PushButton(self, icon=QIcon(':/circle-check.svg'), checked=True)
        q_icon_size = self.butEnable.iconSize()  # <- uncomment this for manual sizing
        self.butEnable.setToolTip(
            "<span>Show / hide filter coefficients in an editable table."
            " For high order systems, table display might be slow.</span>")

        fix_formats = ['Dec', 'Hex', 'Bin', 'CSD']
        self.cmbFormat = QComboBox(self)
        model = self.cmbFormat.model()
        item = QtGui.QStandardItem('Float')
        item.setData('child', Qt.AccessibleDescriptionRole)
        model.appendRow(item)

        item = QtGui.QStandardItem('Fixp.:')
        item.setData('parent', Qt.AccessibleDescriptionRole)
        item.setData(0, QtGui.QFont.Bold)
        item.setFlags(item.flags() & ~Qt.ItemIsEnabled)  # | Qt.ItemIsSelectable))
        model.appendRow(item)

        for idx in range(len(fix_formats)):
            item = QtGui.QStandardItem(fix_formats[idx])
#            item.setForeground(QtGui.QColor('red'))
            model.appendRow(item)

        self.cmbFormat.insertSeparator(1)
        qset_cmb_box(self.cmbFormat, 'float')
        self.cmbFormat.setToolTip('Set the display format.')
        self.cmbFormat.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.spnDigits = QSpinBox(self)
        self.spnDigits.setRange(0, 16)
        self.spnDigits.setValue(params['FMT_ba'])
        self.spnDigits.setToolTip("Number of digits to display.")
        self.lblDigits = QLabel("Digits", self)
        self.lblDigits.setFont(self.bifont)

        self.cmbQFrmt = QComboBox(self)
        q_formats = [('Norm. Frac.', 'qnfrac'), ('Integer', 'qint'),
                     ('Fractional', 'qfrac')]
        for q in q_formats:
            self.cmbQFrmt.addItem(*q)

        self.lbl_W = QLabel("W = ", self)
        self.lbl_W.setFont(self.bifont)

        self.ledW = QLineEdit(self)
        self.ledW.setToolTip("Specify total wordlength.")
        self.ledW.setText("16")
        self.ledW.setMaxLength(2)  # maximum of 2 digits
        self.ledW.setFixedWidth(30)  # width of lineedit in points(?)

        layHDisplay = QHBoxLayout()
        layHDisplay.setAlignment(Qt.AlignLeft)
        layHDisplay.addWidget(self.butEnable)
        layHDisplay.addWidget(self.cmbFormat)
        layHDisplay.addWidget(self.spnDigits)
        layHDisplay.addWidget(self.lblDigits)
        layHDisplay.addWidget(self.cmbQFrmt)
        layHDisplay.addWidget(self.lbl_W)
        layHDisplay.addWidget(self.ledW)
        layHDisplay.addStretch()

        #######################################################################
        # frmButtonsCoeffs
        #
        # This frame contains all buttons for manipulating coefficients
        #######################################################################
        # -----------------------------------------------------------------
        # layHButtonsCoeffs1
        #
        # UI Elements for loading / storing / manipulating cells and rows
        # -----------------------------------------------------------------
        self.cmbFilterType = QComboBox(self)
        self.cmbFilterType.setObjectName("comboFilterType")
        self.cmbFilterType.setToolTip(
            "<span>Select between IIR and FIR filter for manual entry."
            "Changing the type reloads the filter from the filter dict.</span>")
        self.cmbFilterType.addItems(["FIR", "IIR"])
        self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.butAddCells = QPushButton(self)
        self.butAddCells.setIcon(QIcon(':/row_insert_above.svg'))
        self.butAddCells.setIconSize(q_icon_size)
        self.butAddCells.setToolTip(
            "<span>Select cells to insert a new cell above each selected cell. "
            "Use &lt;SHIFT&gt; or &lt;CTRL&gt; to select multiple cells. "
            "When nothing is selected, add a row at the end.</span>")

        self.butDelCells = QPushButton(self)
        self.butDelCells.setIcon(QIcon(':/row_delete.svg'))
        self.butDelCells.setIconSize(q_icon_size)
        self.butDelCells.setToolTip(
            "<span>Delete selected cell(s) from the table. "
            "Use &lt;SHIFT&gt; or &lt;CTRL&gt; to select multiple cells. "
            "When nothing is selected, delete the last row.</span>")

        self.butSave = QPushButton(self)
        self.butSave.setIcon(QIcon(':/upload.svg'))
        self.butSave.setIconSize(q_icon_size)
        self.butSave.setToolTip(
            "<span>Copy coefficient table to filter dict and update all plots"
            "and widgets.</span>")

        self.butLoad = QPushButton(self)
        self.butLoad.setIcon(QIcon(':/download.svg'))
        self.butLoad.setIconSize(q_icon_size)
        self.butLoad.setToolTip("Reload coefficient table from filter dict.")

        self.butClear = QPushButton(self)
        self.butClear.setIcon(QIcon(':/trash.svg'))
        self.butClear.setIconSize(q_icon_size)
        self.butClear.setToolTip("Clear all table entries.")

        self.butFromTable = QPushButton(self)
        self.butFromTable.setIconSize(q_icon_size)

        self.butToTable = QPushButton(self)
        self.butToTable.setIconSize(q_icon_size)

        self.but_csv_options = PushButton(self, icon=QIcon(':/settings.svg'),
                                          checked=False)
        self.but_csv_options.setIconSize(q_icon_size)
        self.but_csv_options.setToolTip(
            "<span>Select CSV format and whether "
            "to copy to/from clipboard or file.</span>")

        self._set_load_save_icons()  # initialize icon / button settings

        layHButtonsCoeffs1 = QHBoxLayout()
        layHButtonsCoeffs1.addWidget(self.cmbFilterType)
        layHButtonsCoeffs1.addWidget(self.butAddCells)
        layHButtonsCoeffs1.addWidget(self.butDelCells)
        layHButtonsCoeffs1.addWidget(self.butClear)
        layHButtonsCoeffs1.addWidget(self.butSave)
        layHButtonsCoeffs1.addWidget(self.butLoad)
        layHButtonsCoeffs1.addWidget(self.butFromTable)
        layHButtonsCoeffs1.addWidget(self.butToTable)
        layHButtonsCoeffs1.addWidget(self.but_csv_options)
        layHButtonsCoeffs1.addStretch()

        # ----------------------------------------------------------------------
        # layHButtonsCoeffs2
        #
        # Eps / set zero settings
        # ---------------------------------------------------------------------
        self.butSetZero = QPushButton("= 0", self)
        self.butSetZero.setToolTip(
            "<span>Set selected coefficients = 0 with a magnitude &lt; &epsilon;. "
            "When nothing is selected, test the whole table.</span>")
        self.butSetZero.setIconSize(q_icon_size)

        lblEps = QLabel(self)
        lblEps.setText("<b><i>for b, a</i> &lt;</b>")

        self.ledEps = QLineEdit(self)
        self.ledEps.setToolTip("Specify tolerance value.")

        layHButtonsCoeffs2 = QHBoxLayout()
        layHButtonsCoeffs2.addWidget(self.butSetZero)
        layHButtonsCoeffs2.addWidget(lblEps)
        layHButtonsCoeffs2.addWidget(self.ledEps)
        layHButtonsCoeffs2.addStretch()

        # -------------------------------------------------------------------
        # Now put the ButtonsCoeffs HBoxes into frmButtonsCoeffs
        # ---------------------------------------------------------------------
        layVButtonsCoeffs = QVBoxLayout()
        layVButtonsCoeffs.addLayout(layHButtonsCoeffs1)
        layVButtonsCoeffs.addLayout(layHButtonsCoeffs2)
        layVButtonsCoeffs.setContentsMargins(0, 5, 0, 0)
        # This frame encompasses all Quantization Settings
        self.frmButtonsCoeffs = QFrame(self)
        self.frmButtonsCoeffs.setLayout(layVButtonsCoeffs)

        # ######################################################################
        # frmQSettings
        #
        # This frame contains all quantization settings
        # ######################################################################
        # -------------------------------------------------------------------
        # layHW_Scale
        #
        # QFormat and scale settings
        # ---------------------------------------------------------------------
        lbl_Q = QLabel("Q =", self)
        lbl_Q.setFont(self.bifont)

        self.ledWI = QLineEdit(self)
        self.ledWI.setToolTip("Specify number of integer bits.")
        self.ledWI.setText("0")
        self.ledWI.setMaxLength(2)  # maximum of 2 digits
        self.ledWI.setFixedWidth(30)  # width of lineedit in points(?)

        self.lblDot = QLabel(".", self)  # class attribute, visibility is toggled
        self.lblDot.setFont(self.bfont)

        self.ledWF = QLineEdit(self)
        self.ledWF.setToolTip("Specify number of fractional bits.")
        self.ledWF.setText("15")
        self.ledWF.setMaxLength(2)  # maximum of 2 digits
#        self.ledWF.setFixedWidth(30) # width of lineedit in points(?)
        self.ledWF.setMaximumWidth(30)

        self.lblScale = QLabel("<b><i>Scale</i> =</b>", self)
        self.ledScale = QLineEdit(self)
        self.ledScale.setToolTip(
            "Set the scale for converting float to fixpoint representation.")
        self.ledScale.setText(str(1))
        self.ledScale.setEnabled(False)

        layHWI_WF = QHBoxLayout()
        layHWI_WF.addWidget(lbl_Q)
        layHWI_WF.addWidget(self.ledWI)
        layHWI_WF.addWidget(self.lblDot)
        layHWI_WF.addWidget(self.ledWF)
        layHWI_WF.addStretch()

        layHScale = QHBoxLayout()
        layHScale.addWidget(self.lblScale)
        layHScale.addWidget(self.ledScale)
        layHScale.addStretch()

        layHW_Scale = QHBoxLayout()
        layHW_Scale.addLayout(layHWI_WF)
        layHW_Scale.addLayout(layHScale)

        # -------------------------------------------------------------------
        # layGQOpt
        #
        # Quantization / Overflow / MSB / LSB settings
        # ---------------------------------------------------------------------
        lblQOvfl = QLabel("Ovfl.:", self)
        lblQOvfl.setFont(self.bifont)
        lblQuant = QLabel("Quant.:", self)
        lblQuant.setFont(self.bifont)

        self.cmbQOvfl = QComboBox(self)
        qOvfl = ['wrap', 'sat']
        self.cmbQOvfl.addItems(qOvfl)
        qset_cmb_box(self.cmbQOvfl, 'sat')
        self.cmbQOvfl.setToolTip("Select overflow behaviour.")
        # ComboBox size is adjusted automatically to fit the longest element
        self.cmbQOvfl.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        layHQOvflOpt = QHBoxLayout()
        layHQOvflOpt.addWidget(lblQOvfl)
        layHQOvflOpt.addWidget(self.cmbQOvfl)
        layHQOvflOpt.addStretch()

        self.cmbQuant = QComboBox(self)
        qQuant = ['none', 'round', 'fix', 'floor']
        self.cmbQuant.addItems(qQuant)
        qset_cmb_box(self.cmbQuant, 'round')
        self.cmbQuant.setToolTip("Select the kind of quantization.")
        self.cmbQuant.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        layHQuantOpt = QHBoxLayout()
        layHQuantOpt.addWidget(lblQuant)
        layHQuantOpt.addWidget(self.cmbQuant)
        layHQuantOpt.addStretch()

        self.butQuant = QPushButton(self)
        self.butQuant.setToolTip(
            "<span>Quantize selected coefficients / "
            "whole table with specified settings.</span>")
        self.butQuant.setIcon(QIcon(':/quantize.svg'))
        self.butQuant.setIconSize(q_icon_size)
        self.butQuant.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        lblMSBtxt = QLabel(self)
        lblMSBtxt.setText("<b><i>MSB</i><sub>10</sub> =</b>")
        self.lblMSB = QLabel(self)
        layHMSB = QHBoxLayout()
        layHMSB.addWidget(lblMSBtxt)
        layHMSB.addWidget(self.lblMSB)
        layHMSB.addStretch()

        lblLSBtxt = QLabel(self)
        lblLSBtxt.setText("<b><i>LSB</i><sub>10</sub> =</b>")
        self.lblLSB = QLabel(self)
        layHLSB = QHBoxLayout()
        layHLSB.addWidget(lblLSBtxt)
        layHLSB.addWidget(self.lblLSB)
        layHLSB.addStretch()

        layGQOpt = QGridLayout()
        layGQOpt.addLayout(layHQOvflOpt, 0, 0)
        layGQOpt.addLayout(layHQuantOpt, 0, 1)
        layGQOpt.addWidget(self.butQuant, 0, 2, Qt.AlignCenter)
        layGQOpt.addLayout(layHMSB, 1, 0)
        layGQOpt.addLayout(layHLSB, 1, 1)

        # -------------------------------------------------------------------
        #   Display MAX
        # ---------------------------------------------------------------------
        lblMAXtxt = QLabel(self)
        lblMAXtxt.setText("<b><i>Max =</i></b>")
        self.lblMAX = QLabel(self)

        layHCoeffs_MAX = QHBoxLayout()
        layHCoeffs_MAX.addWidget(lblMAXtxt)
        layHCoeffs_MAX.addWidget(self.lblMAX)
        layHCoeffs_MAX.addStretch()

        #######################################################################
        # Now put all the coefficient HBoxes into frmQSettings
        # ---------------------------------------------------------------------
        layVButtonsQ = QVBoxLayout()
        layVButtonsQ.addLayout(layHW_Scale)
        layVButtonsQ.addLayout(layGQOpt)
        layVButtonsQ.addLayout(layHCoeffs_MAX)
        layVButtonsQ.setContentsMargins(0, 0, 0, 0)
        # This frame encompasses all Quantization Settings
        self.frmQSettings = QFrame(self)
        self.frmQSettings.setLayout(layVButtonsQ)

        #######################################################################
        # ########################  Main UI Layout ############################
        #######################################################################
        # layout for frame (UI widget)
        layVMainF = QVBoxLayout()
        layVMainF.addLayout(layHDisplay)
        layVMainF.addWidget(self.frmQSettings)
        layVMainF.addWidget(QHLine())
        layVMainF.addWidget(self.frmButtonsCoeffs)

        # This frame encompasses all UI elements
        frmMain = QFrame(self)
        frmMain.setLayout(layVMainF)

        layVMain = QVBoxLayout()
        # the following affects only the first widget (intended here)
        layVMain.setAlignment(Qt.AlignTop)
        layVMain.addWidget(frmMain)
        layVMain.setContentsMargins(*params['wdg_margins'])
        self.setLayout(layVMain)
        #######################################################################

        # --- set initial values from dict ------------
        self.spnDigits.setValue(params['FMT_ba'])
        self.ledEps.setText(str(self.eps))

        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.but_csv_options.clicked.connect(self._open_csv_win)
Ejemplo n.º 28
0
    def _construct_UI(self):
        # ----------- ---------------------------------------------------
        # Run control widgets
        # ---------------------------------------------------------------
        self.chk_auto_run = QCheckBox("Auto", self)
        self.chk_auto_run.setObjectName("chk_auto_run")
        self.chk_auto_run.setToolTip(
            "<span>Update response automatically when "
            "parameters have been changed.</span>")
        self.chk_auto_run.setChecked(True)

        self.but_run = QPushButton(self)
        self.but_run.setText("RUN")
        self.but_run.setToolTip("Run simulation")
        self.but_run.setEnabled(not self.chk_auto_run.isChecked())

        self.cmb_sim_select = QComboBox(self)
        self.cmb_sim_select.addItems(["Float", "Fixpoint"])
        qset_cmb_box(self.cmb_sim_select, "Float")
        self.cmb_sim_select.setToolTip(
            "<span>Simulate floating-point or fixpoint response."
            "</span>")

        self.lbl_N_points = QLabel(to_html("N", frmt='bi') + " =", self)
        self.led_N_points = QLineEdit(self)
        self.led_N_points.setText(str(self.N))
        self.led_N_points.setToolTip(
            "<span>Number of displayed data points. "
            "<i>N</i> = 0 tries to choose for you.</span>")

        self.lbl_N_start = QLabel(to_html("N_0", frmt='bi') + " =", self)
        self.led_N_start = QLineEdit(self)
        self.led_N_start.setText(str(self.N_start))
        self.led_N_start.setToolTip("<span>First point to plot.</span>")

        self.chk_fx_scale = QCheckBox("Int. scale", self)
        self.chk_fx_scale.setObjectName("chk_fx_scale")
        self.chk_fx_scale.setToolTip(
            "<span>Display data with integer (fixpoint) scale.</span>")
        self.chk_fx_scale.setChecked(False)

        self.chk_stim_options = QCheckBox("Stim. Options", self)
        self.chk_stim_options.setObjectName("chk_stim_options")
        self.chk_stim_options.setToolTip("<span>Show stimulus options.</span>")
        self.chk_stim_options.setChecked(True)

        self.but_fft_win = QPushButton(self)
        self.but_fft_win.setText("WIN FFT")
        self.but_fft_win.setToolTip(
            "Show time and frequency response of FFT Window")
        self.but_fft_win.setCheckable(True)
        self.but_fft_win.setChecked(False)

        layH_ctrl_run = QHBoxLayout()
        layH_ctrl_run.addWidget(self.but_run)
        #layH_ctrl_run.addWidget(self.lbl_sim_select)
        layH_ctrl_run.addWidget(self.cmb_sim_select)
        layH_ctrl_run.addWidget(self.chk_auto_run)
        layH_ctrl_run.addStretch(1)
        layH_ctrl_run.addWidget(self.lbl_N_start)
        layH_ctrl_run.addWidget(self.led_N_start)
        layH_ctrl_run.addStretch(1)
        layH_ctrl_run.addWidget(self.lbl_N_points)
        layH_ctrl_run.addWidget(self.led_N_points)
        layH_ctrl_run.addStretch(2)
        layH_ctrl_run.addWidget(self.chk_fx_scale)
        layH_ctrl_run.addStretch(2)
        layH_ctrl_run.addWidget(self.chk_stim_options)
        layH_ctrl_run.addStretch(2)
        layH_ctrl_run.addWidget(self.but_fft_win)
        layH_ctrl_run.addStretch(10)

        #layH_ctrl_run.setContentsMargins(*params['wdg_margins'])

        self.wdg_ctrl_run = QWidget(self)
        self.wdg_ctrl_run.setLayout(layH_ctrl_run)
        # --- end of run control ----------------------------------------

        # ----------- ---------------------------------------------------
        # Controls for time domain
        # ---------------------------------------------------------------
        plot_styles_list = [
            "None", "Dots", "Line", "Line*", "Stem", "Stem*", "Step", "Step*"
        ]

        lbl_plt_time_title = QLabel("<b>View:</b>", self)
        lbl_plt_time_resp = QLabel("Response " + to_html("y", frmt='bi'), self)
        self.cmb_plt_time_resp = QComboBox(self)
        self.cmb_plt_time_resp.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_time_resp, self.plt_time_resp)
        self.cmb_plt_time_resp.setToolTip(
            "<span>Plot style for response.</span>")

        self.lbl_plt_time_stim = QLabel("Stimulus " + to_html("x", frmt='bi'),
                                        self)
        self.cmb_plt_time_stim = QComboBox(self)
        self.cmb_plt_time_stim.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_time_stim, self.plt_time_stim)
        self.cmb_plt_time_stim.setToolTip(
            "<span>Plot style for stimulus.</span>")

        self.lbl_plt_time_stmq = QLabel(
            "Fixp. Stim. " + to_html("x_Q", frmt='bi'), self)
        self.cmb_plt_time_stmq = QComboBox(self)
        self.cmb_plt_time_stmq.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_time_stmq, self.plt_time_stmq)
        self.cmb_plt_time_stmq.setToolTip(
            "<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>"
        )

        self.chk_log_time = QCheckBox("dB : min.", self)
        self.chk_log_time.setObjectName("chk_log_time")
        self.chk_log_time.setToolTip(
            "<span>Logarithmic scale for y-axis.</span>")
        self.chk_log_time.setChecked(False)

        self.led_log_bottom_time = QLineEdit(self)
        self.led_log_bottom_time.setText(str(self.bottom_t))
        self.led_log_bottom_time.setToolTip(
            "<span>Minimum display value for log. scale.</span>")
        self.led_log_bottom_time.setVisible(self.chk_log_time.isChecked())

        if not self.chk_log_time.isChecked():
            self.chk_log_time.setText("dB")
            self.bottom_t = 0

        self.chk_win_time = QCheckBox("FFT Window", self)
        self.chk_win_time.setObjectName("chk_win_time")
        self.chk_win_time.setToolTip(
            "<span>Show FFT windowing function.</span>")
        self.chk_win_time.setChecked(False)

        self.chk_fx_limits = QCheckBox("Min/max.", self)
        self.chk_fx_limits.setObjectName("chk_fx_limits")
        self.chk_fx_limits.setToolTip(
            "<span>Display limits of fixpoint range.</span>")
        self.chk_fx_limits.setChecked(False)

        layH_ctrl_time = QHBoxLayout()
        layH_ctrl_time.addWidget(lbl_plt_time_title)
        layH_ctrl_time.addStretch(1)
        layH_ctrl_time.addWidget(lbl_plt_time_resp)
        layH_ctrl_time.addWidget(self.cmb_plt_time_resp)
        layH_ctrl_time.addStretch(1)
        layH_ctrl_time.addWidget(self.lbl_plt_time_stim)
        layH_ctrl_time.addWidget(self.cmb_plt_time_stim)
        layH_ctrl_time.addStretch(1)
        layH_ctrl_time.addWidget(self.lbl_plt_time_stmq)
        layH_ctrl_time.addWidget(self.cmb_plt_time_stmq)
        layH_ctrl_time.addStretch(2)
        layH_ctrl_time.addWidget(self.chk_log_time)
        layH_ctrl_time.addWidget(self.led_log_bottom_time)
        layH_ctrl_time.addStretch(1)
        layH_ctrl_time.addWidget(self.chk_win_time)
        layH_ctrl_time.addStretch(2)
        layH_ctrl_time.addWidget(self.chk_fx_limits)
        layH_ctrl_time.addStretch(10)

        #layH_ctrl_time.setContentsMargins(*params['wdg_margins'])

        self.wdg_ctrl_time = QWidget(self)
        self.wdg_ctrl_time.setLayout(layH_ctrl_time)
        # ---- end time domain ------------------

        # ---------------------------------------------------------------
        # Controls for frequency domain
        # ---------------------------------------------------------------
        lbl_plt_freq_title = QLabel("<b>View:</b>", self)
        lbl_plt_freq_resp = QLabel("Response " + to_html("Y", frmt='bi'), self)
        self.cmb_plt_freq_resp = QComboBox(self)
        self.cmb_plt_freq_resp.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_freq_resp, self.plt_freq_resp)
        self.cmb_plt_freq_resp.setToolTip(
            "<span>Plot style for response.</span>")

        self.lbl_plt_freq_stim = QLabel("Stimulus " + to_html("X", frmt='bi'),
                                        self)
        self.cmb_plt_freq_stim = QComboBox(self)
        self.cmb_plt_freq_stim.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_freq_stim, self.plt_freq_stim)
        self.cmb_plt_freq_stim.setToolTip(
            "<span>Plot style for stimulus.</span>")

        self.lbl_plt_freq_stmq = QLabel(
            "Fixp. Stim. " + to_html("X_Q", frmt='bi'), self)
        self.cmb_plt_freq_stmq = QComboBox(self)
        self.cmb_plt_freq_stmq.addItems(plot_styles_list)
        qset_cmb_box(self.cmb_plt_freq_stmq, self.plt_freq_stmq)
        self.cmb_plt_freq_stmq.setToolTip(
            "<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>"
        )

        self.chk_log_freq = QCheckBox("dB : min.", self)
        self.chk_log_freq.setObjectName("chk_log_freq")
        self.chk_log_freq.setToolTip(
            "<span>Logarithmic scale for y-axis.</span>")
        self.chk_log_freq.setChecked(True)

        self.led_log_bottom_freq = QLineEdit(self)
        self.led_log_bottom_freq.setText(str(self.bottom_f))
        self.led_log_bottom_freq.setToolTip(
            "<span>Minimum display value for log. scale.</span>")
        self.led_log_bottom_freq.setVisible(self.chk_log_freq.isChecked())

        if not self.chk_log_freq.isChecked():
            self.chk_log_freq.setText("dB")
            self.bottom_f = 0

        self.lbl_win_fft = QLabel("Window: ", self)
        self.cmb_win_fft = QComboBox(self)
        self.cmb_win_fft.addItems(get_window_names())
        self.cmb_win_fft.setToolTip("FFT window type.")
        qset_cmb_box(self.cmb_win_fft, self.window_name)

        self.cmb_win_fft_variant = QComboBox(self)
        self.cmb_win_fft_variant.setToolTip("FFT window variant.")
        self.cmb_win_fft_variant.setVisible(False)

        self.lblWinPar1 = QLabel("Param1")
        self.ledWinPar1 = QLineEdit(self)
        self.ledWinPar1.setText("1")
        self.ledWinPar1.setObjectName("ledWinPar1")

        self.lblWinPar2 = QLabel("Param2")
        self.ledWinPar2 = QLineEdit(self)
        self.ledWinPar2.setText("2")
        self.ledWinPar2.setObjectName("ledWinPar2")

        self.chk_Hf = QCheckBox(self)
        self.chk_Hf.setObjectName("chk_Hf")
        self.chk_Hf.setToolTip(
            "<span>Show ideal frequency response, calculated "
            "from the filter coefficients.</span>")
        self.chk_Hf.setChecked(False)
        self.chk_Hf_lbl = QLabel(to_html("H_id (f)", frmt="bi"), self)

        layH_ctrl_freq = QHBoxLayout()
        layH_ctrl_freq.addWidget(lbl_plt_freq_title)
        layH_ctrl_freq.addStretch(1)
        layH_ctrl_freq.addWidget(lbl_plt_freq_resp)
        layH_ctrl_freq.addWidget(self.cmb_plt_freq_resp)
        layH_ctrl_freq.addStretch(1)
        layH_ctrl_freq.addWidget(self.lbl_plt_freq_stim)
        layH_ctrl_freq.addWidget(self.cmb_plt_freq_stim)
        layH_ctrl_freq.addStretch(1)
        layH_ctrl_freq.addWidget(self.lbl_plt_freq_stmq)
        layH_ctrl_freq.addWidget(self.cmb_plt_freq_stmq)
        layH_ctrl_freq.addStretch(2)
        layH_ctrl_freq.addWidget(self.chk_log_freq)
        layH_ctrl_freq.addWidget(self.led_log_bottom_freq)
        layH_ctrl_freq.addStretch(2)
        layH_ctrl_freq.addWidget(self.lbl_win_fft)
        layH_ctrl_freq.addWidget(self.cmb_win_fft)
        layH_ctrl_freq.addWidget(self.cmb_win_fft_variant)
        layH_ctrl_freq.addWidget(self.lblWinPar1)
        layH_ctrl_freq.addWidget(self.ledWinPar1)
        layH_ctrl_freq.addWidget(self.lblWinPar2)
        layH_ctrl_freq.addWidget(self.ledWinPar2)
        layH_ctrl_freq.addStretch(2)
        layH_ctrl_freq.addWidget(self.chk_Hf)
        layH_ctrl_freq.addWidget(self.chk_Hf_lbl)
        layH_ctrl_freq.addStretch(10)

        #layH_ctrl_freq.setContentsMargins(*params['wdg_margins'])

        self.wdg_ctrl_freq = QWidget(self)
        self.wdg_ctrl_freq.setLayout(layH_ctrl_freq)
        # ---- end Frequency Domain ------------------

        # ---------------------------------------------------------------
        # Controls for stimuli
        # ---------------------------------------------------------------

        lbl_title_stim = QLabel("<b>Stimulus:</b>", self)

        self.lblStimulus = QLabel(to_html("Shape ", frmt='bi'), self)
        self.cmbStimulus = QComboBox(self)
        self.cmbStimulus.addItems([
            "None", "Pulse", "Step", "StepErr", "Cos", "Sine", "Triang", "Saw",
            "Rect", "Comb", "AM", "FM", "PM"
        ])
        self.cmbStimulus.setToolTip("Stimulus type.")
        qset_cmb_box(self.cmbStimulus, self.stim)

        self.chk_stim_bl = QCheckBox("BL", self)
        self.chk_stim_bl.setToolTip(
            "<span>The signal is bandlimited to the Nyquist frequency "
            "to avoid aliasing. However, it is much slower to generate "
            "than the regular version.</span>")
        self.chk_stim_bl.setChecked(True)
        self.chk_stim_bl.setObjectName("stim_bl")

        self.chk_scale_impz_f = QCheckBox("Scale", self)
        self.chk_scale_impz_f.setToolTip(
            "<span>Scale the FFT of the impulse response with <i>N<sub>FFT</sub></i> "
            "so that it has the same magnitude as |H(f)|. DC and Noise need to be "
            "turned off.</span>")
        self.chk_scale_impz_f.setChecked(True)
        self.chk_scale_impz_f.setObjectName("scale_impz_f")

        self.lblDC = QLabel(to_html("DC =", frmt='bi'), self)
        self.ledDC = QLineEdit(self)
        self.ledDC.setText(str(self.DC))
        self.ledDC.setToolTip("DC Level")
        self.ledDC.setObjectName("stimDC")

        layHCmbStim = QHBoxLayout()
        layHCmbStim.addWidget(self.cmbStimulus)
        layHCmbStim.addWidget(self.chk_stim_bl)
        layHCmbStim.addWidget(self.chk_scale_impz_f)

        layVlblCmbDC = QVBoxLayout()
        layVlblCmbDC.addWidget(self.lblStimulus)
        layVlblCmbDC.addWidget(self.lblDC)
        #layVlblAmp.setAlignment(Qt.AlignTop)

        layVCmbDC = QVBoxLayout()
        layVCmbDC.addLayout(layHCmbStim)
        layVCmbDC.addWidget(self.ledDC)

        #----------------------------------------------
        self.lblAmp1 = QLabel(to_html("A_1", frmt='bi') + " =", self)
        self.ledAmp1 = QLineEdit(self)
        self.ledAmp1.setText(str(self.A1))
        self.ledAmp1.setToolTip("Stimulus amplitude")
        self.ledAmp1.setObjectName("stimAmp1")

        self.lblAmp2 = QLabel(to_html("A_2", frmt='bi') + " =", self)
        self.ledAmp2 = QLineEdit(self)
        self.ledAmp2.setText(str(self.A2))
        self.ledAmp2.setToolTip("Stimulus amplitude 2")
        self.ledAmp2.setObjectName("stimAmp2")

        layVlblAmp = QVBoxLayout()
        layVlblAmp.addWidget(self.lblAmp1)
        layVlblAmp.addWidget(self.lblAmp2)
        #layVlblAmp.setAlignment(Qt.AlignTop) # labels are aligned incorrectly

        layVledAmp = QVBoxLayout()
        layVledAmp.addWidget(self.ledAmp1)
        layVledAmp.addWidget(self.ledAmp2)
        #layVledAmp.setAlignment(Qt.AlignTop)

        #----------------------------------------------
        self.lblPhi1 = QLabel(to_html("&phi;_1", frmt='bi') + " =", self)
        self.ledPhi1 = QLineEdit(self)
        self.ledPhi1.setText(str(self.phi1))
        self.ledPhi1.setToolTip("Stimulus phase")
        self.ledPhi1.setObjectName("stimPhi1")
        self.lblPhU1 = QLabel(to_html("&deg;", frmt='b'), self)

        self.lblPhi2 = QLabel(to_html("&phi;_2", frmt='bi') + " =", self)
        self.ledPhi2 = QLineEdit(self)
        self.ledPhi2.setText(str(self.phi2))
        self.ledPhi2.setToolTip("Stimulus phase 2")
        self.ledPhi2.setObjectName("stimPhi2")
        self.lblPhU2 = QLabel(to_html("&deg;", frmt='b'), self)

        layVlblPhi = QVBoxLayout()
        layVlblPhi.addWidget(self.lblPhi1)
        layVlblPhi.addWidget(self.lblPhi2)

        layVledPhi = QVBoxLayout()
        layVledPhi.addWidget(self.ledPhi1)
        layVledPhi.addWidget(self.ledPhi2)

        layVlblPhU = QVBoxLayout()
        layVlblPhU.addWidget(self.lblPhU1)
        layVlblPhU.addWidget(self.lblPhU2)

        #----------------------------------------------
        self.lblFreq1 = QLabel(to_html("f_1", frmt='bi') + " =", self)
        self.ledFreq1 = QLineEdit(self)
        self.ledFreq1.setText(str(self.f1))
        self.ledFreq1.setToolTip("Stimulus frequency 1")
        self.ledFreq1.setObjectName("stimFreq1")
        self.lblFreqUnit1 = QLabel("f_S", self)

        self.lblFreq2 = QLabel(to_html("f_2", frmt='bi') + " =", self)
        self.ledFreq2 = QLineEdit(self)
        self.ledFreq2.setText(str(self.f2))
        self.ledFreq2.setToolTip("Stimulus frequency 2")
        self.ledFreq2.setObjectName("stimFreq2")
        self.lblFreqUnit2 = QLabel("f_S", self)
        layVlblfreq = QVBoxLayout()
        layVlblfreq.addWidget(self.lblFreq1)
        layVlblfreq.addWidget(self.lblFreq2)

        layVledfreq = QVBoxLayout()
        layVledfreq.addWidget(self.ledFreq1)
        layVledfreq.addWidget(self.ledFreq2)

        layVlblfreqU = QVBoxLayout()
        layVlblfreqU.addWidget(self.lblFreqUnit1)
        layVlblfreqU.addWidget(self.lblFreqUnit2)

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

        self.lblNoise = QLabel(to_html("Noise: ", frmt='bi'), self)
        self.cmbNoise = QComboBox(self)
        self.cmbNoise.addItems(["None", "Gauss", "Uniform", "PRBS"])
        self.cmbNoise.setToolTip("Type of additive noise.")
        qset_cmb_box(self.cmbNoise, self.noise)

        self.lblNoi = QLabel("not initialized", self)
        self.ledNoi = QLineEdit(self)
        self.ledNoi.setText(str(self.noi))
        self.ledNoi.setToolTip("not initialized")
        self.ledNoi.setObjectName("stimNoi")

        layVlblNoi = QVBoxLayout()
        layVlblNoi.addWidget(self.lblNoise)
        layVlblNoi.addWidget(self.lblNoi)

        layVcmbledNoi = QVBoxLayout()
        layVcmbledNoi.addWidget(self.cmbNoise)
        layVcmbledNoi.addWidget(self.ledNoi)

        #----------------------------------------------
        #layG_ctrl_stim = QGridLayout()
        layH_ctrl_stim = QHBoxLayout()
        layH_ctrl_stim.addWidget(lbl_title_stim)
        layH_ctrl_stim.addStretch(1)
        layH_ctrl_stim.addLayout(layVlblCmbDC)
        layH_ctrl_stim.addLayout(layVCmbDC)
        layH_ctrl_stim.addStretch(1)
        layH_ctrl_stim.addLayout(layVlblAmp)
        layH_ctrl_stim.addLayout(layVledAmp)
        layH_ctrl_stim.addLayout(layVlblPhi)
        layH_ctrl_stim.addLayout(layVledPhi)
        layH_ctrl_stim.addLayout(layVlblPhU)
        layH_ctrl_stim.addStretch(1)
        layH_ctrl_stim.addLayout(layVlblfreq)
        layH_ctrl_stim.addLayout(layVledfreq)
        layH_ctrl_stim.addLayout(layVlblfreqU)
        layH_ctrl_stim.addStretch(1)
        layH_ctrl_stim.addLayout(layVlblNoi)
        layH_ctrl_stim.addLayout(layVcmbledNoi)
        layH_ctrl_stim.addStretch(10)

        self.wdg_ctrl_stim = QWidget(self)
        self.wdg_ctrl_stim.setLayout(layH_ctrl_stim)
        # --------- end stimuli ---------------------------------

        # frequency widgets require special handling as they are scaled with f_s
        self.ledFreq1.installEventFilter(self)
        self.ledFreq2.installEventFilter(self)

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        # --- run control ---
        self.led_N_start.editingFinished.connect(self.update_N)
        self.led_N_points.editingFinished.connect(self.update_N)

        # --- frequency control ---
        # careful! currentIndexChanged passes the current index to _update_win_fft
        self.cmb_win_fft.currentIndexChanged.connect(self._update_win_fft)
        self.ledWinPar1.editingFinished.connect(self._read_param1)
        self.ledWinPar2.editingFinished.connect(self._read_param2)

        # --- stimulus control ---
        self.chk_stim_options.clicked.connect(self._show_stim_options)

        self.chk_stim_bl.clicked.connect(self._enable_stim_widgets)
        self.cmbStimulus.currentIndexChanged.connect(self._enable_stim_widgets)

        self.cmbNoise.currentIndexChanged.connect(self._update_noi)
        self.ledNoi.editingFinished.connect(self._update_noi)
        self.ledAmp1.editingFinished.connect(self._update_amp1)
        self.ledAmp2.editingFinished.connect(self._update_amp2)
        self.ledPhi1.editingFinished.connect(self._update_phi1)
        self.ledPhi2.editingFinished.connect(self._update_phi2)
        self.ledDC.editingFinished.connect(self._update_DC)
Ejemplo n.º 29
0
class Firwin(QWidget):

    FRMT = 'ba'  # output format(s) of filter design routines 'zpk' / 'ba' / 'sos'
    # currently, only 'ba' is supported for firwin routines

    sig_tx = pyqtSignal(object)

    def __init__(self):
        QWidget.__init__(self)

        self.ft = 'FIR'
        self.fft_window = None
        # dictionary for firwin window settings
        self.win_dict = fb.fil[0]['win_fir']

        c = Common()
        self.rt_dict = c.rt_base_iir

        self.rt_dict_add = {
            'COM': {
                'min': {
                    'msg':
                    ('a',
                     r"<br /><b>Note:</b> Filter order is only a rough approximation "
                     "and most likely far too low!")
                },
                'man': {
                    'msg':
                    ('a', r"Enter desired filter order <b><i>N</i></b> and "
                     "<b>-6 dB</b> pass band corner "
                     "frequency(ies) <b><i>F<sub>C</sub></i></b> .")
                },
            },
            'LP': {
                'man': {},
                'min': {}
            },
            'HP': {
                'man': {
                    'msg': ('a', r"<br /><b>Note:</b> Order needs to be odd!")
                },
                'min': {}
            },
            'BS': {
                'man': {
                    'msg': ('a', r"<br /><b>Note:</b> Order needs to be odd!")
                },
                'min': {}
            },
            'BP': {
                'man': {},
                'min': {}
            },
        }

        self.info = """**Windowed FIR filters**
        
        are designed by truncating the
        infinite impulse response of an ideal filter with a window function.
        The kind of used window has strong influence on ripple etc. of the
        resulting filter.
        
        **Design routines:**

        ``scipy.signal.firwin()``

        """
        #self.info_doc = [] is set in self._update_UI()

        #------------------- end of static info for filter tree ---------------

        #----------------------------------------------------------------------
    def construct_UI(self):
        """
        Create additional subwidget(s) needed for filter design:
        These subwidgets are instantiated dynamically when needed in 
        select_filter.py using the handle to the filter object, fb.filObj .
        """

        # Combobox for selecting the algorithm to estimate minimum filter order
        self.cmb_firwin_alg = QComboBox(self)
        self.cmb_firwin_alg.setObjectName('wdg_cmb_firwin_alg')
        self.cmb_firwin_alg.addItems(['ichige', 'kaiser', 'herrmann'])
        # Minimum size, can be changed in the upper hierarchy levels using layouts:
        self.cmb_firwin_alg.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.cmb_firwin_alg.hide()

        # Combobox for selecting the window used for filter design
        self.cmb_firwin_win = QComboBox(self)
        self.cmb_firwin_win.addItems(get_window_names())
        self.cmb_firwin_win.setObjectName('wdg_cmb_firwin_win')

        # Minimum size, can be changed in the upper hierarchy levels using layouts:
        self.cmb_firwin_win.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.but_fft_win = QPushButton(self)
        self.but_fft_win.setText("WIN FFT")
        self.but_fft_win.setToolTip(
            "Show time and frequency response of FFT Window")
        self.but_fft_win.setCheckable(True)
        self.but_fft_win.setChecked(False)

        self.lblWinPar1 = QLabel("a", self)
        self.lblWinPar1.setObjectName('wdg_lbl_firwin_1')
        self.ledWinPar1 = QLineEdit(self)
        self.ledWinPar1.setText("0.5")
        self.ledWinPar1.setObjectName('wdg_led_firwin_1')
        self.lblWinPar1.setVisible(False)
        self.ledWinPar1.setVisible(False)

        self.lblWinPar2 = QLabel("b", self)
        self.lblWinPar2.setObjectName('wdg_lbl_firwin_2')
        self.ledWinPar2 = QLineEdit(self)
        self.ledWinPar2.setText("0.5")
        self.ledWinPar2.setObjectName('wdg_led_firwin_2')
        self.ledWinPar2.setVisible(False)
        self.lblWinPar2.setVisible(False)

        self.layHWin1 = QHBoxLayout()
        self.layHWin1.addWidget(self.cmb_firwin_win)
        self.layHWin1.addWidget(self.but_fft_win)
        self.layHWin1.addWidget(self.cmb_firwin_alg)
        self.layHWin2 = QHBoxLayout()
        self.layHWin2.addWidget(self.lblWinPar1)
        self.layHWin2.addWidget(self.ledWinPar1)
        self.layHWin2.addWidget(self.lblWinPar2)
        self.layHWin2.addWidget(self.ledWinPar2)

        self.layVWin = QVBoxLayout()
        self.layVWin.addLayout(self.layHWin1)
        self.layVWin.addLayout(self.layHWin2)
        self.layVWin.setContentsMargins(0, 0, 0, 0)

        # Widget containing all subwidgets (cmbBoxes, Labels, lineEdits)
        self.wdg_fil = QWidget(self)
        self.wdg_fil.setObjectName('wdg_fil')
        self.wdg_fil.setLayout(self.layVWin)

        #----------------------------------------------------------------------
        # SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.cmb_firwin_alg.activated.connect(self._update_win_fft)
        self.cmb_firwin_win.activated.connect(self._update_win_fft)
        self.ledWinPar1.editingFinished.connect(self._read_param1)
        self.ledWinPar2.editingFinished.connect(self._read_param2)

        self.but_fft_win.clicked.connect(self.show_fft_win)
        #----------------------------------------------------------------------

        self._load_dict()  # get initial / last setting from dictionary
        self._update_win_fft()

#=============================================================================
# Copied from impz()
#==============================================================================

    def _read_param1(self):
        """Read out textbox when editing is finished and update dict and fft window"""
        param = safe_eval(self.ledWinPar1.text(),
                          self.win_dict['par'][0]['val'],
                          sign='pos',
                          return_type='float')
        if param < self.win_dict['par'][0]['min']:
            param = self.win_dict['par'][0]['min']
        elif param > self.win_dict['par'][0]['max']:
            param = self.win_dict['par'][0]['max']
        self.ledWinPar1.setText(str(param))
        self.win_dict['par'][0]['val'] = param
        self._update_win_fft()

    def _read_param2(self):
        """Read out textbox when editing is finished and update dict and fft window"""
        param = safe_eval(self.ledWinPar2.text(),
                          self.win_dict['par'][1]['val'],
                          return_type='float')
        if param < self.win_dict['par'][1]['min']:
            param = self.win_dict['par'][1]['min']
        elif param > self.win_dict['par'][1]['max']:
            param = self.win_dict['par'][1]['max']
        self.ledWinPar2.setText(str(param))
        self.win_dict['par'][1]['val'] = param
        self._update_win_fft()

    def _update_win_fft(self):
        """ Update window type for FirWin """
        self.alg = str(self.cmb_firwin_alg.currentText())
        self.fir_window_name = qget_cmb_box(self.cmb_firwin_win, data=False)
        self.win = calc_window_function(self.win_dict,
                                        self.fir_window_name,
                                        N=self.N,
                                        sym=True)
        n_par = self.win_dict['n_par']

        self.lblWinPar1.setVisible(n_par > 0)
        self.ledWinPar1.setVisible(n_par > 0)
        self.lblWinPar2.setVisible(n_par > 1)
        self.ledWinPar2.setVisible(n_par > 1)

        if n_par > 0:
            self.lblWinPar1.setText(
                to_html(self.win_dict['par'][0]['name'] + " =", frmt='bi'))
            self.ledWinPar1.setText(str(self.win_dict['par'][0]['val']))
            self.ledWinPar1.setToolTip(self.win_dict['par'][0]['tooltip'])

        if n_par > 1:
            self.lblWinPar2.setText(
                to_html(self.win_dict['par'][1]['name'] + " =", frmt='bi'))
            self.ledWinPar2.setText(str(self.win_dict['par'][1]['val']))
            self.ledWinPar2.setToolTip(self.win_dict['par'][1]['tooltip'])

        # sig_tx -> select_filter -> filter_specs
        self.sig_tx.emit({'sender': __name__, 'filt_changed': 'firwin'})

#=============================================================================

    def _load_dict(self):
        """
        Reload window selection and parameters from filter dictionary
        and set UI elements accordingly. load_dict() is called upon 
        initialization and when the filter is loaded from disk.
        """
        self.N = fb.fil[0]['N']
        win_idx = 0
        alg_idx = 0
        if 'wdg_fil' in fb.fil[0] and 'firwin' in fb.fil[0]['wdg_fil']:
            wdg_fil_par = fb.fil[0]['wdg_fil']['firwin']

            if 'win' in wdg_fil_par:
                if np.isscalar(
                        wdg_fil_par['win']):  # true for strings (non-vectors)
                    window = wdg_fil_par['win']
                else:
                    window = wdg_fil_par['win'][0]
                    self.ledWinPar1.setText(str(wdg_fil_par['win'][1]))
                    if len(wdg_fil_par['win']) > 2:
                        self.ledWinPar2.setText(str(wdg_fil_par['win'][2]))

                # find index for window string
                win_idx = self.cmb_firwin_win.findText(
                    window, Qt.MatchFixedString)  # case insensitive flag
                if win_idx == -1:  # Key does not exist, use first entry instead
                    win_idx = 0

            if 'alg' in wdg_fil_par:
                alg_idx = self.cmb_firwin_alg.findText(wdg_fil_par['alg'],
                                                       Qt.MatchFixedString)
                if alg_idx == -1:  # Key does not exist, use first entry instead
                    alg_idx = 0

        self.cmb_firwin_win.setCurrentIndex(
            win_idx)  # set index for window and
        self.cmb_firwin_alg.setCurrentIndex(alg_idx)  # and algorithm cmbBox

    def _store_entries(self):
        """
        Store window and alg. selection and parameter settings (part of 
        self.firWindow, if any) in filter dictionary.
        """
        if not 'wdg_fil' in fb.fil[0]:
            fb.fil[0].update({'wdg_fil': {}})
        fb.fil[0]['wdg_fil'].update(
            {'firwin': {
                'win': self.firWindow,
                'alg': self.alg
            }})

    def _get_params(self, fil_dict):
        """
        Translate parameters from the passed dictionary to instance
        parameters, scaling / transforming them if needed.
        """
        self.N = fil_dict['N']
        self.F_PB = fil_dict['F_PB']
        self.F_SB = fil_dict['F_SB']
        self.F_PB2 = fil_dict['F_PB2']
        self.F_SB2 = fil_dict['F_SB2']
        self.F_C = fil_dict['F_C']
        self.F_C2 = fil_dict['F_C2']

        # firwin amplitude specs are linear (not in dBs)
        self.A_PB = fil_dict['A_PB']
        self.A_PB2 = fil_dict['A_PB2']
        self.A_SB = fil_dict['A_SB']
        self.A_SB2 = fil_dict['A_SB2']

#        self.alg = 'ichige' # algorithm for determining the minimum order
#        self.alg = self.cmb_firwin_alg.currentText()

    def _test_N(self):
        """
        Warn the user if the calculated order is too high for a reasonable filter
        design.
        """
        if self.N > 1000:
            return qfilter_warning(self, self.N, "FirWin")
        else:
            return True

    def _save(self, fil_dict, arg):
        """
        Convert between poles / zeros / gain, filter coefficients (polynomes)
        and second-order sections and store all available formats in the passed
        dictionary 'fil_dict'.
        """
        fil_save(fil_dict, arg, self.FRMT, __name__)

        try:  # has the order been calculated by a "min" filter design?
            fil_dict['N'] = self.N  # yes, update filterbroker
        except AttributeError:
            pass
#        self._store_entries()

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

    def firwin(self,
               numtaps,
               cutoff,
               window=None,
               pass_zero=True,
               scale=True,
               nyq=1.0,
               fs=None):
        """
        FIR filter design using the window method. This is more or less the 
        same as `scipy.signal.firwin` with the exception that an ndarray with 
        the window values can be passed as an alternative to the window name.
        
        The parameters "width" (specifying a Kaiser window) and "fs" have been
        omitted, they are not needed here.

        This function computes the coefficients of a finite impulse response
        filter.  The filter will have linear phase; it will be Type I if
        `numtaps` is odd and Type II if `numtaps` is even.
        Type II filters always have zero response at the Nyquist rate, so a
        ValueError exception is raised if firwin is called with `numtaps` even and
        having a passband whose right end is at the Nyquist rate.
        
        Parameters
        ----------
        numtaps : int
            Length of the filter (number of coefficients, i.e. the filter
            order + 1).  `numtaps` must be even if a passband includes the
            Nyquist frequency.
        cutoff : float or 1D array_like
            Cutoff frequency of filter (expressed in the same units as `nyq`)
            OR an array of cutoff frequencies (that is, band edges). In the
            latter case, the frequencies in `cutoff` should be positive and
            monotonically increasing between 0 and `nyq`.  The values 0 and
            `nyq` must not be included in `cutoff`.
        window : ndarray or string
            string: use the window with the passed name from scipy.signal.windows
            
            ndarray: The window values - this is an addition to the original 
            firwin routine.
        pass_zero : bool, optional
            If True, the gain at the frequency 0 (i.e. the "DC gain") is 1.
            Otherwise the DC gain is 0.
        scale : bool, optional
            Set to True to scale the coefficients so that the frequency
            response is exactly unity at a certain frequency.
            That frequency is either:
            - 0 (DC) if the first passband starts at 0 (i.e. pass_zero
              is True)
            - `nyq` (the Nyquist rate) if the first passband ends at
              `nyq` (i.e the filter is a single band highpass filter);
              center of first passband otherwise
        nyq : float, optional
            Nyquist frequency.  Each frequency in `cutoff` must be between 0
            and `nyq`.
        Returns
        -------
        h : (numtaps,) ndarray
            Coefficients of length `numtaps` FIR filter.
        Raises
        ------
        ValueError
            If any value in `cutoff` is less than or equal to 0 or greater
            than or equal to `nyq`, if the values in `cutoff` are not strictly
            monotonically increasing, or if `numtaps` is even but a passband
            includes the Nyquist frequency.
        See also
        --------
        scipy.firwin
        """
        cutoff = np.atleast_1d(cutoff) / float(nyq)

        # Check for invalid input.
        if cutoff.ndim > 1:
            raise ValueError("The cutoff argument must be at most "
                             "one-dimensional.")
        if cutoff.size == 0:
            raise ValueError("At least one cutoff frequency must be given.")
        if cutoff.min() <= 0 or cutoff.max() >= 1:
            raise ValueError(
                "Invalid cutoff frequency {0}: frequencies must be "
                "greater than 0 and less than nyq.".format(cutoff))
        if np.any(np.diff(cutoff) <= 0):
            raise ValueError("Invalid cutoff frequencies: the frequencies "
                             "must be strictly increasing.")

        pass_nyquist = bool(cutoff.size & 1) ^ pass_zero
        if pass_nyquist and numtaps % 2 == 0:
            raise ValueError(
                "A filter with an even number of coefficients must "
                "have zero response at the Nyquist rate.")

        # Insert 0 and/or 1 at the ends of cutoff so that the length of cutoff
        # is even, and each pair in cutoff corresponds to passband.
        cutoff = np.hstack(([0.0] * pass_zero, cutoff, [1.0] * pass_nyquist))

        # `bands` is a 2D array; each row gives the left and right edges of
        # a passband.
        bands = cutoff.reshape(-1, 2)

        # Build up the coefficients.
        alpha = 0.5 * (numtaps - 1)
        m = np.arange(0, numtaps) - alpha
        h = 0
        for left, right in bands:
            h += right * sinc(right * m)
            h -= left * sinc(left * m)

        if type(window) == str:
            # Get and apply the window function.
            from scipy.signal.signaltools import get_window
            win = get_window(window, numtaps, fftbins=False)
        elif type(window) == np.ndarray:
            win = window
        else:
            logger.error(
                "The 'window' was neither a string nor a numpy array, it could not be evaluated."
            )
            return None
        # apply the window function.
        h *= win

        # Now handle scaling if desired.
        if scale:
            # Get the first passband.
            left, right = bands[0]
            if left == 0:
                scale_frequency = 0.0
            elif right == 1:
                scale_frequency = 1.0
            else:
                scale_frequency = 0.5 * (left + right)
            c = np.cos(np.pi * m * scale_frequency)
            s = np.sum(h * c)
            h /= s

        return h

    def _firwin_ord(self, F, W, A, alg):
        #http://www.mikroe.com/chapters/view/72/chapter-2-fir-filters/
        delta_f = abs(F[1] - F[0]) * 2  # referred to f_Ny
        delta_A = np.sqrt(A[0] * A[1])
        if self.fir_window_name == 'kaiser':
            N, beta = sig.kaiserord(20 * np.log10(np.abs(fb.fil[0]['A_SB'])),
                                    delta_f)
            self.ledWinPar1.setText(str(beta))
            fb.fil[0]['wdg_fil'][1] = beta
            self._update_UI()
        else:
            N = remezord(F, W, A, fs=1, alg=alg)[0]

        return N

    def LPmin(self, fil_dict):
        self._get_params(fil_dict)
        self.N = self._firwin_ord([self.F_PB, self.F_SB], [1, 0],
                                  [self.A_PB, self.A_SB],
                                  alg=self.alg)
        if not self._test_N():
            return -1
        self.fir_window = calc_window_function(self.win_dict,
                                               self.fir_window_name,
                                               N=self.N,
                                               sym=True)
        fil_dict['F_C'] = (self.F_SB + self.F_PB
                           ) / 2  # use average of calculated F_PB and F_SB
        self._save(
            fil_dict,
            self.firwin(self.N,
                        fil_dict['F_C'],
                        window=self.fir_window,
                        nyq=0.5))

    def LPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self.fir_window = calc_window_function(self.win_dict,
                                               self.fir_window_name,
                                               N=self.N,
                                               sym=True)
        self._save(
            fil_dict,
            self.firwin(self.N,
                        fil_dict['F_C'],
                        window=self.fir_window,
                        nyq=0.5))

    def HPmin(self, fil_dict):
        self._get_params(fil_dict)
        N = self._firwin_ord([self.F_SB, self.F_PB], [0, 1],
                             [self.A_SB, self.A_PB],
                             alg=self.alg)
        self.N = round_odd(N)  # enforce odd order
        if not self._test_N():
            return -1
        self.fir_window = calc_window_function(self.win_dict,
                                               self.fir_window_name,
                                               N=self.N,
                                               sym=True)
        fil_dict['F_C'] = (self.F_SB + self.F_PB
                           ) / 2  # use average of calculated F_PB and F_SB
        self._save(
            fil_dict,
            self.firwin(self.N,
                        fil_dict['F_C'],
                        window=self.fir_window,
                        pass_zero=False,
                        nyq=0.5))

    def HPman(self, fil_dict):
        self._get_params(fil_dict)
        self.N = round_odd(self.N)  # enforce odd order
        if not self._test_N():
            return -1
        self.fir_window = calc_window_function(self.win_dict,
                                               self.fir_window_name,
                                               N=self.N,
                                               sym=True)
        self._save(
            fil_dict,
            self.firwin(self.N,
                        fil_dict['F_C'],
                        window=self.fir_window,
                        pass_zero=False,
                        nyq=0.5))

    # For BP and BS, F_PB and F_SB have two elements each
    def BPmin(self, fil_dict):
        self._get_params(fil_dict)
        self.N = remezord([self.F_SB, self.F_PB, self.F_PB2, self.F_SB2],
                          [0, 1, 0], [self.A_SB, self.A_PB, self.A_SB2],
                          fs=1,
                          alg=self.alg)[0]
        if not self._test_N():
            return -1
        self.fir_window = calc_window_function(self.win_dict,
                                               self.fir_window_name,
                                               N=self.N,
                                               sym=True)

        fil_dict['F_C'] = (self.F_SB + self.F_PB
                           ) / 2  # use average of calculated F_PB and F_SB
        fil_dict['F_C2'] = (self.F_SB2 + self.F_PB2
                            ) / 2  # use average of calculated F_PB and F_SB
        self._save(
            fil_dict,
            self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']],
                        window=self.fir_window,
                        pass_zero=False,
                        nyq=0.5))

    def BPman(self, fil_dict):
        self._get_params(fil_dict)
        if not self._test_N():
            return -1
        self.fir_window = calc_window_function(self.win_dict,
                                               self.fir_window_name,
                                               N=self.N,
                                               sym=True)
        self._save(
            fil_dict,
            self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']],
                        window=self.fir_window,
                        pass_zero=False,
                        nyq=0.5))

    def BSmin(self, fil_dict):
        self._get_params(fil_dict)
        N = remezord([self.F_PB, self.F_SB, self.F_SB2, self.F_PB2], [1, 0, 1],
                     [self.A_PB, self.A_SB, self.A_PB2],
                     fs=1,
                     alg=self.alg)[0]
        self.N = round_odd(N)  # enforce odd order
        if not self._test_N():
            return -1
        self.fir_window = calc_window_function(self.win_dict,
                                               self.fir_window_name,
                                               N=self.N,
                                               sym=True)
        fil_dict['F_C'] = (self.F_SB + self.F_PB
                           ) / 2  # use average of calculated F_PB and F_SB
        fil_dict['F_C2'] = (self.F_SB2 + self.F_PB2
                            ) / 2  # use average of calculated F_PB and F_SB
        self._save(
            fil_dict,
            self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']],
                        window=self.fir_window,
                        pass_zero=True,
                        nyq=0.5))

    def BSman(self, fil_dict):
        self._get_params(fil_dict)
        self.N = round_odd(self.N)  # enforce odd order
        if not self._test_N():
            return -1
        self.fir_window = calc_window_function(self.win_dict,
                                               self.fir_window_name,
                                               N=self.N,
                                               sym=True)
        self._save(
            fil_dict,
            self.firwin(self.N, [fil_dict['F_C'], fil_dict['F_C2']],
                        window=self.fir_window,
                        pass_zero=True,
                        nyq=0.5))

    #------------------------------------------------------------------------------
    def show_fft_win(self):
        """
        Pop-up FFT window
        """
        if self.but_fft_win.isChecked():
            qstyle_widget(self.but_fft_win, "changed")
        else:
            qstyle_widget(self.but_fft_win, "normal")

        if self.fft_window is None:  # no handle to the window? Create a new instance
            if self.but_fft_win.isChecked():
                # important: Handle to window must be class attribute
                # pass the name of the dictionary where parameters are stored and
                # whether a symmetric window or one that can be continued periodically
                # will be constructed
                self.fft_window = Plot_FFT_win(self,
                                               win_dict=self.win_dict,
                                               sym=True,
                                               title="pyFDA FIR Window Viewer")
                self.sig_tx.connect(self.fft_window.sig_rx)
                self.fft_window.sig_tx.connect(self.close_fft_win)
                self.fft_window.show(
                )  # modeless i.e. non-blocking popup window
        else:
            if not self.but_fft_win.isChecked():
                if self.fft_window is None:
                    logger.warning("FFT window is already closed!")
                else:
                    self.fft_window.close()

    def close_fft_win(self):
        self.fft_window = None
        self.but_fft_win.setChecked(False)
        qstyle_widget(self.but_fft_win, "normal")
Ejemplo n.º 30
0
    def _construct_UI(self):
        """
        Intitialize the widget, consisting of:
        - Matplotlib widget with NavigationToolbar
        - Frame with control elements
        """

        self.cmbUnitsPhi = QComboBox(self)
        units = ["rad", "rad/pi",  "deg"]
        scales = [1.,   1./ np.pi, 180./np.pi]
        for unit, scale in zip(units, scales):
            self.cmbUnitsPhi.addItem(unit, scale)
        self.cmbUnitsPhi.setObjectName("cmbUnitsA")
        self.cmbUnitsPhi.setToolTip("Set unit for phase.")
        self.cmbUnitsPhi.setCurrentIndex(0)
        self.cmbUnitsPhi.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.chkWrap = QCheckBox("Wrapped Phase", self)
        self.chkWrap.setChecked(False)
        self.chkWrap.setToolTip("Plot phase wrapped to +/- pi")

        layHControls = QHBoxLayout()
        layHControls.addWidget(self.cmbUnitsPhi)
        layHControls.addWidget(self.chkWrap)
        layHControls.addStretch(10)

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

        #----------------------------------------------------------------------
        #               ### mplwidget ###
        #
        # main widget, encompassing the other widgets
        #----------------------------------------------------------------------
        self.mplwidget = MplWidget(self)
        self.mplwidget.layVMainMpl.addWidget(self.frmControls)
        self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins'])
        self.mplwidget.mplToolbar.a_he.setEnabled(True)
        self.mplwidget.mplToolbar.a_he.info = "manual/plot_phi.html"
        self.setLayout(self.mplwidget.layVMainMpl)

        self.init_axes()

        self.draw() # initial drawing

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

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.chkWrap.clicked.connect(self.draw)
        self.cmbUnitsPhi.currentIndexChanged.connect(self.unit_changed)
        self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx)