Interaktive Fourier-Wellenformvisualisierung erstellen

PythonPythonBeginner
Jetzt üben

This tutorial is from open-source community. Access the source code

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

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.