Einführung
In diesem Lab lernst du, wie du mithilfe von Matplotlib in Python eine einfache grafische Benutzeroberfläche (GUI) erstellst. Genauer gesagt wirst du eine Fourier-Demo erstellen, die zwei Wellenformen in der Frequenz- und Zeitdomäne anzeigt. Du wirst in der Lage sein, die Frequenz und die Amplitude der Wellenformen durch Klicken und Ziehen auf dem Graphen anzupassen.
Tipps für die virtuelle Maschine (VM)
Nachdem der Start der VM abgeschlossen ist, klicke in der oberen linken Ecke, um zur Registerkarte Notebook zu wechseln und Jupyter Notebook für die Übung zu nutzen.
Manchmal musst du einige Sekunden warten, bis Jupyter Notebook vollständig geladen ist. Die Validierung von Vorgängen kann aufgrund der Einschränkungen von Jupyter Notebook nicht automatisiert werden.
Wenn du während des Lernens Probleme stellst, kannst du Labby gerne fragen. Gib nach der Sitzung Feedback, und wir werden das Problem für dich prompt beheben.
Bibliotheken importieren
Der erste Schritt besteht darin, die erforderlichen Bibliotheken zu importieren. Wir werden Matplotlib, wxPython und NumPy verwenden. Matplotlib ist eine Plotbibliothek für Python, wxPython ist ein GUI-Toolkit für Python und NumPy ist eine Bibliothek für numerische Berechnungen mit Python.
import wx
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
Knob- und Param-Klassen definieren
Der nächste Schritt besteht darin, die Knob- und Param-Klassen zu definieren. Diese Klassen werden verwendet, um die Frequenz und die Amplitude der Wellenformen in der grafischen Benutzeroberfläche (GUI) zu steuern.
class Knob:
"""
Knob - einfache Klasse mit einer "setKnob"-Methode.
Eine Knob-Instanz ist an eine Param-Instanz angehängt, z.B. param.attach(knob)
Die Basisklasse dient der Dokumentation.
"""
def setKnob(self, value):
pass
class Param:
"""
Die Idee der "Param"-Klasse ist, dass ein bestimmter Parameter in der GUI möglicherweise
mehrere Knöpfe hat, die ihn sowohl steuern als auch den Zustand des Parameters widerspiegeln, z.B.
ein Schieberegler, Text und Ziehen können alle den Wert der Frequenz in
der Wellenform dieses Beispiels ändern.
Die Klasse ermöglicht eine saubere Möglichkeit, die anderen Knöpfe zu aktualisieren/"zurückzuführen", wenn
einer von ihnen geändert wird. Außerdem behandelt diese Klasse die Min/Max-Beschränkungen für alle
die Knöpfe.
Idee - Knob-Liste - in der "set"-Methode wird auch das Knob-Objekt übergeben
- die anderen Knöpfe in der Knob-Liste haben eine "set"-Methode, die
für die anderen aufgerufen wird.
"""
def __init__(self, initialValue=None, minimum=0., maximum=1.):
self.minimum = minimum
self.maximum = maximum
if initialValue!= self.constrain(initialValue):
raise ValueError('illegal initial value')
self.value = initialValue
self.knobs = []
def attach(self, knob):
self.knobs += [knob]
def set(self, value, knob=None):
self.value = value
self.value = self.constrain(value)
for feedbackKnob in self.knobs:
if feedbackKnob!= knob:
feedbackKnob.setKnob(self.value)
return self.value
def constrain(self, value):
if value <= self.minimum:
value = self.minimum
if value >= self.maximum:
value = self.maximum
return value
SliderGroup-Klasse definieren
Die SliderGroup-Klasse wird in der grafischen Benutzeroberfläche (GUI) einen Schieberegler und ein Textfeld erstellen, um die Frequenz und die Amplitude der Wellenformen anzupassen.
class SliderGroup(Knob):
def __init__(self, parent, label, param):
self.sliderLabel = wx.StaticText(parent, label=label)
self.sliderText = wx.TextCtrl(parent, -1, style=wx.TE_PROCESS_ENTER)
self.slider = wx.Slider(parent, -1)
self.slider.SetRange(0, int(param.maximum * 1000))
self.setKnob(param.value)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.sliderLabel, 0,
wx.EXPAND | wx.ALL,
border=2)
sizer.Add(self.sliderText, 0,
wx.EXPAND | wx.ALL,
border=2)
sizer.Add(self.slider, 1, wx.EXPAND)
self.sizer = sizer
self.slider.Bind(wx.EVT_SLIDER, self.sliderHandler)
self.sliderText.Bind(wx.EVT_TEXT_ENTER, self.sliderTextHandler)
self.param = param
self.param.attach(self)
def sliderHandler(self, event):
value = event.GetInt() / 1000.
self.param.set(value)
def sliderTextHandler(self, event):
value = float(self.sliderText.GetValue())
self.param.set(value)
def setKnob(self, value):
self.sliderText.SetValue(f'{value:g}')
self.slider.SetValue(int(value * 1000))
FourierDemoFrame-Klasse definieren
Die FourierDemoFrame-Klasse wird die grafische Benutzeroberfläche (GUI) mit wxPython und Matplotlib erstellen.
class FourierDemoFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
panel = wx.Panel(self)
## die GUI-Elemente erstellen
self.createCanvas(panel)
self.createSliders(panel)
## sie in einem Sizer für die Layout-Organisation platzieren
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas, 1, wx.EXPAND)
sizer.Add(self.frequencySliderGroup.sizer, 0,
wx.EXPAND | wx.ALL, border=5)
sizer.Add(self.amplitudeSliderGroup.sizer, 0,
wx.EXPAND | wx.ALL, border=5)
panel.SetSizer(sizer)
def createCanvas(self, parent):
self.lines = []
self.figure = Figure()
self.canvas = FigureCanvas(parent, -1, self.figure)
self.canvas.callbacks.connect('button_press_event', self.mouseDown)
self.canvas.callbacks.connect('motion_notify_event', self.mouseMotion)
self.canvas.callbacks.connect('button_release_event', self.mouseUp)
self.state = ''
self.mouseInfo = (None, None, None, None)
self.f0 = Param(2., minimum=0., maximum=6.)
self.A = Param(1., minimum=0.01, maximum=2.)
self.createPlots()
self.f0.attach(self)
self.A.attach(self)
def createSliders(self, panel):
self.frequencySliderGroup = SliderGroup(
panel,
label='Frequency f0:',
param=self.f0)
self.amplitudeSliderGroup = SliderGroup(panel, label=' Amplitude a:',
param=self.A)
def mouseDown(self, event):
if self.lines[0].contains(event)[0]:
self.state = 'frequency'
elif self.lines[1].contains(event)[0]:
self.state = 'time'
else:
self.state = ''
self.mouseInfo = (event.xdata, event.ydata,
max(self.f0.value,.1),
self.A.value)
def mouseMotion(self, event):
if self.state == '':
return
x, y = event.xdata, event.ydata
if x is None: ## außerhalb der Achsen
return
x0, y0, f0Init, AInit = self.mouseInfo
self.A.set(AInit + (AInit * (y - y0) / y0), self)
if self.state == 'frequency':
self.f0.set(f0Init + (f0Init * (x - x0) / x0))
elif self.state == 'time':
if (x - x0) / x0!= -1.:
self.f0.set(1. / (1. / f0Init + (1. / f0Init * (x - x0) / x0)))
def mouseUp(self, event):
self.state = ''
def createPlots(self):
self.subplot1, self.subplot2 = self.figure.subplots(2)
x1, y1, x2, y2 = self.compute(self.f0.value, self.A.value)
color = (1., 0., 0.)
self.lines += self.subplot1.plot(x1, y1, color=color, linewidth=2)
self.lines += self.subplot2.plot(x2, y2, color=color, linewidth=2)
self.subplot1.set_title(
"Click and drag waveforms to change frequency and amplitude",
fontsize=12)
self.subplot1.set_ylabel("Frequency Domain Waveform X(f)", fontsize=8)
self.subplot1.set_xlabel("frequency f", fontsize=8)
self.subplot2.set_ylabel("Time Domain Waveform x(t)", fontsize=8)
self.subplot2.set_xlabel("time t", fontsize=8)
self.subplot1.set_xlim([-6, 6])
self.subplot1.set_ylim([0, 1])
self.subplot2.set_xlim([-2, 2])
self.subplot2.set_ylim([-2, 2])
self.subplot1.text(0.05,.95,
r'$X(f) = \mathcal{F}\{x(t)\}$',
verticalalignment='top',
transform=self.subplot1.transAxes)
self.subplot2.text(0.05,.95,
r'$x(t) = a \cdot \cos(2\pi f_0 t) e^{-\pi t^2}$',
verticalalignment='top',
transform=self.subplot2.transAxes)
def compute(self, f0, A):
f = np.arange(-6., 6., 0.02)
t = np.arange(-2., 2., 0.01)
x = A * np.cos(2 * np.pi * f0 * t) * np.exp(-np.pi * t ** 2)
X = A / 2 * \
(np.exp(-np.pi * (f - f0) ** 2) + np.exp(-np.pi * (f + f0) ** 2))
return f, X, t, x
def setKnob(self, value):
x1, y1, x2, y2 = self.compute(self.f0.value, self.A.value)
self.lines[0].set(xdata=x1, ydata=y1)
self.lines[1].set(xdata=x2, ydata=y2)
self.canvas.draw()
App-Klasse definieren
Die App-Klasse wird die Anwendung erstellen und die grafische Benutzeroberfläche (GUI) anzeigen.
class App(wx.App):
def OnInit(self):
self.frame1 = FourierDemoFrame(parent=None, title="Fourier Demo",
size=(640, 480))
self.frame1.Show()
return True
Führe die Anwendung aus
Der letzte Schritt besteht darin, die Anwendung auszuführen.
if __name__ == "__main__":
app = App()
app.MainLoop()
Zusammenfassung
In diesem Lab haben Sie gelernt, wie Sie mithilfe von Matplotlib in Python eine einfache grafische Benutzeroberfläche (GUI) erstellen. Sie haben ein Fourier-Demo erstellt, das zwei Wellenformen in der Frequenz- und Zeitdomäne anzeigt. Sie konnten die Frequenz und die Amplitude der Wellenformen durch Klicken und Ziehen auf dem Graphen anpassen.