Crear una visualización interactiva de la forma de onda de Fourier

Beginner

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

Introducción

En este laboratorio, aprenderá a crear una interfaz gráfica de usuario (GUI) simple utilizando Matplotlib en Python. Específicamente, creará una demostración de Fourier que muestre dos formas de onda en los dominios de la frecuencia y el tiempo. Podrá ajustar la frecuencia y la amplitud de las formas de onda haciendo clic y arrastrando en la gráfica.

Consejos sobre la VM

Una vez que se haya iniciado la VM, haga clic en la esquina superior izquierda para cambiar a la pestaña Cuaderno y acceder a Jupyter Notebook para practicar.

A veces, es posible que tenga que esperar unos segundos a que Jupyter Notebook termine de cargarse. La validación de las operaciones no se puede automatizar debido a las limitaciones de Jupyter Notebook.

Si tiene problemas durante el aprendizaje, no dude en preguntar a Labby. Deje comentarios después de la sesión y lo resolveremos rápidamente para usted.

Importar bibliotecas

El primer paso es importar las bibliotecas necesarias. Vamos a utilizar Matplotlib, wxPython y NumPy. Matplotlib es una biblioteca de trazado para Python, wxPython es una herramienta de interfaz gráfica de usuario (GUI) para Python y NumPy es una biblioteca para el cálculo numérico con Python.

import wx
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure

Definir las clases Knob y Param

El siguiente paso es definir las clases Knob y Param. Estas clases se utilizarán para controlar la frecuencia y la amplitud de las formas de onda en la interfaz gráfica de usuario (GUI).

class Knob:
    """
    Knob - clase simple con un método "setKnob".
    Una instancia de Knob está adjunta a una instancia de Param, por ejemplo, param.attach(knob)
    La clase base es para fines de documentación.
    """

    def setKnob(self, value):
        pass


class Param:
    """
    La idea de la clase "Param" es que algún parámetro en la GUI puede tener
    varios pernos que tanto lo controlan como reflejan el estado del parámetro, por ejemplo,
    un deslizador, texto y arrastrar todo pueden cambiar el valor de la frecuencia en
    la forma de onda de este ejemplo.
    La clase permite una forma más limpia de actualizar/"dar retroalimentación" a los otros pernos cuando
    uno está siendo cambiado. Además, esta clase maneja las restricciones de mínimo/máximo para todos
    los pernos.
    Idea - lista de pernos - en el método "set", el objeto perno se pasa también
      - los otros pernos en la lista de pernos tienen un método "set" que se llama
        para los demás.
    """

    def __init__(self, initialValue=None, minimum=0., maximum=1.):
        self.minimum = minimum
        self.maximum = maximum
        if initialValue!= self.constrain(initialValue):
            raise ValueError('valor inicial ilegal')
        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

Definir la clase SliderGroup

La clase SliderGroup creará un deslizador y un campo de texto en la interfaz gráfica de usuario (GUI) para ajustar la frecuencia y la amplitud de las formas de onda.

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))

Definir la clase FourierDemoFrame

La clase FourierDemoFrame creará la interfaz gráfica de usuario (GUI) utilizando wxPython y Matplotlib.

class FourierDemoFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        panel = wx.Panel(self)

        ## crear los elementos de la GUI
        self.createCanvas(panel)
        self.createSliders(panel)

        ## colocarlos en un sizer para el diseño
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.canvas, 1, wx.EXPAND)
        sizer.Add(self.frequencySliderGroup.sizer, 0,
                  wx.EXPAND | wx.ALL, borde=5)
        sizer.Add(self.amplitudeSliderGroup.sizer, 0,
                  wx.EXPAND | wx.ALL, borde=5)
        panel.SetSizer(sizer)

    def createCanvas(self, padre):
        self.lines = []
        self.figure = Figure()
        self.canvas = FigureCanvas(padre, -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., mínimo=0., máximo=6.)
        self.A = Param(1., mínimo=0.01, máximo=2.)
        self.createPlots()

        self.f0.attach(self)
        self.A.attach(self)

    def createSliders(self, panel):
        self.frequencySliderGroup = SliderGroup(
            panel,
            etiqueta='Frecuencia f0:',
            param=self.f0)
        self.amplitudeSliderGroup = SliderGroup(panel, etiqueta='Amplitud a:',
                                                param=self.A)

    def mouseDown(self, evento):
        if self.lines[0].contains(evento)[0]:
            self.state = 'frecuencia'
        elif self.lines[1].contains(evento)[0]:
            self.state = 'tiempo'
        else:
            self.state = ''
        self.mouseInfo = (evento.xdata, evento.ydata,
                          max(self.f0.value,.1),
                          self.A.value)

    def mouseMotion(self, evento):
        if self.state == '':
            return
        x, y = evento.xdata, evento.ydata
        if x is None:  ## fuera de los ejes
            return
        x0, y0, f0Init, AInit = self.mouseInfo
        self.A.set(AInit + (AInit * (y - y0) / y0), self)
        if self.state == 'frecuencia':
            self.f0.set(f0Init + (f0Init * (x - x0) / x0))
        elif self.state == 'tiempo':
            if (x - x0) / x0!= -1.:
                self.f0.set(1. / (1. / f0Init + (1. / f0Init * (x - x0) / x0)))

    def mouseUp(self, evento):
        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(
            "Haga clic y arrastre las formas de onda para cambiar la frecuencia y la amplitud",
            fontsize=12)
        self.subplot1.set_ylabel("Forma de onda en el dominio de la frecuencia X(f)", fontsize=8)
        self.subplot1.set_xlabel("frecuencia f", fontsize=8)
        self.subplot2.set_ylabel("Forma de onda en el dominio del tiempo x(t)", fontsize=8)
        self.subplot2.set_xlabel("tiempo 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()

Definir la clase App

La clase App creará la aplicación y mostrará la interfaz gráfica de usuario (GUI).

class App(wx.App):
    def OnInit(self):
        self.frame1 = FourierDemoFrame(parent=None, title="Fourier Demo",
                                       size=(640, 480))
        self.frame1.Show()
        return True

Ejecutar la Aplicación

El último paso es ejecutar la aplicación.

if __name__ == "__main__":
    app = App()
    app.MainLoop()

Resumen

En este laboratorio, aprendiste cómo crear una interfaz gráfica de usuario (GUI) simple utilizando Matplotlib en Python. Creaste una demostración de Fourier que muestra dos formas de onda en los dominios de la frecuencia y el tiempo. Pudiste ajustar la frecuencia y la amplitud de las formas de onda haciendo clic y arrastrando en la gráfica.