def __init__(self, parent): super(PlotTauG, self).__init__(parent) self.verbose = False # suppress warnings # ============================================================================= # #### not needed at the moment ### # self.chkWarnings = QCheckBox("Enable Warnings", self) # self.chkWarnings.setChecked(False) # self.chkWarnings.setToolTip("Print warnings about singular group delay") # # layHControls = QHBoxLayout() # layHControls.addStretch(10) # layHControls.addWidget(self.chkWarnings) # # # This widget encompasses all control subwidgets: # self.frmControls = QFrame(self) # self.frmControls.setObjectName("frmControls") # self.frmControls.setLayout(layHControls) # # ============================================================================= self.mplwidget = MplWidget(self) # self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # initial drawing of tau_g #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui)
def __init__(self, parent): super(PlotTauG, self).__init__(parent) self.chkWarnings = QCheckBox() self.chkWarnings.setText("Enable Warnings") self.chkWarnings.setChecked(False) self.chkWarnings.setToolTip("Print warnings about singular group delay") self.layHChkBoxes = QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.chkWarnings) self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) self.setLayout(self.mplwidget.layVMainMpl) # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self._init_axes() self.draw() # initial drawing of tau_g #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkWarnings.clicked.connect(self.draw)
def __init__(self, parent): super(PlotTauG, self).__init__(parent) self.chkWarnings = QCheckBox("Enable Warnings", self) self.chkWarnings.setChecked(False) self.chkWarnings.setToolTip( "Print warnings about singular group delay") layHControls = QHBoxLayout() layHControls.addStretch(10) layHControls.addWidget(self.chkWarnings) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.ax = self.mplwidget.fig.add_subplot(111) self.draw() # initial drawing of tau_g #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkWarnings.clicked.connect(self.draw)
def __init__(self, parent): super(PlotPZ, self).__init__(parent) layHControls = QHBoxLayout() layHControls.addStretch(10) # This widget encompasses all control subwidgets: # self.frmControls = QFrame(self) # self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(layHControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self._init_axes() self.draw() # calculate and draw poles and zeros
class PlotPZ(QtGui.QMainWindow): def __init__(self, parent = None, DEBUG = False): # default parent = None -> top Window super(PlotPZ, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.mplwidget = MplWidget() self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.draw() # calculate and draw poles and zeros # #============================================= # # Signals & Slots # #============================================= # self.btnWhatever.clicked.connect(self.draw) def initAxes(self): """Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() def draw(self): if self.mplwidget.mplToolbar.enable_update: self.draw_pz() def draw_pz(self): """ (re)draw P/Z plot """ zpk = fb.fil[0]['zpk'] self.ax.clear() [z, p, k] = zplane(self.ax, zpk, verbose = False) self.ax.set_title(r'Pole / Zero Plot') self.ax.set_xlabel('Real axis') self.ax.set_ylabel('Imaginary axis') self.ax.axis([-1.1, 1.1, -1.1, 1.1]) self.mplwidget.redraw()
def __init__(self, parent): super(PlotPZ, self).__init__(parent) layHControls = QHBoxLayout() layHControls.addStretch(10) # This widget encompasses all control subwidgets: # self.frmControls = QFrame(self) # self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(layHControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self.init_axes() self.draw() # calculate and draw poles and zeros #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui)
def __init__(self, parent = None, DEBUG = False): # default parent = None -> top Window super(PlotTauG, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG # # self.lblWrap = QtGui.QLabel("Wrapped Phase") # self.btnWrap = QtGui.QCheckBox() # self.btnWrap.setChecked(False) # self.btnWrap.setToolTip("Plot phase wrapped to +/- pi") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) # self.layHChkBoxes.addWidget(self.cmbUnitsPhi) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.draw() # calculate and draw phi(f)
def __init__(self, parent): super(PlotPhi, self).__init__(parent) self.cmbUnitsPhi = QComboBox(self) units = ["rad", "rad/pi", "deg"] scales = [1., 1./ np.pi, 180./np.pi] for unit, scale in zip(units, scales): self.cmbUnitsPhi.addItem(unit, scale) self.cmbUnitsPhi.setObjectName("cmbUnitsA") self.cmbUnitsPhi.setToolTip("Set unit for phase.") self.cmbUnitsPhi.setCurrentIndex(0) self.cmbUnitsPhi.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.chkWrap = QCheckBox("Wrapped Phase", self) self.chkWrap.setChecked(False) self.chkWrap.setToolTip("Plot phase wrapped to +/- pi") layHControls = QHBoxLayout() # layHControls.addStretch(10) layHControls.addWidget(self.cmbUnitsPhi) layHControls.addWidget(self.chkWrap) layHControls.addStretch(10) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # initial drawing # #============================================= # # Signals & Slots # #============================================= self.chkWrap.clicked.connect(self.draw) self.cmbUnitsPhi.currentIndexChanged.connect(self.draw) self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui)
def __init__(self, parent): super(PlotPhi, self).__init__(parent) self.cmbUnitsPhi = QtGui.QComboBox(self) units = ["rad", "rad/pi", "deg"] scales = [1., 1. / np.pi, 180. / np.pi] for unit, scale in zip(units, scales): self.cmbUnitsPhi.addItem(unit, scale) self.cmbUnitsPhi.setObjectName("cmbUnitsA") self.cmbUnitsPhi.setToolTip("Set unit for phase.") self.cmbUnitsPhi.setCurrentIndex(0) self.cmbUnitsPhi.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.lblWrap = QtGui.QLabel("Wrapped Phase") self.btnWrap = QtGui.QCheckBox() self.btnWrap.setChecked(False) self.btnWrap.setToolTip("Plot phase wrapped to +/- pi") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.cmbUnitsPhi) self.layHChkBoxes.addWidget(self.lblWrap) self.layHChkBoxes.addWidget(self.btnWrap) self.layHChkBoxes.addStretch(10) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) self.setLayout(self.mplwidget.layVMainMpl) # self.mplwidget.setFocus() # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self._init_axes() self.draw() # initial drawing # #============================================= # # Signals & Slots # #============================================= self.btnWrap.clicked.connect(self.draw) self.cmbUnitsPhi.currentIndexChanged.connect(self.draw)
def __init__(self, parent): super(PlotPZ, self).__init__(parent) self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) self.setLayout(self.mplwidget.layVMainMpl) # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self._init_axes() self.draw() # calculate and draw poles and zeros
def __init__(self, parent = None, DEBUG = False): # default parent = None -> top Window super(PlotPhi, self).__init__(parent) # initialize QWidget base class self.DEBUG = DEBUG self.cmbUnitsPhi = QtGui.QComboBox(self) units = ["rad", "rad/pi", "deg"] scales = [1., 1./ np.pi, 180./np.pi] for unit, scale in zip(units, scales): self.cmbUnitsPhi.addItem(unit, scale) self.cmbUnitsPhi.setObjectName("cmbUnitsA") self.cmbUnitsPhi.setToolTip("Set unit for phase.") self.cmbUnitsPhi.setCurrentIndex(0) self.cmbUnitsPhi.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.lblWrap = QtGui.QLabel("Wrapped Phase") self.btnWrap = QtGui.QCheckBox() self.btnWrap.setChecked(False) self.btnWrap.setToolTip("Plot phase wrapped to +/- pi") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.cmbUnitsPhi) self.layHChkBoxes.addWidget(self.lblWrap) self.layHChkBoxes.addWidget(self.btnWrap) self.layHChkBoxes.addStretch(10) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.draw() # calculate and draw phi(f) # #============================================= # # Signals & Slots # #============================================= # self.mplwidget.sldLw.valueChanged.connect(lambda:self.draw()) self.btnWrap.clicked.connect(self.draw) self.cmbUnitsPhi.currentIndexChanged.connect(self.draw)
def __init__(self, parent = None, DEBUG = False): # default parent = None -> top Window super(PlotPZ, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.mplwidget = MplWidget() self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.draw() # calculate and draw poles and zeros
class PlotTauG(QWidget): def __init__(self, parent): super(PlotTauG, self).__init__(parent) self.chkWarnings = QCheckBox() self.chkWarnings.setText("Enable Warnings") self.chkWarnings.setChecked(False) self.chkWarnings.setToolTip("Print warnings about singular group delay") self.layHChkBoxes = QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.chkWarnings) self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) self.setLayout(self.mplwidget.layVMainMpl) # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self._init_axes() self.draw() # initial drawing of tau_g #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkWarnings.clicked.connect(self.draw) #------------------------------------------------------------------------------ def _init_axes(self): """Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() self.ax.set_title(r'Group Delay $ \tau_g$') self.ax.hold(False) #plt.gca().cla() #p.clf() #------------------------------------------------------------------------------ def draw(self): if self.mplwidget.mplToolbar.enable_update: self.draw_taug() #------------------------------------------------------------------------------ def update_view(self): """ Update frequency range etc. without recalculating group delay. """ self.draw() #------------------------------------------------------------------------------ def draw_taug(self): """ Draw group delay """ bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' f_S = fb.fil[0]['f_S'] [w, tau_g] = grpdelay(bb,aa, rc.params['N_FFT'], whole = wholeF, verbose = self.chkWarnings.isChecked()) F = w / (2 * np.pi) * fb.fil[0]['f_S'] if fb.fil[0]['freqSpecsRangeType'] == 'sym': tau_g = np.fft.fftshift(tau_g) F = F - f_S / 2. if fb.fil[0]['freq_specs_unit'] in {'f_S', 'f_Ny'}: tau_str = r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega}) / T_S \; \rightarrow $' else: tau_str = r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega})$'\ + ' in ' + fb.fil[0]['plt_tUnit'] + r' $ \rightarrow $' tau_g = tau_g / fb.fil[0]['f_S'] #--------------------------------------------------------- line_tau_g, = self.ax.plot(F, tau_g, label = "Group Delay") #--------------------------------------------------------- self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(tau_str) # widen y-limits to suppress numerical inaccuracies when tau_g = constant self.ax.set_ylim([max(min(tau_g)-0.5,0), max(tau_g) + 0.5]) self.ax.set_xlim(fb.fil[0]['freqSpecsRange']) self.mplwidget.redraw()
def _init_UI(self): self.lblLog = QLabel(self) self.lblLog.setText("Log:") self.chkLog = QCheckBox(self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Show logarithmic impulse / step response.") self.chkLog.setChecked(False) self.lblLogBottom = QLabel("Bottom = ") self.ledLogBottom = QLineEdit(self) self.ledLogBottom.setText("-80") self.ledLogBottom.setToolTip("Minimum display value for log. scale.") self.lbldB = QLabel("dB") self.lblPltStim = QLabel(self) self.lblPltStim.setText("Stimulus: Show") self.chkPltStim = QCheckBox(self) self.chkPltStim.setChecked(False) self.lblStimulus = QLabel("Type = ") self.cmbStimulus = QComboBox(self) self.cmbStimulus.addItems(["Pulse","Step","StepErr", "Sine", "Rect", "Saw"]) self.cmbStimulus.setToolTip("Select stimulus type.") self.lblFreq = QLabel("<i>f</i> =") self.ledFreq = QLineEdit(self) self.ledFreq.setText(str(self.stim_freq)) self.ledFreq.setToolTip("Stimulus frequency.") self.lblFreqUnit = QLabel("f_S") self.lblNPoints = QLabel("<i>N</i> =") self.ledNPoints = QLineEdit(self) self.ledNPoints.setText("0") self.ledNPoints.setToolTip("Number of points to calculate and display.\n" "N = 0 chooses automatically.") self.layHChkBoxes = QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.lblNPoints) self.layHChkBoxes.addWidget(self.ledNPoints) self.layHChkBoxes.addStretch(2) self.layHChkBoxes.addWidget(self.lblLog) self.layHChkBoxes.addWidget(self.chkLog) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblLogBottom) self.layHChkBoxes.addWidget(self.ledLogBottom) self.layHChkBoxes.addWidget(self.lbldB) self.layHChkBoxes.addStretch(2) self.layHChkBoxes.addWidget(self.lblPltStim) self.layHChkBoxes.addWidget(self.chkPltStim) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblStimulus) self.layHChkBoxes.addWidget(self.cmbStimulus) self.layHChkBoxes.addStretch(2) self.layHChkBoxes.addWidget(self.lblFreq) self.layHChkBoxes.addWidget(self.ledFreq) self.layHChkBoxes.addWidget(self.lblFreqUnit) self.layHChkBoxes.addStretch(10) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) self.setLayout(self.mplwidget.layVMainMpl) #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self.draw) self.ledNPoints.editingFinished.connect(self.draw) self.ledLogBottom.editingFinished.connect(self.draw) self.chkPltStim.clicked.connect(self.draw) self.cmbStimulus.currentIndexChanged.connect(self.draw) self.ledFreq.installEventFilter(self) # self.ledFreq.editingFinished.connect(self.draw) self.draw() # initial calculation and drawing
def __init__(self, parent): super(PlotHf, self).__init__(parent) modes = ['| H |', 're{H}', 'im{H}'] self.cmbShowH = QComboBox(self) self.cmbShowH.addItems(modes) self.cmbShowH.setObjectName("cmbUnitsH") self.cmbShowH.setToolTip( "Show magnitude, real / imag. part of H or H \n" "without linear phase (acausal system).") self.cmbShowH.setCurrentIndex(0) self.lblIn = QLabel("in") units = ['dB', 'V', 'W', 'Auto'] self.cmbUnitsA = QComboBox(self) self.cmbUnitsA.addItems(units) self.cmbUnitsA.setObjectName("cmbUnitsA") self.cmbUnitsA.setToolTip( "Set unit for y-axis:\n" "dB is attenuation (positive values)\nV and W are less than 1.") self.cmbUnitsA.setCurrentIndex(0) self.cmbShowH.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbUnitsA.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.lblLinphase = QLabel("Acausal system") self.chkLinphase = QCheckBox() self.chkLinphase.setToolTip( "Remove linear phase according to filter order.\n" "Attention: this makes no sense for a non-linear phase system!") self.lblInset = QLabel("Inset") self.cmbInset = QComboBox(self) self.cmbInset.addItems(['off', 'edit', 'fixed']) self.cmbInset.setObjectName("cmbInset") self.cmbInset.setToolTip("Display/edit second inset plot") self.cmbInset.setCurrentIndex(0) self.inset_idx = 0 # store previous index for comparison self.lblSpecs = QLabel("Show Specs") self.chkSpecs = QCheckBox() self.chkSpecs.setChecked(False) self.chkSpecs.setToolTip("Display filter specs as hatched regions") self.lblPhase = QLabel("Phase") self.chkPhase = QCheckBox() self.chkPhase.setToolTip("Overlay phase") self.layHChkBoxes = QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.cmbShowH) self.layHChkBoxes.addWidget(self.lblIn) self.layHChkBoxes.addWidget(self.cmbUnitsA) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblLinphase) self.layHChkBoxes.addWidget(self.chkLinphase) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblInset) self.layHChkBoxes.addWidget(self.cmbInset) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblSpecs) self.layHChkBoxes.addWidget(self.chkSpecs) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblPhase) self.layHChkBoxes.addWidget(self.chkPhase) self.layHChkBoxes.addStretch(10) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # calculate and draw |H(f)| #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.cmbUnitsA.currentIndexChanged.connect(self.draw) self.cmbShowH.currentIndexChanged.connect(self.draw) self.chkLinphase.clicked.connect(self.draw) self.cmbInset.currentIndexChanged.connect(self.draw_inset) self.chkSpecs.clicked.connect(self.draw) self.chkPhase.clicked.connect(self.draw)
class PlotPhi(QtGui.QMainWindow): def __init__(self, parent = None, DEBUG = False): # default parent = None -> top Window super(PlotPhi, self).__init__(parent) # initialize QWidget base class self.DEBUG = DEBUG self.cmbUnitsPhi = QtGui.QComboBox(self) units = ["rad", "rad/pi", "deg"] scales = [1., 1./ np.pi, 180./np.pi] for unit, scale in zip(units, scales): self.cmbUnitsPhi.addItem(unit, scale) self.cmbUnitsPhi.setObjectName("cmbUnitsA") self.cmbUnitsPhi.setToolTip("Set unit for phase.") self.cmbUnitsPhi.setCurrentIndex(0) self.cmbUnitsPhi.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.lblWrap = QtGui.QLabel("Wrapped Phase") self.btnWrap = QtGui.QCheckBox() self.btnWrap.setChecked(False) self.btnWrap.setToolTip("Plot phase wrapped to +/- pi") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.cmbUnitsPhi) self.layHChkBoxes.addWidget(self.lblWrap) self.layHChkBoxes.addWidget(self.btnWrap) self.layHChkBoxes.addStretch(10) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.draw() # calculate and draw phi(f) # #============================================= # # Signals & Slots # #============================================= # self.mplwidget.sldLw.valueChanged.connect(lambda:self.draw()) self.btnWrap.clicked.connect(self.draw) self.cmbUnitsPhi.currentIndexChanged.connect(self.draw) def initAxes(self): """Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() self.ax.hold(False) def draw(self): if self.mplwidget.mplToolbar.enable_update: self.draw_phi() def draw_phi(self): """ Re-calculate phi(f) and draw the figure """ self.unitPhi = self.cmbUnitsPhi.currentText() self.bb = fb.fil[0]['ba'][0] self.aa = fb.fil[0]['ba'][1] if self.DEBUG: print("--- plotPhi.draw() ---") print("b,a = ", self.bb, self.aa) wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' f_S = fb.fil[0]['f_S'] [W,H] = sig.freqz(self.bb, self.aa, worN = rc.params['N_FFT'], whole = wholeF) F = W / (2 * np.pi) * f_S if fb.fil[0]['freqSpecsRangeType'] == 'sym': H = np.fft.fftshift(H) F = F - f_S / 2. # scale = self.cmbUnitsPhi.itemData(self.cmbUnitsPhi.currentIndex()) y_str = r'$\angle H(\mathrm{e}^{\mathrm{j} \Omega})$' if self.unitPhi == 'rad': y_str += ' in rad ' + r'$\rightarrow $' scale = 1. elif self.unitPhi == 'rad/pi': y_str += ' in rad' + r'$ / \pi \;\rightarrow $' scale = 1./ np.pi else: y_str += ' in deg ' + r'$\rightarrow $' scale = 180./np.pi fb.fil[0]['plt_phiLabel'] = y_str fb.fil[0]['plt_phiUnit'] = self.unitPhi if self.btnWrap.isChecked(): phi_plt = np.angle(H) * scale else: phi_plt = np.unwrap(np.angle(H)) * scale self.ax.clear() #--------------------------------------------------------- line_phi, = self.ax.plot(F, phi_plt) #--------------------------------------------------------- self.ax.set_title(r'Phase Frequency Response') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(y_str) self.ax.set_xlim(fb.fil[0]['freqSpecsRange']) self.mplwidget.redraw()
class PlotHf(QtGui.QMainWindow): # TODO: inset plot should have useful preset range, depending on filter type, # stop band or pass band should be selectable as well as lin / log scale # TODO: position and size of inset plot should be selectable def __init__(self, parent = None, DEBUG = False): # default parent = None -> top Window super(PlotHf, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG modes = ['| H |', 're{H}', 'im{H}'] self.cmbShowH = QtGui.QComboBox(self) self.cmbShowH.addItems(modes) self.cmbShowH.setObjectName("cmbUnitsH") self.cmbShowH.setToolTip("Show magnitude, real / imag. part of H or H \n" "without linear phase (acausal system).") self.cmbShowH.setCurrentIndex(0) self.lblIn = QtGui.QLabel("in") units = ["dB", "V", "W"] self.cmbUnitsA = QtGui.QComboBox(self) self.cmbUnitsA.addItems(units) self.cmbUnitsA.setObjectName("cmbUnitsA") self.cmbUnitsA.setToolTip("Set unit for y-axis:\n" "dB is attenuation (positive values)\nV and W are less than 1.") self.cmbUnitsA.setCurrentIndex(0) self.cmbShowH.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.cmbUnitsA.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.lblLinphase = QtGui.QLabel("Acausal system") self.chkLinphase = QtGui.QCheckBox() self.chkLinphase.setToolTip("Remove linear phase according to filter order.\n" "Attention: this makes no sense for a non-linear phase system!") self.lblInset = QtGui.QLabel("Inset") self.cmbInset = QtGui.QComboBox(self) self.cmbInset.addItems(['off', 'edit', 'fixed']) self.cmbInset.setObjectName("cmbInset") self.cmbInset.setToolTip("Display/edit second inset plot") self.cmbInset.setCurrentIndex(0) self.inset_idx = 0 # store previous index for comparison self.lblSpecs = QtGui.QLabel("Show Specs") self.chkSpecs = QtGui.QCheckBox() self.chkSpecs.setChecked(False) self.chkSpecs.setToolTip("Display filter specs as hatched regions") self.lblPhase = QtGui.QLabel("Phase") self.chkPhase = QtGui.QCheckBox() self.chkPhase.setToolTip("Overlay phase") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.cmbShowH) self.layHChkBoxes.addWidget(self.lblIn) self.layHChkBoxes.addWidget(self.cmbUnitsA) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblLinphase) self.layHChkBoxes.addWidget(self.chkLinphase) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblInset) self.layHChkBoxes.addWidget(self.cmbInset) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblSpecs) self.layHChkBoxes.addWidget(self.chkSpecs) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblPhase) self.layHChkBoxes.addWidget(self.chkPhase) self.layHChkBoxes.addStretch(10) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.layVMainMpl1.addWidget(self.mplwidget) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.draw() # calculate and draw |H(f)| # #============================================= # # Signals & Slots # #============================================= self.cmbUnitsA.currentIndexChanged.connect(self.draw) self.cmbShowH.currentIndexChanged.connect(self.draw) self.chkLinphase.clicked.connect(self.draw) self.cmbInset.currentIndexChanged.connect(self.draw_inset) self.chkSpecs.clicked.connect(self.draw) self.chkPhase.clicked.connect(self.draw_phase) def initAxes(self): """Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() def plotSpecLimits(self, specAxes): """ Plot the specifications limits (F_SB, A_SB, ...) as lines and as hatched areas. """ # fc = (0.8,0.8,0.8) # color for shaded areas fill_params = {'facecolor':'none','hatch':'/', 'edgecolor':rcParams['figure.edgecolor'], 'lw':0.0} line_params = {'linewidth':1.0, 'color':'blue', 'linestyle':'--'} ax = specAxes # extract from filterTree the parameters that are actually used # myParams = fb.fil_tree[rt][ft][dm][fo]['par'] # freqParams = [l for l in myParams if l[0] == 'F'] if fb.fil[0]['ft'] == "FIR": A_PB_max = self.A_PB # 20*log10(1+del_PB) A_PB2_max = self.A_PB2 else: # IIR log A_PB_max = A_PB2_max = 0 if self.unitA == 'V': dBMul = 20. elif self.unitA == 'W': dBMul = 10. if self.unitA == 'dB': A_PB_min = -self.A_PB A_PB2_min = -self.A_PB2 A_PB_minx = min(A_PB_min, A_PB2_min) - 10# 20*log10(1-del_PB) A_SB = -self.A_SB A_SB2 = -self.A_SB2 A_SBx = A_SB - 10 else: A_PB_max = 10**(A_PB_max/dBMul)# 1 + del_PB A_PB2_max = 10**(A_PB2_max/dBMul)# 1 + del_PB A_PB_min = 10**(-self.A_PB/dBMul) #1 - del_PB A_PB2_min = 10**(-self.A_PB2/dBMul) #1 - del_PB A_PB_minx = A_PB_min / 2 A_SB = 10**(-self.A_SB/dBMul) A_SB2 = 10**(-self.A_SB2/dBMul) A_SBx = A_SB / 5 F_max = self.f_S/2 F_PB = self.F_PB F_SB = fb.fil[0]['F_SB'] * self.f_S F_SB2 = fb.fil[0]['F_SB2'] * self.f_S F_PB2 = fb.fil[0]['F_PB2'] * self.f_S y_min = A_PB_minx y_max = ax.get_ylim()[1] F_lim_lor = [] A_lim_lor = [] if fb.fil[0]['rt'] == 'LP': F_lim_up = [0, F_SB, F_SB, F_max] A_lim_up = [A_PB_max, A_PB_max, A_SB, A_SB] F_lim_lo = [0, F_PB, F_PB] A_lim_lo = [A_PB_min, A_PB_min, A_PB_minx] if fb.fil[0]['rt'] == 'HP': F_lim_up = [0, F_SB, F_SB, F_max] A_lim_up = [A_SB, A_SB, A_PB_max, A_PB_max] F_lim_lo = [F_PB, F_PB, F_max] A_lim_lo = [A_PB_minx, A_PB_min, A_PB_min] if fb.fil[0]['rt'] == 'BS': F_lim_up = [0, F_SB, F_SB, F_SB2, F_SB2, F_max] A_lim_up = [A_PB_max, A_PB_max, A_SB, A_SB, A_PB2_max, A_PB2_max] # lower limits left: F_lim_lo = [0, F_PB, F_PB] A_lim_lo = [A_PB_min, A_PB_min, A_PB_minx] # lower limits right: F_lim_lor = [F_PB2, F_PB2, F_max] A_lim_lor = [A_PB_minx, A_PB2_min, A_PB2_min] if fb.fil[0]['rt'] in {"BP", "HIL"}: F_lim_up = [0, F_SB, F_SB, F_SB2, F_SB2, F_max] A_lim_up = [A_SB, A_SB, A_PB_max, A_PB_max, A_SB2, A_SB2] F_lim_lo = [F_PB, F_PB, F_PB2, F_PB2] A_lim_lo = [A_PB_minx, A_PB_min, A_PB_min, A_PB_minx] F_lim_up = np.array(F_lim_up) F_lim_lo = np.array(F_lim_lo) F_lim_lor = np.array(F_lim_lor) # upper limits: ax.plot(F_lim_up, A_lim_up, **line_params) ax.fill_between(F_lim_up, y_max, A_lim_up, **fill_params) # lower limits: ax.plot(F_lim_lo, A_lim_lo, F_lim_lor, A_lim_lor, **line_params) ax.fill_between(F_lim_lo, y_min, A_lim_lo, **fill_params) ax.fill_between(F_lim_lor, y_min, A_lim_lor, **fill_params) if fb.fil[0]['freqSpecsRangeType'] != 'half': # frequency axis +/- f_S/2 # plot limits for other half of the spectrum if fb.fil[0]['freqSpecsRangeType'] == 'sym': # frequency axis +/- f_S/2 F_lim_up = -F_lim_up F_lim_lo = -F_lim_lo F_lim_lor = -F_lim_lor else: # -> 'whole' F_lim_up = self.f_S - F_lim_up F_lim_lo = self.f_S - F_lim_lo F_lim_lor = self.f_S - F_lim_lor # upper limits: ax.plot(F_lim_up, A_lim_up, **line_params) ax.fill_between(F_lim_up, y_max, A_lim_up, **fill_params) # lower limits: ax.plot(F_lim_lo, A_lim_lo, F_lim_lor, A_lim_lor, **line_params) ax.fill_between(F_lim_lo, y_min, A_lim_lo, **fill_params) ax.fill_between(F_lim_lor, y_min, A_lim_lor, **fill_params) def draw(self): if self.mplwidget.mplToolbar.enable_update: self.draw_hf() def draw_hf(self): """ Re-calculate |H(f)| and draw the figure """ self.unitA = self.cmbUnitsA.currentText() # Linphase settings only makes sense for amplitude plot self.chkLinphase.setCheckable(self.unitA == 'V') self.chkLinphase.setEnabled(self.unitA == 'V') self.lblLinphase.setEnabled(self.unitA == 'V') self.specs = self.chkSpecs.isChecked() self.phase = self.chkPhase.isChecked() self.linphase = self.chkLinphase.isChecked() self.bb = fb.fil[0]['ba'][0] self.aa = fb.fil[0]['ba'][1] self.f_S = fb.fil[0]['f_S'] self.F_PB = fb.fil[0]['F_PB'] * self.f_S self.F_SB = fb.fil[0]['F_SB'] * self.f_S self.A_PB = fb.fil[0]['A_PB'] self.A_PB2 = fb.fil[0]['A_PB2'] self.A_SB = fb.fil[0]['A_SB'] self.A_SB2 = fb.fil[0]['A_SB2'] f_lim = fb.fil[0]['freqSpecsRange'] wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' if self.DEBUG: print("--- plotHf.draw() --- ") print("b, a = ", self.bb, self.aa) # calculate H_c(W) (complex) for W = 0 ... pi: [W, self.H_c] = sig.freqz(self.bb, self.aa, worN = rc.params['N_FFT'], whole = wholeF) self.F = W / (2 * np.pi) * self.f_S if fb.fil[0]['freqSpecsRangeType'] == 'sym': self.H_c = np.fft.fftshift(self.H_c) self.F = self.F - self.f_S/2. if self.linphase: # remove the linear phase self.H_c = self.H_c * np.exp(1j * W * fb.fil[0]["N"]/2.) if self.cmbShowH.currentIndex() == 0: # show magnitude of H H = abs(self.H_c) H_str = r'$|H(\mathrm{e}^{\mathrm{j} \Omega})|$' elif self.cmbShowH.currentIndex() == 1: # show real part of H H = self.H_c.real H_str = r'$\Re \{H(\mathrm{e}^{\mathrm{j} \Omega})\}$' else: # show imag. part of H H = self.H_c.imag H_str = r'$\Im \{H(\mathrm{e}^{\mathrm{j} \Omega})\}$' # clear the axes and (re)draw the plot # if self.ax.get_navigate(): self.ax.clear() #================ Main Plotting Routine ========================= if self.unitA == 'dB': A_lim = [-self.A_SB -10, self.A_PB +1] self.H_plt = 20*np.log10(abs(H)) H_str += ' in dB ' + r'$\rightarrow$' elif self.unitA == 'V': # 'lin' A_lim = [10**((-self.A_SB-10)/20), 10**((self.A_PB+1)/20)] self.H_plt = H H_str +=' in V ' + r'$\rightarrow $' self.ax.axhline(linewidth=1, color='k') # horizontal line at 0 else: # unit is W A_lim = [10**((-self.A_SB-10)/10), 10**((self.A_PB+0.5)/10)] self.H_plt = H * H.conj() H_str += ' in W ' + r'$\rightarrow $' plt_lim = f_lim + A_lim #----------------------------------------------------------- self.ax.plot(self.F, self.H_plt, label = 'H(f)') #----------------------------------------------------------- self.ax_bounds = [self.ax.get_ybound()[0], self.ax.get_ybound()[1]]#, self.ax.get] self.ax.axis(plt_lim) if self.specs: self.plotSpecLimits(specAxes = self.ax) self.ax.set_title(r'Magnitude Frequency Response') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(H_str) self.mplwidget.redraw() def draw_phase(self): self.phase = self.chkPhase.isChecked() if self.phase: self.ax_p = self.ax.twinx() # second axes system with same x-axis for phase phi_str = r'$\angle H(\mathrm{e}^{\mathrm{j} \Omega})$' if fb.fil[0]['plt_phiUnit'] == 'rad': phi_str += ' in rad ' + r'$\rightarrow $' scale = 1. elif fb.fil[0]['plt_phiUnit'] == 'rad/pi': phi_str += ' in rad' + r'$ / \pi \;\rightarrow $' scale = 1./ np.pi else: phi_str += ' in deg ' + r'$\rightarrow $' scale = 180./np.pi #----------------------------------------------------------- self.ax_p.plot(self.F,np.unwrap(np.angle(self.H_c))*scale, 'b--', label = "Phase") #----------------------------------------------------------- self.ax_p.set_ylabel(phi_str, color='blue') nbins = len(self.ax.get_yticks()) # number of ticks on main y-axis # http://stackoverflow.com/questions/28692608/align-grid-lines-on-two-plots # http://stackoverflow.com/questions/3654619/matplotlib-multiple-y-axes-grid-lines-applied-to-both # http://stackoverflow.com/questions/20243683/matplotlib-align-twinx-tick-marks # manual setting: #self.ax_p.set_yticks( np.linspace(self.ax_p.get_ylim()[0],self.ax_p.get_ylim()[1],nbins) ) #ax1.set_yticks(np.linspace(ax1.get_ybound()[0], ax1.get_ybound()[1], 5)) #ax2.set_yticks(np.linspace(ax2.get_ybound()[0], ax2.get_ybound()[1], 5)) #http://stackoverflow.com/questions/3654619/matplotlib-multiple-y-axes-grid-lines-applied-to-both # use helper functions from matplotlib.ticker: # MaxNLocator: set no more than nbins + 1 ticks #self.ax_p.yaxis.set_major_locator( matplotlib.ticker.MaxNLocator(nbins = nbins) ) # further options: integer = False, # prune = [‘lower’ | ‘upper’ | ‘both’ | None] Remove edge ticks # AutoLocator: #self.ax_p.yaxis.set_major_locator( matplotlib.ticker.AutoLocator() ) # LinearLocator: #self.ax_p.yaxis.set_major_locator( matplotlib.ticker.LinearLocator(numticks = nbins -1 ) ) # self.ax_p.locator_params(axis = 'y', nbins = nbins) # # self.ax_p.set_yticks(np.linspace(self.ax_p.get_ybound()[0], # self.ax_p.get_ybound()[1], # len(self.ax.get_yticks())-1)) #N = source_ax.xaxis.get_major_ticks() #target_ax.xaxis.set_major_locator(LinearLocator(N)) else: try: self.mplwidget.fig.delaxes(self.ax_p) except (KeyError, AttributeError): pass self.draw() def draw_inset(self): """ Construct / destruct second axes for an inset second plot """ # TODO: try ax1 = zoomed_inset_axes(ax, 6, loc=1) # zoom = 6 # TODO: choose size & position of inset, maybe dependent on filter type # or specs (i.e. where is passband etc.) if self.DEBUG: print(self.cmbInset.currentIndex(), self.mplwidget.fig.axes) # list of axes in Figure for ax in self.mplwidget.fig.axes: print(ax) print("cmbInset, inset_idx:",self.cmbInset.currentIndex(), self.inset_idx) if self.cmbInset.currentIndex() > 0: if self.inset_idx == 0: # Inset was turned off before, create a new one # Add an axes at position rect [left, bottom, width, height]: self.ax_i = self.mplwidget.fig.add_axes([0.65, 0.61, .3, .3]) self.ax_i.clear() # clear old plot and specs # draw an opaque background with the extent of the inset plot: # self.ax_i.patch.set_facecolor('green') # without label area # self.mplwidget.fig.patch.set_facecolor('green') # whole figure extent = self.mplwidget.full_extent(self.ax_i, pad = 0.0) # Transform this back to figure coordinates - otherwise, it # won't behave correctly when the size of the plot is changed: extent = extent.transformed(self.mplwidget.fig.transFigure.inverted()) rect = Rectangle((extent.xmin, extent.ymin), extent.width, extent.height, facecolor=rcParams['figure.facecolor'], edgecolor='none', transform=self.mplwidget.fig.transFigure, zorder=-1) self.ax_i.patches.append(rect) self.ax_i.set_xlim(fb.fil[0]['freqSpecsRange']) self.ax_i.plot(self.F, self.H_plt) if self.cmbInset.currentIndex() == 1: # edit / navigate inset self.ax_i.set_navigate(True) self.ax.set_navigate(False) if self.specs: self.plotSpecLimits(specAxes = self.ax_i) else: # edit / navigate main plot self.ax_i.set_navigate(False) self.ax.set_navigate(True) else: # inset has been turned off, delete it self.ax.set_navigate(True) try: #remove ax_i from the figure self.mplwidget.fig.delaxes(self.ax_i) except AttributeError: pass self.inset_idx = self.cmbInset.currentIndex() # update index self.draw()
class PlotPZ(QWidget): def __init__(self, parent): super(PlotPZ, self).__init__(parent) layHControls = QHBoxLayout() layHControls.addStretch(10) # This widget encompasses all control subwidgets: # self.frmControls = QFrame(self) # self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(layHControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self._init_axes() self.draw() # calculate and draw poles and zeros #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- # self.btnWhatever.clicked.connect(self.draw) #------------------------------------------------------------------------------ def _init_axes(self): """Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() #------------------------------------------------------------------------------ def update_specs(self): """ Draw the figure with new limits, scale etcs without recalculating H(f) -- not yet implemented, just use draw() for the moment """ self.draw() #------------------------------------------------------------------------------ def draw(self): if self.mplwidget.mplToolbar.enabled: self.draw_pz() #------------------------------------------------------------------------------ def draw_pz(self): """ (re)draw P/Z plot """ p_marker = params['P_Marker'] z_marker = params['Z_Marker'] zpk = fb.fil[0]['zpk'] self.ax.clear() [z, p, k] = self.zplane(z = zpk[0], p = zpk[1], k = zpk[2], plt_ax = self.ax, verbose = False, mps = p_marker[0], mpc = p_marker[1], mzs = z_marker[0], mzc = z_marker[1]) self.ax.set_title(r'Pole / Zero Plot') self.ax.set_xlabel('Real axis') self.ax.set_ylabel('Imaginary axis') self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw() #------------------------------------------------------------------------------ def zplane(self, b=None, a=1, z=None, p=None, k =1, pn_eps=1e-3, analog=False, plt_ax = None, verbose=False, style='square', anaCircleRad=0, lw=2, mps = 10, mzs = 10, mpc = 'r', mzc = 'b', plabel = '', zlabel = ''): """ Plot the poles and zeros in the complex z-plane either from the coefficients (`b,`a) of a discrete transfer function `H`(`z`) (zpk = False) or directly from the zeros and poles (z,p) (zpk = True). When only b is given, an FIR filter with all poles at the origin is assumed. Parameters ---------- b : array_like Numerator coefficients (transversal part of filter) When b is not None, poles and zeros are determined from the coefficients b and a a : array_like (optional, default = 1 for FIR-filter) Denominator coefficients (recursive part of filter) z : array_like, default = None Zeros When b is None, poles and zeros are taken directly from z and p p : array_like, default = None Poles analog : boolean (default: False) When True, create a P/Z plot suitable for the s-plane, i.e. suppress the unit circle (unless anaCircleRad > 0) and scale the plot for a good display of all poles and zeros. pn_eps : float (default : 1e-2) Tolerance for separating close poles or zeros plt_ax : handle to axes for plotting (default: None) When no axes is specified, the current axes is determined via plt.gca() pltLib : string (default: 'matplotlib') Library for plotting the P/Z plane. Currently, only matplotlib is implemented. When pltLib = 'none' or when matplotlib is not available, only pass the poles / zeros and their multiplicity verbose : boolean (default: False) When verbose == True, print poles / zeros and their multiplicity. style : string (default: 'square') Style of the plot, for style == 'square' make scale of x- and y- axis equal. mps : integer (default: 10) Size for pole marker mzs : integer (default: 10) Size for zero marker mpc : char (default: 'r') Pole marker colour mzc : char (default: 'b') Zero marker colour lw : integer (default: 2) Linewidth for unit circle plabel, zlabel : string (default: '') This string is passed to the plot command for poles and zeros and can be displayed by legend() Returns ------- z, p, k : ndarray Notes ----- """ # TODO: # - polar option # - add keywords for size, color etc. of markers and circle -> **kwargs # - add option for multi-dimensional arrays and zpk data # make sure that all inputs are arrays b = np.atleast_1d(b) a = np.atleast_1d(a) z = np.atleast_1d(z) # make sure that p, z are arrays p = np.atleast_1d(p) if b.any(): # coefficients were specified if len(b) < 2 and len(a) < 2: logger.error('No proper filter coefficients: both b and a are scalars!') return z, p, k # The coefficients are less than 1, normalize the coefficients if np.max(b) > 1: kn = np.max(b) b = b / float(kn) else: kn = 1. if np.max(a) > 1: kd = np.max(a) a = a / abs(kd) else: kd = 1. # Calculate the poles, zeros and scaling factor p = np.roots(a) z = np.roots(b) k = kn/kd elif not (len(p) or len(z)): # P/Z were specified logger.error('Either b,a or z,p must be specified!') return z, p, k # find multiple poles and zeros and their multiplicities if len(p) < 2: # single pole, [None] or [0] if not p or p == 0: # only zeros, create equal number of poles at origin p = np.array(0,ndmin=1) # num_p = np.atleast_1d(len(z)) else: num_p = [1.] # single pole != 0 else: #p, num_p = sig.signaltools.unique_roots(p, tol = pn_eps, rtype='avg') p, num_p = unique_roots(p, tol = pn_eps, rtype='avg') # p = np.array(p); num_p = np.ones(len(p)) if len(z) > 0: z, num_z = unique_roots(z, tol = pn_eps, rtype='avg') # z = np.array(z); num_z = np.ones(len(z)) #z, num_z = sig.signaltools.unique_roots(z, tol = pn_eps, rtype='avg') else: num_z = [] ax = plt_ax#.subplot(111) if analog == False: # create the unit circle for the z-plane uc = patches.Circle((0,0), radius=1, fill=False, color='grey', ls='solid', zorder=1) ax.add_patch(uc) if style == 'square': r = 1.1 ax.axis([-r, r, -r, r], 'equal') ax.axis('equal') # ax.spines['left'].set_position('center') # ax.spines['bottom'].set_position('center') # ax.spines['right'].set_visible(True) # ax.spines['top'].set_visible(True) else: # s-plane if anaCircleRad > 0: # plot a circle with radius = anaCircleRad uc = patches.Circle((0,0), radius=anaCircleRad, fill=False, color='grey', ls='solid', zorder=1) ax.add_patch(uc) # plot real and imaginary axis ax.axhline(lw=2, color = 'k', zorder=1) ax.axvline(lw=2, color = 'k', zorder=1) # Plot the zeros ax.scatter(z.real, z.imag, s=mzs*mzs, zorder=2, marker = 'o', facecolor = 'none', edgecolor = mzc, lw = lw, label=zlabel) # t1 = plt.plot(z.real, z.imag, 'go', ms=10, label=label) # plt.setp( t1, markersize=mzs, markeredgewidth=2.0, # markeredgecolor=mzc, markerfacecolor='none') # Plot the poles ax.scatter(p.real, p.imag, s=mps*mps, zorder=2, marker='x', color=mpc, lw=lw, label=plabel) # Print multiplicity of poles / zeros for i in range(len(z)): if verbose == True: print('z', i, z[i], num_z[i]) if num_z[i] > 1: ax.text(np.real(z[i]), np.imag(z[i]),' (' + str(num_z[i]) +')',va = 'bottom') for i in range(len(p)): if verbose == True: print('p', i, p[i], num_p[i]) if num_p[i] > 1: ax.text(np.real(p[i]), np.imag(p[i]), ' (' + str(num_p[i]) +')',va = 'bottom') # increase distance between ticks and labels # to give some room for poles and zeros for tick in ax.get_xaxis().get_major_ticks(): tick.set_pad(12.) tick.label1 = tick._get_text1() for tick in ax.get_yaxis().get_major_ticks(): tick.set_pad(12.) tick.label1 = tick._get_text1() xl = ax.get_xlim(); Dx = max(abs(xl[1]-xl[0]), 0.05) yl = ax.get_ylim(); Dy = max(abs(yl[1]-yl[0]), 0.05) ax.set_xlim((xl[0]-Dx*0.05, max(xl[1]+Dx*0.05,0))) ax.set_ylim((yl[0]-Dy*0.05, yl[1] + Dy*0.05)) # print(ax.get_xlim(),ax.get_ylim()) return z, p, k
class PlotImpz(QtGui.QMainWindow): def __init__(self, parent=None, DEBUG=False): # default parent = None -> top Window super(PlotImpz, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG self.ACTIVE_3D = False self.lblLog = QtGui.QLabel(self) self.lblLog.setText("Log.") self.chkLog = QtGui.QCheckBox(self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Show logarithmic impulse / step response.") self.chkLog.setChecked(False) self.lblLogBottom = QtGui.QLabel("Log. bottom:") self.ledLogBottom = QtGui.QLineEdit(self) self.ledLogBottom.setText("-80") self.ledLogBottom.setToolTip("Minimum display value for log. scale.") self.lblNPoints = QtGui.QLabel("<i>N</i> =") self.ledNPoints = QtGui.QLineEdit(self) self.ledNPoints.setText("0") self.ledNPoints.setToolTip("Number of points to calculate and display.\n" "N = 0 chooses automatically.") self.lblStep = QtGui.QLabel("Step Response") self.chkStep = QtGui.QCheckBox() self.chkStep.setChecked(False) self.chkStep.setToolTip("Show step response instead of impulse response.") # self.lblLockZoom = QtGui.QLabel("Lock Zoom") # self.chkLockZoom = QtGui.QCheckBox() # self.chkLockZoom.setChecked(False) # self.chkLockZoom.setToolTip("Lock zoom to current setting.") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.lblLog) self.layHChkBoxes.addWidget(self.chkLog) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblLogBottom) self.layHChkBoxes.addWidget(self.ledLogBottom) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblStep) self.layHChkBoxes.addWidget(self.chkStep) # self.layHChkBoxes.addStretch(1) # self.layHChkBoxes.addWidget(self.lblLockZoom) # self.layHChkBoxes.addWidget(self.chkLockZoom) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblNPoints) self.layHChkBoxes.addWidget(self.ledNPoints) self.layHChkBoxes.addStretch(10) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.layVMainMpl1.addWidget(self.mplwidget) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) # self.setLayout(self.layHChkBoxes) self.draw() # calculate and draw |H(f)| # #============================================= # # Signals & Slots # #============================================= self.chkLog.clicked.connect(self.draw) self.chkStep.clicked.connect(self.draw) self.ledNPoints.editingFinished.connect(self.draw) self.ledLogBottom.editingFinished.connect(self.draw) def initAxes(self): # clear the axes and (re)draw the plot # try: self.mplwidget.fig.delaxes(self.ax_r) self.mplwidget.fig.delaxes(self.ax_i) except (KeyError, AttributeError, UnboundLocalError): pass if self.cmplx: self.ax_r = self.mplwidget.fig.add_subplot(211) self.ax_r.clear() self.ax_i = self.mplwidget.fig.add_subplot(212) self.ax_i.clear() else: self.ax_r = self.mplwidget.fig.add_subplot(111) self.ax_r.clear() if self.ACTIVE_3D: self.ax3d = Axes3D(fig) def draw(self): if self.mplwidget.mplToolbar.enable_update: self.draw_impz() def draw_impz(self): """ (Re-)calculate h[n] and draw the figure """ log = self.chkLog.isChecked() step = self.chkStep.isChecked() self.lblLogBottom.setEnabled(log) self.ledLogBottom.setEnabled(log) # if np.ndim(fb.fil[0]['coeffs']) == 1: # FIR self.bb = fb.fil[0]["ba"][0] self.aa = fb.fil[0]["ba"][1] self.f_S = fb.fil[0]["f_S"] self.F_PB = fb.fil[0]["F_PB"] * self.f_S self.F_SB = fb.fil[0]["F_SB"] * self.f_S self.A_PB = fb.fil[0]["A_PB"] self.A_PB2 = fb.fil[0]["A_PB2"] self.A_SB = fb.fil[0]["A_SB"] self.A_SB2 = fb.fil[0]["A_SB2"] if self.DEBUG: print("--- plotHf.draw() --- ") print("b, a = ", self.bb, self.aa) # calculate h[n] [h, t] = impz(self.bb, self.aa, self.f_S, step=step, N=int(self.ledNPoints.text())) if step: title_str = r"Step Response" H_str = r"$h_{\epsilon}[n]$" else: title_str = r"Impulse Response" H_str = r"$h[n]$" self.cmplx = np.any(np.iscomplex(h)) if self.cmplx: h_i = h.imag h = h.real H_i_str = r"$\Im\{$" + H_str + "$\}$" H_str = r"$\Re\{$" + H_str + "$\}$" if log: bottom = float(self.ledLogBottom.text()) H_str = r"$\log$ " + H_str + " in dB" h = np.maximum(20 * np.log10(abs(h)), bottom) if self.cmplx: h_i = np.maximum(20 * np.log10(abs(h_i)), bottom) H_i_str = r"$\log$ " + H_i_str + " in dB" else: bottom = 0 self.initAxes() # ================ Main Plotting Routine ========================= [ml, sl, bl] = self.ax_r.stem(t, h, bottom=bottom, markerfmt="bo", linefmt="r") self.ax_r.set_xlim([min(t), max(t)]) self.ax_r.set_title(title_str) if self.cmplx: [ml_i, sl_i, bl_i] = self.ax_i.stem(t, h_i, bottom=bottom, markerfmt="rd", linefmt="b") self.ax_i.set_xlabel(fb.fil[0]["plt_tLabel"]) self.ax_r.set_ylabel(H_str + r"$\rightarrow $") self.ax_i.set_ylabel(H_i_str + r"$\rightarrow $") else: self.ax_r.set_xlabel(fb.fil[0]["plt_tLabel"]) self.ax_r.set_ylabel(H_str + r"$\rightarrow $") if self.ACTIVE_3D: # not implemented yet # plotting the stems for i in range(len(t)): self.ax3d.plot([t[i], t[i]], [h[i], h[i]], [0, h_i[i]], "-", linewidth=2, color="b", alpha=0.5) # plotting a circle on the top of each stem self.ax3d.plot(t, h, h_i, "o", markersize=8, markerfacecolor="none", color="b", label="ib") self.ax3d.set_xlabel("x") self.ax3d.set_ylabel("y") self.ax3d.set_zlabel("z") # fig.setp(ml, 'markerfacecolor', 'r', 'markersize', 8) # ax.setp(sl, ...) # print(self.mplwidget.plt_lim) # ax.axis(self.mplwidget.plt_lim) # if self.mplwidget.plt_lim == [] or not self.chkLockZoom.isChecked(): # # self.mplwidget.plt_lim = t_lim + y_lim # self.mplwidget.x_lim = t_lim self.mplwidget.redraw()
class PlotHf(QWidget): # TODO: inset plot should have useful preset range, depending on filter type, # stop band or pass band should be selectable as well as lin / log scale # TODO: position and size of inset plot should be selectable # TODO: ax.clear() should not be neccessary for each replot? # TODO: Canvas should be grey when disabled def __init__(self, parent): super(PlotHf, self).__init__(parent) modes = ['| H |', 're{H}', 'im{H}'] self.cmbShowH = QComboBox(self) self.cmbShowH.addItems(modes) self.cmbShowH.setObjectName("cmbUnitsH") self.cmbShowH.setToolTip("Show magnitude, real / imag. part of H or H \n" "without linear phase (acausal system).") self.cmbShowH.setCurrentIndex(0) self.lblIn = QLabel("in", self) units = ['dB', 'V', 'W', 'Auto'] self.cmbUnitsA = QComboBox(self) self.cmbUnitsA.addItems(units) self.cmbUnitsA.setObjectName("cmbUnitsA") self.cmbUnitsA.setToolTip("<span>Set unit for y-axis:\n" "dB is attenuation (positive values), V and W are gain (less than 1).</span>") self.cmbUnitsA.setCurrentIndex(0) self.cmbShowH.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbUnitsA.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.chkLinphase = QCheckBox("Zero phase", self) self.chkLinphase.setToolTip("<span>Subtract linear phase according to filter order.\n" "Attention: this makes no sense for a non-linear phase system!</span>") self.lblInset = QLabel("Inset", self) self.cmbInset = QComboBox(self) self.cmbInset.addItems(['off', 'edit', 'fixed']) self.cmbInset.setObjectName("cmbInset") self.cmbInset.setToolTip("Display/edit second inset plot") self.cmbInset.setCurrentIndex(0) self.inset_idx = 0 # store previous index for comparison self.chkSpecs = QCheckBox("Show Specs", self) self.chkSpecs.setChecked(False) self.chkSpecs.setToolTip("Display filter specs as hatched regions") self.chkPhase = QCheckBox("Phase", self) self.chkPhase.setToolTip("Overlay phase") layHControls = QHBoxLayout() layHControls.addStretch(10) layHControls.addWidget(self.cmbShowH) layHControls.addWidget(self.lblIn) layHControls.addWidget(self.cmbUnitsA) layHControls.addStretch(1) layHControls.addWidget(self.chkLinphase) layHControls.addStretch(1) layHControls.addWidget(self.lblInset) layHControls.addWidget(self.cmbInset) layHControls.addStretch(1) layHControls.addWidget(self.chkSpecs) layHControls.addStretch(1) layHControls.addWidget(self.chkPhase) layHControls.addStretch(10) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- # This is the plot pane widget, encompassing the other widgets self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # calculate and draw |H(f)| #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.cmbUnitsA.currentIndexChanged.connect(self.draw) self.cmbShowH.currentIndexChanged.connect(self.draw) self.chkLinphase.clicked.connect(self.draw) self.cmbInset.currentIndexChanged.connect(self.draw_inset) self.chkSpecs.clicked.connect(self.draw) self.chkPhase.clicked.connect(self.draw) self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui) #------------------------------------------------------------------------------ def init_axes(self): """ Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() self.ax.get_xaxis().tick_bottom() # remove axis ticks on top self.ax.get_yaxis().tick_left() # remove axis ticks right #------------------------------------------------------------------------------ def plot_spec_limits(self, ax): """ Plot the specifications limits (F_SB, A_SB, ...) as hatched areas with borders. """ hatch = params['mpl_hatch'] hatch_borders = params['mpl_hatch_border'] def dB(lin): return 20 * np.log10(lin) def _plot_specs(): # upper limits: ax.plot(F_lim_upl, A_lim_upl, F_lim_upc, A_lim_upc, F_lim_upr, A_lim_upr, **hatch_borders) if A_lim_upl: ax.fill_between(F_lim_upl, max(A_lim_upl), A_lim_upl, **hatch) if A_lim_upc: ax.fill_between(F_lim_upc, max(A_lim_upc), A_lim_upc, **hatch) if A_lim_upr: ax.fill_between(F_lim_upr, max(A_lim_upr), A_lim_upr, **hatch) # lower limits: ax.plot(F_lim_lol, A_lim_lol, F_lim_loc, A_lim_loc, F_lim_lor, A_lim_lor, **hatch_borders) if A_lim_lol: ax.fill_between(F_lim_lol, min(A_lim_lol), A_lim_lol, **hatch) if A_lim_loc: ax.fill_between(F_lim_loc, min(A_lim_loc), A_lim_loc, **hatch) if A_lim_lor: ax.fill_between(F_lim_lor, min(A_lim_lor), A_lim_lor, **hatch) if self.unitA == 'V': exp = 1. elif self.unitA == 'W': exp = 2. if self.unitA == 'dB': if fb.fil[0]['ft'] == "FIR": A_PB_max = dB(1 + self.A_PB) A_PB2_max = dB(1 + self.A_PB2) else: # IIR dB A_PB_max = A_PB2_max = 0 A_PB_min = dB(1 - self.A_PB) A_PB2_min = dB(1 - self.A_PB2) A_PB_minx = min(A_PB_min, A_PB2_min) - 5 A_PB_maxx = max(A_PB_max, A_PB2_max) + 5 A_SB = dB(self.A_SB) A_SB2 = dB(self.A_SB2) A_SB_maxx = max(A_SB, A_SB2) + 10 else: # 'V' or 'W' if fb.fil[0]['ft'] == "FIR": A_PB_max = (1 + self.A_PB)**exp A_PB2_max = (1 + self.A_PB2)**exp else: # IIR lin A_PB_max = A_PB2_max = 1 A_PB_min = (1 - self.A_PB)**exp A_PB2_min = (1 - self.A_PB2)**exp A_PB_minx = min(A_PB_min, A_PB2_min) / 1.05 A_PB_maxx = max(A_PB_max, A_PB2_max) * 1.05 A_SB = self.A_SB ** exp A_SB2 = self.A_SB2 ** exp A_SB_maxx = A_PB_min / 10. F_max = self.f_S/2 F_PB = self.F_PB F_SB = fb.fil[0]['F_SB'] * self.f_S F_SB2 = fb.fil[0]['F_SB2'] * self.f_S F_PB2 = fb.fil[0]['F_PB2'] * self.f_S F_lim_upl = F_lim_lol = [] # left side limits, lower and upper A_lim_upl = A_lim_lol = [] F_lim_upc = F_lim_loc = [] # center limits, lower and upper A_lim_upc = A_lim_loc = [] F_lim_upr = F_lim_lor = [] # right side limits, lower and upper A_lim_upr = A_lim_lor = [] if fb.fil[0]['rt'] == 'LP': F_lim_upl = [0, F_PB, F_PB] A_lim_upl = [A_PB_max, A_PB_max, A_PB_maxx] F_lim_lol = F_lim_upl A_lim_lol = [A_PB_min, A_PB_min, A_PB_minx] F_lim_upr = [F_SB, F_SB, F_max] A_lim_upr = [A_SB_maxx, A_SB, A_SB] if fb.fil[0]['rt'] == 'HP': F_lim_upl = [0, F_SB, F_SB] A_lim_upl = [A_SB, A_SB, A_SB_maxx] F_lim_upr = [F_PB, F_PB, F_max] A_lim_upr = [A_PB_maxx, A_PB_max, A_PB_max] F_lim_lor = F_lim_upr A_lim_lor = [A_PB_minx, A_PB_min, A_PB_min] if fb.fil[0]['rt'] == 'BS': F_lim_upl = [0, F_PB, F_PB] A_lim_upl = [A_PB_max, A_PB_max, A_PB_maxx] F_lim_lol = F_lim_upl A_lim_lol = [A_PB_min, A_PB_min, A_PB_minx] F_lim_upc = [F_SB, F_SB, F_SB2, F_SB2] A_lim_upc = [A_SB_maxx, A_SB, A_SB, A_SB_maxx] F_lim_upr = [F_PB2, F_PB2, F_max] A_lim_upr = [A_PB_maxx, A_PB2_max, A_PB2_max] F_lim_lor = F_lim_upr A_lim_lor = [A_PB_minx, A_PB2_min, A_PB2_min] if fb.fil[0]['rt'] == 'BP': F_lim_upl = [0, F_SB, F_SB] A_lim_upl = [A_SB, A_SB, A_SB_maxx] F_lim_upc = [F_PB, F_PB, F_PB2, F_PB2] A_lim_upc = [A_PB_maxx, A_PB_max, A_PB_max, A_PB_maxx] F_lim_loc = F_lim_upc A_lim_loc = [A_PB_minx, A_PB_min, A_PB_min, A_PB_minx] F_lim_upr = [F_SB2, F_SB2, F_max] A_lim_upr = [A_SB_maxx, A_SB2, A_SB2] if fb.fil[0]['rt'] == 'HIL': F_lim_upc = [F_PB, F_PB, F_PB2, F_PB2] A_lim_upc = [A_PB_maxx, A_PB_max, A_PB_max, A_PB_maxx] F_lim_loc = F_lim_upc A_lim_loc = [A_PB_minx, A_PB_min, A_PB_min, A_PB_minx] F_lim_upr = np.array(F_lim_upr) F_lim_lor = np.array(F_lim_lor) F_lim_upl = np.array(F_lim_upl) F_lim_lol = np.array(F_lim_lol) F_lim_upc = np.array(F_lim_upc) F_lim_loc = np.array(F_lim_loc) _plot_specs() # plot specs in the range 0 ... f_S/2 if fb.fil[0]['freqSpecsRangeType'] != 'half': # add plot limits for other half of the spectrum if fb.fil[0]['freqSpecsRangeType'] == 'sym': # frequency axis +/- f_S/2 F_lim_upl = -F_lim_upl F_lim_lol = -F_lim_lol F_lim_upc = -F_lim_upc F_lim_loc = -F_lim_loc F_lim_upr = -F_lim_upr F_lim_lor = -F_lim_lor else: # -> 'whole' F_lim_upl = self.f_S - F_lim_upl F_lim_lol = self.f_S - F_lim_lol F_lim_upc = self.f_S - F_lim_upc F_lim_loc = self.f_S - F_lim_loc F_lim_upr = self.f_S - F_lim_upr F_lim_lor = self.f_S - F_lim_lor _plot_specs() #------------------------------------------------------------------------------ def draw_inset(self): """ Construct / destruct second axes for an inset second plot """ # TODO: try ax1 = zoomed_inset_axes(ax, 6, loc=1) # zoom = 6 # TODO: choose size & position of inset, maybe dependent on filter type # or specs (i.e. where is passband etc.) # DEBUG # print(self.cmbInset.currentIndex(), self.mplwidget.fig.axes) # list of axes in Figure # for ax in self.mplwidget.fig.axes: # print(ax) # print("cmbInset, inset_idx:",self.cmbInset.currentIndex(), self.inset_idx) if self.cmbInset.currentIndex() > 0: if self.inset_idx == 0: # Inset was turned off before, create a new one # Add an axes at position rect [left, bottom, width, height]: self.ax_i = self.mplwidget.fig.add_axes([0.65, 0.61, .3, .3]) self.ax_i.clear() # clear old plot and specs # draw an opaque background with the extent of the inset plot: # self.ax_i.patch.set_facecolor('green') # without label area # self.mplwidget.fig.patch.set_facecolor('green') # whole figure extent = self.mplwidget.get_full_extent(self.ax_i, pad = 0.0) # Transform this back to figure coordinates - otherwise, it # won't behave correctly when the size of the plot is changed: extent = extent.transformed(self.mplwidget.fig.transFigure.inverted()) rect = Rectangle((extent.xmin, extent.ymin), extent.width, extent.height, facecolor=rcParams['figure.facecolor'], edgecolor='none', transform=self.mplwidget.fig.transFigure, zorder=-1) self.ax_i.patches.append(rect) self.ax_i.set_xlim(fb.fil[0]['freqSpecsRange']) self.ax_i.plot(self.F, self.H_plt) if self.cmbInset.currentIndex() == 1: # edit / navigate inset self.ax_i.set_navigate(True) self.ax.set_navigate(False) if self.specs: self.plot_spec_limits(self.ax_i) else: # edit / navigate main plot self.ax_i.set_navigate(False) self.ax.set_navigate(True) else: # inset has been turned off, delete it self.ax.set_navigate(True) try: #remove ax_i from the figure self.mplwidget.fig.delaxes(self.ax_i) except AttributeError: pass self.inset_idx = self.cmbInset.currentIndex() # update index self.draw() #------------------------------------------------------------------------------ def draw_phase(self, ax): """ Draw phase on second y-axis in the axes system passed as the argument """ try: self.mplwidget.fig.delaxes(self.ax_p) except (KeyError, AttributeError): pass if self.chkPhase.isChecked(): self.ax_p = ax.twinx() # second axes system with same x-axis for phase # phi_str = r'$\angle H(\mathrm{e}^{\mathrm{j} \Omega})$' if fb.fil[0]['plt_phiUnit'] == 'rad': phi_str += ' in rad ' + r'$\rightarrow $' scale = 1. elif fb.fil[0]['plt_phiUnit'] == 'rad/pi': phi_str += ' in rad' + r'$ / \pi \;\rightarrow $' scale = 1./ np.pi else: phi_str += ' in deg ' + r'$\rightarrow $' scale = 180./np.pi # replace nan and inf by finite values, otherwise np.unwrap yields # an array full of nans phi = np.angle(np.nan_to_num(self.H_c)) #----------------------------------------------------------- self.ax_p.plot(self.F,np.unwrap(phi)*scale, 'g-.', label = "Phase") #----------------------------------------------------------- self.ax_p.set_ylabel(phi_str) nbins = len(self.ax.get_yticks()) # number of ticks on main y-axis # http://stackoverflow.com/questions/28692608/align-grid-lines-on-two-plots # http://stackoverflow.com/questions/3654619/matplotlib-multiple-y-axes-grid-lines-applied-to-both # http://stackoverflow.com/questions/20243683/matplotlib-align-twinx-tick-marks # manual setting: #self.ax_p.set_yticks( np.linspace(self.ax_p.get_ylim()[0],self.ax_p.get_ylim()[1],nbins) ) #ax1.set_yticks(np.linspace(ax1.get_ybound()[0], ax1.get_ybound()[1], 5)) #ax2.set_yticks(np.linspace(ax2.get_ybound()[0], ax2.get_ybound()[1], 5)) #http://stackoverflow.com/questions/3654619/matplotlib-multiple-y-axes-grid-lines-applied-to-both # use helper functions from matplotlib.ticker: # MaxNLocator: set no more than nbins + 1 ticks #self.ax_p.yaxis.set_major_locator( matplotlib.ticker.MaxNLocator(nbins = nbins) ) # further options: integer = False, # prune = [‘lower’ | ‘upper’ | ‘both’ | None] Remove edge ticks # AutoLocator: #self.ax_p.yaxis.set_major_locator( matplotlib.ticker.AutoLocator() ) # LinearLocator: #self.ax_p.yaxis.set_major_locator( matplotlib.ticker.LinearLocator(numticks = nbins -1 ) ) # self.ax_p.locator_params(axis = 'y', nbins = nbins) # # self.ax_p.set_yticks(np.linspace(self.ax_p.get_ybound()[0], # self.ax_p.get_ybound()[1], # len(self.ax.get_yticks())-1)) #N = source_ax.xaxis.get_major_ticks() #target_ax.xaxis.set_major_locator(LinearLocator(N)) # else: # try: # self.mplwidget.fig.delaxes(self.ax_p) # except (KeyError, AttributeError): # pass # self.draw() #------------------------------------------------------------------------------ def calc_hf(self): """ (Re-)Calculate the complex frequency response H(f) """ # calculate H_cmplx(W) (complex) for W = 0 ... 2 pi: self.W, self.H_cmplx = calc_Hcomplex(fb.fil[0], params['N_FFT'], True) #------------------------------------------------------------------------------ def enable_ui(self): """ Triggered when the toolbar is enabled or disabled """ self.frmControls.setEnabled(self.mplwidget.mplToolbar.enabled) if self.mplwidget.mplToolbar.enabled: self.init_axes() self.draw() #------------------------------------------------------------------------------ def draw(self): """ Re-calculate |H(f)| and draw the figure if enabled """ if self.mplwidget.mplToolbar.enabled: self.calc_hf() self.update_view() #------------------------------------------------------------------------------ def update_view(self): """ Draw the figure with new limits, scale etc without recalculating H(f) """ # Get corners for spec display from the parameters of the target specs subwidget try: param_list = fb.fil_tree[fb.fil[0]['rt']][fb.fil[0]['ft']]\ [fb.fil[0]['fc']][fb.fil[0]['fo']]['tspecs'][1]['amp'] except KeyError: param_list = [] SB = [l for l in param_list if 'A_SB' in l] PB = [l for l in param_list if 'A_PB' in l] if SB: A_min = min([fb.fil[0][l] for l in SB]) else: A_min = 5e-4 if PB: A_max = max([fb.fil[0][l] for l in PB]) else: A_max = 1 if np.all(self.W) is None: # H(f) has not been calculated yet self.calc_hf() if self.cmbUnitsA.currentText() == 'Auto': self.unitA = fb.fil[0]['amp_specs_unit'] else: self.unitA = self.cmbUnitsA.currentText() # Linphase settings only makes sense for amplitude plot self.chkLinphase.setCheckable(self.unitA == 'V') self.chkLinphase.setEnabled(self.unitA == 'V') self.specs = self.chkSpecs.isChecked() self.linphase = self.chkLinphase.isChecked() self.f_S = fb.fil[0]['f_S'] self.F_PB = fb.fil[0]['F_PB'] * self.f_S self.F_SB = fb.fil[0]['F_SB'] * self.f_S self.A_PB = fb.fil[0]['A_PB'] self.A_PB2 = fb.fil[0]['A_PB2'] self.A_SB = fb.fil[0]['A_SB'] self.A_SB2 = fb.fil[0]['A_SB2'] f_lim = fb.fil[0]['freqSpecsRange'] #========= select frequency range to be displayed ===================== #=== shift, scale and select: W -> F, H_cplx -> H_c self.F = self.W / (2 * np.pi) * self.f_S if fb.fil[0]['freqSpecsRangeType'] == 'sym': # shift H and F by f_S/2 self.H_c = np.fft.fftshift(self.H_cmplx) self.F -= self.f_S/2. elif fb.fil[0]['freqSpecsRangeType'] == 'half': # only use the first half of H and F self.H_c = self.H_cmplx[0:params['N_FFT']//2] self.F = self.F[0:params['N_FFT']//2] else: # fb.fil[0]['freqSpecsRangeType'] == 'whole' # use H and F as calculated self.H_c = self.H_cmplx # now calculate mag / real / imaginary part of H_c: if self.linphase: # remove the linear phase self.H_c = self.H_c * np.exp(1j * self.W[0:len(self.F)] * fb.fil[0]["N"]/2.) if self.cmbShowH.currentIndex() == 0: # show magnitude of H H = abs(self.H_c) H_str = r'$|H(\mathrm{e}^{\mathrm{j} \Omega})|$' elif self.cmbShowH.currentIndex() == 1: # show real part of H H = self.H_c.real H_str = r'$\Re \{H(\mathrm{e}^{\mathrm{j} \Omega})\}$' else: # show imag. part of H H = self.H_c.imag H_str = r'$\Im \{H(\mathrm{e}^{\mathrm{j} \Omega})\}$' #================ Main Plotting Routine ========================= #=== clear the axes and (re)draw the plot (if selectable) if self.ax.get_navigate(): if self.unitA == 'dB': A_lim = [20*np.log10(A_min) -10, 20*np.log10(1+A_max) +1] self.H_plt = 20*np.log10(abs(H)) H_str += ' in dB ' + r'$\rightarrow$' elif self.unitA == 'V': # 'lin' A_lim = [0, (1.05 + A_max)] self.H_plt = H H_str +=' in V ' + r'$\rightarrow $' self.ax.axhline(linewidth=1, color='k') # horizontal line at 0 else: # unit is W A_lim = [0, (1.03 + A_max)**2.] self.H_plt = H * H.conj() H_str += ' in W ' + r'$\rightarrow $' #----------------------------------------------------------- self.ax.clear() self.ax.plot(self.F, self.H_plt, label = 'H(f)') self.draw_phase(self.ax) #----------------------------------------------------------- #============= Set Limits and draw specs ========================= if self.specs: self.plot_spec_limits(self.ax) # self.ax_bounds = [self.ax.get_ybound()[0], self.ax.get_ybound()[1]]#, self.ax.get] self.ax.set_xlim(f_lim) self.ax.set_ylim(A_lim) self.ax.set_title(r'Magnitude Frequency Response') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(H_str) self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw()
class PlotTauG(QWidget): def __init__(self, parent): super(PlotTauG, self).__init__(parent) self.chkWarnings = QCheckBox("Enable Warnings", self) self.chkWarnings.setChecked(False) self.chkWarnings.setToolTip( "Print warnings about singular group delay") layHControls = QHBoxLayout() layHControls.addStretch(10) layHControls.addWidget(self.chkWarnings) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.ax = self.mplwidget.fig.add_subplot(111) self.draw() # initial drawing of tau_g #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkWarnings.clicked.connect(self.draw) #------------------------------------------------------------------------------ def draw(self): self.frmControls.setEnabled(self.mplwidget.mplToolbar.enabled) if self.mplwidget.mplToolbar.enabled: self.draw_taug() #------------------------------------------------------------------------------ def update_view(self): """ Update frequency range etc. without recalculating group delay. """ self.draw() #------------------------------------------------------------------------------ def draw_taug(self): """ Draw group delay """ self.ax.clear() bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' f_S = fb.fil[0]['f_S'] [w, tau_g] = grpdelay(bb, aa, params['N_FFT'], whole=wholeF, verbose=self.chkWarnings.isChecked()) F = w / (2 * np.pi) * fb.fil[0]['f_S'] if fb.fil[0]['freqSpecsRangeType'] == 'sym': tau_g = np.fft.fftshift(tau_g) F = F - f_S / 2. if fb.fil[0]['freq_specs_unit'] in {'f_S', 'f_Ny'}: tau_str = r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega}) / T_S \; \rightarrow $' else: tau_str = r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega})$'\ + ' in ' + fb.fil[0]['plt_tUnit'] + r' $ \rightarrow $' tau_g = tau_g / fb.fil[0]['f_S'] #--------------------------------------------------------- line_tau_g, = self.ax.plot(F, tau_g, label="Group Delay") #--------------------------------------------------------- self.ax.set_title(r'Group Delay $ \tau_g$') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(tau_str) # widen y-limits to suppress numerical inaccuracies when tau_g = constant self.ax.set_ylim([max(min(tau_g) - 0.5, 0), max(tau_g) + 0.5]) self.ax.set_xlim(fb.fil[0]['freqSpecsRange']) self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw()
class Plot3D(QtGui.QMainWindow): """ Class for various 3D-plots: - lin / log line plot of H(f) - lin / log surf plot of H(z) - optional display of poles / zeros """ def __init__(self, parent=None, DEBUG=False): # default parent = None -> top Window super(Plot3D, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG self.zmin = 0 self.zmax = 4 self.zmin_dB = -80 self.lblLog = QtGui.QLabel("Log.") self.chkLog = QtGui.QCheckBox(self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Logarithmic scale") self.chkLog.setChecked(False) self.lblBottom = QtGui.QLabel("Bottom:") self.ledBottom = QtGui.QLineEdit(self) self.ledBottom.setObjectName("ledBottom") self.ledBottom.setText(str(self.zmin)) self.ledBottom.setToolTip("Minimum display value.") self.lblTop = QtGui.QLabel("Top:") self.ledTop = QtGui.QLineEdit(self) self.ledTop.setObjectName("ledTop") self.ledTop.setText(str(self.zmax)) self.ledTop.setToolTip("Maximum display value.") # self.ledTop.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) self.lblUC = QtGui.QLabel(self) self.lblUC.setText("UC") self.chkUC = QtGui.QCheckBox(self) self.chkUC.setObjectName("chkUC") self.chkUC.setToolTip("Plot unit circle") self.chkUC.setChecked(True) self.lblPZ = QtGui.QLabel(self) self.lblPZ.setText("P/Z") self.chkPZ = QtGui.QCheckBox(self) self.chkPZ.setObjectName("chkPZ") self.chkPZ.setToolTip("Plot poles and zeros") self.chkPZ.setChecked(True) self.lblHf = QtGui.QLabel(self) self.lblHf.setText("H(f)") self.chkHf = QtGui.QCheckBox(self) self.chkHf.setObjectName("chkHf") self.chkHf.setToolTip("Plot H(f) along the unit circle") self.chkHf.setChecked(True) modes = ['None', 'Mesh', 'Surf', 'Contour'] self.cmbMode3D = QtGui.QComboBox(self) self.cmbMode3D.addItems(modes) self.cmbMode3D.setObjectName("cmbShow3D") self.cmbMode3D.setToolTip("Select 3D-plot mode.") self.cmbMode3D.setCurrentIndex(0) self.cmbMode3D.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.lblColBar = QtGui.QLabel(self) self.lblColBar.setText("Colorbar") self.chkColBar = QtGui.QCheckBox(self) self.chkColBar.setObjectName("chkColBar") self.chkColBar.setToolTip("Show colorbar") self.chkColBar.setChecked(False) self.diaAlpha = QtGui.QDial(self) self.diaAlpha.setRange(0., 10.) self.diaAlpha.setValue(5) self.diaAlpha.setTracking(False) # produce less events when turning self.diaAlpha.setFixedHeight(30) self.diaAlpha.setFixedWidth(30) self.diaAlpha.setWrapping(False) self.diaAlpha.setToolTip("Set transparency for surf and contour plot.") self.diaHatch = QtGui.QDial(self) self.diaHatch.setRange(0., 9.) self.diaHatch.setValue(5) self.diaHatch.setTracking(False) # produce less events when turning self.diaHatch.setFixedHeight(30) self.diaHatch.setFixedWidth(30) self.diaHatch.setWrapping(False) self.diaHatch.setToolTip("Set hatching for H(jw).") self.lblContour2D = QtGui.QLabel(self) self.lblContour2D.setText("Contour2D") self.chkContour2D = QtGui.QCheckBox(self) self.chkContour2D.setObjectName("chkContour2D") self.chkContour2D.setToolTip("Plot 2D-contours for real and imaginary part") self.chkContour2D.setChecked(False) self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.lblLog) self.layHChkBoxes.addWidget(self.chkLog) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblBottom) self.layHChkBoxes.addWidget(self.ledBottom) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblTop) self.layHChkBoxes.addWidget(self.ledTop) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblUC) self.layHChkBoxes.addWidget(self.chkUC) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblPZ) self.layHChkBoxes.addWidget(self.chkPZ) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblHf) self.layHChkBoxes.addWidget(self.chkHf) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.cmbMode3D) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblColBar) self.layHChkBoxes.addWidget(self.chkColBar) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.diaAlpha) self.layHChkBoxes.addWidget(self.diaHatch) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblContour2D) self.layHChkBoxes.addWidget(self.chkContour2D) self.layHChkBoxes.addStretch(1) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.initCoord() self.draw() # calculate and draw phi(f) # #============================================= # # Signals & Slots # #============================================= self.chkLog.clicked.connect(self.logClicked) self.ledBottom.editingFinished.connect(self.logClicked) self.ledTop.editingFinished.connect(self.logClicked) self.chkUC.clicked.connect(self.draw) self.chkHf.clicked.connect(self.draw) self.chkPZ.clicked.connect(self.draw) self.cmbMode3D.currentIndexChanged.connect(self.draw) self.chkColBar.clicked.connect(self.draw) self.diaAlpha.valueChanged.connect(self.draw) self.diaHatch.valueChanged.connect(self.draw) self.chkContour2D.clicked.connect(self.draw) def initCoord(self): """ Initialize coordinates for the unit circle """ # TODO: move creation of x,y-grid here as well self.phi_EK = np.linspace(0, 2*pi, 400, endpoint=True) self.xy_UC = np.exp(1j * self.phi_EK) # x,y coordinates of unity circle def initAxes(self): """Initialize and clear the axes see http://stackoverflow.com/questions/4575588/matplotlib-3d-plot-with-pyqt4-in-qtabwidget-mplwidget """ self.mplwidget.fig.clf() # needed to get rid of colormap self.ax3d = self.mplwidget.fig.add_subplot(111, projection='3d') def logClicked(self): """ Change scale and settings to log / lin """ self.log = self.chkLog.isChecked() if self.sender().objectName() == 'chkLog': # origin of signal that triggered the slot if self.log: self.ledBottom.setText(str(self.zmin_dB)) self.zmax_dB = np.round(20 * log10(self.zmax), 2) self.ledTop.setText(str(self.zmax_dB)) else: self.ledBottom.setText(str(self.zmin)) self.zmax = np.round(10**(self.zmax_dB / 20), 2) self.ledTop.setText(str(self.zmax)) else: if self.log: self.zmin_dB = float(self.ledBottom.text()) self.zmax_dB = float(self.ledTop.text()) else: self.zmin = float(self.ledBottom.text()) self.zmax = float(self.ledTop.text()) self.draw() def draw(self): if self.mplwidget.mplToolbar.enable_update: self.draw_3d() def draw_3d(self): """ Draw various 3D plots """ self.initAxes() # needed to get rid of colormap bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] zz = np.array(fb.fil[0]['zpk'][0]) pp = np.array(fb.fil[0]['zpk'][1]) wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' f_S = fb.fil[0]['f_S'] N_FFT = rc.params['N_FFT'] alpha = self.diaAlpha.value()/10. #----------------------------------------------------------------------------- # Define 3D-Plotting Options #----------------------------------------------------------------------------- OPT_3D_POLAR_SPEC = True # Plot circular range in 3D-Plot OPT_3D_FORCE_ZMAX = True # Enforce absolute limit for 3D-Plot OPT_3D_MSTRIDE = 1 # Schrittweite für MESH und CONT3D OPT_3D_ALPHA = alpha#0.5 # Transparency for surface plot cmap = cm.jet # Colormaps: 'hsv', 'jet_r', 'bone', 'prism' 'gray', 'prism', 'coolwarm' # *_r colormaps reverse the color order # steps = 100 # number of steps for x, y, r, phi rmin = 0; rmax = 1.2 # polar range definition # xmin = -1.5; xmax = 1.5 # cartesian range definition ymin = -1.5; ymax = 1.5 # zmax_rel = 5 # Max. displayed z - value relative to max|H(f)| # Calculate limits etc. for 3D-Plots dr = rmax / steps * 2 dphi = pi / steps # grid size for polar range dx = (xmax - xmin) / steps dy = (ymax - ymin) / steps # grid size cartesian range if OPT_3D_POLAR_SPEC == True: # polar grid [r, phi] = np.meshgrid(np.arange(rmin, rmax, dr), np.linspace(0, 2 * pi, steps, endpoint=True)) x = r * cos(phi) y = r * sin(phi) else: # cartesian grid [x, y] = np.meshgrid(np.arange(xmin, xmax, dx), np.arange(ymin, ymax, dy)) z = x + 1j*y # create coordinate grid for complex plane [w, H] = sig.freqz(bb, aa, N_FFT, wholeF) # calculate H(w) along the # upper half of unity circle # w runs from 0 ... pi, length = N_FFT f = w / (2 * pi) * f_S # translate w to absolute frequencies H_abs = abs(H) H_max = max(H_abs) H_max_dB = 20*log10(H_max) F_max = f[np.argmax(H_abs)] H_min = min(H_abs) H_min_dB = 20*log10(H_min) F_min = f[np.argmin(H_abs)] plevel_rel = 1.05 # height of plotted pole position relative to zmax zlevel_rel = 0.1 # height of plotted zero position relative to zmax # calculate H(jw)| along the unity circle and |H(z)|, each clipped # between bottom and top if self.chkLog.isChecked(): bottom = np.floor(max(self.zmin_dB, H_min_dB) / 10) * 10 top = self.zmax_dB plevel_top = top + (top - bottom) * (plevel_rel - 1) plevel_btm = top zlevel = bottom - (top - bottom) * (zlevel_rel) H_UC = H_mag(bb, aa, self.xy_UC, top, H_min=bottom, log=True) Hmag = H_mag(bb, aa, z, top, H_min=bottom, log=True) else: bottom = max(self.zmin, H_min) top = self.zmax # top = zmax_rel * H_max # calculate display top from max. of H(f) H_UC = H_mag(bb, aa, self.xy_UC, top, H_min=bottom) Hmag = H_mag(bb, aa, z, top, H_min=bottom) zlevel = zlevel_rel * top # height of displayed zero position if self.cmbMode3D.currentText() == 'None': # "Poleposition" for H(f) plot only plevel_top = H_max * 0.3 # plevel = H_max * 0.1 / zlevel = 0.1 plevel_btm = bottom else: plevel_top = plevel_rel * top # height of displayed pole position plevel_btm = top #=============================================================== ## plot unit circle #=============================================================== if self.chkUC.isChecked(): # Plot unit circle and marker at (1,0): self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, ones(len(self.xy_UC)) * bottom, lw=2, color='k') self.ax3d.plot([0.97, 1.03], [0, 0], [bottom, bottom], lw=2, color='k') #=============================================================== ## plot ||H(f)| along unit circle as 3D-lineplot #=============================================================== if self.chkHf.isChecked(): self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, H_UC) # draw once more as dashed white line to improve visibility self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, H_UC, 'w--') NL = 10 - self.diaHatch.value() # plot line every NL points on the UC if NL < 10: for k in range(len(self.xy_UC[::NL])): self.ax3d.plot([self.xy_UC.real[::NL][k], self.xy_UC.real[::NL][k]], [self.xy_UC.imag[::NL][k], self.xy_UC.imag[::NL][k]], [np.ones(len(self.xy_UC[::NL]))[k]*bottom, H_UC[::NL][k]], linewidth=1, color=(0.5, 0.5, 0.5)) #=============================================================== ## plot Poles and Zeros #=============================================================== if self.chkPZ.isChecked(): PN_SIZE = 8 # size of P/N symbols # Plot zero markers at |H(z_i)| = zlevel with "stems": self.ax3d.plot(zz.real, zz.imag, ones(len(zz)) * zlevel, 'o', markersize=PN_SIZE, markeredgecolor='blue', markeredgewidth=2.0, markerfacecolor='none') for k in range(len(zz)): # plot zero "stems" self.ax3d.plot([zz[k].real, zz[k].real], [zz[k].imag, zz[k].imag], [bottom, zlevel], linewidth=1, color='b') # Plot the poles at |H(z_p)| = plevel with "stems": self.ax3d.plot(np.real(pp), np.imag(pp), plevel_top, 'x', markersize=PN_SIZE, markeredgewidth=2.0, markeredgecolor='red') for k in range(len(pp)): # plot pole "stems" self.ax3d.plot([pp[k].real, pp[k].real], [pp[k].imag, pp[k].imag], [plevel_btm, plevel_top], linewidth=1, color='r') #=============================================================== ## 3D-Plots of |H(z)| clipped between |H(z)| = top #=============================================================== # ## Mesh plot if self.cmbMode3D.currentText() == 'Mesh': # fig_mlab = mlab.figure(fgcolor=(0., 0., 0.), bgcolor=(1, 1, 1)) # self.ax3d.set_zlim(0,2) self.ax3d.plot_wireframe(x, y, Hmag, rstride=5, cstride=OPT_3D_MSTRIDE, linewidth=1, color='gray') #--------------------------------------------------------------- ## 3D-surface plot; elif self.cmbMode3D.currentText() == 'Surf': s = self.ax3d.plot_surface(x, y, Hmag, alpha=OPT_3D_ALPHA, rstride=1, cstride=1, cmap=cmap, linewidth=0, antialiased=False, shade=True) # facecolors= cmap ?? s.set_edgecolor('gray') #--------------------------------------------------------------- ## 3D-Contour plot elif self.cmbMode3D.currentText() == 'Contour': s = self.ax3d.contourf3D(x, y, Hmag, 20, alpha=alpha, rstride=OPT_3D_MSTRIDE, cstride=OPT_3D_MSTRIDE, cmap=cmap) #--------------------------------------------------------------- ## 2D-Contour plot # TODO: 2D contour plots do not plot correctly together with 3D plots in # current matplotlib 1.4.3 -> disable them for now # TODO: zdir = x / y delivers unexpected results -> rather plot max(H) # along the other axis? # TODO: colormap is created depending on the zdir = 'z' contour plot # -> set limits of (all) other plots manually? if self.chkContour2D.isChecked(): # self.ax3d.contourf(x, y, Hmag, 20, zdir='x', offset=xmin, # cmap=cmap, alpha = alpha)#, vmin = bottom)#, vmax = top, vmin = bottom) # self.ax3d.contourf(x, y, Hmag, 20, zdir='y', offset=ymax, # cmap=cmap, alpha = alpha)#, vmin = bottom)#, vmax = top, vmin = bottom) s = self.ax3d.contourf(x, y, Hmag, 20, zdir='z', offset=bottom - (top - bottom) * 0.05, cmap=cmap, alpha=alpha) if self.cmbMode3D.currentText() in {'Contour', 'Surf'}\ or self.chkContour2D.isChecked(): if self.chkColBar.isChecked(): self.colb = self.mplwidget.fig.colorbar(s, ax=self.ax3d, shrink=0.8, aspect=20, pad=0.02, fraction=0.08) self.ax3d.set_xlim3d(xmin, xmax) self.ax3d.set_ylim3d(ymin, ymax) self.ax3d.set_zlim3d(bottom, top) self.ax3d.set_xlabel('Re')#(fb.fil[0]['plt_fLabel']) self.ax3d.set_ylabel('Im') #(r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega}) / T_S \; \rightarrow $') # self.ax3d.set_zlabel(r'$|H(z)|\; \rightarrow $') self.ax3d.set_title(r'3D-Plot of $|H(\mathrm{e}^{\mathrm{j} \Omega})|$ and $|H(z)|$') self.mplwidget.redraw()
class PlotPhi(QtGui.QWidget): def __init__(self, parent): super(PlotPhi, self).__init__(parent) self.cmbUnitsPhi = QtGui.QComboBox(self) units = ["rad", "rad/pi", "deg"] scales = [1., 1. / np.pi, 180. / np.pi] for unit, scale in zip(units, scales): self.cmbUnitsPhi.addItem(unit, scale) self.cmbUnitsPhi.setObjectName("cmbUnitsA") self.cmbUnitsPhi.setToolTip("Set unit for phase.") self.cmbUnitsPhi.setCurrentIndex(0) self.cmbUnitsPhi.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.lblWrap = QtGui.QLabel("Wrapped Phase") self.btnWrap = QtGui.QCheckBox() self.btnWrap.setChecked(False) self.btnWrap.setToolTip("Plot phase wrapped to +/- pi") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.cmbUnitsPhi) self.layHChkBoxes.addWidget(self.lblWrap) self.layHChkBoxes.addWidget(self.btnWrap) self.layHChkBoxes.addStretch(10) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) self.setLayout(self.mplwidget.layVMainMpl) # self.mplwidget.setFocus() # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self._init_axes() self.draw() # initial drawing # #============================================= # # Signals & Slots # #============================================= self.btnWrap.clicked.connect(self.draw) self.cmbUnitsPhi.currentIndexChanged.connect(self.draw) def _init_axes(self): """Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() self.ax.hold(False) def update_view(self): """ place holder; should update only the limits without recalculating the phase """ self.draw() def draw(self): """ main entry point for drawing the phase """ if self.mplwidget.mplToolbar.enable_update: self.draw_phi() def draw_phi(self): """ Re-calculate phi(f) and draw the figure """ self.unitPhi = self.cmbUnitsPhi.currentText() self.bb = fb.fil[0]['ba'][0] self.aa = fb.fil[0]['ba'][1] wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' f_S = fb.fil[0]['f_S'] [W, H] = sig.freqz(self.bb, self.aa, worN=rc.params['N_FFT'], whole=wholeF) F = W / (2 * np.pi) * f_S if fb.fil[0]['freqSpecsRangeType'] == 'sym': H = np.fft.fftshift(H) F = F - f_S / 2. # scale = self.cmbUnitsPhi.itemData(self.cmbUnitsPhi.currentIndex()) y_str = r'$\angle H(\mathrm{e}^{\mathrm{j} \Omega})$ in ' if self.unitPhi == 'rad': y_str += 'rad ' + r'$\rightarrow $' scale = 1. elif self.unitPhi == 'rad/pi': y_str += 'rad' + r'$ / \pi \;\rightarrow $' scale = 1. / np.pi else: y_str += 'deg ' + r'$\rightarrow $' scale = 180. / np.pi fb.fil[0]['plt_phiLabel'] = y_str fb.fil[0]['plt_phiUnit'] = self.unitPhi # replace nan and inf by finite values, otherwise np.unwrap yields # an array full of nans H = np.nan_to_num(H) if self.btnWrap.isChecked(): phi_plt = np.angle(H) * scale else: phi_plt = np.unwrap(np.angle(H)) * scale self.ax.clear() # need to clear, doesn't overwrite #--------------------------------------------------------- line_phi, = self.ax.plot(F, phi_plt) #--------------------------------------------------------- self.ax.set_title(r'Phase Frequency Response') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(y_str) self.ax.set_xlim(fb.fil[0]['freqSpecsRange']) self.mplwidget.redraw()
def _init_UI(self): self.chkLog = QCheckBox(self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("<span>Logarithmic scale for y-axis.</span>") self.chkLog.setChecked(False) self.lblLog = QLabel("Log. y-axis", self) self.lblLogBottom = QLabel("Bottom = ", self) self.ledLogBottom = QLineEdit(self) self.ledLogBottom.setText(str(self.bottom)) self.ledLogBottom.setToolTip("<span>Minimum display value for log. scale.</span>") self.lbldB = QLabel("dB") self.lblPltStim = QLabel(self) self.lblPltStim.setText("Stimulus:") self.chkPltStim = QCheckBox("Show", self) self.chkPltStim.setChecked(False) self.chkPltStim.setToolTip("Show stimulus signal.") self.lblStimulus = QLabel("Type = ", self) self.cmbStimulus = QComboBox(self) self.cmbStimulus.addItems(["Pulse","Step","StepErr", "Cos", "Sine", "Rect", "Saw", "RandN", "RandU"]) self.cmbStimulus.setToolTip("Select stimulus type.") self.lblAmp = QLabel("<i>A</i> =", self) self.ledAmp = QLineEdit(self) self.ledAmp.setText(str(self.A)) self.ledAmp.setToolTip("Stimulus amplitude.") self.ledAmp.setObjectName("stimAmp") self.lblFreq = QLabel("<i>f</i> =", self) self.ledFreq = QLineEdit(self) self.ledFreq.setText(str(self.stim_freq)) self.ledFreq.setToolTip("Stimulus frequency.") self.ledFreq.setObjectName("stimFreq") self.lblFreqUnit = QLabel("f_S", self) self.lblNPoints = QLabel("<i>N</i> =", self) self.ledNPoints = QLineEdit(self) self.ledNPoints.setText("0") self.ledNPoints.setToolTip("<span>Number of points to calculate and display. " "N = 0 selects automatically.</span>") layHControls = QHBoxLayout() layHControls.addWidget(self.lblNPoints) layHControls.addWidget(self.ledNPoints) layHControls.addStretch(2) layHControls.addWidget(self.chkLog) layHControls.addWidget(self.lblLog) layHControls.addStretch(1) layHControls.addWidget(self.lblLogBottom) layHControls.addWidget(self.ledLogBottom) layHControls.addWidget(self.lbldB) layHControls.addStretch(2) layHControls.addWidget(self.lblPltStim) layHControls.addWidget(self.chkPltStim) layHControls.addStretch(1) layHControls.addWidget(self.lblStimulus) layHControls.addWidget(self.cmbStimulus) layHControls.addStretch(2) layHControls.addWidget(self.lblAmp) layHControls.addWidget(self.ledAmp) layHControls.addWidget(self.lblFreq) layHControls.addWidget(self.ledFreq) layHControls.addWidget(self.lblFreqUnit) layHControls.addStretch(10) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self.draw) self.ledNPoints.editingFinished.connect(self.draw) self.ledLogBottom.editingFinished.connect(self.draw) self.chkPltStim.clicked.connect(self.draw) # self.cmbStimulus.currentIndexChanged.connect(self.draw) self.cmbStimulus.activated.connect(self.draw) self.ledAmp.editingFinished.connect(self.draw) self.ledFreq.installEventFilter(self) self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui) self.draw() # initial calculation and drawing
class PlotImpz(QWidget): def __init__(self, parent): super(PlotImpz, self).__init__(parent) self.ACTIVE_3D = False # initial settings for line edit widgets self.stim_freq = 0.02 self.A = 1.0 self.bottom = -80 self._init_UI() def _init_UI(self): self.chkLog = QCheckBox(self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("<span>Logarithmic scale for y-axis.</span>") self.chkLog.setChecked(False) self.lblLog = QLabel("Log. y-axis", self) self.lblLogBottom = QLabel("Bottom = ", self) self.ledLogBottom = QLineEdit(self) self.ledLogBottom.setText(str(self.bottom)) self.ledLogBottom.setToolTip("<span>Minimum display value for log. scale.</span>") self.lbldB = QLabel("dB") self.lblPltStim = QLabel(self) self.lblPltStim.setText("Stimulus:") self.chkPltStim = QCheckBox("Show", self) self.chkPltStim.setChecked(False) self.chkPltStim.setToolTip("Show stimulus signal.") self.lblStimulus = QLabel("Type = ", self) self.cmbStimulus = QComboBox(self) self.cmbStimulus.addItems(["Pulse","Step","StepErr", "Cos", "Sine", "Rect", "Saw", "RandN", "RandU"]) self.cmbStimulus.setToolTip("Select stimulus type.") self.lblAmp = QLabel("<i>A</i> =", self) self.ledAmp = QLineEdit(self) self.ledAmp.setText(str(self.A)) self.ledAmp.setToolTip("Stimulus amplitude.") self.ledAmp.setObjectName("stimAmp") self.lblFreq = QLabel("<i>f</i> =", self) self.ledFreq = QLineEdit(self) self.ledFreq.setText(str(self.stim_freq)) self.ledFreq.setToolTip("Stimulus frequency.") self.ledFreq.setObjectName("stimFreq") self.lblFreqUnit = QLabel("f_S", self) self.lblNPoints = QLabel("<i>N</i> =", self) self.ledNPoints = QLineEdit(self) self.ledNPoints.setText("0") self.ledNPoints.setToolTip("<span>Number of points to calculate and display. " "N = 0 selects automatically.</span>") layHControls = QHBoxLayout() layHControls.addWidget(self.lblNPoints) layHControls.addWidget(self.ledNPoints) layHControls.addStretch(2) layHControls.addWidget(self.chkLog) layHControls.addWidget(self.lblLog) layHControls.addStretch(1) layHControls.addWidget(self.lblLogBottom) layHControls.addWidget(self.ledLogBottom) layHControls.addWidget(self.lbldB) layHControls.addStretch(2) layHControls.addWidget(self.lblPltStim) layHControls.addWidget(self.chkPltStim) layHControls.addStretch(1) layHControls.addWidget(self.lblStimulus) layHControls.addWidget(self.cmbStimulus) layHControls.addStretch(2) layHControls.addWidget(self.lblAmp) layHControls.addWidget(self.ledAmp) layHControls.addWidget(self.lblFreq) layHControls.addWidget(self.ledFreq) layHControls.addWidget(self.lblFreqUnit) layHControls.addStretch(10) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self.draw) self.ledNPoints.editingFinished.connect(self.draw) self.ledLogBottom.editingFinished.connect(self.draw) self.chkPltStim.clicked.connect(self.draw) # self.cmbStimulus.currentIndexChanged.connect(self.draw) self.cmbStimulus.activated.connect(self.draw) self.ledAmp.editingFinished.connect(self.draw) self.ledFreq.installEventFilter(self) self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui) self.draw() # initial calculation and drawing #------------------------------------------------------------------------------ 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 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: self.stim_freq = safe_eval(source.text(), self.stim_freq * fb.fil[0]['f_S'], return_type='float') / fb.fil[0]['f_S'] self.spec_edited = False # reset flag self.draw() if isinstance(source, QLineEdit): # could be extended for other widgets if event.type() == QEvent.FocusIn: self.spec_edited = False self.load_dict() 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 source.setText(str(params['FMT'].format(self.stim_freq * fb.fil[0]['f_S']))) elif event.type() == QEvent.FocusOut: _store_entry(source) source.setText(str(params['FMT'].format(self.stim_freq * fb.fil[0]['f_S']))) # Call base class method to continue normal event processing: return super(PlotImpz, self).eventFilter(source, event) #------------------------------------------------------------- def load_dict(self): """ Reload textfields from filter dictionary 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_dict() 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 logger.debug("exec load_dict") if not self.ledFreq.hasFocus(): # widget has no focus, round the display self.ledFreq.setText( str(params['FMT'].format(self.stim_freq * fb.fil[0]['f_S']))) else: # widget has focus, show full precision self.ledFreq.setText(str(self.stim_freq * fb.fil[0]['f_S'])) #------------------------------------------------------------------------------ def init_axes(self): # clear the axes and (re)draw the plot # try: self.mplwidget.fig.delaxes(self.ax_r) self.mplwidget.fig.delaxes(self.ax_i) except (KeyError, AttributeError, UnboundLocalError): pass if self.cmplx: self.ax_r = self.mplwidget.fig.add_subplot(211) self.ax_r.clear() self.ax_r.get_xaxis().tick_bottom() # remove axis ticks on top self.ax_r.get_yaxis().tick_left() # remove axis ticks right self.ax_i = self.mplwidget.fig.add_subplot(212, sharex = self.ax_r) self.ax_i.clear() self.ax_i.get_xaxis().tick_bottom() # remove axis ticks on top self.ax_i.get_yaxis().tick_left() # remove axis ticks right else: self.ax_r = self.mplwidget.fig.add_subplot(111) self.ax_r.clear() self.ax_r.get_xaxis().tick_bottom() # remove axis ticks on top self.ax_r.get_yaxis().tick_left() # remove axis ticks right self.mplwidget.fig.subplots_adjust(hspace = 0.5) if self.ACTIVE_3D: # not implemented / tested yet self.ax3d = self.mplwidget.fig.add_subplot(111, projection='3d') #------------------------------------------------------------------------------ def update_view(self): """ place holder; should update only the limits without recalculating the impulse respons """ self.draw() #------------------------------------------------------------------------------ def enable_ui(self): """ Triggered when the toolbar is enabled or disabled """ self.frmControls.setEnabled(self.mplwidget.mplToolbar.enabled) if self.mplwidget.mplToolbar.enabled: # self.init_axes() # called by self.draw self.draw() #------------------------------------------------------------------------------ def draw(self): if self.mplwidget.mplToolbar.enabled: self.draw_impz() #------------------------------------------------------------------------------ def draw_impz(self): """ (Re-)calculate h[n] and draw the figure """ log = self.chkLog.isChecked() stim = str(self.cmbStimulus.currentText()) periodic_sig = stim in {"Cos", "Sine","Rect", "Saw"} self.lblLogBottom.setVisible(log) self.ledLogBottom.setVisible(log) self.lbldB.setVisible(log) self.lblFreq.setVisible(periodic_sig) self.ledFreq.setVisible(periodic_sig) self.lblFreqUnit.setVisible(periodic_sig) self.lblFreqUnit.setText(rt_label(fb.fil[0]['freq_specs_unit'])) self.load_dict() self.bb = np.asarray(fb.fil[0]['ba'][0]) self.aa = np.asarray(fb.fil[0]['ba'][1]) if min(len(self.aa), len(self.bb)) < 2: logger.error('No proper filter coefficients: len(a), len(b) < 2 !') return sos = np.asarray(fb.fil[0]['sos']) antiCausal = 'zpkA' in fb.fil[0] causal = not (antiCausal) self.f_S = fb.fil[0]['f_S'] N_entry = safe_eval(self.ledNPoints.text(), 0, return_type='int', sign='pos') N = self.calc_n_points(N_entry) if N_entry != 0: # automatic calculation self.ledNPoints.setText(str(N)) self.A = safe_eval(self.ledAmp.text(), self.A, return_type='float') self.ledAmp.setText(str(self.A)) t = np.linspace(0, N/self.f_S, N, endpoint=False) title_str = r'Impulse Response' # default H_str = r'$h[n]$' # default # calculate h[n] if stim == "Pulse": x = np.zeros(N) x[0] = self.A # create dirac impulse as input signal elif stim == "Step": x = self.A * np.ones(N) # create step function title_str = r'Step Response' H_str = r'$h_{\epsilon}[n]$' elif stim == "StepErr": x = self.A * np.ones(N) # create step function title_str = r'Settling Error' H_str = r'$h_{\epsilon, \infty} - h_{\epsilon}[n]$' elif stim in {"Cos"}: x = self.A * np.cos(2 * np.pi * t * float(self.ledFreq.text())) if stim == "Cos": title_str = r'Transient Response to Cosine Signal' H_str = r'$y_{\cos}[n]$' elif stim in {"Sine", "Rect"}: x = self.A * np.sin(2 * np.pi * t * float(self.ledFreq.text())) if stim == "Sine": title_str = r'Transient Response to Sine Signal' H_str = r'$y_{\sin}[n]$' else: x = self.A * np.sign(x) title_str = r'Transient Response to Rect. Signal' H_str = r'$y_{rect}[n]$' elif stim == "Saw": x = self.A * sig.sawtooth(t * (float(self.ledFreq.text())* 2*np.pi)) title_str = r'Transient Response to Sawtooth Signal' H_str = r'$y_{saw}[n]$' elif stim == "RandN": x = self.A * np.random.randn(N) title_str = r'Transient Response to Gaussian Noise' H_str = r'$y_{gauss}[n]$' elif stim == "RandU": x = self.A * (np.random.rand(N)-0.5) title_str = r'Transient Response to Uniform Noise' H_str = r'$y_{uni}[n]$' else: logger.error('Unknown stimulus "{0}"'.format(stim)) return if len(sos) > 0 and (causal): # has second order sections and is causal h = sig.sosfilt(sos, x) elif (antiCausal): h = sig.filtfilt(self.bb, self.aa, x, -1, None) else: # no second order sections or antiCausals for current filter h = sig.lfilter(self.bb, self.aa, x) dc = sig.freqz(self.bb, self.aa, [0]) if stim == "StepErr": h = h - abs(dc[1]) # subtract DC value from response h = np.real_if_close(h, tol = 1e3) # tol specified in multiples of machine eps self.cmplx = np.any(np.iscomplex(h)) if self.cmplx: h_i = h.imag h = h.real H_i_str = r'$\Im\{$' + H_str + '$\}$' H_str = r'$\Re\{$' + H_str + '$\}$' if log: self.bottom = safe_eval(self.ledLogBottom.text(), self.bottom, return_type='float') self.ledLogBottom.setText(str(self.bottom)) H_str = r'$|$ ' + H_str + '$|$ in dB' h = np.maximum(20 * np.log10(abs(h)), self.bottom) if self.cmplx: h_i = np.maximum(20 * np.log10(abs(h_i)), self.bottom) H_i_str = r'$\log$ ' + H_i_str + ' in dB' else: self.bottom = 0 self.init_axes() #================ Main Plotting Routine ========================= [ml, sl, bl] = self.ax_r.stem(t, h, bottom=self.bottom, markerfmt='o', label = '$h[n]$') stem_fmt = params['mpl_stimuli'] if self.chkPltStim.isChecked(): [ms, ss, bs] = self.ax_r.stem(t, x, bottom=self.bottom, label = 'Stim.', **stem_fmt) ms.set_mfc(stem_fmt['mfc']) ms.set_mec(stem_fmt['mec']) ms.set_ms(stem_fmt['ms']) ms.set_alpha(stem_fmt['alpha']) for stem in ss: stem.set_linewidth(stem_fmt['lw']) stem.set_color(stem_fmt['mec']) stem.set_alpha(stem_fmt['alpha']) bs.set_visible(False) # invisible bottomline expand_lim(self.ax_r, 0.02) self.ax_r.set_title(title_str) if self.cmplx: [ml_i, sl_i, bl_i] = self.ax_i.stem(t, h_i, bottom=self.bottom, markerfmt='d', label = '$h_i[n]$') self.ax_i.set_xlabel(fb.fil[0]['plt_tLabel']) # self.ax_r.get_xaxis().set_ticklabels([]) # removes both xticklabels # plt.setp(ax_r.get_xticklabels(), visible=False) # is shorter but imports matplotlib, set property directly instead: [label.set_visible(False) for label in self.ax_r.get_xticklabels()] self.ax_r.set_ylabel(H_str + r'$\rightarrow $') self.ax_i.set_ylabel(H_i_str + r'$\rightarrow $') else: self.ax_r.set_xlabel(fb.fil[0]['plt_tLabel']) self.ax_r.set_ylabel(H_str + r'$\rightarrow $') if self.ACTIVE_3D: # not implemented / tested yet # plotting the stems for i in range(len(t)): self.ax3d.plot([t[i], t[i]], [h[i], h[i]], [0, h_i[i]], '-', linewidth=2, alpha=.5) # plotting a circle on the top of each stem self.ax3d.plot(t, h, h_i, 'o', markersize=8, markerfacecolor='none', label='$h[n]$') self.ax3d.set_xlabel('x') self.ax3d.set_ylabel('y') self.ax3d.set_zlabel('z') self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw() #------------------------------------------------------------------------------ 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 # TODO: IIR: more intelligent algorithm needed (based on transients) else: N = min(len(self.bb), 100) # FIR: N = number of coefficients (max. 100) else: N = N_user return N
class PlotHf(QWidget): # TODO: inset plot should have useful preset range, depending on filter type, # stop band or pass band should be selectable as well as lin / log scale # TODO: position and size of inset plot should be selectable def __init__(self, parent): super(PlotHf, self).__init__(parent) modes = ['| H |', 're{H}', 'im{H}'] self.cmbShowH = QComboBox(self) self.cmbShowH.addItems(modes) self.cmbShowH.setObjectName("cmbUnitsH") self.cmbShowH.setToolTip( "Show magnitude, real / imag. part of H or H \n" "without linear phase (acausal system).") self.cmbShowH.setCurrentIndex(0) self.lblIn = QLabel("in") units = ['dB', 'V', 'W', 'Auto'] self.cmbUnitsA = QComboBox(self) self.cmbUnitsA.addItems(units) self.cmbUnitsA.setObjectName("cmbUnitsA") self.cmbUnitsA.setToolTip( "Set unit for y-axis:\n" "dB is attenuation (positive values)\nV and W are less than 1.") self.cmbUnitsA.setCurrentIndex(0) self.cmbShowH.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbUnitsA.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.lblLinphase = QLabel("Acausal system") self.chkLinphase = QCheckBox() self.chkLinphase.setToolTip( "Remove linear phase according to filter order.\n" "Attention: this makes no sense for a non-linear phase system!") self.lblInset = QLabel("Inset") self.cmbInset = QComboBox(self) self.cmbInset.addItems(['off', 'edit', 'fixed']) self.cmbInset.setObjectName("cmbInset") self.cmbInset.setToolTip("Display/edit second inset plot") self.cmbInset.setCurrentIndex(0) self.inset_idx = 0 # store previous index for comparison self.lblSpecs = QLabel("Show Specs") self.chkSpecs = QCheckBox() self.chkSpecs.setChecked(False) self.chkSpecs.setToolTip("Display filter specs as hatched regions") self.lblPhase = QLabel("Phase") self.chkPhase = QCheckBox() self.chkPhase.setToolTip("Overlay phase") self.layHChkBoxes = QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.cmbShowH) self.layHChkBoxes.addWidget(self.lblIn) self.layHChkBoxes.addWidget(self.cmbUnitsA) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblLinphase) self.layHChkBoxes.addWidget(self.chkLinphase) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblInset) self.layHChkBoxes.addWidget(self.cmbInset) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblSpecs) self.layHChkBoxes.addWidget(self.chkSpecs) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblPhase) self.layHChkBoxes.addWidget(self.chkPhase) self.layHChkBoxes.addStretch(10) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # calculate and draw |H(f)| #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.cmbUnitsA.currentIndexChanged.connect(self.draw) self.cmbShowH.currentIndexChanged.connect(self.draw) self.chkLinphase.clicked.connect(self.draw) self.cmbInset.currentIndexChanged.connect(self.draw_inset) self.chkSpecs.clicked.connect(self.draw) self.chkPhase.clicked.connect(self.draw) #------------------------------------------------------------------------------ def init_axes(self): """ Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() #------------------------------------------------------------------------------ def plot_spec_limits(self, ax): """ Plot the specifications limits (F_SB, A_SB, ...) as lines and as hatched areas. """ def dB(lin): return 20 * np.log10(lin) def _plot_specs(): # upper limits: ax.plot(F_lim_upl, A_lim_upl, F_lim_upc, A_lim_upc, F_lim_upr, A_lim_upr, **line_params) if A_lim_upl: ax.fill_between(F_lim_upl, max(A_lim_upl), A_lim_upl, **fill_params) if A_lim_upc: ax.fill_between(F_lim_upc, max(A_lim_upc), A_lim_upc, **fill_params) if A_lim_upr: ax.fill_between(F_lim_upr, max(A_lim_upr), A_lim_upr, **fill_params) # lower limits: ax.plot(F_lim_lol, A_lim_lol, F_lim_loc, A_lim_loc, F_lim_lor, A_lim_lor, **line_params) if A_lim_lol: ax.fill_between(F_lim_lol, min(A_lim_lol), A_lim_lol, **fill_params) if A_lim_loc: ax.fill_between(F_lim_loc, min(A_lim_loc), A_lim_loc, **fill_params) if A_lim_lor: ax.fill_between(F_lim_lor, min(A_lim_lor), A_lim_lor, **fill_params) # fc = (0.8,0.8,0.8) # color for shaded areas fill_params = { 'facecolor': 'none', 'hatch': '/', 'edgecolor': rcParams['figure.edgecolor'], 'lw': 0.0 } line_params = {'linewidth': 1.0, 'color': 'blue', 'linestyle': '--'} if self.unitA == 'V': exp = 1. elif self.unitA == 'W': exp = 2. if self.unitA == 'dB': if fb.fil[0]['ft'] == "FIR": A_PB_max = dB(1 + self.A_PB) A_PB2_max = dB(1 + self.A_PB2) else: # IIR dB A_PB_max = A_PB2_max = 0 A_PB_min = dB(1 - self.A_PB) A_PB2_min = dB(1 - self.A_PB2) A_PB_minx = min(A_PB_min, A_PB2_min) - 5 A_PB_maxx = max(A_PB_max, A_PB2_max) + 5 A_SB = dB(self.A_SB) A_SB2 = dB(self.A_SB2) A_SB_maxx = max(A_SB, A_SB2) + 10 else: # 'V' or 'W' if fb.fil[0]['ft'] == "FIR": A_PB_max = (1 + self.A_PB)**exp A_PB2_max = (1 + self.A_PB2)**exp else: # IIR lin A_PB_max = A_PB2_max = 1 A_PB_min = (1 - self.A_PB)**exp A_PB2_min = (1 - self.A_PB)**exp A_PB_minx = A_PB_min / 1.05 A_PB_maxx = max(A_PB_max, A_PB2_max) * 1.05 A_SB = self.A_SB**exp A_SB2 = self.A_SB2**exp A_SB_maxx = A_PB_min / 10. F_max = self.f_S / 2 F_PB = self.F_PB F_SB = fb.fil[0]['F_SB'] * self.f_S F_SB2 = fb.fil[0]['F_SB2'] * self.f_S F_PB2 = fb.fil[0]['F_PB2'] * self.f_S F_lim_upl = F_lim_lol = [] # left side limits, lower and upper A_lim_upl = A_lim_lol = [] F_lim_upc = F_lim_loc = [] # center limits, lower and upper A_lim_upc = A_lim_loc = [] F_lim_upr = F_lim_lor = [] # right side limits, lower and upper A_lim_upr = A_lim_lor = [] if fb.fil[0]['rt'] == 'LP': F_lim_upl = [0, F_PB, F_PB] A_lim_upl = [A_PB_max, A_PB_max, A_PB_maxx] F_lim_lol = F_lim_upl A_lim_lol = [A_PB_min, A_PB_min, A_PB_minx] F_lim_upr = [F_SB, F_SB, F_max] A_lim_upr = [A_SB_maxx, A_SB, A_SB] if fb.fil[0]['rt'] == 'HP': F_lim_upl = [0, F_SB, F_SB] A_lim_upl = [A_SB, A_SB, A_SB_maxx] F_lim_upr = [F_PB, F_PB, F_max] A_lim_upr = [A_PB_maxx, A_PB_max, A_PB_max] F_lim_lor = F_lim_upr A_lim_lor = [A_PB_minx, A_PB_min, A_PB_min] if fb.fil[0]['rt'] == 'BS': F_lim_upl = [0, F_PB, F_PB] A_lim_upl = [A_PB_max, A_PB_max, A_PB_maxx] F_lim_lol = F_lim_upl A_lim_lol = [A_PB_min, A_PB_min, A_PB_minx] F_lim_upc = [F_SB, F_SB, F_SB2, F_SB2] A_lim_upc = [A_SB_maxx, A_SB, A_SB, A_SB_maxx] F_lim_upr = [F_PB2, F_PB2, F_max] A_lim_upr = [A_PB_maxx, A_PB2_max, A_PB2_max] F_lim_lor = F_lim_upr A_lim_lor = [A_PB_minx, A_PB2_min, A_PB2_min] if fb.fil[0]['rt'] in {"BP", "HIL"}: F_lim_upl = [0, F_SB, F_SB] A_lim_upl = [A_SB, A_SB, A_SB_maxx] F_lim_upc = [F_PB, F_PB, F_PB2, F_PB2] A_lim_upc = [A_PB_maxx, A_PB_max, A_PB_max, A_PB_maxx] F_lim_loc = F_lim_upc A_lim_loc = [A_PB_minx, A_PB_min, A_PB_min, A_PB_minx] F_lim_upr = [F_SB2, F_SB2, F_max] A_lim_upr = [A_SB_maxx, A_SB2, A_SB2] F_lim_upr = np.array(F_lim_upr) F_lim_lor = np.array(F_lim_lor) F_lim_upl = np.array(F_lim_upl) F_lim_lol = np.array(F_lim_lol) F_lim_upc = np.array(F_lim_upc) F_lim_loc = np.array(F_lim_loc) _plot_specs() # plot specs in the range 0 ... f_S/2 if fb.fil[0]['freqSpecsRangeType'] != 'half': # add plot limits for other half of the spectrum if fb.fil[0][ 'freqSpecsRangeType'] == 'sym': # frequency axis +/- f_S/2 F_lim_upl = -F_lim_upl F_lim_lol = -F_lim_lol F_lim_upc = -F_lim_upc F_lim_loc = -F_lim_loc F_lim_upr = -F_lim_upr F_lim_lor = -F_lim_lor else: # -> 'whole' F_lim_upl = self.f_S - F_lim_upl F_lim_lol = self.f_S - F_lim_lol F_lim_upc = self.f_S - F_lim_upc F_lim_loc = self.f_S - F_lim_loc F_lim_upr = self.f_S - F_lim_upr F_lim_lor = self.f_S - F_lim_lor _plot_specs() #------------------------------------------------------------------------------ def draw_inset(self): """ Construct / destruct second axes for an inset second plot """ # TODO: try ax1 = zoomed_inset_axes(ax, 6, loc=1) # zoom = 6 # TODO: choose size & position of inset, maybe dependent on filter type # or specs (i.e. where is passband etc.) # DEBUG # print(self.cmbInset.currentIndex(), self.mplwidget.fig.axes) # list of axes in Figure # for ax in self.mplwidget.fig.axes: # print(ax) # print("cmbInset, inset_idx:",self.cmbInset.currentIndex(), self.inset_idx) if self.cmbInset.currentIndex() > 0: if self.inset_idx == 0: # Inset was turned off before, create a new one # Add an axes at position rect [left, bottom, width, height]: self.ax_i = self.mplwidget.fig.add_axes([0.65, 0.61, .3, .3]) self.ax_i.clear() # clear old plot and specs # draw an opaque background with the extent of the inset plot: # self.ax_i.patch.set_facecolor('green') # without label area # self.mplwidget.fig.patch.set_facecolor('green') # whole figure extent = self.mplwidget.get_full_extent(self.ax_i, pad=0.0) # Transform this back to figure coordinates - otherwise, it # won't behave correctly when the size of the plot is changed: extent = extent.transformed( self.mplwidget.fig.transFigure.inverted()) rect = Rectangle((extent.xmin, extent.ymin), extent.width, extent.height, facecolor=rcParams['figure.facecolor'], edgecolor='none', transform=self.mplwidget.fig.transFigure, zorder=-1) self.ax_i.patches.append(rect) self.ax_i.set_xlim(fb.fil[0]['freqSpecsRange']) self.ax_i.plot(self.F, self.H_plt) if self.cmbInset.currentIndex() == 1: # edit / navigate inset self.ax_i.set_navigate(True) self.ax.set_navigate(False) if self.specs: self.plot_spec_limits(self.ax_i) else: # edit / navigate main plot self.ax_i.set_navigate(False) self.ax.set_navigate(True) else: # inset has been turned off, delete it self.ax.set_navigate(True) try: #remove ax_i from the figure self.mplwidget.fig.delaxes(self.ax_i) except AttributeError: pass self.inset_idx = self.cmbInset.currentIndex() # update index self.draw() #------------------------------------------------------------------------------ def draw_phase(self, ax): """ Draw phase on second y-axis in the axes system passed as the argument """ try: self.mplwidget.fig.delaxes(self.ax_p) except (KeyError, AttributeError): pass if self.chkPhase.isChecked(): self.ax_p = ax.twinx( ) # second axes system with same x-axis for phase # phi_str = r'$\angle H(\mathrm{e}^{\mathrm{j} \Omega})$' if fb.fil[0]['plt_phiUnit'] == 'rad': phi_str += ' in rad ' + r'$\rightarrow $' scale = 1. elif fb.fil[0]['plt_phiUnit'] == 'rad/pi': phi_str += ' in rad' + r'$ / \pi \;\rightarrow $' scale = 1. / np.pi else: phi_str += ' in deg ' + r'$\rightarrow $' scale = 180. / np.pi # replace nan and inf by finite values, otherwise np.unwrap yields # an array full of nans phi = np.angle(np.nan_to_num(self.H_c)) #----------------------------------------------------------- self.ax_p.plot(self.F, np.unwrap(phi) * scale, 'b--', label="Phase") #----------------------------------------------------------- self.ax_p.set_ylabel(phi_str, color='blue') nbins = len(self.ax.get_yticks()) # number of ticks on main y-axis # http://stackoverflow.com/questions/28692608/align-grid-lines-on-two-plots # http://stackoverflow.com/questions/3654619/matplotlib-multiple-y-axes-grid-lines-applied-to-both # http://stackoverflow.com/questions/20243683/matplotlib-align-twinx-tick-marks # manual setting: #self.ax_p.set_yticks( np.linspace(self.ax_p.get_ylim()[0],self.ax_p.get_ylim()[1],nbins) ) #ax1.set_yticks(np.linspace(ax1.get_ybound()[0], ax1.get_ybound()[1], 5)) #ax2.set_yticks(np.linspace(ax2.get_ybound()[0], ax2.get_ybound()[1], 5)) #http://stackoverflow.com/questions/3654619/matplotlib-multiple-y-axes-grid-lines-applied-to-both # use helper functions from matplotlib.ticker: # MaxNLocator: set no more than nbins + 1 ticks #self.ax_p.yaxis.set_major_locator( matplotlib.ticker.MaxNLocator(nbins = nbins) ) # further options: integer = False, # prune = [‘lower’ | ‘upper’ | ‘both’ | None] Remove edge ticks # AutoLocator: #self.ax_p.yaxis.set_major_locator( matplotlib.ticker.AutoLocator() ) # LinearLocator: #self.ax_p.yaxis.set_major_locator( matplotlib.ticker.LinearLocator(numticks = nbins -1 ) ) # self.ax_p.locator_params(axis = 'y', nbins = nbins) # # self.ax_p.set_yticks(np.linspace(self.ax_p.get_ybound()[0], # self.ax_p.get_ybound()[1], # len(self.ax.get_yticks())-1)) #N = source_ax.xaxis.get_major_ticks() #target_ax.xaxis.set_major_locator(LinearLocator(N)) # else: # try: # self.mplwidget.fig.delaxes(self.ax_p) # except (KeyError, AttributeError): # pass # self.draw() #------------------------------------------------------------------------------ def calc_hf(self): """ (Re-)Calculate the complex frequency response H(f) """ # whole = fb.fil[0]['freqSpecsRangeType'] != 'half' # calculate H_cplx(W) (complex) for W = 0 ... 2 pi: [self.W, self.H_cplx] = sig.freqz(fb.fil[0]['ba'][0], fb.fil[0]['ba'][1], worN=rc.params['N_FFT'], whole=True) # bb, aa, N_FFT, 0 ... 2 pi #------------------------------------------------------------------------------ def draw(self): """ Re-calculate |H(f)| and draw the figure """ if self.mplwidget.mplToolbar.enable_update: self.calc_hf() self.update_view() #------------------------------------------------------------------------------ def update_view(self): """ Draw the figure with new limits, scale etc without recalculating H(f) """ if np.all(self.W) is None: # H(f) has not been calculated yet self.calc_hf() if self.cmbUnitsA.currentText() == 'Auto': self.unitA = fb.fil[0]['amp_specs_unit'] else: self.unitA = self.cmbUnitsA.currentText() # Linphase settings only makes sense for amplitude plot self.chkLinphase.setCheckable(self.unitA == 'V') self.chkLinphase.setEnabled(self.unitA == 'V') self.lblLinphase.setEnabled(self.unitA == 'V') self.specs = self.chkSpecs.isChecked() self.linphase = self.chkLinphase.isChecked() self.f_S = fb.fil[0]['f_S'] self.F_PB = fb.fil[0]['F_PB'] * self.f_S self.F_SB = fb.fil[0]['F_SB'] * self.f_S self.A_PB = fb.fil[0]['A_PB'] self.A_PB2 = fb.fil[0]['A_PB2'] self.A_SB = fb.fil[0]['A_SB'] self.A_SB2 = fb.fil[0]['A_SB2'] f_lim = fb.fil[0]['freqSpecsRange'] # shift, scale and select frequency range to be displayed: # W -> F, H_cplx -> H_c self.H_c = self.H_cplx self.F = self.W / (2 * np.pi) * self.f_S if fb.fil[0]['freqSpecsRangeType'] == 'sym': self.H_c = np.fft.fftshift(self.H_cplx) self.F -= self.f_S / 2. elif fb.fil[0]['freqSpecsRangeType'] == 'half': self.H_c = self.H_cplx[0:rc.params['N_FFT'] // 2] self.F = self.F[0:rc.params['N_FFT'] // 2] # now calculate mag / real / imaginary part of H_c: if self.linphase: # remove the linear phase self.H_c = self.H_c * np.exp( 1j * self.W[0:len(self.F)] * fb.fil[0]["N"] / 2.) if self.cmbShowH.currentIndex() == 0: # show magnitude of H H = abs(self.H_c) H_str = r'$|H(\mathrm{e}^{\mathrm{j} \Omega})|$' elif self.cmbShowH.currentIndex() == 1: # show real part of H H = self.H_c.real H_str = r'$\Re \{H(\mathrm{e}^{\mathrm{j} \Omega})\}$' else: # show imag. part of H H = self.H_c.imag H_str = r'$\Im \{H(\mathrm{e}^{\mathrm{j} \Omega})\}$' # clear the axes and (re)draw the plot if self.ax.get_navigate(): self.ax.clear() #================ Main Plotting Routine ========================= if self.unitA == 'dB': A_lim = [ 20 * np.log10(self.A_SB) - 10, 20 * np.log10(1 + self.A_PB) + 1 ] self.H_plt = 20 * np.log10(abs(H)) H_str += ' in dB ' + r'$\rightarrow$' elif self.unitA == 'V': # 'lin' A_lim = [0, (self.A_PB + 1)] self.H_plt = H H_str += ' in V ' + r'$\rightarrow $' self.ax.axhline(linewidth=1, color='k') # horizontal line at 0 else: # unit is W A_lim = [0, (1 + self.A_PB)**2.] self.H_plt = H * H.conj() H_str += ' in W ' + r'$\rightarrow $' #----------------------------------------------------------- self.ax.plot(self.F, self.H_plt, label='H(f)') self.draw_phase(self.ax) #----------------------------------------------------------- # self.ax_bounds = [self.ax.get_ybound()[0], self.ax.get_ybound()[1]]#, self.ax.get] self.ax.set_xlim(f_lim) self.ax.set_ylim(A_lim) if self.specs: self.plot_spec_limits(self.ax) self.ax.set_title(r'Magnitude Frequency Response') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(H_str) self.mplwidget.redraw()
class PlotTauG(QWidget): def __init__(self, parent): super(PlotTauG, self).__init__(parent) self.verbose = False # suppress warnings # ============================================================================= # #### not needed at the moment ### # self.chkWarnings = QCheckBox("Enable Warnings", self) # self.chkWarnings.setChecked(False) # self.chkWarnings.setToolTip("Print warnings about singular group delay") # # layHControls = QHBoxLayout() # layHControls.addStretch(10) # layHControls.addWidget(self.chkWarnings) # # # This widget encompasses all control subwidgets: # self.frmControls = QFrame(self) # self.frmControls.setObjectName("frmControls") # self.frmControls.setLayout(layHControls) # # ============================================================================= self.mplwidget = MplWidget(self) # self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # initial drawing of tau_g #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui) #------------------------------------------------------------------------------ def init_axes(self): """ Initialize and clear the axes """ self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() self.ax.get_xaxis().tick_bottom() # remove axis ticks on top self.ax.get_yaxis().tick_left() # remove axis ticks right #------------------------------------------------------------------------------ def calc_tau_g(self): """ (Re-)Calculate the complex frequency response H(f) """ bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] # calculate H_cmplx(W) (complex) for W = 0 ... 2 pi: self.W, self.tau_g = grpdelay( bb, aa, params['N_FFT'], whole=True, verbose=self.verbose) # self.chkWarnings.isChecked()) # Zero phase filters have no group delay (Causal+AntiCausal) if 'baA' in fb.fil[0]: self.tau_g = np.zeros(self.tau_g.size) #------------------------------------------------------------------------------ def enable_ui(self): """ Triggered when the toolbar is enabled or disabled """ # self.frmControls.setEnabled(self.mplwidget.mplToolbar.enabled) if self.mplwidget.mplToolbar.enabled: self.init_axes() self.draw() #------------------------------------------------------------------------------ def draw(self): if self.mplwidget.mplToolbar.enabled: self.calc_tau_g() self.update_view() #------------------------------------------------------------------------------ def update_view(self): """ Draw the figure with new limits, scale etc without recalculating H(f) """ #========= select frequency range to be displayed ===================== #=== shift, scale and select: W -> F, H_cplx -> H_c f_S2 = fb.fil[0]['f_S'] / 2. F = self.W * f_S2 / np.pi if fb.fil[0]['freqSpecsRangeType'] == 'sym': # shift tau_g and F by f_S/2 tau_g = np.fft.fftshift(self.tau_g) F -= f_S2 elif fb.fil[0]['freqSpecsRangeType'] == 'half': # only use the first half of H and F tau_g = self.tau_g[0:params['N_FFT'] // 2] F = F[0:params['N_FFT'] // 2] else: # fb.fil[0]['freqSpecsRangeType'] == 'whole' # use H and F as calculated tau_g = self.tau_g #================ Main Plotting Routine ========================= #=== clear the axes and (re)draw the plot if fb.fil[0]['freq_specs_unit'] in {'f_S', 'f_Ny'}: tau_str = r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega}) / T_S \; \rightarrow $' else: tau_str = r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega})$'\ + ' in ' + fb.fil[0]['plt_tUnit'] + r' $ \rightarrow $' tau_g = tau_g / fb.fil[0]['f_S'] #--------------------------------------------------------- self.ax.clear() # need to clear, doesn't overwrite line_tau_g, = self.ax.plot(F, tau_g, label="Group Delay") #--------------------------------------------------------- self.ax.set_title(r'Group Delay $ \tau_g$') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(tau_str) # widen y-limits to suppress numerical inaccuracies when tau_g = constant self.ax.set_ylim([max(min(tau_g) - 0.5, 0), max(tau_g) + 0.5]) self.ax.set_xlim(fb.fil[0]['freqSpecsRange']) self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw()
def __init__(self, parent=None, DEBUG=False): # default parent = None -> top Window super(Plot3D, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG self.zmin = 0 self.zmax = 4 self.zmin_dB = -80 self.lblLog = QtGui.QLabel("Log.") self.chkLog = QtGui.QCheckBox(self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Logarithmic scale") self.chkLog.setChecked(False) self.lblBottom = QtGui.QLabel("Bottom:") self.ledBottom = QtGui.QLineEdit(self) self.ledBottom.setObjectName("ledBottom") self.ledBottom.setText(str(self.zmin)) self.ledBottom.setToolTip("Minimum display value.") self.lblTop = QtGui.QLabel("Top:") self.ledTop = QtGui.QLineEdit(self) self.ledTop.setObjectName("ledTop") self.ledTop.setText(str(self.zmax)) self.ledTop.setToolTip("Maximum display value.") # self.ledTop.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) self.lblUC = QtGui.QLabel(self) self.lblUC.setText("UC") self.chkUC = QtGui.QCheckBox(self) self.chkUC.setObjectName("chkUC") self.chkUC.setToolTip("Plot unit circle") self.chkUC.setChecked(True) self.lblPZ = QtGui.QLabel(self) self.lblPZ.setText("P/Z") self.chkPZ = QtGui.QCheckBox(self) self.chkPZ.setObjectName("chkPZ") self.chkPZ.setToolTip("Plot poles and zeros") self.chkPZ.setChecked(True) self.lblHf = QtGui.QLabel(self) self.lblHf.setText("H(f)") self.chkHf = QtGui.QCheckBox(self) self.chkHf.setObjectName("chkHf") self.chkHf.setToolTip("Plot H(f) along the unit circle") self.chkHf.setChecked(True) modes = ['None', 'Mesh', 'Surf', 'Contour'] self.cmbMode3D = QtGui.QComboBox(self) self.cmbMode3D.addItems(modes) self.cmbMode3D.setObjectName("cmbShow3D") self.cmbMode3D.setToolTip("Select 3D-plot mode.") self.cmbMode3D.setCurrentIndex(0) self.cmbMode3D.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.lblColBar = QtGui.QLabel(self) self.lblColBar.setText("Colorbar") self.chkColBar = QtGui.QCheckBox(self) self.chkColBar.setObjectName("chkColBar") self.chkColBar.setToolTip("Show colorbar") self.chkColBar.setChecked(False) self.diaAlpha = QtGui.QDial(self) self.diaAlpha.setRange(0., 10.) self.diaAlpha.setValue(5) self.diaAlpha.setTracking(False) # produce less events when turning self.diaAlpha.setFixedHeight(30) self.diaAlpha.setFixedWidth(30) self.diaAlpha.setWrapping(False) self.diaAlpha.setToolTip("Set transparency for surf and contour plot.") self.diaHatch = QtGui.QDial(self) self.diaHatch.setRange(0., 9.) self.diaHatch.setValue(5) self.diaHatch.setTracking(False) # produce less events when turning self.diaHatch.setFixedHeight(30) self.diaHatch.setFixedWidth(30) self.diaHatch.setWrapping(False) self.diaHatch.setToolTip("Set hatching for H(jw).") self.lblContour2D = QtGui.QLabel(self) self.lblContour2D.setText("Contour2D") self.chkContour2D = QtGui.QCheckBox(self) self.chkContour2D.setObjectName("chkContour2D") self.chkContour2D.setToolTip("Plot 2D-contours for real and imaginary part") self.chkContour2D.setChecked(False) self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.lblLog) self.layHChkBoxes.addWidget(self.chkLog) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblBottom) self.layHChkBoxes.addWidget(self.ledBottom) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblTop) self.layHChkBoxes.addWidget(self.ledTop) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblUC) self.layHChkBoxes.addWidget(self.chkUC) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblPZ) self.layHChkBoxes.addWidget(self.chkPZ) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblHf) self.layHChkBoxes.addWidget(self.chkHf) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.cmbMode3D) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblColBar) self.layHChkBoxes.addWidget(self.chkColBar) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.diaAlpha) self.layHChkBoxes.addWidget(self.diaHatch) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblContour2D) self.layHChkBoxes.addWidget(self.chkContour2D) self.layHChkBoxes.addStretch(1) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.initCoord() self.draw() # calculate and draw phi(f) # #============================================= # # Signals & Slots # #============================================= self.chkLog.clicked.connect(self.logClicked) self.ledBottom.editingFinished.connect(self.logClicked) self.ledTop.editingFinished.connect(self.logClicked) self.chkUC.clicked.connect(self.draw) self.chkHf.clicked.connect(self.draw) self.chkPZ.clicked.connect(self.draw) self.cmbMode3D.currentIndexChanged.connect(self.draw) self.chkColBar.clicked.connect(self.draw) self.diaAlpha.valueChanged.connect(self.draw) self.diaHatch.valueChanged.connect(self.draw) self.chkContour2D.clicked.connect(self.draw)
class Plot3D(QtGui.QWidget): """ Class for various 3D-plots: - lin / log line plot of H(f) - lin / log surf plot of H(z) - optional display of poles / zeros """ def __init__(self, parent): super(Plot3D, self).__init__(parent) self.zmin = 0 self.zmax = 4 self.zmin_dB = -80 self.cmap_default = 'RdYlBu_r' self._init_UI() def _init_UI(self): self.chkLog = QtGui.QCheckBox(self) self.chkLog.setText("Log.") self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Logarithmic scale") self.chkLog.setChecked(False) self.chkPolar = QtGui.QCheckBox(self) self.chkPolar.setText("Polar") self.chkPolar.setObjectName("chkPolar") self.chkPolar.setToolTip("Polar coordinates") self.chkPolar.setChecked(False) self.lblBottom = QtGui.QLabel("Bottom =") self.ledBottom = QtGui.QLineEdit(self) self.ledBottom.setObjectName("ledBottom") self.ledBottom.setText(str(self.zmin)) self.ledBottom.setToolTip("Minimum display value.") self.lblTop = QtGui.QLabel("Top:") self.ledTop = QtGui.QLineEdit(self) self.ledTop.setObjectName("ledTop") self.ledTop.setText(str(self.zmax)) self.ledTop.setToolTip("Maximum display value.") # self.ledTop.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) self.chkUC = QtGui.QCheckBox(self) self.chkUC.setText("UC") self.chkUC.setObjectName("chkUC") self.chkUC.setToolTip("Plot unit circle") self.chkUC.setChecked(True) self.chkPZ = QtGui.QCheckBox(self) self.chkPZ.setText("P/Z") self.chkPZ.setObjectName("chkPZ") self.chkPZ.setToolTip("Plot poles and zeros") self.chkPZ.setChecked(True) self.chkHf = QtGui.QCheckBox(self) self.chkHf.setText("H(f)") self.chkHf.setObjectName("chkHf") self.chkHf.setToolTip("Plot H(f) along the unit circle") self.chkHf.setChecked(True) modes = ['None', 'Mesh', 'Surf', 'Contour'] self.cmbMode3D = QtGui.QComboBox(self) self.cmbMode3D.addItems(modes) self.cmbMode3D.setObjectName("cmbShow3D") self.cmbMode3D.setToolTip("Select 3D-plot mode.") self.cmbMode3D.setCurrentIndex(0) self.cmbMode3D.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.chkColormap_r = QtGui.QCheckBox(self) self.chkColormap_r.setText("reverse") self.chkColormap_r.setToolTip("reverse colormap") self.chkColormap_r.setChecked(True) self.cmbColormap = QtGui.QComboBox(self) self._init_cmb_colormap() self.cmbColormap.setToolTip("Select colormap") self.chkColBar = QtGui.QCheckBox(self) self.chkColBar.setText("Colorbar") self.chkColBar.setObjectName("chkColBar") self.chkColBar.setToolTip("Show colorbar") self.chkColBar.setChecked(False) self.chkLighting = QtGui.QCheckBox(self) self.chkLighting.setText("Lighting") self.chkLighting.setObjectName("chkLighting") self.chkLighting.setToolTip("Enable light source") self.chkLighting.setChecked(False) self.lblAlpha = QtGui.QLabel("Alpha") self.diaAlpha = QtGui.QDial(self) self.diaAlpha.setRange(0., 10.) self.diaAlpha.setValue(10) self.diaAlpha.setTracking(False) # produce less events when turning self.diaAlpha.setFixedHeight(30) self.diaAlpha.setFixedWidth(30) self.diaAlpha.setWrapping(False) self.diaAlpha.setToolTip( "Set transparency for surf and 3D-contour plot.") self.lblHatch = QtGui.QLabel("Stride") self.diaHatch = QtGui.QDial(self) self.diaHatch.setRange(0., 9.) self.diaHatch.setValue(5) self.diaHatch.setTracking(False) # produce less events when turning self.diaHatch.setFixedHeight(30) self.diaHatch.setFixedWidth(30) self.diaHatch.setWrapping(False) self.diaHatch.setToolTip("Set hatching for H(jw).") self.chkContour2D = QtGui.QCheckBox(self) self.chkContour2D.setText("Contour2D") self.chkContour2D.setObjectName("chkContour2D") self.chkContour2D.setToolTip("Plot 2D-contours at z =0") self.chkContour2D.setChecked(False) #---------------------------------------------------------------------- # LAYOUT for UI widgets #---------------------------------------------------------------------- spc = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.layGSelect = QtGui.QGridLayout() self.layGSelect.setObjectName('plotSpecSelect') self.layGSelect.addWidget(self.chkLog, 0, 0) self.layGSelect.addWidget(self.chkPolar, 1, 0) self.layGSelect.addWidget(self.lblTop, 0, 2) self.layGSelect.addWidget(self.lblBottom, 1, 2) self.layGSelect.addWidget(self.ledTop, 0, 4) self.layGSelect.addWidget(self.ledBottom, 1, 4) self.layGSelect.addItem(spc, 0, 5) self.layGSelect.addWidget(self.chkUC, 0, 6) self.layGSelect.addWidget(self.chkHf, 1, 6) self.layGSelect.addWidget(self.chkPZ, 0, 8) self.layGSelect.addWidget(self.cmbColormap, 0, 10, 1, 1) self.layGSelect.addWidget(self.chkColormap_r, 1, 10) self.layGSelect.addWidget(self.cmbMode3D, 0, 12) self.layGSelect.addWidget(self.chkContour2D, 1, 12) self.layGSelect.addWidget(self.chkLighting, 0, 14) self.layGSelect.addWidget(self.chkColBar, 1, 14) self.layGSelect.addWidget(self.diaAlpha, 0, 16) self.layGSelect.addWidget(self.lblAlpha, 0, 15) self.layGSelect.addWidget(self.diaHatch, 1, 16) self.layGSelect.addWidget(self.lblHatch, 1, 15) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layGSelect) self.setLayout(self.mplwidget.layVMainMpl) # self.mplwidget.setFocus() # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self._init_grid() # initialize grid and do initial plot #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self._log_clicked) self.ledBottom.editingFinished.connect(self._log_clicked) self.ledTop.editingFinished.connect(self._log_clicked) self.chkPolar.clicked.connect(self._init_grid) self.chkUC.clicked.connect(self.draw) self.chkHf.clicked.connect(self.draw) self.chkPZ.clicked.connect(self.draw) self.cmbMode3D.currentIndexChanged.connect(self.draw) self.chkColBar.clicked.connect(self.draw) self.cmbColormap.currentIndexChanged.connect(self.draw) self.chkColormap_r.clicked.connect(self._init_cmb_colormap) self.chkLighting.clicked.connect(self.draw) self.diaAlpha.valueChanged.connect(self.draw) self.diaHatch.valueChanged.connect(self.draw) self.chkContour2D.clicked.connect(self.draw) logger.debug("UI initialized") #------------------------------------------------------------------------------ def _init_cmb_colormap(self): """ (Re-)Load combobox with available colormaps""" if self.chkColormap_r.isChecked(): cmap_list = [m for m in cm.datad if m.endswith("_r")] else: cmap_list = [m for m in cm.datad if not m.endswith("_r")] # *_r colormaps reverse the color order cmap_list.sort() self.cmbColormap.blockSignals(True) # don't send signal "indexChanged" self.cmbColormap.clear() self.cmbColormap.addItems(cmap_list) self.cmbColormap.blockSignals(False) idx = self.cmbColormap.findText(self.cmap_default) if idx == -1: idx = 0 self.cmbColormap.setCurrentIndex(idx) #------------------------------------------------------------------------------ def _init_grid(self): """ Initialize (x,y,z) coordinate grid + (re)draw plot.""" phi_UC = np.linspace(0, 2 * pi, 400, endpoint=True) # angles for unit circle self.xy_UC = np.exp(1j * phi_UC) # x,y coordinates of unity circle steps = 100 # number of steps for x, y, r, phi # self.xmin = -1.5 self.xmax = 1.5 # cartesian range limits self.ymin = -1.5 self.ymax = 1.5 rmin = 0 rmax = self.xmin # polar range limits # Calculate grids for 3D-Plots dr = rmax / steps * 2 # grid size for polar range dx = (self.xmax - self.xmin) / steps dy = (self.ymax - self.ymin) / steps # grid size cartesian range if self.chkPolar.isChecked(): # # Plot circular range in 3D-Plot [r, phi] = np.meshgrid(np.arange(rmin, rmax, dr), np.linspace(0, 2 * pi, steps, endpoint=True)) self.x = r * cos(phi) self.y = r * sin(phi) else: # cartesian grid [self.x, self.y] = np.meshgrid(np.arange(self.xmin, self.xmax, dx), np.arange(self.ymin, self.ymax, dy)) self.z = self.x + 1j * self.y # create coordinate grid for complex plane self.draw() # initial plot #------------------------------------------------------------------------------ def _init_axes(self): """ Initialize and clear the axes to get rid of colorbar The azimuth / elevation / distance settings of the camera are restored after clearing the axes. See http://stackoverflow.com/questions/4575588/matplotlib-3d-plot-with-pyqt4-in-qtabwidget-mplwidget """ self._save_axes() self.mplwidget.fig.clf() # needed to get rid of colorbar self.ax3d = self.mplwidget.fig.add_subplot(111, projection='3d') self._restore_axes() #------------------------------------------------------------------------------ def _save_axes(self): """ Store x/y/z - limits and camera position """ try: self.azim = self.ax3d.azim self.elev = self.ax3d.elev self.dist = self.ax3d.dist self.xlim = self.ax3d.get_xlim3d() self.ylim = self.ax3d.get_ylim3d() self.zlim = self.ax3d.get_zlim3d() except AttributeError: # not yet initialized, set standard values self.azim = -65 self.elev = 30 self.dist = 10 self.xlim = (self.xmin, self.xmax) self.ylim = (self.ymin, self.ymax) self.zlim = (self.zmin, self.zmax) #------------------------------------------------------------------------------ def _restore_axes(self): """ Restore x/y/z - limits and camera position """ if self.mplwidget.mplToolbar.lock_zoom: self.ax3d.set_xlim3d(self.xlim) self.ax3d.set_ylim3d(self.ylim) self.ax3d.set_zlim3d(self.zlim) self.ax3d.azim = self.azim self.ax3d.elev = self.elev self.ax3d.dist = self.dist #------------------------------------------------------------------------------ def _log_clicked(self): """ Change scale and settings to log / lin when log setting is changed Update min / max settings when lineEdits have been edited """ self.log = self.chkLog.isChecked() if self.sender().objectName( ) == 'chkLog': # clicking chkLog triggered the slot if self.log: self.ledBottom.setText(str(self.zmin_dB)) self.zmax_dB = np.round(20 * log10(self.zmax), 2) self.ledTop.setText(str(self.zmax_dB)) else: self.ledBottom.setText(str(self.zmin)) self.zmax = np.round(10**(self.zmax_dB / 20), 2) self.ledTop.setText(str(self.zmax)) else: # finishing a lineEdit field triggered the slot if self.log: self.zmin_dB = float(self.ledBottom.text()) self.zmax_dB = float(self.ledTop.text()) else: self.zmin = float(self.ledBottom.text()) self.zmax = float(self.ledTop.text()) self.draw() #------------------------------------------------------------------------------ def draw(self): """ Main drawing entry point: Check whether updating is enabled in the toolbar and then perform the actual plot """ if self.mplwidget.mplToolbar.enable_update: self.draw_3d() #------------------------------------------------------------------------------ def draw_3d(self): """ Draw various 3D plots """ self._init_axes() bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] zz = np.array(fb.fil[0]['zpk'][0]) pp = np.array(fb.fil[0]['zpk'][1]) wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' # not used f_S = fb.fil[0]['f_S'] N_FFT = rc.params['N_FFT'] alpha = self.diaAlpha.value() / 10. cmap = cm.get_cmap(str(self.cmbColormap.currentText())) # Number of Lines /step size for H(f) stride, mesh, contour3d: stride = 10 - self.diaHatch.value() NL = 3 * self.diaHatch.value() + 5 #cNorm = colors.Normalize(vmin=0, vmax=values[-1]) #scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet) #----------------------------------------------------------------------------- # Calculate H(w) along the upper half of unity circle #----------------------------------------------------------------------------- [w, H] = sig.freqz(bb, aa, worN=N_FFT, whole=True) H = np.nan_to_num(H) # replace nans and inf by finite numbers H_abs = abs(H) H_max = max(H_abs) H_min = min(H_abs) #f = w / (2 * pi) * f_S # translate w to absolute frequencies #F_min = f[np.argmin(H_abs)] plevel_rel = 1.05 # height of plotted pole position relative to zmax zlevel_rel = 0.1 # height of plotted zero position relative to zmax if self.chkLog.isChecked(): # logarithmic scale bottom = np.floor(max(self.zmin_dB, 20 * log10(H_min)) / 10) * 10 top = self.zmax_dB top_bottom = top - bottom zlevel = bottom - top_bottom * zlevel_rel if self.cmbMode3D.currentText( ) == 'None': # "Poleposition" for H(f) plot only plevel_top = 2 * bottom - zlevel # height of displayed pole position plevel_btm = bottom else: plevel_top = top + top_bottom * (plevel_rel - 1) plevel_btm = top else: # linear scale bottom = max(self.zmin, H_min) # min. display value top = self.zmax # max. display value top_bottom = top - bottom # top = zmax_rel * H_max # calculate display top from max. of H(f) zlevel = bottom + top_bottom * zlevel_rel # height of displayed zero position if self.cmbMode3D.currentText( ) == 'None': # "Poleposition" for H(f) plot only #H_max = np.clip(max(H_abs), 0, self.zmax) # make height of displayed poles same to zeros plevel_top = bottom + top_bottom * zlevel_rel plevel_btm = bottom else: plevel_top = plevel_rel * top plevel_btm = top # calculate H(jw)| along the unity circle and |H(z)|, each clipped # between bottom and top H_UC = H_mag(bb, aa, self.xy_UC, top, H_min=bottom, log=self.chkLog.isChecked()) Hmag = H_mag(bb, aa, self.z, top, H_min=bottom, log=self.chkLog.isChecked()) #=============================================================== ## plot Unit Circle (UC) #=============================================================== if self.chkUC.isChecked(): # Plot unit circle and marker at (1,0): self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, ones(len(self.xy_UC)) * bottom, lw=2, color='k') self.ax3d.plot([0.97, 1.03], [0, 0], [bottom, bottom], lw=2, color='k') #=============================================================== ## plot ||H(f)| along unit circle as 3D-lineplot #=============================================================== if self.chkHf.isChecked(): self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, H_UC, alpha=0.5) # draw once more as dashed white line to improve visibility self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, H_UC, 'w--') if stride < 10: # plot thin vertical line every stride points on the UC for k in range(len(self.xy_UC[::stride])): self.ax3d.plot([ self.xy_UC.real[::stride][k], self.xy_UC.real[::stride][k] ], [ self.xy_UC.imag[::stride][k], self.xy_UC.imag[::stride][k] ], [ np.ones(len(self.xy_UC[::stride]))[k] * bottom, H_UC[::stride][k] ], linewidth=1, color=(0.5, 0.5, 0.5)) #=============================================================== ## plot Poles and Zeros #=============================================================== if self.chkPZ.isChecked(): PN_SIZE = 8 # size of P/N symbols # Plot zero markers at |H(z_i)| = zlevel with "stems": self.ax3d.plot(zz.real, zz.imag, ones(len(zz)) * zlevel, 'o', markersize=PN_SIZE, markeredgecolor='blue', markeredgewidth=2.0, markerfacecolor='none') for k in range(len(zz)): # plot zero "stems" self.ax3d.plot([zz[k].real, zz[k].real], [zz[k].imag, zz[k].imag], [bottom, zlevel], linewidth=1, color='b') # Plot the poles at |H(z_p)| = plevel with "stems": self.ax3d.plot(np.real(pp), np.imag(pp), plevel_top, 'x', markersize=PN_SIZE, markeredgewidth=2.0, markeredgecolor='red') for k in range(len(pp)): # plot pole "stems" self.ax3d.plot([pp[k].real, pp[k].real], [pp[k].imag, pp[k].imag], [plevel_btm, plevel_top], linewidth=1, color='r') #=============================================================== ## 3D-Plots of |H(z)| clipped between |H(z)| = top #=============================================================== m_cb = cm.ScalarMappable( cmap=cmap) # normalized proxy object that is mappable m_cb.set_array(Hmag) # for colorbar #--------------------------------------------------------------- ## 3D-mesh plot #--------------------------------------------------------------- if self.cmbMode3D.currentText() == 'Mesh': # fig_mlab = mlab.figure(fgcolor=(0., 0., 0.), bgcolor=(1, 1, 1)) # self.ax3d.set_zlim(0,2) self.ax3d.plot_wireframe(self.x, self.y, Hmag, rstride=5, cstride=stride, linewidth=1, color='gray') #--------------------------------------------------------------- ## 3D-surface plot #--------------------------------------------------------------- # http://stackoverflow.com/questions/28232879/phong-shading-for-shiny-python-3d-surface-plots elif self.cmbMode3D.currentText() == 'Surf': if MLAB: ## Mayavi surf = mlab.surf(self.x, self.y, H_mag, colormap='RdYlBu', warp_scale='auto') # Change the visualization parameters. surf.actor.property.interpolation = 'phong' surf.actor.property.specular = 0.1 surf.actor.property.specular_power = 5 # s = mlab.contour_surf(self.x, self.y, Hmag, contour_z=0) mlab.show() else: if self.chkLighting.isChecked(): ls = LightSource(azdeg=0, altdeg=65) # Create light source object rgb = ls.shade( Hmag, cmap=cmap) # Shade data, creating an rgb array cmap_surf = None else: rgb = None cmap_surf = cmap # s = self.ax3d.plot_surface(self.x, self.y, Hmag, # alpha=OPT_3D_ALPHA, rstride=1, cstride=1, cmap=cmap, # linewidth=0, antialiased=False, shade=True, facecolors = rgb) # s.set_edgecolor('gray') s = self.ax3d.plot_surface(self.x, self.y, Hmag, alpha=alpha, rstride=1, cstride=1, linewidth=0, antialiased=False, facecolors=rgb, cmap=cmap_surf, shade=True) s.set_edgecolor(None) #--------------------------------------------------------------- ## 3D-Contour plot #--------------------------------------------------------------- elif self.cmbMode3D.currentText() == 'Contour': s = self.ax3d.contourf3D(self.x, self.y, Hmag, NL, alpha=alpha, cmap=cmap) #--------------------------------------------------------------- ## 2D-Contour plot # TODO: 2D contour plots do not plot correctly together with 3D plots in # current matplotlib 1.4.3 -> disable them for now # TODO: zdir = x / y delivers unexpected results -> rather plot max(H) # along the other axis? # TODO: colormap is created depending on the zdir = 'z' contour plot # -> set limits of (all) other plots manually? if self.chkContour2D.isChecked(): # self.ax3d.contourf(x, y, Hmag, 20, zdir='x', offset=xmin, # cmap=cmap, alpha = alpha)#, vmin = bottom)#, vmax = top, vmin = bottom) # self.ax3d.contourf(x, y, Hmag, 20, zdir='y', offset=ymax, # cmap=cmap, alpha = alpha)#, vmin = bottom)#, vmax = top, vmin = bottom) s = self.ax3d.contourf(self.x, self.y, Hmag, NL, zdir='z', offset=bottom - (top - bottom) * 0.05, cmap=cmap, alpha=alpha) # plot colorbar for suitable plot modes if self.chkColBar.isChecked() and (self.chkContour2D.isChecked() or str(self.cmbMode3D.currentText()) in {'Contour', 'Surf'}): self.colb = self.mplwidget.fig.colorbar(m_cb, ax=self.ax3d, shrink=0.8, aspect=20, pad=0.02, fraction=0.08) #---------------------------------------------------------------------- ## Set view limits and labels #---------------------------------------------------------------------- if not self.mplwidget.mplToolbar.lock_zoom: self.ax3d.set_xlim3d(self.xmin, self.xmax) self.ax3d.set_ylim3d(self.ymin, self.ymax) self.ax3d.set_zlim3d(bottom, top) else: self._restore_axes() self.ax3d.set_xlabel('Re') #(fb.fil[0]['plt_fLabel']) self.ax3d.set_ylabel( 'Im' ) #(r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega}) / T_S \; \rightarrow $') # self.ax3d.set_zlabel(r'$|H(z)|\; \rightarrow $') self.ax3d.set_title( r'3D-Plot of $|H(\mathrm{e}^{\mathrm{j} \Omega})|$ and $|H(z)|$') self.mplwidget.redraw()
def __init__(self, parent=None, DEBUG=False): # default parent = None -> top Window super(PlotImpz, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG self.ACTIVE_3D = False self.lblLog = QtGui.QLabel(self) self.lblLog.setText("Log.") self.chkLog = QtGui.QCheckBox(self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Show logarithmic impulse / step response.") self.chkLog.setChecked(False) self.lblLogBottom = QtGui.QLabel("Log. bottom:") self.ledLogBottom = QtGui.QLineEdit(self) self.ledLogBottom.setText("-80") self.ledLogBottom.setToolTip("Minimum display value for log. scale.") self.lblNPoints = QtGui.QLabel("<i>N</i> =") self.ledNPoints = QtGui.QLineEdit(self) self.ledNPoints.setText("0") self.ledNPoints.setToolTip("Number of points to calculate and display.\n" "N = 0 chooses automatically.") self.lblStep = QtGui.QLabel("Step Response") self.chkStep = QtGui.QCheckBox() self.chkStep.setChecked(False) self.chkStep.setToolTip("Show step response instead of impulse response.") # self.lblLockZoom = QtGui.QLabel("Lock Zoom") # self.chkLockZoom = QtGui.QCheckBox() # self.chkLockZoom.setChecked(False) # self.chkLockZoom.setToolTip("Lock zoom to current setting.") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.lblLog) self.layHChkBoxes.addWidget(self.chkLog) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblLogBottom) self.layHChkBoxes.addWidget(self.ledLogBottom) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblStep) self.layHChkBoxes.addWidget(self.chkStep) # self.layHChkBoxes.addStretch(1) # self.layHChkBoxes.addWidget(self.lblLockZoom) # self.layHChkBoxes.addWidget(self.chkLockZoom) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblNPoints) self.layHChkBoxes.addWidget(self.ledNPoints) self.layHChkBoxes.addStretch(10) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.layVMainMpl1.addWidget(self.mplwidget) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) # self.setLayout(self.layHChkBoxes) self.draw() # calculate and draw |H(f)| # #============================================= # # Signals & Slots # #============================================= self.chkLog.clicked.connect(self.draw) self.chkStep.clicked.connect(self.draw) self.ledNPoints.editingFinished.connect(self.draw) self.ledLogBottom.editingFinished.connect(self.draw)
class PlotImpz(QWidget): def __init__(self, parent): super(PlotImpz, self).__init__(parent) self.ACTIVE_3D = False self.stim_freq = 0.02 self._init_UI() def _init_UI(self): self.chkLog = QCheckBox(self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("<span>Logarithmic scale for y-axis.</span>") self.chkLog.setChecked(False) self.lblLog = QLabel("Log. y-axis", self) self.lblLogBottom = QLabel("Bottom = ", self) self.ledLogBottom = QLineEdit(self) self.ledLogBottom.setText("-80") self.ledLogBottom.setToolTip("Minimum display value for log. scale.") self.lbldB = QLabel("dB") self.lblPltStim = QLabel(self) self.lblPltStim.setText("Stimulus:") self.chkPltStim = QCheckBox("Show", self) self.chkPltStim.setChecked(False) self.chkPltStim.setToolTip("Show stimulus signal.") self.lblStimulus = QLabel("Type = ", self) self.cmbStimulus = QComboBox(self) self.cmbStimulus.addItems([ "Pulse", "Step", "StepErr", "Sine", "Rect", "Saw", "RandN", "RandU" ]) self.cmbStimulus.setToolTip("Select stimulus type.") self.lblFreq = QLabel("<i>f</i> =", self) self.ledFreq = QLineEdit(self) self.ledFreq.setText(str(self.stim_freq)) self.ledFreq.setToolTip("Stimulus frequency.") self.lblFreqUnit = QLabel("f_S", self) self.lblNPoints = QLabel("<i>N</i> =", self) self.ledNPoints = QLineEdit(self) self.ledNPoints.setText("0") self.ledNPoints.setToolTip( "Number of points to calculate and display.\n" "N = 0 selects automatically.") layHControls = QHBoxLayout() layHControls.addWidget(self.lblNPoints) layHControls.addWidget(self.ledNPoints) layHControls.addStretch(2) layHControls.addWidget(self.chkLog) layHControls.addWidget(self.lblLog) layHControls.addStretch(1) layHControls.addWidget(self.lblLogBottom) layHControls.addWidget(self.ledLogBottom) layHControls.addWidget(self.lbldB) layHControls.addStretch(2) layHControls.addWidget(self.lblPltStim) layHControls.addWidget(self.chkPltStim) layHControls.addStretch(1) layHControls.addWidget(self.lblStimulus) layHControls.addWidget(self.cmbStimulus) layHControls.addStretch(2) layHControls.addWidget(self.lblFreq) layHControls.addWidget(self.ledFreq) layHControls.addWidget(self.lblFreqUnit) layHControls.addStretch(10) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self.draw) self.ledNPoints.editingFinished.connect(self.draw) self.ledLogBottom.editingFinished.connect(self.draw) self.chkPltStim.clicked.connect(self.draw) # self.cmbStimulus.currentIndexChanged.connect(self.draw) self.cmbStimulus.activated.connect(self.draw) self.ledFreq.installEventFilter(self) self.draw() # initial calculation and drawing #------------------------------------------------------------------------------ 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 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: self.stim_freq = safe_eval(source.text()) / fb.fil[0]['f_S'] self.spec_edited = False # reset flag self.draw() if isinstance(source, QLineEdit): # could be extended for other widgets if event.type() == QEvent.FocusIn: self.spec_edited = False self.load_dict() 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 source.setText( str(params['FMT'].format(self.stim_freq * fb.fil[0]['f_S']))) elif event.type() == QEvent.FocusOut: _store_entry(source) source.setText( str(params['FMT'].format(self.stim_freq * fb.fil[0]['f_S']))) # Call base class method to continue normal event processing: return super(PlotImpz, self).eventFilter(source, event) #------------------------------------------------------------- def load_dict(self): """ Reload textfields from filter dictionary 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_dict() 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 logger.debug("exec load_dict") if not self.ledFreq.hasFocus(): # widget has no focus, round the display self.ledFreq.setText( str(params['FMT'].format(self.stim_freq * fb.fil[0]['f_S']))) else: # widget has focus, show full precision self.ledFreq.setText(str(self.stim_freq * fb.fil[0]['f_S'])) #------------------------------------------------------------------------------ def _init_axes(self): # clear the axes and (re)draw the plot # try: self.mplwidget.fig.delaxes(self.ax_r) self.mplwidget.fig.delaxes(self.ax_i) except (KeyError, AttributeError, UnboundLocalError): pass if self.cmplx: self.ax_r = self.mplwidget.fig.add_subplot(211) self.ax_r.clear() self.ax_i = self.mplwidget.fig.add_subplot(212, sharex=self.ax_r) self.ax_i.clear() else: self.ax_r = self.mplwidget.fig.add_subplot(111) self.ax_r.clear() self.mplwidget.fig.subplots_adjust(hspace=0.5) if self.ACTIVE_3D: # not implemented / tested yet self.ax3d = self.mplwidget.fig.add_subplot(111, projection='3d') #------------------------------------------------------------------------------ def update_view(self): """ place holder; should update only the limits without recalculating the impulse respons """ self.draw() #------------------------------------------------------------------------------ def draw(self): self.frmControls.setEnabled(self.mplwidget.mplToolbar.enabled) if self.mplwidget.mplToolbar.enabled: self.draw_impz() #------------------------------------------------------------------------------ def draw_impz(self): """ (Re-)calculate h[n] and draw the figure """ log = self.chkLog.isChecked() stim = str(self.cmbStimulus.currentText()) periodic_sig = stim in {"Sine", "Rect", "Saw"} self.lblLogBottom.setVisible(log) self.ledLogBottom.setVisible(log) self.lbldB.setVisible(log) self.lblFreq.setVisible(periodic_sig) self.ledFreq.setVisible(periodic_sig) self.lblFreqUnit.setVisible(periodic_sig) self.lblFreqUnit.setText(rt_label(fb.fil[0]['freq_specs_unit'])) self.load_dict() self.bb = np.asarray(fb.fil[0]['ba'][0]) self.aa = np.asarray(fb.fil[0]['ba'][1]) if min(len(self.aa), len(self.bb)) < 2: logger.error('No proper filter coefficients: len(a), len(b) < 2 !') return sos = np.asarray(fb.fil[0]['sos']) self.f_S = fb.fil[0]['f_S'] N = self.calc_n_points(abs(int(self.ledNPoints.text()))) t = np.linspace(0, N / self.f_S, N, endpoint=False) # calculate h[n] if stim == "Pulse": x = np.zeros(N) x[0] = 1.0 # create dirac impulse as input signal title_str = r'Impulse Response' H_str = r'$h[n]$' elif stim == "Step": x = np.ones(N) # create step function title_str = r'Step Response' H_str = r'$h_{\epsilon}[n]$' elif stim == "StepErr": x = np.ones(N) # create step function title_str = r'Settling Error' H_str = r'$h_{\epsilon, \infty} - h_{\epsilon}[n]$' elif stim in {"Sine", "Rect"}: x = np.sin(2 * np.pi * t * float(self.ledFreq.text())) if stim == "Sine": title_str = r'Transient Response to Sine Signal' H_str = r'$y_{\sin}[n]$' else: x = np.sign(x) title_str = r'Transient Response to Rect. Signal' H_str = r'$y_{rect}[n]$' elif stim == "Saw": x = sig.sawtooth(t * (float(self.ledFreq.text()) * 2 * np.pi)) title_str = r'Transient Response to Sawtooth Signal' H_str = r'$y_{saw}[n]$' elif stim == "RandN": x = np.random.randn(N) title_str = r'Transient Response to Gaussian Noise' H_str = r'$y_{gauss}[n]$' elif stim == "RandU": x = np.random.rand(N) - 0.5 title_str = r'Transient Response to Uniform Noise' H_str = r'$y_{uni}[n]$' else: logger.error('Unknown stimulus "{0}"'.format(stim)) return if len(sos) > 0: # has second order sections h = sig.sosfilt(sos, x) dc = sig.freqz(self.bb, self.aa, [0]) else: # no second order sections for current filter h = sig.lfilter(self.bb, self.aa, x) dc = sig.freqz(self.bb, self.aa, [0]) if stim == "StepErr": h = h - abs(dc[1]) # subtract DC value from response self.cmplx = np.any(np.iscomplex(h)) if self.cmplx: h_i = h.imag h = h.real H_i_str = r'$\Im\{$' + H_str + '$\}$' H_str = r'$\Re\{$' + H_str + '$\}$' if log: bottom = float(self.ledLogBottom.text()) H_str = r'$|$ ' + H_str + '$|$ in dB' h = np.maximum(20 * np.log10(abs(h)), bottom) if self.cmplx: h_i = np.maximum(20 * np.log10(abs(h_i)), bottom) H_i_str = r'$\log$ ' + H_i_str + ' in dB' else: bottom = 0 self._init_axes() #================ Main Plotting Routine ========================= [ml, sl, bl] = self.ax_r.stem(t, h, bottom=bottom, markerfmt='o', label='$h[n]$') stem_fmt = params['mpl_stimuli'] if self.chkPltStim.isChecked(): [ms, ss, bs] = self.ax_r.stem(t, x, bottom=bottom, label='Stim.', **stem_fmt) ms.set_mfc(stem_fmt['mfc']) ms.set_mec(stem_fmt['mec']) ms.set_ms(stem_fmt['ms']) ms.set_alpha(stem_fmt['alpha']) for stem in ss: stem.set_linewidth(stem_fmt['lw']) stem.set_color(stem_fmt['mec']) stem.set_alpha(stem_fmt['alpha']) bs.set_visible(False) # invisible bottomline expand_lim(self.ax_r, 0.02) self.ax_r.set_title(title_str) if self.cmplx: [ml_i, sl_i, bl_i] = self.ax_i.stem(t, h_i, bottom=bottom, markerfmt='d', label='$h_i[n]$') self.ax_i.set_xlabel(fb.fil[0]['plt_tLabel']) # self.ax_r.get_xaxis().set_ticklabels([]) # removes both xticklabels # plt.setp(ax_r.get_xticklabels(), visible=False) # is shorter but imports matplotlib, set property directly instead: [label.set_visible(False) for label in self.ax_r.get_xticklabels()] self.ax_r.set_ylabel(H_str + r'$\rightarrow $') self.ax_i.set_ylabel(H_i_str + r'$\rightarrow $') else: self.ax_r.set_xlabel(fb.fil[0]['plt_tLabel']) self.ax_r.set_ylabel(H_str + r'$\rightarrow $') if self.ACTIVE_3D: # not implemented / tested yet # plotting the stems for i in range(len(t)): self.ax3d.plot([t[i], t[i]], [h[i], h[i]], [0, h_i[i]], '-', linewidth=2, alpha=.5) # plotting a circle on the top of each stem self.ax3d.plot(t, h, h_i, 'o', markersize=8, markerfacecolor='none', label='$h[n]$') self.ax3d.set_xlabel('x') self.ax3d.set_ylabel('y') self.ax3d.set_zlabel('z') self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw() #------------------------------------------------------------------------------ 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 # TODO: IIR: more intelligent algorithm needed else: N = min(len(self.bb), 100) # FIR: N = number of coefficients (max. 100) else: N = N_user return N
def __init__(self, parent): super(PlotHf, self).__init__(parent) modes = ['| H |', 're{H}', 'im{H}'] self.cmbShowH = QComboBox(self) self.cmbShowH.addItems(modes) self.cmbShowH.setObjectName("cmbUnitsH") self.cmbShowH.setToolTip("Show magnitude, real / imag. part of H or H \n" "without linear phase (acausal system).") self.cmbShowH.setCurrentIndex(0) self.lblIn = QLabel("in", self) units = ['dB', 'V', 'W', 'Auto'] self.cmbUnitsA = QComboBox(self) self.cmbUnitsA.addItems(units) self.cmbUnitsA.setObjectName("cmbUnitsA") self.cmbUnitsA.setToolTip("<span>Set unit for y-axis:\n" "dB is attenuation (positive values), V and W are gain (less than 1).</span>") self.cmbUnitsA.setCurrentIndex(0) self.cmbShowH.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbUnitsA.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.chkLinphase = QCheckBox("Zero phase", self) self.chkLinphase.setToolTip("<span>Subtract linear phase according to filter order.\n" "Attention: this makes no sense for a non-linear phase system!</span>") self.lblInset = QLabel("Inset", self) self.cmbInset = QComboBox(self) self.cmbInset.addItems(['off', 'edit', 'fixed']) self.cmbInset.setObjectName("cmbInset") self.cmbInset.setToolTip("Display/edit second inset plot") self.cmbInset.setCurrentIndex(0) self.inset_idx = 0 # store previous index for comparison self.chkSpecs = QCheckBox("Show Specs", self) self.chkSpecs.setChecked(False) self.chkSpecs.setToolTip("Display filter specs as hatched regions") self.chkPhase = QCheckBox("Phase", self) self.chkPhase.setToolTip("Overlay phase") layHControls = QHBoxLayout() layHControls.addStretch(10) layHControls.addWidget(self.cmbShowH) layHControls.addWidget(self.lblIn) layHControls.addWidget(self.cmbUnitsA) layHControls.addStretch(1) layHControls.addWidget(self.chkLinphase) layHControls.addStretch(1) layHControls.addWidget(self.lblInset) layHControls.addWidget(self.cmbInset) layHControls.addStretch(1) layHControls.addWidget(self.chkSpecs) layHControls.addStretch(1) layHControls.addWidget(self.chkPhase) layHControls.addStretch(10) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- # This is the plot pane widget, encompassing the other widgets self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # calculate and draw |H(f)| #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.cmbUnitsA.currentIndexChanged.connect(self.draw) self.cmbShowH.currentIndexChanged.connect(self.draw) self.chkLinphase.clicked.connect(self.draw) self.cmbInset.currentIndexChanged.connect(self.draw_inset) self.chkSpecs.clicked.connect(self.draw) self.chkPhase.clicked.connect(self.draw) self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui)
class PlotPhi(QWidget): def __init__(self, parent): super(PlotPhi, self).__init__(parent) self.cmbUnitsPhi = QComboBox(self) units = ["rad", "rad/pi", "deg"] scales = [1., 1./ np.pi, 180./np.pi] for unit, scale in zip(units, scales): self.cmbUnitsPhi.addItem(unit, scale) self.cmbUnitsPhi.setObjectName("cmbUnitsA") self.cmbUnitsPhi.setToolTip("Set unit for phase.") self.cmbUnitsPhi.setCurrentIndex(0) self.cmbUnitsPhi.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.chkWrap = QCheckBox("Wrapped Phase", self) self.chkWrap.setChecked(False) self.chkWrap.setToolTip("Plot phase wrapped to +/- pi") layHControls = QHBoxLayout() # layHControls.addStretch(10) layHControls.addWidget(self.cmbUnitsPhi) layHControls.addWidget(self.chkWrap) layHControls.addStretch(10) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # initial drawing # #============================================= # # Signals & Slots # #============================================= self.chkWrap.clicked.connect(self.draw) self.cmbUnitsPhi.currentIndexChanged.connect(self.draw) self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui) #------------------------------------------------------------------------------ def init_axes(self): """ Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() self.ax.get_xaxis().tick_bottom() # remove axis ticks on top self.ax.get_yaxis().tick_left() # remove axis ticks right #------------------------------------------------------------------------------ def calc_hf(self): """ (Re-)Calculate the complex frequency response H(f) """ # calculate H_cplx(W) (complex) for W = 0 ... 2 pi: self.W, self.H_cmplx = calc_Hcomplex(fb.fil[0], params['N_FFT'], wholeF=True) # replace nan and inf by finite values, otherwise np.unwrap yields # an array full of nans self.H_cmplx = np.nan_to_num(self.H_cmplx) #------------------------------------------------------------------------------ def enable_ui(self): """ Triggered when the toolbar is enabled or disabled """ self.frmControls.setEnabled(self.mplwidget.mplToolbar.enabled) if self.mplwidget.mplToolbar.enabled: self.init_axes() self.draw() #------------------------------------------------------------------------------ def draw(self): """ Main entry point: Re-calculate |H(f)| and draw the figure if enabled """ if self.mplwidget.mplToolbar.enabled: self.calc_hf() self.update_view() #------------------------------------------------------------------------------ def update_view(self): """ Draw the figure with new limits, scale etc without recalculating H(f) """ self.unitPhi = self.cmbUnitsPhi.currentText() f_S2 = fb.fil[0]['f_S'] / 2. #========= select frequency range to be displayed ===================== #=== shift, scale and select: W -> F, H_cplx -> H_c F = self.W * f_S2 / np.pi if fb.fil[0]['freqSpecsRangeType'] == 'sym': # shift H and F by f_S/2 H = np.fft.fftshift(self.H_cmplx) F -= f_S2 elif fb.fil[0]['freqSpecsRangeType'] == 'half': # only use the first half of H and F H = self.H_cmplx[0:params['N_FFT']//2] F = F[0:params['N_FFT']//2] else: # fb.fil[0]['freqSpecsRangeType'] == 'whole' # use H and F as calculated H = self.H_cmplx y_str = r'$\angle H(\mathrm{e}^{\mathrm{j} \Omega})$ in ' if self.unitPhi == 'rad': y_str += 'rad ' + r'$\rightarrow $' scale = 1. elif self.unitPhi == 'rad/pi': y_str += 'rad' + r'$ / \pi \;\rightarrow $' scale = 1./ np.pi else: y_str += 'deg ' + r'$\rightarrow $' scale = 180./np.pi fb.fil[0]['plt_phiLabel'] = y_str fb.fil[0]['plt_phiUnit'] = self.unitPhi if self.chkWrap.isChecked(): phi_plt = np.angle(H) * scale else: phi_plt = np.unwrap(np.angle(H)) * scale #--------------------------------------------------------- self.ax.clear() # need to clear, doesn't overwrite line_phi, = self.ax.plot(F, phi_plt) #--------------------------------------------------------- self.ax.set_title(r'Phase Frequency Response') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(y_str) self.ax.set_xlim(fb.fil[0]['freqSpecsRange']) self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw()
def __init__(self, parent = None, DEBUG = False): # default parent = None -> top Window super(PlotHf, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG modes = ['| H |', 're{H}', 'im{H}'] self.cmbShowH = QtGui.QComboBox(self) self.cmbShowH.addItems(modes) self.cmbShowH.setObjectName("cmbUnitsH") self.cmbShowH.setToolTip("Show magnitude, real / imag. part of H or H \n" "without linear phase (acausal system).") self.cmbShowH.setCurrentIndex(0) self.lblIn = QtGui.QLabel("in") units = ["dB", "V", "W"] self.cmbUnitsA = QtGui.QComboBox(self) self.cmbUnitsA.addItems(units) self.cmbUnitsA.setObjectName("cmbUnitsA") self.cmbUnitsA.setToolTip("Set unit for y-axis:\n" "dB is attenuation (positive values)\nV and W are less than 1.") self.cmbUnitsA.setCurrentIndex(0) self.cmbShowH.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.cmbUnitsA.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.lblLinphase = QtGui.QLabel("Acausal system") self.chkLinphase = QtGui.QCheckBox() self.chkLinphase.setToolTip("Remove linear phase according to filter order.\n" "Attention: this makes no sense for a non-linear phase system!") self.lblInset = QtGui.QLabel("Inset") self.cmbInset = QtGui.QComboBox(self) self.cmbInset.addItems(['off', 'edit', 'fixed']) self.cmbInset.setObjectName("cmbInset") self.cmbInset.setToolTip("Display/edit second inset plot") self.cmbInset.setCurrentIndex(0) self.inset_idx = 0 # store previous index for comparison self.lblSpecs = QtGui.QLabel("Show Specs") self.chkSpecs = QtGui.QCheckBox() self.chkSpecs.setChecked(False) self.chkSpecs.setToolTip("Display filter specs as hatched regions") self.lblPhase = QtGui.QLabel("Phase") self.chkPhase = QtGui.QCheckBox() self.chkPhase.setToolTip("Overlay phase") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) self.layHChkBoxes.addWidget(self.cmbShowH) self.layHChkBoxes.addWidget(self.lblIn) self.layHChkBoxes.addWidget(self.cmbUnitsA) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblLinphase) self.layHChkBoxes.addWidget(self.chkLinphase) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblInset) self.layHChkBoxes.addWidget(self.cmbInset) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblSpecs) self.layHChkBoxes.addWidget(self.chkSpecs) self.layHChkBoxes.addStretch(1) self.layHChkBoxes.addWidget(self.lblPhase) self.layHChkBoxes.addWidget(self.chkPhase) self.layHChkBoxes.addStretch(10) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.layVMainMpl1.addWidget(self.mplwidget) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.draw() # calculate and draw |H(f)| # #============================================= # # Signals & Slots # #============================================= self.cmbUnitsA.currentIndexChanged.connect(self.draw) self.cmbShowH.currentIndexChanged.connect(self.draw) self.chkLinphase.clicked.connect(self.draw) self.cmbInset.currentIndexChanged.connect(self.draw_inset) self.chkSpecs.clicked.connect(self.draw) self.chkPhase.clicked.connect(self.draw_phase)
class PlotPZ(QWidget): def __init__(self, parent): super(PlotPZ, self).__init__(parent) layHControls = QHBoxLayout() layHControls.addStretch(10) # This widget encompasses all control subwidgets: # self.frmControls = QFrame(self) # self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(layHControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self.init_axes() self.draw() # calculate and draw poles and zeros #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui) #------------------------------------------------------------------------------ def init_axes(self): """Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() self.ax.get_xaxis().tick_bottom() # remove axis ticks on top self.ax.get_yaxis().tick_left() # remove axis ticks right #------------------------------------------------------------------------------ def update_specs(self): """ Draw the figure with new limits, scale etcs without recalculating H(f) -- not yet implemented, just use draw() for the moment """ self.draw() #------------------------------------------------------------------------------ def enable_ui(self): """ Triggered when the toolbar is enabled or disabled """ # self.frmControls.setEnabled(self.mplwidget.mplToolbar.enabled) # no control widgets if self.mplwidget.mplToolbar.enabled: self.init_axes() self.draw() #------------------------------------------------------------------------------ def draw(self): if self.mplwidget.mplToolbar.enabled: self.draw_pz() #------------------------------------------------------------------------------ def draw_pz(self): """ (re)draw P/Z plot """ p_marker = params['P_Marker'] z_marker = params['Z_Marker'] zpk = fb.fil[0]['zpk'] # add antiCausals if they exist (must take reciprocal to plot) if 'rpk' in fb.fil[0]: zA = fb.fil[0]['zpk'][0] zA = np.conj(1./zA) pA = fb.fil[0]['zpk'][1] pA = np.conj(1./pA) zC = np.append(zpk[0],zA) pC = np.append(zpk[1],pA) zpk[0] = zC zpk[1] = pC self.ax.clear() [z,p,k] = self.zplane(z = zpk[0], p = zpk[1], k = zpk[2], plt_ax = self.ax, verbose = False, mps = p_marker[0], mpc = p_marker[1], mzs = z_marker[0], mzc = z_marker[1]) self.ax.set_title(r'Pole / Zero Plot') self.ax.set_xlabel('Real axis') self.ax.set_ylabel('Imaginary axis') self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw() #------------------------------------------------------------------------------ def zplane(self, b=None, a=1, z=None, p=None, k =1, pn_eps=1e-3, analog=False, plt_ax = None, verbose=False, style='square', anaCircleRad=0, lw=2, mps = 10, mzs = 10, mpc = 'r', mzc = 'b', plabel = '', zlabel = ''): """ Plot the poles and zeros in the complex z-plane either from the coefficients (`b,`a) of a discrete transfer function `H`(`z`) (zpk = False) or directly from the zeros and poles (z,p) (zpk = True). When only b is given, an FIR filter with all poles at the origin is assumed. Parameters ---------- b : array_like Numerator coefficients (transversal part of filter) When b is not None, poles and zeros are determined from the coefficients b and a a : array_like (optional, default = 1 for FIR-filter) Denominator coefficients (recursive part of filter) z : array_like, default = None Zeros When b is None, poles and zeros are taken directly from z and p p : array_like, default = None Poles analog : boolean (default: False) When True, create a P/Z plot suitable for the s-plane, i.e. suppress the unit circle (unless anaCircleRad > 0) and scale the plot for a good display of all poles and zeros. pn_eps : float (default : 1e-2) Tolerance for separating close poles or zeros plt_ax : handle to axes for plotting (default: None) When no axes is specified, the current axes is determined via plt.gca() pltLib : string (default: 'matplotlib') Library for plotting the P/Z plane. Currently, only matplotlib is implemented. When pltLib = 'none' or when matplotlib is not available, only pass the poles / zeros and their multiplicity verbose : boolean (default: False) When verbose == True, print poles / zeros and their multiplicity. style : string (default: 'square') Style of the plot, for style == 'square' make scale of x- and y- axis equal. mps : integer (default: 10) Size for pole marker mzs : integer (default: 10) Size for zero marker mpc : char (default: 'r') Pole marker colour mzc : char (default: 'b') Zero marker colour lw : integer (default: 2) Linewidth for unit circle plabel, zlabel : string (default: '') This string is passed to the plot command for poles and zeros and can be displayed by legend() Returns ------- z, p, k : ndarray Notes ----- """ # TODO: # - polar option # - add keywords for size, color etc. of markers and circle -> **kwargs # - add option for multi-dimensional arrays and zpk data # make sure that all inputs are arrays b = np.atleast_1d(b) a = np.atleast_1d(a) z = np.atleast_1d(z) # make sure that p, z are arrays p = np.atleast_1d(p) if b.any(): # coefficients were specified if len(b) < 2 and len(a) < 2: logger.error('No proper filter coefficients: both b and a are scalars!') return z, p, k # The coefficients are less than 1, normalize the coefficients if np.max(b) > 1: kn = np.max(b) b = b / float(kn) else: kn = 1. if np.max(a) > 1: kd = np.max(a) a = a / abs(kd) else: kd = 1. # Calculate the poles, zeros and scaling factor p = np.roots(a) z = np.roots(b) k = kn/kd elif not (len(p) or len(z)): # P/Z were specified logger.error('Either b,a or z,p must be specified!') return z, p, k # find multiple poles and zeros and their multiplicities if len(p) < 2: # single pole, [None] or [0] if not p or p == 0: # only zeros, create equal number of poles at origin p = np.array(0,ndmin=1) # num_p = np.atleast_1d(len(z)) else: num_p = [1.] # single pole != 0 else: #p, num_p = sig.signaltools.unique_roots(p, tol = pn_eps, rtype='avg') p, num_p = unique_roots(p, tol = pn_eps, rtype='avg') # p = np.array(p); num_p = np.ones(len(p)) if len(z) > 0: z, num_z = unique_roots(z, tol = pn_eps, rtype='avg') # z = np.array(z); num_z = np.ones(len(z)) #z, num_z = sig.signaltools.unique_roots(z, tol = pn_eps, rtype='avg') else: num_z = [] ax = plt_ax#.subplot(111) if analog == False: # create the unit circle for the z-plane uc = patches.Circle((0,0), radius=1, fill=False, color='grey', ls='solid', zorder=1) ax.add_patch(uc) if style == 'square': r = 1.1 ax.axis([-r, r, -r, r], 'equal') ax.axis('equal') # ax.spines['left'].set_position('center') # ax.spines['bottom'].set_position('center') # ax.spines['right'].set_visible(True) # ax.spines['top'].set_visible(True) else: # s-plane if anaCircleRad > 0: # plot a circle with radius = anaCircleRad uc = patches.Circle((0,0), radius=anaCircleRad, fill=False, color='grey', ls='solid', zorder=1) ax.add_patch(uc) # plot real and imaginary axis ax.axhline(lw=2, color = 'k', zorder=1) ax.axvline(lw=2, color = 'k', zorder=1) # Plot the zeros ax.scatter(z.real, z.imag, s=mzs*mzs, zorder=2, marker = 'o', facecolor = 'none', edgecolor = mzc, lw = lw, label=zlabel) # t1 = plt.plot(z.real, z.imag, 'go', ms=10, label=label) # plt.setp( t1, markersize=mzs, markeredgewidth=2.0, # markeredgecolor=mzc, markerfacecolor='none') # Plot the poles ax.scatter(p.real, p.imag, s=mps*mps, zorder=2, marker='x', color=mpc, lw=lw, label=plabel) # Print multiplicity of poles / zeros for i in range(len(z)): if verbose == True: print('z', i, z[i], num_z[i]) if num_z[i] > 1: ax.text(np.real(z[i]), np.imag(z[i]),' (' + str(num_z[i]) +')',va = 'bottom') for i in range(len(p)): if verbose == True: print('p', i, p[i], num_p[i]) if num_p[i] > 1: ax.text(np.real(p[i]), np.imag(p[i]), ' (' + str(num_p[i]) +')',va = 'bottom') # increase distance between ticks and labels # to give some room for poles and zeros for tick in ax.get_xaxis().get_major_ticks(): tick.set_pad(12.) tick.label1 = tick._get_text1() for tick in ax.get_yaxis().get_major_ticks(): tick.set_pad(12.) tick.label1 = tick._get_text1() xl = ax.get_xlim(); Dx = max(abs(xl[1]-xl[0]), 0.05) yl = ax.get_ylim(); Dy = max(abs(yl[1]-yl[0]), 0.05) ax.set_xlim((xl[0]-Dx*0.05, max(xl[1]+Dx*0.05,0))) ax.set_ylim((yl[0]-Dy*0.05, yl[1] + Dy*0.05)) # print(ax.get_xlim(),ax.get_ylim()) return z, p, k
class Plot3D(QWidget): """ Class for various 3D-plots: - lin / log line plot of H(f) - lin / log surf plot of H(z) - optional display of poles / zeros """ def __init__(self, parent): super(Plot3D, self).__init__(parent) self.zmin = 0 self.zmax = 4 self.zmin_dB = -80 self.cmap_default = 'RdYlBu_r' self._construct_UI() def _construct_UI(self): self.chkLog = QCheckBox("Log.", self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Logarithmic scale") self.chkLog.setChecked(False) self.chkPolar = QCheckBox("Polar", self) self.chkPolar.setObjectName("chkPolar") self.chkPolar.setToolTip("Polar coordinate range") self.chkPolar.setChecked(False) self.lblBottom = QLabel("Bottom =", self) self.ledBottom = QLineEdit(self) self.ledBottom.setObjectName("ledBottom") self.ledBottom.setText(str(self.zmin)) self.ledBottom.setToolTip("Minimum display value.") self.lblTop = QLabel("Top:", self) self.ledTop = QLineEdit(self) self.ledTop.setObjectName("ledTop") self.ledTop.setText(str(self.zmax)) self.ledTop.setToolTip("Maximum display value.") self.chkUC = QCheckBox("UC", self) self.chkUC.setObjectName("chkUC") self.chkUC.setToolTip("Plot unit circle") self.chkUC.setChecked(True) self.chkPZ = QCheckBox("P/Z", self) self.chkPZ.setObjectName("chkPZ") self.chkPZ.setToolTip("Plot poles and zeros") self.chkPZ.setChecked(True) self.chkHf = QCheckBox("H(f)", self) self.chkHf.setObjectName("chkHf") self.chkHf.setToolTip("Plot H(f) along the unit circle") self.chkHf.setChecked(True) modes = ['None', 'Mesh', 'Surf', 'Contour'] self.cmbMode3D = QComboBox(self) self.cmbMode3D.addItems(modes) self.cmbMode3D.setObjectName("cmbShow3D") self.cmbMode3D.setToolTip("Select 3D-plot mode.") self.cmbMode3D.setCurrentIndex(0) self.cmbMode3D.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.chkColormap_r = QCheckBox("reverse", self) self.chkColormap_r.setToolTip("reverse colormap") self.chkColormap_r.setChecked(True) self.cmbColormap = QComboBox(self) self._init_cmb_colormap() self.cmbColormap.setToolTip("Select colormap") self.chkColBar = QCheckBox("Colorbar", self) self.chkColBar.setObjectName("chkColBar") self.chkColBar.setToolTip("Show colorbar") self.chkColBar.setChecked(False) self.chkLighting = QCheckBox("Lighting", self) self.chkLighting.setObjectName("chkLighting") self.chkLighting.setToolTip("Enable light source") self.chkLighting.setChecked(False) self.lblAlpha = QLabel("Alpha", self) self.diaAlpha = QDial(self) self.diaAlpha.setRange(0., 10.) self.diaAlpha.setValue(10) self.diaAlpha.setTracking(False) # produce less events when turning self.diaAlpha.setFixedHeight(30) self.diaAlpha.setFixedWidth(30) self.diaAlpha.setWrapping(False) self.diaAlpha.setToolTip("<span>Set transparency for surf and contour plots.</span>") self.lblHatch = QLabel("Stride", self) self.diaHatch = QDial(self) self.diaHatch.setRange(0., 9.) self.diaHatch.setValue(5) self.diaHatch.setTracking(False) # produce less events when turning self.diaHatch.setFixedHeight(30) self.diaHatch.setFixedWidth(30) self.diaHatch.setWrapping(False) self.diaHatch.setToolTip("Set line density for various plots.") self.chkContour2D = QCheckBox("Contour2D", self) self.chkContour2D.setObjectName("chkContour2D") self.chkContour2D.setToolTip("Plot 2D-contours at z =0") self.chkContour2D.setChecked(False) #---------------------------------------------------------------------- # LAYOUT for UI widgets #---------------------------------------------------------------------- layGControls = QGridLayout() layGControls.addWidget(self.chkLog, 0, 0) layGControls.addWidget(self.chkPolar, 1, 0) layGControls.addWidget(self.lblTop, 0, 2) layGControls.addWidget(self.lblBottom, 1, 2) layGControls.addWidget(self.ledTop, 0, 4) layGControls.addWidget(self.ledBottom, 1, 4) layGControls.setColumnStretch(5,1) layGControls.addWidget(self.chkUC, 0, 6) layGControls.addWidget(self.chkHf, 1, 6) layGControls.addWidget(self.chkPZ, 0, 8) layGControls.addWidget(self.cmbMode3D, 0, 10) layGControls.addWidget(self.chkContour2D, 1, 10) layGControls.addWidget(self.cmbColormap, 0,12,1,1) layGControls.addWidget(self.chkColormap_r, 1,12) layGControls.addWidget(self.chkLighting, 0, 14) layGControls.addWidget(self.chkColBar, 1, 14) layGControls.addWidget(self.lblAlpha, 0, 15) layGControls.addWidget(self.diaAlpha, 0, 16) layGControls.addWidget(self.lblHatch, 1, 15) layGControls.addWidget(self.diaHatch, 1, 16) # This widget encompasses all control subwidgets self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layGControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- # This is the plot pane widget, encompassing the other widgets self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self._init_grid() # initialize grid and do initial plot #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self._log_clicked) self.ledBottom.editingFinished.connect(self._log_clicked) self.ledTop.editingFinished.connect(self._log_clicked) self.chkPolar.clicked.connect(self._init_grid) self.chkUC.clicked.connect(self.draw) self.chkHf.clicked.connect(self.draw) self.chkPZ.clicked.connect(self.draw) self.cmbMode3D.currentIndexChanged.connect(self.draw) self.chkColBar.clicked.connect(self.draw) self.cmbColormap.currentIndexChanged.connect(self.draw) self.chkColormap_r.clicked.connect(self._init_cmb_colormap) self.chkLighting.clicked.connect(self.draw) self.diaAlpha.valueChanged.connect(self.draw) self.diaHatch.valueChanged.connect(self.draw) self.chkContour2D.clicked.connect(self.draw) self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui) self.mplwidget.mplToolbar.enable_update(state = False) # disable initially #------------------------------------------------------------------------------ def _init_cmb_colormap(self): """ (Re-)Load combobox with available colormaps""" if self.chkColormap_r.isChecked(): cmap_list = [m for m in cm.datad if m.endswith("_r")] else: cmap_list = [m for m in cm.datad if not m.endswith("_r")] # *_r colormaps reverse the color order cmap_list.sort() self.cmbColormap.blockSignals(True) # don't send signal "indexChanged" self.cmbColormap.clear() self.cmbColormap.addItems(cmap_list) self.cmbColormap.blockSignals(False) idx = self.cmbColormap.findText(self.cmap_default) if idx == -1: idx = 0 self.cmbColormap.setCurrentIndex(idx) #------------------------------------------------------------------------------ def _init_grid(self): """ Initialize (x,y,z) coordinate grid + (re)draw plot.""" phi_UC = np.linspace(0, 2*pi, 400, endpoint=True) # angles for unit circle self.xy_UC = np.exp(1j * phi_UC) # x,y coordinates of unity circle steps = 100 # number of steps for x, y, r, phi # self.xmin = -1.5; self.xmax = 1.5 # cartesian range limits self.ymin = -1.5; self.ymax = 1.5 rmin = 0; rmax = self.xmin # polar range limits # Calculate grids for 3D-Plots dr = rmax / steps * 2 # grid size for polar range dx = (self.xmax - self.xmin) / steps dy = (self.ymax - self.ymin) / steps # grid size cartesian range if self.chkPolar.isChecked(): # # Plot circular range in 3D-Plot [r, phi] = np.meshgrid(np.arange(rmin, rmax, dr), np.linspace(0, 2 * pi, steps, endpoint=True)) self.x = r * cos(phi) self.y = r * sin(phi) else: # cartesian grid [self.x, self.y] = np.meshgrid(np.arange(self.xmin, self.xmax, dx), np.arange(self.ymin, self.ymax, dy)) self.z = self.x + 1j*self.y # create coordinate grid for complex plane self.draw() # initial plot #------------------------------------------------------------------------------ def init_axes(self): """ Initialize and clear the axes to get rid of colorbar The azimuth / elevation / distance settings of the camera are restored after clearing the axes. See http://stackoverflow.com/questions/4575588/matplotlib-3d-plot-with-pyqt4-in-qtabwidget-mplwidget """ self._save_axes() self.mplwidget.fig.clf() # needed to get rid of colorbar self.ax3d = self.mplwidget.fig.add_subplot(111, projection='3d') self._restore_axes() #------------------------------------------------------------------------------ def _save_axes(self): """ Store x/y/z - limits and camera position """ try: self.azim = self.ax3d.azim self.elev = self.ax3d.elev self.dist = self.ax3d.dist self.xlim = self.ax3d.get_xlim3d() self.ylim = self.ax3d.get_ylim3d() self.zlim = self.ax3d.get_zlim3d() except AttributeError: # not yet initialized, set standard values self.azim = -65 self.elev = 30 self.dist = 10 self.xlim = (self.xmin, self.xmax) self.ylim = (self.ymin, self.ymax) self.zlim = (self.zmin, self.zmax) #------------------------------------------------------------------------------ def _restore_axes(self): """ Restore x/y/z - limits and camera position """ if self.mplwidget.mplToolbar.lock_zoom: self.ax3d.set_xlim3d(self.xlim) self.ax3d.set_ylim3d(self.ylim) self.ax3d.set_zlim3d(self.zlim) self.ax3d.azim = self.azim self.ax3d.elev = self.elev self.ax3d.dist = self.dist #------------------------------------------------------------------------------ def _log_clicked(self): """ Change scale and settings to log / lin when log setting is changed Update min / max settings when lineEdits have been edited """ self.log = self.chkLog.isChecked() if self.sender().objectName() == 'chkLog': # clicking chkLog triggered the slot if self.log: self.ledBottom.setText(str(self.zmin_dB)) self.zmax_dB = np.round(20 * log10(self.zmax), 2) self.ledTop.setText(str(self.zmax_dB)) else: self.ledBottom.setText(str(self.zmin)) self.zmax = np.round(10**(self.zmax_dB / 20), 2) self.ledTop.setText(str(self.zmax)) else: # finishing a lineEdit field triggered the slot if self.log: self.zmin_dB = safe_eval(self.ledBottom.text(), self.zmin_dB, return_type='float') self.ledBottom.setText(str(self.zmin_dB)) self.zmax_dB = safe_eval(self.ledTop.text(), self.zmax_dB, return_type='float') self.ledTop.setText(str(self.zmax_dB)) else: self.zmin = safe_eval(self.ledBottom.text(), self.zmin, return_type='float') self.ledBottom.setText(str(self.zmin)) self.zmax = safe_eval(self.ledTop.text(), self.zmax, return_type='float') self.ledTop.setText(str(self.zmax)) self.draw() #------------------------------------------------------------------------------ def enable_ui(self): """ Triggered when the toolbar is enabled or disabled """ self.frmControls.setEnabled(self.mplwidget.mplToolbar.enabled) if self.mplwidget.mplToolbar.enabled: self.init_axes() self.draw() #------------------------------------------------------------------------------ def draw(self): """ Main drawing entry point: Check whether updating is enabled in the toolbar and then perform the actual plot """ if self.mplwidget.mplToolbar.enabled: self.draw_3d() #------------------------------------------------------------------------------ def draw_3d(self): """ Draw various 3D plots """ self.init_axes() bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] zz = np.array(fb.fil[0]['zpk'][0]) pp = np.array(fb.fil[0]['zpk'][1]) wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' # not used f_S = fb.fil[0]['f_S'] N_FFT = params['N_FFT'] alpha = self.diaAlpha.value()/10. cmap = cm.get_cmap(str(self.cmbColormap.currentText())) # Number of Lines /step size for H(f) stride, mesh, contour3d: stride = 10 - self.diaHatch.value() NL = 3 * self.diaHatch.value() + 5 surf_enabled = qget_cmb_box(self.cmbMode3D, data=False) in {'Surf', 'Contour'} self.cmbColormap.setEnabled(surf_enabled) self.chkColormap_r.setEnabled(surf_enabled) self.chkLighting.setEnabled(surf_enabled) self.chkColBar.setEnabled(surf_enabled) self.diaAlpha.setEnabled(surf_enabled or self.chkContour2D.isChecked()) #cNorm = colors.Normalize(vmin=0, vmax=values[-1]) #scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet) #----------------------------------------------------------------------------- # Calculate H(w) along the upper half of unity circle #----------------------------------------------------------------------------- [w, H] = sig.freqz(bb, aa, worN=N_FFT, whole=True) H = np.nan_to_num(H) # replace nans and inf by finite numbers H_abs = abs(H) H_max = max(H_abs) H_min = min(H_abs) #f = w / (2 * pi) * f_S # translate w to absolute frequencies #F_min = f[np.argmin(H_abs)] plevel_rel = 1.05 # height of plotted pole position relative to zmax zlevel_rel = 0.1 # height of plotted zero position relative to zmax if self.chkLog.isChecked(): # logarithmic scale bottom = np.floor(max(self.zmin_dB, 20*log10(H_min)) / 10) * 10 top = self.zmax_dB top_bottom = top - bottom zlevel = bottom - top_bottom * zlevel_rel if self.cmbMode3D.currentText() == 'None': # "Poleposition" for H(f) plot only plevel_top = 2 * bottom - zlevel # height of displayed pole position plevel_btm = bottom else: plevel_top = top + top_bottom * (plevel_rel - 1) plevel_btm = top else: # linear scale bottom = max(self.zmin, H_min) # min. display value top = self.zmax # max. display value top_bottom = top - bottom # top = zmax_rel * H_max # calculate display top from max. of H(f) zlevel = bottom + top_bottom * zlevel_rel # height of displayed zero position if self.cmbMode3D.currentText() == 'None': # "Poleposition" for H(f) plot only #H_max = np.clip(max(H_abs), 0, self.zmax) # make height of displayed poles same to zeros plevel_top = bottom + top_bottom * zlevel_rel plevel_btm = bottom else: plevel_top = plevel_rel * top plevel_btm = top # calculate H(jw)| along the unity circle and |H(z)|, each clipped # between bottom and top H_UC = H_mag(bb, aa, self.xy_UC, top, H_min=bottom, log=self.chkLog.isChecked()) Hmag = H_mag(bb, aa, self.z, top, H_min=bottom, log=self.chkLog.isChecked()) #=============================================================== ## plot Unit Circle (UC) #=============================================================== if self.chkUC.isChecked(): # Plot unit circle and marker at (1,0): self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, ones(len(self.xy_UC)) * bottom, lw=2, color='k') self.ax3d.plot([0.97, 1.03], [0, 0], [bottom, bottom], lw=2, color='k') #=============================================================== ## plot ||H(f)| along unit circle as 3D-lineplot #=============================================================== if self.chkHf.isChecked(): self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, H_UC, alpha = 0.5) # draw once more as dashed white line to improve visibility self.ax3d.plot(self.xy_UC.real, self.xy_UC.imag, H_UC, 'w--') if stride < 10: # plot thin vertical line every stride points on the UC for k in range(len(self.xy_UC[::stride])): self.ax3d.plot([self.xy_UC.real[::stride][k], self.xy_UC.real[::stride][k]], [self.xy_UC.imag[::stride][k], self.xy_UC.imag[::stride][k]], [np.ones(len(self.xy_UC[::stride]))[k]*bottom, H_UC[::stride][k]], linewidth=1, color=(0.5, 0.5, 0.5)) #=============================================================== ## plot Poles and Zeros #=============================================================== if self.chkPZ.isChecked(): PN_SIZE = 8 # size of P/N symbols # Plot zero markers at |H(z_i)| = zlevel with "stems": self.ax3d.plot(zz.real, zz.imag, ones(len(zz)) * zlevel, 'o', markersize=PN_SIZE, markeredgecolor='blue', markeredgewidth=2.0, markerfacecolor='none') for k in range(len(zz)): # plot zero "stems" self.ax3d.plot([zz[k].real, zz[k].real], [zz[k].imag, zz[k].imag], [bottom, zlevel], linewidth=1, color='b') # Plot the poles at |H(z_p)| = plevel with "stems": self.ax3d.plot(np.real(pp), np.imag(pp), plevel_top, 'x', markersize=PN_SIZE, markeredgewidth=2.0, markeredgecolor='red') for k in range(len(pp)): # plot pole "stems" self.ax3d.plot([pp[k].real, pp[k].real], [pp[k].imag, pp[k].imag], [plevel_btm, plevel_top], linewidth=1, color='r') #=============================================================== ## 3D-Plots of |H(z)| clipped between |H(z)| = top #=============================================================== m_cb = cm.ScalarMappable(cmap=cmap) # normalized proxy object that is mappable m_cb.set_array(Hmag) # for colorbar #--------------------------------------------------------------- ## 3D-mesh plot #--------------------------------------------------------------- if self.cmbMode3D.currentText() == 'Mesh': # fig_mlab = mlab.figure(fgcolor=(0., 0., 0.), bgcolor=(1, 1, 1)) # self.ax3d.set_zlim(0,2) self.ax3d.plot_wireframe(self.x, self.y, Hmag, rstride=5, cstride=stride, linewidth=1, color='gray') #--------------------------------------------------------------- ## 3D-surface plot #--------------------------------------------------------------- # http://stackoverflow.com/questions/28232879/phong-shading-for-shiny-python-3d-surface-plots elif self.cmbMode3D.currentText() == 'Surf': if MLAB: ## Mayavi surf = mlab.surf(self.x, self.y, H_mag, colormap='RdYlBu', warp_scale='auto') # Change the visualization parameters. surf.actor.property.interpolation = 'phong' surf.actor.property.specular = 0.1 surf.actor.property.specular_power = 5 # s = mlab.contour_surf(self.x, self.y, Hmag, contour_z=0) mlab.show() else: if self.chkLighting.isChecked(): ls = LightSource(azdeg=0, altdeg=65) # Create light source object rgb = ls.shade(Hmag, cmap=cmap) # Shade data, creating an rgb array cmap_surf = None else: rgb = None cmap_surf = cmap # s = self.ax3d.plot_surface(self.x, self.y, Hmag, # alpha=OPT_3D_ALPHA, rstride=1, cstride=1, cmap=cmap, # linewidth=0, antialiased=False, shade=True, facecolors = rgb) # s.set_edgecolor('gray') s = self.ax3d.plot_surface(self.x, self.y, Hmag, alpha=alpha, rstride=1, cstride=1, linewidth=0, antialiased=False, facecolors=rgb, cmap=cmap_surf, shade=True) s.set_edgecolor(None) #--------------------------------------------------------------- ## 3D-Contour plot #--------------------------------------------------------------- elif self.cmbMode3D.currentText() == 'Contour': s = self.ax3d.contourf3D(self.x, self.y, Hmag, NL, alpha=alpha, cmap=cmap) #--------------------------------------------------------------- ## 2D-Contour plot # TODO: 2D contour plots do not plot correctly together with 3D plots in # current matplotlib 1.4.3 -> disable them for now # TODO: zdir = x / y delivers unexpected results -> rather plot max(H) # along the other axis? # TODO: colormap is created depending on the zdir = 'z' contour plot # -> set limits of (all) other plots manually? if self.chkContour2D.isChecked(): # self.ax3d.contourf(x, y, Hmag, 20, zdir='x', offset=xmin, # cmap=cmap, alpha = alpha)#, vmin = bottom)#, vmax = top, vmin = bottom) # self.ax3d.contourf(x, y, Hmag, 20, zdir='y', offset=ymax, # cmap=cmap, alpha = alpha)#, vmin = bottom)#, vmax = top, vmin = bottom) s = self.ax3d.contourf(self.x, self.y, Hmag, NL, zdir='z', offset=bottom - (top - bottom) * 0.05, cmap=cmap, alpha=alpha) # plot colorbar for suitable plot modes if self.chkColBar.isChecked() and (self.chkContour2D.isChecked() or str(self.cmbMode3D.currentText()) in {'Contour', 'Surf'}): self.colb = self.mplwidget.fig.colorbar(m_cb, ax=self.ax3d, shrink=0.8, aspect=20, pad=0.02, fraction=0.08) #---------------------------------------------------------------------- ## Set view limits and labels #---------------------------------------------------------------------- if not self.mplwidget.mplToolbar.lock_zoom: self.ax3d.set_xlim3d(self.xmin, self.xmax) self.ax3d.set_ylim3d(self.ymin, self.ymax) self.ax3d.set_zlim3d(bottom, top) else: self._restore_axes() self.ax3d.set_xlabel('Re')#(fb.fil[0]['plt_fLabel']) self.ax3d.set_ylabel('Im') #(r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega}) / T_S \; \rightarrow $') # self.ax3d.set_zlabel(r'$|H(z)|\; \rightarrow $') self.ax3d.set_title(r'3D-Plot of $|H(\mathrm{e}^{\mathrm{j} \Omega})|$ and $|H(z)|$') self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw()
class PlotTauG(QtGui.QMainWindow): def __init__(self, parent = None, DEBUG = False): # default parent = None -> top Window super(PlotTauG, self).__init__(parent) # initialize QWidget base class # QtGui.QMainWindow.__init__(self) # alternative syntax self.DEBUG = DEBUG # # self.lblWrap = QtGui.QLabel("Wrapped Phase") # self.btnWrap = QtGui.QCheckBox() # self.btnWrap.setChecked(False) # self.btnWrap.setToolTip("Plot phase wrapped to +/- pi") self.layHChkBoxes = QtGui.QHBoxLayout() self.layHChkBoxes.addStretch(10) # self.layHChkBoxes.addWidget(self.cmbUnitsPhi) self.mplwidget = MplWidget() # self.mplwidget.setParent(self) self.mplwidget.layVMainMpl.addLayout(self.layHChkBoxes) # self.mplwidget.setFocus() # make this the central widget, taking all available space: self.setCentralWidget(self.mplwidget) self.initAxes() self.draw() # calculate and draw phi(f) # #============================================= # # Signals & Slots # #============================================= # self.btnWrap.clicked.connect(self.draw) # self.cmbUnitsPhi.currentIndexChanged.connect(self.draw) def initAxes(self): """Initialize and clear the axes """ # self.ax = self.mplwidget.ax self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() self.ax.set_title(r'Group Delay $ \tau_g$') self.ax.hold(False) #plt.gca().cla() #p.clf() def draw(self): if self.mplwidget.mplToolbar.enable_update: self.draw_taug() def draw_taug(self): """ Draw group delay """ bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] wholeF = fb.fil[0]['freqSpecsRangeType'] != 'half' f_S = fb.fil[0]['f_S'] # scale = self.cmbUnitsPhi.itemData(self.cmbUnitsPhi.currentIndex()) [tau_g, w] = grpdelay(bb,aa, rc.params['N_FFT'], whole = wholeF) F = w / (2 * np.pi) * fb.fil[0]['f_S'] if fb.fil[0]['freqSpecsRangeType'] == 'sym': tau_g = np.fft.fftshift(tau_g) F = F - f_S / 2. self.ax.plot(F, tau_g, label = "Group Delay") self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega}) / T_S \; \rightarrow $') # widen limits to suppress numerical inaccuracies when tau_g = constant self.ax.axis(fb.fil[0]['freqSpecsRange'] + [max(min(tau_g)-0.5,0), max(tau_g) + 0.5]) self.mplwidget.redraw()
def _construct_UI(self): self.chkLog = QCheckBox("Log.", self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Logarithmic scale") self.chkLog.setChecked(False) self.chkPolar = QCheckBox("Polar", self) self.chkPolar.setObjectName("chkPolar") self.chkPolar.setToolTip("Polar coordinate range") self.chkPolar.setChecked(False) self.lblBottom = QLabel("Bottom =", self) self.ledBottom = QLineEdit(self) self.ledBottom.setObjectName("ledBottom") self.ledBottom.setText(str(self.zmin)) self.ledBottom.setToolTip("Minimum display value.") self.lblTop = QLabel("Top:", self) self.ledTop = QLineEdit(self) self.ledTop.setObjectName("ledTop") self.ledTop.setText(str(self.zmax)) self.ledTop.setToolTip("Maximum display value.") self.chkUC = QCheckBox("UC", self) self.chkUC.setObjectName("chkUC") self.chkUC.setToolTip("Plot unit circle") self.chkUC.setChecked(True) self.chkPZ = QCheckBox("P/Z", self) self.chkPZ.setObjectName("chkPZ") self.chkPZ.setToolTip("Plot poles and zeros") self.chkPZ.setChecked(True) self.chkHf = QCheckBox("H(f)", self) self.chkHf.setObjectName("chkHf") self.chkHf.setToolTip("Plot H(f) along the unit circle") self.chkHf.setChecked(True) modes = ['None', 'Mesh', 'Surf', 'Contour'] self.cmbMode3D = QComboBox(self) self.cmbMode3D.addItems(modes) self.cmbMode3D.setObjectName("cmbShow3D") self.cmbMode3D.setToolTip("Select 3D-plot mode.") self.cmbMode3D.setCurrentIndex(0) self.cmbMode3D.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.chkColormap_r = QCheckBox("reverse", self) self.chkColormap_r.setToolTip("reverse colormap") self.chkColormap_r.setChecked(True) self.cmbColormap = QComboBox(self) self._init_cmb_colormap() self.cmbColormap.setToolTip("Select colormap") self.chkColBar = QCheckBox("Colorbar", self) self.chkColBar.setObjectName("chkColBar") self.chkColBar.setToolTip("Show colorbar") self.chkColBar.setChecked(False) self.chkLighting = QCheckBox("Lighting", self) self.chkLighting.setObjectName("chkLighting") self.chkLighting.setToolTip("Enable light source") self.chkLighting.setChecked(False) self.lblAlpha = QLabel("Alpha", self) self.diaAlpha = QDial(self) self.diaAlpha.setRange(0., 10.) self.diaAlpha.setValue(10) self.diaAlpha.setTracking(False) # produce less events when turning self.diaAlpha.setFixedHeight(30) self.diaAlpha.setFixedWidth(30) self.diaAlpha.setWrapping(False) self.diaAlpha.setToolTip( "Set transparency for surf and 3D-contour plot.") self.lblHatch = QLabel("Stride", self) self.diaHatch = QDial(self) self.diaHatch.setRange(0., 9.) self.diaHatch.setValue(5) self.diaHatch.setTracking(False) # produce less events when turning self.diaHatch.setFixedHeight(30) self.diaHatch.setFixedWidth(30) self.diaHatch.setWrapping(False) self.diaHatch.setToolTip("Set hatching for H(jw).") self.chkContour2D = QCheckBox("Contour2D", self) self.chkContour2D.setObjectName("chkContour2D") self.chkContour2D.setToolTip("Plot 2D-contours at z =0") self.chkContour2D.setChecked(False) #---------------------------------------------------------------------- # LAYOUT for UI widgets #---------------------------------------------------------------------- layGControls = QGridLayout() layGControls.addWidget(self.chkLog, 0, 0) layGControls.addWidget(self.chkPolar, 1, 0) layGControls.addWidget(self.lblTop, 0, 2) layGControls.addWidget(self.lblBottom, 1, 2) layGControls.addWidget(self.ledTop, 0, 4) layGControls.addWidget(self.ledBottom, 1, 4) layGControls.setColumnStretch(5, 1) layGControls.addWidget(self.chkUC, 0, 6) layGControls.addWidget(self.chkHf, 1, 6) layGControls.addWidget(self.chkPZ, 0, 8) layGControls.addWidget(self.cmbMode3D, 0, 10) layGControls.addWidget(self.chkContour2D, 1, 10) layGControls.addWidget(self.cmbColormap, 0, 12, 1, 1) layGControls.addWidget(self.chkColormap_r, 1, 12) layGControls.addWidget(self.chkLighting, 0, 14) layGControls.addWidget(self.chkColBar, 1, 14) layGControls.addWidget(self.lblAlpha, 0, 15) layGControls.addWidget(self.diaAlpha, 0, 16) layGControls.addWidget(self.lblHatch, 1, 15) layGControls.addWidget(self.diaHatch, 1, 16) # This widget encompasses all control subwidgets self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layGControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- # This is the plot pane widget, encompassing the other widgets self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self._init_grid() # initialize grid and do initial plot #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self._log_clicked) self.ledBottom.editingFinished.connect(self._log_clicked) self.ledTop.editingFinished.connect(self._log_clicked) self.chkPolar.clicked.connect(self._init_grid) self.chkUC.clicked.connect(self.draw) self.chkHf.clicked.connect(self.draw) self.chkPZ.clicked.connect(self.draw) self.cmbMode3D.currentIndexChanged.connect(self.draw) self.chkColBar.clicked.connect(self.draw) self.cmbColormap.currentIndexChanged.connect(self.draw) self.chkColormap_r.clicked.connect(self._init_cmb_colormap) self.chkLighting.clicked.connect(self.draw) self.diaAlpha.valueChanged.connect(self.draw) self.diaHatch.valueChanged.connect(self.draw) self.chkContour2D.clicked.connect(self.draw) self.mplwidget.mplToolbar.enable_update( state=False) # disable initially
def _construct_UI(self): self.chkLog = QCheckBox("Log.", self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Logarithmic scale") self.chkLog.setChecked(False) self.chkPolar = QCheckBox("Polar", self) self.chkPolar.setObjectName("chkPolar") self.chkPolar.setToolTip("Polar coordinate range") self.chkPolar.setChecked(False) self.lblBottom = QLabel("Bottom =", self) self.ledBottom = QLineEdit(self) self.ledBottom.setObjectName("ledBottom") self.ledBottom.setText(str(self.zmin)) self.ledBottom.setToolTip("Minimum display value.") self.lblTop = QLabel("Top:", self) self.ledTop = QLineEdit(self) self.ledTop.setObjectName("ledTop") self.ledTop.setText(str(self.zmax)) self.ledTop.setToolTip("Maximum display value.") self.chkUC = QCheckBox("UC", self) self.chkUC.setObjectName("chkUC") self.chkUC.setToolTip("Plot unit circle") self.chkUC.setChecked(True) self.chkPZ = QCheckBox("P/Z", self) self.chkPZ.setObjectName("chkPZ") self.chkPZ.setToolTip("Plot poles and zeros") self.chkPZ.setChecked(True) self.chkHf = QCheckBox("H(f)", self) self.chkHf.setObjectName("chkHf") self.chkHf.setToolTip("Plot H(f) along the unit circle") self.chkHf.setChecked(True) modes = ['None', 'Mesh', 'Surf', 'Contour'] self.cmbMode3D = QComboBox(self) self.cmbMode3D.addItems(modes) self.cmbMode3D.setObjectName("cmbShow3D") self.cmbMode3D.setToolTip("Select 3D-plot mode.") self.cmbMode3D.setCurrentIndex(0) self.cmbMode3D.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.chkColormap_r = QCheckBox("reverse", self) self.chkColormap_r.setToolTip("reverse colormap") self.chkColormap_r.setChecked(True) self.cmbColormap = QComboBox(self) self._init_cmb_colormap() self.cmbColormap.setToolTip("Select colormap") self.chkColBar = QCheckBox("Colorbar", self) self.chkColBar.setObjectName("chkColBar") self.chkColBar.setToolTip("Show colorbar") self.chkColBar.setChecked(False) self.chkLighting = QCheckBox("Lighting", self) self.chkLighting.setObjectName("chkLighting") self.chkLighting.setToolTip("Enable light source") self.chkLighting.setChecked(False) self.lblAlpha = QLabel("Alpha", self) self.diaAlpha = QDial(self) self.diaAlpha.setRange(0., 10.) self.diaAlpha.setValue(10) self.diaAlpha.setTracking(False) # produce less events when turning self.diaAlpha.setFixedHeight(30) self.diaAlpha.setFixedWidth(30) self.diaAlpha.setWrapping(False) self.diaAlpha.setToolTip("<span>Set transparency for surf and contour plots.</span>") self.lblHatch = QLabel("Stride", self) self.diaHatch = QDial(self) self.diaHatch.setRange(0., 9.) self.diaHatch.setValue(5) self.diaHatch.setTracking(False) # produce less events when turning self.diaHatch.setFixedHeight(30) self.diaHatch.setFixedWidth(30) self.diaHatch.setWrapping(False) self.diaHatch.setToolTip("Set line density for various plots.") self.chkContour2D = QCheckBox("Contour2D", self) self.chkContour2D.setObjectName("chkContour2D") self.chkContour2D.setToolTip("Plot 2D-contours at z =0") self.chkContour2D.setChecked(False) #---------------------------------------------------------------------- # LAYOUT for UI widgets #---------------------------------------------------------------------- layGControls = QGridLayout() layGControls.addWidget(self.chkLog, 0, 0) layGControls.addWidget(self.chkPolar, 1, 0) layGControls.addWidget(self.lblTop, 0, 2) layGControls.addWidget(self.lblBottom, 1, 2) layGControls.addWidget(self.ledTop, 0, 4) layGControls.addWidget(self.ledBottom, 1, 4) layGControls.setColumnStretch(5,1) layGControls.addWidget(self.chkUC, 0, 6) layGControls.addWidget(self.chkHf, 1, 6) layGControls.addWidget(self.chkPZ, 0, 8) layGControls.addWidget(self.cmbMode3D, 0, 10) layGControls.addWidget(self.chkContour2D, 1, 10) layGControls.addWidget(self.cmbColormap, 0,12,1,1) layGControls.addWidget(self.chkColormap_r, 1,12) layGControls.addWidget(self.chkLighting, 0, 14) layGControls.addWidget(self.chkColBar, 1, 14) layGControls.addWidget(self.lblAlpha, 0, 15) layGControls.addWidget(self.diaAlpha, 0, 16) layGControls.addWidget(self.lblHatch, 1, 15) layGControls.addWidget(self.diaHatch, 1, 16) # This widget encompasses all control subwidgets self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layGControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- # This is the plot pane widget, encompassing the other widgets self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self._init_grid() # initialize grid and do initial plot #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self._log_clicked) self.ledBottom.editingFinished.connect(self._log_clicked) self.ledTop.editingFinished.connect(self._log_clicked) self.chkPolar.clicked.connect(self._init_grid) self.chkUC.clicked.connect(self.draw) self.chkHf.clicked.connect(self.draw) self.chkPZ.clicked.connect(self.draw) self.cmbMode3D.currentIndexChanged.connect(self.draw) self.chkColBar.clicked.connect(self.draw) self.cmbColormap.currentIndexChanged.connect(self.draw) self.chkColormap_r.clicked.connect(self._init_cmb_colormap) self.chkLighting.clicked.connect(self.draw) self.diaAlpha.valueChanged.connect(self.draw) self.diaHatch.valueChanged.connect(self.draw) self.chkContour2D.clicked.connect(self.draw) self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui) self.mplwidget.mplToolbar.enable_update(state = False) # disable initially
def _init_UI(self): self.chkLog = QtGui.QCheckBox(self) self.chkLog.setText("Log.") self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("Logarithmic scale") self.chkLog.setChecked(False) self.chkPolar = QtGui.QCheckBox(self) self.chkPolar.setText("Polar") self.chkPolar.setObjectName("chkPolar") self.chkPolar.setToolTip("Polar coordinates") self.chkPolar.setChecked(False) self.lblBottom = QtGui.QLabel("Bottom =") self.ledBottom = QtGui.QLineEdit(self) self.ledBottom.setObjectName("ledBottom") self.ledBottom.setText(str(self.zmin)) self.ledBottom.setToolTip("Minimum display value.") self.lblTop = QtGui.QLabel("Top:") self.ledTop = QtGui.QLineEdit(self) self.ledTop.setObjectName("ledTop") self.ledTop.setText(str(self.zmax)) self.ledTop.setToolTip("Maximum display value.") # self.ledTop.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) self.chkUC = QtGui.QCheckBox(self) self.chkUC.setText("UC") self.chkUC.setObjectName("chkUC") self.chkUC.setToolTip("Plot unit circle") self.chkUC.setChecked(True) self.chkPZ = QtGui.QCheckBox(self) self.chkPZ.setText("P/Z") self.chkPZ.setObjectName("chkPZ") self.chkPZ.setToolTip("Plot poles and zeros") self.chkPZ.setChecked(True) self.chkHf = QtGui.QCheckBox(self) self.chkHf.setText("H(f)") self.chkHf.setObjectName("chkHf") self.chkHf.setToolTip("Plot H(f) along the unit circle") self.chkHf.setChecked(True) modes = ['None', 'Mesh', 'Surf', 'Contour'] self.cmbMode3D = QtGui.QComboBox(self) self.cmbMode3D.addItems(modes) self.cmbMode3D.setObjectName("cmbShow3D") self.cmbMode3D.setToolTip("Select 3D-plot mode.") self.cmbMode3D.setCurrentIndex(0) self.cmbMode3D.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.chkColormap_r = QtGui.QCheckBox(self) self.chkColormap_r.setText("reverse") self.chkColormap_r.setToolTip("reverse colormap") self.chkColormap_r.setChecked(True) self.cmbColormap = QtGui.QComboBox(self) self._init_cmb_colormap() self.cmbColormap.setToolTip("Select colormap") self.chkColBar = QtGui.QCheckBox(self) self.chkColBar.setText("Colorbar") self.chkColBar.setObjectName("chkColBar") self.chkColBar.setToolTip("Show colorbar") self.chkColBar.setChecked(False) self.chkLighting = QtGui.QCheckBox(self) self.chkLighting.setText("Lighting") self.chkLighting.setObjectName("chkLighting") self.chkLighting.setToolTip("Enable light source") self.chkLighting.setChecked(False) self.lblAlpha = QtGui.QLabel("Alpha") self.diaAlpha = QtGui.QDial(self) self.diaAlpha.setRange(0., 10.) self.diaAlpha.setValue(10) self.diaAlpha.setTracking(False) # produce less events when turning self.diaAlpha.setFixedHeight(30) self.diaAlpha.setFixedWidth(30) self.diaAlpha.setWrapping(False) self.diaAlpha.setToolTip( "Set transparency for surf and 3D-contour plot.") self.lblHatch = QtGui.QLabel("Stride") self.diaHatch = QtGui.QDial(self) self.diaHatch.setRange(0., 9.) self.diaHatch.setValue(5) self.diaHatch.setTracking(False) # produce less events when turning self.diaHatch.setFixedHeight(30) self.diaHatch.setFixedWidth(30) self.diaHatch.setWrapping(False) self.diaHatch.setToolTip("Set hatching for H(jw).") self.chkContour2D = QtGui.QCheckBox(self) self.chkContour2D.setText("Contour2D") self.chkContour2D.setObjectName("chkContour2D") self.chkContour2D.setToolTip("Plot 2D-contours at z =0") self.chkContour2D.setChecked(False) #---------------------------------------------------------------------- # LAYOUT for UI widgets #---------------------------------------------------------------------- spc = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.layGSelect = QtGui.QGridLayout() self.layGSelect.setObjectName('plotSpecSelect') self.layGSelect.addWidget(self.chkLog, 0, 0) self.layGSelect.addWidget(self.chkPolar, 1, 0) self.layGSelect.addWidget(self.lblTop, 0, 2) self.layGSelect.addWidget(self.lblBottom, 1, 2) self.layGSelect.addWidget(self.ledTop, 0, 4) self.layGSelect.addWidget(self.ledBottom, 1, 4) self.layGSelect.addItem(spc, 0, 5) self.layGSelect.addWidget(self.chkUC, 0, 6) self.layGSelect.addWidget(self.chkHf, 1, 6) self.layGSelect.addWidget(self.chkPZ, 0, 8) self.layGSelect.addWidget(self.cmbColormap, 0, 10, 1, 1) self.layGSelect.addWidget(self.chkColormap_r, 1, 10) self.layGSelect.addWidget(self.cmbMode3D, 0, 12) self.layGSelect.addWidget(self.chkContour2D, 1, 12) self.layGSelect.addWidget(self.chkLighting, 0, 14) self.layGSelect.addWidget(self.chkColBar, 1, 14) self.layGSelect.addWidget(self.diaAlpha, 0, 16) self.layGSelect.addWidget(self.lblAlpha, 0, 15) self.layGSelect.addWidget(self.diaHatch, 1, 16) self.layGSelect.addWidget(self.lblHatch, 1, 15) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addLayout(self.layGSelect) self.setLayout(self.mplwidget.layVMainMpl) # self.mplwidget.setFocus() # make this the central widget, taking all available space: # self.setCentralWidget(self.mplwidget) self._init_grid() # initialize grid and do initial plot #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self._log_clicked) self.ledBottom.editingFinished.connect(self._log_clicked) self.ledTop.editingFinished.connect(self._log_clicked) self.chkPolar.clicked.connect(self._init_grid) self.chkUC.clicked.connect(self.draw) self.chkHf.clicked.connect(self.draw) self.chkPZ.clicked.connect(self.draw) self.cmbMode3D.currentIndexChanged.connect(self.draw) self.chkColBar.clicked.connect(self.draw) self.cmbColormap.currentIndexChanged.connect(self.draw) self.chkColormap_r.clicked.connect(self._init_cmb_colormap) self.chkLighting.clicked.connect(self.draw) self.diaAlpha.valueChanged.connect(self.draw) self.diaHatch.valueChanged.connect(self.draw) self.chkContour2D.clicked.connect(self.draw) logger.debug("UI initialized")
class PlotTauG(QWidget): def __init__(self, parent): super(PlotTauG, self).__init__(parent) self.verbose = False # suppress warnings # ============================================================================= # #### not needed at the moment ### # self.chkWarnings = QCheckBox("Enable Warnings", self) # self.chkWarnings.setChecked(False) # self.chkWarnings.setToolTip("Print warnings about singular group delay") # # layHControls = QHBoxLayout() # layHControls.addStretch(10) # layHControls.addWidget(self.chkWarnings) # # # This widget encompasses all control subwidgets: # self.frmControls = QFrame(self) # self.frmControls.setObjectName("frmControls") # self.frmControls.setLayout(layHControls) # # ============================================================================= self.mplwidget = MplWidget(self) # self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # initial drawing of tau_g #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui) #------------------------------------------------------------------------------ def init_axes(self): """ Initialize and clear the axes """ self.ax = self.mplwidget.fig.add_subplot(111) self.ax.clear() self.ax.get_xaxis().tick_bottom() # remove axis ticks on top self.ax.get_yaxis().tick_left() # remove axis ticks right #------------------------------------------------------------------------------ def calc_tau_g(self): """ (Re-)Calculate the complex frequency response H(f) """ bb = fb.fil[0]['ba'][0] aa = fb.fil[0]['ba'][1] # calculate H_cmplx(W) (complex) for W = 0 ... 2 pi: self.W, self.tau_g = grpdelay(bb, aa, params['N_FFT'], whole = True, verbose = self.verbose) # self.chkWarnings.isChecked()) # Zero phase filters have no group delay (Causal+AntiCausal) if 'baA' in fb.fil[0]: self.tau_g = np.zeros(self.tau_g.size) #------------------------------------------------------------------------------ def enable_ui(self): """ Triggered when the toolbar is enabled or disabled """ # self.frmControls.setEnabled(self.mplwidget.mplToolbar.enabled) if self.mplwidget.mplToolbar.enabled: self.init_axes() self.draw() #------------------------------------------------------------------------------ def draw(self): if self.mplwidget.mplToolbar.enabled: self.calc_tau_g() self.update_view() #------------------------------------------------------------------------------ def update_view(self): """ Draw the figure with new limits, scale etc without recalculating H(f) """ #========= select frequency range to be displayed ===================== #=== shift, scale and select: W -> F, H_cplx -> H_c f_S2 = fb.fil[0]['f_S'] / 2. F = self.W * f_S2 / np.pi if fb.fil[0]['freqSpecsRangeType'] == 'sym': # shift tau_g and F by f_S/2 tau_g = np.fft.fftshift(self.tau_g) F -= f_S2 elif fb.fil[0]['freqSpecsRangeType'] == 'half': # only use the first half of H and F tau_g = self.tau_g[0:params['N_FFT']//2] F = F[0:params['N_FFT']//2] else: # fb.fil[0]['freqSpecsRangeType'] == 'whole' # use H and F as calculated tau_g = self.tau_g #================ Main Plotting Routine ========================= #=== clear the axes and (re)draw the plot if fb.fil[0]['freq_specs_unit'] in {'f_S', 'f_Ny'}: tau_str = r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega}) / T_S \; \rightarrow $' else: tau_str = r'$ \tau_g(\mathrm{e}^{\mathrm{j} \Omega})$'\ + ' in ' + fb.fil[0]['plt_tUnit'] + r' $ \rightarrow $' tau_g = tau_g / fb.fil[0]['f_S'] #--------------------------------------------------------- self.ax.clear() # need to clear, doesn't overwrite line_tau_g, = self.ax.plot(F, tau_g, label = "Group Delay") #--------------------------------------------------------- self.ax.set_title(r'Group Delay $ \tau_g$') self.ax.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax.set_ylabel(tau_str) # widen y-limits to suppress numerical inaccuracies when tau_g = constant self.ax.set_ylim([max(min(tau_g)-0.5,0), max(tau_g) + 0.5]) self.ax.set_xlim(fb.fil[0]['freqSpecsRange']) self.redraw() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw()
def _init_UI(self): self.chkLog = QCheckBox(self) self.chkLog.setObjectName("chkLog") self.chkLog.setToolTip("<span>Logarithmic scale for y-axis.</span>") self.chkLog.setChecked(False) self.lblLog = QLabel("Log. y-axis", self) self.lblLogBottom = QLabel("Bottom = ", self) self.ledLogBottom = QLineEdit(self) self.ledLogBottom.setText("-80") self.ledLogBottom.setToolTip("Minimum display value for log. scale.") self.lbldB = QLabel("dB") self.lblPltStim = QLabel(self) self.lblPltStim.setText("Stimulus:") self.chkPltStim = QCheckBox("Show", self) self.chkPltStim.setChecked(False) self.chkPltStim.setToolTip("Show stimulus signal.") self.lblStimulus = QLabel("Type = ", self) self.cmbStimulus = QComboBox(self) self.cmbStimulus.addItems([ "Pulse", "Step", "StepErr", "Sine", "Rect", "Saw", "RandN", "RandU" ]) self.cmbStimulus.setToolTip("Select stimulus type.") self.lblFreq = QLabel("<i>f</i> =", self) self.ledFreq = QLineEdit(self) self.ledFreq.setText(str(self.stim_freq)) self.ledFreq.setToolTip("Stimulus frequency.") self.lblFreqUnit = QLabel("f_S", self) self.lblNPoints = QLabel("<i>N</i> =", self) self.ledNPoints = QLineEdit(self) self.ledNPoints.setText("0") self.ledNPoints.setToolTip( "Number of points to calculate and display.\n" "N = 0 selects automatically.") layHControls = QHBoxLayout() layHControls.addWidget(self.lblNPoints) layHControls.addWidget(self.ledNPoints) layHControls.addStretch(2) layHControls.addWidget(self.chkLog) layHControls.addWidget(self.lblLog) layHControls.addStretch(1) layHControls.addWidget(self.lblLogBottom) layHControls.addWidget(self.ledLogBottom) layHControls.addWidget(self.lbldB) layHControls.addStretch(2) layHControls.addWidget(self.lblPltStim) layHControls.addWidget(self.chkPltStim) layHControls.addStretch(1) layHControls.addWidget(self.lblStimulus) layHControls.addWidget(self.cmbStimulus) layHControls.addStretch(2) layHControls.addWidget(self.lblFreq) layHControls.addWidget(self.ledFreq) layHControls.addWidget(self.lblFreqUnit) layHControls.addStretch(10) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.chkLog.clicked.connect(self.draw) self.ledNPoints.editingFinished.connect(self.draw) self.ledLogBottom.editingFinished.connect(self.draw) self.chkPltStim.clicked.connect(self.draw) # self.cmbStimulus.currentIndexChanged.connect(self.draw) self.cmbStimulus.activated.connect(self.draw) self.ledFreq.installEventFilter(self) self.draw() # initial calculation and drawing
def __init__(self, parent): super(PlotHf, self).__init__(parent) modes = ['| H |', 're{H}', 'im{H}'] self.cmbShowH = QComboBox(self) self.cmbShowH.addItems(modes) self.cmbShowH.setObjectName("cmbUnitsH") self.cmbShowH.setToolTip( "Show magnitude, real / imag. part of H or H \n" "without linear phase (acausal system).") self.cmbShowH.setCurrentIndex(0) self.lblIn = QLabel("in", self) units = ['dB', 'V', 'W', 'Auto'] self.cmbUnitsA = QComboBox(self) self.cmbUnitsA.addItems(units) self.cmbUnitsA.setObjectName("cmbUnitsA") self.cmbUnitsA.setToolTip( "<span>Set unit for y-axis:\n" "dB is attenuation (positive values), V and W are gain (less than 1).</span>" ) self.cmbUnitsA.setCurrentIndex(0) self.cmbShowH.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbUnitsA.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.chkLinphase = QCheckBox("Zero phase", self) self.chkLinphase.setToolTip( "<span>Subtract linear phase according to filter order.\n" "Attention: this makes no sense for a non-linear phase system!</span>" ) self.lblInset = QLabel("Inset", self) self.cmbInset = QComboBox(self) self.cmbInset.addItems(['off', 'edit', 'fixed']) self.cmbInset.setObjectName("cmbInset") self.cmbInset.setToolTip("Display/edit second inset plot") self.cmbInset.setCurrentIndex(0) self.inset_idx = 0 # store previous index for comparison self.chkSpecs = QCheckBox("Show Specs", self) self.chkSpecs.setChecked(False) self.chkSpecs.setToolTip("Display filter specs as hatched regions") self.chkPhase = QCheckBox("Phase", self) self.chkPhase.setToolTip("Overlay phase") layHControls = QHBoxLayout() layHControls.addStretch(10) layHControls.addWidget(self.cmbShowH) layHControls.addWidget(self.lblIn) layHControls.addWidget(self.cmbUnitsA) layHControls.addStretch(1) layHControls.addWidget(self.chkLinphase) layHControls.addStretch(1) layHControls.addWidget(self.lblInset) layHControls.addWidget(self.cmbInset) layHControls.addStretch(1) layHControls.addWidget(self.chkSpecs) layHControls.addStretch(1) layHControls.addWidget(self.chkPhase) layHControls.addStretch(10) # This widget encompasses all control subwidgets: self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # mplwidget #---------------------------------------------------------------------- # This is the plot pane widget, encompassing the other widgets self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) self.setLayout(self.mplwidget.layVMainMpl) self.init_axes() self.draw() # calculate and draw |H(f)| #---------------------------------------------------------------------- # SIGNALS & SLOTs #---------------------------------------------------------------------- self.cmbUnitsA.currentIndexChanged.connect(self.draw) self.cmbShowH.currentIndexChanged.connect(self.draw) self.chkLinphase.clicked.connect(self.draw) self.cmbInset.currentIndexChanged.connect(self.draw_inset) self.chkSpecs.clicked.connect(self.draw) self.chkPhase.clicked.connect(self.draw) self.mplwidget.mplToolbar.sigEnabled.connect(self.enable_ui)