4 pointage, a module for pymecavideo:
5 a program to track moving points
in a video frameset
7 Copyright (C) 2023 Georges Khaznadar <georgesk
@debian.org>
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.
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.
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/>.
23from PyQt6.QtCore import QObject
24from PyQt6.QtGui import QMouseEvent
26from vecteur import vecteur
27from echelle import echelle
29from collections import deque
31class Pointage(QObject):
33 Une classe pour représenter les pointages : séquences éventuellement
34 creuses, de quadruplets (date, désignation d'objet, vecteur)
37 QObject.__init__(self)
43 self.data y est un dictionaire ordonné, qui a pour clés des dates
44 croissantes ; chaque date renvoie un dictionnaire de type
45 désignation d'objet => vecteur.
47 self.suivis est une liste limitative de désignations d'objets
49 self.deltaT est l'intervalle de temps entre deux images d'une vidéo
51 self.
echelle est l
'échelle en px par mètre
68 retire le dernier pointage de self.data et l'empile dans
73 t = self.
dates[der - 1]
75 self.
data[t] = {obj:
None for obj
in self.
suivis}
80 dépile un pointage de self.defaits et le rajoute à la fin de
85 if der
and der < self.image_max:
90 self.
data[t] = pointage
95 @return vrai si on peut défaire un pointage
101 @return vrai si on peut refaire un pointage
107 purge les données à refaire si
108 on vient de cliquer sur la vidéo pour un pointage
123 Crée les structures de données quand on en connaît par avance
125 @param n_suivis le nombre d
'objets à suivre par pointage
126 @param deltaT l
'intervalle de temps
127 @param n_images le nombre d
'images de la vidéo étudiée
129 self.suivis = list(range(1, n_suivis+1))
131 self.
dates = [deltaT * i
for i
in range(n_images)]
133 for index
in range(n_images):
137 self.
data[index*deltaT] = {o:
None for o
in self.
suivis}
140 def pointe(self, objet, position, index=None, date=None):
142 ajoute un pointage aux données ; on peut soit préciser l'index
143 et la date s'en déduit, soit directement la date
144 @param objet la désignation d
'un objet suivi ; couramment : un nombre
146 @param index s
'il est donné la date est index * self.deltaT
147 @param date permet de donner directement la date ; l
'index reste
150 if index
is None and date
is None:
152 "index et date tous deux inconnus pour Pointage.pointe")
153 if isinstance(position, QMouseEvent):
154 position =
vecteur(qPoint = position.position())
155 elif isinstance(position, vecteur):
158 raise Exception(
"dans Pointage.pointe, la position est soit QMouseEvent, soit vecteur")
159 if index
is not None:
160 date = index * self.
deltaT
161 if date
not in self.
data:
162 raise Exception(f
"date incorrecte dans Pointage.pointe : {date}")
163 self.
data[date][objet] = position
166 def position(self, objet, index=None, date=None, unite="px"):
168 ajoute un pointage aux données ; on peut soit préciser l'index
169 et la date s'en déduit, soit directement la date
170 @param objet la désignation d
'un objet suivi ; couramment : un nombre
171 @param index s
'il est donné la date est index * self.deltaT
172 @param date permet de donner directement la date ; l
'index reste
174 @param unite l
'unité du vecteur position : peut être "px" pour pixel
175 (par défaut) ou "m" pour mètre
177 @return un vecteur : position de l
'objet à la date donnée
179 if index
is None and date
is None:
181 "index et date tous deux inconnus pour Pointage.position")
182 if index
is not None:
183 date = index * self.
deltaT
184 if date
not in self.
dates:
185 raise Exception(
"date incorrecte dans Pointage.pointe")
187 return self.
data[date][objet]
191 raise Exception(f
"dans Pointage.position, unité illégale {unite}")
197 if self.
dates is not None:
198 return len(self.
dates)
203 @return faux si toutes les pointages sont
None
214 donne le numéro de la première image pointée (1 au minimum),
215 ou None si aucun pointage n
'est fait
217 for i, t
in enumerate(self.
dates):
224 donne le numéro de la dernière image pointée (on compte à partir de 1),
225 ou None si aucun pointage n
'est fait
227 for i, t
in zip(list(range(len(self.
dates))[::-1]), self.
dates[::-1]):
232 def csv_string(self, sep =";", unite="px", debut=1, origine=vecteur(0,0)):
234 renvoie self.data sous une forme acceptable (CSV)
235 @param sep le séparateur de champ, point-virgule par défaut.
236 @param unite l
'unité du vecteur position : peut être "px" pour pixel
237 (par défaut) ou "m" pour mètre
238 @param debut la première image qui a été pointée
239 @param origine un vecteur pour l
'origine du repère ; (0,0) par défaut
246 raise Exception(f
"dans Pointage.trajectoire, unité illégale {unite}")
251 en_tete.append(f
"x{o}")
252 en_tete.append(f
"y{o}")
253 result.append(sep.join(en_tete))
254 dates = list(self.
data.keys())
255 dates_pointees = dates[debut-1:]
256 dd = zip(dates, dates_pointees)
257 for t, t_point
in list(dd) :
263 if self.
data[t_point][o]
is not None:
264 ligne.append(f
"{self.sens_X * (self.data[t_point][o].x - origine.x) * mul:4g}")
265 ligne.append(f
"{- self.sens_Y * (self.data[t_point][o].y - origine.y) * mul:4g}")
266 result.append(sep.join(ligne))
268 return "\n".join(result)
270 def trajectoire(self, objet, mode="liste", unite = "px"):
272 @param objet la désignation d
'un objet suivi ; couramment : un nombre
273 @param mode
"liste" ou
"dico" (
"liste" par défaut)
274 @param unite l
'unité du vecteur position : peut être "px" pour pixel
275 (par défaut) ou "m" pour mètre
277 @return une liste de vecteurs (ou
None quand la position est inconnue)
278 les mode =
"liste", sinon un dictionnaire date=>vecteur
285 raise Exception(f
"dans Pointage.trajectoire, unité illégale {unite}")
287 return [self.
data[t][objet]*mul
for t
in self.
dates]
288 return {t: self.
data[t][objet]*mul
for t
in self.
dates}
292 renvoie la séquence de positions d'un objet pointé (seulement là
293 où il a été pointé, ni avant, ni après)
294 @param obj un des objets mobiles pointés
295 @return une liste [instance de vecteur, ...]
297 return [self.
data[t][obj]
for t
in self.
dates if self.
data[t][obj]]
301 renvoie un dictionnaire objet => trajectoire de l'objet
302 @return { objet: [instance de vecteur, ...], ...}
308 renvoie la liste des numéros des images pointés au long des
309 trajectoire. N.B. : la première image d'un film est numérotée 1
310 @param debut permet de choisir le numéro de la toute première image
311 du film (1 par défaut)
313 return [i + debut
for i,t
in enumerate(self.
dates)
318 renvoie un point, dont les coordonnées sont en mètre, dans un
319 référentiel "à l'endroit"
320 @param p un point en
"coordonnées d'écran"
322 self.dbg.p(2, "rentre dans 'pointEnMetre'")
323 if p
is None:
return None
330 Une routine d'itération généralisée qui permet de lancer une action
331 spécifique pour chaque date et une action pour chaque pointage.
333 @param callback_t est
None, ou une fonction de rappel dont les
334 paramètres sont i (index commençant à 0), t (la date) ;
335 cette fonction de rappel prend soin des « lignes » de données
336 @param callback_p est
None, ou une fonction de rappel dont les
337 paramètres sont i, t, j (index d
'objet commençant à 0),
338 obj (un objet suivi) et p son pointage de type vecteur,
339 v sa vitesse, de type vecteur ; cette fonction de rappel
340 prend soin de chacune des « cases » de données
341 @param unite (
"px", pour pixels, par défaut) si l
'unité est "px",
342 les données brutes du pointage en pixels sont renvoyées ; si
343 l'unité est "m" alors les coordonnées du point sont en mètre
345 if self.
dates is None:
return
346 precedents = [
None] * self.
nb_obj
347 for i,t
in enumerate(self.
dates):
348 if callback_t
is not None:
350 for j, obj
in enumerate(self.
suivis):
351 if callback_p
is not None:
352 p = self.
data[t][obj]
354 if p
is not None and precedents[j]
is not None:
355 v = (p - precedents[j]) * (1 / self.
deltaT)
359 callback_p(i, t, j, obj, p, v)
364 Permet de lancer une itération pour chacun des objets suivis
365 @param cb_o une fonction de rappel, utilisée itérativement pour
366 chaque objet. Les paramètres de cette fonction sont :
367 i : un index d
'objet débutant à 0, obj : un objet suivi
368 @param cb_p une fonction de rappel, utilisée pour chacun des points
369 du pointage. Les paramètres de cette fonction sont :
370 i : un index d
'objet débutant à 0, obj : un objet suivi,
371 p : un pointage (de type vecteur)
372 @param unite (
"px", pour pixels, par défaut) si l
'unité est "px",
373 les données brutes du pointage en pixels sont renvoyées ; si
374 l'unité est "m" alors les coordonnées du point sont en mètre
376 for i, o
in enumerate(self.
suivis):
387 renvoie la liste des dates où on a pointé des positions
388 @return une liste [float, ...]
390 return [t
for t
in self.
dates
395 renvoie la liste des pointages pour un objet
396 @param obj désigne l
'objet choisi; si obj est None,
397 c'est le premier des objets
399 if obj
is None: obj = self.
suivis[0]
400 return [self.
data[t][obj]
for t
in self.
dates
401 if self.
data[t][obj]
is not None]
406 @return le nombre d
'objets suivis
408 if self.
suivis is None:
return 0
Une classe pour représenter les pointages : séquences éventuellement creuses, de quadruplets (date,...
def pointe(self, objet, position, index=None, date=None)
ajoute un pointage aux données ; on peut soit préciser l'index et la date s'en déduit,...
def pointEnMetre(self, p)
renvoie un point, dont les coordonnées sont en mètre, dans un référentiel "à l'endroit"
def une_trajectoire(self, obj)
renvoie la séquence de positions d'un objet pointé (seulement là où il a été pointé,...
def index_trajectoires(self, debut=1)
renvoie la liste des numéros des images pointés au long des trajectoire.
def refaire(self)
dépile un pointage de self.defaits et le rajoute à la fin de self.data
def premiere_image(self)
donne le numéro de la première image pointée (1 au minimum), ou None si aucun pointage n'est fait
def position(self, objet, index=None, date=None, unite="px")
ajoute un pointage aux données ; on peut soit préciser l'index et la date s'en déduit,...
def iteration_objet(self, cb_o, cb_p, unite="px")
Permet de lancer une itération pour chacun des objets suivis.
def iteration_data(self, callback_t, callback_p, unite="px")
Une routine d'itération généralisée qui permet de lancer une action spécifique pour chaque date et un...
def les_trajectoires(self)
renvoie un dictionnaire objet => trajectoire de l'objet
def trajectoire(self, objet, mode="liste", unite="px")
def purge_defaits(self)
purge les données à refaire si on vient de cliquer sur la vidéo pour un pointage
def derniere_image(self)
donne le numéro de la dernière image pointée (on compte à partir de 1), ou None si aucun pointage n'e...
def defaire(self)
retire le dernier pointage de self.data et l'empile dans self.defaits
def liste_pointages(self, obj=None)
renvoie la liste des pointages pour un objet
def liste_t_pointes(self)
renvoie la liste des dates où on a pointé des positions
def dimensionne(self, n_suivis, deltaT, n_images)
Crée les structures de données quand on en connaît par avance le nombre.
def csv_string(self, sep=";", unite="px", debut=1, origine=vecteur(0, 0))
renvoie self.data sous une forme acceptable (CSV)
def clearEchelle(self)
oublie la valeur de self.echelle_image
def init_pointage(self)
self.data y est un dictionaire ordonné, qui a pour clés des dates croissantes ; chaque date renvoie u...
une classe pour des vecteurs 2D ; les coordonnées sont flottantes, et on peut accéder à celles-ci par...