wolfhece 2.2.38__py3-none-any.whl → 2.2.39__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.
- wolfhece/Coordinates_operations.py +5 -0
- wolfhece/GraphNotebook.py +72 -1
- wolfhece/GraphProfile.py +1 -1
- wolfhece/MulticriteriAnalysis.py +1579 -0
- wolfhece/PandasGrid.py +62 -1
- wolfhece/PyCrosssections.py +194 -43
- wolfhece/PyDraw.py +891 -73
- wolfhece/PyGui.py +913 -72
- wolfhece/PyGuiHydrology.py +528 -74
- wolfhece/PyPalette.py +26 -4
- wolfhece/PyParams.py +33 -0
- wolfhece/PyPictures.py +2 -2
- wolfhece/PyVertex.py +25 -0
- wolfhece/PyVertexvectors.py +94 -28
- wolfhece/PyWMS.py +52 -36
- wolfhece/acceptability/acceptability.py +15 -8
- wolfhece/acceptability/acceptability_gui.py +507 -360
- wolfhece/acceptability/func.py +80 -183
- wolfhece/apps/version.py +1 -1
- wolfhece/compare_series.py +480 -0
- wolfhece/drawing_obj.py +12 -1
- wolfhece/hydrology/Catchment.py +228 -162
- wolfhece/hydrology/Internal_variables.py +43 -2
- wolfhece/hydrology/Models_characteristics.py +69 -67
- wolfhece/hydrology/Optimisation.py +893 -182
- wolfhece/hydrology/PyWatershed.py +267 -165
- wolfhece/hydrology/SubBasin.py +185 -140
- wolfhece/hydrology/cst_exchanges.py +76 -1
- wolfhece/hydrology/forcedexchanges.py +413 -49
- wolfhece/hydrology/read.py +65 -5
- wolfhece/hydrometry/kiwis.py +14 -7
- wolfhece/insyde_be/INBE_func.py +746 -0
- wolfhece/insyde_be/INBE_gui.py +1776 -0
- wolfhece/insyde_be/__init__.py +3 -0
- wolfhece/interpolating_raster.py +366 -0
- wolfhece/irm_alaro.py +1457 -0
- wolfhece/irm_qdf.py +889 -57
- wolfhece/lifewatch.py +6 -3
- wolfhece/picc.py +124 -8
- wolfhece/pyLandUseFlanders.py +146 -0
- wolfhece/pydownloader.py +2 -1
- wolfhece/pywalous.py +225 -31
- wolfhece/toolshydrology_dll.py +149 -0
- wolfhece/wolf_array.py +63 -25
- {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/METADATA +3 -1
- {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/RECORD +49 -40
- {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,746 @@
|
|
1
|
+
"""Imports et bons paths"""
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
_root_dir = os.getcwd()
|
5
|
+
original_sys_path = sys.path.copy()
|
6
|
+
sys.path.insert(0, os.path.join(_root_dir, r"..\.."))
|
7
|
+
|
8
|
+
from wolfhece.PyVertexvectors import Zones, zone, vector, wolfvertex
|
9
|
+
from wolfhece.picc import Picc_data, bbox_creation
|
10
|
+
from wolfhece.wolf_array import WolfArray
|
11
|
+
from wolfhece.Results2DGPU import wolfres2DGPU
|
12
|
+
import geopandas as gpd
|
13
|
+
import logging
|
14
|
+
from pathlib import Path
|
15
|
+
from tqdm import tqdm
|
16
|
+
import pandas as pd
|
17
|
+
import numpy as np
|
18
|
+
import matplotlib.pyplot as plt
|
19
|
+
from wolfhece.wolf_vrt import create_vrt_from_diverged_files_first_based, translate_vrt2tif
|
20
|
+
from wolfhece.wolf_array import WolfArray
|
21
|
+
from wolfhece.PyTranslate import _
|
22
|
+
from osgeo import gdal
|
23
|
+
|
24
|
+
|
25
|
+
#Other repository : hece_damage-------------------------------------------------------------
|
26
|
+
_root_dir = os.path.dirname(os.path.abspath(__file__))
|
27
|
+
hece_damage_path = os.path.abspath(os.path.join(_root_dir, "..", "..", "..", "hece_damage"))
|
28
|
+
sys.path.insert(0, hece_damage_path)
|
29
|
+
try:
|
30
|
+
from insyde_be.Py_INBE import PyINBE, INBE_Manager
|
31
|
+
print("Import OK")
|
32
|
+
except Exception as e:
|
33
|
+
logging.error(_(f"Problem to import insyde_be.Py_INBE: {e}"))
|
34
|
+
try:
|
35
|
+
from insyde_be.Py_INBE import PyINBE, INBE_Manager
|
36
|
+
print("Import OK")
|
37
|
+
except Exception as e:
|
38
|
+
logging.error(_(f"Problem to import insyde_be.Py_INBE: {e}"))
|
39
|
+
sys.path.pop(0)
|
40
|
+
|
41
|
+
class INBE_functions():
|
42
|
+
def __init__(self):
|
43
|
+
pass
|
44
|
+
|
45
|
+
# Assembly (FR : agglomération)
|
46
|
+
# -----------------------------
|
47
|
+
"""Basically the same operations as in the config manager to agglomerate several rasters
|
48
|
+
The class Config_Manager_2D_GPU is called, however some functions were rewritten to allow
|
49
|
+
the search of a more specific word ('vuln', and not 'bath', 'mann', or 'inf').
|
50
|
+
"""
|
51
|
+
|
52
|
+
def tree_name_tif(self, folder_path, name):
|
53
|
+
"""Find all .tiff files starting with 'vuln' in the directory and return paths"""
|
54
|
+
folder = Path(folder_path)
|
55
|
+
vuln_tiff_files = {file for file in folder.rglob("*.tiff") if file.name.startswith(name)}
|
56
|
+
vuln_tif_files = {file for file in folder.rglob("*.tif") if file.name.startswith(name)}
|
57
|
+
|
58
|
+
vuln_files = vuln_tiff_files.union(vuln_tif_files)
|
59
|
+
|
60
|
+
tiff_trees = []
|
61
|
+
if len(vuln_files) !=0:
|
62
|
+
for tiff in vuln_files:
|
63
|
+
curtree = [tiff]
|
64
|
+
while tiff.parent != folder:
|
65
|
+
tiff = tiff.parent
|
66
|
+
curtree.insert(0, tiff)
|
67
|
+
tiff_trees.append(curtree)
|
68
|
+
return tiff_trees
|
69
|
+
|
70
|
+
def select_name_tif(self, path_baseline: Path, folder_path: Path, name, filter_type = False) -> tuple[list[Path], list[Path]]:
|
71
|
+
"""
|
72
|
+
Collects all .tiff files starting with `name` from `folder_path` and appends them to a list.
|
73
|
+
Checks each file's data type against the baseline raster and renames files with a mismatched type,
|
74
|
+
(allowing it not to be surimposed as it does not begin by "vuln_" anymore).
|
75
|
+
|
76
|
+
:param path_baseline: Path to the baseline raster file.
|
77
|
+
:type path_baseline: pathlib.Path
|
78
|
+
:param folder_path: Path to the folder containing raster files to check.
|
79
|
+
:type folder_path: pathlib.Path
|
80
|
+
:param name: Prefix name to filter .tiff files in the folder.
|
81
|
+
:type name: str
|
82
|
+
:return: A tuple containing two lists:
|
83
|
+
- files: List of paths with matching data type.
|
84
|
+
- unused_files: List of paths renamed due to type mismatch.
|
85
|
+
:rtype: tuple[list[pathlib.Path], list[pathlib.Path]]
|
86
|
+
"""
|
87
|
+
|
88
|
+
files = []
|
89
|
+
unused_files = []
|
90
|
+
|
91
|
+
# Check type of base of assembly
|
92
|
+
ds_base = gdal.Open(path_baseline.as_posix())
|
93
|
+
type_base = gdal.GetDataTypeName(ds_base.GetRasterBand(1).DataType)
|
94
|
+
ds_base = None
|
95
|
+
|
96
|
+
files.append(path_baseline.as_posix())
|
97
|
+
|
98
|
+
#any files that begin by 'name'
|
99
|
+
tiff_trees = self.tree_name_tif(folder_path, name)
|
100
|
+
for tree in tiff_trees:
|
101
|
+
file_path = tree[-1]
|
102
|
+
ds = gdal.Open(file_path.as_posix())
|
103
|
+
file_type = gdal.GetDataTypeName(ds.GetRasterBand(1).DataType)
|
104
|
+
ds = None
|
105
|
+
#filter if not the same type and renaming
|
106
|
+
if filter_type == True :
|
107
|
+
if file_type != type_base:
|
108
|
+
suffix_to_add = f"_{file_type}"
|
109
|
+
new_name = file_path.parent / f"{file_path.name}{suffix_to_add}"
|
110
|
+
if not file_path.stem.endswith(suffix_to_add):
|
111
|
+
file_path.rename(new_name)
|
112
|
+
unused_files.append(new_name)
|
113
|
+
else:
|
114
|
+
unused_files.append(file_path)
|
115
|
+
else:
|
116
|
+
files.append(file_path.as_posix())
|
117
|
+
else:
|
118
|
+
files.append(file_path.as_posix())
|
119
|
+
return files, unused_files
|
120
|
+
|
121
|
+
def check_nodata(self, name, path_baseline, fn_scenario):
|
122
|
+
""" Check nodata in a path """
|
123
|
+
from ..wolf_array import WOLF_ARRAY_FULL_INTEGER8
|
124
|
+
list_tif, unused = self.select_name_tif(path_baseline, fn_scenario, name)
|
125
|
+
for cur_lst in list_tif:
|
126
|
+
curarray:WolfArray = WolfArray(cur_lst)
|
127
|
+
if curarray.wolftype != WOLF_ARRAY_FULL_INTEGER8:
|
128
|
+
if curarray.nullvalue != 99999.:
|
129
|
+
curarray.nullvalue = 99999.
|
130
|
+
curarray.set_nullvalue_in_mask()
|
131
|
+
curarray.write_all()
|
132
|
+
logging.warning(_('nodata changed in favor of 99999. value for file {} !'.format(cur_lst)))
|
133
|
+
else:
|
134
|
+
logging.info(_('nodata value is {} for file {} !'.format(curarray.nullvalue, cur_lst)))
|
135
|
+
|
136
|
+
|
137
|
+
def create_vrtIfExists(self, fn_baseline, fn_scenario, fn_vrt, name):
|
138
|
+
""" Create a vrt file from a path """
|
139
|
+
logging.info(_('Checking nodata values...'))
|
140
|
+
self.check_nodata(name, fn_baseline, fn_scenario)
|
141
|
+
list_tif, list_fail = self.select_name_tif(fn_baseline, fn_scenario, name, filter_type = True)
|
142
|
+
if len(list_fail)>0:
|
143
|
+
return False, list_fail
|
144
|
+
#création du fichier vrt - assembly/agglomération
|
145
|
+
if len(list_tif)>1:
|
146
|
+
logging.info(_('Creating .vrt from files (first based)...'))
|
147
|
+
if name == "vuln_":
|
148
|
+
create_vrt_from_diverged_files_first_based(list_tif, fn_vrt, Nodata = 127)
|
149
|
+
else:
|
150
|
+
create_vrt_from_diverged_files_first_based(list_tif, fn_vrt)
|
151
|
+
return True, list_fail
|
152
|
+
else:
|
153
|
+
return False, list_fail
|
154
|
+
|
155
|
+
|
156
|
+
def translate_vrt2tif(self, fn_VRT, fn_vuln_s):
|
157
|
+
""" Translate vrt from OUTPUT > ... > Scenario to tif saved in the same folder, and delete the vrt file """
|
158
|
+
if (fn_VRT).exists():
|
159
|
+
translate_vrt2tif(fn_VRT, fn_vuln_s)
|
160
|
+
os.remove(fn_VRT)
|
161
|
+
|
162
|
+
def copy_tif_files(self, files: list[Path], destination_dir: Path) -> None:
|
163
|
+
destination_dir.mkdir(parents=True, exist_ok=True)
|
164
|
+
|
165
|
+
for file in files:
|
166
|
+
destination_file = destination_dir / file.name
|
167
|
+
dataset = gdal.Open(str(file))
|
168
|
+
if dataset is None:
|
169
|
+
logging.warning(f"Could not open {file} with GDAL.")
|
170
|
+
continue
|
171
|
+
gdal_driver = gdal.GetDriverByName('GTiff')
|
172
|
+
gdal_driver.CreateCopy(str(destination_file), dataset, strict=0)
|
173
|
+
|
174
|
+
dataset = None
|
175
|
+
|
176
|
+
logging.info(_("All the existing .tif files have been copied to the destination directory."))
|
177
|
+
|
178
|
+
# Divers
|
179
|
+
def name_existence(self, Zones1, key, name):
|
180
|
+
names = [str(curzone.get_values(key)[0]) for curzone in Zones1.myzones]
|
181
|
+
if (str(name) in names):
|
182
|
+
print(f'There exists objects with name {name}.')
|
183
|
+
else :
|
184
|
+
print(f'No objects with name {name}')
|
185
|
+
|
186
|
+
def PICC_read(self, manager_inbe, name:str = "Habitation"):
|
187
|
+
study_area_path = Path(manager_inbe.IN_STUDY_AREA) / manager_inbe._study_area
|
188
|
+
bbox = bbox_creation(study_area_path)
|
189
|
+
|
190
|
+
_picc_data = Picc_data(data_dir=Path(),bbox=bbox)
|
191
|
+
path_vector = Path(manager_inbe.IN_DATABASE) / "PICC_Vesdre.shp"
|
192
|
+
if not path_vector.exists():
|
193
|
+
return None, None
|
194
|
+
|
195
|
+
zones_hab = _picc_data.create_zone_picc(path_vector = path_vector, path_points = Path(manager_inbe.IN_DATABASE) / "PICC_Vesdre_points.shp", name=name, column = "GEOREF_ID", bbox=bbox)
|
196
|
+
return zones_hab, zones_hab.nbzones, bbox
|
197
|
+
|
198
|
+
def create_table_wolfsimu_INTERPWD(self, manager_inbe, simu, operator_wd:str, ZonesX:Zones, Zones_v:Zones = None, percentil:float=None, title: str = "table_wolfsimu.xlsx"):
|
199
|
+
"""Creates the minimum inputs needed for INBE based on the simulations. One table per simulations."""
|
200
|
+
where_wd = manager_inbe.IN_SA_INTERP
|
201
|
+
where_v = manager_inbe.IN_SCEN_DIR_V
|
202
|
+
out= manager_inbe.IN_CSV_SCEN
|
203
|
+
out = Path(out) / title.replace(".xlsx", f"_{simu}.xlsx")
|
204
|
+
|
205
|
+
colonnes = [
|
206
|
+
"code", "he_max", "he_out", "h_gf", "v", "date_arr_base",
|
207
|
+
"date_leave_base", "no_s", "no_q", "FA", "NF", "GL", "YY", "Y_renov", "EP"
|
208
|
+
]
|
209
|
+
df = pd.DataFrame(columns=colonnes)
|
210
|
+
|
211
|
+
where_v = Path(where_v) / f"v_danger_{simu}_{manager_inbe.scenario}.tif"
|
212
|
+
|
213
|
+
simu_path = simu + ".tif"
|
214
|
+
print(f"where_v : {where_v}")
|
215
|
+
print(f"where_wd : {Path(where_wd) / simu_path}")
|
216
|
+
if not ((Path(where_wd) / simu_path).exists() and (where_v).exists()):
|
217
|
+
return print(f"Files of water depths and/or velocities does not exist.")
|
218
|
+
|
219
|
+
WA_wd = WolfArray(where_wd / simu_path)
|
220
|
+
WA_v = WolfArray(where_v)
|
221
|
+
|
222
|
+
#Exclure le mask dilaté (car pas plein bord...) de la rivière !
|
223
|
+
WA_river = WolfArray(manager_inbe.IN_RIVER_MASK_SCEN_tif)
|
224
|
+
i,j = np.where(WA_river.array.mask == False)
|
225
|
+
WA_wd.array[i,j] = WA_wd.nodata
|
226
|
+
WA_v.array[i,j] = WA_v.nodata
|
227
|
+
|
228
|
+
i = 0
|
229
|
+
val_h = 0
|
230
|
+
logging.info(_("Scanning zone for water depth inside buildings."))
|
231
|
+
for curzone in tqdm(ZonesX.myzones):
|
232
|
+
for curvector in curzone.myvectors:
|
233
|
+
if i >= ZonesX.nbzones:
|
234
|
+
break
|
235
|
+
wd_values = WA_wd.get_values_insidepoly(curvector)
|
236
|
+
|
237
|
+
if np.all(len(wd_values[0]) != 0):#np.all(len(wd_values[0]) != 0): #critère sur WD #statistics WA
|
238
|
+
if operator_wd == "mean":
|
239
|
+
val_h = wd_values[0].mean()
|
240
|
+
elif operator_wd == "max":
|
241
|
+
val_h = wd_values[0].max()
|
242
|
+
elif operator_wd == "median":
|
243
|
+
val_h = np.median(wd_values[0])
|
244
|
+
elif operator_wd == "percentil":
|
245
|
+
if percentil != None:
|
246
|
+
val_h = np.quantile(wd_values[0], percentil / 100)
|
247
|
+
else:
|
248
|
+
logging.error(_(f"Bad value for percentil {percentil}"))
|
249
|
+
return
|
250
|
+
else:
|
251
|
+
logging.error(_("To be coded."))
|
252
|
+
|
253
|
+
if val_h !=0:
|
254
|
+
df.loc[i, "code"] = str(curvector)
|
255
|
+
df.loc[i, "he_out"] = val_h
|
256
|
+
df.loc[i, "FA"] = curzone.area
|
257
|
+
curvector.update_lengths()
|
258
|
+
df.loc[i, "EP"] = curvector.length2D#perimeters[0]
|
259
|
+
i += 1
|
260
|
+
|
261
|
+
if Zones_v != None : #Si prise en compte de la vitesse, alors demande de parcourir un objet Zones avec buffer (autre loop necessaire)
|
262
|
+
logging.info(_("Scanning zone for velocity along the buffers."))
|
263
|
+
for curzone_v in tqdm(Zones_v.myzones):
|
264
|
+
for curvector_v in curzone_v.myvectors:
|
265
|
+
if i >= Zones_v.nbzones:
|
266
|
+
break
|
267
|
+
if str(curvector_v) in df['code'].astype(str).values:
|
268
|
+
v_values = WA_v.get_values_insidepoly(curvector_v, usemask=False)
|
269
|
+
v_values[0][v_values[0] == -99999] = 0
|
270
|
+
if len(v_values[0]) != 0:
|
271
|
+
if v_values[0].max() != 0 :
|
272
|
+
df.loc[df['code'] == str(curvector_v), "v"] = v_values[0].max()
|
273
|
+
else :
|
274
|
+
print("No v_values")
|
275
|
+
try:
|
276
|
+
df.to_excel(out, index=False)
|
277
|
+
except PermissionError:
|
278
|
+
logging.error(_(f"Permission denied: please ensure the file '{out.name}' is not open in another program."))
|
279
|
+
return
|
280
|
+
|
281
|
+
def pre_processing_auto(self, Ti_list, main_dir, Study_area, scenario, multiple, dx, percentil = None, operator_wd = "mean", hazard = "both"):
|
282
|
+
manager_inbe = INBE_Manager(main_dir=main_dir, Study_area=Study_area, scenario = scenario)
|
283
|
+
|
284
|
+
"""Création bbox par Polygon pour la classe Zone et lecture du PICC"""
|
285
|
+
if hazard == "both":
|
286
|
+
"""Prise en compte de la vitesse, via buffer"""
|
287
|
+
"""Buffer """
|
288
|
+
Zones_reading, unused, unused = self.PICC_read(manager_inbe, "Habitation")
|
289
|
+
|
290
|
+
"""Buffer creation"""
|
291
|
+
dx_buffer = multiple #multiple de la resolution topo
|
292
|
+
dx_buffer = multiple*dx
|
293
|
+
Zones_v = Zones_reading.buffer(distance = dx_buffer, resolution= 4, inplace = False)
|
294
|
+
#Zones_v.saveas(filename = r"C:\Users\damien\Desktop\test_zone_reading.shp")
|
295
|
+
|
296
|
+
elif hazard == "wd":
|
297
|
+
"""Seulement water_depth, pas de vitesse"""
|
298
|
+
Zones_reading, unused, unused = self.PICC_read(manager_inbe, "Habitation")
|
299
|
+
Zones_v = None
|
300
|
+
|
301
|
+
save_where_output = []
|
302
|
+
for curT in Ti_list:
|
303
|
+
print(f"----------------------------> Computing input table for {curT}")
|
304
|
+
#create_table_wolfsimu_BUFFERWD(manager_inbe = manager_inbe, simu=curT, ZonesX= Zones_reading, Zones_v = Zones_reading2)
|
305
|
+
self.create_table_wolfsimu_INTERPWD(manager_inbe = manager_inbe, simu=curT, operator_wd = operator_wd, ZonesX= Zones_reading, Zones_v = Zones_v, percentil=percentil)
|
306
|
+
print(f"--Preprocessing table created in {manager_inbe.IN_CSV_SCEN}")
|
307
|
+
save_where_output.append(manager_inbe.IN_CSV_SCEN)
|
308
|
+
return save_where_output
|
309
|
+
|
310
|
+
|
311
|
+
def computation_dfresults(self, input_table,type_computation, inflation):
|
312
|
+
Pyinsyde = PyINBE(input_table=input_table)
|
313
|
+
df_results,unused,unused,unused,unused,unused,unused,unused,df_input = Pyinsyde.run_R_insyde(type_computation=type_computation, inflation=inflation)
|
314
|
+
df_ifvalue = df_results[df_results['d_total'] > 0]
|
315
|
+
return df_ifvalue, df_input
|
316
|
+
|
317
|
+
def computation_combined_damage(self, pond: dict, manager_inbe) -> pd.DataFrame:
|
318
|
+
"""
|
319
|
+
! Première version assez basique
|
320
|
+
|
321
|
+
Compute the weighted sum of damage results across multiple return‑period DataFrames.
|
322
|
+
|
323
|
+
:param df_results_Ti: Dictionary mapping return‑period keys (e.g. "T2", "T5", ...) to pandas DataFrames.
|
324
|
+
Each DataFrame must have a "code" column identifying each building and one or more numeric columns representing damage categories.
|
325
|
+
:type df_results_Ti: dict
|
326
|
+
:param pond: Dictionary mapping the same return‑period keys to their weighting coefficients (e.g. {"T2": 0.65, "T5": 0.216667, ...}).
|
327
|
+
:type pond: dict
|
328
|
+
:return: A DataFrame with one row per unique building "code" (coordinate of the center of the polygon), containing the column "code"
|
329
|
+
plus each damage category column equal to the weighted sum across all Ti.
|
330
|
+
:rtype: pd.DataFrame
|
331
|
+
"""
|
332
|
+
df_results_Ti = manager_inbe.get_individualdamage()
|
333
|
+
weighted_dfs = []
|
334
|
+
for Ti, df in df_results_Ti.items():
|
335
|
+
coeff = pond.loc[int(Ti), "Ponderation"]
|
336
|
+
df_w = df.copy()
|
337
|
+
# Multiplying by the weigting coeff
|
338
|
+
numeric_cols = df_w.select_dtypes(include="number").columns
|
339
|
+
df_w[numeric_cols] = df_w[numeric_cols].mul(coeff)
|
340
|
+
weighted_dfs.append(df_w)
|
341
|
+
|
342
|
+
# Grouping and summing thanks to 'code' whioch
|
343
|
+
all_concat = pd.concat(weighted_dfs, ignore_index=True, sort=False)
|
344
|
+
final_df = all_concat.groupby("code", as_index=False).sum()
|
345
|
+
output_path = manager_inbe.OUT_COMB #Path(manager_inbe.OUT_SCEN_DIR) / f"combined_damage.xlsx"
|
346
|
+
try:
|
347
|
+
with open(output_path, 'wb') as f:
|
348
|
+
pass
|
349
|
+
final_df.to_excel(output_path, index=False)
|
350
|
+
except PermissionError:
|
351
|
+
logging.error(_(f"Permission denied: please ensure the file '{output_path}' is not open in another program."))
|
352
|
+
return
|
353
|
+
|
354
|
+
return final_df, output_path
|
355
|
+
|
356
|
+
def plot_damage(self, df_results, idx=None, cat=None, sorted_cond=None):
|
357
|
+
"""
|
358
|
+
Displays a damage bar chart for a specific index (specific building) or for all entries, and for all
|
359
|
+
damage categories or only one.
|
360
|
+
|
361
|
+
:param df_results: DataFrame containing damage computed by INBE.
|
362
|
+
:type df_results: pd.DataFrame
|
363
|
+
:param idx: Index of a specific row to plot. Defaults to None (every building plotted).
|
364
|
+
:type idx: int, optional
|
365
|
+
:param cat: Specific damage category to plot if idx is None (global mode). Defaults to None (every category plotted).
|
366
|
+
:type cat: str, optional
|
367
|
+
"""
|
368
|
+
|
369
|
+
with plt.rc_context({'text.usetex': True, 'font.size': 18, 'font.family': 'serif'}):
|
370
|
+
plt.rcParams.update({'text.usetex': False})
|
371
|
+
labels = ['d_cleaning', 'd_removal', 'd_non_stru', 'd_structural', 'd_finishing', 'd_systems']
|
372
|
+
labels_latex = [r'$D_{\mathrm{cleaning}}$',r'$D_{\mathrm{removal}}$',r'$D_{\mathrm{non stru}}$',r'$D_{\mathrm{structural}}$',r'$D_{\mathrm{finishing}}$',r'$D_{\mathrm{systems}}$']
|
373
|
+
|
374
|
+
if cat is not None:
|
375
|
+
labels = [cat]
|
376
|
+
|
377
|
+
if sorted_cond == True:
|
378
|
+
df_sorted = df_results.sort_values('d_total')
|
379
|
+
df_plot = df_sorted
|
380
|
+
else:
|
381
|
+
df_plot = df_results
|
382
|
+
|
383
|
+
fig, ax = plt.subplots(figsize=(8,6))
|
384
|
+
|
385
|
+
if idx is not None:
|
386
|
+
values = [df_plot[label][idx] / 1e3 for label in labels]
|
387
|
+
|
388
|
+
x = np.arange(len(labels))
|
389
|
+
colors = plt.cm.tab10.colors[:len(labels)]
|
390
|
+
bars = ax.bar(x, values, color=colors, alpha=0.6)
|
391
|
+
ax.set_xticks(x)
|
392
|
+
ax.set_xticklabels(labels, rotation=45)
|
393
|
+
ax.tick_params(axis='y', labelsize=13)
|
394
|
+
ax.set_xlabel("Categories of damage [-]")
|
395
|
+
ax.set_ylabel("d_total [10³€]", fontsize=13)
|
396
|
+
ax.grid(True, axis='y', linestyle='--', alpha=0.7)
|
397
|
+
|
398
|
+
else:
|
399
|
+
combined = list(zip(labels, labels_latex))
|
400
|
+
combined_sorted = sorted(combined, key=lambda x: df_plot[x[0]].sum(), reverse=True)
|
401
|
+
labels, labels_latex = zip(*combined_sorted)
|
402
|
+
|
403
|
+
colors = plt.cm.tab10.colors[:len(labels)]
|
404
|
+
x = np.arange(len(df_plot.index))
|
405
|
+
bottom = np.zeros(len(df_plot.index))
|
406
|
+
bars = []
|
407
|
+
for i, label in enumerate(labels):
|
408
|
+
values = df_plot[label] / 100
|
409
|
+
bar = ax.bar(x, values, bottom=bottom, color=colors[i], label=labels_latex[i], alpha=0.6)
|
410
|
+
bars.append(bar)
|
411
|
+
bottom += values
|
412
|
+
|
413
|
+
ax.set_xticks([]) # pas de ticks sur x
|
414
|
+
ax.set_ylabel("Damage [10³€]")
|
415
|
+
ax.legend(loc='lower center', bbox_to_anchor=(0.5, 1.02), ncol=3, borderaxespad=0.)
|
416
|
+
ax.grid(True, axis='y', linestyle='--', alpha=0.7)
|
417
|
+
|
418
|
+
# Tooltip setup - gadget... mais sympa
|
419
|
+
annot = ax.annotate("", xy=(0,0), xytext=(15,15), textcoords="offset points",
|
420
|
+
bbox=dict(boxstyle="round", fc="w"),
|
421
|
+
arrowprops=dict(arrowstyle="->"))
|
422
|
+
annot.set_visible(False)
|
423
|
+
|
424
|
+
def update_annot(bar, idx_bar):
|
425
|
+
x_mid_data = (ax.get_xlim()[0] + ax.get_xlim()[1]) / 2
|
426
|
+
|
427
|
+
y_pos = bar.get_height() + bar.get_y()
|
428
|
+
|
429
|
+
annot.xy = (bar.get_x() + bar.get_width() / 2, y_pos)
|
430
|
+
code = df_plot['code'].iloc[idx_bar]
|
431
|
+
annot.set_text(f"code: {code}")
|
432
|
+
annot.get_bbox_patch().set_alpha(0.8)
|
433
|
+
annot.set_fontsize(10)
|
434
|
+
|
435
|
+
arrow_disp = ax.transData.transform(annot.xy)
|
436
|
+
fig_width = fig.bbox.width
|
437
|
+
x_text_disp = fig_width / 2
|
438
|
+
y_text_disp = arrow_disp[1] + 15 # décalage vertical 15 pixels
|
439
|
+
|
440
|
+
annot.set_position((x_text_disp - arrow_disp[0], y_text_disp - arrow_disp[1]))
|
441
|
+
|
442
|
+
def hover(event):
|
443
|
+
vis = annot.get_visible()
|
444
|
+
for bar_group in bars:
|
445
|
+
for i, bar in enumerate(bar_group):
|
446
|
+
if bar.contains(event)[0]:
|
447
|
+
update_annot(bar, i)
|
448
|
+
annot.set_visible(True)
|
449
|
+
fig.canvas.draw_idle()
|
450
|
+
return
|
451
|
+
if vis:
|
452
|
+
annot.set_visible(False)
|
453
|
+
fig.canvas.draw_idle()
|
454
|
+
|
455
|
+
fig.canvas.mpl_connect("motion_notify_event", hover)
|
456
|
+
|
457
|
+
plt.tight_layout()
|
458
|
+
plt.show()
|
459
|
+
|
460
|
+
|
461
|
+
type_computation = "from_wolfsimu"
|
462
|
+
def computation_dfesults_forfolder(self, manager_inbe, type_computation, Ti_list, inflation): #changer nom "for folder" --> "for selected Ti"
|
463
|
+
"""
|
464
|
+
Process Excel files in the folder INPUT>CSVs matching the pattern 'table_wolfsimu_T<number>.xlsx',
|
465
|
+
extracts and sorts the T identifiers numerically, computes results (INBE) for each file using
|
466
|
+
computation_dfresults, and returns a dictionary of results keyed by each T identifier.
|
467
|
+
|
468
|
+
:param manager_inbe: Object containing the path to the input CSV scenario folder.
|
469
|
+
:type manager_inbe: Any
|
470
|
+
:param type_computation: Parameter specifying the type of computation to perform.
|
471
|
+
:type type_computation: Any
|
472
|
+
:param inflation: Inflation parameter used in the computation.
|
473
|
+
:type inflation: Any
|
474
|
+
:return: Dictionary with keys as T identifiers (e.g., 'T2') and values as computation results.
|
475
|
+
:rtype: dict
|
476
|
+
"""
|
477
|
+
|
478
|
+
df_results = {} #dictionnaire dont la clé sera les Ti dispo dans CSVs
|
479
|
+
df_input = {}
|
480
|
+
save_output_TEMP = []
|
481
|
+
save_output_defaultINPUT = []
|
482
|
+
|
483
|
+
for curT in Ti_list:
|
484
|
+
input_table = manager_inbe.IN_CSV_SCEN / ("table_wolfsimu_" + curT + ".xlsx")
|
485
|
+
if not input_table.exists():
|
486
|
+
logging.error(_(f"Input table table_wolfsimu_{curT}.xlsx does not exist."))
|
487
|
+
continue
|
488
|
+
logging.info(_(f"Computing damage for table_wolfsimu{curT}"))
|
489
|
+
df_results[curT], df_input[curT] = self.computation_dfresults(input_table, type_computation, inflation)
|
490
|
+
output_path = Path(manager_inbe.OUT_SCEN_DIR) / f"individual_damage_{curT}.xlsx"
|
491
|
+
try:
|
492
|
+
with open(output_path, 'wb') as f:
|
493
|
+
pass
|
494
|
+
df_results[curT].to_excel(output_path, index=False)
|
495
|
+
except PermissionError:
|
496
|
+
logging.error(_(f"Permission denied: please ensure the file '{output_path}' is not open in another program."))
|
497
|
+
return
|
498
|
+
|
499
|
+
output_path = Path(*output_path.parts[-5:])
|
500
|
+
save_output_TEMP.append(output_path)
|
501
|
+
output_path = Path(manager_inbe.OUT_SCEN_DIR_IN) / f"input_with_default_data_{curT}.xlsx"
|
502
|
+
try:
|
503
|
+
with open(output_path, 'wb') as f:
|
504
|
+
pass
|
505
|
+
df_input[curT].to_excel(output_path, index=False)
|
506
|
+
except PermissionError:
|
507
|
+
logging.error(_(f"Permission denied: please ensure the file '{output_path}' is not open in another program."))
|
508
|
+
return
|
509
|
+
|
510
|
+
output_path = Path(*output_path.parts[-5:])
|
511
|
+
save_output_defaultINPUT.append(output_path)
|
512
|
+
return df_results, save_output_TEMP, save_output_defaultINPUT
|
513
|
+
|
514
|
+
def adding_INBEresults_toZones(self, manager_inbex, Zones_trix, Ti = None):
|
515
|
+
cols = ["code", "d_cleaning", "d_removal", "d_non_stru", "d_structural", "d_finishing", "d_systems", "d_total"]
|
516
|
+
cols_d = ["d_cleaning", "d_removal", "d_non_stru", "d_structural", "d_finishing", "d_systems", "d_total"]
|
517
|
+
if Ti != None:
|
518
|
+
path_to_indiv_damage = manager_inbex.OUT_SCEN_DIR / ("individual_damage_" + str(Ti) + ".xlsx")
|
519
|
+
df_results = pd.read_excel(path_to_indiv_damage, usecols=cols)
|
520
|
+
if Ti == None :
|
521
|
+
Ti = None
|
522
|
+
df_results = pd.read_excel(manager_inbex.OUT_COMB, usecols=cols)
|
523
|
+
|
524
|
+
Zones_torasterize = Zones_trix
|
525
|
+
|
526
|
+
for curzone in tqdm(Zones_torasterize.myzones):
|
527
|
+
for curvector in curzone.myvectors:
|
528
|
+
row_match = df_results[df_results['code'].astype(str) == str(curvector)]#donne bool
|
529
|
+
if not row_match.empty:
|
530
|
+
row = row_match.iloc[0]
|
531
|
+
for key in cols_d:
|
532
|
+
read_value = []
|
533
|
+
read_value.append(str(row[key]))
|
534
|
+
arr = np.array(read_value, dtype=str)
|
535
|
+
curzone.add_values(key=str(key), values=arr)
|
536
|
+
#print(f"E.g : pour l'id {curzone.get_values('GEOREF_ID')} on a d = {curzone.get_values('d_total')}")
|
537
|
+
return Zones_torasterize
|
538
|
+
|
539
|
+
def label_0_needed(self, Zones1):
|
540
|
+
for curzone in tqdm(Zones1.myzones):
|
541
|
+
cols_d = ["d_cleaning", "d_removal", "d_non_stru", "d_structural", "d_finishing", "d_systems", "d_total"]
|
542
|
+
id_vals = [curzone.get_values(col) for col in cols_d]
|
543
|
+
id_clean = [v[0] if isinstance(v, (list, np.ndarray)) else v for v in id_vals]
|
544
|
+
|
545
|
+
if all(v is None for v in id_clean):
|
546
|
+
for col in cols_d:
|
547
|
+
curzone.add_values(key=col, values=np.array([0], dtype=int))
|
548
|
+
|
549
|
+
def raster_damage_in_zones(self, path_damage_out, path_onedanger, Zones_torasterize, scenario, Ti):
|
550
|
+
self.label_0_needed(Zones_torasterize)
|
551
|
+
WA_raster = WolfArray(path_onedanger)
|
552
|
+
WA_raster.unmask
|
553
|
+
WA_raster.array[WA_raster.array<=99999] = 99999
|
554
|
+
|
555
|
+
WA_raster.rasterize_zones_valuebyid(Zones_torasterize, id="d_total", method="linear")
|
556
|
+
|
557
|
+
if Ti != None:
|
558
|
+
out_title = Path(path_damage_out) / f"inbe_{scenario}_{Ti}.tif"
|
559
|
+
else :
|
560
|
+
out_title = Path(path_damage_out) / ("raster_" + str(scenario) + ".tif")
|
561
|
+
WA_raster.unmask
|
562
|
+
WA_raster.nodata=99999
|
563
|
+
WA_raster.write_all(out_title)
|
564
|
+
return print(f"WolfArray written at {out_title}")
|
565
|
+
|
566
|
+
def select_and_count_in_zones(self, Zones1, name):
|
567
|
+
comptage = 0
|
568
|
+
Zones2=Zones()
|
569
|
+
for curzone in Zones1.myzones:
|
570
|
+
if str(curzone.get_values("NATUR_DESC")[0]) == str(name):
|
571
|
+
comptage +=1
|
572
|
+
Zones2.add_zone(curzone, forceparent = True)
|
573
|
+
#print(f"--> Number of {name} : {comptage}")
|
574
|
+
return Zones2, comptage
|
575
|
+
|
576
|
+
def raster_auto(self, manager_inbe, which_result, Ti_list=None):
|
577
|
+
#1. Lecture du PICC
|
578
|
+
Zones_tri,unused,unused = self.PICC_read(manager_inbe, "Habitation")
|
579
|
+
if which_result == "individual_damage":
|
580
|
+
for Ti in Ti_list:
|
581
|
+
logging.info(_(f"Raster creation for {Ti}."))
|
582
|
+
#3. """Calcul damage : individual and combined""" --> déjà calculés et save dans excel
|
583
|
+
Zones_torasterize = self.adding_INBEresults_toZones(manager_inbe, Zones_tri, Ti = Ti)
|
584
|
+
tif_files = list(manager_inbe.IN_SA_INTERP.glob("T*.tif"))
|
585
|
+
if not tif_files:
|
586
|
+
logging.error(_(r"Please provide the water depth input."))
|
587
|
+
return
|
588
|
+
one_danger = tif_files[0]
|
589
|
+
self.raster_damage_in_zones(manager_inbe.OUT_SCEN_DIR, one_danger, Zones_torasterize, manager_inbe.scenario, Ti)
|
590
|
+
|
591
|
+
if which_result == "combined" :
|
592
|
+
Zones_tri,unused = self.select_and_count_in_zones(Zones_tri, "Habitation")
|
593
|
+
|
594
|
+
tif_files = list(manager_inbe.IN_SA_INTERP.glob("T*.tif"))
|
595
|
+
if not tif_files:
|
596
|
+
logging.error(_(r"Please provide the water depth input."))
|
597
|
+
return
|
598
|
+
one_danger = tif_files[0]
|
599
|
+
Zones_torasterize = self.adding_INBEresults_toZones(manager_inbe, Zones_tri, Ti = None)
|
600
|
+
self.raster_damage_in_zones(manager_inbe.OUT_SCEN_DIR, one_danger, Zones_torasterize, manager_inbe.scenario, None)
|
601
|
+
|
602
|
+
def load_quant_sum(self, path, quant):
|
603
|
+
d_total = pd.read_excel(path)['d_total']
|
604
|
+
p75 = d_total.quantile(quant)
|
605
|
+
return d_total[d_total <= p75].sum()
|
606
|
+
|
607
|
+
def extract_scenario_label(self, path_str):
|
608
|
+
p = Path(path_str)
|
609
|
+
parts = p.parts
|
610
|
+
label = None
|
611
|
+
for i, part in enumerate(parts):
|
612
|
+
if part == "TEMP" or part == "OUTPUT":
|
613
|
+
if i + 1 < len(parts):
|
614
|
+
label = parts[i + 2]
|
615
|
+
break
|
616
|
+
return label
|
617
|
+
|
618
|
+
def histo_total_from_list(self, list_path, quant):
|
619
|
+
damages = []
|
620
|
+
labels = []
|
621
|
+
colors = []
|
622
|
+
|
623
|
+
base_color = (212/255, 101/255, 10/255)
|
624
|
+
alt_colors = [
|
625
|
+
(119/255, 147/255, 60/255),
|
626
|
+
(127/255, 127/255, 127/255),
|
627
|
+
(91/255, 155/255, 213/255),
|
628
|
+
(255/255, 192/255, 0/255),
|
629
|
+
(255/255, 0/255, 255/255)
|
630
|
+
]
|
631
|
+
for i, path in enumerate(list_path):
|
632
|
+
D_tot = self.load_quant_sum(path, quant) / 1000
|
633
|
+
damages.append(D_tot)
|
634
|
+
labels.append(self.extract_scenario_label(path))
|
635
|
+
colors.append(base_color if i == 0 else alt_colors[(i - 1) % len(alt_colors)])
|
636
|
+
|
637
|
+
base_damage = damages[0]
|
638
|
+
rel_percents = [0]
|
639
|
+
for dmg in damages[1:]:
|
640
|
+
rel = round(dmg / base_damage * 100 - 100)
|
641
|
+
rel_percents.append(rel)
|
642
|
+
|
643
|
+
with plt.rc_context({'text.usetex': False, 'font.size': 16, 'font.family': 'serif'}):
|
644
|
+
labels2 = [str(lab) for lab in labels]
|
645
|
+
bars = plt.bar(labels2, damages, color=colors)
|
646
|
+
|
647
|
+
ylim_max = max(damages) * 1.15
|
648
|
+
|
649
|
+
for i in range(1, len(damages)):
|
650
|
+
rel = rel_percents[i]
|
651
|
+
label = f"+{rel}%" if rel > 0 else f"{rel}%"
|
652
|
+
color_txt = 'red' if rel > 0 else 'green'
|
653
|
+
plt.text(i, damages[i] + ylim_max*0.02, label, ha='center', color=color_txt, fontsize=20)
|
654
|
+
|
655
|
+
plt.ylabel('Total damage [10³€]')
|
656
|
+
plt.xticks(rotation=45)
|
657
|
+
plt.grid(True)
|
658
|
+
plt.ylim(0, ylim_max)
|
659
|
+
plt.tight_layout()
|
660
|
+
plt.show()
|
661
|
+
|
662
|
+
|
663
|
+
def scatter_INBE_dtotal(out, manager1, manager2, max_val=None, Ti=None, quant=None):
|
664
|
+
#...2 : baseline (...1 is the scenario we compare to ...2)
|
665
|
+
if Ti != None:
|
666
|
+
path2 = Path(manager1.OUT_SCEN_DIR) / f"individual_damage_T{Ti}.xlsx"
|
667
|
+
path1 = Path(manager2.OUT_SCEN_DIR) / f"individual_damage_T{Ti}.xlsx"
|
668
|
+
#title = r"$\textrm{" + Ti[1:] + r"-year flood}$"
|
669
|
+
title = rf"{Ti}-year flood"
|
670
|
+
else :
|
671
|
+
path2 = Path(manager1.OUT_COMB)
|
672
|
+
path1 = Path(manager2.OUT_COMB)
|
673
|
+
title = r"Combined damage"
|
674
|
+
|
675
|
+
df1 = pd.read_excel(path1)
|
676
|
+
df2 = pd.read_excel(path2)
|
677
|
+
|
678
|
+
df1 = df1[['code', 'd_total']].copy()
|
679
|
+
df2 = df2[['code', 'd_total']].copy()
|
680
|
+
|
681
|
+
df1.rename(columns={'d_total': 'd_total_sc1'}, inplace=True)
|
682
|
+
df2.rename(columns={'d_total': 'd_total_sc2'}, inplace=True)
|
683
|
+
|
684
|
+
merged = pd.merge(df1, df2, on='code', how='outer')
|
685
|
+
|
686
|
+
if quant != None :
|
687
|
+
percentile_sc1 = merged['d_total_sc1'].quantile(quant)
|
688
|
+
percentile_sc2 = merged['d_total_sc2'].quantile(quant)
|
689
|
+
|
690
|
+
# Filtrer les données pour ne garder que les valeurs supérieures ou égales au 90ᵉ percentile
|
691
|
+
filtered = merged[(merged['d_total_sc1'] <= percentile_sc1) | (merged['d_total_sc2'] <= percentile_sc2)]
|
692
|
+
merged=filtered
|
693
|
+
|
694
|
+
#ENSEMBLES
|
695
|
+
common = merged[ merged['d_total_sc1'].notna() & merged['d_total_sc2'].notna() ]
|
696
|
+
only_sc1 = merged[ merged['d_total_sc1'].notna() & merged['d_total_sc2'].isna() ]
|
697
|
+
only_sc2 = merged[ merged['d_total_sc2'].notna() & merged['d_total_sc1'].isna() ]
|
698
|
+
|
699
|
+
only_sc1_filled = only_sc1.copy()
|
700
|
+
only_sc1_filled['d_total_sc2'] = 0
|
701
|
+
|
702
|
+
only_sc2_filled = only_sc2.copy()
|
703
|
+
only_sc2_filled['d_total_sc1'] = 0
|
704
|
+
|
705
|
+
#COLOUR
|
706
|
+
#colors_common = [get_color(x, y) for x, y in zip(common['d_total_sc2'], common['d_total_sc1'])]
|
707
|
+
#colors_sc1 = [get_color(x, y) for x, y in zip(only_sc1_filled['d_total_sc2'], only_sc1_filled['d_total_sc1'])]
|
708
|
+
#colors_sc2 = [get_color(x, y) for x, y in zip(only_sc2_filled['d_total_sc2'], only_sc2_filled['d_total_sc1'])]
|
709
|
+
|
710
|
+
with plt.rc_context({'text.usetex': False, 'font.size': 16, 'font.family': 'serif'}):
|
711
|
+
fig, ax = plt.subplots(figsize=(8, 8))
|
712
|
+
ax.scatter(common['d_total_sc2'], common['d_total_sc1'], marker = 'x', color='black')#colors_common, label='Both scenarios')
|
713
|
+
ax.scatter(only_sc1_filled['d_total_sc2'], only_sc1_filled['d_total_sc1'], marker = 'x', color='black')#colors_common s=45, facecolors='none', color=colors_sc1, marker='s', label=f'Only in {scenario1}')
|
714
|
+
ax.scatter(only_sc2_filled['d_total_sc2'], only_sc2_filled['d_total_sc1'], marker = 'x', color='black')#colors_common s=60, facecolors='none', edgecolors=colors_sc2, label=f'Only in {scenario2}')
|
715
|
+
|
716
|
+
label2 = manager1.scenario
|
717
|
+
label1 = manager2.scenario
|
718
|
+
|
719
|
+
#min_val = merged[['d_total_sc1', 'd_total_sc2']].min().min()
|
720
|
+
if max_val ==None :
|
721
|
+
max_val = merged[['d_total_sc1', 'd_total_sc2']].max().max()
|
722
|
+
#common_marker = mlines.Line2D([], [], marker='x', color='black', linestyle='None', markersize=10, label='Both scenarios')
|
723
|
+
#sc1_marker = mlines.Line2D([], [], marker='s', color='black', linestyle='None', markersize=10, markerfacecolor='none', label=f'Only in {scenario1}')
|
724
|
+
#sc2_marker = mlines.Line2D([], [], marker='o', linestyle='None', markersize=10, markerfacecolor='none', markeredgecolor='black', label=f'Only in {scenario2}')
|
725
|
+
#ax.legend(handles=[common_marker, sc1_marker, sc2_marker])
|
726
|
+
|
727
|
+
#Formatting for latex
|
728
|
+
#label2_escaped = label2.replace(' ', r'\ ')
|
729
|
+
#label1_escaped = label1.replace(' ', r'\ ')
|
730
|
+
#xlabel = r"$\textrm{Total damage [€] for }\mathrm{" + label2_escaped + "}$"
|
731
|
+
#ylabel = r"$\textrm{Total damage [€] for }\mathrm{" + label1_escaped + "}$"
|
732
|
+
#ylabel = r"$\textrm{Scores [-] in }\mathrm{" + label1_escaped + "}$"
|
733
|
+
|
734
|
+
xlabel = rf"Total damage [€] for {label2}"
|
735
|
+
ylabel = rf"Total damage [€] for {label1}"
|
736
|
+
|
737
|
+
|
738
|
+
plt.plot([0, max_val], [0, max_val], '--', color='grey')
|
739
|
+
plt.xlabel(xlabel)
|
740
|
+
plt.ylabel(ylabel)
|
741
|
+
plt.title(title, loc='left')
|
742
|
+
plt.grid(True)
|
743
|
+
plt.tight_layout()
|
744
|
+
#out = Path(out) / f"scatterD_{Ti}_{label1}VS{label2}.PNG"
|
745
|
+
#plt.savefig(out, dpi=900)
|
746
|
+
plt.show()
|