Introduction
Dans ce laboratoire, vous allez apprendre à créer une interface graphique utilisateur (GUI) simple à l'aide de Matplotlib en Python. Plus précisément, vous allez créer un démonstrateur de Fourier qui affiche deux formes d'ondes dans les domaines de la fréquence et du temps. Vous pourrez ajuster la fréquence et l'amplitude des formes d'ondes en cliquant et en traînant sur le graphique.
Conseils sur la machine virtuelle (VM)
Une fois le démarrage de la VM terminé, cliquez dans le coin supérieur gauche pour basculer vers l'onglet Carnet de notes pour accéder au carnet Jupyter Notebook pour la pratique.
Parfois, vous devrez peut-être attendre quelques secondes pour que le carnet Jupyter Notebook ait fini de charger. La validation des opérations ne peut pas être automatisée en raison des limites du carnet Jupyter Notebook.
Si vous rencontrez des problèmes pendant l'apprentissage, n'hésitez pas à demander à Labby. Donnez votre feedback après la session, et nous résoudrons rapidement le problème pour vous.
Importation des bibliothèques
La première étape consiste à importer les bibliothèques nécessaires. Nous allons utiliser Matplotlib, wxPython et NumPy. Matplotlib est une bibliothèque de tracé pour Python, wxPython est un kit de développement d'interface graphique (GUI) pour Python, et NumPy est une bibliothèque pour le calcul numérique avec Python.
import wx
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
Définition des classes Knob et Param
L'étape suivante consiste à définir les classes Knob et Param. Ces classes seront utilisées pour contrôler la fréquence et l'amplitude des formes d'ondes dans l'interface graphique utilisateur (GUI).
class Knob:
"""
Knob - simple class with a "setKnob" method.
A Knob instance is attached to a Param instance, e.g., param.attach(knob)
Base class is for documentation purposes.
"""
def setKnob(self, value):
pass
class Param:
"""
L'idée de la classe "Param" est que certains paramètres dans l'interface graphique peuvent avoir
plusieurs boutons de réglage qui le contrôlent et reflètent également l'état du paramètre, par exemple
un curseur, du texte et le fait de faire glisser peuvent tous modifier la valeur de la fréquence dans
la forme d'onde de cet exemple.
Cette classe permet une manière plus propre de mettre à jour / "renvoyer des informations" aux autres boutons de réglage lorsque
l'un d'entre eux est modifié. De plus, cette classe gère les contraintes min/max pour tous
les boutons de réglage.
Idée - liste de boutons de réglage - dans la méthode "set", l'objet bouton de réglage est également passé
- les autres boutons de réglage dans la liste des boutons de réglage ont une méthode "set" qui est
appelée pour les autres.
"""
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
Définition de la classe SliderGroup
La classe SliderGroup créera un curseur et un champ de texte dans l'interface graphique utilisateur (GUI) pour ajuster la fréquence et l'amplitude des formes d'ondes.
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))
Définition de la classe FourierDemoFrame
La classe FourierDemoFrame créera l'interface graphique utilisateur (GUI) à l'aide de wxPython et Matplotlib.
class FourierDemoFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
panel = wx.Panel(self)
## créer les éléments de l'interface graphique
self.createCanvas(panel)
self.createSliders(panel)
## les placer dans un sizer pour la mise en page
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='Fréquence 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 = 'fréquence'
elif self.lines[1].contains(event)[0]:
self.state = 'temps'
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: ## en dehors des axes
return
x0, y0, f0Init, AInit = self.mouseInfo
self.A.set(AInit + (AInit * (y - y0) / y0), self)
if self.state == 'fréquence':
self.f0.set(f0Init + (f0Init * (x - x0) / x0))
elif self.state == 'temps':
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)
couleur = (1., 0., 0.)
self.lines += self.subplot1.plot(x1, y1, couleur=couleur, linewidth=2)
self.lines += self.subplot2.plot(x2, y2, couleur=couleur, linewidth=2)
self.subplot1.set_title(
"Cliquez et faites glisser les formes d'onde pour changer la fréquence et l'amplitude",
fontsize=12)
self.subplot1.set_ylabel("Forme d'onde dans le domaine de la fréquence X(f)", fontsize=8)
self.subplot1.set_xlabel("fréquence f", fontsize=8)
self.subplot2.set_ylabel("Forme d'onde dans le domaine du temps x(t)", fontsize=8)
self.subplot2.set_xlabel("temps 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()
Définition de la classe App
La classe App créera l'application et affichera l'interface graphique utilisateur (GUI).
class App(wx.App):
def OnInit(self):
self.frame1 = FourierDemoFrame(parent=None, title="Fourier Demo",
size=(640, 480))
self.frame1.Show()
return True
Exécuter l'application
La dernière étape consiste à exécuter l'application.
if __name__ == "__main__":
app = App()
app.MainLoop()
Sommaire
Dans ce laboratoire, vous avez appris à créer une interface graphique utilisateur (GUI) simple à l'aide de Matplotlib en Python. Vous avez créé un démonstrateur de Fourier qui affiche deux formes d'onde dans les domaines de la fréquence et du temps. Vous avez pu ajuster la fréquence et l'amplitude des formes d'onde en cliquant et en faisant glisser sur le tracé.