# -*- coding: utf-8 -*-
#
# This file is part of the pyFDA project hosted at https://github.com/chipmuenk/pyfda
#
# Copyright © pyFDA Project Contributors
# Licensed under the terms of the MIT License
# (see file LICENSE in root directory for details)
"""
Create the UI for the Plot_Tran_Impz class
"""
import collections
from pyfda.libs.compat import (
QWidget, QComboBox, QLineEdit, QLabel, QPushButton,
pyqtSignal, QEvent, Qt, QHBoxLayout, QVBoxLayout, QGridLayout)
from pyfda.libs.pyfda_lib import to_html, safe_eval, pprint_log
import pyfda.filterbroker as fb
from pyfda.libs.pyfda_qt_lib import (
qcmb_box_populate, qget_cmb_box, qtext_width, qstyle_widget, QVLine, PushButton)
# FMT string for QLineEdit fields, e.g. '{:.3g}'
from pyfda.pyfda_rc import params
import logging
logger = logging.getLogger(__name__)
[docs]
class Plot_Tran_Stim_UI(QWidget):
"""
Create the UI for the PlotImpz class
"""
# incoming:
sig_rx = pyqtSignal(object)
# outgoing: from various UI elements to PlotImpz ('ui_local_changed':'xxx')
sig_tx = pyqtSignal(object)
# outgoing: to fft related widgets (FFT window widget, qfft_win_select)
sig_tx_fft = pyqtSignal(object)
from pyfda.libs.pyfda_qt_lib import emit
# ------------------------------------------------------------------------------
[docs]
def process_sig_rx(self, dict_sig=None):
"""
Process signals coming from
- FFT window widget
- qfft_win_select
"""
# logger.warning("PROCESS_SIG_RX - vis: {0}\n{1}"
# .format(self.isVisible(), pprint_log(dict_sig)))
if 'id' in dict_sig and dict_sig['id'] == id(self):
logger.warning("Stopped infinite loop:\n{0}".format(
pprint_log(dict_sig)))
return
elif 'view_changed' in dict_sig:
if dict_sig['view_changed'] == 'f_S':
self.normalize_freqs()
# ------------------------------------------------------------------------------
def __init__(self, objectName='plot_tran_stim_ui_inst'):
super().__init__()
"""
Intitialize the widget, consisting of:
- top chkbox row
- coefficient table
- two bottom rows with action buttons
"""
# initial settings
self.setObjectName(objectName)
self.N_FFT = 0 # TODO: FFT value needs to be passed here somehow?
# stimuli
self.cmb_stim_item = "impulse"
self.cmb_stim_periodic_item = "rect_per"
self.cmb_stim_modulation_item = "am"
self.stim = "dirac"
self.impulse_type = "dirac"
self.sinusoid_type = "sine"
self.chirp_type = "linear"
self.cmb_file_io_default = "use"
self.f1 = 0.02
self.f2 = 0.03
self.A1 = 1.0
self.A2 = 0.0
self.phi1 = self.phi2 = 0
self.T1 = self.T2 = 0
self.TW1 = self.TW2 = 10
self.BW1 = self.BW2 = 0.5
self.N1 = self.N2 = 5
self.noi = 0.1
self.noise = "none"
self.mls_b = 8
self.DC = 0.0
self.stim_formula = "A1 * abs(sin(2 * pi * f1 * n))"
self.stim_par1 = 0.5
self.scale_impz = 1 # optional energy scaling for impulses
# self.bottom_f = -120 # initial value for log. scale
# self.param = None
# dictionaries with widgets needed for the various stimuli
self.stim_wdg_dict = collections.OrderedDict()
self.stim_wdg_dict.update({
"none": {"dc", "noise"},
"dirac": {"dc", "a1", "T1", "norm", "noise"},
"sinc": {"dc", "a1", "a2", "T1", "T2", "f1", "f2", "norm", "noise"},
"gauss": {"dc", "a1", "a2", "T1", "T2", "f1", "f2", "BW1", "BW2",
"norm", "noise"},
"rect": {"dc", "a1", "T1", "TW1", "norm", "noise"},
"step": {"a1", "T1", "noise"},
"cos": {"dc", "a1", "a2", "phi1", "phi2", "f1", "f2", "noise"},
"sine": {"dc", "a1", "a2", "phi1", "phi2", "f1", "f2", "noise"},
"exp": {"dc", "a1", "a2", "phi1", "phi2", "f1", "f2", "noise"},
"diric": {"dc", "a1", "T1", "N1", "f1", "noise"},
"chirp": {"dc", "a1", "phi1", "f1", "f2", "T2", "noise"},
"triang": {"dc", "a1", "phi1", "f1", "noise", "bl"},
"saw": {"dc", "a1", "phi1", "f1", "noise", "bl"},
"rect_per": {"dc", "a1", "phi1", "f1", "noise", "bl", "par1"},
"comb": {"dc", "a1", "phi1", "f1", "noise"},
"am": {"dc", "a1", "a2", "phi1", "phi2", "f1", "f2", "noise"},
"pmfm": {"dc", "a1", "a2", "phi1", "phi2", "f1", "f2", "noise"},
"pwm": {"dc", "a1", "a2", "phi1", "phi2", "f1", "f2", "noise", "bl"},
"formula": {"dc", "a1", "a2", "phi1", "phi2", "f1", "f2", "N1", "N2",
"T1", "T2", "BW1", "BW2", "noise"}
})
# combobox tooltip + data / text / tooltip for stimulus category items
self.cmb_stim_items = [
("<span>Stimulus category.</span>"),
("none", "None", "<span>Only noise and DC can be selected.</span>"),
("impulse", "Impulse", "<span>Different impulses</span>"),
("step", "Step", "<span>Calculate step response and its error.</span>"),
("sinusoid", "Sinusoid", "<span>Sinusoidal waveforms</span>"),
("chirp", "Chirp", "<span>Different frequency sweeps.</span>"),
("periodic", "Periodic", "<span>Periodic functions with discontinuities, "
"either band-limited or with aliasing.</span>"),
("modulation", "Modulat.", "<span>Modulated waveforms.</span>"),
("formula", "Formula", "<span>Formula defined stimulus.</span>")
]
# combobox tooltip + data / text / tooltip for file I/O usage
self.cmb_file_io_items = [
("<span>Select data from File I/O widget (file needs to be loaded)</span>"),
("use", "Use",
"<span><b>Use</b> "
"file I/O data as stimuli (file needs to be loaded).</span>"),
("add", "Add", "<span><b>Add</b> "
"file I/O data to other stimuli (file needs to be loaded).</span>")
]
# combobox tooltip + data / text / tooltip for periodic signals items
self.cmb_stim_periodic_items = [
"<span>Periodic functions with discontinuities.</span>",
("rect_per", "Rect", "<span>Rectangular signal with duty cycle α</span>"),
("saw", "Saw", "Sawtooth signal"),
("triang", "Triang", "Triangular signal"),
("comb", "Comb", "Comb signal")
]
# combobox tooltip + data / text / tooltip for chirp signals items
self.cmb_stim_chirp_items = [
"<span>Type of frequency sweep from <i>f</i><sub>1</sub> @ <i>t</i> = 0 to "
"<i>f</i><sub>2</sub> @ t = <i>T</i><sub>2</sub>.</span>",
("linear", "Lin", "Linear frequency sweep"),
("quadratic", "Square", "Quadratic frequency sweep"),
("logarithmic", "Log", "Logarithmic frequency sweep"),
("hyperbolic", "Hyper", "Hyperbolic frequency sweep")
]
self.cmb_stim_impulse_items = [
"<span>Different aperiodic impulse forms</span>",
("dirac", "Dirac",
"<span>Discrete-time dirac impulse for simulating impulse and "
"frequency response.</span>"),
("gauss", "Gauss",
"<span>Gaussian pulse with bandpass spectrum and controllable "
"relative -6 dB bandwidth.</span>"),
("sinc", "Sinc",
"<span>Sinc pulse with rectangular baseband spectrum</span>"),
("rect", "Rect", "<span>Rectangular pulse with sinc-shaped spectrum</span>")
]
self.cmb_stim_sinusoid_items = [
"Sinusoidal or similar signals",
("sine", "Sine", "Sine signal"),
("cos", "Cos", "Cosine signal"),
("exp", "Exp", "Complex exponential"),
("diric", "Diric", "<span>Periodic sinc (Dirichlet) function, "
"diric(x, N) = sin(Nx/2) / N*sin(x/2) with x = 2 pi f_1 n</span>")
]
self.cmb_stim_modulation_items = [
"Modulated signals",
("am", "AM", "<span>Sinusoidal amplitude modulation of a sine</span>"),
("pmfm", "PM / FM", "<span>Sinusoidal phase or frequency modulation "
"of a sine</span>"),
("pwm", "PWM", "sinusoidal pulse width modulation")
]
# data / text / tooltip for noise stimulus combobox.
self.cmb_stim_noise_items = [
"Type of additive noise.",
("none", "None", ""),
("gauss", "Gauss",
"<span>Normal- or Gauss-distributed process with std. deviation σ."
"</span>"),
("uniform", "Uniform",
"<span>Uniformly distributed process in the range ± Δ/2."
"</span>"),
("randint", "RandInt",
"<span>Random integer sequence in the interval [0, <i>I</i>]</span>"),
("mls", "MLS",
"<span>Maximum Length Sequence with amplitude <i>A</i> and maximum length "
"2<sup><i>b</i></sup> - 1 after which the sequence is repeated.</span>"),
("brownian", "Brownian",
"<span>Brownian (cumulated sum) process based on Gaussian noise with"
" std. deviation σ.</span>")
]
# Dict with objectNames as keys and tuples with normalized variables
# and scaling factors as values, e.g. {'led_f1': (self.f1, self.f_scale)}
self.dict_filtered_widgets = {
'led_f1': ('f1', 'f_scale'),
'led_f2': ('f2', 'f_scale'),
'led_T1': ('T1', 't_scale'),
'led_T2': ('T2', 't_scale'),
'led_TW1': ('TW1', 't_scale'),
'led_TW2': ('TW2', 't_scale')
}
self._construct_UI()
self._enable_stim_widgets()
self._update_noi()
def _construct_UI(self):
# =====================================================================
# Controls for stimuli
# =====================================================================
self.lbl_title_stim = QLabel("Stim:", objectName="large")
#
self.cmbStimulus = QComboBox(self)
qcmb_box_populate(self.cmbStimulus,
self.cmb_stim_items, self.cmb_stim_item)
self.lblStimPar1 = QLabel(to_html("α =", frmt='b'), self)
self.ledStimPar1 = QLineEdit(self, objectName="ledStimPar1")
self.ledStimPar1.setText("0.5")
self.ledStimPar1.setToolTip("Duty Cycle, 0 ... 1")
self.but_stim_bl = PushButton(self, text="BL", objectName="stim_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.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)
qcmb_box_populate(
self.cmbModulationType, self.cmb_stim_modulation_items,
self.cmb_stim_modulation_item)
# -------------------------------------
self.but_step_err = PushButton(
text="Error", objectName="stim_step_err")
self.but_step_err.setToolTip(
"<span>Display the step response error.</span>")
# self.but_step_err.setMaximumWidth(qtext_width(text="Error "))
self.but_step_err.setCheckable(True)
self.but_step_err.setChecked(False)
#
self.but_file_io = PushButton(self, "<", checkable=False)
self.but_file_io.setToolTip(
"<span>Use file length as number of data points.</span>")
self.lbl_file_io = QLabel(to_html(" File IO", frmt='bi'))
self.cmb_file_io = QComboBox(self, objectName="cmb_file_io")
qcmb_box_populate(
self.cmb_file_io, self.cmb_file_io_items, self.cmb_file_io_default)
# TODO: layH_file_io is instantiated in plot_impz, this is very hacky
self.layH_file_io = QHBoxLayout()
self.layH_file_io.addWidget(self.but_file_io)
self.layH_file_io.addWidget(self.lbl_file_io)
self.layH_file_io.addWidget(self.cmb_file_io)
self.layH_file_io.setContentsMargins(0, 0, 0, 0)
# -------------------------------------
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.but_step_err)
self.lblDC = QLabel(to_html("DC =", frmt='bi'), self)
self.ledDC = QLineEdit(self, objectName="stimDC")
self.ledDC.setText(str(self.DC))
self.ledDC.setToolTip("DC Level")
layHStimDC = QHBoxLayout()
layHStimDC.addWidget(self.lblDC)
layHStimDC.addWidget(self.ledDC)
# ======================================================================
self.lblAmp1 = QLabel(to_html(" A_1", frmt='bi') + " =", self)
self.ledAmp1 = QLineEdit(self, objectName="stimAmp1")
self.ledAmp1.setText(str(self.A1))
self.ledAmp1.setToolTip(
"Stimulus amplitude; complex values like 3j - 1 are allowed")
self.lblAmp2 = QLabel(to_html(" A_2", frmt='bi') + " =", self)
self.ledAmp2 = QLineEdit(self, objectName="stimAmp2")
self.ledAmp2.setText(str(self.A2))
self.ledAmp2.setToolTip(
"Stimulus amplitude 2; complex values like 3j - 1 are allowed")
# ----------------------------------------------
self.lblPhi1 = QLabel(to_html(" φ_1", frmt='bi') + " =", self)
self.ledPhi1 = QLineEdit(self, objectName="stimPhi1")
self.ledPhi1.setText(str(self.phi1))
self.ledPhi1.setToolTip("Stimulus phase 1 in degrees")
self.lblPhU1 = QLabel(to_html("°", frmt='i'), self)
self.lblPhi2 = QLabel(to_html(" φ_2", frmt='bi') + " =", self)
self.ledPhi2 = QLineEdit(self, objectName="stimPhi2")
self.ledPhi2.setText(str(self.phi2))
self.ledPhi2.setToolTip("Stimulus phase 2 in degrees")
self.lblPhU2 = QLabel(to_html("°", frmt='i'), self)
# ----------------------------------------------
self.lbl_T1 = QLabel(to_html(" T_1", frmt='bi') + " =", self)
self.led_T1 = QLineEdit(self, objectName="led_T1")
self.led_T1.setText(str(self.T1))
self.led_T1.setToolTip("Time shift 1")
self.lbl_TU1 = QLabel(to_html("T_S", frmt='i'), self)
self.lbl_T2 = QLabel(to_html(" T_2", frmt='bi') + " =", self)
self.led_T2 = QLineEdit(self, objectName="led_T2")
self.led_T2.setText(str(self.T2))
self.led_T2.setToolTip("Time shift 2")
self.lbl_TU2 = QLabel(to_html("T_S", frmt='i'), self)
# ----------------------------------------------
self.lbl_N1 = QLabel(to_html(" N_1", frmt='bi') + " =", self)
self.led_N1 = QLineEdit(self, objectName="stimN1")
self.led_N1.setText(str(self.N1))
self.led_N1.setToolTip("Parameter N1")
self.lbl_N2 = QLabel(to_html(" N_2", frmt='bi') + " =", self)
self.led_N2 = QLineEdit(self, objectName="stimN2")
self.led_N2.setText(str(self.N2))
self.led_N2.setToolTip("Parameter N2")
# ---------------------------------------------
self.lbl_TW1 = QLabel(
to_html(" ΔT_1", frmt='bi') + " =", self)
self.led_TW1 = QLineEdit(self, objectName="led_TW1")
self.led_TW1.setText(str(self.TW1))
self.led_TW1.setToolTip("Time width")
self.lbl_TWU1 = QLabel(to_html("T_S", frmt='i'), self)
self.lbl_TW2 = QLabel(
to_html(" ΔT_2", frmt='bi') + " =", self)
self.led_TW2 = QLineEdit(self, objectName="led_TW2")
self.led_TW2.setText(str(self.TW2))
self.led_TW2.setToolTip("Time width 2")
self.lbl_TWU2 = QLabel(to_html("T_S", frmt='i'), self)
# ----------------------------------------------
self.txtFreq1_f = to_html(" f_1", frmt='bi') + " ="
self.txtFreq1_F = to_html(" F_1", frmt='bi') + " ="
self.txtFreq1_k = to_html(" k_1", frmt='bi') + " ="
self.lblFreq1 = QLabel(self.txtFreq1_f, self)
self.led_f1 = QLineEdit(self, objectName="led_f1")
self.led_f1.setText(str(self.f1))
self.led_f1.setToolTip("Stimulus frequency")
self.lblFreqUnit1 = QLabel(to_html("f_S", frmt='i'), self)
self.txtFreq2_f = to_html(" f_2", frmt='bi') + " ="
self.txtFreq2_F = to_html(" F_2", frmt='bi') + " ="
self.txtFreq2_k = to_html(" k_2", frmt='bi') + " ="
self.lblFreq2 = QLabel(self.txtFreq2_f, self)
self.led_f2 = QLineEdit(self, objectName="led_f2")
self.led_f2.setText(str(self.f2))
self.led_f2.setToolTip("Stimulus frequency 2")
self.lblFreqUnit2 = QLabel(to_html("f_S", frmt='i'), self)
# ----------------------------------------------
self.lbl_BW1 = QLabel(
to_html(self.tr(" BW_1"), frmt='bi') + " =", self)
self.led_BW1 = QLineEdit(self, objectName="stimBW1")
self.led_BW1.setText(str(self.BW1))
self.led_BW1.setToolTip(self.tr("Relative bandwidth"))
self.lbl_BW2 = QLabel(
to_html(self.tr(" BW_2"), frmt='bi') + " =", self)
self.led_BW2 = QLineEdit(self, objectName="stimBW2")
self.led_BW2.setText(str(self.BW2))
self.led_BW2.setToolTip(self.tr("Relative bandwidth 2"))
# ----------------------------------------------
self.lblNoise = QLabel(to_html(" Noise", frmt='bi'), self)
self.cmb_stim_noise = QComboBox(self)
qcmb_box_populate(self.cmb_stim_noise, self.cmb_stim_noise_items, self.noise)
line2 = QVLine()
self.lblNoi = QLabel("not initialized", self)
self.ledNoi = QLineEdit(self, objectName="stimNoi")
self.ledNoi.setMaximumWidth(self.cmb_stim_noise.width())
self.ledNoi.setText(str(self.noi))
self.ledNoi.setToolTip("not initialized")
self.lblNoi_par = QLabel("not initialized", self)
self.ledNoi_par = QLineEdit(self)
self.ledNoi_par.setMaximumWidth(qtext_width(N_x=4))
layH_noi_params = QHBoxLayout()
layH_noi_params.addWidget(self.ledNoi)
layH_noi_params.addWidget(self.lblNoi_par)
layH_noi_params.addWidget(self.ledNoi_par)
# ----------------------------------------------
# Widget and Layout containing formula editor
self.lblStimFormula = QLabel(to_html("x =", frmt='bi'), self)
self.ledStimFormula = QLineEdit(self, objectName="stimFormula")
self.ledStimFormula.setText(str(self.stim_formula))
self.ledStimFormula.setToolTip(
"<span>Enter formula for stimulus in numexpr syntax, using the index "
"<i>n</i> or the time vector <i>t</i> and the following UI settings: "
+ to_html("A_1, A_2, phi_1, phi_2, f_1, f_2, T_1, T_2, BW_1, BW_2",
frmt='i') + ".</span>")
layH_formula_stim = QHBoxLayout()
layH_formula_stim.addWidget(self.lblStimFormula)
layH_formula_stim.addWidget(self.ledStimFormula)
layH_formula_stim.setContentsMargins(0, 0, 0, 0)
self.wdg_formula_stim = QWidget(self)
self.wdg_formula_stim.setLayout(layH_formula_stim)
self.wdg_formula_stim.setContentsMargins(0, 0, 0, 0)
# Main grid layout for all control elements
layG_ctrl_stim = QGridLayout()
i = 0
layG_ctrl_stim.addLayout(layHCmbStim, 0, i)
layG_ctrl_stim.addLayout(layHStimDC, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lblAmp1, 0, i)
layG_ctrl_stim.addWidget(self.lblAmp2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.ledAmp1, 0, i)
layG_ctrl_stim.addWidget(self.ledAmp2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lblFreq1, 0, i)
layG_ctrl_stim.addWidget(self.lblFreq2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.led_f1, 0, i)
layG_ctrl_stim.addWidget(self.led_f2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lblFreqUnit1, 0, i)
layG_ctrl_stim.addWidget(self.lblFreqUnit2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lblPhi1, 0, i)
layG_ctrl_stim.addWidget(self.lblPhi2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.ledPhi1, 0, i)
layG_ctrl_stim.addWidget(self.ledPhi2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lblPhU1, 0, i)
layG_ctrl_stim.addWidget(self.lblPhU2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lbl_BW1, 0, i)
layG_ctrl_stim.addWidget(self.lbl_BW2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.led_BW1, 0, i)
layG_ctrl_stim.addWidget(self.led_BW2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lbl_T1, 0, i)
layG_ctrl_stim.addWidget(self.lbl_T2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.led_T1, 0, i)
layG_ctrl_stim.addWidget(self.led_T2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lbl_TU1, 0, i)
layG_ctrl_stim.addWidget(self.lbl_TU2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lbl_N1, 0, i)
layG_ctrl_stim.addWidget(self.lbl_N2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.led_N1, 0, i)
layG_ctrl_stim.addWidget(self.led_N2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lbl_TW1, 0, i)
layG_ctrl_stim.addWidget(self.lbl_TW2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.led_TW1, 0, i)
layG_ctrl_stim.addWidget(self.led_TW2, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.lbl_TWU1, 0, i)
layG_ctrl_stim.addWidget(self.lbl_TWU2, 1, i)
i += 1
layG_ctrl_stim.addWidget(line2, 0, i, 2, 1)
i += 1
layG_ctrl_stim.addWidget(self.lblNoise, 0, i)
layG_ctrl_stim.addWidget(self.lblNoi, 1, i)
i += 1
layG_ctrl_stim.addWidget(self.cmb_stim_noise, 0, i)
layG_ctrl_stim.addLayout(layH_noi_params, 1, i)
i += 1
layG_ctrl_stim.setColumnStretch(i, 1)
i += 1
# place formula widget in row 2, stretching over i columns
layG_ctrl_stim.addWidget(self.wdg_formula_stim, 2, 0, 1, i)
# ----------------------------------------------------------------------
# Main Widget
# ----------------------------------------------------------------------
# Widget with the text block "Stim:"
layH_title_stim = QHBoxLayout()
layH_title_stim.addWidget(self.lbl_title_stim)
self.wdg_title_stim = QWidget(self)
self.wdg_title_stim.setLayout(layH_title_stim)
self.wdg_title_stim.setContentsMargins(0, 0, 0, 0)
# Widget containing all control elements for stimuli
self.wdg_ctrl_stim = QWidget(self)
self.wdg_ctrl_stim.setLayout(layG_ctrl_stim)
layH_stim = QHBoxLayout()
layH_stim.addWidget(self.wdg_title_stim)
layH_stim.addWidget(self.wdg_ctrl_stim)
layH_stim.setContentsMargins(0, 0, 0, 0)
self.wdg_stim = QWidget(self, objectName="transparent")
self.wdg_stim.setLayout(layH_stim)
self.wdg_stim.setContentsMargins(0, 0, 0, 0)
# ----------------------------------------------------------------------
# Initialization
# ----------------------------------------------------------------------
self.normalize_freqs() # set f_scale and t_scale factors
# ----------------------------------------------------------------------
# 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.but_step_err.clicked.connect(self._enable_stim_widgets)
self.cmbStimulus.currentIndexChanged.connect(self._enable_stim_widgets)
self.cmb_stim_noise.currentIndexChanged.connect(self._update_noi)
self.ledNoi.editingFinished.connect(self._update_noi)
self.ledNoi_par.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.led_N1.editingFinished.connect(self._update_N1)
self.led_N2.editingFinished.connect(self._update_N2)
self.cmb_file_io.currentIndexChanged.connect(self._enable_stim_widgets)
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)
# ----------------------------------------------------------------------
# Event Filter
# ----------------------------------------------------------------------
# time / frequency related widgets have to be scaled with f_s, this
# special handling is performed in an EventFilter (hence no regular
# signal-slot connection).
self.led_f1.installEventFilter(self)
self.led_f2.installEventFilter(self)
self.led_T1.installEventFilter(self)
self.led_T2.installEventFilter(self)
self.led_TW1.installEventFilter(self)
self.led_TW2.installEventFilter(self)
# ------------------------------------------------------------------------------
[docs]
def update_freq_units(self):
"""
Update labels for time / frequency related specs
"""
if False: # fb.fil[0]["tab_yn"]["display_index_k"]: # button 'index_k' is set
# doesn't work yet, frequencies are scaled wrongly
unit_frmt = None
f_unit = ''
t_unit = ''
self.lblFreq1.setText(self.txtFreq1_k)
self.lblFreq2.setText(self.txtFreq2_k)
else:
f_unit = fb.fil[0]['plt_fUnit']
t_unit = fb.fil[0]['plt_tUnit'].replace(r"$\mu$", "μ")
if fb.fil[0]['freq_specs_unit'] in {'f_S', 'f_Ny'}:
# Normalized frequency labels with capital F
self.lblFreq1.setText(self.txtFreq1_F)
self.lblFreq2.setText(self.txtFreq2_F)
unit_frmt = "i" # print 'f_S' and 'f_Ny' in italic
else:
# absolute frequencies with lower case f
self.lblFreq1.setText(self.txtFreq1_f)
self.lblFreq2.setText(self.txtFreq2_f)
unit_frmt = None # don't print units like kHz in italic
self.lblFreqUnit1.setText(to_html(f_unit, frmt=unit_frmt))
self.lblFreqUnit2.setText(to_html(f_unit, frmt=unit_frmt))
self.lbl_TU1.setText(to_html(t_unit, frmt=unit_frmt))
self.lbl_TU2.setText(to_html(t_unit, frmt=unit_frmt))
# ------------------------------------------------------------------------------
[docs]
def eventFilter(self, source, event):
"""
Filter all events generated by the monitored frequency / time related widgets
led_f1 and 2, T1 / T2 and TW1 / TW2.
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``) or when
the Return key is pressed, store current value normalized to f_S with
full precision and display the denormalized value in selected format
or full precision when `spec_edited == True`. Emit 'ui_local_changed':'stim'.
"""
def _reload_entry(source, full_prec=False):
"""
Reload text entry for active line edit field in denormalized format,
either with full precision (`full_prec == True`) or rounded.
"""
try:
var_name, param_name = self.dict_filtered_widgets[source.objectName()]
var = getattr(self, var_name)
scale = getattr(self, param_name)
if full_prec:
source.setText(str(var * scale))
else:
source.setText(str(params['FMT'].format(var * scale)))
except KeyError:
logger.warning(f"Unknown objectName {source.objectName}!")
#------------------------------------------------------------
def _store_entry(source):
""" Store transformed frequency / time values """
if self.spec_edited:
try:
var_name, param_name = self.dict_filtered_widgets[source.objectName()]
scale = getattr(self, param_name) # get scale value
var_old = getattr(self, var_name) # get old var value
# assign var with either content of text field or fallback value:
var = safe_eval(
source.text(), var_old * scale, return_type='float') / scale
# assign evaluated text field to variable
setattr(self, var_name, var)
# set textfield with scaled value of `var_name`:
source.setText(str(params['FMT'].format(var * scale)))
# highlight lineedit field in red when normalized frequency is > 0.5
if var >= 0.5 and "_f" in source.objectName(): # only test this for 'led_f1' and 'led_f2'
qstyle_widget(source, 'failed')
else:
qstyle_widget(source, 'normal')
except KeyError:
pass
self.spec_edited = False # reset flag
self._update_energy_scaling_impz()
self.emit({'ui_local_changed': 'stim'})
# nothing has changed, but display frequencies in rounded format anyway
else:
_reload_entry(source)
# --------------------------------------------------------------------
# ------ EventFilter main part ---------------------------------------
if event.type() in {QEvent.FocusIn, QEvent.KeyPress, QEvent.FocusOut}:
if event.type() == QEvent.FocusIn:
self.spec_edited = False
_reload_entry(source, full_prec=True)
elif event.type() == QEvent.KeyPress:
self.spec_edited = True # entry has been changed
key = event.key()
if key in {Qt.Key_Return, Qt.Key_Enter}:
_store_entry(source)
elif key == Qt.Key_Escape: # revert changes
self.spec_edited = False
_reload_entry(source)
elif event.type() == QEvent.FocusOut:
_store_entry(source)
# Call base class method to continue normal event processing:
return super(Plot_Tran_Stim_UI, self).eventFilter(source, event)
# -------------------------------------------------------------
[docs]
def normalize_freqs(self):
# TODO: move this to plot_tran_stim and update N_FFT
"""
Update normalized frequencies and periods if required.
`normalize_freqs()` is called when sampling frequency has been changed
via signal ['view_changed':'f_S'] from plot_impz.process_sig_rx
Frequency and time related entries are always stored normalized w.r.t. f_S
which is loaded from filter dictionary and stored as `self.f_scale`
(except when the frequency unit is k when `f_scale = self.N_FFT`).
- When the `f_S` lock button is unchecked, only the displayed
values for frequency entries are updated with f_S, not the normalized freqs.
- When the `f_S` lock button is checked, the absolute frequency values in
the widget fields are kept constant, and the normalized freqs are updated.
"""
f_corr = 1
if fb.fil[0]['freq_locked']:
f_corr = fb.fil[0]['f_S_prev'] / fb.fil[0]['f_S']
self.f1 *= f_corr
self.f2 *= f_corr
self.T1 /= f_corr
self.T2 /= f_corr
self.TW1 /= f_corr
self.TW2 /= f_corr
self._update_energy_scaling_impz()
# recalculate displayed freq spec values for (maybe) changed f_S
if False: # fb.fil[0]["tab_yn"]["display_index_k"]:
# doesn't work yet, frequencies are scaled wrongly
self.f_scale = self.N_FFT
else:
self.f_scale = fb.fil[0]['f_S']
self.t_scale = fb.fil[0]['T_S']
# logger.warning(f"f_S = {fb.fil[0]['f_S']}, prev = {fb.fil[0]['f_S_prev']}\n"
# f"f_scale = {self.f_scale}, f_1 = {self.f1}, f_corr = {f_corr}")
# update and round the display
for w in self.dict_filtered_widgets:
var_name, param_name = self.dict_filtered_widgets[w]
# read value and scale of normalized frequency / time value
var = getattr(self, var_name)
scale = getattr(self, param_name)
# access lineedit object
led = getattr(self, w)
# logger.warning(f"{w} - {var} - {getattr(self, w).text()}")
# update the text with the denormalized frequency / time variable
led.setText(str(params['FMT'].format(var * scale)))
# self.led_f1.setText(str(params['FMT'].format(self.f1 * self.f_scale)))
# highlight lineedit field in red when normalized frequency is > 0.5
if var >= 0.5 and "_f" in w: # only test this for 'led_f1' and 'led_f2'
qstyle_widget(led, 'failed')
else:
qstyle_widget(led, 'normal')
self.update_freq_units()
# emit a signal if normalized frequencies have changed due to an update
# of f_S
if fb.fil[0]['freq_locked']:
self.emit({'ui_local_changed': 'f1_f2'})
# -------------------------------------------------------------
def _enable_stim_widgets(self):
""" Enable / disable widgets depending on the selected stimulus """
self.cmb_stim = qget_cmb_box(self.cmbStimulus)
self.wdg_formula_stim.setVisible(self.cmb_stim == "formula")
if self.cmb_stim == "impulse":
self.stim = qget_cmb_box(self.cmbImpulseType)
# recalculate the energy scaling for impulse functions
self._update_energy_scaling_impz()
elif self.cmb_stim == "sinusoid":
self.stim = qget_cmb_box(self.cmbSinusoidType)
elif self.cmb_stim == "periodic":
self.stim = qget_cmb_box(self.cmbPeriodicType)
elif self.cmb_stim == "modulation":
self.stim = qget_cmb_box(self.cmbModulationType)
else:
self.stim = self.cmb_stim
# read out which stimulus widgets are enabled
stim_wdg = self.stim_wdg_dict[self.stim]
self.lblDC.setVisible("dc" in stim_wdg)
self.ledDC.setVisible("dc" in stim_wdg)
self.but_step_err.setVisible(self.stim == "step")
self.lblStimPar1.setVisible("par1" in stim_wdg)
self.ledStimPar1.setVisible("par1" in stim_wdg)
self.but_stim_bl.setVisible("bl" in stim_wdg)
self.lblAmp1.setVisible("a1" in stim_wdg)
self.ledAmp1.setVisible("a1" in stim_wdg)
self.lblPhi1.setVisible("phi1" in stim_wdg)
self.ledPhi1.setVisible("phi1" in stim_wdg)
self.lblPhU1.setVisible("phi1" in stim_wdg)
self.lbl_T1.setVisible("T1" in stim_wdg)
self.led_T1.setVisible("T1" in stim_wdg)
self.lbl_TU1.setVisible("T1" in stim_wdg)
self.lbl_N1.setVisible("N1" in stim_wdg)
self.led_N1.setVisible("N1" in stim_wdg)
self.lbl_TW1.setVisible("TW1" in stim_wdg)
self.led_TW1.setVisible("TW1" in stim_wdg)
self.lbl_TWU1.setVisible("TW1" in stim_wdg)
self.lblFreq1.setVisible("f1" in stim_wdg)
self.led_f1.setVisible("f1" in stim_wdg)
self.lblFreqUnit1.setVisible("f1" in stim_wdg)
self.lbl_BW1.setVisible("BW1" in stim_wdg)
self.led_BW1.setVisible("BW1" in stim_wdg)
self.lblAmp2.setVisible("a2" in stim_wdg)
self.ledAmp2.setVisible("a2" in stim_wdg)
self.lblPhi2.setVisible("phi2" in stim_wdg)
self.ledPhi2.setVisible("phi2" in stim_wdg)
self.lblPhU2.setVisible("phi2" in stim_wdg)
self.lbl_T2.setVisible("T2" in stim_wdg)
self.led_T2.setVisible("T2" in stim_wdg)
self.lbl_TU2.setVisible("T2" in stim_wdg)
self.lbl_N2.setVisible("N2" in stim_wdg)
self.led_N2.setVisible("N2" in stim_wdg)
self.lbl_TW2.setVisible("TW2" in stim_wdg)
self.led_TW2.setVisible("TW2" in stim_wdg)
self.lbl_TWU2.setVisible("TW2" in stim_wdg)
self.lblFreq2.setVisible("f2" in stim_wdg)
self.led_f2.setVisible("f2" in stim_wdg)
self.lblFreqUnit2.setVisible("f2" in stim_wdg)
self.lbl_BW2.setVisible("BW2" in stim_wdg)
self.led_BW2.setVisible("BW2" in stim_wdg)
self.cmbImpulseType.setVisible(self.cmb_stim == 'impulse')
self.cmbSinusoidType.setVisible(self.cmb_stim == 'sinusoid')
self.cmbChirpType.setVisible(self.cmb_stim == 'chirp')
self.cmbPeriodicType.setVisible(self.cmb_stim == 'periodic')
self.cmbModulationType.setVisible(self.cmb_stim == 'modulation')
self.emit({'ui_local_changed': 'stim'})
# -------------------------------------------------------------
def _update_amp1(self):
""" Update value for self.A1 from QLineEditWidget"""
self.A1 = safe_eval(self.ledAmp1.text(), self.A1, return_type='cmplx')
self.ledAmp1.setText(str(self.A1))
self.emit({'ui_local_changed': 'a1'})
def _update_amp2(self):
""" Update value for self.A2 from the QLineEditWidget"""
self.A2 = safe_eval(self.ledAmp2.text(), self.A2, return_type='cmplx')
self.ledAmp2.setText(str(self.A2))
self.emit({'ui_local_changed': 'a2'})
def _update_phi1(self):
""" Update value for self.phi1 from QLineEditWidget"""
self.phi1 = safe_eval(self.ledPhi1.text(),
self.phi1, return_type='float')
self.ledPhi1.setText(str(self.phi1))
self.emit({'ui_local_changed': 'phi1'})
def _update_N1(self):
""" Update value for self.N1 from `self.led_N1`"""
self.N1 = safe_eval(self.led_N1.text(), self.N1, return_type='int', sign='pos')
self.led_N1.setText(str(self.N1))
self.emit({'ui_local_changed': 'N1'})
def _update_N2(self):
""" Update value for self.N2 from `self.led_N2`"""
self.N2 = safe_eval(self.led_N2.text(), self.N2, return_type='int', sign='pos')
self.led_N2.setText(str(self.N2))
self.emit({'ui_local_changed': 'N2'})
def _update_BW1(self):
""" Update value for self.BW1 from QLineEditWidget"""
self.BW1 = safe_eval(
self.led_BW1.text(), self.BW1, return_type='float', sign='pos')
self.led_BW1.setText(str(self.BW1))
self._update_energy_scaling_impz()
self.emit({'ui_local_changed': 'BW1'})
def _update_BW2(self):
""" Update value for self.BW2 from QLineEditWidget"""
self.BW2 = safe_eval(
self.led_BW2.text(), self.BW2, return_type='float', sign='pos')
self.led_BW2.setText(str(self.BW2))
self.emit({'ui_local_changed': 'BW2'})
def _update_energy_scaling_impz(self):
"""
recalculate the energy scaling for impulse functions when impulse type or
relevant frequency / bandwidth parameter have been updated
"""
if self.stim == "dirac":
self.scale_impz = 1.
elif self.stim == "sinc":
self.scale_impz = self.f1 * 2
elif self.stim == "gauss":
self.scale_impz = self.f1 * 2 * self.BW1
elif self.stim == "rect":
self.scale_impz = 1. / self.TW1
def _update_phi2(self):
""" Update value for self.phi2 from the QLineEditWidget"""
self.phi2 = safe_eval(self.ledPhi2.text(),
self.phi2, return_type='float')
self.ledPhi2.setText(str(self.phi2))
self.emit({'ui_local_changed': 'phi2'})
def _update_chirp_type(self):
""" Update value for self.chirp_type from data field of ComboBox"""
self.chirp_type = qget_cmb_box(self.cmbChirpType)
self.emit({'ui_local_changed': 'chirp_type'})
def _update_impulse_type(self):
""" Update value for self.impulse_type from data field of ComboBox"""
self.impulse_type = qget_cmb_box(self.cmbImpulseType)
self._enable_stim_widgets()
def _update_sinusoid_type(self):
""" Update value for self.sinusoid_type from data field of ComboBox"""
self.sinusoid_type = qget_cmb_box(self.cmbSinusoidType)
self._enable_stim_widgets()
def _update_periodic_type(self):
""" Update value for self.periodic_type from data field of ComboBox"""
self.periodic_type = qget_cmb_box(self.cmbPeriodicType)
self._enable_stim_widgets()
def _update_modulation_type(self):
""" Update value for self.modulation_type from from data field of ComboBox"""
self.modulation_type = qget_cmb_box(self.cmbModulationType)
self._enable_stim_widgets()
# -------------------------------------------------------------
def _update_noi(self):
""" Update type + value + label for self.noi for noise"""
self.noise = qget_cmb_box(self.cmb_stim_noise)
self.lblNoi.setVisible(self.noise != 'none')
self.ledNoi.setVisible(self.noise != 'none')
self.lblNoi_par.setVisible(self.noise == 'mls')
self.ledNoi_par.setVisible(self.noise == 'mls')
if self.noise != 'none':
self.noi = safe_eval(self.ledNoi.text(), 0, return_type='cmplx')
self.ledNoi.setText(str(self.noi))
if self.noise == 'gauss':
self.lblNoi.setText(to_html(" σ =", frmt='bi'))
self.ledNoi.setToolTip(
"<span>Standard deviation, "
"noise power is <i>P</i> = σ<sup>2</sup></span>")
elif self.noise == 'uniform':
self.lblNoi.setText(to_html(" Δ =", frmt='bi'))
self.ledNoi.setToolTip(
"<span>Interval size for uniformly distributed process (e.g. "
"quantization step size for quantization noise), centered around 0. "
"Noise power is <i>P</i> = Δ<sup>2</sup>/12.</span>")
elif self.noise == 'randint':
self.lblNoi.setText(to_html(" I =", frmt='bi'))
self.ledNoi.setToolTip(
"<span>Max. integer value <i>I</i> of random integer process."
"</span>")
elif self.noise == 'mls':
self.lblNoi.setText(to_html(" A =", frmt='bi'))
self.ledNoi.setToolTip(
"<span>Amplitude of Maximum Length Sequence. "
"Noise power is <i>P</i> = A<sup>2</sup>/2.</span>")
self.lblNoi_par.setText(to_html(" b =", frmt='bi'))
self.mls_b = safe_eval(
self.ledNoi_par.text(), self.mls_b, return_type='int', sign='pos')
if self.mls_b < 2:
self.mls_b = 2
if self.mls_b > 32:
self.mls_b = 32
self.ledNoi_par.setText(str(self.mls_b))
self.ledNoi_par.setToolTip("<span>Length of sequence will be "
"2<sup><i>b</i></sup> - 1 with <i>b</i> "
"in the range 2 ... 32./span>")
elif self.noise == 'brownian':
self.lblNoi.setText(to_html(" σ =", frmt='bi'))
self.ledNoi.setToolTip("<span>Standard deviation of the Gaussian process "
"that is cumulated.</span>")
self.emit({'ui_local_changed': 'noi'})
def _update_DC(self):
""" Update value for self.DC from the QLineEditWidget"""
self.DC = safe_eval(self.ledDC.text(), 0, return_type='cmplx')
self.ledDC.setText(str(self.DC))
self.emit({'ui_local_changed': 'dc'})
def _update_stim_formula(self):
"""Update string with formula to be evaluated by numexpr"""
self.stim_formula = self.ledStimFormula.text().strip()
self.ledStimFormula.setText(str(self.stim_formula))
self.emit({'ui_local_changed': 'stim_formula'})
def _update_stim_par1(self):
""" Update value for self.par1 from QLineEditWidget"""
self.stim_par1 = safe_eval(self.ledStimPar1.text(), self.stim_par1,
sign='pos', return_type='float')
self.ledStimPar1.setText(str(self.stim_par1))
self.emit({'ui_local_changed': 'stim_par1'})
# ------------------------------------------------------------------------------
[docs]
def main():
import sys
from pyfda.libs.compat import QApplication
app = QApplication(sys.argv)
mainw = Plot_Tran_Stim_UI(None)
layVMain = QVBoxLayout()
layVMain.addWidget(mainw.wdg_stim)
# (left, top, right, bottom)
layVMain.setContentsMargins(*params['wdg_margins'])
mainw.setLayout(layVMain)
app.setActiveWindow(mainw)
mainw.show()
sys.exit(app.exec_())
if __name__ == "__main__":
""" Run widget standalone with
`python -m pyfda.plot_widgets.tran.plot_tran_stim_ui` """
import sys
from pyfda.libs.compat import QApplication
from pyfda import pyfda_rc as rc
app = QApplication(sys.argv)
app.setStyleSheet(rc.qss_rc)
mainw = Plot_Tran_Stim_UI()
layVMain = QVBoxLayout()
layVMain.addWidget(mainw.wdg_stim)
mainw.setLayout(layVMain)
app.setActiveWindow(mainw)
mainw.show()
sys.exit(app.exec_())