interekt.py 199 KB
Newer Older
hchauvet's avatar
hchauvet committed
1
2
3
4
5
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Created on Mon May 18 14:14:54 2015

Félix Hartmann's avatar
Félix Hartmann committed
6
7
8
9
Interekt: INTERactive Exploration of Kinematics and Tropisms

Graphical user interface for analyzing plant organ movements from sequences of
photographs.
hchauvet's avatar
hchauvet committed
10

11
@authors: Hugo Chauvet and Félix Hartmann
hchauvet's avatar
hchauvet committed
12

13
Changes:
Félix Hartmann's avatar
Félix Hartmann committed
14
15
16
04/02/2022: [Félix] More versatile version, which deals with poplar stems and has a
                    contextual organ menu based on the conceptual model chosen.
09/04/2021: [Félix] Multilingual support for English, Esperanto and French + bugfixes.
Félix Hartmann's avatar
Félix Hartmann committed
17
18
19
20
21
05/10/2020: [Félix] A lot of changes: bugfixes for Py2/3 compatibility
                    + multilingual support with gettext (English-only for now)
                    + partial refactoring, for the multilingual support
                    + new ways of estimating the growth length
                    + new estimation of B, with the selection of a steady-state image
22
04/06/2020: [Félix] Transition to Python3, while maintaining compatibility with Python2.
23
24
29/05/2020: [Félix] Adding growth rate estimation for computing beta_tilde and beta_tilde
                    + improved export of phenotyping data to CSV file.
Félix Hartmann's avatar
Félix Hartmann committed
25
26
20/01/2020: [Félix] Adding Lagrangian marks
                    + popup window for manualy estimating the growth length using curvature heatmap
27
06/09/2019: [Hugo] Fin de la première version compatible HDF5
Félix Hartmann's avatar
Félix Hartmann committed
28
19/07/2019: [Félix] Ajout du calcul de gamma pour expérience sous clinostat, Hugo ajout de la fonction reset_globals_data
29
04/05/2019: [Hugo] Correct bugs on export when tige_id_mapper was defined with string names for bases. Allow float in the slide to select sensitivity.
30
24/09/2018: [Hugo] Remove test dectection (ne marche pas en Thread avec matplotlib!) + Correction bug pour traiter une seule image.
31
08/06/2018: [Hugo] Correct queue.qsize() bug in osX (car marche pas sur cette plateforme
32
18/04/2018: [Hugo] correct datetime bug. Set percent_diam to 1.4 in MethodOlivier (previous 0.9). Windows system can now use thread
33
22/10/2017: [Hugo] Optimisation du positionnement du GUI, utilisation de Tk.grid a la place de Tk.pack
34
16/10/2015: [Hugo] Ajout de divers options pour la tige (suppression etc..) avec menu click droit
hchauvet's avatar
hchauvet committed
35
36
37
38
39
40
                   +Refactorisation de plot_image et de la gestion du global_tige_id_mapper (pour gerer les suppressions)

30/07/2015: [Hugo] Correction bugs pour les racines dans libgravimacro + Ajout visu position de la moyenne de l'ange + Ajout d'options pour le temps des photos
25/05/2015: [Hugo] Ajout des menus pour l'export des moyennes par tiges et pour toutes les tiges + figures
20/05/2015: [Hugo] Première version
"""
41

Félix Hartmann's avatar
Félix Hartmann committed
42
43
from __future__ import (unicode_literals, absolute_import, division, print_function)

44
45
import sys, os
if sys.version_info[0] < 3:
46
47
48
49
50
    python2 = True
else:
    python2 = False

if python2:
51
    import Tkinter as Tk, tkFileDialog as filedialog, tkMessageBox as messagebox
52
    from ttk import Style, Button, Frame, Progressbar, Label, Entry, Scale, Checkbutton
53
54
55
56
    import Queue as queue
else:
    import tkinter as Tk
    from tkinter import filedialog, messagebox
57
58
    from tkinter.ttk import (Style, Button, Frame, Progressbar, Label, Entry, Scale,
                             Checkbutton)
59
60
    import queue

61
if python2:
62
63
    import cPickle as pkl

hchauvet's avatar
hchauvet committed
64
65
66
67
68
69
70
71
72
import matplotlib
matplotlib.use('TkAgg')

#Remove zoom key shorcuts
matplotlib.rcParams['keymap.back'] = 'c'
matplotlib.rcParams['keymap.forward'] = 'v'

import matplotlib.pylab as mpl
from matplotlib.collections import LineCollection
73
from matplotlib.ticker import MultipleLocator
Félix Hartmann's avatar
Félix Hartmann committed
74
75
76
from matplotlib.colors import SymLogNorm
from matplotlib.widgets import RectangleSelector
from matplotlib.backend_bases import key_press_handler
77
if python2:
78
79
80
81
    from matplotlib.backends.backend_tkagg import (
        FigureCanvasTkAgg, NavigationToolbar2TkAgg as NavigationToolbar2Tk)
else:
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
hchauvet's avatar
hchauvet committed
82

83
84
85
from numpy import (array, arange, zeros_like, zeros, ones, linspace, newaxis, NaN, isnan,
                   hstack, ma, exp, log, arctan2, sqrt, sin, cos, pi, mean,
                   timedelta64, argmin, argsort, nonzero, flatnonzero, diff, gradient,
86
                   rad2deg, deg2rad, quantile,
87
                   )
88
from numpy.linalg import norm
hchauvet's avatar
hchauvet committed
89
90
91
92

from scipy.optimize import fmin
import pandas as pd

93
94
95
96
import re
finddigits = re.compile(r'\d+?')
from threading import Thread
from collections import OrderedDict
97

Félix Hartmann's avatar
Félix Hartmann committed
98
99
import gettext  # internationalization

100
from new_libgravimacro import (ProcessImages, traite_tiges2, traite_tige2,
101
                               moving_average, compute_moving_window_size,
Félix Hartmann's avatar
Félix Hartmann committed
102
                               integrate_diff_arc_length,
103
                               get_tige_curvatures, get_tige_curvatures_for_one_image,
Félix Hartmann's avatar
Félix Hartmann committed
104
                               convert_angle,
105
                               plot_sequence,
106
107
                               do_intersect,
                               load_results) # 'load_results' is old method for pkl files
hchauvet's avatar
hchauvet committed
108

109
import interekt_hdf5_store as h5store
110

111
from Extrawidgets import DraggableLines, DraggablePoints
hchauvet's avatar
hchauvet committed
112

Félix Hartmann's avatar
Félix Hartmann committed
113
__version__ = '2022-02-04'
114

hchauvet's avatar
hchauvet committed
115
########################## GLOBAL DATA ########################################
Félix Hartmann's avatar
Félix Hartmann committed
116
strings = dict()  # strings for the user interface, with gettext
117
tiges_data = None
118
tiges_names = []
hchauvet's avatar
hchauvet committed
119
120
img_object = None
text_object = None
121
tiges_plot_object = []
hchauvet's avatar
hchauvet committed
122
123
base_pts_out = None
base_dir_path = None
124
cur_image = None
hchauvet's avatar
hchauvet committed
125
cur_tige = None
126
toptige = None  # top level window for the settings of an organ
hchauvet's avatar
hchauvet committed
127
128
129
130
131
add_tige = False
nbclick = 0
base_tiges = []
btige_plt = []
btige_text = []
132
btige_arrow = []
hchauvet's avatar
hchauvet committed
133
tiges_colors = None
Félix Hartmann's avatar
Félix Hartmann committed
134
135
136
137
add_mark = False
marks = []
mark_plt = []
mark_text = []
hchauvet's avatar
hchauvet committed
138
dtphoto = []
139
thread_process = None  # To store the thread that run image processing
hchauvet's avatar
hchauvet committed
140
141
infos_traitement = None
old_step = 0
142
add_dist_draw = False
hchauvet's avatar
hchauvet committed
143
144
145
dist_measure_pts = []
pixel_distance = None
cm_distance = None
146
147
148
149
interekt = None # contains the main window
hdf5file = None # path to hdf5 data file
tk_list_images = None # Tk listbox object which contain the list of images
tk_toplevel_listimages = None # Tk window which displays the list of images
150
151
# contains the number of points which have to be removed from the end of organ skeleton
tk_nb_points_to_remove_from_end = None
152
153
154
tk_tige_offset = None # contains the offset for tige skeleton detection
tk_tige_percent_diam = None # contains the diameter multplier for the transverse range of tige skeleton detection
PERCENT_DIAM_DEFAULT = 1.4    # default value chosen by Hugo
155
CURVATURE_AVERAGING_LENGTH = 2  # Length (in cm) of the zone over which the curvature is averaged
156

hchauvet's avatar
hchauvet committed
157
def reset_graph_data():
Félix Hartmann's avatar
Félix Hartmann committed
158
159
160
    global img_object, text_object, tiges_plot_object
    global btige_plt, btige_text, btige_arrow
    global mark_plt, mark_text
hchauvet's avatar
hchauvet committed
161
162
163

    img_object = None
    text_object = None
164
    tiges_plot_object = []
hchauvet's avatar
hchauvet committed
165
166
    btige_plt = []
    btige_text = []
167
    btige_arrow = []
Félix Hartmann's avatar
Félix Hartmann committed
168
169
    mark_plt = []
    mark_text = []
hchauvet's avatar
hchauvet committed
170

Félix Hartmann's avatar
Félix Hartmann committed
171

172
173
def reset_globals_data():
    """
174
    Reset all global variables, ala Fortran.
175

176
    Useful when loading a new file.
177
    """
178
    print("Reset all variables and data.")
Félix Hartmann's avatar
Félix Hartmann committed
179

180
    global data_out, text_object, tige_plot_object, base_pts_out, base_dir_path
181
    global cur_image, cur_tige, toptige, add_tige, btige_arrow
182
    global nbclick, base_tiges, btige_plt, btige_text, tiges_colors
Félix Hartmann's avatar
Félix Hartmann committed
183
    global marks, mark_plt, mark_text
184
    global dtphoto, local_photo_path, thread_process, infos_traitement
185
186
    global old_step, add_dist_draw, dist_measure_pts, pixel_distance, cm_distance
    global tk_list_images, tk_toplevel_listimages
187
    global tk_tige_offset, tk_tige_percent_diam
188
    global hdf5file, tiges_data, tiges_names
Félix Hartmann's avatar
Félix Hartmann committed
189

190
    # Reset all gl;obalvariables
191
192
193
194
195
196
197
    # RAZ des autres valeurs
    data_out = None
    img_object = None
    text_object = None
    tige_plot_object = None
    base_pts_out = None
    base_dir_path = None
198
    cur_image = None
199
200
201
202
203
204
205
    cur_tige = None
    toptige = None
    add_tige = False
    nbclick = 0
    base_tiges = []
    btige_plt = []
    btige_text = []
206
    btige_arrow = []
207
    tiges_colors = None
Félix Hartmann's avatar
Félix Hartmann committed
208
209
210
211
    add_mark = False
    marks = []
    mark_plt = []
    mark_text = []
212
213
214
215
216
217
218
219
220
221
    dtphoto = []
    local_photo_path = False  # If true the photo path is removed
    thread_process = None  # To store the thread that run image processing
    infos_traitement = None
    old_step = 0
    add_dist_draw = False
    dist_measure_pts = []
    pixel_distance = None
    cm_distance = None
    tk_list_images = None
222
223
    tk_tige_offset = None
    tk_tige_percent_diam = None
224
    hdf5file = None
225
    tiges_data = None
226
    tiges_names = []
Félix Hartmann's avatar
Félix Hartmann committed
227
228

    # On ferme la liste des images si elle est ouverte
229
230
231
    if tk_toplevel_listimages:
        tk_toplevel_listimages.destroy()
        tk_toplevel_listimages = None
Félix Hartmann's avatar
Félix Hartmann committed
232

hchauvet's avatar
hchauvet committed
233
234
###############################################################################

Félix Hartmann's avatar
Félix Hartmann committed
235
236
237
238
239
########################## CONVERT OLD PKL TO HDF5 ############################

def convert_pkl_to_hdf(pklfile, hdf5file, display_message=False, display_progress=False):
    """Converts the olf format .pkl into hdf5."""
    #TODO localize strings
240
    global tiges_data
Félix Hartmann's avatar
Félix Hartmann committed
241
242
243

    base_dir_path = os.path.dirname(pklfile) + '/'

244
245
246
    # On charge le fichier pkl
    data_out = load_results(pklfile)

Félix Hartmann's avatar
Félix Hartmann committed
247
248
249
250
251
252
    # On convertit les images (avec deux images réduites)

    if display_message:
        interekt.display_message(
                """Ancien format de données: CONVERSION (rootstem_data.pkl -> """
                """interekt_data.h5)\nConversion des images...""")
253
254
255
256
257
258
259
260
261

    Nimgs = len(data_out['tiges_info'])
    for tps_step in data_out['tiges_info']:
        # Test si les images sont disponibles à partir du chemin
        # d'origine ou dans le même dossier que le fichier
        # RootStemData.pkl
        if os.path.exists(tps_step['imgname']):
            img_path = tps_step['imgname']
        else:
Félix Hartmann's avatar
Félix Hartmann committed
262
            img_path = base_dir_path + os.path.basename(tps_step['imgname'])
263
264

        # On affiche l'état d'avancement
Félix Hartmann's avatar
Félix Hartmann committed
265
266
267
268
269
270
271
272
273
274
275
276
        if display_message:
            interekt.display_message(
                    """Ancien format de données: CONVERSION (rootstem_data.pkl -> """
                    """interekt_data.h5)\nConversion des images..."""
                    """(%i/%i)"""%(tps_step['iimg'], Nimgs))

        if display_progress:
            # On augmente la barre de progression du GUI
            dstep = 1/float(Nimgs) * 100.
            interekt.prog_bar.step(dstep)
            interekt.prog_bar.update_idletasks()
            interekt.master.update()
277

278
279
        # On sauvagarde la photo dans le fichier hdf5, on fait garde
        # que le niveau 0 et 2
280

281
282
        # On sauvegarde les valeurs des résolutions de la pyramide
        # juste pour le dernier pas de temps
283
284
285
        store_resolutions = False
        if tps_step['iimg'] == Nimgs-1:
            store_resolutions = True
Félix Hartmann's avatar
Félix Hartmann committed
286

287
288
        h5store.image_to_hdf(hdf5file, img_path,
                             tps_step['iimg'],
289
                             max_pyramid_layers=2,
290
291
                             save_resolutions=store_resolutions,
                             selected_resolutions=[0,2])
292

Félix Hartmann's avatar
Félix Hartmann committed
293
294
295
296
297
    if display_progress:
        # Remise a zero de la progress bar
        interekt.prog_bar.step(0.0)
        interekt.prog_bar.update_idletasks()
        interekt.master.update()
298
299
300
301
302
303
304
305
306
307
308
309
310
311

    # Chargement des points de base
    tiges_data = data_out['tiges_data']
    if 'pts_base' in data_out:
        base_tiges = data_out['pts_base']
    else:
        base_tiges = [[(tiges_data.xc[ti,0,0], tiges_data.yc[ti,0,0])]*2 for ti in range(len(tiges_data.yc[:,0,0]))]

    #chargement des numeros des tiges
    if os.path.isfile(base_dir_path+'tige_id_map.pkl'):
        with open(base_dir_path+'tige_id_map.pkl', 'rb') as f:
            datapkl = pkl.load(f)
            tige_id_mapper = datapkl['tige_id_mapper']

312
313
314
315
            # Cherche si il y a d'autre données du post-processing a
            # charger Leur nom de variable est le même que la clef du
            # dico, on utilise la fonction eval pour créer la variable
            # Expl: scale_cmpix = datapkl['scale_cmpix']
Félix Hartmann's avatar
Félix Hartmann committed
316
            for dname in ('scale_cmpix', 'growth_data', 'B_data', 'beta_data',
317
                          'gamma_data', 'End_point_data', 'Tiges_seuil_offset'):
Félix Hartmann's avatar
Félix Hartmann committed
318

319
320
321
322
323
324
                if dname in datapkl:
                    exec("%s = datapkl['%s']" % (dname, dname))
                else:
                    #Creation d'un dico vide
                    exec("%s = {}" % (dname))
    else:
Félix Hartmann's avatar
Félix Hartmann committed
325
        tige_id_mapper = {}
326

327
328
    # Création des dossiers des tiges et du tige_id_mapper si il n'a
    # pas été defini dans le fichier tige_id_map.pkl
329
    for it in range(len(base_tiges)):
330
        h5store.create_tige_in_hdf(hdf5file, it)
331
        if it not in tige_id_mapper:
332
333
            tige_id_mapper[it] = it + 1

334
335
336
    # On lance le postrocessing des données des squelette des tiges
    # pour les enregistrer ensuite dans le fichier hdf5
    Traite_data(save_to_hdf5=True)
337

338
    # Pour l'échelle de l'image
339
    scale_cmpix = None
Félix Hartmann's avatar
Félix Hartmann committed
340

341
342
343
344
345
346
347
348
    # Chargement des données du traitement dans le fichier hdf5
    for id_tige in range(len(base_tiges)):
        # Est ce qu'il y a un nom pour cette tige
        if id_tige in tige_id_mapper:
            tige_nom = tige_id_mapper[id_tige]
        else:
            tige_nom = ""

Félix Hartmann's avatar
Félix Hartmann committed
349
350
351
352
353
354
        if display_message:
            interekt.display_message(
                    """Ancien format de données: CONVERSION (rootstem_data.pkl -> """
                    """interekt_data.h5)\nConversion des squelettes..."""
                    """(tige %i/%i)"""%(id_tige+1, len(base_tiges)))

355
356
357
358
359
360
        h5store.tige_to_hdf5(hdf5file, id_tige, tige_nom, base_tiges[id_tige],
                             tiges_data.xc[id_tige], tiges_data.yc[id_tige],
                             tiges_data.theta[id_tige], tiges_data.diam[id_tige],
                             tiges_data.xb1[id_tige], tiges_data.yb1[id_tige],
                             tiges_data.xb2[id_tige], tiges_data.yb2[id_tige])

361
        # Enregistrement des données comme dans fonction l'ancienne
362
363
364
        # fonction save_tige_idmapper() On sauve aussi cet ancien
        # tige_id_mapper dans le champ 'name' de la tige qui est plus
        # logique et qui serra dorénavant utilisé
365
        postprocess_data = {'OLD_tige_id_mapper': tige_id_mapper[id_tige]}
366

367
368
        h5store.save_tige_name(hdf5file, id_tige, str(tige_id_mapper[id_tige]))

369
370
        # On boucle sur les autres données du post-processing leur nom
        # est le même que le nom de la variable
Félix Hartmann's avatar
Félix Hartmann committed
371
372
        for dname in ('growth_data', 'B_data', 'beta_data', 'gamma_data',
                      'End_point_data', 'Tiges_seuil_offset'):
373
374
375
376
377
378
            try:
                postprocess_data[dname] = eval('%s[%i]' % (dname, id_tige))
            except Exception as e:
                print('No postprocessing data %s for tige %i' % (dname, id_tige))
                print(e)

379
380
            # On enregistre la valeur de l'échelle pour la sauvegarder
            # dans la H5 une seule fois à la fin
381
382
            if 'scale_cmpix' in postprocess_data:
                scale_cmpix = postprocess_data['scale_cmpix']
383

384
        # Cas un peu particulier de B_data qui contient des clefs
385
        # avec des '/' ce qui fait des sous-dossiers dans le
386
        # fichier h5, il faut donc les changer
387
        if 'B_data' in postprocess_data:
388
389
390
391
392
            if 'Lc (fit Log(A/A0))' in postprocess_data['B_data']:
                try:
                    postprocess_data['B_data']['Lc (fit Log(A over A0))'] = postprocess_data['B_data'].pop('Lc (fit Log(A/A0))')
                except:
                    print('Pas capable de convertir la clef "Lc (fit Log(A/A0))"')
393

394
395
396
397
398
            if 'Lc (fit exp(A/A0))' in postprocess_data['B_data']:
                try:
                    postprocess_data['B_data']['Lc (fit exp(A over A0))'] = postprocess_data['B_data'].pop('Lc (fit exp(A/A0))')
                except:
                    print('Pas capable de convertir la clef "Lc (fit exp(A/A0))"')
Félix Hartmann's avatar
Félix Hartmann committed
399

400
        h5store.save_tige_dict_to_hdf(hdf5file, id_tige, postprocess_data)
401

402
    # Sauvegarde de l'échelle
403
    h5store.save_pixelscale(hdf5file, scale_cmpix)
Félix Hartmann's avatar
Félix Hartmann committed
404

Félix Hartmann's avatar
Félix Hartmann committed
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
###############################################################################

def set_tiges_colormap():
    global tiges_colors
    #Fonction pour construire le vecteur des couleurs pour les tiges
    if len(base_tiges) > 20:
        s = len(base_tiges)+1
    else:
        s = 20

    try:
        tiges_colors = mpl.cm.tab20(linspace(0, 1, s))
    except Exception as e:
        print(e, "Update Matplotlib!")
        tiges_colors = mpl.cm.hsv(linspace(0, 1, s))

def get_hdf5_tigeid(h5file, gui_tige_id):
    """Returns the id under which the tige is saved in the hdf5 file."""

    tiges_ids = h5store.get_tiges_indices(h5file)
    tigeid = tiges_ids[gui_tige_id]

    return tigeid
428

429
def load_hdf5_tiges(hdf5file, display_message=True):
430
    """Loads organ data from the hdf5 file and set global variables."""
431
432
433

    global tiges_data, base_tiges, btige_plt, btige_text, tiges_names
    global tiges_plot_object
Félix Hartmann's avatar
Félix Hartmann committed
434

435
    # How many organs have been precessed
436
437
    Ntiges = h5store.get_number_of_tiges(hdf5file)

438
    # Let's create lists for storing texts and plots for the organs
439
440
    btige_plt = [None]*Ntiges
    btige_text = [None]*Ntiges
441
    tiges_plot_object = [None]*Ntiges
Félix Hartmann's avatar
Félix Hartmann committed
442

443
    # Loading organ  names
444
    if display_message:
Félix Hartmann's avatar
Félix Hartmann committed
445
        interekt.display_message(_("""Loading data"""))
446
    tiges_names = h5store.get_tiges_names(hdf5file)
447
    base_tiges = h5store.get_tiges_bases(hdf5file)
448
449

    # Reloading colormap for the organs to display
450
    set_tiges_colormap()
Félix Hartmann's avatar
Félix Hartmann committed
451

452
    # Get a TigesManager object the hdf5 file data
453
    tiges_data = h5store.get_tigesmanager(hdf5file)
454

455
def load_hdf5_file(hdf5file):
Félix Hartmann's avatar
Félix Hartmann committed
456
    """Loads a hdf5 file into Interekt."""
457
    global dtphoto
Félix Hartmann's avatar
Félix Hartmann committed
458

459
460
    # On charge les données des tiges
    load_hdf5_tiges(hdf5file)
Félix Hartmann's avatar
Félix Hartmann committed
461

Félix Hartmann's avatar
Félix Hartmann committed
462
463
464
465
466
467
468
469
470
471
472
473
474
    interekt.get_photo_datetime.set(True)

    # Doit on charger le temps des photos
    #if interekt.get_photo_datetime.get():
    dtphoto = h5store.get_images_datetimes(hdf5file)

    # On regarde si tous les temps sont des datetime sinon on
    # prends le numéro de la photo
    for t in dtphoto:
        if t is None:
            print("Error: No timestamp for all photos; photo numbers are used instead.")
            dtphoto = []
            break
475
476
477
478

    # Si pas de temps dans les photos on mets l'option dans
    # l'interface graphique à False
    if dtphoto == []:
Félix Hartmann's avatar
Félix Hartmann committed
479
        interekt.get_photo_datetime.set(False)
480

481
    # Retrieve the space step for organ skeleton detection from the hdf5 file
482
    detection_step = h5store.get_detection_step(hdf5file)
483
    interekt.detection_step.set(detection_step)
484

485
486
487
488
489
    # Retrieve the data on the steady state from the h5 file
    steady_state, exclude_steady_state = h5store.get_steady_state(hdf5file)
    interekt.steady_state_image = steady_state
    interekt.exclude_steady_state_from_time_series.set(exclude_steady_state)

490

Félix Hartmann's avatar
Félix Hartmann committed
491
######################### HANDLING OF THE IMAGE LIST ##########################
hchauvet's avatar
hchauvet committed
492
493
494

def onChangeImage(event):
    try:
Félix Hartmann's avatar
Félix Hartmann committed
495
        sender = event.widget
hchauvet's avatar
hchauvet committed
496
        idx = sender.curselection()
Félix Hartmann's avatar
Félix Hartmann committed
497
        interekt.plot_image(idx[0], keep_zoom=True)
hchauvet's avatar
hchauvet committed
498
    except Exception as e:
Félix Hartmann's avatar
Félix Hartmann committed
499
        print("Erreur de chargement !!!")
hchauvet's avatar
hchauvet committed
500
501
502
        print(e)

def show_image_list():
503
504
505
506
    global tk_list_images, tk_toplevel_listimages

    if tk_toplevel_listimages is None:
        tk_toplevel_listimages = Tk.Toplevel(master=root)
Félix Hartmann's avatar
Félix Hartmann committed
507
        tk_toplevel_listimages.title(_("List of open images"))
hchauvet's avatar
hchauvet committed
508

509
510
511
512
513
        topsbar = Tk.Scrollbar(tk_toplevel_listimages, orient=Tk.VERTICAL)
        listb = Tk.Listbox(master=tk_toplevel_listimages,
                           yscrollcommand=topsbar.set)
        topsbar.config(command=listb.yview)
        topsbar.pack(side=Tk.RIGHT, fill=Tk.Y)
hchauvet's avatar
hchauvet committed
514

515
516
        for imgname in h5store.get_images_names(hdf5file):
            listb.insert(Tk.END, imgname)
hchauvet's avatar
hchauvet committed
517

518
519
520
521
522
523
524
        # Selection de l'image que l'on regarde
        if cur_image:
            listb.selection_set(cur_image)
            listb.see(cur_image)
        else:
            listb.selection_set(0)
            listb.see(0)
hchauvet's avatar
hchauvet committed
525

526
527
528
        listb.bind("<<ListboxSelect>>", onChangeImage)
        listb.pack(side=Tk.LEFT, fill=Tk.BOTH, expand=1)
        tk_list_images = listb
hchauvet's avatar
hchauvet committed
529

530
531
532
        # Signal pour déclencher la fonction qui ferme proprement la
        # liste d'image en gérant les globals
        tk_toplevel_listimages.protocol("WM_DELETE_WINDOW", destroy_list_images)
Félix Hartmann's avatar
Félix Hartmann committed
533

534
535
536
    else:
        # Si la fenêtre existe déjà mais est cachée on la ramène devant
        tk_toplevel_listimages.lift()
Félix Hartmann's avatar
Félix Hartmann committed
537

538
def destroy_list_images():
Félix Hartmann's avatar
Félix Hartmann committed
539
    """Safely closed the image list window."""
540
541
    global tk_list_images, tk_toplevel_listimages

Félix Hartmann's avatar
Félix Hartmann committed
542
    # On détruit la fenetre
543
544
545
546
547
    tk_toplevel_listimages.destroy()

    # On remet les variables global à None (Fortran Style)
    tk_list_images = None
    tk_toplevel_listimages = None
Félix Hartmann's avatar
Félix Hartmann committed
548

hchauvet's avatar
hchauvet committed
549
550
551
###############################################################################


552
553
554
555
556
########################## HANDLING OF TIMES ##################################

def get_time_data():
    """Returns useful variables for plotting time series.

557
558
    The time unit is chosen based on the total duration of the experiment.

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
    Returns:
    --------
    - times: 1D array of photo times
    - time_units: dictionary with time-related units
    - time_label: label for the time axis in plots (str)
    - graduation: on the time axis of a plot, a tick will be placed
      at each multiple of 'graduation'
    - excluded_image: index of the image to be excluded from time
      series (otberwise, 'None')
    - mask: a mask array for excluding the above-mentioned image
    """

    nb_images = h5store.get_number_of_images(hdf5file)

    # We define a mask which will be useful if the steady-state image
    # has to be excluded from time series.
    mask = ones(nb_images, dtype=bool)
    if (interekt.steady_state_image is not None
            and interekt.exclude_steady_state_from_time_series.get()):
        excluded_image = interekt.steady_state_image
        mask[excluded_image] = False
    else:
        excluded_image = None

    if interekt.get_photo_datetime.get() and dtphoto:
        # photos have timestamps

        # time in minutes
        times = array([(t - dtphoto[0]).total_seconds() for t in dtphoto]) / 60.

        # total duration of the experiment in minutes
        total_minutes = times[mask].max()

        # choose time unit and graduation for the graphs,
        # based on the total duration
        if total_minutes < 12 * 60:  # less than 12 hours
            time_unit = strings["min"]
            graduation = 60   # a graduation every hour
        elif total_minutes < 24 * 60:  # between 12 and 24 hours
            times /= 60  # time in hours
            time_unit = strings["hours"]
            graduation = 1   # a graduation every hour
        elif total_minutes <  7 * 24 * 60:  # between one day and one week
            times /= 60  # time in hours
            time_unit = strings["hours"]
            graduation = 24   # a graduation every day
        elif total_minutes < 30 * 24 * 60:  # between one week and one month
            times /= 24 * 60  # time in days
            time_unit = strings["days"]
            graduation = 1   # a graduation every day
        elif total_minutes < 3 * 30 * 24 * 60:  # between one month and three month
            times /= 24 * 60  # time in days
            time_unit = strings["days"]
            graduation = 7   # a graduation every week
        elif total_minutes >= 3 * 30 * 24 * 60:  # more than three month
            times /= 24 * 60  # time in days
            time_unit = strings["days"]
            graduation = 30   # a graduation every 30 days

        time_label = strings["time"] + " (%s)"%time_unit
        time_units = {"time unit": time_unit,
                      "RER unit": "h-1",
                      "RER unit TeX": r"h$^{-1}$",
                      "dAdt unit": "rad.h-1",
                      "dAdt unit TeX": r"rad.h$^{-1}$"}

    else:
        times = arange(1, nb_images+1)  # time = image number

        # one tick every 60 minutes if photos taken every 6 minutes
        # (this is a legacy from the Murinas project)
        graduation = 10

        time_label = strings["image number"]
        RER_unit = strings["image"] + "-1"
        RER_unit_TeX = strings["image"] + r"$^{-1}$"
        time_units = {"time unit": strings["image"],
                      "RER unit": RER_unit,
                      "RER unit TeX": RER_unit_TeX,
                      "dAdt unit": "rad." + RER_unit,
                      "dAdt unit TeX": r"rad." + RER_unit_TeX}

    return times, time_units, time_label, graduation, excluded_image, mask


##########W####################################################################


hchauvet's avatar
hchauvet committed
647
648
########################## Pour le Traitement #################################
def _export_to_csv():
649
650
651
652
653
    """
    Fonction pour exporter la serie temporelle pour les tiges soit
    l'évolution de l'angle moyen au bout et de la taille au cours du
    temps
    """
hchauvet's avatar
hchauvet committed
654

655
    if len(base_tiges) > 0:
Félix Hartmann's avatar
Félix Hartmann committed
656
657
658
659
660
661
662
663
        proposed_filename = "time_series_tipangle_length.csv"
        outfileName = filedialog.asksaveasfilename(
                parent=root,
                filetypes=[("Comma Separated Value, csv","*.csv")],
                title=_("Export time series"),
                #title=_("Export séries temporelles"),
                initialfile=proposed_filename,
                initialdir=base_dir_path)
hchauvet's avatar
hchauvet committed
664
665
666

    if len(outfileName) > 0:

667
668
669
670
671
672
673
674
        # Creation du tableau Pandas pour stocquer les données
        data_out = pd.DataFrame(columns=['tige', 'angle', 'temps',
                                         'taille', 'rayon', 'angle_0_360'])

        # Récupérations des id des tiges dans le fichier h5
        hdf_tiges_id = h5store.get_tiges_indices(hdf5file)
        tiges_names = h5store.get_tiges_names(hdf5file)
        pictures_names = h5store.get_images_names(hdf5file)
Félix Hartmann's avatar
Félix Hartmann committed
675

676
        # Récupère le temps
677
        if interekt.get_photo_datetime.get() and dtphoto:
678
679
680
            tps = dtphoto
        else:
            tps = arange(len(pictures_names))
Félix Hartmann's avatar
Félix Hartmann committed
681

682
683
        # Boucle sur les tiges
        for i in range(len(base_tiges)):
hchauvet's avatar
hchauvet committed
684

685
            hdfid = hdf_tiges_id[i]
686
687
            tige_x, tige_y, tige_s, tige_taille, tige_angle, tige_tip_angle, tige_zone, \
                    dummy = load_postprocessed_data(hdf5file, hdfid)
688
            tige_R = tiges_data.diam[i]/2.0
Félix Hartmann's avatar
Félix Hartmann committed
689
            tige_name = tiges_names[i]
Félix Hartmann's avatar
Félix Hartmann committed
690

Félix Hartmann's avatar
Félix Hartmann committed
691
            data_tmp = pd.DataFrame({'tige': tige_name, 'angle': tige_tip_angle,
692
693
                                     'temps': tps, 'taille': tige_taille,
                                     'rayon': tige_R.mean(1),
694
                                     'angle_0_360': convert_angle(tige_tip_angle)})
hchauvet's avatar
hchauvet committed
695

696
            data_out = data_out.append(data_tmp, ignore_index=True)
hchauvet's avatar
hchauvet committed
697
698
699

        #Add some usefull data
        output = []
700
        for tige, datatige in data_out.groupby('tige'):
701
            # print(dataout.tige)
Félix Hartmann's avatar
Félix Hartmann committed
702

hchauvet's avatar
hchauvet committed
703
            #Compute dt (min)
Félix Hartmann's avatar
Félix Hartmann committed
704
            if interekt.get_photo_datetime.get():
hchauvet's avatar
hchauvet committed
705
706
707
                dtps = (datatige['temps']-datatige['temps'].min())/timedelta64(1,'m')
                datatige.loc[:,'dt (min)'] = dtps

708
            datatige.loc[:,'pictures name'] = pictures_names
hchauvet's avatar
hchauvet committed
709

710
711
            # Trouve la position de la tige dans les données du GUI
            itige = tiges_names.index(tige)
Félix Hartmann's avatar
Félix Hartmann committed
712

713
            datatige.loc[:,'x base (pix)'] = [tiges_data.xc[itige,:,0].mean()]*len(datatige.index)
hchauvet's avatar
hchauvet committed
714
715
716
            output += [datatige]

        dataout = pd.concat(output)
717
        print(dataout.head())
hchauvet's avatar
hchauvet committed
718
719
720
721
        #print datatige.tail()
        dataout.to_csv(outfileName, index=False)

def _export_xytemps_to_csv():
722
723
724
725
726
    """
    Export du squelette des tiges/racines au cours du temps sous
    format csv, pour un pas de temps on aura le xy complet (soit pour
    chaque abscisse curviligne).
    """
hchauvet's avatar
hchauvet committed
727

728
    if len(base_tiges) > 0:
Félix Hartmann's avatar
Félix Hartmann committed
729
730
731
732
733
734
735
        proposed_filename = "skeleton.csv"
        outfileName = filedialog.asksaveasfilename(
                parent=root,
                filetypes=[("Comma Separated Value, csv","*.csv")],
                title=_("Export skeleton"),
                initialfile=proposed_filename,
                initialdir=base_dir_path)
hchauvet's avatar
hchauvet committed
736
737

    if len(outfileName) > 0:
738
739
740
741
742
743
744
745
746
        # Creation du tableau Pandas pour stocquer les données
        data_out = pd.DataFrame(columns=['tige', 'angle', 'temps',
                                         'taille', 'rayon', 'x', 'y', 'nom photo',
                                         'angle_0_360'])

        # Récupérations des id des tiges dans le fichier h5
        hdf_tiges_id = h5store.get_tiges_indices(hdf5file)
        tiges_names = h5store.get_tiges_names(hdf5file)
        pictures_names = h5store.get_images_names(hdf5file)
747

748
        # Récupère le temps
749
        if interekt.get_photo_datetime.get() and dtphoto:
750
            tps = dtphoto
hchauvet's avatar
hchauvet committed
751
        else:
752
            tps = arange(len(pictures_names))
hchauvet's avatar
hchauvet committed
753

754
755
        # Boucle sur les tiges
        for i in range(len(base_tiges)):
hchauvet's avatar
hchauvet committed
756

757
            hdfid = hdf_tiges_id[i]
758
            tige_x, tige_y, tige_s, tige_taille, tige_angle, tige_tip_angle, tige_zone,\
759
                    dummy = load_postprocessed_data(hdf5file, hdfid)
760
            tige_R = tiges_data.diam[i]/2.0
Félix Hartmann's avatar
Félix Hartmann committed
761
            tige_name = tiges_names[i]
762

Félix Hartmann's avatar
Félix Hartmann committed
763
            data_tmp = pd.DataFrame({'tige': tige_name, 'angle': tige_tip_angle,
764
765
766
767
768
                                     'temps': tps, 'taille': tige_taille,
                                     'rayon': tige_R.mean(1),
                                     'x': tige_x.tolist(),
                                     'y': tige_y.tolist(),
                                     'nom photo': pictures_names,
769
                                     'angle_0_360': convert_angle(tige_tip_angle)})
770
771
772
773

            data_out = data_out.append(data_tmp, ignore_index=True)

        print(data_out.head())
Félix Hartmann's avatar
Félix Hartmann committed
774
        print("Enregistré dans %s" % outfileName)
775
        data_out.to_csv(outfileName, index=False)
hchauvet's avatar
hchauvet committed
776
777

def _export_mean_to_csv():
778
779
780
781
    """
    Fonction qui exporte la courbe moyennée pour toutes les tiges de
    l'angle et taille en fonction du temps
    """
hchauvet's avatar
hchauvet committed
782

783
    if len(base_tiges) > 0:
hchauvet's avatar
hchauvet committed
784
        proposed_filename = "Serie_tempotelle_moyenne.csv"
Félix Hartmann's avatar
Félix Hartmann committed
785
786
787
788
789
790
        outfileName = filedialog.asksaveasfilename(
                parent=root,
                filetypes=[("Comma Separated Value, csv","*.csv")],
                title="Export serie temporelle",
                initialfile=proposed_filename,
                initialdir=base_dir_path)
hchauvet's avatar
hchauvet committed
791
792

    if len(outfileName) > 0:
793

Félix Hartmann's avatar
Félix Hartmann committed
794
        # Creation du tableau Pandas pour stocquer les données
795
796
797
798
799
800
801
        data_out = pd.DataFrame(columns=['tige', 'angle', 'temps',
                                         'taille', 'rayon', 'angle_0_360'])

        # Récupérations des id des tiges dans le fichier h5
        hdf_tiges_id = h5store.get_tiges_indices(hdf5file)
        tiges_names = h5store.get_tiges_names(hdf5file)
        pictures_names = h5store.get_images_names(hdf5file)
Félix Hartmann's avatar
Félix Hartmann committed
802

803
        # Récupère le temps
804
        if interekt.get_photo_datetime.get() and dtphoto:
805
            tps = dtphoto
hchauvet's avatar
hchauvet committed
806
        else:
807
            tps = arange(len(pictures_names))
Félix Hartmann's avatar
Félix Hartmann committed
808

809
810
        # Boucle sur les tiges
        for i in range(len(base_tiges)):
hchauvet's avatar
hchauvet committed
811

812
            hdfid = hdf_tiges_id[i]
813
814
            tige_x, tige_y, tige_s, tige_taille, tige_angle, tige_tip_angle, tige_zone, \
                   dummy = load_postprocessed_data(hdf5file, hdfid)
815
            tige_R = tiges_data.diam[i]/2.0
Félix Hartmann's avatar
Félix Hartmann committed
816
            tige_name = tiges_names[i]
Félix Hartmann's avatar
Félix Hartmann committed
817

Félix Hartmann's avatar
Félix Hartmann committed
818
            data_tmp = pd.DataFrame({'tige': tige_name, 'angle': tige_tip_angle,
819
820
                                     'temps': tps, 'taille': tige_taille,
                                     'rayon': tige_R.mean(1),
821
                                     'angle_0_360': convert_angle(tige_tip_angle)})
hchauvet's avatar
hchauvet committed
822

823
            data_out = data_out.append(data_tmp, ignore_index=True)
Félix Hartmann's avatar
Félix Hartmann committed
824

hchauvet's avatar
hchauvet committed
825
826

        #Creation de la moyenne
827
828
829
830
        datamoy = data_out.groupby('temps').mean()
        datamoy['temps'] = data_out.temps.unique()
        datamoy['tige'] = ['%s->%s'%(str(data_out['tige'].min()),
                                     str(data_out['tige'].max()))]*len(datamoy['temps'])
Félix Hartmann's avatar
Félix Hartmann committed
831

hchauvet's avatar
hchauvet committed
832
        #Convert to timedelta in minute
Félix Hartmann's avatar
Félix Hartmann committed
833
        if interekt.get_photo_datetime.get():
834
            dtps = (datamoy['temps']-datamoy['temps'][0])/timedelta64(1,'m')
hchauvet's avatar
hchauvet committed
835
836
            datamoy['dt (min)'] = dtps

837
        datamoy['pictures name'] = pictures_names
Félix Hartmann's avatar
Félix Hartmann committed
838
        print("Saved to %s"%outfileName)
hchauvet's avatar
hchauvet committed
839
840
841
        datamoy.to_csv(outfileName, index=False)

def _export_meandata_for_each_tiges():
842
843
844
845
846
    """
    Dans un dossier donnée par l'utilisateur on exporte les données
    taille et angle au cours de temps avec un fichier .csv par
    tiges/racines
    """
hchauvet's avatar
hchauvet committed
847
848

    #Ask for a directory where to save all files
Félix Hartmann's avatar
Félix Hartmann committed
849
850
    outdir = filedialog.askdirectory(
            title="Choisir un répertoire pour sauvegarder les tiges")
hchauvet's avatar
hchauvet committed
851
    if len(outdir) > 0:
852
853
854
        # Creation du tableau Pandas pour stocquer les données
        data_out = pd.DataFrame(columns=['tige', 'angle', 'temps',
                                         'taille', 'rayon', 'angle_0_360'])
hchauvet's avatar
hchauvet committed
855

856
857
858
859
        # Récupérations des id des tiges dans le fichier h5
        hdf_tiges_id = h5store.get_tiges_indices(hdf5file)
        tiges_names = h5store.get_tiges_names(hdf5file)
        pictures_names = h5store.get_images_names(hdf5file)
Félix Hartmann's avatar
Félix Hartmann committed
860

861
        # Récupère le temps
862
        if interekt.get_photo_datetime.get() and dtphoto:
863
864
865
            tps = dtphoto
        else:
            tps = arange(len(pictures_names))
Félix Hartmann's avatar
Félix Hartmann committed
866

867
868
        # Boucle sur les tiges
        for i in range(len(base_tiges)):
hchauvet's avatar
hchauvet committed
869

870
            hdfid = hdf_tiges_id[i]
871
872
            tige_x, tige_y, tige_s, tige_taille, tige_angle, tige_tip_angle, tige_zone, \
                    dummy = load_postprocessed_data(hdf5file, hdfid)
873
            tige_R = tiges_data.diam[i]/2.0
Félix Hartmann's avatar
Félix Hartmann committed
874
            tige_name = tiges_names[i]
Félix Hartmann's avatar
Félix Hartmann committed
875

Félix Hartmann's avatar
Félix Hartmann committed
876
            data_tmp = pd.DataFrame({'tige': tige_name, 'angle': tige_tip_angle,
877
878
                                     'temps': tps, 'taille': tige_taille,
                                     'rayon': tige_R.mean(1),
879
                                     'angle_0_360': convert_angle(tige_tip_angle)})
hchauvet's avatar
hchauvet committed
880

881
            data_out = data_out.append(data_tmp, ignore_index=True)
Félix Hartmann's avatar
Félix Hartmann committed
882

hchauvet's avatar
hchauvet committed
883
        #Loop over tiges
884
        for tige, datatige in data_out.groupby('tige'):
hchauvet's avatar
hchauvet committed
885
            #Compute dt (min)
Félix Hartmann's avatar
Félix Hartmann committed
886
            if interekt.get_photo_datetime.get():
887
                dtps = (datatige['temps']-datatige['temps'][datatige.index[0]])/timedelta64(1,'m')
hchauvet's avatar
hchauvet committed
888
889
                datatige['dt (min)'] = dtps

890
891
892
893
            datatige['pictures name'] = pictures_names
            # Trouve la position de la tige dans les données du GUI
            itige = tiges_names.index(tige)
            datatige['x base (pix)'] = [tiges_data.xc[itige,:,0].mean()]*len(datatige.index)
hchauvet's avatar
hchauvet committed
894
895

            #print datatige.head()
896
            outfileName = outdir+'/data_mean_for_tige_%s.csv'%str(tige)
Félix Hartmann's avatar
Félix Hartmann committed
897
            print("Sauvegardé dans %s"%outfileName)
hchauvet's avatar
hchauvet committed
898
899
            datatige.to_csv(outfileName, index=False)

Félix Hartmann's avatar
Félix Hartmann committed
900
901
###############################################################################

hchauvet's avatar
hchauvet committed
902
903
def update_tk_progress(infos, root):
    global old_step
904
    if infos is not None:
hchauvet's avatar
hchauvet committed
905
906
907
908

        im_num = infos['inum']
        old_num = infos['old_inum']
        tot = infos['tot']
Félix Hartmann's avatar
Félix Hartmann committed
909
        start_msg = _("Image processing")
Félix Hartmann's avatar
Félix Hartmann committed
910
911
        msg = start_msg + " %i / %i"%(int(im_num),int(tot))
        root.wm_title("Interekt | %s"%msg)
hchauvet's avatar
hchauvet committed
912
913
        root.update_idletasks()

Félix Hartmann's avatar
Félix Hartmann committed
914
        #New version with Tk progressbar
915
        if im_num != old_step and tot > 1:
hchauvet's avatar
hchauvet committed
916
917
918
919
            old_step = im_num
            #print(old_step, nstep, nstep-old_step)
            dstep = (im_num-old_num)/float(tot-1) * 100.
            #print(dstep)
Félix Hartmann's avatar
Félix Hartmann committed
920
921
            interekt.prog_bar.step(dstep)
            interekt.prog_bar.update_idletasks()
hchauvet's avatar
hchauvet committed
922
923
924
925
926
927
            root.update()

def plot_progress(**kwargs):
    global infos_traitement
    infos_traitement = kwargs

928
929
930
def none_print(**kwargs):
    output = kwargs

hchauvet's avatar
hchauvet committed
931
932
933
def check_process():
    global root, infos_traitement

934
    if thread_process.is_alive():
hchauvet's avatar
hchauvet committed
935
936
937
938
939
        update_tk_progress(infos_traitement, root)
        root.after(20, check_process)

    else:
        update_tk_progress(infos_traitement, root)
Félix Hartmann's avatar
Félix Hartmann committed
940
        root.wm_title("Interekt")
hchauvet's avatar
hchauvet committed
941
942
        thread_process.join()
        infos_traitement = None
943
944
945
946
947
        try:
            process_data()
        except Exception as e:
            print('Failed to process data')
            print(e)
hchauvet's avatar
hchauvet committed
948

949
950
def process_data():
    """Process the raw skeleton and store data into the hdf5 file."""
951
    global data_out, tiges_data
hchauvet's avatar
hchauvet committed
952

953
    # Get queue result
hchauvet's avatar
hchauvet committed
954
955
    data_out = data_out.get()

956
    # When it's done get the tiges_data from the data from data_out
957
    tiges_data = data_out['data']['tiges_data']
Félix Hartmann's avatar
Félix Hartmann committed
958

959
    print("Saving data into the .h5 file")
960
961
962
963
964
965
966
967
    for idt in range(len(base_tiges)):
        idhdf = get_hdf5_tigeid(hdf5file, idt)
        tige_name = h5store.get_tiges_names(hdf5file, idhdf)
        h5store.tige_to_hdf5(hdf5file, idhdf, tige_name, base_tiges[idt],
                             tiges_data.xc[idt], tiges_data.yc[idt],
                             tiges_data.theta[idt], tiges_data.diam[idt],
                             tiges_data.xb1[idt], tiges_data.yb1[idt],