Pymecavideo 8.0
Étude cinématique à l'aide de vidéos
graphWidget.py
1# -*- coding: utf-8 -*-
2
3"""
4 graphWidget, a module for pymecavideo:
5 a program to track moving points in a video frameset
6
7 Copyright (C) 2007 Jean-Baptiste Butet <ashashiwa@gmail.com>
8 Copyright (C) 2023 Georges Khaznadar <georgesk@debian.org>
9
10 This program is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
22"""
23
24from PyQt6.QtCore import QThread, pyqtSignal, QLocale, QTranslator, Qt, \
25 QSize, QTimer, QObject, QRect, QPoint, QPointF, QEvent
26from PyQt6.QtGui import QKeySequence, QIcon, QPixmap, QImage, QPainter, \
27 QCursor, QPen, QColor, QFont, QResizeEvent, QShortcut
28from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QLayout, \
29 QFileDialog, QTableWidgetItem, QInputDialog, QLineEdit, QMessageBox, \
30 QTableWidgetSelectionRange, QPushButton, QVBoxLayout
31
32import os, time, re, sys
33import locale
34import pyqtgraph as pg
35import pyqtgraph.exporters
36import math
37
38from version import Version
39from vecteur import vecteur
40from image_widget import ImageWidget
41from globdef import cible_icon, DOCUMENT_PATH, inhibe, pattern_float
42from toQimage import toQImage
43from suivi_auto import SelRectWidget
44from detect import filter_picture
45from cadreur import Cadreur, openCvReader
46from export import Export, EXPORT_FORMATS
47from grandeurs import grandeurs
48from dbg import Dbg
49
50import interfaces.icon_rc
51
52from interfaces.Ui_graphWidget import Ui_graphWidget
53from etatsGraph import Etats
54
55class GraphWidget(QWidget, Ui_graphWidget, Etats):
56 """
57 Widget principal de l'onglet grapheur
58
59 paramètres du constructeur :
60 @param parent l'onglet des graphiques
61 """
62 def __init__(self, parent):
63 QWidget.__init__(self, parent)
64 Ui_graphWidget.__init__(self)
65 Etats.__init__(self)
66 self.setupUi(self)
67 self.connecte_ui()
68 self.locals = {} # dictionnaire de variables locales, pour eval
69 self.graphe_deja_choisi = None
70 self.graphW = None
71 return
72
73 def setApp(self, app):
74 """
75 Crée des liens avec la fenêtre principale, le débogueur,
76 le wigdet de pointage
77 @param app la fenêtre principale
78 """
79 self.app = app
80 self.dbg = app.dbg
81 self.pointage = app.pointage
82 return
83
84 def connecte_ui(self):
85 """
86 Connecte les signaux des sous-widgets
87 """
88 self.comboBox_X.currentIndexChanged.connect(self.dessine_graphe_avant)
89 self.comboBox_Y.currentIndexChanged.connect(self.dessine_graphe_avant)
90 self.lineEdit_m.textChanged.connect(self.verifie_m_grapheur)
91 self.lineEdit_g.textChanged.connect(self.verifie_g_grapheur)
92 self.comboBox_style.currentIndexChanged.connect(self.dessine_graphe)
93 self.pushButton_save_plot.clicked.connect(self.enregistre_graphe)
94 return
95
96 def dessine_graphe_avant(self):
97 if self.graphe_deja_choisi is not None :
98 self.graphe_deja_choisi = None #si changement ds les combobox, on réinitilaise le choix.
99 self.dessine_graphe()
100 return
101
102 def dessine_graphe(self):
103 """dessine les graphes avec pyqtgraph au moment où les combobox sont choisies"""
104 self.dbg.p(2, "rentre dans 'dessine_graphe'")
105 X, Y = [], []
106 styles = {0: {'pen': None, 'symbol': '+'}, 1: {'pen': (0, 0, 0), 'symbol': '+'}, 2: {'pen': (
107 0, 0, 0), 'symbol': None}} # Dictionnaire contenant les différents styles de graphes
108 # Index du comboxBox styles, inspirés de Libreoffice
109 style = self.comboBox_style.currentIndex()
110
111 if self.graphe_deja_choisi is not None :
112 abscisse = self.graphe_deja_choisi[1].strip('|')
113 ordonnee = self.graphe_deja_choisi[0].strip('|')
114 else :
115 abscisse = self.comboBox_X.currentText().strip('|')
116 ordonnee = self.comboBox_Y.currentText().strip('|')
117 # Définition des paramètres 'pen' et 'symbol' pour pyqtgraph
118 pen, symbol = styles[style]['pen'], styles[style]['symbol']
119 grandeurX = abscisse
120 grandeurY = ordonnee
121 # rien à faire si le choix des axes est indéfini
122 if grandeurX == "Choisir ..." or grandeurY == "Choisir ...": return
123
124 if grandeurX == 't':
125 X = self.pointage.dates
126 elif grandeurX in self.locals :
127 X = self.locals[grandeurX]
128 if grandeurY == 't':
129 Y = self.pointage.dates
130 elif grandeurY in self.locals :
131 Y = self.locals[grandeurY]
132 # on retire toutes les parties non définies
133 # zip (*[liste de tuples]) permet de "dézipper"
134 X, Y = zip(*[(x, y) for x,y in zip(X, Y) if x is not None and y is not None])
135 X = list(X)
136 Y = list(Y)
137
138 if X != [] and Y != []:
139 pg.setConfigOption('background', 'w')
140 pg.setConfigOption('foreground', 'k')
141 titre = "%s en fonction de %s" % (ordonnee, abscisse)
142 # gestion des unités
143 if 't' in abscisse:
144 unite_x = "t(s)"
145 elif 'V' in abscisse:
146 unite_x = abscisse+'(m/s)'
147 elif 'E' in abscisse:
148 unite_x = abscisse+'(J)'
149 elif 'A' in abscisse:
150 unite_x = abscisse+'(m/s²)'
151 else:
152 unite_x = abscisse+'(m)'
153
154 if 't' in ordonnee:
155 unite_y = "t(s)"
156 elif 'V' in ordonnee:
157 unite_y = ordonnee+'(m/s)'
158 elif 'E' in ordonnee:
159 unite_y = ordonnee+'(J)'
160 elif 'A' in ordonnee:
161 unite_y = ordonnee+'(m/s²)'
162 else:
163 unite_y = ordonnee+'(m)'
164
165 if not self.graphW: # premier tour
166 self.graphW = pg.PlotWidget(
167 title=titre, parent=self.widget_graph)
168 self.graphW.setMenuEnabled(False)
169 self.graphW.setLabel('bottom', unite_x)
170 self.graphW.setLabel('left', unite_y)
171 self.vLayout = QVBoxLayout(self.widget_graph)
172 self.vLayout.setContentsMargins(0, 0, 0, 0)
173 self.vLayout.setObjectName(
174 "verticalLayout_graph")
175 self.vLayout.addWidget(self.graphW)
176 self.graphW.plot(X, Y, pen=pen, symbol=symbol)
177 self.graphW.autoRange()
178 self.graphW.show()
179 self.pg_exporter = pg.exporters.ImageExporter(self.graphW.plotItem)
180 else:
181 plotItem = self.graphW.getPlotItem()
182 plotItem.setTitle(titre)
183 self.graphW.setLabel('bottom', unite_x)
184 self.graphW.setLabel('left', unite_y)
185 self.graphW.clear()
186 self.graphW.plot(X, Y, pen=pen, symbol=symbol)
187 self.graphW.autoRange()
188 self.graphW.show()
189 self.graphe_deja_choisi = (ordonnee, abscisse)
190 return
191
192 def enregistre_graphe(self):
193 if hasattr (self, 'pg_exporter'):
194 base_name = os.path.splitext(os.path.basename(self.pointage.filename))[0]
195 defaultName = os.path.join(DOCUMENT_PATH, base_name+'.png')
196 fichier = QFileDialog.getSaveFileName(
197 self,
198 self.tr("Enregistrer le graphique"),
199 defaultName, self.tr("fichiers images(*.png)"))
200 self.pg_exporter.export(fichier[0])
201 return
202
203 def verifie_m_grapheur(self):
204 m = self.lineEdit_m.text().replace(',', '.')
205 if m != "":
206 if not pattern_float.match(m):
207 QMessageBox.critical(
208 self,
209 self.tr("MAUVAISE VALEUR !"),
210 self.tr("La valeur rentrée (m = {}) n'est pas compatible avec le calcul").format(m))
211 else:
212 self.affiche_grapheur()
213 self.dessine_graphe()
214 return
215
216 def verifie_g_grapheur(self):
217 g = self.lineEdit_g.text().replace(',', '.')
218 if g != "":
219 if not pattern_float.match(g):
220 QMessageBox.critical(
221 self,
222 self.tr("MAUVAISE VALEUR !"),
223 self.tr("La valeur rentrée (g = {}) n'est pas compatible avec le calcul").format(g))
224 else:
225 self.affiche_grapheur()
226 self.dessine_graphe()
227 return
228
229 def affiche_grapheur(self, MAJ=True):
230 self.dbg.p(2, "rentre dans 'affiche_grapheur'")
231 m = self.lineEdit_m.text().replace(',', '.')
232 g = self.lineEdit_g.text().replace(',', '.')
233 if not pattern_float.match(m) or not pattern_float.match(g): return
234 deltaT = self.pointage.deltaT
235 m = float(m)
236 g = float(g)
237
238 # initialisation de self.locals avec des listes vides
239 for obj in self.pointage.suivis:
240 for gr in grandeurs:
241 self.locals[gr+str(obj)] = []
242
243 # remplissage des self.locals pour les positions,
244 # les vitesses, Ec et Epp
245 def cb_points(i, t, j, obj, p, v):
246 """
247 fonction de rappel pour une itération sur les dates
248 """
249 self.locals["X"+str(obj)].append(p.x if p else None)
250 self.locals["Y"+str(obj)].append(p.y if p else None)
251 self.locals["Vx"+str(obj)].append(v.x if v else None)
252 self.locals["Vy"+str(obj)].append(v.y if v else None)
253 self.locals["V"+str(obj)].append(v.norme if v else None)
254 self.locals["Ec"+str(obj)].append(
255 0.5 * m * v.norme ** 2 if v else None)
256 self.locals["Epp"+str(obj)].append(
257 self.pointage.sens_Y * m * g * p.y if p else None)
258 return
259 self.pointage.iteration_data(None, cb_points, unite="m")
260
261 # on complète le remplissage de self.locals
262 for obj in self.pointage.suivis:
263 # énergie mécanique
264 self.locals["Em"+str(obj)] = \
265 [ec + epp if ec is not None and epp is not None else None
266 for ec, epp in zip (
267 self.locals["Ec"+str(obj)],
268 self.locals["Epp"+str(obj)])]
269 # accélération Ax
270 liste0 = self.locals["Vx"+str(obj)][1:-1] # commence à l'index 1
271 liste1 = self.locals["Vx"+str(obj)][2:] # commence à l'index 2
272 self.locals["Ax"+str(obj)] = [None, None] + \
273 [(v1 - v0) / deltaT if v1 is not None and v0 is not None else None
274 for v1, v0 in zip(liste1, liste0)]
275 # accélération Ay
276 liste0 = self.locals["Vy"+str(obj)][1:-1] # commence à l'index 1
277 liste1 = self.locals["Vy"+str(obj)][2:] # commence à l'index 2
278 self.locals["Ay"+str(obj)] = [None, None] + \
279 [(v1 - v0) / deltaT if v1 is not None and v0 is not None else None
280 for v1, v0 in zip(liste1, liste0)]
281 # module de l'accélération A
282 self.locals["A"+str(obj)] = \
283 [vecteur(ax, ay).norme if ax is not None else None
284 for ax, ay in zip(
285 self.locals["Ax"+str(obj)],
286 self.locals["Ay"+str(obj)]
287 )]
288 # mise à jour des éléments graphiques
289 if self.graphe_deja_choisi is None :
290 # tout premier choix de graphe
291 self.comboBox_X.clear()
292 self.comboBox_Y.clear()
293 self.comboBox_X.insertItem(-1,
294 self.tr("Choisir ..."))
295 self.comboBox_Y.insertItem(-1,
296 self.tr("Choisir ..."))
297 self.comboBox_X.addItem('t')
298 self.comboBox_Y.addItem('t')
299 for grandeur in self.locals.keys():
300 if self.locals[grandeur] != []:
301 numero = ''.join(
302 [grandeur[-2] if grandeur[-2].isdigit() else "", grandeur[-1]])
303 if 'prime' in grandeur :
304 if 'x' in grandeur :
305 grandeur_a_afficher = 'Vx'+numero
306 elif 'y' in grandeur :
307 grandeur_a_afficher = 'Vy'+numero
308 elif 'abs' in grandeur :
309 grandeur_a_afficher = 'Ax'+numero
310 elif 'ord' in grandeur :
311 grandeur_a_afficher = 'Ay'+numero
312 elif 'A' in grandeur or 'V' in grandeur:
313 grandeur_a_afficher = '|'+grandeur+'|'
314
315 else :
316 grandeur_a_afficher = grandeur
317 self.comboBox_X.addItem(grandeur_a_afficher)
318 self.comboBox_Y.addItem(grandeur_a_afficher)
319 #else : #il y a déjà eu un choix de graphe
320 #self.comboBox_X.setItem(self.graphe_deja_choisi[1])
321 #self.comboBox_Y.setItem(self.graphe_deja_choisi[0])
322 return
323
324
Une classe qui permet de définir les états pour le ccordWidget debut, A, AB, B, C,...
Definition: etatsGraph.py:34
Widget principal de l'onglet grapheur.
Definition: graphWidget.py:61
def dessine_graphe(self)
dessine les graphes avec pyqtgraph au moment où les combobox sont choisies
Definition: graphWidget.py:103
def setApp(self, app)
Crée des liens avec la fenêtre principale, le débogueur, le wigdet de pointage.
Definition: graphWidget.py:78
def connecte_ui(self)
Connecte les signaux des sous-widgets.
Definition: graphWidget.py:87
def affiche_grapheur(self, MAJ=True)
fonction de rappel pour une itération sur les dates
Definition: graphWidget.py:229
une classe pour des vecteurs 2D ; les coordonnées sont flottantes, et on peut accéder à celles-ci par...
Definition: vecteur.py:44