Introdução
Neste laboratório, você aprenderá como criar uma GUI simples usando Matplotlib em Python. Especificamente, você criará uma Demonstração de Fourier que exibe duas formas de onda nos domínios da frequência e do tempo. Você poderá ajustar a frequência e a amplitude das formas de onda clicando e arrastando no gráfico.
Dicas para a VM
Após a inicialização da VM, clique no canto superior esquerdo para mudar para a aba Notebook e acessar o Jupyter Notebook para praticar.
Às vezes, pode ser necessário aguardar alguns segundos para que o Jupyter Notebook termine de carregar. A validação das operações não pode ser automatizada devido a limitações no Jupyter Notebook.
Se você enfrentar problemas durante o aprendizado, sinta-se à vontade para perguntar ao Labby. Forneça feedback após a sessão, e resolveremos o problema prontamente para você.
Importar Bibliotecas
O primeiro passo é importar as bibliotecas necessárias. Usaremos Matplotlib, wxPython e NumPy. Matplotlib é uma biblioteca de plotagem para Python, wxPython é um kit de ferramentas GUI (Graphical User Interface - Interface Gráfica do Usuário) para Python, e NumPy é uma biblioteca para computação numérica com Python.
import wx
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
Definir as Classes Knob e Param
O próximo passo é definir as classes Knob e Param. Essas classes serão usadas para controlar a frequência e a amplitude das formas de onda na GUI.
class Knob:
"""
Knob - classe simples com um método "setKnob".
Uma instância de Knob é anexada a uma instância de Param, por exemplo, param.attach(knob)
A classe base é para fins de documentação.
"""
def setKnob(self, value):
pass
class Param:
"""
A ideia da classe "Param" é que algum parâmetro na GUI pode ter
vários knobs que o controlam e refletem o estado do parâmetro, por exemplo,
um controle deslizante, texto e arrastar podem alterar o valor da frequência em
a forma de onda deste exemplo.
A classe permite uma maneira mais limpa de atualizar/"feedback" para os outros knobs quando
um está sendo alterado. Além disso, esta classe lida com restrições de mínimo/máximo para todos
os knobs.
Ideia - lista de knobs - no método "set", o objeto knob também é passado
- os outros knobs na lista de knobs têm um método "set" que é
chamado para os outros.
"""
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 a Classe SliderGroup
A classe SliderGroup criará um controle deslizante (slider) e um campo de texto na GUI para ajustar a frequência e a amplitude das 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 a Classe FourierDemoFrame
A classe FourierDemoFrame criará a GUI usando wxPython e Matplotlib.
class FourierDemoFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
panel = wx.Panel(self)
## criar os elementos da GUI
self.createCanvas(panel)
self.createSliders(panel)
## colocá-los em um sizer para o Layout
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: ## fora dos eixos
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(
"Clique e arraste as formas de onda para alterar a frequência e a 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()
Definir a Classe App
A classe App criará a aplicação e exibirá a GUI.
class App(wx.App):
def OnInit(self):
self.frame1 = FourierDemoFrame(parent=None, title="Fourier Demo",
size=(640, 480))
self.frame1.Show()
return True
Executar a Aplicação
O passo final é executar a aplicação.
if __name__ == "__main__":
app = App()
app.MainLoop()
Resumo
Neste laboratório, você aprendeu como criar uma GUI simples usando Matplotlib em Python. Você criou uma Demonstração de Fourier (Fourier Demo) que exibe duas formas de onda nos domínios da frequência e do tempo. Você foi capaz de ajustar a frequência e a amplitude das formas de onda clicando e arrastando no gráfico.