wolfhece 2.2.2__py3-none-any.whl → 2.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2124 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.widgets import Slider
5
+ from numpy import random as rnd
6
+ import math
7
+ from pathlib import Path
8
+ import json
9
+ import timeit
10
+ import multiprocessing
11
+ import cProfile, pstats
12
+ import wx
13
+ from os.path import join
14
+ import threading
15
+ from sklearn.neighbors import KernelDensity
16
+ from scipy.ndimage import maximum_filter
17
+ from scipy.spatial.distance import cdist
18
+ from datetime import datetime, timedelta
19
+
20
+ try:
21
+ from ..PyParams import *
22
+ from ..drawing_obj import Element_To_Draw
23
+ from ..Results2DGPU import getkeyblock
24
+ from ..PyTranslate import _
25
+ from ..PyVertex import cloud_vertices
26
+ from ..wolf_array import WolfArray, header_wolf
27
+ from ..PandasGrid import PandasGrid
28
+ except:
29
+ from wolfhece.PyParams import *
30
+ from wolfhece.drawing_obj import Element_To_Draw
31
+ from wolfhece.Results2DGPU import getkeyblock
32
+ from wolfhece.PyTranslate import _
33
+ from wolfhece.PyVertex import cloud_vertices
34
+ from wolfhece.wolf_array import WolfArray
35
+ from wolfhece.PandasGrid import PandasGrid
36
+
37
+ try:
38
+ from .drowning_functions import *
39
+ except:
40
+ from wolfhece.drowning_victims.drowning_functions import *
41
+
42
+
43
+ #index 0 1 2 3 4 5 6 7
44
+ COLUMN_Z_PARAM = ['vertical','U_z','z_0','mu_stat','Time_float','T_w','ADD','ADD_resurface']
45
+ COLUMNS_HUMAN = ['Age','BMI','BSA','CAM','CDA','CLA','Death','dm','eps','fp_x','fp_y','fp_z','gender','height','lungs_volume_FRC','lungs_volume_TLC','mass','rho','Volume','V_clothes_o','V_clothes_one','V_clothes_two','error_perc_fat','CSA']
46
+
47
+ class Drowning_victim:
48
+ def __init__(self,Path_dir:str = None):
49
+ """
50
+ Initialize the simulation parameters.
51
+
52
+ :param Path_loading:
53
+ Path of the simulation loaded.
54
+
55
+ Attributes:
56
+ Profile_this (bool): Binary parameter to activate the profiling of the code.
57
+ saving (bool): Binary parameter to save your results.
58
+ file_name (str): Name of the file to be saved.
59
+ Path_saving (str): Path where you want the file saved.
60
+ loading (bool): Binary parameter to load previous results and start from them.
61
+ Path_loading (str): Path of the simulation loaded.
62
+ Path_Wolf (str): Path of the WolfGPU simulation.
63
+ plot_pos (bool): Binary parameter to plot your results.
64
+ CFL (float): CFL number to calculate the time step of your simulation.
65
+ dt_min (float): Minimum time step for your variable time step.
66
+ dt_max (float): Maximum time step for your variable time step.
67
+ t_initial (float): Initial time of the simulation.
68
+ Days (int): Number of days of the simulation.
69
+ Hours (int): Number of hours of the simulation.
70
+ Minutes (int): Number of minutes of the simulation.
71
+ Seconds (int): Number of seconds of the simulation.
72
+ wanted_time (list): Array with all the times at which we want a save.
73
+ n_t (int): Length of wanted_time.
74
+ count_initial (int): Initial step of the simulation.
75
+ count_pre (int): Initial step of the simulation - 1.
76
+ n_b (int): Number of simulated bodies.
77
+ n_parallel (int): Number of times the process is parallelized (number of cores used).
78
+ random_IP (float): Radius of the uncertainty area of the drowning point (in cells).
79
+ T_water (float): Average water temperature in °C.
80
+ vertical (bool): Binary parameter to consider vertical motion.
81
+ DZ (float): Step size for vertical motion (used in simulation).
82
+ Z_param (pd.DataFrame): Dataframe holding the parameters for vertical motion simulation.
83
+ """
84
+
85
+ self.Default_values()
86
+ self.from_attributes_to_dictionnary()
87
+
88
+ ## Loads all the parameters from the parameters.param file
89
+ if Path_dir is not None:
90
+ self.Path_saving = Path(Path_dir)
91
+ self.from_dot_param_to_dictionnary(store_dir=self.Path_saving)
92
+ self.from_dictionnary_to_attributes()
93
+ # self.update_params(str(self.Path_saving))
94
+
95
+ def Default_values(self):
96
+ """
97
+ Sets the default values for each parameter by creating a first parameter.param
98
+ """
99
+ import pandas as pd
100
+ import math
101
+ import numpy.random as rnd
102
+
103
+ self.Profile_this = 0
104
+ self.Redraw = 0#[1,2,3] #0 for no
105
+
106
+ current_dir = Path(__file__).resolve().parent
107
+ self.current_dir = current_dir
108
+ self.saving = 1
109
+ self.file_name = 'Test'
110
+
111
+ self.loading = 0
112
+ self.Path_loading = None
113
+ self.Path_saving = None
114
+
115
+ self.Path_Wolf = None
116
+
117
+ self.plot_pos = 0
118
+
119
+ self.a_RK = 0.5
120
+
121
+ self.image = 0
122
+
123
+ self.CFL = 0.01
124
+ self.dt_min = 0.01
125
+ self.dt_max = 1 #s
126
+ self.t_initial = 0*60*60*24
127
+ self.i_initial = math.floor(self.t_initial/self.dt_max)+1
128
+
129
+ self.Days = int(0) #days
130
+ self.Hours = int(1) #h
131
+ self.Minutes = int(0) #min
132
+ self.Seconds = int(0) #s
133
+ time_goal = self.Days*24*60*60 + self.Hours*60*60 + self.Minutes*60 + self.Seconds #s
134
+ self.time_goal = time_goal
135
+
136
+ self.ind_pos_0_x = 0 #For L14: 3983, L_30: 4303
137
+ self.ind_pos_0_y = 0 #For L14: 3780, L_30: 3902
138
+
139
+ self.origx = 215702
140
+ self.origy = 130000
141
+ self.dx = 5
142
+ self.dy = 5
143
+ self.nbx = 8460
144
+ self.nby = 10416
145
+
146
+ self.n_saved = 1
147
+
148
+ self.n_b = 10000
149
+ n_b = self.n_b
150
+ self.n_parallel = 2 #Number of processes to be ran in parallel
151
+ Z_param = pd.DataFrame(data=None,columns=COLUMN_Z_PARAM,dtype=np.int32)
152
+ Z_param.vertical = 1 # 1 = Consider the vertical motion, 0 = not considered
153
+ Z_param.U_z = 0*np.ones((n_b)) #0 = U constant on depth, 1 = U varies with the depth (log law)
154
+ d_50 = 2*2*40 *10**-3 #to be confirmed
155
+ Z_param.z_0 = d_50/30*np.ones((n_b)) #experimental results of Nikuradse (not found if published in 1933 or 1950 but nobody seems to care)
156
+ Z_param.mu_stat = 1 * np.ones((n_b)) #rnd.beta(1,1,size=(n_b))*(1-0.3)+0.3
157
+ Z_param.Time_float = 0*np.ones((n_b))
158
+ Z_param.T_w = 15*np.ones((n_b))
159
+ Z_param.ADD = time_goal/60/60/24*15
160
+ Z_param.ADD_resurface = 5250/15 * rnd.beta(4,4,size=n_b) #source: Heaton 2011 considering a TADS between 14 and 15 as maximum expension
161
+ self.Z_param = Z_param
162
+
163
+ ## Let the viewer edit the parameters
164
+ self.victim()
165
+
166
+ self.path = Path(current_dir)
167
+ # self.save_json(Path(self.Path_saving))
168
+
169
+ def victim(self):
170
+ """
171
+ Definition of the victim's caracteristics
172
+
173
+ gender : Gender of the victim, 1 for man, 2 for women
174
+ Age : Age of the victim in years
175
+ height : Height of the victim in m
176
+ mass : Mass of the victim in kg
177
+ BMI : BMI of the victim in kg/m²
178
+ clothes : clothing type of the victim (0 for naked, 1 for summer clothes, 2 for spring clothes, 3 for winter clothes)
179
+ T_w : Average water temperature in °C
180
+ ini_drowning : Time at which the victim drowned in the day (format 24H)
181
+
182
+ """
183
+ self.gender = -1
184
+ self.Age = -1
185
+ self.height = -1
186
+ self.mass = -1
187
+ self.BMI = -1
188
+ self.clothes = -1
189
+ self.T_w = 15
190
+ self.ini_drowning = 10 #simpledialog.askinteger('Hour at which the victim fell in the water','Time of drowning in hours: \nExample: 2 AM (2h00) being 2 \n5 PM (17h00) being 17',minvalue=0,maxvalue=23,parent=root)
191
+ self.m_b_add = 0 #mass of added accessories
192
+
193
+ def from_attributes_to_dictionnary(self):
194
+ "Create a dictionnary from the attributes of the class"
195
+
196
+ param_dict = {}
197
+
198
+ # Dictionnaire des sections et paramètres à ajouter
199
+ param_dict = {
200
+ "Options": {
201
+ "Profile": {
202
+ "value": self.Profile_this,
203
+ "explicit name": "Profile",
204
+ "description": "Do you want to profile your code?",
205
+ "type": "Integer",
206
+ "choices": {
207
+ "Don't profile the code":0,
208
+ "Profile the code":1
209
+ },
210
+ "mandatory": False
211
+ },
212
+ "Save": {
213
+ "value": self.saving,
214
+ "explicit name": "Save",
215
+ "description": "Enable saving of results?",
216
+ "type": "Integer",
217
+ "choices": {
218
+ "Don't save":0,
219
+ "Save the results":1
220
+ },
221
+ "mandatory": True
222
+ },
223
+ "Load": {
224
+ "value": self.loading,
225
+ "explicit name": "Load",
226
+ "description": "Enable loading of previous results?",
227
+ "type": "Integer",
228
+ "choices": {
229
+ "Don't load the results from a previous simulation":0,
230
+ "Load":1
231
+ },
232
+ "mandatory": True
233
+ },
234
+ "Plot": {
235
+ "value": self.plot_pos,
236
+ "explicit name": "Plot",
237
+ "description": "Enable plotting of results?",
238
+ "type": "Integer",
239
+ "choices": {
240
+ "Don't plot":0,
241
+ "Plot the results when simulation is over":1
242
+ },
243
+ "mandatory": False
244
+ },
245
+ "n_parallel": {
246
+ "value": self.n_parallel,
247
+ "explicit name": "Number of parallel processes",
248
+ "description": "Number of parallel processes to use",
249
+ "type": "Integer",
250
+ "choices": None,
251
+ "mandatory": True
252
+ },
253
+ "vertical": {
254
+ "value": 1, # Assuming vertical motion is always enabled
255
+ "explicit name": "Vertical motion",
256
+ "description": "Consider vertical motion in the simulation?",
257
+ "type": "Integer",
258
+ "choices": {
259
+ "No vertical motion allowed":0,
260
+ "With vertical motion allowed":1
261
+ },
262
+ "mandatory": False
263
+ },
264
+ "a_RK": {
265
+ "value": self.a_RK,
266
+ "explicit name": "Runge-Kutta ponderation coefficient",
267
+ "description": "Coefficient for RK22 integration",
268
+ "type": "Float",
269
+ "choices": None,
270
+ "mandatory": False
271
+ },
272
+ "image": {
273
+ "value": self.image,
274
+ "explicit name": "Progression bar",
275
+ "description": "Enable image generation",
276
+ "type": "Integer",
277
+ "choices": {
278
+ "Plot progress with loading bar":0,
279
+ "Plot progress with progress image":1
280
+ },
281
+ "mandatory": False
282
+ }
283
+ },
284
+ "Paths": {
285
+ "File": {
286
+ "value": self.file_name,
287
+ "explicit name": "File name",
288
+ "description": "Name of the file to save",
289
+ "type": "String",
290
+ "choices": None,
291
+ "mandatory": True
292
+ },
293
+ "Save": {
294
+ "value": self.Path_saving,
295
+ "explicit name": "Save path",
296
+ "description": "Path where results will be saved",
297
+ "type": "Directory",
298
+ "choices": None,
299
+ "mandatory": True
300
+ },
301
+ "Load": {
302
+ "value": self.Path_loading,
303
+ "explicit name": "Load path",
304
+ "description": "Path to load previous results",
305
+ "type": "Directory",
306
+ "choices": None,
307
+ "mandatory": False
308
+ },
309
+ "Wolf": {
310
+ "value": self.Path_Wolf,
311
+ "explicit name": "Results of Wolf GPU simulation path",
312
+ "description": "Path to the WolfGPU simulation",
313
+ "type": "Directory",
314
+ "choices": None,
315
+ "mandatory": True
316
+ }
317
+ },
318
+ "DT": {
319
+ "CFL": {
320
+ "value": self.CFL,
321
+ "explicit name": "CFL",
322
+ "description": "Courant number for time step calculation",
323
+ "type": "Float",
324
+ "choices": None,
325
+ "mandatory": False
326
+ },
327
+ "dt_min": {
328
+ "value": self.dt_min,
329
+ "explicit name": "Minimum time step",
330
+ "description": "Minimum time step in seconds",
331
+ "type": "Float",
332
+ "choices": None,
333
+ "mandatory": False
334
+ },
335
+ "dt_max": {
336
+ "value": self.dt_max,
337
+ "explicit name": "Maximum time step",
338
+ "description": "Maximum time step in seconds",
339
+ "type": "Float",
340
+ "choices": None,
341
+ "mandatory": False
342
+ }
343
+ },
344
+ "Duration": {
345
+ "t_d": {
346
+ "value": self.Days,
347
+ "explicit name": "Days",
348
+ "description": "Number of days for the simulation",
349
+ "type": "Integer",
350
+ "choices": None,
351
+ "mandatory": True
352
+ },
353
+ "t_h": {
354
+ "value": self.Hours,
355
+ "explicit name": "Hours",
356
+ "description": "Number of hours for the simulation",
357
+ "type": "Integer",
358
+ "choices": None,
359
+ "mandatory": True
360
+ },
361
+ "t_min": {
362
+ "value": self.Minutes,
363
+ "explicit name": "Minutes",
364
+ "description": "Number of minutes for the simulation",
365
+ "type": "Integer",
366
+ "choices": None,
367
+ "mandatory": True
368
+ },
369
+ "t_s": {
370
+ "value": self.Seconds,
371
+ "explicit name": "Seconds",
372
+ "description": "Number of seconds for the simulation",
373
+ "type": "Integer",
374
+ "choices": None,
375
+ "mandatory": True
376
+ }
377
+ },
378
+ "Victim (-1 for unknown)": {
379
+ "n_b": {
380
+ "value": self.n_b,
381
+ "explicit name": "Number of bodies",
382
+ "description": "Number of simulated bodies",
383
+ "type": "Integer",
384
+ "choices": None,
385
+ "mandatory": True
386
+ },
387
+ "gender": {
388
+ "value": self.gender,
389
+ "explicit name": "Gender",
390
+ "description": "Gender of the victim (1 for male, 2 for female)",
391
+ "type": "Integer",
392
+ "choices": {
393
+ "Unknown":-1,
394
+ "Man":1,
395
+ "Woman":2
396
+ },
397
+ "mandatory": True
398
+ },
399
+ "age": {
400
+ "value": self.Age,
401
+ "explicit name": "Age [years]",
402
+ "description": "Age of the victim in years",
403
+ "type": "Integer",
404
+ "choices": None,
405
+ "mandatory": True
406
+ },
407
+ "h_b": {
408
+ "value": self.height,
409
+ "explicit name": "Height [m]",
410
+ "description": "Height of the victim in meters",
411
+ "type": "Float",
412
+ "choices": None,
413
+ "mandatory": True
414
+ },
415
+ "m_b": {
416
+ "value": self.mass,
417
+ "explicit name": "Mass [kg]",
418
+ "description": "Mass of the victim in kilograms",
419
+ "type": "Float",
420
+ "choices": None,
421
+ "mandatory": True
422
+ },
423
+ "BMI": {
424
+ "value": self.BMI,
425
+ "explicit name": "BMI [kg/m²]",
426
+ "description": "Body Mass Index of the victim, needed only if the mass is unknown",
427
+ "type": "Float",
428
+ "choices": None,
429
+ "mandatory": True
430
+ },
431
+ "clothes": {
432
+ "value": self.clothes,
433
+ "explicit name": "Clothing type",
434
+ "description": "Clothing type of the victim",
435
+ "type": "Integer",
436
+ "choices": {
437
+ "Unknown":-1,
438
+ "Naked":0,
439
+ "Short and T-shirt (Summer clothes)":1,
440
+ "Sweater and trousers (Spring/Fall)":2,
441
+ "Sweater, trousers and heavy warm jacket (Winter clothes)":3
442
+ },
443
+ "mandatory": True
444
+ },
445
+ "T_w": {
446
+ "value": self.T_w,
447
+ "explicit name": "Water temperature [°C]",
448
+ "description": "Average water temperature in °C",
449
+ "type": "Float",
450
+ "choices": None,
451
+ "mandatory": True
452
+ },
453
+ "ini_drowning": {
454
+ "value": self.ini_drowning,
455
+ "explicit name": "Initial drowning time",
456
+ "description": "Time at which the victim drowned",
457
+ "type": "Integer",
458
+ "choices": None,
459
+ "mandatory": True
460
+ },
461
+ "m_b_add": {
462
+ "value": self.m_b_add,
463
+ "explicit name": "Added mass of accessories [kg]",
464
+ "description": "Mass of added accessories, like a backpack or lifting weights",
465
+ "type": "Float",
466
+ "choices": None,
467
+ "mandatory": True
468
+ }
469
+ },
470
+ "Initial_drowning_point": {
471
+ "x": {
472
+ "value": self.ind_pos_0_x,
473
+ "explicit name": "X-cell",
474
+ "description": "X-coordinate of the initial drowning point",
475
+ "type": "Integer",
476
+ "choices": None,
477
+ "mandatory": True
478
+ },
479
+ "y": {
480
+ "value": self.ind_pos_0_y,
481
+ "explicit name": "Y-cell",
482
+ "description": "Y-coordinate of the initial drowning point",
483
+ "type": "Integer",
484
+ "choices": None,
485
+ "mandatory": True
486
+ }
487
+ },
488
+ "Grid": {
489
+ "origx": {
490
+ "value": self.origx,
491
+ "explicit name": "Origin X",
492
+ "description": "Origin of the matrix in X",
493
+ "type": "Float",
494
+ "choices": None,
495
+ "mandatory": True
496
+ },
497
+ "origy": {
498
+ "value": self.origy,
499
+ "explicit name": "Origin Y",
500
+ "description": "Origin of the matrix in Y",
501
+ "type": "Float",
502
+ "choices": None,
503
+ "mandatory": True
504
+ },
505
+ "dx": {
506
+ "value": self.dx,
507
+ "explicit name": "Delta X",
508
+ "description": "Spatial step of the matrix in X",
509
+ "type": "Float",
510
+ "choices": None,
511
+ "mandatory": True
512
+ },
513
+ "dy": {
514
+ "value": self.dy,
515
+ "explicit name": "Delta Y",
516
+ "description": "Spatial step of the matrix in Y",
517
+ "type": "Float",
518
+ "choices": None,
519
+ "mandatory": True
520
+ },
521
+ "nbx": {
522
+ "value": self.nbx,
523
+ "explicit name": "Nb X",
524
+ "description": "Number of steps of the matrix in X",
525
+ "type": "Integer",
526
+ "choices": None,
527
+ "mandatory": True
528
+ },
529
+ "nby": {
530
+ "value": self.nby,
531
+ "explicit name": "Nb Y",
532
+ "description": "Number of steps of the matrix in Y",
533
+ "type": "Integer",
534
+ "choices": None,
535
+ "mandatory": True
536
+ }
537
+ }
538
+ }
539
+
540
+ self.param_dict = param_dict
541
+
542
+ return
543
+
544
+ def from_dictionnary_to_attributes(self):
545
+ """
546
+ Update the attributes of the class based on the values in self.param_dict.
547
+ """
548
+ # Parcourir les sections et les paramètres dans param_dict
549
+ for section, params in self.param_dict.items():
550
+ for key, param_data in params.items():
551
+ # Récupérer la valeur du paramètre
552
+ value = param_data.get("value", None)
553
+ if param_data.get("type", None)=='Integer':
554
+ value = int(value)
555
+
556
+ # Mettre à jour l'attribut correspondant dans self
557
+ if section == "Options":
558
+ if key == "Profile":
559
+ self.Profile_this = value
560
+ elif key == "Save":
561
+ self.saving = value
562
+ elif key == "Load":
563
+ self.loading = value
564
+ elif key == "Plot":
565
+ self.plot_pos = value
566
+ elif key == "n_parallel":
567
+ self.n_parallel = value
568
+ elif key == "vertical":
569
+ self.Z_param.vertical = value
570
+ elif key == "a_RK":
571
+ self.a_RK = value
572
+ elif key == "image":
573
+ self.image = value
574
+
575
+ elif section == "Paths":
576
+ if key == "File":
577
+ self.file_name = value
578
+ elif key == "Save":
579
+ self.Path_saving = value
580
+ elif key == "Load":
581
+ self.Path_loading = value
582
+ elif key == "Wolf":
583
+ self.Path_Wolf = value
584
+
585
+ elif section == "DT":
586
+ if key == "CFL":
587
+ self.CFL = value
588
+ elif key == "dt_min":
589
+ self.dt_min = value
590
+ elif key == "dt_max":
591
+ self.dt_max = value
592
+
593
+ elif section == "Duration":
594
+ if key == "Days":
595
+ self.Days = value
596
+ elif key == "Hours":
597
+ self.Hours = value
598
+ elif key == "Minutes":
599
+ self.Minutes = value
600
+ elif key == "Seconds":
601
+ self.Seconds = value
602
+
603
+ elif section == "Victim (-1 for unknown)":
604
+ if key == "n_b":
605
+ self.n_b = value
606
+ elif key == "gender":
607
+ self.gender = value
608
+ elif key == "age":
609
+ self.Age = value
610
+ elif key == "h_b":
611
+ self.height = value
612
+ elif key == "m_b":
613
+ self.mass = value
614
+ elif key == "BMI":
615
+ self.BMI = value
616
+ elif key == "clothes":
617
+ self.clothes = value
618
+ elif key == "T_w":
619
+ self.T_w = value
620
+ elif key == "ini_drowning":
621
+ self.ini_drowning = value
622
+ elif key == "m_b_add":
623
+ self.m_b_add = value
624
+
625
+ elif section == "Initial_drowning_point":
626
+ if key == "x":
627
+ self.ind_pos_0_x = value
628
+ elif key == "y":
629
+ self.ind_pos_0_y = value
630
+
631
+ elif section == "Grid":
632
+ if key == "Origin X":
633
+ self.origx = value
634
+ elif key == "Origin Y":
635
+ self.origy = value
636
+ elif key == "Delta X":
637
+ self.dx = value
638
+ elif key == "Delta Y":
639
+ self.dy = value
640
+ elif key == "Nb X":
641
+ self.nbx = value
642
+ elif key == "Nb Y":
643
+ self.nby = value
644
+
645
+ # Initialise the parameters of the simulation with default values and values given in the parameters.param file
646
+ self.t_initial = 0
647
+ self.i_initial = 0
648
+ self.time_goal = self.Days*24*60*60 + self.Hours*60*60 + self.Minutes*60 + self.Seconds #s
649
+ self.wanted_time = np.array([self.t_initial])
650
+ self.n_saved = 1
651
+ for i in np.arange(10,self.time_goal+10,10):
652
+ if i<60:
653
+ self.wanted_time = np.append(self.wanted_time,i)
654
+ self.n_saved += 1
655
+ elif np.logical_and(i<10*60,(i%60==0)):
656
+ self.wanted_time = np.append(self.wanted_time,i)
657
+ self.n_saved += 1
658
+ elif np.logical_and(i<30*60,(i%(5*60)==0)):
659
+ self.wanted_time = np.append(self.wanted_time,i)
660
+ self.n_saved += 1
661
+ elif np.logical_and(i<60*60,(i%(10*60)==0)):
662
+ self.wanted_time = np.append(self.wanted_time,i)
663
+ self.n_saved += 1
664
+ elif np.logical_and(i<=24*60*60,(i%(60*60)==0)):
665
+ self.wanted_time = np.append(self.wanted_time,i)
666
+ self.n_saved += 1
667
+ elif np.logical_and(i<=2*24*60*60,(i%(2*60*60)==0)):
668
+ self.wanted_time = np.append(self.wanted_time,i)
669
+ self.n_saved += 1
670
+ elif (i%(3*60*60)==0):
671
+ self.wanted_time = np.append(self.wanted_time,i)
672
+ self.n_saved += 1
673
+
674
+ self.wanted_time = np.append(self.wanted_time,0)
675
+
676
+ self.n_t = math.floor(self.time_goal/self.dt_min)+1
677
+ self.count_initial = 1
678
+ self.count_pre = self.count_initial-1
679
+
680
+ n_b = self.n_b
681
+
682
+ self.random_IP = 1 # number of cells considered for the radius of random position
683
+
684
+ ## Parameters of vertical motion (ADD, temp)
685
+
686
+ self.DZ = 0.1
687
+
688
+ # Update of the dataframe
689
+ Z_param = pd.DataFrame(data=None,columns=COLUMN_Z_PARAM,dtype=np.int32)
690
+ Z_param.U_z = 0*np.ones((n_b)) #0 = U constant on depth, 1 = U varies with the depth (log law)
691
+ d_50 = 2*2*40 *10**-3 #to be confirmed
692
+ Z_param.z_0 = d_50/30*np.ones((n_b)) #experimental results of Nikuradse (not found if published in 1933 or 1950 but nobody seems to care)
693
+ Z_param.mu_stat = 1 * np.ones((n_b)) #rnd.beta(1,1,size=(n_b))*(1-0.3)+0.3
694
+ Z_param.Time_float = 0*np.ones((n_b))
695
+ Z_param.T_w = self.T_w*np.ones((n_b))
696
+ Z_param.ADD = self.time_goal/60/60/24*self.T_w
697
+ Z_param.ADD_resurface = 5250/self.T_w * rnd.beta(4,4,size=n_b) #source: Heaton 2011 considering a TADS between 14 and 15 as maximum expension
698
+ self.Z_param = Z_param
699
+
700
+ def from_dot_param_to_dictionnary(self,store_dir: Path = None):
701
+ """
702
+ Update the parameters with the modifications made by the user with the file parameters.param
703
+
704
+ :param store_dir: directory where the file parameters.param is
705
+ """
706
+
707
+ # Charger le dictionnaire existant
708
+ param_dict = self.param_dict
709
+
710
+ data = {}
711
+ text_file_path = join(store_dir,"parameters.param")
712
+
713
+ if not os.path.exists(text_file_path):
714
+ raise FileNotFoundError(f"Le fichier {text_file_path} est introuvable.")
715
+
716
+ with open(text_file_path, 'r', encoding='ISO-8859-1') as file:
717
+ for line in file:
718
+ line = line.strip()
719
+
720
+ # Vérifier si la ligne est un nom de section (par exemple, 'Options:', 'Path:', etc.)
721
+ if line.endswith(":"):
722
+ # Crée une nouvelle sous-section
723
+ current_section = line[:-1] # Retire le ':' pour obtenir le nom de section
724
+ data[current_section] = {}
725
+ elif "\t" in line and current_section:
726
+ # Split clé et valeur
727
+ key, value = line.split("\t", 1)
728
+
729
+ # Convertir la valeur en nombre si possible
730
+ try:
731
+ value = float(value)
732
+ except ValueError:
733
+ pass # Garde la valeur comme chaîne de caractères si non convertible
734
+
735
+ # Ajout de la clé et de la valeur dans la section actuelle
736
+ data[current_section][key] = value
737
+
738
+ # Mettre à jour self.param_dict avec les valeurs de data
739
+ for section, params in data.items():
740
+ if section in param_dict:
741
+ for key, value in params.items():
742
+ # Rechercher la clé dans le dictionnaire existant
743
+ for param_key, param_data in param_dict[section].items():
744
+ if param_data["explicit name"] == key:
745
+ # Mettre à jour la valeur
746
+ param_dict[section][param_key]["value"] = value
747
+ break
748
+
749
+ # Sauvegarder le dictionnaire mis à jour dans self.param_dict
750
+ self.param_dict = param_dict
751
+
752
+ return
753
+
754
+ def Human_generation(self):
755
+ """
756
+ Generates the bodies for the simulation
757
+
758
+ :return Human
759
+
760
+ Attributes:
761
+ Human : Dataframe panda with each line representing a body and n_b lines, so one for eahc body
762
+ gender : Gender of the victim, 1 for man, 2 for women
763
+ Age : Age of the victim in years
764
+ height : Height of the victim in m
765
+ mass : Mass of the victim in kg
766
+ BMI : BMI of the victim in kg/m²
767
+ clothes : clothing type of the victim (0 for naked, 1 for summer clothes, 2 for spring clothes, 3 for winter clothes)
768
+ CAM : Added mass coefficient of the body
769
+ CDA : Drag area of the body (drag coefficient * a reference area)
770
+ CLA : Lift area of the body
771
+ CSA : Side area of the body
772
+ fp_x : Projection coefficient along the x-axis to go from the BSA to the frontal area
773
+ fp_y : Projection coefficient along the y-axis to go from the BSA to the frontal area
774
+ fp_z : Projection coefficient along the z-axis to go from the BSA to the frontal area
775
+ lungs_volume_TLC : Lungs volume at Total Lungs Capacity
776
+ lungs_volume_FRC : Lungs volume at Functionnal Residual Capacity (at rest, after normally expiring)
777
+ dm : Amount of swallowed water
778
+ BSA : Body surface area (i.e. surface of the skin)
779
+ Death : Type of death
780
+ eps : Width of the body
781
+ V_clothes_o : Initial volume of clothes (according to Barwood et al., 2011)
782
+ V_clothes_one : Volume of clothes after 20min at rest (according to Barwood et al., 2011)
783
+ V_clothes_two : Volume of clothes after 20min of swimming (according to Barwood et al., 2011)
784
+ error_perc_fat : Deviation to the average on the percentage body fat of the body calculated from the equation of Siri et al. 1960
785
+
786
+ """
787
+
788
+ Human = pd.DataFrame(data=None,columns=COLUMNS_HUMAN,dtype=np.int32)
789
+
790
+ n_b = self.n_b
791
+
792
+ ##Gender
793
+ Human.gender = self.gender * np.ones((n_b))
794
+ if self.gender == -1:
795
+ Human.gender = np.zeros(n_b)
796
+ Human.gender[:n_b // 2] = 2
797
+ Human.gender[n_b // 2:] = 1
798
+ ind_m = np.where(Human.gender==1)[0]
799
+ ind_w = np.where(Human.gender==2)[0]
800
+
801
+ ##Age
802
+ Human.Age = self.Age * np.ones((n_b))
803
+ if self.Age==-1:
804
+ age_min = 18
805
+ age_max = 90 + 1
806
+ Human.Age = rnd.randint(age_min,age_max,size=(n_b))
807
+
808
+ #Height
809
+ h_av = self.height
810
+ h_max = np.array([205, 190]) /100
811
+ h_min = np.array([150, 140]) /100
812
+ [ah_w,bh_w] = known_1(2,h_min[1],h_max[1],h_av-0.025,h_av+0.025,0.1,0.9)
813
+ [ah_m,bh_m] = known_1(1,h_min[0],h_max[0],h_av-0.025,h_av+0.025,0.1,0.9)
814
+ Human.loc[ind_m,'height'] = rnd.beta(ah_m,bh_m,size=(len(ind_m)))*(h_max[0]-h_min[0])+h_min[0] #men
815
+ Human.loc[ind_w,'height'] = rnd.beta(ah_w,bh_w,size=(len(ind_w)))*(h_max[1]-h_min[1])+h_min[1] #women
816
+ if h_av == -1:
817
+ Human.loc[ind_m,'height'] = rnd.beta(5.8697,6.075,size=(len(ind_m)))*(h_max[0]-h_min[0])+h_min[0] #men
818
+ Human.loc[ind_w,'height'] = rnd.beta(3.976,5.965,size=(len(ind_w)))*(h_max[1]-h_min[1])+h_min[1] #women
819
+
820
+ ##Mass or BMI
821
+ m_av = self.mass
822
+ m_max = np.array([135, 130])
823
+ m_min = np.array([35, 35])
824
+ [am_w,bm_w] = known_1(2+3,m_min[1],m_max[1],m_av-2.5,m_av+2.5,0.1,0.9)
825
+ [am_m,bm_m] = known_1(1+3,m_min[0],m_max[0],m_av-2.5,m_av+2.5,0.1,0.9)
826
+ Human.loc[ind_m,'mass'] = rnd.beta(am_m,bm_m,size=((len(ind_m))))*(m_max[0] - m_min[0]) + m_min[0]
827
+ Human.loc[ind_w,'mass'] = rnd.beta(am_w,bm_w,size=((len(ind_w))))*(m_max[1] - m_min[1]) + m_min[1]
828
+ Human.BMI = Human.mass / Human.height**2
829
+ known = 1
830
+ if m_av < 0:
831
+ BMI = self.BMI
832
+ BMI_min = 16
833
+ BMI_max = 40
834
+ BMI_25 = [20, 21.3, 22.5, 23.3, 22.9, 23.7, 23.1]
835
+ BMI_50 = [21.7, 23.4, 24.8, 25.7, 25.9, 26.3, 25.3]
836
+ BMI_75 = [24.3, 26.4, 28, 29, 29.1, 29.7, 28]
837
+ ind_Age = np.minimum(math.floor(np.mean(Human.Age.to_numpy())/10)-1,6)
838
+ if BMI > 0:
839
+ BMI_down = BMI-1
840
+ BMI_up = BMI+1
841
+ [abmi,bbmi] = known_1(3,BMI_min,BMI_max,BMI_down,BMI_up,0.1,0.9)
842
+ Human.BMI = rnd.beta(abmi,bbmi,size=((n_b)))*(BMI_max-BMI_min)+BMI_min
843
+ else:
844
+ [abmi,bbmi] = known_1(3,BMI_min,BMI_max,BMI_25[ind_Age],BMI_75[ind_Age],0.25,0.75)
845
+ Human.BMI = rnd.beta(abmi,bbmi,size=((n_b)))*(BMI_max-BMI_min)+BMI_min
846
+ known = 0
847
+ Human.mass = Human.BMI*Human.height**2
848
+
849
+ ##Clothes
850
+ clothes = self.clothes #simpledialog.askinteger('Dialog title','Clothing type with: \n-1 if unknown \n0 for naked or in underwear\n1 for summer clothes (short and a t-shirt or dress)\n2 for autumn/spring clothes (trousers, a t-shirt, a sweater, and eventually a water/windproof jacket)\n3 for winter clothes (trousers, a t-shirt, a sweater, and a heavy warm jacket or more clothes)',minvalue=-1,maxvalue=3,parent=root) #0 for naked, 1 for summer clothes, 2 for autumn/spring clothes, 3 for winter clothes
851
+ if clothes==-1:
852
+ clothes=2
853
+
854
+ Human,error_perc_fat = Skinfold(n_b,known,Human)
855
+
856
+
857
+ Human.CAM = ((2-Human.gender)*0.268+(Human.gender-1)*0.236) *np.ones((n_b)) #according Caspersen et al., 2010: Added mass in human swimmers
858
+
859
+ #CD, CL and CS fitted to the results of tests realised in the wind tunnel on 28/02/2023, tight clothing = no clothes or summer clothes
860
+ Human.CDA = 0.4181*np.ones(n_b)*(clothes<2) + 0.5172*np.ones(n_b)*(clothes>=2)
861
+ Human.CLA = 0.07019*np.ones(n_b)*(clothes<2) + 0.08387*np.ones(n_b)*(clothes>=2)
862
+ Human.CSA = 0.03554*np.ones(n_b)*(clothes<2) + 0.04047*np.ones(n_b)*(clothes>=2)
863
+ # Human.CDA = rnd.normal(0.4181,0.03434,n_b)*(clothes<2) + rnd.normal(0.5172,0.0406,n_b)*(clothes>=2)
864
+ # Human.CLA = rnd.normal(0.07019,0.05035,n_b)*(clothes<2) + rnd.normal(0.08387,0.08138,n_b)*(clothes>=2)
865
+ # Human.CSA = rnd.normal(0.03554,0.02545,n_b)*(clothes<2) + rnd.normal(0.04047,0.04311,n_b)*(clothes>=2)
866
+
867
+ #Mandatory for the structure of the numpy variable Human generated for the calculations
868
+ Human.fp_x = rnd.beta(1,1,size=(n_b))*(0.36-0.16)+0.16
869
+ Human.fp_y = 0.36-Human.fp_x+0.16
870
+ Human.fp_z = np.ones((n_b)) * 0.2
871
+
872
+
873
+ Human.lungs_volume_TLC = 10**-3 * ((7.99*Human.height-7.08) * (2-Human.gender) + (6.6*Human.height-5.79) * (Human.gender-1)) #Formulas ERC valid for men between 1.55 and 1.95 m and women between 1.45 and 1.8 m
874
+ Human.lungs_volume_FRC = 10**-3 * ((2.34*Human.height+0.01*Human.Age-1.09) * (2-Human.gender) + (2.24*Human.height+0.001*Human.Age-1) * (Human.gender-1)) #Formulas ERC valid for men between 1.55 and 1.95 m and women between 1.45 and 1.8 m
875
+ Human.lungs_volume_TLC = Human.lungs_volume_TLC * (0.0403*Human.BMI**2 - 3.1049*Human.BMI + 149.58)/100 #Digitalization of TOTAL part of figure 4 of doi:10.1136/bmjresp-2017-000231
876
+ Human.lungs_volume_FRC = Human.lungs_volume_FRC * (0.102*Human.BMI**2 - 7.4504*Human.BMI + 229.61)/100 #Digitalization of TOTAL part of figure 4 of doi:10.1136/bmjresp-2017-000231
877
+
878
+ Human.dm = Human.mass * (0.1 * rnd.beta(1.5,1.5,size=((n_b))) + 0.0) #Between x and y% of the body mass (usually around 10 according to test on dogs)
879
+ Human.dm += self.m_b_add
880
+
881
+ Human.BSA = ((128.1 * Human.mass**0.44 * (Human.height*100)**0.6) * (2-Human.gender) + (147.4 * Human.mass**0.47 * (Human.height*100)**0.55) * (Human.gender-1))*10**-4
882
+
883
+ Human.Death = np.ones(n_b)
884
+
885
+ Human.eps = np.ones(n_b)*0.2
886
+
887
+
888
+ clothes_alpha_m = np.array([[1,0.1771,0.0192,0.0082],[1,5.7342e-6,0.0253,0.8511],[1,5.9719e-6,0.333,4.981e-6]])
889
+ clothes_alpha_w = np.array([[1,0.7676,4.4037,0.8523],[1,0.5375,0.3333,3.5128],[1,0.5375,0.7082,2.333]])
890
+ clothes_beta_m = np.array([[1,0.3542,0.0385,0.0095],[1,9.9565e-6,0.0505,1.7022],[1,1.1522e-5,0.667,8.7348e-6]])
891
+ clothes_beta_w = np.array([[1,1.5353,8.8072,1.7046],[1,1.0749,0.6667,7.0255],[1,1.0749,1.4165,4.6659]])
892
+
893
+ clothes_mean_m = np.array([[0.5,0.6127,2.7573,4.3912],[0.5,0.204,0.715,1.021],[0.5,0.408,1.1233,0.613]])*10**-3
894
+ clothes_mean_w = np.array([[0.5,1.123,2.655,4.493],[0.5,0.9191,1.1233,1.94],[0.5,0.9191,1.2254,2.0424]])*10**-3
895
+ clothes_std_m = np.array([[0.5,0.7148,0.817,0.6127],[0.5,0.9191,1.1233,0.817],[0.5,0.817,1.1233,2.451]])*10**-3
896
+ clothes_std_w = np.array([[0.5,0.9191,1.6339,1.6339],[0.5,0.817,1.1233,1.225],[0.5,0.817,1.0212,0.817]])*10**-3
897
+
898
+ clothes_max_m = clothes_mean_m + 2*clothes_std_m
899
+ clothes_min_m = clothes_mean_m - 2*clothes_std_m
900
+ clothes_max_w = clothes_mean_w + 2*clothes_std_w
901
+ clothes_min_w = clothes_mean_w - 2*clothes_std_w
902
+
903
+
904
+ Human.V_clothes_o = (clothes!=0)* ((rnd.beta(clothes_alpha_m[0,clothes],clothes_beta_m[0,clothes],size=(n_b))*(clothes_max_m[0,clothes]-clothes_min_m[0,clothes])+clothes_min_m[0,clothes])*(2-Human.gender) + (rnd.beta(clothes_alpha_w[0,clothes],clothes_beta_w[0,clothes],size=(n_b))*(clothes_max_w[0,clothes]-clothes_min_w[0,clothes])+clothes_min_w[0,clothes])*(Human.gender-1))
905
+ Human.V_clothes_one = (clothes!=0)* ((rnd.beta(clothes_alpha_m[1,clothes],clothes_beta_m[1,clothes],size=(n_b))*(clothes_max_m[1,clothes]-clothes_min_m[1,clothes])+clothes_min_m[1,clothes])*(2-Human.gender) + (rnd.beta(clothes_alpha_w[1,clothes],clothes_beta_w[1,clothes],size=(n_b))*(clothes_max_w[1,clothes]-clothes_min_w[1,clothes])+clothes_min_w[1,clothes])*(Human.gender-1))
906
+ Human.V_clothes_two = (clothes!=0)* ((rnd.beta(clothes_alpha_m[2,clothes],clothes_beta_m[2,clothes],size=(n_b))*(clothes_max_m[2,clothes]-clothes_min_m[2,clothes])+clothes_min_m[2,clothes])*(2-Human.gender) + (rnd.beta(clothes_alpha_w[2,clothes],clothes_beta_w[2,clothes],size=(n_b))*(clothes_max_w[2,clothes]-clothes_min_w[2,clothes])+clothes_min_w[2,clothes])*(Human.gender-1))
907
+
908
+ Human.error_perc_fat = error_perc_fat
909
+
910
+ self.Human = Human
911
+
912
+ def Initialisation_arrays(self):
913
+ """
914
+ Function where the matrixes of body position, speed, time, resurfacing and sinking are initialised, both for computing and saving
915
+ Initialisation of other variables used in the simulation
916
+
917
+ Attributes:
918
+
919
+ BC_cells : Array containing the index of all cells that are boundary conditions for the hydrodynamic simulation
920
+ DT_WOLF : Time step of the WOLF simulation
921
+ NbX : Number of cells in the x-direction for the WOLF simulation
922
+ NbY : Number of cells in the y-direction for the WOLF simulation
923
+ ini_drowning : Hour at which the victim fell in the water
924
+ count_Wolf : Time step of the Wolf simulation that we consider as our initial time in the Lagrangian simulation
925
+ wanted_Wolf : Array containing all the times at which we have a new Wolf result
926
+ Delta : Array containing the spatial and time steps
927
+ Pos : Working array containing the 3D positions of all bodies at time t and t-dt with shape (n_b,3,2)
928
+ Pos_b : Saving array containing the 3D positions of all bodies at all saving times with shape (n_b,3,n_t)
929
+ U : Working array containing the 3D velocities of all bodies at time t and t-dt with shape (n_b,3,2)
930
+ U_b : Saving array containing the 3D velocities of all bodies at all saving times with shape (n_b,3,n_t)
931
+ time : Working array containing the time associated to each body with shape (n_b,)
932
+ resurface : Saving array containing the resurfacing time of all bodies with shape (n_b,)
933
+ sinking : Saving array containing the sinking time of all bodies with shape (n_b,)
934
+ count : Counter to evaluate the progression of the savings
935
+ sp : Parameter deserving to work with the working variables
936
+ """
937
+
938
+ self.Human_generation()
939
+ n_b = self.n_b
940
+ n_saved = self.n_saved
941
+
942
+ self.BC_cells,self.DT_WOLF,DX,DY,H_mat,self.NbX,self.NbY,t_tot_Wolf = Read_Wolf_GPU_metadata(self.Path_Wolf)
943
+
944
+ X = np.arange(0,DX*self.NbX,DX)+DX/2
945
+ Y = np.arange(0,DY*self.NbY,DY)+DY/2
946
+
947
+ self.count_Wolf = self.ini_drowning -1
948
+ self.wanted_Wolf = np.arange(0,t_tot_Wolf+self.DT_WOLF,self.DT_WOLF)
949
+
950
+
951
+ Delta = np.zeros((5))
952
+ Delta[0] = DX
953
+ Delta[1] = DY
954
+ Delta[2] = 1 #DZ
955
+ Delta[3] = self.dt_max
956
+ Delta[4] = np.sqrt(DX**2+DY**2)
957
+ self.Delta = Delta
958
+
959
+ ind_pos_0_x = self.ind_pos_0_x
960
+ ind_pos_0_y = self.ind_pos_0_y
961
+ NbZ = H_mat[ind_pos_0_y,ind_pos_0_x]/Delta[2]
962
+ ind_pos_0_z = NbZ.astype(int)
963
+
964
+ index_b = np.zeros((n_b,3,n_saved)) #number of the body,(xyz) index in the matrix, time step
965
+ index_b = index_b.astype(int)
966
+
967
+ Pos_b = np.zeros((n_b,3,n_saved)) #number of the body,(xyz), time step
968
+
969
+ U_b = np.zeros((n_b,3,n_saved)) #number of the body,(xyz), time step
970
+
971
+ self.time_b = np.zeros((n_b,n_saved))
972
+
973
+ index_b[:,0,0] = np.ones((n_b)) * ind_pos_0_x
974
+ index_b[:,1,0] = np.ones((n_b)) * ind_pos_0_y
975
+ index_b[:,2,0] = np.zeros((n_b))
976
+
977
+ Pos_b[:,0,0] = np.ones((n_b)) * X[ind_pos_0_x]
978
+ Pos_b[:,1,0] = np.ones((n_b)) * Y[ind_pos_0_y]
979
+
980
+ ## Calculation of horizontal and vertical body motion
981
+
982
+ Pos = np.zeros((n_b,3,2))
983
+ U = np.zeros((n_b,3,2))
984
+
985
+ # Generation of uncertainty on the drownin point
986
+ if self.loading == 0:
987
+ rand_x = DX* rnd.uniform(size=(n_b))*np.sign(rnd.uniform(size=(n_b))-1/2)*self.random_IP
988
+ rand_y = DY* rnd.uniform(size=(n_b))*np.sign(rnd.uniform(size=(n_b))-1/2)*self.random_IP
989
+
990
+ Pos[:,0,0] = X[int(index_b[0,0,0])]+rand_x
991
+ Pos[:,1,0] = Y[int(index_b[0,1,0])]+rand_y
992
+ Pos[:,2,0] = H_mat[int(index_b[0,1,0]),int(index_b[0,0,0])]
993
+ Pos[:,0,1] = X[int(index_b[0,0,0])]+rand_x
994
+ Pos[:,1,1] = Y[int(index_b[0,1,0])]+rand_y
995
+ Pos[:,2,1] = H_mat[int(index_b[0,1,0]),int(index_b[0,0,0])]
996
+ Pos_b[:,:,0] = Pos[:,:,0]
997
+
998
+ else:
999
+ self.count_initial,self.Human,n_loaded,Pos_b,self.time_b,U_b,self.Z_param = Loading(self.Path_loading,Pos_b,self.time_b,U_b)
1000
+
1001
+ Pos[:,0,0] = Pos_b[:,0,n_loaded]
1002
+ Pos[:,1,0] = Pos_b[:,1,n_loaded]
1003
+ Pos[:,2,0] = Pos_b[:,2,n_loaded]
1004
+ Pos[:,0,1] = Pos_b[:,0,n_loaded]
1005
+ Pos[:,1,1] = Pos_b[:,1,n_loaded]
1006
+ Pos[:,2,1] = Pos_b[:,2,n_loaded]
1007
+
1008
+ U[:,0,0] = U_b[:,0,n_loaded]
1009
+ U[:,1,0] = U_b[:,1,n_loaded]
1010
+ U[:,2,0] = U_b[:,2,n_loaded]
1011
+ U[:,0,1] = U_b[:,0,n_loaded]
1012
+ U[:,1,1] = U_b[:,1,n_loaded]
1013
+ U[:,2,1] = U_b[:,2,n_loaded]
1014
+
1015
+ self.Pos = Pos
1016
+ self.Pos_b = Pos_b
1017
+
1018
+ self.U = U
1019
+ self.U_b = U_b
1020
+
1021
+ self.time = self.t_initial*np.ones((n_b))
1022
+ self.resurface = np.zeros((n_b,2))
1023
+ self.sinking = np.zeros((n_b,2))
1024
+ self.count = self.count_initial
1025
+ self.sp = 1
1026
+
1027
+ def start(self):
1028
+
1029
+ """
1030
+ Main of the class, runs the code with a parallelised code (n_parallel>1) or without
1031
+
1032
+ """
1033
+
1034
+ start = timeit.default_timer()
1035
+
1036
+ self.Initialisation_arrays()
1037
+
1038
+ if self.Profile_this ==1:
1039
+ profiler = cProfile.Profile()
1040
+ profiler.enable()
1041
+
1042
+ # Conversion of dataframe to numpy array for parallel processing and memory efficiency
1043
+ Human_np = self.Human.to_numpy()
1044
+ Z_param_np = self.Z_param.to_numpy()
1045
+
1046
+ # Multiprocess run
1047
+ if self.n_parallel>1:
1048
+ # Set up progress queue for inter-process communication
1049
+ with multiprocessing.Manager() as manager:
1050
+ progress_queue = manager.Queue()
1051
+ stop_event = threading.Event() # Indicateur pour arrêter le thread
1052
+
1053
+ # Start wxPython application and create the frame
1054
+ app = wx.App(False)
1055
+ frame = wx.Frame(None)
1056
+ if self.image==1:
1057
+ frame = ProgressImage(self.n_parallel,self.time_goal, None)
1058
+ else:
1059
+ frame = ProgressBar(None,n_processes=self.n_parallel,total=self.time_goal)
1060
+ frame.Show()
1061
+
1062
+ tasks = Preparation_parallelisation(progress_queue,self.a_RK,self.BC_cells,self.count,self.count_Wolf,self.CFL,self.Delta,Human_np,self.i_initial,self.n_b,self.n_saved,self.n_parallel,self.n_t,self.NbX,self.NbY,self.Path_saving,self.Path_Wolf,self.Pos,self.Pos_b,self.resurface,self.sinking,self.time,self.time_b,self.time_goal,self.U,self.U_b,self.wanted_time,self.wanted_Wolf,Z_param_np)
1063
+
1064
+ # Création de la thread de suivi de la progression
1065
+ time_viewer = 1
1066
+ monitor_thread = threading.Thread(target=state_of_run, args=(progress_queue,frame, time_viewer))
1067
+ monitor_thread.daemon = True # Ensure it terminates when the program exits
1068
+ monitor_thread.start()
1069
+
1070
+ with multiprocessing.Pool(processes=self.n_parallel) as pool:
1071
+ result_async = pool.map_async(Parallel_loop, tasks)
1072
+ while not result_async.ready():
1073
+ wx.Yield() # This allows the GUI to update while waiting for the results
1074
+ # Wait for the result to be ready
1075
+ results = result_async.get()
1076
+
1077
+
1078
+ def on_close(event):
1079
+ frame.Close() # Ferme la fenêtre
1080
+ app.ExitMainLoop()
1081
+
1082
+ frame.Bind(wx.EVT_CLOSE, on_close)
1083
+ frame.Close()
1084
+ stop_event.set() # Stop the monitoring thread
1085
+
1086
+ self.Pos_b = np.concatenate([result[0] for result in results], axis=0)
1087
+ self.resurface = np.concatenate([result[1] for result in results], axis=0)
1088
+ self.sinking = np.concatenate([result[2] for result in results], axis=0)
1089
+ self.time_b = np.concatenate([result[3] for result in results], axis=0)
1090
+ self.U_b = np.concatenate([result[4] for result in results], axis=0)
1091
+
1092
+ # No use of multiprocessing
1093
+ else:
1094
+ self.Pos_b,self.resurface,self.sinking,self.time_b,self.U_b = Loop_management(-1,-1,self.a_RK,self.BC_cells,self.count,self.count_Wolf,self.CFL,self.Delta,Human_np,self.i_initial,self.n_b,self.n_saved,self.n_t,self.NbX,self.NbY,self.Path_saving,self.Path_Wolf,self.Pos,self.Pos_b,self.resurface,self.sinking,self.time,self.time_b,self.time_goal,self.U,self.U_b,self.wanted_time,self.wanted_Wolf,Z_param_np)
1095
+
1096
+
1097
+ stop = timeit.default_timer()
1098
+ execution_time = stop - start
1099
+
1100
+ n_b = self.n_b
1101
+ time_goal = self.time_goal
1102
+
1103
+ # Save of the results
1104
+ Path_save = os.path.join(self.Path_saving,'Results')
1105
+ os.makedirs(Path_save,exist_ok=True)
1106
+ np.savez(Path_save,Pos_b=self.Pos_b,U_b=self.U_b,Human=self.Human,Z_param=self.Z_param,wanted_time=self.wanted_time,time_b=self.time_b)
1107
+
1108
+ logging.info(f"Program executed in "+str(round(execution_time/60,1))+" min, for "+str(n_b)+" bodies and "+str(np.floor(time_goal/(60*60*24)))+" days "+str(int((time_goal/60/60-24*int(time_goal/60/60/24))))+" h "+str((np.floor((time_goal/60)%60)))+" min "+str(time_goal%60)+ " s")
1109
+ if self.Profile_this ==1:
1110
+ profiler.disable()
1111
+ stats = pstats.Stats(profiler).sort_stats('ncalls')
1112
+ stats.sort_stats('tottime')
1113
+ stats.print_stats()
1114
+
1115
+
1116
+ if self.saving == 1:
1117
+ np.savez(self.Path_saving,Pos_b=self.Pos_b,U_b=self.U_b,Human=self.Human,Z_param=self.Z_param,wanted_time=self.wanted_time,time_b=self.time_b)
1118
+
1119
+ def Parallel_loop(args):
1120
+ """
1121
+ Necessary for the parallelisation as we have to give a list of arguments to the function instead of all the args separately
1122
+ """
1123
+
1124
+ result = Loop_management(*args)
1125
+
1126
+ return result
1127
+
1128
+
1129
+ class Drowning_victim_Viewer(Element_To_Draw):
1130
+
1131
+ def __init__(self, idx = '', plotted = True, mapviewer = None, need_for_wx = False,filedir = None):
1132
+ super().__init__(idx, plotted, mapviewer, need_for_wx)
1133
+
1134
+ self.filename = None
1135
+ self.filedir = filedir
1136
+ self.file_drowning = None
1137
+ self.n_peaks = 2
1138
+ self.init_plot()
1139
+
1140
+ self.newdrowning = Drowning_victim(Path_dir=filedir)
1141
+
1142
+ self.from_dictionnary_to_wp()
1143
+
1144
+ def selection_drowning_point(self,event):
1145
+ """
1146
+ Function to select the drowning point in the viewer
1147
+ """
1148
+ from ..PyDraw import draw_type
1149
+ liste = self.mapviewer.get_list_keys(draw_type.RES2D,checked_state=None)
1150
+
1151
+ if not liste:
1152
+ dialog = wx.DirDialog(None, "Folder containing the wanted simulation WOLF 2D GPU", style=wx.DD_DEFAULT_STYLE)
1153
+
1154
+ # Afficher la boîte de dialogue et attendre l'interaction de l'utilisateur
1155
+ if dialog.ShowModal() == wx.ID_OK:
1156
+ # Récupérer le chemin sélectionné
1157
+ self.newdrowning.Path_Wolf = dialog.GetPath()
1158
+ self.wp.change_param('Paths','Results of Wolf GPU simulation path',self.newdrowning.Path_Wolf)
1159
+
1160
+ # Ajouter l'objet avec le chemin sélectionné
1161
+ self.mapviewer.add_object(which='res2d_gpu', ToCheck=True, filename=join(self.newdrowning.Path_Wolf, 'Results'))
1162
+ self.mapviewer.menu_wolf2d()
1163
+ self.mapviewer.menu_2dgpu()
1164
+ self.mapviewer.Autoscale()
1165
+ dialog.Destroy()
1166
+ else:
1167
+ # L'utilisateur a annulé la boîte de dialogue
1168
+ logging.info(_('No folder selected for the WOLF 2D GPU simulation.'))
1169
+ # Détruire la boîte de dialogue pour libérer les ressources
1170
+ dialog.Destroy()
1171
+ return
1172
+
1173
+ else:
1174
+ myitem = self.mapviewer.single_choice_key(draw_type.RES2D,checked_state=None)
1175
+ # nameitem = self.mapviewer.treelist.GetItemText(myitem).lower()
1176
+ # curobj = self.mapviewer.getobj_from_id(nameitem)
1177
+ # myobj = self.mapviewer.treelist.GetItemData(myitem)
1178
+ # self.mapviewer.active_res2d = myobj
1179
+
1180
+ self.wp.change_param('Grid','Origin X',self.mapviewer.active_res2d.origx)
1181
+ self.wp.change_param('Grid','Origin Y',self.mapviewer.active_res2d.origy)
1182
+
1183
+ with open(join(self.newdrowning.Path_Wolf,'parameters.json'), 'r', encoding='utf-8') as file:
1184
+ data = json.load(file)
1185
+ dx = data['parameters']['dx']
1186
+ dy = data['parameters']['dy']
1187
+ nbx = data['parameters']['nbx']
1188
+ nby = data['parameters']['nby']
1189
+ self.wp.change_param('Grid','Delta X',dx)
1190
+ self.wp.change_param('Grid','Delta Y',dy)
1191
+ self.wp.change_param('Grid','Nb X',nbx)
1192
+ self.wp.change_param('Grid','Nb Y',nby)
1193
+
1194
+ self.button_selection_progress = wx.Button(self.wp,label='Drowning point')
1195
+ self.button_selection_progress.Bind(wx.EVT_BUTTON,self.selection_progress)
1196
+ self.button_selection_progress.SetToolTip('Check if you have exactly one drowning point selected')
1197
+ self.wp.sizerbut.Insert(4,self.button_selection_progress,1,wx.EXPAND)
1198
+ self.wp.sizer.Fit(self.wp)
1199
+
1200
+ self.wp.SetSize(0,0,self.w,800)
1201
+ self.wp.Show(True)
1202
+
1203
+ def selection_progress(self,event):
1204
+ """
1205
+ Function to select the drowning point in the viewer
1206
+ """
1207
+
1208
+ if self.mapviewer.active_res2d.SelectionData.nb==0:
1209
+ wx.MessageBox(_("No point selected, please select a drowning point"), "Error", wx.OK | wx.ICON_ERROR)
1210
+ return
1211
+
1212
+ elif self.mapviewer.active_res2d.SelectionData.nb==1:
1213
+ value_got = self.mapviewer.active_res2d.myblocks[getkeyblock(0)].SelectionData.myselection
1214
+ x,y = value_got[0]
1215
+ self.newdrowning.ind_pos_0_x, self.newdrowning.ind_pos_0_y = self.mapviewer.active_res2d.get_ij_from_xy(x=x,y=y)
1216
+ self.update_drowning_pos()
1217
+ self.mapviewer.active_res2d.SelectionData.reset_all()
1218
+ self.button_selection_progress.SetBackgroundColour(wx.Colour(50, 190, 50))
1219
+ self.file_drowning = 1
1220
+ return
1221
+
1222
+ elif self.mapviewer.active_res2d.SelectionData.nb>1:
1223
+ wx.MessageBox(_("More than one point selected, please select only one drowning point"), "Error", wx.OK | wx.ICON_ERROR)
1224
+ return
1225
+
1226
+ def update_drowning_pos(self):
1227
+ """
1228
+ Update the values of "X-cell" and "Y-cell" in the parameters.param file.
1229
+ """
1230
+
1231
+ self.wp.change_param("Initial_drowning_point",'X-cell', int(self.newdrowning.ind_pos_0_x))
1232
+ self.wp.change_param("Initial_drowning_point",'Y-cell', int(self.newdrowning.ind_pos_0_y))
1233
+
1234
+ def create_exe_file(self,event):
1235
+ """
1236
+ Start the drowning in a separate process
1237
+ """
1238
+ import subprocess
1239
+ try:
1240
+ if self.filedir is None:
1241
+ wx.MessageBox(_("No directory selected for the simulation. \nPlease, save your drowning in a directory."), "Error", wx.OK | wx.ICON_ERROR)
1242
+ return
1243
+
1244
+ self.filedir = Path(self.filedir)
1245
+ # Créer le répertoire self.Path_saving s'il n'existe pas
1246
+ self.filedir.mkdir(parents=True, exist_ok=True)
1247
+ # Définir le chemin du fichier exe_drowning.py
1248
+ self.exe_file = self.filedir / "exe_drowning.py"
1249
+ project_root = Path(__file__).resolve().parents[2]
1250
+ # Contenu du fichier exe_drowning.py
1251
+ script_content = f"""
1252
+ import sys
1253
+ import os
1254
+ from pathlib import Path
1255
+
1256
+ directory = r"{project_root}"
1257
+ os.chdir(directory)
1258
+
1259
+ _root_dir = os.path.dirname(os.path.realpath(__file__))
1260
+ sys.path.insert(0,os.path.join(directory,'./wolfhece'))
1261
+
1262
+ try:
1263
+ from .Drowning_victims.Class import Drowning_victim
1264
+ except:
1265
+ from Drowning_victims.Class import Drowning_victim
1266
+
1267
+ if __name__ == "__main__":
1268
+ # Définir le chemin de sauvegarde
1269
+ Path_saving = r"{self.filedir}"
1270
+
1271
+ # Exécuter la simulation
1272
+ newdrowning = Drowning_victim(Path_dir=Path_saving)
1273
+ newdrowning.start()
1274
+ """
1275
+
1276
+ # Créer et écrire le fichier exe_drowning.py
1277
+ with open(self.exe_file, "w", encoding="utf-8") as file:
1278
+ file.write(script_content)
1279
+ logging.info(f"Drowning simulation file created at: {self.exe_file}")
1280
+ except subprocess.CalledProcessError as e:
1281
+ logging.error(f"Error while creating the drowning simulation file: {e}")
1282
+
1283
+ def run_code(self,event):
1284
+ if self.file_drowning is not None:
1285
+ import subprocess
1286
+ try:
1287
+ self.newdrowning.start()
1288
+ logging.info(_("Drowning simulation done."))
1289
+ except subprocess.CalledProcessError as e:
1290
+ logging.error(f"Error while running the drowning simulation file: {e}")
1291
+ # process = multiprocessing.Process(target=self.__main__())
1292
+ # process.start() # Démarre code2 dans un processus distinct
1293
+ else:
1294
+ logging.error(('No drowning point selected, select one before starting the simulation'))
1295
+
1296
+ def show_properties(self):
1297
+
1298
+ self.w=800
1299
+
1300
+ self.wp._set_gui(title='Parameters for the drowning simulation', toShow=True, w=self.w)
1301
+ self.wp.hide_selected_buttons([Buttons.Reload,Buttons.Save])
1302
+
1303
+ select_button = wx.Button(self.wp,id=10,label="Wolf2D simulation")
1304
+ select_button.SetToolTip(_("Select reference Wolf2D simulation to choose your drowning point"))
1305
+ create_file_button = wx.Button(self.wp,id=11,label="Create exe file")
1306
+ create_file_button.SetToolTip(_("Create the executable file to run the drowning"))
1307
+ run_button = wx.Button(self.wp,id=12,label="Run")
1308
+ run_button.SetToolTip(_("Run the drowning simulation \nNot recommended here"))
1309
+
1310
+ select_button.Bind(wx.EVT_BUTTON, self.selection_drowning_point)
1311
+ create_file_button.Bind(wx.EVT_BUTTON, self.create_exe_file)
1312
+ run_button.Bind(wx.EVT_BUTTON, self.run_code)
1313
+ run_button.SetBackgroundColour(wx.Colour(240,160,160))
1314
+
1315
+ self.wp.sizerbut.Add(select_button,2,wx.EXPAND)
1316
+ self.wp.sizerbut.Add(create_file_button,2,wx.EXPAND)
1317
+ self.wp.sizerbut.Add(run_button,1,wx.EXPAND)
1318
+
1319
+
1320
+ self.wp.SetSizer(self.wp.sizer)
1321
+ # self.SetSize(w,h)
1322
+ self.wp.SetAutoLayout(1)
1323
+ self.wp.sizer.Fit(self.wp)
1324
+
1325
+ self.wp.SetSize(0,0,self.w,800)
1326
+ self.wp.Show(True)
1327
+
1328
+ # self.wp.myparams = self.merge_dicts(self.wp.myparams,self.wp.myparams_default)
1329
+ self.from_wp_to_dictionnary()
1330
+ self.newdrowning.from_dictionnary_to_attributes()
1331
+
1332
+ def from_dictionnary_to_wp(self):
1333
+ """
1334
+ Return a Wolf_Param object that represents the parameters of the simulation,
1335
+ directly using the attributes of the class.
1336
+ """
1337
+ # Initialisation de l'objet Wolf_Param
1338
+ wp = Wolf_Param(
1339
+ parent=None,
1340
+ title="Drift of a drowning victim",
1341
+ to_read=False,
1342
+ withbuttons=True,
1343
+ toShow=False,
1344
+ init_GUI=False,
1345
+ force_even_if_same_default=True,
1346
+ filename=self.filename
1347
+ )
1348
+
1349
+ params_dict = self.newdrowning.param_dict
1350
+
1351
+ # Ajout des paramètres au Wolf_Param
1352
+ for current_section in params_dict.keys():
1353
+ for key in params_dict[current_section].keys():
1354
+
1355
+ value = params_dict[current_section][key]["value"]
1356
+ description = params_dict[current_section][key]["description"]
1357
+ name = params_dict[current_section][key]["explicit name"]
1358
+
1359
+ wp.add_param(
1360
+ groupname=current_section,
1361
+ name=name,
1362
+ value=value,
1363
+ type=params_dict[current_section][key]["type"],
1364
+ whichdict="All" if params_dict[current_section][key]["mandatory"] else "Default",
1365
+ jsonstr={"Values": params_dict[current_section][key]["choices"]} if params_dict[current_section][key]["choices"] else None,
1366
+ comment= description
1367
+ )
1368
+ self.wp = wp
1369
+
1370
+ self.newdrowning.time_goal = self.newdrowning.Days*24*60*60 + self.newdrowning.Hours*60*60 + self.newdrowning.Minutes*60 + self.newdrowning.Seconds #s
1371
+ wanted_time = np.array([self.newdrowning.t_initial])
1372
+ self.newdrowning.n_saved = 1
1373
+ for i in np.arange(10,self.newdrowning.time_goal+10,10):
1374
+ if i<60:
1375
+ wanted_time = np.append(wanted_time,i)
1376
+ self.newdrowning.n_saved += 1
1377
+ elif np.logical_and(i<10*60,(i%60==0)):
1378
+ wanted_time = np.append(wanted_time,i)
1379
+ self.newdrowning.n_saved += 1
1380
+ elif np.logical_and(i<30*60,(i%(5*60)==0)):
1381
+ wanted_time = np.append(wanted_time,i)
1382
+ self.newdrowning.n_saved += 1
1383
+ elif np.logical_and(i<60*60,(i%(10*60)==0)):
1384
+ wanted_time = np.append(wanted_time,i)
1385
+ self.newdrowning.n_saved += 1
1386
+ elif np.logical_and(i<=24*60*60,(i%(60*60)==0)):
1387
+ wanted_time = np.append(wanted_time,i)
1388
+ self.newdrowning.n_saved += 1
1389
+ elif np.logical_and(i<=2*24*60*60,(i%(2*60*60)==0)):
1390
+ wanted_time = np.append(wanted_time,i)
1391
+ self.newdrowning.n_saved += 1
1392
+ elif (i%(3*60*60)==0):
1393
+ wanted_time = np.append(wanted_time,i)
1394
+ self.newdrowning.n_saved += 1
1395
+
1396
+ self.newdrowning.wanted_time = np.append(wanted_time,0)
1397
+
1398
+ def from_wp_to_dictionnary(self):
1399
+ """
1400
+ Compare the parameters in self.wp with self.newdrowning.param_dict and update the values
1401
+ in self.newdrowning.param_dict when they differ.
1402
+ """
1403
+ # Charger le dictionnaire existant
1404
+ param_dict = self.newdrowning.param_dict
1405
+
1406
+ # Parcourir les sections et les clés de wp
1407
+ for current_section in self.wp.myparams.keys():
1408
+ if current_section in param_dict:
1409
+ for key, wp_param in self.wp.myparams[current_section].items():
1410
+ # Trouver la clé correspondante dans param_dict
1411
+ for param_key, param_data in param_dict[current_section].items():
1412
+ if param_data["explicit name"] == key:
1413
+ # Comparer les valeurs et mettre à jour si elles diffèrent
1414
+ if param_data["value"] != wp_param[key_Param.VALUE]:
1415
+ param_dict[current_section][param_key]["value"] = wp_param[key_Param.VALUE]
1416
+ break
1417
+
1418
+ # Sauvegarder le dictionnaire mis à jour dans self.newdrowning.param_dict
1419
+ self.newdrowning.param_dict = param_dict
1420
+
1421
+ def hide_properties(self):
1422
+ """
1423
+ Hide properties window
1424
+ """
1425
+ if self.wp is not None:
1426
+ self.wp.Destroy()
1427
+ self.wp = None
1428
+
1429
+ def save(self):
1430
+ '''
1431
+ Save the parameters in a text file
1432
+ '''
1433
+ if self.filename is None:
1434
+ self.saveas()
1435
+
1436
+ else:
1437
+ self.wp.Save(self.filename)
1438
+
1439
+ def saveas(self):
1440
+ '''
1441
+ Save the parameters in a text file
1442
+ '''
1443
+ fdlg = wx.DirDialog(None, "Where should the parameters be stored? File automatically named parameters", style=wx.FD_SAVE)
1444
+ ret = fdlg.ShowModal()
1445
+ if ret == wx.ID_OK:
1446
+ self.filedir = fdlg.GetPath()
1447
+ self.filename = self.filedir + "/parameters.param"
1448
+ self.Path_saving = self.filedir
1449
+ self.wp.change_param("Paths","Save path",self.filedir)
1450
+ self.save()
1451
+
1452
+ fdlg.Destroy()
1453
+
1454
+ def load_results(self):
1455
+ """
1456
+ Load the results from the 'Results.npz' file and assign the arrays as attributes of the class.
1457
+ """
1458
+
1459
+ # Construire le chemin du fichier Results.npz
1460
+ results_file = join(self.filedir, 'Results.npz')
1461
+
1462
+ # Vérifier si le fichier existe
1463
+ if not os.path.exists(results_file):
1464
+ logging.error(f"Le fichier {results_file} est introuvable.")
1465
+ return
1466
+
1467
+ # Charger le fichier npz
1468
+ with np.load(results_file, allow_pickle=True) as data:
1469
+ # Assigner les tableaux comme attributs de la classe
1470
+ self.Human = data['Human']
1471
+ self.Pos_b = data['Pos_b']
1472
+ self.U_b = data['U_b']
1473
+ self.Z_param = data['Z_param']
1474
+ self.wanted_time = data['wanted_time']
1475
+ self.time_b = data['time_b']
1476
+
1477
+ self.Pos_b[:,0,:] += self.newdrowning.origx
1478
+ self.Pos_b[:,1,:] += self.newdrowning.origy
1479
+
1480
+ def init_plot(self):
1481
+
1482
+ self.bottom_cells = None
1483
+ self.bottom_kde = None
1484
+ self.vertex_bottom_run = None
1485
+
1486
+ self.surface_cells = None
1487
+ self.surface_kde = None
1488
+ self.vertex_surface_run = None
1489
+
1490
+ self.plot_runs = None
1491
+ self.plot_cells = None
1492
+ self.plot_KDE = None
1493
+
1494
+ def read_oneresult(self,idx):
1495
+ """
1496
+ Read one result of the simulation and update the parameters in the GUI
1497
+ """
1498
+
1499
+ count=0
1500
+
1501
+ self.time_id = idx
1502
+
1503
+ if self.plot_runs is not None:
1504
+ self.prepare_plot_runs_positions()
1505
+ count +=1
1506
+ if self.plot_cells is not None:
1507
+ self.prepare_plot_cells_positions()
1508
+ count +=1
1509
+ if self.plot_KDE is not None:
1510
+ self.prepare_plot_kde()
1511
+ count +=1
1512
+
1513
+ if count==0:
1514
+ self.prepare_plot_runs_positions()
1515
+
1516
+ return
1517
+
1518
+ def read_last_result(self):
1519
+ """
1520
+ Read the last results of the simulation and update the parameters in the GUI
1521
+ """
1522
+
1523
+ self.time_id = -1
1524
+
1525
+ self.read_oneresult(idx=-1)
1526
+
1527
+ return
1528
+
1529
+ def find_minmax(self, update=False):
1530
+ """
1531
+ Generic function to find min and max spatial extent in data
1532
+
1533
+ example : a WolfMapViewer instance needs spatial extent to zoom or test if
1534
+ element must be plotted
1535
+ """
1536
+
1537
+ self.xmin=999999. # spatial extension - lower left corner X
1538
+ self.ymin=999999. # spatial extension - lower left corner Y
1539
+ self.xmax=-999999. # spatial extension - upper right corner X
1540
+ self.ymax=-999999. # spatial extension - upper right corner Y
1541
+
1542
+ if self.bottom_kde is not None:
1543
+ [xmin, xmax], [ymin, ymax] = self.bottom_kde.get_bounds()
1544
+ self.xmin = min(self.xmin, xmin)
1545
+ self.xmax = max(self.xmax, xmax)
1546
+ self.ymin = min(self.ymin, ymin)
1547
+ self.ymax = max(self.ymax, ymax)
1548
+ if self.surface_kde is not None:
1549
+ [xmin, xmax], [ymin, ymax] = self.surface_kde.get_bounds()
1550
+ self.xmin = min(self.xmin, xmin)
1551
+ self.xmax = max(self.xmax, xmax)
1552
+ self.ymin = min(self.ymin, ymin)
1553
+ self.ymax = max(self.ymax, ymax)
1554
+
1555
+ if self.bottom_cells is not None:
1556
+ [xmin, xmax], [ymin, ymax] = self.bottom_cells.get_bounds()
1557
+ self.xmin = min(self.xmin, xmin)
1558
+ self.xmax = max(self.xmax, xmax)
1559
+ self.ymin = min(self.ymin, ymin)
1560
+ self.ymax = max(self.ymax, ymax)
1561
+ if self.surface_cells is not None:
1562
+ [xmin, xmax], [ymin, ymax] = self.surface_cells.get_bounds()
1563
+ self.xmin = min(self.xmin, xmin)
1564
+ self.xmax = max(self.xmax, xmax)
1565
+ self.ymin = min(self.ymin, ymin)
1566
+ self.ymax = max(self.ymax, ymax)
1567
+
1568
+ if self.vertex_bottom_run is not None:
1569
+ self.vertex_bottom_run.find_minmax(update)
1570
+ self.xmin = min(self.xmin, self.vertex_bottom_run.xmin)
1571
+ self.xmax = max(self.xmax, self.vertex_bottom_run.xmax)
1572
+ self.ymin = min(self.ymin, self.vertex_bottom_run.ymin)
1573
+ self.ymax = max(self.ymax, self.vertex_bottom_run.ymax)
1574
+ if self.vertex_surface_run is not None:
1575
+ self.vertex_surface_run.find_minmax(update)
1576
+ self.xmin = min(self.xmin, self.vertex_surface_run.xmin)
1577
+ self.xmax = max(self.xmax, self.vertex_surface_run.xmax)
1578
+ self.ymin = min(self.ymin, self.vertex_surface_run.ymin)
1579
+ self.ymax = max(self.ymax, self.vertex_surface_run.ymax)
1580
+
1581
+ pass
1582
+
1583
+ def plot(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None, size=None):
1584
+ """
1585
+ Plot data in OpenGL context
1586
+ """
1587
+ if self.plotted:
1588
+
1589
+ self.plotting = True
1590
+
1591
+ if self.bottom_kde is not None:
1592
+ self.bottom_kde.check_plot()
1593
+ self.bottom_kde.plot(sx, sy, xmin, ymin, xmax, ymax, size)
1594
+ if self.surface_kde is not None:
1595
+ self.surface_kde.check_plot()
1596
+ self.surface_kde.plot(sx, sy, xmin, ymin, xmax, ymax, size)
1597
+
1598
+ if self.bottom_cells is not None:
1599
+ self.bottom_cells.check_plot()
1600
+ self.bottom_cells.plot(sx, sy, xmin, ymin, xmax, ymax, size)
1601
+ if self.surface_cells is not None:
1602
+ self.surface_cells.check_plot()
1603
+ self.surface_cells.plot(sx, sy, xmin, ymin, xmax, ymax, size)
1604
+
1605
+ if self.vertex_bottom_run is not None:
1606
+ self.vertex_bottom_run.plot()
1607
+ if self.vertex_surface_run is not None:
1608
+ self.vertex_surface_run.plot()
1609
+
1610
+ self.plotting = False
1611
+
1612
+ def sort_positions_bodies(self):
1613
+
1614
+ time_id = self.time_id
1615
+
1616
+ ind_surface = np.where(self.Pos_b[:,2,time_id] > 0.2)[0]
1617
+ ind_bottom = np.where(self.Pos_b[:,2,time_id] <= 0.2)[0]
1618
+ if len(ind_surface) == 0:
1619
+ ind_surface = [0]
1620
+ if len(ind_bottom) == 0:
1621
+ ind_bottom = [0]
1622
+
1623
+ return ind_bottom,ind_surface,time_id
1624
+
1625
+ def prepare_plot_runs_positions(self):
1626
+ """
1627
+ Plot the runs position on a georeferenced map with bodies in blue being at the bottom and red being at the surface.
1628
+ """
1629
+
1630
+ self.plot_runs = 1
1631
+
1632
+ ind_bottom,ind_surface,time_id = self.sort_positions_bodies()
1633
+
1634
+ self.vertex_bottom_run = cloud_vertices(mapviewer=self.mapviewer)
1635
+ self.vertex_surface_run = cloud_vertices(mapviewer=self.mapviewer)
1636
+
1637
+ self.vertex_bottom_run.init_from_nparray(self.Pos_b[ind_bottom,:,time_id])
1638
+ self.vertex_surface_run.init_from_nparray(self.Pos_b[ind_surface,:,time_id])
1639
+
1640
+ self.vertex_bottom_run.myprop.color = [40,50,250]
1641
+ self.vertex_surface_run.myprop.color = [250,100,80]
1642
+
1643
+ self.vertex_bottom_run.myprop.alpha = 0.5
1644
+ self.vertex_surface_run.myprop.alpha = 0.5
1645
+
1646
+ self.find_minmax(True)
1647
+
1648
+ return
1649
+
1650
+ def reset_plot_runs_positions(self):
1651
+ self.vertex_bottom_run = None
1652
+ self.vertex_surface_run = None
1653
+ self.plot_runs = None
1654
+
1655
+ def kde_on_grid(self,points, bandwidth, xmin, xmax, ymin, ymax, grid_size):
1656
+ """
1657
+ Function used to calculate the kde on a point cloud. Use a large grid size to identify peaks and a small one to refine
1658
+ """
1659
+
1660
+
1661
+ x_grid = np.linspace(xmin, xmax, grid_size[0])
1662
+ y_grid = np.linspace(ymin, ymax, grid_size[1])
1663
+ X, Y = np.meshgrid(x_grid, y_grid)
1664
+ sample_grid = np.vstack([X.ravel(), Y.ravel()]).T
1665
+
1666
+ kde = KernelDensity(bandwidth=bandwidth)
1667
+ kde.fit(points)
1668
+ Z = np.exp(kde.score_samples(sample_grid)).reshape(grid_size[0], grid_size[1])
1669
+
1670
+ return Z, x_grid, y_grid
1671
+
1672
+ def detect_peaks(self,x,y,radius,num_peaks=2):
1673
+ """
1674
+ Détecte les pics locaux dans une matrice 2D sans skimage.
1675
+
1676
+ param: x X coordinate of the points cloud
1677
+ param: y Y coordinate of the points cloud
1678
+ param: radius size of the grid to detect peaks
1679
+ param: n_peaks number of peaks to detect
1680
+
1681
+ """
1682
+
1683
+ x += -self.newdrowning.origx
1684
+ y += -self.newdrowning.origy
1685
+
1686
+ dx = self.newdrowning.dx
1687
+ dy = self.newdrowning.dy
1688
+
1689
+ ij = np.array([np.int32(x/radius), np.int32(y/radius)]).T
1690
+ unique_positions, counts = np.unique(ij,axis=0, return_counts=True)
1691
+
1692
+ ind_peaks = []
1693
+ selected_mask = np.zeros(len(counts), dtype=bool) # pour marquer les indices déjà exclus
1694
+
1695
+ while True:
1696
+ # On masque les indices déjà exclus
1697
+ valid_indices = np.where(~selected_mask)[0]
1698
+ if len(valid_indices) == 0:
1699
+ break
1700
+
1701
+ # Trouver le max parmi les valides
1702
+ idx_max = valid_indices[np.argmax(counts[valid_indices])]
1703
+ ind_peaks.append(idx_max)
1704
+
1705
+ # Marquer les indices trop proches pour les exclure ensuite
1706
+ pos_max = unique_positions[idx_max]
1707
+ i_diff = np.abs(unique_positions[:, 0] - pos_max[0])
1708
+ j_diff = np.abs(unique_positions[:, 1] - pos_max[1])
1709
+ too_close = (i_diff <= 0) & (j_diff <= 0)
1710
+
1711
+ selected_mask |= too_close # mettre à jour le masque d'exclusion
1712
+
1713
+ if len(ind_peaks) >= num_peaks:
1714
+ break
1715
+
1716
+ x_peaks = (unique_positions[ind_peaks,0]*radius) + radius/2 + self.newdrowning.origx
1717
+ y_peaks = (unique_positions[ind_peaks,1]*radius) + radius/2 + self.newdrowning.origy
1718
+
1719
+ selected_peaks = np.zeros((len(ind_peaks),2))
1720
+ selected_peaks[:,0] = x_peaks
1721
+ selected_peaks[:,1] = y_peaks
1722
+
1723
+ return selected_peaks
1724
+
1725
+ def kde_refined_based_coarse(self, points, wolfarray, bandwidth=50,
1726
+ coarse_grid_size=50, fine_grid_size=5,
1727
+ window_size=200, radius=150, n_peaks=3):
1728
+ """
1729
+ Optimisation à 2 étages : détection des pics sur grille grossière puis raffinement local.
1730
+
1731
+ Returns:
1732
+ - refined_peaks : coordonnées des pics raffinés
1733
+ - clusters : liste de points pour chaque cluster
1734
+ - coords : coordonnées (x, y) de chaque maille dans les zones raffinées
1735
+ - values : valeur KDE associée à chaque maille
1736
+ """
1737
+ array = wolfarray.array[:,:]
1738
+
1739
+ x_min, y_min = points.min(axis=0)
1740
+ x_max, y_max = points.max(axis=0)
1741
+
1742
+ # 1. Déduction de dx, dy depuis array
1743
+ ny, nx = array.shape
1744
+ dx = (x_max - x_min) / nx
1745
+ dy = (y_max - y_min) / ny
1746
+
1747
+ # 3. Préparation des résultats
1748
+ all_indices = []
1749
+ all_values = []
1750
+
1751
+ ij = wolfarray.get_ij_from_xy_array(points)
1752
+ unique_positions, counts = np.unique(ij,axis=0, return_counts=True)
1753
+ delta = radius/self.newdrowning.dx
1754
+
1755
+ ind_peaks = []
1756
+ selected_mask = np.zeros(len(counts), dtype=bool) # pour marquer les indices déjà exclus
1757
+
1758
+ while True:
1759
+ # On masque les indices déjà exclus
1760
+ valid_indices = np.where(~selected_mask)[0]
1761
+ if len(valid_indices) == 0:
1762
+ break
1763
+
1764
+ # Trouver le max parmi les valides
1765
+ idx_max = valid_indices[np.argmax(counts[valid_indices])]
1766
+ ind_peaks.append(idx_max)
1767
+
1768
+ # Marquer les indices trop proches pour les exclure ensuite
1769
+ pos_max = unique_positions[idx_max]
1770
+ i_diff = np.abs(unique_positions[:, 0] - pos_max[0])
1771
+ j_diff = np.abs(unique_positions[:, 1] - pos_max[1])
1772
+ too_close = (i_diff <= delta) & (j_diff <= delta)
1773
+
1774
+ selected_mask |= too_close # mettre à jour le masque d'exclusion
1775
+
1776
+ if len(ind_peaks) >= n_peaks:
1777
+ break
1778
+
1779
+ for ind_peak in ind_peaks:
1780
+
1781
+ x0 = int((unique_positions[ind_peak,0]-delta)*dx+x_min)
1782
+ x1 = int((unique_positions[ind_peak,0]+delta)*dx+x_min)
1783
+ y0 = int((unique_positions[ind_peak,1]-delta)*dy+y_min)
1784
+ y1 = int((unique_positions[ind_peak,1]+delta)*dy+y_min)
1785
+
1786
+ grid_nx = max(2, int((x1 - x0) / dx))
1787
+ grid_ny = max(2, int((y1 - y0) / dy))
1788
+
1789
+ Z_fine, xg_fine, yg_fine = self.kde_on_grid(
1790
+ points, bandwidth, x0, x1, y0, y1, grid_size=(grid_nx, grid_ny)
1791
+ )
1792
+
1793
+ # Z_fine, xg_fine, yg_fine = self.kde_on_grid(
1794
+ # local_points, bandwidth, x0, x1, y0, y1, grid_size=grid
1795
+ # )
1796
+
1797
+ # Coordonnées physiques -> indices dans `array`
1798
+ Y, X = np.meshgrid(yg_fine, xg_fine, indexing='ij')
1799
+ x_flat = X.ravel()
1800
+ y_flat = Y.ravel()
1801
+ i = np.floor((y_flat - y_min) / dy).astype(int)
1802
+ j = np.floor((x_flat - x_min) / dx).astype(int)
1803
+
1804
+ # Filtrage : indices valides dans array
1805
+ valid = (i >= 0) & (i < ny) & (j >= 0) & (j < nx)
1806
+ indices = np.stack((i[valid], j[valid]), axis=1) # shape (n, 2)
1807
+ values = Z_fine.ravel()[valid]
1808
+
1809
+ all_indices.append(indices)
1810
+ all_values.append(values)
1811
+
1812
+ # Empilement final
1813
+ coords_array = np.vstack(all_indices) if all_indices else np.empty((0, 2), dtype=int)
1814
+ values_array = np.concatenate(all_values) if all_values else np.array([])
1815
+
1816
+ return coords_array,values_array
1817
+
1818
+ def prepare_plot_kde(self):
1819
+ """
1820
+ Plot the kernel density estimation of positions on a georeferenced map with bodies in blue being at the bottom and red being at the surface.
1821
+ """
1822
+
1823
+ self.plot_KDE = 1
1824
+
1825
+ self.n_peaks = 2
1826
+
1827
+ ind_bottom,ind_surface,time_id = self.sort_positions_bodies()
1828
+
1829
+ head = header_wolf()
1830
+
1831
+ head.set_origin(self.newdrowning.origx, self.newdrowning.origy)
1832
+ head.set_resolution(self.newdrowning.dx, self.newdrowning.dy)
1833
+ head.nbx, head.nby = self.newdrowning.nbx, self.newdrowning.nby
1834
+ head.set_translation(0., 0.)
1835
+
1836
+ self.bottom_kde = WolfArray(mapviewer=self.mapviewer, srcheader= head, nullvalue= 0.)
1837
+ self.surface_kde = WolfArray(mapviewer=self.mapviewer, srcheader= head, nullvalue= 0.)
1838
+
1839
+ for locarray, locind in zip([self.bottom_kde, self.surface_kde],
1840
+ [ind_bottom, ind_surface]):
1841
+
1842
+ xy = self.Pos_b[locind,:2,time_id]
1843
+ coords,values = self.kde_refined_based_coarse(xy,locarray,bandwidth=50,coarse_grid_size=200,fine_grid_size=5,window_size=50,radius=25,n_peaks=self.n_peaks)
1844
+
1845
+ locarray.array[:,:] = 0.
1846
+ locarray.array[coords[:,1],coords[:,0]] = values
1847
+ locarray.mask_data(0.)
1848
+
1849
+ self.bottom_kde.mypal.defaultblue_minmax(self.bottom_kde.array)
1850
+ self.surface_kde.mypal.defaultred_minmax(self.surface_kde.array)
1851
+
1852
+ self.bottom_kde.reset_plot()
1853
+ self.surface_kde.reset_plot()
1854
+
1855
+ return
1856
+
1857
+ def reset_plot_kde(self):
1858
+ self.bottom_kde = None
1859
+ self.surface_kde = None
1860
+ self.plot_KDE = None
1861
+
1862
+ def prepare_plot_cells_positions(self):
1863
+ """
1864
+ Plot the cells of the WOLF simulation, with a colorbar associated to the number of elements in the cell
1865
+ """
1866
+ self.plot_cells = 1
1867
+
1868
+ ind_bottom,ind_surface,time_id = self.sort_positions_bodies()
1869
+
1870
+ head = header_wolf()
1871
+
1872
+ head.set_origin(self.newdrowning.origx, self.newdrowning.origy)
1873
+ head.set_resolution(self.newdrowning.dx, self.newdrowning.dy)
1874
+ head.nbx, head.nby = self.newdrowning.nbx, self.newdrowning.nby
1875
+ head.set_translation(0., 0.)
1876
+
1877
+ self.bottom_cells = WolfArray(mapviewer=self.mapviewer, srcheader= head, nullvalue= 0.)
1878
+ self.surface_cells = WolfArray(mapviewer=self.mapviewer, srcheader= head, nullvalue= 0.)
1879
+
1880
+ for locarray, locind in zip([self.bottom_cells, self.surface_cells],
1881
+ [ind_bottom, ind_surface]):
1882
+
1883
+ # i_bottom,j_bottom = self.bottom_cells.get_ij_from_xy_array(self.Pos_b[ind_bottom,:2,time_id])
1884
+ ij = locarray.get_ij_from_xy_array(self.Pos_b[locind,:2,time_id])
1885
+
1886
+ unique_positions, counts = np.unique(ij,axis=0, return_counts=True)
1887
+
1888
+ locarray.array[:,:] = 0.
1889
+ locarray.array[unique_positions[:,0],unique_positions[:,1]] = counts/self.newdrowning.n_b*100
1890
+ locarray.mask_data(0.)
1891
+
1892
+ array = self.bottom_cells.array
1893
+ self.bottom_cells.mypal.nb = 2
1894
+ self.bottom_cells.mypal.values = np.asarray([np.min(array), np.max(array)], dtype=np.float64)
1895
+ self.bottom_cells.mypal.colors = np.asarray([[175, 200, 255, 255], [0, 0, 255, 255]], dtype=np.int32)
1896
+ self.bottom_cells.mypal.colorsflt = np.asarray([[0., 0., 0., 1.], [1., 1., 1., 1.]], dtype=np.float64)
1897
+ self.bottom_cells.mypal.fill_segmentdata()
1898
+
1899
+ array = self.surface_cells.array
1900
+ self.surface_cells.mypal.nb = 2
1901
+ self.surface_cells.mypal.values = np.asarray([np.min(array), np.max(array)], dtype=np.float64)
1902
+ self.surface_cells.mypal.colors = np.asarray([[255, 200, 175, 255], [0, 0, 255, 255]], dtype=np.int32)
1903
+ self.surface_cells.mypal.colorsflt = np.asarray([[0., 0.2, 0.6, 1.], [1., 1., 1., 1.]], dtype=np.float64)
1904
+ self.surface_cells.mypal.fill_segmentdata()
1905
+
1906
+ self.bottom_cells.reset_plot()
1907
+ self.surface_cells.reset_plot()
1908
+
1909
+ return
1910
+
1911
+ def reset_plot_cells_positions(self):
1912
+ self.bottom_cells = None
1913
+ self.surface_cells = None
1914
+ self.plot_cells = None
1915
+
1916
+ def zoom_on_hotspots(self,memory_view):
1917
+ """
1918
+ Zoom on the hotspots of the KDE
1919
+ """
1920
+
1921
+ delta = 150
1922
+
1923
+ ind_bottom,ind_surface,time_id = self.sort_positions_bodies()
1924
+
1925
+ xy_peaks_bottom = self.detect_peaks(self.Pos_b[ind_bottom,0,time_id], self.Pos_b[ind_bottom,1,time_id], radius=100, num_peaks=self.n_peaks)
1926
+ xy_peaks_surface = self.detect_peaks(self.Pos_b[ind_surface,0,time_id], self.Pos_b[ind_surface,1,time_id], radius=100, num_peaks=self.n_peaks)
1927
+ xy_peaks = np.concatenate((xy_peaks_bottom, xy_peaks_surface),axis=0)
1928
+
1929
+ names = ['Highest peak at the bottom','Second peak at the bottom','Highest peak at the surface','Second peak at the surface']
1930
+
1931
+ for locxy,locname in zip(xy_peaks,names):
1932
+ xmin = locxy[0] - delta
1933
+ xmax = locxy[0] + delta
1934
+ ymin = locxy[1] - delta
1935
+ ymax = locxy[1] + delta
1936
+
1937
+ memory_view.add_view(locname, self.mapviewer.canvaswidth, self.mapviewer.canvasheight, xmin, xmax, ymin, ymax)
1938
+
1939
+ return
1940
+
1941
+ def get_bodies_characteristics(self):
1942
+ """
1943
+ Plots a table of the Dataframe panda "Human" with the characteristics of each run
1944
+ """
1945
+
1946
+ if not isinstance(self.Human, pd.DataFrame):
1947
+ Human = pd.DataFrame(self.Human, columns=COLUMNS_HUMAN)
1948
+ else:
1949
+ Human = self.Human
1950
+
1951
+ self.grid = PandasGrid(parent=self.mapviewer, id = self.idx, df=Human)
1952
+ self.grid.ShowModal()
1953
+ self.grid.Destroy()
1954
+
1955
+ return
1956
+
1957
+ def get_vertical_position_proportion(self):
1958
+ """
1959
+ Gives the proportion of bodies at the surface and at the bottom of the water
1960
+ """
1961
+
1962
+ def update_pie(time_idx):
1963
+ """Met à jour uniquement la KDE en fonction de l'index temporel sélectionné."""
1964
+ time_idx = int(time_idx) # Assurer que l'indice est un entier
1965
+ time_value = self.wanted_time[time_idx] # Temps sélectionné à afficher
1966
+
1967
+ ax.clear()
1968
+ # Mise à jour du titre pour refléter le temps sélectionné
1969
+ days = time_value // (24*3600)
1970
+ hours = (time_value % (24*3600)) // 3600
1971
+ minutes = (time_value % (3600)) // 60
1972
+ seconds = time_value % 60
1973
+ ax.set_title(f"Proportion of bodies at the bottom and surface after \n{days} days, {hours} hours, {minutes} minutes, {seconds} seconds")
1974
+
1975
+ # Obtenir les positions x, y à l'instant de temps spécifié par time_idx
1976
+ z = self.Pos_b[:,2,time_idx]
1977
+
1978
+ surface = np.where(z > 0.2)[0]
1979
+ bottom = np.where(z <= 0.2)[0]
1980
+
1981
+ counts = [len(surface)/self.newdrowning.n_b*100, len(bottom)/self.newdrowning.n_b*100]
1982
+ labels = ['Surface', 'Bottom']
1983
+ colors = [[1,0.7,0.6,1], [0.6,0.7,1,1]]
1984
+
1985
+ ax.pie(counts, labels=labels, colors=colors, autopct='%1.1f%%')
1986
+
1987
+ # Rafraîchissement du graphique
1988
+ fig.canvas.draw_idle()
1989
+
1990
+ # Initialisation de la figure et des axes
1991
+ fig, ax = plt.subplots()
1992
+
1993
+ time_idx_initial = 0
1994
+ z = self.Pos_b[:,2,time_idx_initial]
1995
+
1996
+ surface = np.where(z > 0.2)[0]
1997
+ bottom = np.where(z <= 0.2)[0]
1998
+
1999
+ counts = [len(surface)/self.newdrowning.n_b*100, len(bottom)/self.newdrowning.n_b*100]
2000
+ labels = ['Surface', 'Bottom']
2001
+ colors = [[1,0.7,0.6,1], [0.6,0.7,1,1]]
2002
+
2003
+ ax.pie(counts, labels=labels, colors=colors, autopct='%1.1f%%')
2004
+
2005
+ # Création du slider pour ajuster l'indice temporel
2006
+ ax_slider = plt.axes([0.1, 0.1, 0.8, 0.05], facecolor='lightgoldenrodyellow')
2007
+ time_slider = Slider(ax_slider, 'Time', 0, len(self.wanted_time) - 2, valinit=time_idx_initial, valstep=1)
2008
+
2009
+ # Mise à jour de la KDE lorsque le slider est utilisé
2010
+ time_slider.on_changed(update_pie)
2011
+
2012
+ plt.show()
2013
+
2014
+
2015
+ return
2016
+
2017
+ class ProgressBar(wx.Frame):
2018
+ """
2019
+ Creates and manages the progress frame
2020
+ """
2021
+ def __init__(self,parent, n_processes,total):
2022
+ super(ProgressBar, self).__init__(parent)
2023
+ self.n_processes = n_processes
2024
+ self.total = total
2025
+
2026
+ # Set up the main panel and sizer
2027
+ panel = wx.Panel(self)
2028
+ sizer = wx.BoxSizer(wx.VERTICAL)
2029
+
2030
+ progress_bar = wx.Gauge(panel, range=100)
2031
+ sizer.Add(progress_bar, flag=wx.EXPAND | wx.ALL, border=5)
2032
+ self.progress_bars = progress_bar
2033
+ self.progress_text = wx.StaticText(panel, label="0%")
2034
+ sizer.Add(self.progress_text, flag=wx.ALIGN_CENTER | wx.TOP, border=5)
2035
+
2036
+ panel.SetSizer(sizer)
2037
+ self.SetTitle(_("Drowning Progress"))
2038
+ self.SetSize((300, 90))
2039
+
2040
+ def update_progress(self, progress_dict):
2041
+ """
2042
+ Update the progress bars based on the values in `progress_dict`.
2043
+ """
2044
+
2045
+ min_progress_value = None
2046
+ for i, progress in progress_dict.items():
2047
+ # Assume progress is a percentage (0 to 100)
2048
+ if progress is not None:
2049
+ if min_progress_value is None or progress < min_progress_value:
2050
+ min_progress_value = progress
2051
+ progress_percent = int(min_progress_value/self.total*100)
2052
+
2053
+ self.progress_bars.SetValue(progress_percent)
2054
+ self.progress_text.SetLabel(f"{progress_percent}%")
2055
+ self.SetTitle(f"Drowning Progress - {progress_percent}%")
2056
+
2057
+
2058
+ class ProgressImage(wx.Frame):
2059
+ """
2060
+ Creates and manages the progress frame with a fractioned image appearance
2061
+ """
2062
+ def __init__(self, n_processes, total, *args, **kw):
2063
+ super(ProgressImage, self).__init__(*args, **kw)
2064
+ self.n_processes = n_processes
2065
+ total_segments = 10*n_processes
2066
+ self.total_segments = total_segments
2067
+ self.total = total
2068
+
2069
+ # Set up the main panel and sizer
2070
+ panel = wx.Panel(self)
2071
+ grid_sizer = wx.GridSizer(rows=total_segments, cols=n_processes, gap=(5, 5))
2072
+
2073
+ current_dir = Path(__file__).resolve().parent
2074
+ # Path to your main image (set it to a suitable path)
2075
+ self.image_path = str(current_dir / "image.png")
2076
+
2077
+ # Load the image
2078
+ image = wx.Image(str(self.image_path), wx.BITMAP_TYPE_PNG)
2079
+ img_width, img_height = image.GetSize()
2080
+
2081
+ # Calculate segment dimensions
2082
+ segment_height = img_height // total_segments
2083
+ segment_width = img_width // n_processes
2084
+
2085
+ # Initialize a 2D list to hold each segment for each process
2086
+ self.image_segments = []
2087
+
2088
+ # Create a grid of segments for each process
2089
+ for row in range(total_segments):
2090
+ row_segments = []
2091
+ for col in range(n_processes):
2092
+ # Extract the specific segment for each cell in the grid
2093
+ x = col * segment_width
2094
+ y = row * segment_height
2095
+ segment = image.GetSubImage((x, y, segment_width, segment_height))
2096
+
2097
+ # Create a bitmap for the segment and add it to the grid, initially hidden
2098
+ segment_bitmap = wx.StaticBitmap(panel, bitmap=wx.Bitmap(segment))
2099
+ segment_bitmap.Hide() # Hide initially; show as progress advances
2100
+ grid_sizer.Add(segment_bitmap, flag=wx.ALIGN_CENTER)
2101
+ row_segments.append(segment_bitmap)
2102
+
2103
+ self.image_segments.append(row_segments)
2104
+
2105
+ panel.SetSizer(grid_sizer)
2106
+ self.SetTitle(_("Drowning Progress"))
2107
+ self.SetSize((img_width + 50, img_height + 50))
2108
+
2109
+ def update_progress(self, progress_dict):
2110
+ """
2111
+ Update the visibility of image segments based on the progress.
2112
+ """
2113
+ for process_id, progress in progress_dict.items():
2114
+ if progress is not None:
2115
+ # Calculate the number of segments to show based on progress
2116
+ num_segments_to_show = int((progress / self.total) * (self.total_segments // self.n_processes))
2117
+
2118
+ # Show the segments that correspond to the current progress
2119
+ for segment_id in range(num_segments_to_show):
2120
+ self.image_segments[segment_id][process_id].Show()
2121
+
2122
+ # Refresh the layout after updating visibility
2123
+ self.Layout()
2124
+ self.Refresh()