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 (currently, these are freq_specs and freq_units). 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 prec. # 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'})
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'})
class PlotImpz_UI(QWidget): """ Create the UI for the PlotImpz class """ # incoming: not implemented at the moment, update_N is triggered directly # by plot_impz # sig_rx = pyqtSignal(object) # outgoing: from various UI elements to PlotImpz ('ui_changed':'xxx') sig_tx = pyqtSignal(object) # outgoing to local fft window sig_tx_fft = pyqtSignal(object) def __init__(self, parent): """ Pass instance `parent` of parent class (FilterCoeffs) """ super(PlotImpz_UI, self).__init__(parent) """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ # initial settings self.N_start = 0 self.N_user = 0 self.N = 0 # time self.plt_time_resp = "Stem" self.plt_time_stim = "None" self.plt_time_stmq = "None" self.plt_time_spgr = "None" self.bottom_t = -80 # initial value for log. scale (time) self.nfft_spgr_time = 256 # number of fft points per spectrogram segment self.ovlp_spgr_time = 128 # number of overlap points between spectrogram segments self.mode_spgr_time = "magnitude" # stimuli self.stim = "Impulse" self.chirp_method = 'Linear' self.noise = "None" self.f1 = 0.02 self.f2 = 0.03 self.A1 = 1.0 self.A2 = 0.0 self.phi1 = self.phi2 = 0 self.noi = 0.1 self.noise = 'none' self.DC = 0.0 self.stim_formula = "A1 * abs(sin(2 * pi * f1 * n))" # frequency self.plt_freq_resp = "Line" self.plt_freq_stim = "None" self.plt_freq_stmq = "None" self.bottom_f = -120 # initial value for log. scale self.param = None # dictionary for fft window settings self.win_dict = fb.fil[0]['win_fft'] self.fft_window = None # handle for FFT window pop-up widget self.window_name = "Rectangular" self._construct_UI() self._enable_stim_widgets() self.update_N(emit=False) # also updates window function self._update_noi() def _construct_UI(self): # ----------- --------------------------------------------------- # Run control widgets # --------------------------------------------------------------- self.chk_auto_run = QCheckBox("Auto", self) self.chk_auto_run.setObjectName("chk_auto_run") self.chk_auto_run.setToolTip("<span>Update response automatically when " "parameters have been changed.</span>") self.chk_auto_run.setChecked(True) self.but_run = QPushButton(self) self.but_run.setText("RUN") self.but_run.setToolTip("Run simulation") self.but_run.setEnabled(not self.chk_auto_run.isChecked()) self.cmb_sim_select = QComboBox(self) self.cmb_sim_select.addItems(["Float","Fixpoint"]) qset_cmb_box(self.cmb_sim_select, "Float") self.cmb_sim_select.setToolTip("<span>Simulate floating-point or fixpoint response." "</span>") self.lbl_N_points = QLabel(to_html("N", frmt='bi') + " =", self) self.led_N_points = QLineEdit(self) self.led_N_points.setText(str(self.N)) self.led_N_points.setToolTip("<span>Number of displayed data points. " "<i>N</i> = 0 tries to choose for you.</span>") self.lbl_N_start = QLabel(to_html("N_0", frmt='bi') + " =", self) self.led_N_start = QLineEdit(self) self.led_N_start.setText(str(self.N_start)) self.led_N_start.setToolTip("<span>First point to plot.</span>") self.chk_fx_scale = QCheckBox("Int. scale", self) self.chk_fx_scale.setObjectName("chk_fx_scale") self.chk_fx_scale.setToolTip("<span>Display data with integer (fixpoint) scale.</span>") self.chk_fx_scale.setChecked(False) self.chk_stim_options = QCheckBox("Stim. Options", self) self.chk_stim_options.setObjectName("chk_stim_options") self.chk_stim_options.setToolTip("<span>Show stimulus options.</span>") self.chk_stim_options.setChecked(True) self.but_fft_win = QPushButton(self) self.but_fft_win.setText("WIN FFT") self.but_fft_win.setToolTip('<span> time and frequency response of FFT Window ' '(can be modified in the "Frequency" tab)</span>') self.but_fft_win.setCheckable(True) self.but_fft_win.setChecked(False) layH_ctrl_run = QHBoxLayout() layH_ctrl_run.addWidget(self.but_run) #layH_ctrl_run.addWidget(self.lbl_sim_select) layH_ctrl_run.addWidget(self.cmb_sim_select) layH_ctrl_run.addWidget(self.chk_auto_run) layH_ctrl_run.addStretch(1) layH_ctrl_run.addWidget(self.lbl_N_start) layH_ctrl_run.addWidget(self.led_N_start) layH_ctrl_run.addStretch(1) layH_ctrl_run.addWidget(self.lbl_N_points) layH_ctrl_run.addWidget(self.led_N_points) layH_ctrl_run.addStretch(2) layH_ctrl_run.addWidget(self.chk_fx_scale) layH_ctrl_run.addStretch(2) layH_ctrl_run.addWidget(self.chk_stim_options) layH_ctrl_run.addStretch(2) layH_ctrl_run.addWidget(self.but_fft_win) layH_ctrl_run.addStretch(10) #layH_ctrl_run.setContentsMargins(*params['wdg_margins']) self.wdg_ctrl_run = QWidget(self) self.wdg_ctrl_run.setLayout(layH_ctrl_run) # --- end of run control ---------------------------------------- # ----------- --------------------------------------------------- # Controls for time domain # --------------------------------------------------------------- plot_styles_list = ["None","Dots","Line","Line*","Stem","Stem*","Step","Step*"] lbl_plt_time_title = QLabel("<b>View:</b>", self) self.lbl_plt_time_stim = QLabel(to_html("Stimulus x", frmt='bi'), self) self.cmb_plt_time_stim = QComboBox(self) self.cmb_plt_time_stim.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_time_stim, self.plt_time_stim) self.cmb_plt_time_stim.setToolTip("<span>Plot style for stimulus.</span>") self.lbl_plt_time_stmq = QLabel(to_html(" Fixp. Stim. x_Q", frmt='bi'), self) self.cmb_plt_time_stmq = QComboBox(self) self.cmb_plt_time_stmq.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_time_stmq, self.plt_time_stmq) self.cmb_plt_time_stmq.setToolTip("<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>") lbl_plt_time_resp = QLabel(to_html(" Response y", frmt='bi'), self) self.cmb_plt_time_resp = QComboBox(self) self.cmb_plt_time_resp.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_time_resp, self.plt_time_resp) self.cmb_plt_time_resp.setToolTip("<span>Plot style for response.</span>") lbl_win_time = QLabel(to_html(" FFT Window", frmt='bi'), self) self.chk_win_time = QCheckBox(self) self.chk_win_time.setObjectName("chk_win_time") self.chk_win_time.setToolTip('<span>Show FFT windowing function (can be modified in the "Frequency" tab).</span>') self.chk_win_time.setChecked(False) lbl_log_time = QLabel(to_html("dB", frmt='b'), self) self.chk_log_time = QCheckBox(self) self.chk_log_time.setObjectName("chk_log_time") self.chk_log_time.setToolTip("<span>Logarithmic scale for y-axis.</span>") self.chk_log_time.setChecked(False) self.lbl_log_bottom_time = QLabel(to_html("min =", frmt='bi'), self) self.lbl_log_bottom_time.setVisible(True) self.led_log_bottom_time = QLineEdit(self) self.led_log_bottom_time.setText(str(self.bottom_t)) self.led_log_bottom_time.setToolTip("<span>Minimum display value for time " "and spectrogram plots with log. scale.</span>") self.led_log_bottom_time.setVisible(True) lbl_plt_time_spgr = QLabel(to_html(" Spectrogram", frmt='bi'), self) self.cmb_plt_time_spgr = QComboBox(self) self.cmb_plt_time_spgr.addItems(["None", "x[n]", "x_q[n]", "y[n]"]) qset_cmb_box(self.cmb_plt_time_spgr, self.plt_time_spgr) self.cmb_plt_time_spgr.setToolTip("<span>Show Spectrogram for selected signal.</span>") spgr_en = self.plt_time_spgr != "None" self.lbl_log_spgr_time = QLabel(to_html(" dB", frmt='b'), self) self.lbl_log_spgr_time.setVisible(spgr_en) self.chk_log_spgr_time = QCheckBox(self) self.chk_log_spgr_time.setObjectName("chk_log_spgr") self.chk_log_spgr_time.setToolTip("<span>Logarithmic scale for spectrogram.</span>") self.chk_log_spgr_time.setChecked(True) self.chk_log_spgr_time.setVisible(spgr_en) self.lbl_nfft_spgr_time = QLabel(to_html(" N_FFT =", frmt='bi'), self) self.lbl_nfft_spgr_time.setVisible(spgr_en) self.led_nfft_spgr_time = QLineEdit(self) self.led_nfft_spgr_time.setText(str(self.nfft_spgr_time)) self.led_nfft_spgr_time.setToolTip("<span>Number of FFT points per spectrogram segment.</span>") self.led_nfft_spgr_time.setVisible(spgr_en) self.lbl_ovlp_spgr_time = QLabel(to_html(" N_OVLP =", frmt='bi'), self) self.lbl_ovlp_spgr_time.setVisible(spgr_en) self.led_ovlp_spgr_time = QLineEdit(self) self.led_ovlp_spgr_time.setText(str(self.ovlp_spgr_time)) self.led_ovlp_spgr_time.setToolTip("<span>Number of overlap data points between spectrogram segments.</span>") self.led_ovlp_spgr_time.setVisible(spgr_en) self.lbl_mode_spgr_time = QLabel(to_html(" Mode", frmt='bi'), self) self.lbl_mode_spgr_time.setVisible(spgr_en) self.cmb_mode_spgr_time = QComboBox(self) spgr_modes = [("PSD","psd"), ("Mag.","magnitude"),\ ("Angle","angle"), ("Phase","phase")] for i in spgr_modes: self.cmb_mode_spgr_time.addItem(*i) qset_cmb_box(self.cmb_mode_spgr_time, self.mode_spgr_time, data=True) self.cmb_mode_spgr_time.setToolTip("<span>Spectrogram display mode.</span>") self.cmb_mode_spgr_time.setVisible(spgr_en) self.lbl_byfs_spgr_time = QLabel(to_html(" per f_S", frmt='b'), self) self.lbl_byfs_spgr_time.setVisible(spgr_en) self.chk_byfs_spgr_time = QCheckBox(self) self.chk_byfs_spgr_time.setObjectName("chk_log_spgr") self.chk_byfs_spgr_time.setToolTip("<span>Display spectral density i.e. scale by f_S</span>") self.chk_byfs_spgr_time.setChecked(True) self.chk_byfs_spgr_time.setVisible(spgr_en) # self.lbl_colorbar_time = QLabel(to_html(" Col.bar", frmt='b'), self) # self.lbl_colorbar_time.setVisible(spgr_en) # self.chk_colorbar_time = QCheckBox(self) # self.chk_colorbar_time.setObjectName("chk_colorbar_time") # self.chk_colorbar_time.setToolTip("<span>Enable colorbar</span>") # self.chk_colorbar_time.setChecked(True) # self.chk_colorbar_time.setVisible(spgr_en) self.chk_fx_limits = QCheckBox("Min/max.", self) self.chk_fx_limits.setObjectName("chk_fx_limits") self.chk_fx_limits.setToolTip("<span>Display limits of fixpoint range.</span>") self.chk_fx_limits.setChecked(False) layH_ctrl_time = QHBoxLayout() layH_ctrl_time.addWidget(lbl_plt_time_title) layH_ctrl_time.addStretch(1) layH_ctrl_time.addWidget(self.lbl_plt_time_stim) layH_ctrl_time.addWidget(self.cmb_plt_time_stim) # layH_ctrl_time.addWidget(self.lbl_plt_time_stmq) layH_ctrl_time.addWidget(self.cmb_plt_time_stmq) # layH_ctrl_time.addWidget(lbl_plt_time_resp) layH_ctrl_time.addWidget(self.cmb_plt_time_resp) # layH_ctrl_time.addWidget(lbl_win_time) layH_ctrl_time.addWidget(self.chk_win_time) layH_ctrl_time.addStretch(1) layH_ctrl_time.addWidget(lbl_log_time) layH_ctrl_time.addWidget(self.chk_log_time) layH_ctrl_time.addWidget(self.lbl_log_bottom_time) layH_ctrl_time.addWidget(self.led_log_bottom_time) # layH_ctrl_time.addStretch(1) # layH_ctrl_time.addWidget(lbl_plt_time_spgr) layH_ctrl_time.addWidget(self.cmb_plt_time_spgr) layH_ctrl_time.addWidget(self.lbl_log_spgr_time) layH_ctrl_time.addWidget(self.chk_log_spgr_time) layH_ctrl_time.addWidget(self.lbl_nfft_spgr_time) layH_ctrl_time.addWidget(self.led_nfft_spgr_time) layH_ctrl_time.addWidget(self.lbl_ovlp_spgr_time) layH_ctrl_time.addWidget(self.led_ovlp_spgr_time) layH_ctrl_time.addWidget(self.lbl_mode_spgr_time) layH_ctrl_time.addWidget(self.cmb_mode_spgr_time) layH_ctrl_time.addWidget(self.lbl_byfs_spgr_time) layH_ctrl_time.addWidget(self.chk_byfs_spgr_time) layH_ctrl_time.addStretch(2) layH_ctrl_time.addWidget(self.chk_fx_limits) layH_ctrl_time.addStretch(10) #layH_ctrl_time.setContentsMargins(*params['wdg_margins']) self.wdg_ctrl_time = QWidget(self) self.wdg_ctrl_time.setLayout(layH_ctrl_time) # ---- end time domain ------------------ # --------------------------------------------------------------- # Controls for frequency domain # --------------------------------------------------------------- lbl_plt_freq_title = QLabel("<b>View:</b>", self) self.lbl_plt_freq_stim = QLabel(to_html("Stimulus X", frmt='bi'), self) self.cmb_plt_freq_stim = QComboBox(self) self.cmb_plt_freq_stim.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_freq_stim, self.plt_freq_stim) self.cmb_plt_freq_stim.setToolTip("<span>Plot style for stimulus.</span>") self.lbl_plt_freq_stmq = QLabel(to_html(" Fixp. Stim. X_Q", frmt='bi'), self) self.cmb_plt_freq_stmq = QComboBox(self) self.cmb_plt_freq_stmq.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_freq_stmq, self.plt_freq_stmq) self.cmb_plt_freq_stmq.setToolTip("<span>Plot style for <em>fixpoint</em> (quantized) stimulus.</span>") lbl_plt_freq_resp = QLabel(to_html(" Response Y", frmt='bi'), self) self.cmb_plt_freq_resp = QComboBox(self) self.cmb_plt_freq_resp.addItems(plot_styles_list) qset_cmb_box(self.cmb_plt_freq_resp, self.plt_freq_resp) self.cmb_plt_freq_resp.setToolTip("<span>Plot style for response.</span>") lbl_log_freq = QLabel(to_html("dB", frmt='b'), self) self.chk_log_freq = QCheckBox(self) self.chk_log_freq.setObjectName("chk_log_freq") self.chk_log_freq.setToolTip("<span>Logarithmic scale for y-axis.</span>") self.chk_log_freq.setChecked(True) self.lbl_log_bottom_freq = QLabel(to_html("min =", frmt='bi'), self) self.lbl_log_bottom_freq.setVisible(self.chk_log_freq.isChecked()) self.led_log_bottom_freq = QLineEdit(self) self.led_log_bottom_freq.setText(str(self.bottom_f)) self.led_log_bottom_freq.setToolTip("<span>Minimum display value for log. scale.</span>") self.led_log_bottom_freq.setVisible(self.chk_log_freq.isChecked()) if not self.chk_log_freq.isChecked(): self.bottom_f = 0 lbl_re_im_freq = QLabel(to_html("Re / Im", frmt='b'), self) self.chk_re_im_freq = QCheckBox(self) self.chk_re_im_freq.setObjectName("chk_re_im_freq") self.chk_re_im_freq.setToolTip("<span>Show real and imaginary part of spectrum</span>") self.chk_re_im_freq.setChecked(False) self.lbl_win_fft = QLabel(to_html("Window", frmt='bi'), self) self.cmb_win_fft = QComboBox(self) self.cmb_win_fft.addItems(get_window_names()) self.cmb_win_fft.setToolTip("FFT window type.") qset_cmb_box(self.cmb_win_fft, self.window_name) self.cmb_win_fft_variant = QComboBox(self) self.cmb_win_fft_variant.setToolTip("FFT window variant.") self.cmb_win_fft_variant.setVisible(False) self.lblWinPar1 = QLabel("Param1") self.ledWinPar1 = QLineEdit(self) self.ledWinPar1.setText("1") self.ledWinPar1.setObjectName("ledWinPar1") self.lblWinPar2 = QLabel("Param2") self.ledWinPar2 = QLineEdit(self) self.ledWinPar2.setText("2") self.ledWinPar2.setObjectName("ledWinPar2") self.chk_Hf = QCheckBox(self) self.chk_Hf.setObjectName("chk_Hf") self.chk_Hf.setToolTip("<span>Show ideal frequency response, calculated " "from the filter coefficients.</span>") self.chk_Hf.setChecked(False) self.chk_Hf_lbl = QLabel(to_html("H_id (f)", frmt="bi"), self) lbl_show_info_freq = QLabel(to_html("Info", frmt='b'), self) self.chk_show_info_freq = QCheckBox(self) self.chk_show_info_freq.setObjectName("chk_show_info_freq") self.chk_show_info_freq.setToolTip("<span>Show infos about signal power " "and window properties.</span>") self.chk_show_info_freq.setChecked(False) layH_ctrl_freq = QHBoxLayout() layH_ctrl_freq.addWidget(lbl_plt_freq_title) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(self.lbl_plt_freq_stim) layH_ctrl_freq.addWidget(self.cmb_plt_freq_stim) # layH_ctrl_freq.addWidget(self.lbl_plt_freq_stmq) layH_ctrl_freq.addWidget(self.cmb_plt_freq_stmq) # layH_ctrl_freq.addWidget(lbl_plt_freq_resp) layH_ctrl_freq.addWidget(self.cmb_plt_freq_resp) # layH_ctrl_freq.addWidget(self.chk_Hf_lbl) layH_ctrl_freq.addWidget(self.chk_Hf) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(lbl_log_freq) layH_ctrl_freq.addWidget(self.chk_log_freq) layH_ctrl_freq.addWidget(self.lbl_log_bottom_freq) layH_ctrl_freq.addWidget(self.led_log_bottom_freq) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(lbl_re_im_freq) layH_ctrl_freq.addWidget(self.chk_re_im_freq) layH_ctrl_freq.addStretch(2) layH_ctrl_freq.addWidget(self.lbl_win_fft) layH_ctrl_freq.addWidget(self.cmb_win_fft) layH_ctrl_freq.addWidget(self.cmb_win_fft_variant) layH_ctrl_freq.addWidget(self.lblWinPar1) layH_ctrl_freq.addWidget(self.ledWinPar1) layH_ctrl_freq.addWidget(self.lblWinPar2) layH_ctrl_freq.addWidget(self.ledWinPar2) layH_ctrl_freq.addStretch(1) layH_ctrl_freq.addWidget(lbl_show_info_freq) layH_ctrl_freq.addWidget(self.chk_show_info_freq) layH_ctrl_freq.addStretch(10) #layH_ctrl_freq.setContentsMargins(*params['wdg_margins']) self.wdg_ctrl_freq = QWidget(self) self.wdg_ctrl_freq.setLayout(layH_ctrl_freq) # ---- end Frequency Domain ------------------ # --------------------------------------------------------------- # Controls for stimuli # --------------------------------------------------------------- lbl_title_stim = QLabel("<b>Stimulus:</b>", self) self.lblStimulus = QLabel(to_html("Type", frmt='bi'), self) self.cmbStimulus = QComboBox(self) self.cmbStimulus.addItems(["None","Impulse","Step","StepErr","Cos","Sine", "Chirp", "Triang","Saw","Rect","Comb","AM","PM / FM","Formula"]) self.cmbStimulus.setToolTip("Stimulus type.") qset_cmb_box(self.cmbStimulus, self.stim) self.chk_stim_bl = QCheckBox("BL", self) self.chk_stim_bl.setToolTip("<span>The signal is bandlimited to the Nyquist frequency " "to avoid aliasing. However, it is much slower to generate " "than the regular version.</span>") self.chk_stim_bl.setChecked(True) self.chk_stim_bl.setObjectName("stim_bl") self.cmbChirpMethod = QComboBox(self) for t in [("Lin","Linear"),("Square","Quadratic"),("Log", "Logarithmic"), ("Hyper", "Hyperbolic")]: self.cmbChirpMethod.addItem(*t) qset_cmb_box(self.cmbChirpMethod, self.chirp_method, data=False) self.chk_scale_impz_f = QCheckBox("Scale", self) self.chk_scale_impz_f.setToolTip("<span>Scale the FFT of the impulse response with <i>N<sub>FFT</sub></i> " "so that it has the same magnitude as |H(f)|. DC and Noise need to be " "turned off.</span>") self.chk_scale_impz_f.setChecked(True) self.chk_scale_impz_f.setObjectName("scale_impz_f") self.lblDC = QLabel(to_html("DC =", frmt='bi'), self) self.ledDC = QLineEdit(self) self.ledDC.setText(str(self.DC)) self.ledDC.setToolTip("DC Level") self.ledDC.setObjectName("stimDC") layHCmbStim = QHBoxLayout() layHCmbStim.addWidget(self.cmbStimulus) layHCmbStim.addWidget(self.chk_stim_bl) layHCmbStim.addWidget(self.chk_scale_impz_f) layHCmbStim.addWidget(self.cmbChirpMethod) #---------------------------------------------- self.lblAmp1 = QLabel(to_html(" A_1", frmt='bi') + " =", self) self.ledAmp1 = QLineEdit(self) self.ledAmp1.setText(str(self.A1)) self.ledAmp1.setToolTip("Stimulus amplitude, complex values like 3j - 1 are allowed") self.ledAmp1.setObjectName("stimAmp1") self.lblAmp2 = QLabel(to_html(" A_2", frmt='bi') + " =", self) self.ledAmp2 = QLineEdit(self) self.ledAmp2.setText(str(self.A2)) self.ledAmp2.setToolTip("Stimulus amplitude 2, complex values like 3j - 1 are allowed") self.ledAmp2.setObjectName("stimAmp2") #---------------------------------------------- self.lblPhi1 = QLabel(to_html(" φ_1", frmt='bi') + " =", self) self.ledPhi1 = QLineEdit(self) self.ledPhi1.setText(str(self.phi1)) self.ledPhi1.setToolTip("Stimulus phase") self.ledPhi1.setObjectName("stimPhi1") self.lblPhU1 = QLabel(to_html("°", frmt='b'), self) self.lblPhi2 = QLabel(to_html(" φ_2", frmt='bi') + " =", self) self.ledPhi2 = QLineEdit(self) self.ledPhi2.setText(str(self.phi2)) self.ledPhi2.setToolTip("Stimulus phase 2") self.ledPhi2.setObjectName("stimPhi2") self.lblPhU2 = QLabel(to_html("°", frmt='b'), self) #---------------------------------------------- self.lblFreq1 = QLabel(to_html(" f_1", frmt='bi') + " =", self) self.ledFreq1 = QLineEdit(self) self.ledFreq1.setText(str(self.f1)) self.ledFreq1.setToolTip("Stimulus frequency 1") self.ledFreq1.setObjectName("stimFreq1") self.lblFreqUnit1 = QLabel("f_S", self) self.lblFreq2 = QLabel(to_html(" f_2", frmt='bi') + " =", self) self.ledFreq2 = QLineEdit(self) self.ledFreq2.setText(str(self.f2)) self.ledFreq2.setToolTip("Stimulus frequency 2") self.ledFreq2.setObjectName("stimFreq2") self.lblFreqUnit2 = QLabel("f_S", self) #---------------------------------------------- self.lblNoise = QLabel(to_html(" Noise", frmt='bi'), self) self.cmbNoise = QComboBox(self) self.cmbNoise.addItems(["None","Gauss","Uniform","PRBS"]) self.cmbNoise.setToolTip("Type of additive noise.") qset_cmb_box(self.cmbNoise, self.noise) self.lblNoi = QLabel("not initialized", self) self.ledNoi = QLineEdit(self) self.ledNoi.setText(str(self.noi)) self.ledNoi.setToolTip("not initialized") self.ledNoi.setObjectName("stimNoi") layGStim = QGridLayout() layGStim.addWidget(self.lblStimulus, 0, 0) layGStim.addWidget(self.lblDC, 1, 0) layGStim.addLayout(layHCmbStim, 0, 1) layGStim.addWidget(self.ledDC, 1, 1) layGStim.addWidget(self.lblAmp1, 0, 2) layGStim.addWidget(self.lblAmp2, 1, 2) layGStim.addWidget(self.ledAmp1, 0, 3) layGStim.addWidget(self.ledAmp2, 1, 3) layGStim.addWidget(self.lblPhi1, 0, 4) layGStim.addWidget(self.lblPhi2, 1, 4) layGStim.addWidget(self.ledPhi1, 0, 5) layGStim.addWidget(self.ledPhi2, 1, 5) layGStim.addWidget(self.lblPhU1, 0, 6) layGStim.addWidget(self.lblPhU2, 1, 6) layGStim.addWidget(self.lblFreq1, 0, 7) layGStim.addWidget(self.lblFreq2, 1, 7) layGStim.addWidget(self.ledFreq1, 0, 8) layGStim.addWidget(self.ledFreq2, 1, 8) layGStim.addWidget(self.lblFreqUnit1, 0, 9) layGStim.addWidget(self.lblFreqUnit2, 1, 9) layGStim.addWidget(self.lblNoise, 0, 10) layGStim.addWidget(self.lblNoi, 1, 10) layGStim.addWidget(self.cmbNoise, 0, 11) layGStim.addWidget(self.ledNoi, 1, 11) #---------------------------------------------- self.lblStimFormula = QLabel(to_html("x =", frmt='bi'), self) self.ledStimFormula = QLineEdit(self) self.ledStimFormula.setText(str(self.stim_formula)) self.ledStimFormula.setToolTip("<span>Enter formula for stimulus in numexpr syntax" "</span>") self.ledStimFormula.setObjectName("stimFormula") layH_ctrl_stim_formula = QHBoxLayout() layH_ctrl_stim_formula.addWidget(self.lblStimFormula) layH_ctrl_stim_formula.addWidget(self.ledStimFormula,10) #---------------------------------------------- #layG_ctrl_stim = QGridLayout() layH_ctrl_stim_par = QHBoxLayout() layH_ctrl_stim_par.addLayout(layGStim) layV_ctrl_stim = QVBoxLayout() layV_ctrl_stim.addLayout(layH_ctrl_stim_par) layV_ctrl_stim.addLayout(layH_ctrl_stim_formula) layH_ctrl_stim = QHBoxLayout() layH_ctrl_stim.addWidget(lbl_title_stim) layH_ctrl_stim.addStretch(1) layH_ctrl_stim.addLayout(layV_ctrl_stim) layH_ctrl_stim.addStretch(10) self.wdg_ctrl_stim = QWidget(self) self.wdg_ctrl_stim.setLayout(layH_ctrl_stim) # --------- end stimuli --------------------------------- # frequency widgets require special handling as they are scaled with f_s self.ledFreq1.installEventFilter(self) self.ledFreq2.installEventFilter(self) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- # --- run control --- self.led_N_start.editingFinished.connect(self.update_N) self.led_N_points.editingFinished.connect(self.update_N) # --- frequency control --- # careful! currentIndexChanged passes the current index to _update_win_fft self.cmb_win_fft.currentIndexChanged.connect(self._update_win_fft) self.ledWinPar1.editingFinished.connect(self._read_param1) self.ledWinPar2.editingFinished.connect(self._read_param2) # --- stimulus control --- self.chk_stim_options.clicked.connect(self._show_stim_options) self.chk_stim_bl.clicked.connect(self._enable_stim_widgets) self.cmbStimulus.currentIndexChanged.connect(self._enable_stim_widgets) self.cmbNoise.currentIndexChanged.connect(self._update_noi) self.ledNoi.editingFinished.connect(self._update_noi) self.ledAmp1.editingFinished.connect(self._update_amp1) self.ledAmp2.editingFinished.connect(self._update_amp2) self.ledPhi1.editingFinished.connect(self._update_phi1) self.ledPhi2.editingFinished.connect(self._update_phi2) self.cmbChirpMethod.currentIndexChanged.connect(self._update_chirp_method) self.ledDC.editingFinished.connect(self._update_DC) self.ledStimFormula.editingFinished.connect(self._update_stim_formula) #------------------------------------------------------------------------------ def eventFilter(self, source, event): """ Filter all events generated by the monitored 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 normalized to f_S with full precision (only if ``spec_edited == True``) and display the stored value in selected format """ def _store_entry(source): if self.spec_edited: if source.objectName() == "stimFreq1": self.f1 = safe_eval(source.text(), self.f1 * fb.fil[0]['f_S'], return_type='float') / fb.fil[0]['f_S'] source.setText(str(params['FMT'].format(self.f1 * fb.fil[0]['f_S']))) elif source.objectName() == "stimFreq2": self.f2 = safe_eval(source.text(), self.f2 * fb.fil[0]['f_S'], return_type='float') / fb.fil[0]['f_S'] source.setText(str(params['FMT'].format(self.f2 * fb.fil[0]['f_S']))) self.spec_edited = False # reset flag self.sig_tx.emit({'sender':__name__, 'ui_changed':'stim'}) # if isinstance(source, QLineEdit): # if source.objectName() in {"stimFreq1","stimFreq2"}: if event.type() in {QEvent.FocusIn,QEvent.KeyPress, QEvent.FocusOut}: if event.type() == QEvent.FocusIn: self.spec_edited = False self.load_fs() 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 if source.objectName() == "stimFreq1": source.setText(str(params['FMT'].format(self.f1 * fb.fil[0]['f_S']))) elif source.objectName() == "stimFreq2": source.setText(str(params['FMT'].format(self.f2 * fb.fil[0]['f_S']))) elif event.type() == QEvent.FocusOut: _store_entry(source) # Call base class method to continue normal event processing: return super(PlotImpz_UI, self).eventFilter(source, event) #------------------------------------------------------------- def _show_stim_options(self): """ Hide / show panel with stimulus options """ self.wdg_ctrl_stim.setVisible(self.chk_stim_options.isChecked()) def _enable_stim_widgets(self): """ Enable / disable widgets depending on the selected stimulus""" self.stim = qget_cmb_box(self.cmbStimulus, data=False) f1_en = self.stim in {"Cos","Sine","Chirp","PM / FM","AM","Formula","Rect","Saw","Triang","Comb"} f2_en = self.stim in {"Cos","Sine","Chirp","PM / FM","AM","Formula"} dc_en = self.stim not in {"Step", "StepErr"} self.chk_stim_bl.setVisible(self.stim in {"Triang", "Saw", "Rect"}) self.lblAmp1.setVisible(self.stim != "None") self.ledAmp1.setVisible(self.stim != "None") self.chk_scale_impz_f.setVisible(self.stim == 'Impulse') self.chk_scale_impz_f.setEnabled((self.noi == 0 or self.cmbNoise.currentText() == 'None')\ and self.DC == 0) self.cmbChirpMethod.setVisible(self.stim == 'Chirp') self.lblPhi1.setVisible(f1_en) self.ledPhi1.setVisible(f1_en) self.lblPhU1.setVisible(f1_en) self.lblFreq1.setVisible(f1_en) self.ledFreq1.setVisible(f1_en) self.lblFreqUnit1.setVisible(f1_en) self.lblFreq2.setVisible(f2_en) self.ledFreq2.setVisible(f2_en) self.lblFreqUnit2.setVisible(f2_en) self.lblAmp2.setVisible(f2_en and self.stim != "Chirp") self.ledAmp2.setVisible(f2_en and self.stim != "Chirp") self.lblPhi2.setVisible(f2_en and self.stim != "Chirp") self.ledPhi2.setVisible(f2_en and self.stim != "Chirp") self.lblPhU2.setVisible(f2_en and self.stim != "Chirp") self.lblDC.setVisible(dc_en) self.ledDC.setVisible(dc_en) self.lblStimFormula.setVisible(self.stim == "Formula") self.ledStimFormula.setVisible(self.stim == "Formula") self.sig_tx.emit({'sender':__name__, 'ui_changed':'stim'}) #------------------------------------------------------------- def load_fs(self): """ Reload sampling frequency from filter dictionary and transform the displayed frequency spec input fields according to the units setting (i.e. f_S). 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! load_fs() is called during init and when the frequency unit or the sampling frequency have been changed. It should be called when sigSpecsChanged or sigFilterDesigned is emitted at another place, indicating that a reload is required. """ # recalculate displayed freq spec values for (maybe) changed f_S if self.ledFreq1.hasFocus(): # widget has focus, show full precision self.ledFreq1.setText(str(self.f1 * fb.fil[0]['f_S'])) elif self.ledFreq2.hasFocus(): # widget has focus, show full precision self.ledFreq2.setText(str(self.f2 * fb.fil[0]['f_S'])) else: # widgets have no focus, round the display self.ledFreq1.setText( str(params['FMT'].format(self.f1 * fb.fil[0]['f_S']))) self.ledFreq2.setText( str(params['FMT'].format(self.f2 * fb.fil[0]['f_S']))) 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.sig_tx.emit({'sender':__name__, 'ui_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.sig_tx.emit({'sender':__name__, 'ui_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.sig_tx.emit({'sender':__name__, 'ui_changed':'phi1'}) 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.sig_tx.emit({'sender':__name__, 'ui_changed':'phi2'}) def _update_chirp_method(self): """ Update value for self.chirp_method from the QLineEditWidget""" self.chirp_method = qget_cmb_box(self.cmbChirpMethod) # read current data string self.sig_tx.emit({'sender':__name__, 'ui_changed':'chirp_method'}) def _update_noi(self): """ Update type + value + label for self.noi for noise""" self.noise = qget_cmb_box(self.cmbNoise, data=False).lower() self.lblNoi.setVisible(self.noise!='none') self.ledNoi.setVisible(self.noise!='none') 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 of statistical process," "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 == 'prbs': self.lblNoi.setText(to_html(" A =", frmt='bi')) self.ledNoi.setToolTip("<span>Amplitude of bipolar Pseudorandom Binary Sequence. " "Noise power is <i>P</i> = A<sup>2</sup>.</span>") self.sig_tx.emit({'sender':__name__, 'ui_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.sig_tx.emit({'sender':__name__, 'ui_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.sig_tx.emit({'sender':__name__, 'ui_changed':'stim_formula'}) # ------------------------------------------------------------------------- def update_N(self, emit=True): # called directly from impz or locally # between local triggering and updates upstream """ Update values for self.N and self.N_start from the QLineEditWidget, update the window and fire "ui_changed" """ if not isinstance(emit, bool): logger.error("update N: emit={0}".format(emit)) self.N_start = safe_eval(self.led_N_start.text(), self.N_start, return_type='int', sign='poszero') self.led_N_start.setText(str(self.N_start)) # update widget self.N_user = safe_eval(self.led_N_points.text(), self.N_user, return_type='int', sign='poszero') if self.N_user == 0: # automatic calculation self.N = self.calc_n_points(self.N_user) # widget remains set to 0 self.led_N_points.setText("0") # update widget else: self.N = self.N_user self.led_N_points.setText(str(self.N)) # update widget self.N_end = self.N + self.N_start # total number of points to be calculated: N + N_start # FFT window needs to be updated due to changed number of data points self._update_win_fft(emit=False) # don't emit anything here if emit: self.sig_tx.emit({'sender':__name__, 'ui_changed':'N'}) 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'], 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, arg=None, emit=True): """ Update window type for FFT with different arguments: - signal-slot connection to combo-box -> index (int), absorbed by `arg` emit is not set -> emit=True - called by _read_param() -> empty -> emit=True - called by update_N(emit=False) """ if not isinstance(emit, bool): logger.error("update win: emit={0}".format(emit)) self.window_name = qget_cmb_box(self.cmb_win_fft, data=False) self.win = calc_window_function(self.win_dict, self.window_name, N=self.N, sym=False) 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']) self.nenbw = self.N * np.sum(np.square(self.win)) / (np.square(np.sum(self.win))) self.cgain = np.sum(self.win) / self.N # coherent gain self.win /= self.cgain # correct gain for periodic signals # only emit a signal for local triggers to prevent infinite loop: # - signal-slot connection passes a bool or an integer # - local function calls don't pass anything if emit is True: self.sig_tx.emit({'sender':__name__, 'ui_changed':'win'}) # ... but always notify the FFT widget via sig_tx_fft self.sig_tx_fft.emit({'sender':__name__, 'view_changed':'win'}) #------------------------------------------------------------------------------ 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 otherwise it # (and the attached window) is deleted immediately when it goes out of scope self.fft_window = Plot_FFT_win(self, win_dict=self.win_dict, sym=False, title="pyFDA Spectral Window Viewer") self.sig_tx_fft.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") #------------------------------------------------------------------------------ def calc_n_points(self, N_user = 0): """ Calculate number of points to be displayed, depending on type of filter (FIR, IIR) and user input. If the user selects 0 points, the number is calculated automatically. An improvement would be to calculate the dominant pole and the corresponding settling time. """ if N_user == 0: # set number of data points automatically if fb.fil[0]['ft'] == 'IIR': N = 100 else: N = min(len(fb.fil[0]['ba'][0]),100) # FIR: N = number of coefficients (max. 100) else: N = N_user return N
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_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 # ------------------------------------------------------------------------------ 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.recalc_freqs() # ------------------------------------------------------------------------------ def __init__(self): super().__init__() """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ # initial settings self.N_FFT = 0 # TODO: FFT value needs to be passed here somehow? # stimuli self.cmb_stim_item = "impulse" self.cmb_stim_periodic_item = 'square' self.stim = "dirac" self.impulse_type = 'dirac' self.sinusoid_type = 'sine' self.chirp_type = 'linear' self.modulation_type = 'am' self.noise = "None" 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 = 1 self.BW1 = self.BW2 = 0.5 self.noi = 0.1 self.noise = 'none' 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", "phi1", "T1", "TW1", "f1", "noise"}, "chirp": {"dc", "a1", "phi1", "f1", "f2", "T2", "noise"}, "triang": {"dc", "a1", "phi1", "f1", "noise", "bl"}, "saw": {"dc", "a1", "phi1", "f1", "noise", "bl"}, "square": {"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"}, "formula": { "dc", "a1", "a2", "phi1", "phi2", "f1", "f2", "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 periodic signals items self.cmb_stim_periodic_items = [ "<span>Periodic functions with discontinuities.</span>", ("square", "Square", "<span>Square 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", "Sinc", "<span>Periodic Sinc (Dirichlet function)</span>") ] # 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>"), ("prbs", "PRBS", "<span>Pseudo-Random Binary Sequence with values ± A.</span>" ), ("mls", "MLS", "<span>Maximum Length Sequence with values ± A. The sequence is " "always the same as the state is not stored for the next sequence start." "</span>"), ("brownian", "Brownian", "<span>Brownian (cumulated sum) process based on Gaussian noise with" " std. deviation σ.</span>") ] self._construct_UI() self._enable_stim_widgets() self._update_noi() def _construct_UI(self): # ===================================================================== # Controls for stimuli # ===================================================================== self.cmbStimulus = QComboBox(self) qcmb_box_populate(self.cmbStimulus, self.cmb_stim_items, self.cmb_stim_item) self.lblStimPar1 = QLabel(to_html("α =", frmt='b'), self) self.ledStimPar1 = QLineEdit(self) self.ledStimPar1.setText("0.5") self.ledStimPar1.setToolTip("Duty Cycle, 0 ... 1") self.ledStimPar1.setObjectName("ledStimPar1") self.but_stim_bl = QPushButton(self) self.but_stim_bl.setText("BL") self.but_stim_bl.setToolTip( "<span>Bandlimit the signal to the Nyquist " "frequency to avoid aliasing. However, this is much slower " "to calculate especially for a large number of points.</span>") self.but_stim_bl.setMaximumWidth(qtext_width(text="BL ")) self.but_stim_bl.setCheckable(True) self.but_stim_bl.setChecked(True) self.but_stim_bl.setObjectName("stim_bl") # ------------------------------------- self.cmbChirpType = QComboBox(self) qcmb_box_populate(self.cmbChirpType, self.cmb_stim_chirp_items, self.chirp_type) self.cmbImpulseType = QComboBox(self) qcmb_box_populate(self.cmbImpulseType, self.cmb_stim_impulse_items, self.impulse_type) self.cmbSinusoidType = QComboBox(self) qcmb_box_populate(self.cmbSinusoidType, self.cmb_stim_sinusoid_items, self.sinusoid_type) self.cmbPeriodicType = QComboBox(self) qcmb_box_populate(self.cmbPeriodicType, self.cmb_stim_periodic_items, self.cmb_stim_periodic_item) self.cmbModulationType = QComboBox(self) for t in [("AM", "am"), ("PM / FM", "pmfm")]: # text, data self.cmbModulationType.addItem(*t) qset_cmb_box(self.cmbModulationType, self.modulation_type, data=True) # ------------------------------------- self.chk_step_err = QPushButton("Error", self) self.chk_step_err.setToolTip( "<span>Display the step response error.</span>") self.chk_step_err.setMaximumWidth(qtext_width(text="Error ")) self.chk_step_err.setCheckable(True) self.chk_step_err.setChecked(False) self.chk_step_err.setObjectName("stim_step_err") layHCmbStim = QHBoxLayout() layHCmbStim.addWidget(self.cmbStimulus) layHCmbStim.addWidget(self.cmbImpulseType) layHCmbStim.addWidget(self.cmbSinusoidType) layHCmbStim.addWidget(self.cmbChirpType) layHCmbStim.addWidget(self.cmbPeriodicType) layHCmbStim.addWidget(self.cmbModulationType) layHCmbStim.addWidget(self.but_stim_bl) layHCmbStim.addWidget(self.lblStimPar1) layHCmbStim.addWidget(self.ledStimPar1) layHCmbStim.addWidget(self.chk_step_err) self.lblDC = QLabel(to_html("DC =", frmt='bi'), self) self.ledDC = QLineEdit(self) self.ledDC.setText(str(self.DC)) self.ledDC.setToolTip("DC Level") self.ledDC.setObjectName("stimDC") layHStimDC = QHBoxLayout() layHStimDC.addWidget(self.lblDC) layHStimDC.addWidget(self.ledDC) # ====================================================================== self.lblAmp1 = QLabel(to_html(" A_1", frmt='bi') + " =", self) self.ledAmp1 = QLineEdit(self) self.ledAmp1.setText(str(self.A1)) self.ledAmp1.setToolTip( "Stimulus amplitude, complex values like 3j - 1 are allowed") self.ledAmp1.setObjectName("stimAmp1") self.lblAmp2 = QLabel(to_html(" A_2", frmt='bi') + " =", self) self.ledAmp2 = QLineEdit(self) self.ledAmp2.setText(str(self.A2)) self.ledAmp2.setToolTip( "Stimulus amplitude 2, complex values like 3j - 1 are allowed") self.ledAmp2.setObjectName("stimAmp2") # ---------------------------------------------- self.lblPhi1 = QLabel(to_html(" φ_1", frmt='bi') + " =", self) self.ledPhi1 = QLineEdit(self) self.ledPhi1.setText(str(self.phi1)) self.ledPhi1.setToolTip("Stimulus phase") self.ledPhi1.setObjectName("stimPhi1") self.lblPhU1 = QLabel(to_html("°", frmt='b'), self) self.lblPhi2 = QLabel(to_html(" φ_2", frmt='bi') + " =", self) self.ledPhi2 = QLineEdit(self) self.ledPhi2.setText(str(self.phi2)) self.ledPhi2.setToolTip("Stimulus phase 2") self.ledPhi2.setObjectName("stimPhi2") self.lblPhU2 = QLabel(to_html("°", frmt='b'), self) # ---------------------------------------------- self.lbl_T1 = QLabel(to_html(" T_1", frmt='bi') + " =", self) self.led_T1 = QLineEdit(self) self.led_T1.setText(str(self.T1)) self.led_T1.setToolTip("Time shift") self.led_T1.setObjectName("stimT1") self.lbl_TU1 = QLabel(to_html("T_S", frmt='b'), self) self.lbl_T2 = QLabel(to_html(" T_2", frmt='bi') + " =", self) self.led_T2 = QLineEdit(self) self.led_T2.setText(str(self.T2)) self.led_T2.setToolTip("Time shift 2") self.led_T2.setObjectName("stimT2") self.lbl_TU2 = QLabel(to_html("T_S", frmt='b'), self) # --------------------------------------------- self.lbl_TW1 = QLabel( to_html(" ΔT_1", frmt='bi') + " =", self) self.led_TW1 = QLineEdit(self) self.led_TW1.setText(str(self.TW1)) self.led_TW1.setToolTip("Time width") self.led_TW1.setObjectName("stimTW1") self.lbl_TWU1 = QLabel(to_html("T_S", frmt='b'), self) self.lbl_TW2 = QLabel( to_html(" ΔT_2", frmt='bi') + " =", self) self.led_TW2 = QLineEdit(self) self.led_TW2.setText(str(self.TW2)) self.led_TW2.setToolTip("Time width 2") self.led_TW2.setObjectName("stimTW2") self.lbl_TWU2 = QLabel(to_html("T_S", frmt='b'), self) # ---------------------------------------------- self.txtFreq1_f = to_html(" f_1", frmt='bi') + " =" self.txtFreq1_k = to_html(" k_1", frmt='bi') + " =" self.lblFreq1 = QLabel(self.txtFreq1_f, self) self.ledFreq1 = QLineEdit(self) self.ledFreq1.setText(str(self.f1)) self.ledFreq1.setToolTip("Stimulus frequency") self.ledFreq1.setObjectName("stimFreq1") self.lblFreqUnit1 = QLabel("f_S", self) self.txtFreq2_f = to_html(" f_2", frmt='bi') + " =" self.txtFreq2_k = to_html(" k_2", frmt='bi') + " =" self.lblFreq2 = QLabel(self.txtFreq2_f, self) self.ledFreq2 = QLineEdit(self) self.ledFreq2.setText(str(self.f2)) self.ledFreq2.setToolTip("Stimulus frequency 2") self.ledFreq2.setObjectName("stimFreq2") self.lblFreqUnit2 = QLabel("f_S", self) # ---------------------------------------------- self.lbl_BW1 = QLabel( to_html(self.tr(" BW_1"), frmt='bi') + " =", self) self.led_BW1 = QLineEdit(self) self.led_BW1.setText(str(self.BW1)) self.led_BW1.setToolTip(self.tr("Relative bandwidth")) self.led_BW1.setObjectName("stimBW1") self.lbl_BW2 = QLabel( to_html(self.tr(" BW_2"), frmt='bi') + " =", self) self.led_BW2 = QLineEdit(self) self.led_BW2.setText(str(self.BW2)) self.led_BW2.setToolTip(self.tr("Relative bandwidth 2")) self.led_BW2.setObjectName("stimBW2") # ---------------------------------------------- self.lblNoise = QLabel(to_html(" Noise", frmt='bi'), self) self.cmbNoise = QComboBox(self) qcmb_box_populate(self.cmbNoise, self.cmb_stim_noise_items, self.noise) self.lblNoi = QLabel("not initialized", self) self.ledNoi = QLineEdit(self) self.ledNoi.setText(str(self.noi)) self.ledNoi.setToolTip("not initialized") self.ledNoi.setObjectName("stimNoi") layGStim = QGridLayout() layGStim.addLayout(layHCmbStim, 0, 1) layGStim.addLayout(layHStimDC, 1, 1) layGStim.addWidget(self.lblAmp1, 0, 2) layGStim.addWidget(self.lblAmp2, 1, 2) layGStim.addWidget(self.ledAmp1, 0, 3) layGStim.addWidget(self.ledAmp2, 1, 3) layGStim.addWidget(self.lblPhi1, 0, 4) layGStim.addWidget(self.lblPhi2, 1, 4) layGStim.addWidget(self.ledPhi1, 0, 5) layGStim.addWidget(self.ledPhi2, 1, 5) layGStim.addWidget(self.lblPhU1, 0, 6) layGStim.addWidget(self.lblPhU2, 1, 6) layGStim.addWidget(self.lbl_T1, 0, 7) layGStim.addWidget(self.lbl_T2, 1, 7) layGStim.addWidget(self.led_T1, 0, 8) layGStim.addWidget(self.led_T2, 1, 8) layGStim.addWidget(self.lbl_TU1, 0, 9) layGStim.addWidget(self.lbl_TU2, 1, 9) layGStim.addWidget(self.lbl_TW1, 0, 10) layGStim.addWidget(self.lbl_TW2, 1, 10) layGStim.addWidget(self.led_TW1, 0, 11) layGStim.addWidget(self.led_TW2, 1, 11) layGStim.addWidget(self.lbl_TWU1, 0, 12) layGStim.addWidget(self.lbl_TWU2, 1, 12) layGStim.addWidget(self.lblFreq1, 0, 13) layGStim.addWidget(self.lblFreq2, 1, 13) layGStim.addWidget(self.ledFreq1, 0, 14) layGStim.addWidget(self.ledFreq2, 1, 14) layGStim.addWidget(self.lblFreqUnit1, 0, 15) layGStim.addWidget(self.lblFreqUnit2, 1, 15) layGStim.addWidget(self.lbl_BW1, 0, 16) layGStim.addWidget(self.lbl_BW2, 1, 16) layGStim.addWidget(self.led_BW1, 0, 17) layGStim.addWidget(self.led_BW2, 1, 17) layGStim.addWidget(self.lblNoise, 0, 18) layGStim.addWidget(self.lblNoi, 1, 18) layGStim.addWidget(self.cmbNoise, 0, 19) layGStim.addWidget(self.ledNoi, 1, 19) # ---------------------------------------------- self.lblStimFormula = QLabel(to_html("x =", frmt='bi'), self) self.ledStimFormula = QLineEdit(self) self.ledStimFormula.setText(str(self.stim_formula)) self.ledStimFormula.setToolTip( "<span>Enter formula for stimulus in numexpr syntax.</span>") self.ledStimFormula.setObjectName("stimFormula") layH_stim_formula = QHBoxLayout() layH_stim_formula.addWidget(self.lblStimFormula) layH_stim_formula.addWidget(self.ledStimFormula, 10) # ---------------------------------------------------------------------- # Main Widget # ---------------------------------------------------------------------- layH_stim_par = QHBoxLayout() layH_stim_par.addLayout(layGStim) layV_stim = QVBoxLayout() layV_stim.addLayout(layH_stim_par) layV_stim.addLayout(layH_stim_formula) layH_stim = QHBoxLayout() layH_stim.addLayout(layV_stim) layH_stim.addStretch(10) self.wdg_stim = QWidget(self) self.wdg_stim.setLayout(layH_stim) self.wdg_stim.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) # ---------------------------------------------------------------------- # Event Filter # ---------------------------------------------------------------------- # frequency related widgets are scaled with f_s, requiring special handling self.ledFreq1.installEventFilter(self) self.ledFreq2.installEventFilter(self) self.led_T1.installEventFilter(self) self.led_T2.installEventFilter(self) self.led_TW1.installEventFilter(self) self.led_TW2.installEventFilter(self) # ---------------------------------------------------------------------- # GLOBAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.sig_rx.connect(self.process_sig_rx) # ---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs # ---------------------------------------------------------------------- # --- stimulus control --- self.but_stim_bl.clicked.connect(self._enable_stim_widgets) self.chk_step_err.clicked.connect(self._enable_stim_widgets) self.cmbStimulus.currentIndexChanged.connect(self._enable_stim_widgets) self.cmbNoise.currentIndexChanged.connect(self._update_noi) self.ledNoi.editingFinished.connect(self._update_noi) self.ledAmp1.editingFinished.connect(self._update_amp1) self.ledAmp2.editingFinished.connect(self._update_amp2) self.ledPhi1.editingFinished.connect(self._update_phi1) self.ledPhi2.editingFinished.connect(self._update_phi2) self.led_BW1.editingFinished.connect(self._update_BW1) self.led_BW2.editingFinished.connect(self._update_BW2) self.cmbImpulseType.currentIndexChanged.connect( self._update_impulse_type) self.cmbSinusoidType.currentIndexChanged.connect( self._update_sinusoid_type) self.cmbChirpType.currentIndexChanged.connect(self._update_chirp_type) self.cmbPeriodicType.currentIndexChanged.connect( self._update_periodic_type) self.cmbModulationType.currentIndexChanged.connect( self._update_modulation_type) self.ledDC.editingFinished.connect(self._update_DC) self.ledStimFormula.editingFinished.connect(self._update_stim_formula) self.ledStimPar1.editingFinished.connect(self._update_stim_par1) # ------------------------------------------------------------------------------ def update_freq_units(self): """ Update labels referrring to frequency specs """ if fb.fil[0]['freq_specs_unit'] == 'k': 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$", "μ") self.lblFreq1.setText(self.txtFreq1_f) self.lblFreq2.setText(self.txtFreq2_f) if f_unit in {"f_S", "f_Ny"}: unit_frmt = "i" # italic else: 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)) # ------------------------------------------------------------------------------ def eventFilter(self, source, event): """ Filter all events generated by the monitored widgets (ledFreq1 and 2 and T1 / T2). 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 normalized to f_S with full precision (only if ``spec_edited == True``) and display the stored value in selected format Emit 'ui_changed':'stim' """ def _reload_entry(source): """ Reload text entry for active line edit field in rounded format """ if source.objectName() == "stimFreq1": source.setText( str(params['FMT'].format(self.f1 * self.f_scale))) elif source.objectName() == "stimFreq2": source.setText( str(params['FMT'].format(self.f2 * self.f_scale))) elif source.objectName() == "stimT1": source.setText( str(params['FMT'].format(self.T1 * self.t_scale))) elif source.objectName() == "stimT2": source.setText( str(params['FMT'].format(self.T2 * self.t_scale))) elif source.objectName() == "stimTW1": source.setText( str(params['FMT'].format(self.TW1 * self.t_scale))) elif source.objectName() == "stimTW2": source.setText( str(params['FMT'].format(self.TW2 * self.t_scale))) def _store_entry(source): if self.spec_edited: if source.objectName() == "stimFreq1": self.f1 = safe_eval(source.text(), self.f1 * self.f_scale, return_type='float') / self.f_scale source.setText( str(params['FMT'].format(self.f1 * self.f_scale))) elif source.objectName() == "stimFreq2": self.f2 = safe_eval(source.text(), self.f2 * self.f_scale, return_type='float') / self.f_scale source.setText( str(params['FMT'].format(self.f2 * self.f_scale))) elif source.objectName() == "stimT1": self.T1 = safe_eval(source.text(), self.T1 * self.t_scale, return_type='float') / self.t_scale source.setText( str(params['FMT'].format(self.T1 * self.t_scale))) elif source.objectName() == "stimT2": self.T2 = safe_eval(source.text(), self.T2 * self.t_scale, return_type='float') / self.t_scale source.setText( str(params['FMT'].format(self.T2 * self.t_scale))) elif source.objectName() == "stimTW1": self.TW1 = safe_eval(source.text(), self.TW1 * self.t_scale, sign='pos', return_type='float') / self.t_scale source.setText( str(params['FMT'].format(self.TW1 * self.t_scale))) elif source.objectName() == "stimTW2": self.TW2 = safe_eval(source.text(), self.TW2 * self.t_scale, sign='pos', return_type='float') / self.t_scale source.setText( str(params['FMT'].format(self.TW2 * self.t_scale))) self.spec_edited = False # reset flag self._update_scale_impz() self.emit({'ui_changed': 'stim'}) # nothing has changed, but display frequencies in rounded format anyway else: _reload_entry(source) # -------------------------------------------------------------------- # if isinstance(source, QLineEdit): # if source.objectName() in {"stimFreq1","stimFreq2"}: if event.type() in {QEvent.FocusIn, QEvent.KeyPress, QEvent.FocusOut}: if event.type() == QEvent.FocusIn: self.spec_edited = False self.update_freqs() 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) # ------------------------------------------------------------- def recalc_freqs(self): """ Update normalized frequencies if required. This is called by via signal ['ui_changed':'f_S'] from plot_impz.process_sig_rx """ if fb.fil[0]['freq_locked']: self.f1 *= fb.fil[0]['f_S_prev'] / fb.fil[0]['f_S'] self.f2 *= fb.fil[0]['f_S_prev'] / fb.fil[0]['f_S'] self.T1 *= fb.fil[0]['f_S'] / fb.fil[0]['f_S_prev'] self.T2 *= fb.fil[0]['f_S'] / fb.fil[0]['f_S_prev'] self.TW1 *= fb.fil[0]['f_S'] / fb.fil[0]['f_S_prev'] self.TW2 *= fb.fil[0]['f_S'] / fb.fil[0]['f_S_prev'] self._update_scale_impz() self.update_freqs() self.emit({'ui_changed': 'f1_f2'}) # ------------------------------------------------------------- def update_freqs(self): """ `update_freqs()` is called: - when one of the stimulus frequencies has changed via eventFilter() - sampling frequency has been changed via signal ['ui_changed':'f_S'] from plot_impz.process_sig_rx -> self.recalc_freqs The sampling frequency is loaded from filter dictionary and stored as `self.f_scale` (except when the frequency unit is k when `f_scale = self.N_FFT`). Frequency field entries are always stored normalized w.r.t. f_S in the dictionary: When the `f_S` lock button is unlocked, only the displayed values for frequency entries are updated with f_S, not the dictionary. When the `f_S` lock button is pressed, the absolute frequency values in the widget fields are kept constant, and the dictionary entries are updated. """ # recalculate displayed freq spec values for (maybe) changed f_S if fb.fil[0]['freq_specs_unit'] == 'k': self.f_scale = self.N_FFT else: self.f_scale = fb.fil[0]['f_S'] self.t_scale = fb.fil[0]['T_S'] if self.ledFreq1.hasFocus(): # widget has focus, show full precision self.ledFreq1.setText(str(self.f1 * self.f_scale)) elif self.ledFreq2.hasFocus(): self.ledFreq2.setText(str(self.f2 * self.f_scale)) elif self.led_T1.hasFocus(): self.led_T1.setText(str(self.T1 * self.t_scale)) elif self.led_T2.hasFocus(): self.led_T2.setText(str(self.T2 * self.t_scale)) elif self.led_TW1.hasFocus(): self.led_TW1.setText(str(self.TW1 * self.t_scale)) elif self.led_TW2.hasFocus(): self.led_TW2.setText(str(self.TW2 * self.t_scale)) else: # widgets have no focus, round the display self.ledFreq1.setText( str(params['FMT'].format(self.f1 * self.f_scale))) self.ledFreq2.setText( str(params['FMT'].format(self.f2 * self.f_scale))) self.led_T1.setText( str(params['FMT'].format(self.T1 * self.t_scale))) self.led_T2.setText( str(params['FMT'].format(self.T2 * self.t_scale))) self.led_TW1.setText( str(params['FMT'].format(self.TW1 * self.t_scale))) self.led_TW2.setText( str(params['FMT'].format(self.TW2 * self.t_scale))) self.update_freq_units( ) # TODO: should only be called at f_S / unit update # ------------------------------------------------------------- def _enable_stim_widgets(self): """ Enable / disable widgets depending on the selected stimulus """ self.cmb_stim = qget_cmb_box(self.cmbStimulus) if self.cmb_stim == "impulse": self.stim = qget_cmb_box(self.cmbImpulseType) # recalculate the energy scaling for impulse functions self._update_scale_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.chk_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_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.ledFreq1.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_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.ledFreq2.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.lblStimFormula.setVisible(self.stim == "formula") self.ledStimFormula.setVisible(self.stim == "formula") 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_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_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_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_changed': 'phi1'}) 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_scale_impz() self.emit({'ui_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_changed': 'BW2'}) def _update_scale_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_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_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.cmbNoise) self.lblNoi.setVisible(self.noise != 'none') self.ledNoi.setVisible(self.noise != 'none') 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 of statistical process," "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 == 'prbs': self.lblNoi.setText(to_html(" A =", frmt='bi')) self.ledNoi.setToolTip( "<span>Amplitude of bipolar Pseudorandom Binary Sequence. " "Noise power is <i>P</i> = A<sup>2</sup>.</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>.</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_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_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_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_changed': 'stim_par1'})