Pymecavideo 8.0
Étude cinématique à l'aide de vidéos
trajWidget.py
1# -*- coding: utf-8 -*-
2"""
3 trajWidget, a module for pymecavideo:
4 a program to track moving points in a video frameset
5
6 Copyright (C) 2007 Jean-Baptiste Butet <ashashiwa@gmail.com>
7 Copyright (C) 2022 Georges Khaznadar <georgesk@debian.org>
8
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21"""
22from math import sqrt, atan2, degrees
23
24from PyQt6.QtCore import QThread, pyqtSignal, QLocale, QTranslator, Qt, QSize, QTimer, QRect, QPoint, QPointF
25from PyQt6.QtGui import QKeySequence, QIcon, QPixmap, QImage, QPicture, QPainter, QColor, QFont, QPainterPath, QPen, QFontMetrics, QShortcut
26from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QLayout, QFileDialog, QTableWidgetItem, QInputDialog, QLineEdit, QMessageBox, QTableWidgetSelectionRange
27
28from image_widget import ImageWidget
29from vecteur import vecteur
30from globdef import pattern_float
31
32class trajWidget(ImageWidget):
33 """
34 Classe pour l'élément qui remplit l'essentiel de l'onglet trajectoire
35 paramètres du constructeur :
36
37 @param parent un des layouts de l'onglet
38 """
39 def __init__(self, parent):
40 ImageWidget.__init__(self, parent)
41 self.chrono = False
42
43 self.setCursor(Qt.CursorShape.ArrowCursor)
44 self.setAutoFillBackground(True)
45 self.couleurs = ["red", "blue", "cyan", "magenta", "yellow", "gray", "green", "red", "blue", "cyan", "magenta",
46 "yellow", "gray", "green"]
47 self.setMouseTracking(True)
48 self.speedToDraw = []
49 self.speedtest = []
50 self.pos_souris = None
51 # self.update()
52 self.picture = QPicture()
53 self.origine_mvt = vecteur(0,0)
54 self.origine = vecteur(0,0)
55 self.referentiel = 0
56 return
57
58 def maj(self):
59 self.origine_mvt = self.pointage.origine
60
61 def prepare_vecteurs_pour_paint(self):
62 if self.trajectoire.checkBoxVectorSpeed.isChecked():
63 vitesse = self.trajectoire.checkBoxScale.currentText().replace(
64 ",",".")
65 if not pattern_float.match(vitesse): return
66 self.speedToDraw = self.pointage.vecteursVitesse(float(vitesse))
67 return
68
69 def mouseMoveEvent(self, event):
70 # Look if mouse is near a point
71 self.pos_souris = event.pos()
72 if self.trajectoire.radioButtonNearMouse.isChecked():
73 self.update()
74 return
75
76 def paintText(self, x, y, text,
77 color = QColor("white"), bgcolor = QColor("lightGray"),
78 fontsize = 12,
79 fontfamily = None,
80 center=False):
81 """
82 Trace un texte (self.painter doit être actif !)
83 @param x abscisse
84 @param y ordonnée
85 @param text le teste à tracer
86 @param color couleur de texte (defaut : Qt.white)
87 @param bgcolor couleur de fond (par défaut : Qt.lightGray) ;
88 peut être None pour pas de fond
89 @param fontsize taille de police (par défaut : 12)
90 @param fontfamily famille de police (par défaut: None)
91 @param center (faux par défaut) centrage horizontal demandé
92 """
93 if fontfamily:
94 font = QFont(fontfamily, fontsize, weight = 700) # bold
95 else:
96 font = QFont()
97 font.setPointSize(fontsize)
98 self.painter.setFont(font)
99 font_metrics = QFontMetrics(font)
100 r = font_metrics.boundingRect(text)
101 text_width = r.width()
102 text_height = r.height()
103 self.painter.setPen(color)
104 offset_x = 0 if center == False else text_width // 2
105 if bgcolor:
106 self.painter.setBrush(bgcolor)
107 self.painter.drawRect(
108 x-5-offset_x, y-text_height-5,
109 text_width+10, text_height+10)
110 self.painter.drawText(x-offset_x, y, text)
111 return
112
113 def paintEvent(self, event):
114 self.painter = QPainter()
115 self.painter.begin(self)
116 self.painter.setRenderHint(QPainter.RenderHint.Antialiasing)
117 # self.painter.save()
118 if self.chrono == 2 :
119 couleur_de_fond = QColor("white")
120 else :
121 couleur_de_fond = QColor("grey")
122 if not self.chrono == 1 :
123 self.painter.fillRect(
124 QRect(0, 0, self.video.image_w, self.video.image_h), couleur_de_fond)
125
126 ############################################################
127 # paint the origin
128 if not self.chrono == 2:
129 self.painter.setPen(QColor("green"))
130 self.painter.drawLine(
131 round(self.origine_mvt.x) - 5, round(self.origine_mvt.y),
132 round(self.origine_mvt.x) + 5, round(self.origine_mvt.y))
133 self.painter.drawLine(
134 round(self.origine_mvt.x), round(self.origine_mvt.y) - 5,
135 round(self.origine_mvt.x), round(self.origine_mvt.y) + 5)
136 self.painter.drawText(
137 round(self.origine_mvt.x), round(self.origine_mvt.y) + 15, "O")
138 self.painter.end()
139
140 # peint les informations pour le mode chronophotographie
141 if self.chrono: # ceci est géré dans pymecavideo.py, chercher : self.label_trajectoire.chrono
142 self.painter = QPainter()
143 self.painter.begin(self)
144 if self.chrono==1:
145 self.painter.drawPixmap(0, 0, self.image)
146 self.painter.setRenderHint(QPainter.RenderHint.Antialiasing)
147 x1 = 50 # marge en largeur
148 y1 = 50 # marge en hauteur
149 # Ecrit l'intervalle de temps
150 if self.chrono == 1: # rends plus lisible si le fond est foncé
151 text = f"Δt = {self.pointage.deltaT:.3f} s"
152 self.paintText(self.width() - x1 - 100, y1, text)
153 text = f"t = {self.pointage.deltaT*(self.trajectoire.spinBox_chrono.value()-1):.3f} s"
154 self.paintText(self.width() - x1 - 100, 2 * y1, text)
155 # dessine l'échelle
156 if self.chrono == 2: # chronogramme
157 self.painter.setPen(QColor("black"))
158 if self.pointage.echelle_image : #dessine une échelle en haut, horizontalement
159 longueur = round((self.pointage.echelle_image.p1 - self.pointage.echelle_image.p2).norme)
160 self.painter.drawLine(x1, y1-10, x1, y1+10)
161 self.painter.drawLine(x1, y1, longueur+x1, y1)
162 self.painter.drawLine(longueur+x1, y1-10, longueur+x1, y1+10)
163 text = "d = {0:.2e} m".format(self.pointage.echelle_image.longueur_reelle_etalon)
164 self.paintText(
165 max(x1+round(longueur/2), 0), y1+30,
166 text,
167 color = QColor("black"),
168 bgcolor = None,
169 center = True
170 )
171 else : #échelle non faite
172 self.paintText(
173 x1, y1+20,
174 "échelle non précisée",
175 color = QColor("black"),
176 bgcolor = None)
177 self.painter.end()
178
179 ############################################################
180 # Peindre l'échelle si chronophotographie
181 if self.chrono == 1: # chronophotographie
182 self.painter = QPainter()
183 self.painter.begin(self)
184 self.painter.setRenderHint(QPainter.RenderHint.Antialiasing)
185 pen = QPen(QColor("blue"))
186 pen.setWidth(3)
187 self.painter.setPen(pen)
188 if self.pointage.echelle:
189 self.painter.drawLine(
190 round(self.pointage.echelle_trace.p1.x),
191 round(self.pointage.echelle_trace.p1.y),
192 round(self.pointage.echelle_trace.p2.x),
193 round(self.pointage.echelle_trace.p2.y))
194
195 echelle = "d = {0:.2e} m".format(
196 self.pointage.echelle_image.longueur_reelle_etalon)
197
198 self.paintText(
199 round(self.pointage.echelle_trace.p1.x),
200 round((self.pointage.echelle_trace.p1.y + self.pointage.echelle_trace.p2.y)/2)+20,
201 echelle,
202 color = Qt.blue,
203 bgcolor = None,
204 fontfamily = "Times",
205 fontsize = 15,
206 )
207 else : #pas d'échelle
208 self.paintText(x1, y1+20, "échelle non précisée",
209 color = QColor("blue"),
210 bgcolor = None,
211 fontfamily = "Times",
212 fontsize = 15,)
213
214 self.painter.end()
215
216 ############################################################
217
218 ############################################################
219 # Paint points
220 self.painter = QPainter()
221 self.painter.begin(self)
222 self.painter.setRenderHint(QPainter.RenderHint.Antialiasing)
223
224 # préparation de la fonction de rappel
225 data = self.pointage.data
226 def cb_point(i, t, j, obj, p, v):
227 """
228 fonction de rappel pour usage avec iteration_data du videoWidget
229 """
230 if self.referentiel != 0:
231 obj_reference = data[t][self.referentiel]
232 else:
233 obj_reference = vecteur(0, 0)
234 if p:
235 self.painter.setFont(QFont("", 10))
236 self.painter.translate(
237 round(p.x + self.origine.x - obj_reference.x),
238 round(p.y + self.origine.y - obj_reference.y))
239 if self.chrono == 2:
240 self.painter.setPen(QColor("black"))
241 self.painter.drawLine(-4, 0, 4, 0)
242 self.painter.drawLine(0, -4, 0, 4)
243 self.painter.translate(-10, +10)
244 decal = -25
245 if p.x + decal < 0 :
246 decal = 5
247 self.painter.drawText(decal, 5, "M"+"'"*j+str(i))
248 else :
249 self.painter.setPen(QColor(self.couleurs[j]))
250 self.painter.drawLine(-2, 0, 2, 0)
251 self.painter.drawLine(0, -2, 0, 2)
252 self.painter.translate(-10, +10)
253 self.painter.drawText(0, 0, str(j + 1))
254 self.painter.translate(
255 round(-p.x - self.origine.x + obj_reference.x) + 10,
256 round(-p.y - self.origine.y + obj_reference.y) - 10)
257 return
258
259 self.pointage.iteration_data(None, cb_point)
260 self.painter.end()
261 ############################################################
262 # paint repere
263 if not self.chrono == 2 :
264 self.painter = QPainter()
265 self.painter.begin(self)
266 self.painter.setRenderHint(QPainter.RenderHint.Antialiasing)
267 self.painter.setPen(QColor("green"))
268 # self.painter.translate(0,0)
269 self.painter.translate(
270 round(self.origine_mvt.x), round(self.origine_mvt.y))
271 p1 = QPoint(round(self.pointage.sens_X * (-40)), 0)
272 p2 = QPoint(round(self.pointage.sens_X * (40)), 0)
273 p3 = QPoint(round(self.pointage.sens_X * (36)), 2)
274 p4 = QPoint(round(self.pointage.sens_X * (36)), -2)
275 self.painter.scale(1, 1)
276 self.painter.drawPolyline(p1, p2, p3, p4, p2)
277 self.painter.rotate(self.pointage.sens_X *
278 self.pointage.sens_Y * (-90))
279 self.painter.drawPolyline(p1, p2, p3, p4, p2)
280 self.painter.rotate(self.pointage.sens_X *
281 self.pointage.sens_Y * (90))
282 self.painter.translate(
283 round(-self.origine_mvt.x), round(-self.origine_mvt.y))
284 self.painter.end()
285
286 # paint speed vectors if asked
287
288 if self.speedToDraw != [] and \
289 self.trajectoire.checkBoxVectorSpeed.isChecked():
290 for obj in self.speedToDraw:
291 for (org, ext) in self.speedToDraw[obj]:
292 if org == ext: continue # si la vitesse est nulle
293 # on prend en considération éventuellement la distance
294 # du pointeur de souris;
295 # s'il est à plus de 20 pixels de l'origine du vecteur
296 # on ne trace rien
297 if self.trajectoire.radioButtonNearMouse.isChecked():
298 ecart = vecteur(qPoint = self.pos_souris) - org
299 if ecart.manhattanLength() > 20: continue
300 self.painter = QPainter()
301 self.painter.begin(self)
302 self.painter.setRenderHint(QPainter.RenderHint.Antialiasing)
303 self.painter.setPen(QColor(self.video.couleurs[int(obj) - 1]))
304 vec = ext - org # le vecteur de la flèche
305 ortho = vecteur(-vec.y, vec.x) # idem tourné de 90°
306 p1 = ext - vec * 0.1 + ortho * 0.05
307 p2 = p1 - ortho * 0.1
308 self.painter.drawPolyline(
309 org.toQPointF(),
310 ext.toQPointF(),
311 p1.toQPointF(),
312 p2.toQPointF(),
313 ext.toQPointF()
314 )
315 self.painter.end()
316 else:
317 pass # null speed
Un widget contenant une image.
Definition: image_widget.py:33
Classe pour l'élément qui remplit l'essentiel de l'onglet trajectoire paramètres du constructeur :
Definition: trajWidget.py:38
chrono
paint the origin
Definition: trajWidget.py:41
def paintEvent(self, event)
fonction de rappel pour usage avec iteration_data du videoWidget
Definition: trajWidget.py:113
def paintText(self, x, y, text, color=QColor("white"), bgcolor=QColor("lightGray"), fontsize=12, fontfamily=None, center=False)
Trace un texte (self.painter doit être actif !)
Definition: trajWidget.py:92
une classe pour des vecteurs 2D ; les coordonnées sont flottantes, et on peut accéder à celles-ci par...
Definition: vecteur.py:44