-
Notifications
You must be signed in to change notification settings - Fork 0
/
make_360_lot.py
1655 lines (1386 loc) · 77 KB
/
make_360_lot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import datetime
import os
import sys
import time
import pyproj
import urllib.request, urllib.parse, urllib.error
import urllib.parse
import logging
import xml.etree.ElementTree as ET
import shutil
from builtins import input
from collections import namedtuple
from dateutil.tz import tzlocal
from lib.exif_read import ExifRead as EXIF
from lib.exif_write import ExifEdit
from lib.geo import interpolate_lat_lon
from lib.gps_parser import get_lat_lon_time_from_gpx, get_lat_lon_time_from_nmea
logfile_name = "correlate.log"
# source for logging : http://sametmax.com/ecrire-des-logs-en-python/
# création de l'objet logger qui va nous servir à écrire dans les logs
logger = logging.getLogger()
# on met le niveau du logger à DEBUG, comme ça il écrit tout
logger.setLevel(logging.INFO)
# création d'un formateur qui va ajouter le temps, le niveau
# de chaque message quand on écrira un message dans le log
formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(message)s')
# création d'un handler qui va rediriger une écriture du log vers
# un fichier
file_handler = logging.FileHandler(logfile_name, 'w')
# on lui met le niveau sur DEBUG, on lui dit qu'il doit utiliser le formateur
# créé précédement et on ajoute ce handler au logger
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# création d'un second handler qui va rediriger chaque écriture de log
# sur la console
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)
logger.addHandler(stream_handler)
Master_Picture_infos = namedtuple('Picture_infos', ['path', 'DateTimeOriginal', 'SubSecTimeOriginal', 'Latitude', 'Longitude', 'Ele'])
Picture_infos = Master_Picture_infos(path=None, DateTimeOriginal=None, SubSecTimeOriginal=None, Latitude=None, Longitude=None, Ele=None)
New_Picture_infos = namedtuple('New_Picture_infos',
['path', 'DateTimeOriginal', 'SubSecTimeOriginal', "New_DateTimeOriginal",
"New_SubSecTimeOriginal", "Longitude", "Latitude", "Ele", "ImgDirection"])
log_infos = namedtuple('log_infos',
['log_timestamp', 'action', 'return_timestamp', 'time_to_answer', 'cam_return', 'pic_number', ])
# NOTE : modif dans lib.exifedit.py
# ajout de
# def add_subsec_time_original(self, subsec):
# """Add subsecond."""
# self.ef.exif.primary.ExtendedEXIF.SubSecTimeOriginal = subsec
# Voir si on ne peut pas récupérer directement la microsecond dans add_subsec_time_original
# et l'ajouter au tag SubSecTimeOriginal
# NOTE : correction du bug de lecture des microseconds dans EXIF
#
#
# NOTE : modif de pexif.py
# ajout de "ASCII" aux lignes 653 à 655
# TODO : pour le calcul du delta moyen sur les x premières photos, peut-être ignorer la ou les premières.
class Cam_Infos:
def __init__(self, name, dir_source, bearing, distance_from_center, logpos, log = None):
self.name = name
self.source_dir = dir_source
self.bearing = bearing
self.distance_from_center = distance_from_center
self.log_pos = logpos
self.log_list = log
self.image_list = None
self.new_image_list = None
self.log_count = None
self.pic_count = None
def add_log(self,loglist):
self.log_count = 0
self.log_list = []
for i, log_line in enumerate(loglist):
this_cam_return = True if int(log_line.cam_return[0 - (self.log_pos + 1)]) == 1 else False
self.log_list.append(log_infos(log_line.log_timestamp,
log_line.action,
log_line.return_timestamp,
log_line.time_to_answer,
this_cam_return,
log_line.pic_number))
if this_cam_return is True:
self.log_count += 1
def get_image_list(self, path_to_pics):
"""
Create a list of image tuples sorted by capture timestamp.
@param directory: directory with JPEG files
@return: a list of image tuples with time, directory, lat,long...
:param path_to_pics:
"""
print("Searching for jpeg images in ", path_to_pics, end=" ")
file_list = []
for root, sub_folders, files in os.walk(path_to_pics):
file_list += [os.path.join(root, filename) for filename in files if filename.lower().endswith(".jpg")]
files = []
# get DateTimeOriginal data from the images and sort the list by timestamp
for filepath in file_list:
#print(filepath)
metadata = EXIF(filepath)
try:
t = metadata.extract_capture_time()
s = int(t.microsecond / 1000000)
files.append(Picture_infos._replace(path=filepath, DateTimeOriginal = t, SubSecTimeOriginal = s))
# print t
# print type(t)
except KeyError as e:
# if any of the required tags are not set the image is not added to the list
print("Skipping {0}: {1}".format(filepath, e))
except Exception as e:
print("Exception on file ", filepath, e)
sys.exit()
files.sort(key=lambda file: file.DateTimeOriginal)
# print_list(files)
self.image_list = files
self.pic_count = len(self.image_list)
print("{:5} found".format(self.pic_count))
def check_pic_count(self):
"""
TODO revoir docstring
Count pic's number in the log, and count the real number of pics taken for each cam
:return: list containing the results (cam1 pic count from log, cam1 pic, cam2 pic count from log.... )
"""
print("pictures in the log vs pictures taken :")
self.log_count = 0
#TODO peut mieux faire
for log_line in self.loglist:
if log_line.cam_return == 1:
self.log_count += 1
# print("Camera {0} : {1} pictures in the log".format(cam + 1, pic_count[cam*2]))
# print("Camera {0} : {1} pictures taken".format(cam + 1, pic_count[cam*2 +1]))
print("Camera {0} : log/cam {1}/{2}".format(self.name, self.log_count, self.pic_count))
if self.pic_count > self.log_count:
print("1st log - 1st image : {0} - {1}".format(self.log_list[0].log_timestamp,
self.image_list[0].DateTimeOriginal))
print("2th log - 2th image : {0} - {1}".format(self.log_list[1].log_timestamp,
self.image_list[1].DateTimeOriginal))
print("...")
print("last-1 log - last-1 image : {0} - {1}".format(self.log_list[-2].log_timestamp,
self.image_list[-2].DateTimeOriginal))
print("last log - last image : {0} - {1}".format(self.log_list[-1].log_timestamp,
self.image_list[-1].DateTimeOriginal))
def filter_images(self):
"""
Filter the new_image list to remove the "False" and "path=None" items
"""
#TODO vérfier pourquoi j'ai du False et du None. Peut-être tout basculer
#sur la même valeur None OU False.
piclist = [j for j in self.new_image_list if isinstance(j, New_Picture_infos)]
piclist = [j for j in piclist if j.path is not None]
self.new_image_list = piclist
def filter_no_latlon(self):
"""
Filter the new_image_list to remove the pictures without lat/long data
"""
piclist = [j for j in self.new_image_list if j.Latitude]
self.new_image_list = piclist
class Cam_Group(list):
def __init__(self, cam_list=[], name=None):
self.name = name
list.__init__(self, cam_list)
def __repr__(self):
for cam in self:
print("{} - bearing: {} - pics: {}".format(cam.name, cam.bearing, cam.pic_count))
def __getattr__(self, cam_count):
return len(self)
def add_log(self, loglist):
for cam in self:
cam.add_log(loglist)
def get_image_list(self):
for cam in self:
cam.get_image_list(cam.source_dir)
def filter_images(self, data=False, latlon=False):
if data:
for cam in self:
cam.filter_images()
if latlon:
for cam in self:
cam.filter_no_latlon()
class BraceMessage(object):
"""This class here to use the new-style formating inside the logger. More details here :
https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles
"""
def __init__(self, fmt, *args, **kwargs):
self.fmt = fmt
self.args = args
self.kwargs = kwargs
def __str__(self):
return self.fmt.format(*self.args, **self.kwargs)
__ = BraceMessage
def list_geoimages(directory):
"""
Create a list of image tuples sorted by capture timestamp.
@param directory: directory with JPEG files
@return: a list of image tuples with time, directory, lat,long...
"""
file_list = []
for root, sub_folders, files in os.walk(directory):
file_list += [os.path.join(root, filename) for filename in files if filename.lower().endswith(".jpg")]
files = []
# get DateTimeOriginal data from the images and sort the list by timestamp
for filepath in file_list:
metadata = EXIF(filepath)
try:
t = metadata.extract_capture_time()
s = int(t.microsecond / 1000000)
geo = metadata.extract_geo()
lat = geo.get("latitude")
lon = geo.get("longitude")
ele = geo.get("altitude")
files.append(Picture_infos._replace(path=filepath, DateTimeOriginal = t, SubSecTimeOriginal = s,
Latitude = lat, Longitude = lon, Ele = ele))
# print t
# print type(t)
except KeyError as e:
# if any of the required tags are not set the image is not added to the list
print("Skipping {0}: {1}".format(filepath, e))
files.sort(key=lambda file: file.DateTimeOriginal)
# print_list(files)
return files
def write_metadata(image_lists):
"""
Write the exif metadata in the jpeg file
:param image_lists : A list in list of New_Picture_infos namedtuple
"""
for image_list in image_lists:
for image in image_list:
#TODO dans ces if, chercher pourquoi j'ai '' comme valeur, au lieu de None, ce qui
#rendrait la condition plus lisible (if image.Latitude is not None:)
# metadata = pyexiv2.ImageMetadata(image.path)
metadata = ExifEdit(image.path)
# metadata.read()
metadata.add_date_time_original(image.New_DateTimeOriginal)
# metadata.add_subsec_time_original(image.New_SubSecTimeOriginal)
if image.Latitude != "" and image.Longitude != "":
#import pdb; pdb.set_trace()
metadata.add_lat_lon(image.Latitude, image.Longitude)
if image.ImgDirection != "":
metadata.add_direction(image.ImgDirection)
if image.Ele != "" and image.Ele is not None:
metadata.add_altitude(image.Ele)
metadata.write()
print('Writing new Exif metadata to ', image.path)
def filter_images(piclists):
"""
Filter the image lists to remove the "False" and "path=None" items
:param piclists: A list of list of Picture_infos namedtuple
:return: The same lists, but filtered
"""
for i, piclist in enumerate(piclists):
piclist = [j for j in piclist if type(j) != bool]
piclist = [j for j in piclist if j.path is not None]
piclists[i] = piclist
return piclists
def correlate_nearest_time_exclusive(camera_obj, loglist = None, piclist = None, user_delta = True):
"""Try to find the right image for each log's timestamp.
Find the closest image for each timestamp in the log.
:param user_delta:
:param loglist: a list of log_infos nametuple
:param piclist: a list of Picture_infos namedtuple
:return: a list of New_Picture_infos namedtuple, the standard deviation between log's timestamp
and image's timestamp"""
# calcule le delta moyen log-pic sur les premiers 10% des photos
if loglist == None : loglist = camera_obj.log_list
if piclist == None : piclist = camera_obj.image_list
piclist = manual_timestamp(camera_obj, loglist, piclist)
delta_list = []
try:
for i, log_line in enumerate(loglist[:int(len(loglist) // 10 + 1)]):
if piclist[i].path is not None:
delta_list.append((log_line.log_timestamp - piclist[i].DateTimeOriginal).total_seconds())
print("{0} : calcul {1} - {2} : {3}".format(i, log_line.log_timestamp, piclist[i].DateTimeOriginal, (log_line.log_timestamp - piclist[i].DateTimeOriginal).total_seconds()))
except ZeroDivisionError:
# print("except")
delta_list.append((loglist[0].log_timestamp - piclist[0].DateTimeOriginal).total_seconds())
# print(delta_list)
#import pdb; pdb.set_trace()
try:
avg_delta = sum(delta_list) / len(delta_list)
except ZeroDivisionError:
avg_delta = -0.5
print("ecart moyen entre le log et les photos : ", avg_delta)
if user_delta:
user_delta = input("Enter a new delta value: ")
if user_delta is not None and len(user_delta) != 0:
avg_delta = float(user_delta)
gap = 0
piclist_corrected = []
logger.info(__("len loglist:{0}".format(len(loglist))))
logger.info(__("len piclist:{0}".format(len(piclist))))
logger.info(__("avg delta = {0}".format(avg_delta)))
#We will use a loglist copy as we will modify it
cp_loglist = loglist[:]
backup_delta = avg_delta
for i, pic in enumerate(piclist):
standby_delay = 0
n = 1
#print("i, gap, n", i, gap, n)
#delta = abs((loglist[i + gap].log_timestamp - pic.DateTimeOriginal).total_seconds() - avg_delta)
#print("loglist {0} et piclist {1}".format(i+gap + n, i))
#if len(piclist_corrected) > 0 and piclist_corrected[-1].new_datetimeoriginal >= log
try:
#Si le chemin de l'image suivant celle en cours de calcul est None,
# alors c'est une image virtuelle qui a été ajoutée pour recaler.
#Dans ce cas, son timestamp est forcément bon, alors on force celle en cours à la position actuelle
# en assignant une valeur de 0 à delta. Si on ne le faisait pas, avg_delta pourrait décaler l'image en cours
# à la place de l'image virtuelle.
if i + 1 < len(piclist) and piclist[i+1].path is None:
delta = 0
next_delta = 1
logger.info("l'image suivante est une Image virtuelle.")
else:
#S'il s'est passé plus de 60 secondes entre la dernière photo et celle en cours, alors les caméras se sont mise
#en veille, ce qui fait que celle en cours aura un timestamp un peu retardé par rapport aux suivantes.
#Pour cette raison, on ajout 0.8s pour éviter que la photo soit calé sur le timestamp suivant.
#Le try except est là pour éviter l'erreur pour la toute première photo.
try:
standby_delay = 0.8 if (pic.DateTimeOriginal - piclist[i-1].DateTimeOriginal).total_seconds() > 50 else 0
if standby_delay != 0:
logger.info(__("standby_delay vaut {}".format(standby_delay)))
except IndexError:
#TODO la première photo sera en retard uniquement si la cam a eu le temps de se mettre en veille depuis son
# démarrage
logger.info("première photo, elle sera un peu en retard")
standby_delay = 0.8
short_path = os.path.basename(pic.path) if pic.path is not None else "virtual image"
delta = abs((cp_loglist[i + gap].log_timestamp - pic.DateTimeOriginal).total_seconds() - avg_delta + standby_delay)
logger.info(__("A Calcul de la diff entre {0} et {1} : {2}".format(cp_loglist[i + gap].log_timestamp, short_path, delta)))
# gestion du cas de la dernière photo
if i + gap + n < len(cp_loglist):
next_delta = abs((cp_loglist[i + gap + n].log_timestamp - pic.DateTimeOriginal).total_seconds() - avg_delta + standby_delay)
logger.info(__("B Calcul de la diff entre {0} et {1} : {2}".format(cp_loglist[i + gap + n].log_timestamp, short_path, next_delta)))
else:
delta = 0
logger.info("Fin de la liste")
while next_delta <= delta:
piclist_corrected.insert(len(piclist_corrected), None)
delta = next_delta
n = n + 1
next_delta = abs(
(cp_loglist[i + gap + n].log_timestamp - pic.DateTimeOriginal).total_seconds() - avg_delta + standby_delay)
logger.info("="*10)
logger.info(__("delta = {0} pour loglist {1} et piclist {2}".format(delta, cp_loglist[i + gap + n - 1].log_timestamp, os.path.basename(pic.path))))
logger.info(__("delta2 = {0} pour loglist {1} et piclist {2}".format(next_delta, cp_loglist[i + gap + n].log_timestamp, os.path.basename(pic.path))))
new_datetimeoriginal = cp_loglist[i + gap + n - 1].log_timestamp
new_subsectimeoriginal = "%.6d" % (cp_loglist[i + gap + n - 1].log_timestamp.microsecond)
piclist_corrected.append(New_Picture_infos(pic.path, pic.DateTimeOriginal, pic.SubSecTimeOriginal,
new_datetimeoriginal, new_subsectimeoriginal, "", "", "", ""))
if pic.path is not None:
logger.info(__(">>>>Association de log {0} avec pic {1}".format(cp_loglist[i + gap + n - 1].log_timestamp, os.path.basename(pic.path))))
else:
logger.info(__(">>>>Association de log {0} avec pic {1}".format(cp_loglist[i + gap + n - 1].log_timestamp, pic.path)))
"""# On recalcule le delta habituel entre le log et les images
# TODO peut-être renommer la variable avg_delta, qui n'est plus une moyenne.
# ou bien affecter avg_delta vers une autre variable
avg_delta = (loglist[i + gap + n - 1].log_timestamp - pic.DateTimeOriginal).total_seconds()
print("Average delta : {0}".format(avg_delta))"""
except Exception as e:
logger.warning(__("Exception: {}".format(e)))
#import pdb; pdb.set_trace()
#print("i, gap, n")
#print("End of list")
gap = gap + n - 1
"""
for missing_pic in range(n - 1):
piclist_corrected.insert(len(piclist_corrected) - 1, None)
# display information
try:
print("=" * 30)
print("Il manque une photo pour {0} :".format(cp_loglist[i + gap]))
print(os.path.basename(piclist[i - 1].path))
print("et")
print(os.path.basename(piclist[i].path))
print("-" * 30)
#print("index de la photo : ", i)
#print(loglist[i + gap + n - 3])
#print(loglist[i + gap + n - 2])
#print(loglist[i + gap + n - 1])
#print("="*30)
# add a gap to correlate the next pic with the correct log_timestamp
except Exception as e:
#print (e)
pass
#gap += 1
#print("Gap est à : ", gap)
"""
# piclist_corrected = [i for i in piclist_corrected if (type(i) == New_Picture_infos and type(i.path) != None) or type(i) == bool]
deviation = standard_deviation(compute_delta3(loglist, piclist_corrected))
# print("standard deviation : ", deviation)
"""for pic in piclist_corrected:
if isinstance(pic, New_Picture_infos):
try:
#pourquoi ce print ne marche pas en logger.info ??
print(os.path.basename(pic.path),
pic.New_DateTimeOriginal,
pic.DateTimeOriginal,
(pic.New_DateTimeOriginal - pic.DateTimeOriginal).total_seconds(),
)
except TypeError:
logger.info("Virtual Image")
else:
logger.info("Pas une image")
"""
#TODO Attention, ce nouvel appel à manual_timestamp ne permettra pas de faire
# faire des modifications, puisque qu'il faut refaire la correlation ensuite.
# trouver une solution élégante pour ça. Soit supprimer la possibilité de faire des
# modifs, soit refaire la correlation ensuite.
piclist_corrected=manual_timestamp(camera_obj, loglist, piclist_corrected)
deviation = standard_deviation(compute_delta3(loglist, piclist_corrected))
print("standard deviation : ", deviation)
return piclist_corrected, deviation
def correlate_nearest_time_manual(camera_obj, loglist = None, piclist = None, user_delta = True):
"""Try to find the right image for each log's timestamp.
Find the closest image for each timestamp in the log.
:param user_delta:
:param loglist: a list of log_infos nametuple
:param piclist: a list of Picture_infos namedtuple
:return: a list of New_Picture_infos namedtuple, the standard deviation between log's timestamp
and image's timestamp"""
# calcule le delta moyen log-pic sur les premiers 5% des photos
if loglist == None : loglist = camera_obj.log_list
if piclist == None : piclist = camera_obj.image_list
idx_start = 0
idx_range = 200
total_lenght = len(loglist)
piclist = manual_timestamp(loglist, piclist)
if user_delta:
user_delta = input("Enter a new delta value: ")
if user_delta is not None:
avg_delta = float(user_delta)
#import pdb; pdb.set_trace()
piclist_corrected = []
print("len loglist:{0}".format(len(loglist)))
print("len piclist:{0}".format(len(piclist)))
for i, pic in enumerate(piclist):
n = 1
#print("i, gap, n", i, gap, n)
#delta = abs((loglist[i + gap].log_timestamp - pic.DateTimeOriginal).total_seconds() - avg_delta)
#print("loglist {0} et piclist {1}".format(i+gap + n, i))
#if len(piclist_corrected) > 0 and piclist_corrected[-1].new_datetimeoriginal >= log
try:
delta = abs((loglist[i].log_timestamp - pic.DateTimeOriginal).total_seconds() - avg_delta)
next_delta = abs((loglist[i + n].log_timestamp - pic.DateTimeOriginal).total_seconds() - avg_delta)
if pic.path is not None:
print("A Calcul de la diff entre {0} et {1}".format(loglist[i].log_timestamp, os.path.basename(pic.path)))
print("B Calcul de la diff entre {0} et {1}".format(loglist[i + n].log_timestamp, os.path.basename(pic.path)))
while next_delta <= delta:
print("="*10)
print("delta = {0} pour loglist {1} et piclist {2}".format(delta, loglist[i].log_timestamp, os.path.basename(pic.path)))
print("delta2 = {0} pour loglist {1} et piclist {2}".format(abs((loglist[i + n].log_timestamp - pic.DateTimeOriginal).total_seconds() - avg_delta), loglist[i + n].log_timestamp, os.path.basename(pic.path)))
delta = next_delta
n = n + 1
next_delta = abs(
(loglist[i + n].log_timestamp - pic.DateTimeOriginal).total_seconds() - avg_delta)
new_datetimeoriginal = loglist[i + n - 1].log_timestamp
new_subsectimeoriginal = "%.6d" % (loglist[i + n - 1].log_timestamp.microsecond)
piclist_corrected.append(New_Picture_infos(pic.path, pic.DateTimeOriginal, pic.SubSecTimeOriginal,
new_datetimeoriginal, new_subsectimeoriginal, "", "", "", ""))
if pic.path is not None:
print(">>>>Association de log {0} avec pic {1}".format(loglist[i + n - 1].log_timestamp, os.path.basename(pic.path)))
"""# On recalcule le delta habituel entre le log et les images
# TODO peut-être renommer la variable avg_delta, qui n'est plus une moyenne.
# ou bien affecter avg_delta vers une autre variable
avg_delta = (loglist[i + gap + n - 1].log_timestamp - pic.DateTimeOriginal).total_seconds()
print("Average delta : {0}".format(avg_delta))"""
except Exception as e:
print("Exception:", e)
# print("i, gap, n")
# print("End of list")
# pass
for missing_pic in range(n - 1):
piclist_corrected.insert(len(piclist_corrected) - 1, None)
# display information
try:
print("=" * 30)
print("Il manque une photo pour {0} :".format(loglist[i]))
print(os.path.basename(piclist[i - 1].path))
print("et")
print(os.path.basename(piclist[i].path))
print("-" * 30)
#print("index de la photo : ", i)
#print(loglist[i + gap + n - 3])
#print(loglist[i + gap + n - 2])
#print(loglist[i + gap + n - 1])
#print("="*30)
# add a gap to correlate the next pic with the correct log_timestamp
except Exception as e:
#print (e)
pass
#gap += 1
#print("Gap est à : ", gap)
for idx in range(n):
loglist[i + idx] = loglist[i + idx]._replace(log_timestamp = loglist[i + idx].log_timestamp - datetime.timedelta(days = 20))
# piclist_corrected = [i for i in piclist_corrected if (type(i) == New_Picture_infos and type(i.path) != None) or type(i) == bool]
deviation = standard_deviation(compute_delta3(loglist, piclist_corrected))
# print("standard deviation : ", deviation)
#import pdb; pdb.set_trace()
for pic in piclist_corrected:
if isinstance(pic, New_Picture_infos):
try:
print(os.path.basename(pic.path), pic.New_DateTimeOriginal, pic.DateTimeOriginal, (pic.New_DateTimeOriginal - pic.DateTimeOriginal).total_seconds())
except Exception as e:
print(e)
return piclist_corrected, deviation
def manual_timestamp(camera_obj, loglist = None, piclist = None, user_delta = True):
if loglist == None : loglist = camera_obj.log_list
if piclist == None : piclist = camera_obj.image_list
idx_start = 0
idx_range = 100
total_lenght = len(loglist)
piclist = piclist[:]
#import pdb; pdb.set_trace()
while True:
delta_list = []
idx_end = idx_start + idx_range if idx_start + idx_range < total_lenght else total_lenght
for i, log_line in enumerate(loglist[idx_start:idx_end], idx_start):
try:
if piclist[i] is not None and piclist[i].path is not None:
delta_list.append((log_line.log_timestamp - piclist[i].DateTimeOriginal).total_seconds())
#TODO ajouter une indication si la cam a répondu (T ou F, true false)
"""print("{0:8} : calcul {1}{2} - {3}{4} : {5}".format(i,
log_line.log_timestamp,
'T' if camera_obj.cam_return[i] == True else 'F',
piclist[i].DateTimeOriginal,
'T' if piclist[i] is not None and piclist[i].path is not None else 'F',
(log_line.log_timestamp - piclist[i].DateTimeOriginal).total_seconds()))"""
print("{0:8} : calcul {1}{2} - {3}{4} : {5}".format(i,
log_line.log_timestamp,
'T' if log_line.cam_return == True else 'F',
piclist[i].DateTimeOriginal,
'T' if piclist[i] is not None and piclist[i].path is not None else 'F',
(log_line.log_timestamp - piclist[i].DateTimeOriginal).total_seconds()))
except ZeroDivisionError:
# print("except")
delta_list.append((loglist[0].log_timestamp - piclist[0].DateTimeOriginal).total_seconds())
# print(delta_list)
except IndexError:
print("{0:8} : calcul {1}{2}".format(i, log_line.log_timestamp, 'T' if log_line.cam_return == True else 'F'))
except AttributeError:
print("{0:8} : calcul {1}{2}".format(i, log_line.log_timestamp, 'T' if log_line.cam_return == True else 'F'))
try:
avg_delta = sum(delta_list) / len(delta_list)
print("ecart moyen entre le log et les photos : ", avg_delta)
print("ecart min : {}".format(min(delta_list)))
print("ecart max : {}".format(max(delta_list)))
except ZeroDivisionError as e:
avg_delta = "ZeroDivisionError"
#avg_delta = 1.5
print("Type 'a10' to insert a virtual pic before index 10")
print("Type 'r23' to remove a pic at index 23")
print("Press 'Enter' to go to the next range")
print("Press 's' to move to the list beginning")
print("Press 'q' to quit this menu")
value = input("Your command: ")
if len(value) > 1:
idx = int(value[1:])
if value[0].lower() == 'a':
piclist.insert(idx, New_Picture_infos(None, loglist[idx].log_timestamp, None, None, None, None, None, None, None))
elif value[0].lower() == 'r':
del(piclist[idx])
idx_start = idx -5 if idx > 5 else 0
elif len(value) == 0:
#TODO gérer lorsque index error
if idx_end + idx_range <= total_lenght:
idx_start += idx_range
else:
idx_start = total_lenght - idx_range
elif len(value) == 1 and value[0].lower() == 'm':
piclist = insert_missing_timestamp(cam)
idx_start = 0
elif len(value) == 1 and value[0].lower() == 'q':
break
elif len(value) == 1 and value[0].lower() == 's':
idx_start = 0
elif len(value) == 1 and value[0].lower() == 'd':
import pdb; pdb.set_trace()
print("len loglist:{0}".format(len(loglist)))
print("len piclist:{0}".format(len(piclist)))
return piclist
def correlate_manual(camera_obj, loglist = None, piclist = None, user_delta = True):
"""Try to find the right image for each log's timestamp.
Find the closest image for each timestamp in the log.
:param user_delta:
:param loglist: a list of log_infos nametuple
:param piclist: a list of Picture_infos namedtuple
:return: a list of New_Picture_infos namedtuple, the standard deviation between log's timestamp
and image's timestamp"""
if loglist == None : loglist = camera_obj.log_list
if piclist == None : piclist = camera_obj.image_list
piclist = manual_timestamp(cam, loglist, piclist)
piclist_corrected = []
for log_line, pic in zip(loglist, piclist):
new_datetimeoriginal = log_line.log_timestamp
new_subsectimeoriginal = "%.6d" % (log_line.log_timestamp.microsecond)
# single_cam_image_list[i] = single_cam_image_list[i]._replace(New_DateTimeOriginal=new_datetimeoriginal, New_SubSecTimeOriginal=new_subsectimeoriginal)
piclist_corrected.append(New_Picture_infos(pic.path,
pic.DateTimeOriginal,
pic.SubSecTimeOriginal,
new_datetimeoriginal, new_subsectimeoriginal, "", "",
"", ""))
# piclist_corrected = [i for i in piclist_corrected if (type(i) == New_Picture_infos and type(i.path) != None) or type(i) == bool]
deviation = standard_deviation(compute_delta3(loglist, piclist_corrected))
print("standard deviation : ", deviation)
#import pdb; pdb.set_trace()
"""
for pic in piclist_corrected:
if isinstance(pic, New_Picture_infos):
try:
print(os.path.basename(pic.path), pic.New_DateTimeOriginal, pic.DateTimeOriginal, (pic.New_DateTimeOriginal - pic.DateTimeOriginal).total_seconds())
except Exception as e:
print(e)
"""
return piclist_corrected, deviation
"""Try to find the right image for each log's timestamp.
Compute timedelta (from the beginning) between x+1 and x log's timestamp, timedelta between x+1 and x pic timestamp,
and compute the timedelta between y pic timedelta and y log timedelta.
The longest double difference timedelta will be used for the missing images.
Then the timestamps from the log are copied to the images.
:param loglist: a list of log_infos nametuple
:param piclist: a list of Picture_infos namedtuple
:param pic_count_diff: how many images are missing
:param cam_number: cam's number
:return: a list of New_Picture_infos namedtuple, the standard deviation between log's timestamp
and image's timestamp"""
# On va calculer le delta entre chaque déclenchement du log, et entre les photos
# Calcul du delta entre les logs
log_pic_delta = []
for i, log_line in enumerate(loglist[:-1]):
log_pic_delta.append((loglist[i + 1].log_timestamp - loglist[i].log_timestamp).total_seconds())
# Calcul du delta entre les images
pic_delta = []
for i, pic_timestamp in enumerate(piclist[:-1]):
# On calcule le delta entre 2 images prises par la caméra
pic_delta.append((piclist[i + 1].DateTimeOriginal - piclist[i].DateTimeOriginal).total_seconds())
# print("log_pic_delta : ", log_pic_delta)
# print("pic_delta : ", pic_delta)
# ========================================================================
# Calcul du delta entre les delta, depuis le début vers la fin
inter_delta = []
for i, delta in enumerate(log_pic_delta):
try:
inter_delta.append(log_pic_delta[i] - pic_delta[i])
except:
# print("fin de liste")
pass
# print("inter_delta", inter_delta)
# print(sorted(inter_delta))
# Dans le cas où on a des variations de vitesse et des images manquantes, les inter_delta peuvent êtres
# trompeur. On supprime ceux qui sont incorrectes, en faisant la division de la valeur max par la valeur min.
# Si elle est < -1 il ne faut pas tenir compte de ces min max.
# print("Max est : ", max(inter_delta))
# print ("Min est : ", min(inter_delta))
while max(inter_delta) / min(inter_delta) < -1:
inter_delta[inter_delta.index(max(inter_delta))] = 0
inter_delta[inter_delta.index(min(inter_delta))] = 0
# print("On met a 0 inter_delta ", inter_delta.index(max(inter_delta)))
# print("On met a 0 inter_delta ", inter_delta.index(min(inter_delta)))
# print("inter_delta sans les bad max-min : ")
# print(inter_delta)
# Maintenant, on cherche la ou les valeurs max nécessaires
idx = []
for i in range(pic_count_diff):
idx.append(inter_delta.index(min(inter_delta)))
inter_delta[idx[i]] = 0
print("=" * 30)
print("idx ordre normal : ", idx)
for i in idx:
print()
print("Il manque la photo entre :")
print(piclist[i])
print("et")
print(piclist[i + 1], ".\n")
print("=" * 30)
# On trie la liste d'index pour éviter d'insérer au même endroit car
# le fait d'insérer décale l'insertion suivante
idx.sort()
# On insère une "image vide" à "idx +1"
for i, missing_pic in enumerate(idx):
piclist.insert(missing_pic + i + 1, False)
# C'est bon, on peut recopier les timestamps depuis le log
piclist_corrected = []
for i, log_line in enumerate(loglist):
# print ("test int : ", int(log_line.cam_return[0 - (cam_number +1)]) == 1)
# print ("test type : ", type(piclist[i]) == Picture_infos)
# print("type est : ", type(piclist[i]))
# print("retour de isinstance(piclist[i], Picture_infos): ", isinstance(piclist[i], Picture_infos))
if type(piclist[i]) == Picture_infos:
new_datetimeoriginal = log_line.log_timestamp
new_subsectimeoriginal = "%.6d" % (log_line.log_timestamp.microsecond)
piclist_corrected.append(New_Picture_infos(piclist[i].path,
piclist[i].DateTimeOriginal,
piclist[i].SubSecTimeOriginal,
new_datetimeoriginal,
new_subsectimeoriginal,
"", "", "", ""))
elif type(piclist[i]) == bool:
piclist_corrected.append(piclist[i])
# print("piclist_corrected : ", piclist_corrected)
deviation = standard_deviation(compute_delta3(loglist, piclist_corrected))
return piclist_corrected, deviation
"""Try to find the right image for each log's timestamp.
Compute timedelta (from the end) between x+1 and x log's timestamp, timedelta between x+1 and x pic timestamp,
and compute the timedelta between y pic timedelta and y log timedelta.
The longest double difference timedelta will be used for the missing images.
Then the timestamps from the log are copied to the images.
:param loglist: a list of log_infos nametuple
:param piclist: a list of Picture_infos namedtuple
:param pic_count_diff: how many images are missing
:param cam_number: cam's number
:return: a list of New_Picture_infos namedtuple, the standard deviation between log's timestamp
and image's timestamp"""
# On va calculer le delta entre chaque déclenchement du log, et entre les photos
# Calcul du delta entre les logs
log_pic_delta = []
for i, log_line in enumerate(loglist[:-1]):
log_pic_delta.append((loglist[i + 1].log_timestamp - loglist[i].log_timestamp).total_seconds())
# Calcul du delta entre les images
pic_delta = []
for i, pic_timestamp in enumerate(piclist[:-1]):
# On calcule le delta entre 2 images prises par la caméra
pic_delta.append((piclist[i + 1].DateTimeOriginal - piclist[i].DateTimeOriginal).total_seconds())
# print("log_pic_delta : ", log_pic_delta)
# print("pic_delta : ", pic_delta)
# ========================================================================
# Calcul du delta entre les delta, depuis la fin vers le début
inter_delta_reverse = []
log_pic_delta_reversed = log_pic_delta[::-1]
pic_delta_reversed = pic_delta[::-1]
for i, delta in enumerate(log_pic_delta_reversed):
try:
inter_delta_reverse.append(log_pic_delta_reversed[i] - pic_delta_reversed[i])
except:
# print("fin de liste")
pass
# print("inter_delta_reverse")
# print(inter_delta_reverse)
# Dans le cas où on a des variations de vitesse et des images manquantes, les inter_delta peuvent êtres
# trompeur. On supprime ceux qui sont incorrectes, en faisant la division de la valeur max par la valeur min.
# Si elle est < -1 il ne faut pas tenir compte de ces min max.
# print("Max reverse est : ", max(inter_delta_reverse))
# print ("Min reverse est : ", min(inter_delta_reverse))
while max(inter_delta_reverse) / min(inter_delta_reverse) < -1:
inter_delta_reverse[inter_delta_reverse.index(max(inter_delta_reverse))] = 0
inter_delta_reverse[inter_delta_reverse.index(min(inter_delta_reverse))] = 0
# print("On met a 0 inter_delta_reverse ", inter_delta_reverse.index(max(inter_delta_reverse)))
# print("On met a 0 inter_delta_reverse ", inter_delta_reverse.index(min(inter_delta_reverse)))
# print("inter_delta_reverse sans les bad max-min", inter_delta_reverse)
idx = []
for i in range(pic_count_diff):
idx.append(inter_delta_reverse.index(min(inter_delta_reverse)))
inter_delta_reverse[idx[i]] = 0
print("=" * 30)
print("idx ordre inverse : ", idx)
for i in idx:
print("Il manque la photo entre :")
print(piclist[-(i + 2)])
print("et")
print(piclist[-(i + 1)])
print("=" * 30)
# On trie la liste d'index pour éviter d'insérer au même endroit car
# le fait d'insérer décale l'insertion suivante
idx.sort(reverse=True)
# On insère une "image vide" à "idx-1"
for missing_pic in idx:
piclist.insert(len(piclist) - missing_pic - 1, False)
# print("On insert un False à ", len(piclist)-missing_pic-1)
# print(piclist[len(piclist)-missing_pic-1])
# print(piclist[len(piclist)-missing_pic-2])
# C'est bon, on peut recopier les timestamps depuis le log
# debug
# for pic in piclist[40:60]:
# print(pic)
piclist_corrected = []
for i, log_line in enumerate(loglist):
if int(log_line.cam_return[0 - (cam_number + 1)]) == 1 and type(piclist[i]) == Picture_infos:
new_datetimeoriginal = log_line.log_timestamp
new_subsectimeoriginal = "%.6d" % (log_line.log_timestamp.microsecond)
piclist_corrected.append(New_Picture_infos(piclist[i].path,
piclist[i].DateTimeOriginal,
piclist[i].SubSecTimeOriginal,
new_datetimeoriginal,
new_subsectimeoriginal,
"", "", "", ""))
elif type(piclist[i]) == bool:
piclist_corrected.append(piclist[i])
deviation = standard_deviation(compute_delta3(loglist, piclist_corrected))
# print("standard deviation : ", deviation)
return piclist_corrected, deviation
def insert_missing_timestamp(cam):
"""Insert missing timestamp in the piclists, when the log indicate that the cam didn't answer to the shutter request
:param cam: a Cam_Infos object
:return: the list of Picture_infos namedtuple with the missing timestamp inserted
"""
# On insert les timestamps qu'on sait manquants (caméra qui n'ont pas répondu, donc 0 dans le log).
# Cela évite de fausser les calculs des différences de temps entre chaque images
new_piclist = []
gap = 0
for i, log_line in enumerate(cam.log_list):
if log_line.cam_return is True:
try:
new_piclist.append(cam.image_list[i - gap])
# print("Ajout en position {0} de {1}".format(i, piclists[cam][i]))
except:
# print("End of list")
pass
else:
try:
new_piclist.append(Picture_infos._replace(path=None, DateTimeOriginal = log_line.log_timestamp))
gap += 1
# print("Ajout en position {0} de {1}".format(i, Picture_infos(None, log_line.log_timestamp, 0)))
except:
# print("End of list")
pass
return new_piclist
def correlate_log_and_pic(camera_obj, auto=True):
"""Correlate the images timestamp with the log timestamps.
If there are more log's timestamps than pic'count, 3 different algorithms will try to find
which timestamp has no image related to the it.
:param camera_obj:
:param auto:
:return: a new list of New_Picture_infos namedtuple with the more accurate timestamps.
"""
piclist_corrected = []
pic_count_diff = cam.log_count - cam.pic_count
single_cam_image_list = insert_missing_timestamp(cam)
#print("single_cam_image_list : ", single_cam_image_list)
original_deviation = standard_deviation(compute_delta3(cam.log_list, single_cam_image_list))
if auto:
if pic_count_diff == 0:
print("Camera {0} : Exact correlation between logfile and pictures".format(camera_obj.name))
for i, log_line in enumerate(camera_obj.log_list):
if log_line.cam_return is True:
new_datetimeoriginal = log_line.log_timestamp
new_subsectimeoriginal = "%.6d" % (log_line.log_timestamp.microsecond)
# single_cam_image_list[i] = single_cam_image_list[i]._replace(New_DateTimeOriginal=new_datetimeoriginal, New_SubSecTimeOriginal=new_subsectimeoriginal)
single_cam_image_list[i] = New_Picture_infos(single_cam_image_list[i].path,
single_cam_image_list[i].DateTimeOriginal,
single_cam_image_list[i].SubSecTimeOriginal,
new_datetimeoriginal, new_subsectimeoriginal, "", "",
"", "")
#piclist_corrected = correlate_nearest_time_manual(camera_obj.log_list, camera_obj.image_list[:])
#deviation = standard_deviation(compute_delta3(camera_obj.log_list, nearest))
#print("standard deviation after correction: ", deviation)
#debug :
for pic in piclist_corrected:
if isinstance(pic, New_Picture_infos):
print(os.path.basename(pic.path), pic.New_DateTimeOriginal, pic.DateTimeOriginal, (pic.New_DateTimeOriginal - pic.DateTimeOriginal).total_seconds())
piclist_corrected = single_cam_image_list