Введение
В этом практическом занятии вы научитесь создавать простой графический интерфейс пользователя (GUI) с использованием Matplotlib в Python. В частности, вы создадите демо-функцию Фурье, которая отображает две волновые формы в частотной и временной областях. Вы сможете настраивать частоту и амплитуду волновых форм, щелкая и перетаскивая мышью по графику.
Советы по работе с ВМ
После запуска виртуальной машины (VM) щелкните в левом верхнем углу, чтобы переключиться на вкладку Notebook и получить доступ к Jupyter Notebook для практики.
Иногда вам может потребоваться подождать несколько секунд, пока Jupyter Notebook загрузится. Проверка операций не может быть автоматизирована из-за ограничений Jupyter Notebook.
Если вы сталкиваетесь с проблемами во время обучения, не стесняйтесь обращаться к Labby. Оставьте отзыв после занятия, и мы оперативно решим проблему для вас.
Импорт библиотек
Первым шагом является импорт необходимых библиотек. Мы будем использовать Matplotlib, wxPython и NumPy. Matplotlib - это библиотека для построения графиков в Python, wxPython - это инструмент для создания графического интерфейса пользователя (GUI) в Python, а NumPy - это библиотека для численных вычислений с использованием Python.
import wx
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
Определение классов Knob и Param
Следующим шагом является определение классов Knob и Param. Эти классы будут использоваться для управления частотой и амплитудой волновых форм в графическом интерфейсе пользователя (GUI).
class Knob:
"""
Knob - простой класс с методом "setKnob".
Экземпляр Knob прикрепляется к экземпляру Param, например, param.attach(knob)
Базовый класс используется для целей документирования.
"""
def setKnob(self, value):
pass
class Param:
"""
В идее класса "Param" некоторые параметры в GUI могут иметь
несколько вращателей, которые и контролируют их, и отражают состояние параметра, например,
ползунок, текст и перетаскивание могут все изменить значение частоты в
波形 этом примера.
Класс позволяет более чистый способ обновления/"отзвука" для других вращателей, когда
один из них изменяется. Также этот класс обрабатывает ограничения min/max для всех
вращателей.
Идея - список вращателей - в методе "set" объект вращателя передается также
- другие вращатели в списке вращателей имеют метод "set", который вызывается для других.
"""
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
Класс SliderGroup создаст ползунок и текстовое поле в графическом интерфейсе пользователя (GUI) для настройки частоты и амплитуды волновых форм.
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
Класс FourierDemoFrame создаст графический интерфейс пользователя (GUI) с использованием wxPython и Matplotlib.
class FourierDemoFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
panel = wx.Panel(self)
## создаем элементы GUI
self.createCanvas(panel)
self.createSliders(panel)
## размещаем их в сайзере для макета
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: ## вне осей
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
Класс App создаст приложение и отобразит графический интерфейс пользователя (GUI).
class App(wx.App):
def OnInit(self):
self.frame1 = FourierDemoFrame(parent=None, title="Fourier Demo",
size=(640, 480))
self.frame1.Show()
return True
Запустите приложение
Последним шагом является запуск приложения.
if __name__ == "__main__":
app = App()
app.MainLoop()
Резюме
В этом практическом занятии вы узнали, как создавать простой графический интерфейс пользователя (GUI) с использованием Matplotlib в Python. Вы создали демо-версию для преобразования Фурье, которая отображает две волновые формы в частотной и временной областях. Вы могли настроить частоту и амплитуду волновых форм, щелкнув и перетаскивая мышью по графику.