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.