wolfhece 2.2.44__py3-none-any.whl → 2.2.46__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/PyCrosssections.py +215 -24
- wolfhece/PyDraw.py +18 -11
- wolfhece/PyGui.py +1 -1
- wolfhece/PyVertexvectors.py +45 -22
- wolfhece/apps/version.py +1 -1
- wolfhece/dike.py +20 -8
- wolfhece/lazviewer/laz_viewer.py +29 -6
- wolfhece/math_parser/calculator.py +1 -1
- wolfhece/matplotlib_fig.py +4 -4
- wolfhece/mesh2d/wolf2dprev.py +5 -13
- wolfhece/pydike.py +3 -274
- wolfhece/report/common.py +141 -24
- wolfhece/report/compare_arrays.py +43 -34
- wolfhece/report/compare_cs_dem.py +1453 -0
- wolfhece/report/pdf.py +1 -1
- wolfhece/synthetic_dike.py +276 -0
- wolfhece/wolf_array.py +11 -6
- wolfhece/wolfresults_2D.py +99 -0
- {wolfhece-2.2.44.dist-info → wolfhece-2.2.46.dist-info}/METADATA +1 -1
- {wolfhece-2.2.44.dist-info → wolfhece-2.2.46.dist-info}/RECORD +23 -21
- {wolfhece-2.2.44.dist-info → wolfhece-2.2.46.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.44.dist-info → wolfhece-2.2.46.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.44.dist-info → wolfhece-2.2.46.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1453 @@
|
|
1
|
+
import logging
|
2
|
+
import math
|
3
|
+
from turtle import left, right
|
4
|
+
import numpy as np
|
5
|
+
import numpy.ma as ma
|
6
|
+
from pathlib import Path
|
7
|
+
import matplotlib.pyplot as plt
|
8
|
+
from enum import Enum
|
9
|
+
from scipy.ndimage import label, sum_labels, find_objects
|
10
|
+
import pymupdf as pdf
|
11
|
+
import wx
|
12
|
+
from tqdm import tqdm
|
13
|
+
from matplotlib import use, get_backend
|
14
|
+
from typing import Literal
|
15
|
+
|
16
|
+
from .common import A4_rect, rect_cm, list_to_html, list_to_html_aligned, get_rect_from_text, dict_to_html, dataframe_to_html
|
17
|
+
from .common import inches2cm, pts2cm, cm2pts, cm2inches, DefaultLayoutA4, NamedTemporaryFile, pt2inches, TemporaryDirectory
|
18
|
+
from ..wolf_array import WolfArray, header_wolf, vector, zone, Zones, wolfvertex as wv, wolfpalette
|
19
|
+
from ..PyVertexvectors import vector, zone, Zones, wolfvertex as wv
|
20
|
+
from ..PyCrosssections import crosssections, profile
|
21
|
+
from ..PyTranslate import _
|
22
|
+
from .pdf import PDFViewer
|
23
|
+
|
24
|
+
class CSvsDEM_MainLayout(DefaultLayoutA4):
|
25
|
+
"""
|
26
|
+
Layout for comparing cross-sections, array and Lidar LAZ in a report.
|
27
|
+
|
28
|
+
1 cadre pour la zone traitée avec photo de fond ign + contour vectoriel
|
29
|
+
1 cadre avec zoom plus large min 250m
|
30
|
+
1 cadre avec matrice ref + contour vectoriel
|
31
|
+
1 cadre avec matrice à comparer + contour vectoriel
|
32
|
+
1 cadre avec différence
|
33
|
+
1 cadre avec valeurs de synthèse
|
34
|
+
|
35
|
+
1 cadre avec histogramme
|
36
|
+
1 cadre avec histogramme des différences
|
37
|
+
"""
|
38
|
+
|
39
|
+
def __init__(self, title:str, filename = '', ox = 0, oy = 0, tx = 0, ty = 0, parent=None, is2D=True, idx = '', plotted = True, mapviewer=None, need_for_wx = False, bbox = None, find_minmax = True, shared = False, colors = None):
|
40
|
+
super().__init__(title, filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx, bbox, find_minmax, shared, colors)
|
41
|
+
|
42
|
+
useful = self.useful_part
|
43
|
+
|
44
|
+
width = useful.xmax - useful.xmin
|
45
|
+
height = useful.ymax - useful.ymin
|
46
|
+
|
47
|
+
self._map = self.add_element("Map", width=10., height=10., x = useful.xmin, y=useful.ymax - 10.)
|
48
|
+
|
49
|
+
self._summaries = self.add_element_repeated("Summary", width= width - 10. - self.padding, height=5. - self.padding/2.,
|
50
|
+
first_x=self._map.xmax + self.padding, first_y=self._map.ymin,
|
51
|
+
count_x=1, count_y=2, padding=0.5)
|
52
|
+
|
53
|
+
self._tables = self.add_element_repeated("Tables", width= (width-self.padding) / 2, height= 10.,
|
54
|
+
first_x=useful.xmin, first_y=useful.ymin,
|
55
|
+
count_x=2, count_y=1, padding=0.5)
|
56
|
+
|
57
|
+
self._histogram = self.add_element("Histogram", width= width, height= 3.,
|
58
|
+
x=useful.xmin, y=self._tables.ymax + self.padding)
|
59
|
+
|
60
|
+
class CSvsDEM_IndividualLayout(DefaultLayoutA4):
|
61
|
+
|
62
|
+
def __init__(self, title:str, filename = '', ox = 0, oy = 0, tx = 0, ty = 0, parent=None, is2D=True, idx = '', plotted = True, mapviewer=None, need_for_wx = False, bbox = None, find_minmax = True, shared = False, colors = None):
|
63
|
+
super().__init__(title, filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx, bbox, find_minmax, shared, colors)
|
64
|
+
|
65
|
+
useful = self.useful_part
|
66
|
+
|
67
|
+
width = useful.xmax - useful.xmin
|
68
|
+
height = useful.ymax - useful.ymin
|
69
|
+
|
70
|
+
self._maps = self.add_element_repeated("Maps", width= (width-self.padding) / 2, height=9., count_x=2, count_y=1, first_x=useful.xmin, first_y=useful.ymax - 9.)
|
71
|
+
|
72
|
+
self._cs = self.add_element("Cross-Sections", width= width, height=9., x=useful.xmin, y=self._maps.ymin - 9. - self.padding)
|
73
|
+
|
74
|
+
self._dem = self.add_element("DEM", width= (width - self.padding) / 3, height=5., x = useful.xmin, y=useful.ymin)
|
75
|
+
self._compare_cs = self.add_element("Comparison", width= (width - self.padding) * 2 / 3, height=5., x = self._dem.xmax + self.padding, y=useful.ymin)
|
76
|
+
|
77
|
+
class CSvsDEM():
|
78
|
+
"""
|
79
|
+
Class to manage the difference between a unique cross-section and a DEM.
|
80
|
+
"""
|
81
|
+
|
82
|
+
def __init__(self, data_group:list, idx:int, dem: WolfArray, title:str = "", index_group:int = 0,index_cs:int = 0, rebinned_dem:WolfArray = None):
|
83
|
+
|
84
|
+
self._dpi = 600
|
85
|
+
self.default_size_hitograms = (12, 6)
|
86
|
+
self.default_size_arrays = (10, 10)
|
87
|
+
self._fontsize = 6
|
88
|
+
|
89
|
+
self._data_group = data_group
|
90
|
+
self._idx = idx
|
91
|
+
|
92
|
+
self.dem = dem
|
93
|
+
self._rebinned_dem:WolfArray = rebinned_dem
|
94
|
+
|
95
|
+
if self.dem.nbnotnull > 1_000_000 and self._rebinned_dem is None:
|
96
|
+
logging.warning("The DEM has more than 1 million valid cells. Plotting a rebin one.")
|
97
|
+
self._rebinned_dem = WolfArray(mold=dem)
|
98
|
+
self._rebinned_dem.rebin(10)
|
99
|
+
self._rebinned_dem.mypal = dem.mypal
|
100
|
+
|
101
|
+
self._cs: profile
|
102
|
+
self._cs = data_group[idx]['profile']
|
103
|
+
|
104
|
+
assert isinstance(self.dem, WolfArray), "DEM must be a WolfArray instance."
|
105
|
+
assert isinstance(self._cs, profile), "Cross-section must be a profile instance."
|
106
|
+
|
107
|
+
self.title = title
|
108
|
+
self.index_cs = index_cs
|
109
|
+
self.index_group = index_group
|
110
|
+
|
111
|
+
self._background = 'IGN'
|
112
|
+
|
113
|
+
@property
|
114
|
+
def differences(self) -> tuple[float, float]:
|
115
|
+
""" Get the difference between the cross-section and the DEM at extremities. """
|
116
|
+
|
117
|
+
if not isinstance(self.dem, WolfArray):
|
118
|
+
raise TypeError("DEM must be an instance of WolfArray")
|
119
|
+
|
120
|
+
# Get the DEM value at the cross-section location
|
121
|
+
dem_value = self.dem.get_value(self._cs[0].x, self._cs[0].y)
|
122
|
+
|
123
|
+
if dem_value is None or ma.is_masked(dem_value):
|
124
|
+
return np.nan
|
125
|
+
|
126
|
+
# Get the cross-section value (assuming it's a single value at this point)
|
127
|
+
cs_value = self._cs[0].z
|
128
|
+
|
129
|
+
if cs_value is None or ma.is_masked(cs_value):
|
130
|
+
return np.nan
|
131
|
+
|
132
|
+
diff_left = cs_value - dem_value
|
133
|
+
|
134
|
+
dem_value = self.dem.get_value(self._cs[-1].x, self._cs[-1].y)
|
135
|
+
if dem_value is None or ma.is_masked(dem_value):
|
136
|
+
return np.nan
|
137
|
+
cs_value = self._cs[-1].z
|
138
|
+
if cs_value is None or ma.is_masked(cs_value):
|
139
|
+
return np.nan
|
140
|
+
diff_right = cs_value - dem_value
|
141
|
+
return diff_left, diff_right
|
142
|
+
|
143
|
+
def __str__(self):
|
144
|
+
|
145
|
+
l_diff, r_diff = self.differences
|
146
|
+
|
147
|
+
ret = self._label + '\n'
|
148
|
+
ret += _("Group : ") + str(self._idx) + '\n'
|
149
|
+
ret += _("Section : ") + str(self._cs.myname) + '\n'
|
150
|
+
ret += _("Left coordinates (X, Y) : ({:3f},{:3f})").format(self._cs[0].x, self._cs[0].y) + '\n'
|
151
|
+
ret += _("Right coordinates (X, Y) : ({:3f},{:3f})").format(self._cs[-1].x, self._cs[-1].y) + '\n'
|
152
|
+
ret += _("Left difference : {:3f}").format(l_diff) + '\n'
|
153
|
+
ret += _("Right difference : {:3f}").format(r_diff) + '\n'
|
154
|
+
|
155
|
+
return ret
|
156
|
+
|
157
|
+
def set_palette_distribute(self, minval:float, maxval:float, step:int=0):
|
158
|
+
"""
|
159
|
+
Set the palette for both arrays.
|
160
|
+
"""
|
161
|
+
self.dem.mypal.distribute_values(minval, maxval, step)
|
162
|
+
|
163
|
+
def set_palette(self, values:list[float], colors:list[tuple[int, int, int]]):
|
164
|
+
"""
|
165
|
+
Set the palette for both arrays based on specific values.
|
166
|
+
"""
|
167
|
+
self.dem.mypal.set_values_colors(values, colors)
|
168
|
+
|
169
|
+
def plot_position_grey(self, figax:tuple[plt.Figure, plt.Axes]=None, size_around:float=250) -> tuple[plt.Figure, plt.Axes]:
|
170
|
+
"""
|
171
|
+
Plot the reference array with a background.
|
172
|
+
"""
|
173
|
+
if figax is None:
|
174
|
+
figax = plt.subplots()
|
175
|
+
|
176
|
+
fig, ax = figax
|
177
|
+
|
178
|
+
h = self.dem.get_header()
|
179
|
+
h.origx = (self._cs[0].x + self._cs[-1].x)/2. - size_around
|
180
|
+
h.origy = (self._cs[0].y + self._cs[-1].y)/2. - size_around
|
181
|
+
h.dx = size_around * 2
|
182
|
+
h.dy = size_around * 2
|
183
|
+
h.nbx = 1
|
184
|
+
h.nby = 1
|
185
|
+
|
186
|
+
new = WolfArray(srcheader=h)
|
187
|
+
new.array.mask[:,:] = True
|
188
|
+
|
189
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
190
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
191
|
+
first_mask_data=False, with_legend=False,
|
192
|
+
update_palette= False,
|
193
|
+
Cartoweb= True,
|
194
|
+
cat = 'topo_grey',
|
195
|
+
)
|
196
|
+
else:
|
197
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
198
|
+
first_mask_data=False, with_legend=False,
|
199
|
+
update_palette= False,
|
200
|
+
Cartoweb= False,
|
201
|
+
cat = 'topo_grey',
|
202
|
+
)
|
203
|
+
|
204
|
+
copy = self._cs.deepcopy()
|
205
|
+
copy.myprop.width = 1
|
206
|
+
copy.myprop.color = 0xFF0000
|
207
|
+
copy.plot_matplotlib(ax=ax)
|
208
|
+
self._cs._plot_extremities(ax, s=10)
|
209
|
+
|
210
|
+
self.plot_cs_in_group(figax=figax, width=1, color=0x0000FF)
|
211
|
+
|
212
|
+
return fig, ax
|
213
|
+
|
214
|
+
|
215
|
+
def plot_position(self, figax:tuple[plt.Figure, plt.Axes]=None,
|
216
|
+
width:int = 3,
|
217
|
+
color:int = 0xFF0000) -> tuple[plt.Figure, plt.Axes]:
|
218
|
+
"""
|
219
|
+
Plot the dem array.
|
220
|
+
"""
|
221
|
+
if figax is None:
|
222
|
+
figax = plt.subplots()
|
223
|
+
|
224
|
+
fig, ax = figax
|
225
|
+
|
226
|
+
h = self.dem.get_header()
|
227
|
+
h.dx = h.nbx * h.dx
|
228
|
+
h.dy = h.nby * h.dy
|
229
|
+
h.nbx = 1
|
230
|
+
h.nby = 1
|
231
|
+
|
232
|
+
new_array = WolfArray(srcheader=h)
|
233
|
+
new_array.array.mask[:,:] = True
|
234
|
+
|
235
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
236
|
+
new_array.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
237
|
+
first_mask_data=False, with_legend=False,
|
238
|
+
update_palette= False,
|
239
|
+
IGN= True,
|
240
|
+
cat = 'orthoimage_coverage',
|
241
|
+
)
|
242
|
+
|
243
|
+
elif self._background.upper() == 'WALONMAP':
|
244
|
+
new_array.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
245
|
+
first_mask_data=False, with_legend=False,
|
246
|
+
update_palette= False,
|
247
|
+
Walonmap= True,
|
248
|
+
cat = 'IMAGERIE/ORTHO_2022_ETE',
|
249
|
+
)
|
250
|
+
else:
|
251
|
+
new_array.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
252
|
+
first_mask_data=False, with_legend=False,
|
253
|
+
update_palette= False,
|
254
|
+
Walonmap= False,
|
255
|
+
)
|
256
|
+
|
257
|
+
|
258
|
+
copy = self._cs.deepcopy()
|
259
|
+
copy.myprop.width = width
|
260
|
+
copy.myprop.color = color
|
261
|
+
copy.plot_matplotlib(ax=ax)
|
262
|
+
del copy
|
263
|
+
|
264
|
+
return fig, ax
|
265
|
+
|
266
|
+
def plot_position_around(self,
|
267
|
+
figax:tuple[plt.Figure, plt.Axes]=None,
|
268
|
+
size_around:float = 50.,
|
269
|
+
width:int = 3,
|
270
|
+
color:int = 0xFF0000,
|
271
|
+
s_extremities:int = 50,
|
272
|
+
colors_extremities:tuple[str, str] = ('blue', 'green')) -> tuple[plt.Figure, plt.Axes]:
|
273
|
+
"""
|
274
|
+
Plot the dem array.
|
275
|
+
"""
|
276
|
+
if figax is None:
|
277
|
+
figax = plt.subplots()
|
278
|
+
|
279
|
+
fig, ax = figax
|
280
|
+
|
281
|
+
# search bounds in group
|
282
|
+
min_x = min([sect['profile'].xmin for sect in self._data_group])
|
283
|
+
max_x = max([sect['profile'].xmax for sect in self._data_group])
|
284
|
+
min_y = min([sect['profile'].ymin for sect in self._data_group])
|
285
|
+
max_y = max([sect['profile'].ymax for sect in self._data_group])
|
286
|
+
|
287
|
+
h = self.dem.get_header()
|
288
|
+
h.origx = min_x - size_around
|
289
|
+
h.origy = min_y - size_around
|
290
|
+
h.dx = (max_x - min_x + size_around * 2)
|
291
|
+
h.dy = (max_y - min_y + size_around * 2)
|
292
|
+
h.nbx = 1
|
293
|
+
h.nby = 1
|
294
|
+
|
295
|
+
new_array = WolfArray(srcheader=h)
|
296
|
+
new_array.array.mask[:,:] = True
|
297
|
+
|
298
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
299
|
+
new_array.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
300
|
+
first_mask_data=False, with_legend=False,
|
301
|
+
update_palette= False,
|
302
|
+
IGN= True,
|
303
|
+
cat = 'orthoimage_coverage',
|
304
|
+
)
|
305
|
+
|
306
|
+
elif self._background.upper() == 'WALONMAP':
|
307
|
+
new_array.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
308
|
+
first_mask_data=False, with_legend=False,
|
309
|
+
update_palette= False,
|
310
|
+
Walonmap= True,
|
311
|
+
cat = 'IMAGERIE/ORTHO_2022_ETE',
|
312
|
+
)
|
313
|
+
else:
|
314
|
+
new_array.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
315
|
+
first_mask_data=False, with_legend=False,
|
316
|
+
update_palette= False,
|
317
|
+
Walonmap= False,
|
318
|
+
)
|
319
|
+
|
320
|
+
|
321
|
+
copy = self._cs.deepcopy()
|
322
|
+
copy.myprop.width = width
|
323
|
+
copy.myprop.color = color
|
324
|
+
copy.plot_matplotlib(ax=ax)
|
325
|
+
self._cs._plot_extremities(ax, s=s_extremities, colors=colors_extremities)
|
326
|
+
|
327
|
+
self.plot_cs_in_group(figax=figax, width=2, color=0x0000FF)
|
328
|
+
|
329
|
+
return fig, ax
|
330
|
+
|
331
|
+
def plot_cs_in_group(self, figax:tuple[plt.Figure, plt.Axes]=None,
|
332
|
+
width:int = 2,
|
333
|
+
color:int = 0x0000FF) -> tuple[plt.Figure, plt.Axes]:
|
334
|
+
"""
|
335
|
+
Plot the others cross-sections in the group if exists.
|
336
|
+
"""
|
337
|
+
|
338
|
+
if len(self._data_group) <= 1:
|
339
|
+
return figax
|
340
|
+
|
341
|
+
if figax is None:
|
342
|
+
figax = plt.subplots()
|
343
|
+
|
344
|
+
fig, ax = figax
|
345
|
+
|
346
|
+
for idx, sect in enumerate(self._data_group):
|
347
|
+
if idx == self._idx:
|
348
|
+
continue
|
349
|
+
|
350
|
+
cs:profile
|
351
|
+
cs = sect['profile']
|
352
|
+
copy = cs.deepcopy()
|
353
|
+
copy.myprop.width = width
|
354
|
+
copy.myprop.color = color
|
355
|
+
copy.plot_matplotlib(ax=ax)
|
356
|
+
del copy
|
357
|
+
|
358
|
+
return fig, ax
|
359
|
+
|
360
|
+
def plot_dem_around(self,
|
361
|
+
figax:tuple[plt.Figure, plt.Axes]=None,
|
362
|
+
size_around:float = 10.,
|
363
|
+
width:int = 3,
|
364
|
+
color:int = 0xFF0000,
|
365
|
+
s_extremities:int = 50,
|
366
|
+
colors_extremities:tuple[str, str] = ('blue', 'green')) -> tuple[plt.Figure, plt.Axes]:
|
367
|
+
"""
|
368
|
+
Plot the dem array.
|
369
|
+
"""
|
370
|
+
if figax is None:
|
371
|
+
figax = plt.subplots()
|
372
|
+
|
373
|
+
fig, ax = figax
|
374
|
+
|
375
|
+
h = self.dem.get_header()
|
376
|
+
h.origx = (self._cs[0].x + self._cs[-1].x)/2. - size_around
|
377
|
+
h.origy = (self._cs[0].y + self._cs[-1].y)/2. - size_around
|
378
|
+
h.dx = size_around * 2
|
379
|
+
h.dy = size_around * 2
|
380
|
+
h.nbx = 1
|
381
|
+
h.nby = 1
|
382
|
+
|
383
|
+
new_array = WolfArray(mold = self.dem, crop=h.get_bounds())
|
384
|
+
new_array.updatepalette()
|
385
|
+
new_array.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
386
|
+
|
387
|
+
copy = self._cs.deepcopy()
|
388
|
+
copy.myprop.width = width
|
389
|
+
copy.myprop.color = color
|
390
|
+
copy.plot_matplotlib(ax=ax)
|
391
|
+
self._cs._plot_extremities(ax, s=s_extremities, colors=colors_extremities)
|
392
|
+
|
393
|
+
ax.legend(fontsize=6)
|
394
|
+
|
395
|
+
return fig, ax
|
396
|
+
|
397
|
+
|
398
|
+
def plot_position_scaled(self, scale = 4,
|
399
|
+
figax:tuple[plt.Figure, plt.Axes]=None,
|
400
|
+
width:int = 3,
|
401
|
+
color:int = 0xFF0000) -> tuple[plt.Figure, plt.Axes]:
|
402
|
+
"""
|
403
|
+
Plot the reference array.
|
404
|
+
|
405
|
+
:param scale: Scale factor to apply to the extent of the DEM. For example, scale=1 will double the extent, scale=2 will triple it, etc.
|
406
|
+
:param figax: Tuple of (Figure, Axes) to plot on. If None, a new figure and axes will be created.
|
407
|
+
"""
|
408
|
+
if figax is None:
|
409
|
+
figax = plt.subplots()
|
410
|
+
|
411
|
+
fig, ax = figax
|
412
|
+
|
413
|
+
h = self.dem.get_header()
|
414
|
+
a_width = h.nbx * h.dx
|
415
|
+
a_height = h.nby * h.dy
|
416
|
+
|
417
|
+
h.origx += -a_width * scale / 2
|
418
|
+
h.origy += -a_height *scale / 2
|
419
|
+
h.nbx = 1
|
420
|
+
h.nby = 1
|
421
|
+
h.dx = a_width *(scale + 1)
|
422
|
+
h.dy = a_height *(scale + 1)
|
423
|
+
|
424
|
+
new = WolfArray(srcheader=h)
|
425
|
+
new.array.mask[:,:] = True
|
426
|
+
|
427
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
428
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
429
|
+
first_mask_data=False, with_legend=False,
|
430
|
+
update_palette= False,
|
431
|
+
IGN= True,
|
432
|
+
cat = 'orthoimage_coverage')
|
433
|
+
elif self._background.upper() == 'WALONMAP':
|
434
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
435
|
+
first_mask_data=False, with_legend=False,
|
436
|
+
update_palette= False,
|
437
|
+
Walonmap= True,
|
438
|
+
cat = 'IMAGERIE/ORTHO_2022_ETE')
|
439
|
+
else:
|
440
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
441
|
+
first_mask_data=False, with_legend=False,
|
442
|
+
update_palette= False,
|
443
|
+
Walonmap= False)
|
444
|
+
|
445
|
+
copy = self._cs.deepcopy()
|
446
|
+
copy.myprop.width = width
|
447
|
+
copy.myprop.color = color
|
448
|
+
copy.plot_matplotlib(ax=ax)
|
449
|
+
del copy
|
450
|
+
|
451
|
+
return fig, ax
|
452
|
+
|
453
|
+
def plot_dem(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
454
|
+
"""
|
455
|
+
Plot the reference array.
|
456
|
+
"""
|
457
|
+
if figax is None:
|
458
|
+
figax = plt.subplots()
|
459
|
+
|
460
|
+
fig, ax = figax
|
461
|
+
|
462
|
+
if self._rebinned_dem is not None:
|
463
|
+
self._rebinned_dem.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
464
|
+
else:
|
465
|
+
self.dem.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
466
|
+
|
467
|
+
copy = self._cs.deepcopy()
|
468
|
+
copy.myprop.width = 5
|
469
|
+
copy.myprop.color = 0xFF0000
|
470
|
+
copy.plot_matplotlib(ax=ax)
|
471
|
+
del copy
|
472
|
+
|
473
|
+
return fig, ax
|
474
|
+
|
475
|
+
def plot_cs(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
476
|
+
"""
|
477
|
+
Plot the cross section to compare.
|
478
|
+
"""
|
479
|
+
if figax is None:
|
480
|
+
figax = plt.subplots()
|
481
|
+
|
482
|
+
fig, ax = figax
|
483
|
+
|
484
|
+
old_plotted = self.dem.plotted
|
485
|
+
self.dem.plotted = True
|
486
|
+
self._cs.plot_cs(fig = fig, ax= ax, linked_arrays={"DEM": self.dem})
|
487
|
+
self.dem.plotted = old_plotted
|
488
|
+
|
489
|
+
return fig, ax
|
490
|
+
|
491
|
+
def plot_cs_min_at_x0(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
492
|
+
"""
|
493
|
+
Plot the cross section to compare.
|
494
|
+
"""
|
495
|
+
if figax is None:
|
496
|
+
figax = plt.subplots()
|
497
|
+
|
498
|
+
fig, ax = figax
|
499
|
+
|
500
|
+
self._cs._plot_only_cs_min_at_x0(fig = fig, ax= ax, label = 'Cross-Section', style='solid', lw=2)
|
501
|
+
|
502
|
+
return fig, ax
|
503
|
+
|
504
|
+
def plot_cs_limited(self, figax:tuple[plt.Figure, plt.Axes]=None, tolerance:float = 1.) -> tuple[plt.Figure, plt.Axes]:
|
505
|
+
"""
|
506
|
+
Plot the cross section to compare.
|
507
|
+
"""
|
508
|
+
if figax is None:
|
509
|
+
figax = plt.subplots()
|
510
|
+
|
511
|
+
fig, ax = figax
|
512
|
+
|
513
|
+
old_plotted = self.dem.plotted
|
514
|
+
self.dem.plotted = True
|
515
|
+
self._cs.plot_cs(fig = fig, ax= ax, linked_arrays={"DEM": self.dem}, forceaspect=False)
|
516
|
+
self.dem.plotted = old_plotted
|
517
|
+
|
518
|
+
minz_cs = self._cs.zmin - tolerance
|
519
|
+
maxz_cs = self._cs.zmax + tolerance
|
520
|
+
|
521
|
+
# round to 0 decimals but keep as float
|
522
|
+
minz_cs = round(minz_cs, 0)
|
523
|
+
maxz_cs = math.ceil(maxz_cs)
|
524
|
+
|
525
|
+
ax.set_ylim(minz_cs, maxz_cs)
|
526
|
+
|
527
|
+
return fig, ax
|
528
|
+
|
529
|
+
def plot_up_down_min_at_x0(self, figax:tuple[plt.Figure, plt.Axes]=None, n_iter = 2) -> tuple[plt.Figure, plt.Axes]:
|
530
|
+
"""
|
531
|
+
Plot the cross section to compare.
|
532
|
+
"""
|
533
|
+
if figax is None:
|
534
|
+
figax = plt.subplots()
|
535
|
+
|
536
|
+
fig, ax = figax
|
537
|
+
|
538
|
+
self._cs._plot_only_cs_min_at_x0(fig = fig, ax= ax, label = _('Cross-Section'), style='solid', lw=2)
|
539
|
+
|
540
|
+
n_iter_up = n_iter
|
541
|
+
n_iter_down = n_iter
|
542
|
+
|
543
|
+
cs_up = self._cs.up
|
544
|
+
while n_iter_up > 0 and cs_up is not None:
|
545
|
+
if cs_up is self._cs:
|
546
|
+
break
|
547
|
+
cs_up._plot_only_cs_min_at_x0(fig = fig, ax= ax, style='dashed', label = _('Upstream {}').format(n_iter - n_iter_up + 1), col_ax='green', lw = n_iter_up, alpha= 1 - (n_iter - n_iter_up + 1 ) * 0.25)
|
548
|
+
cs_up = cs_up.up
|
549
|
+
n_iter_up -= 1
|
550
|
+
|
551
|
+
cs_down = self._cs.down
|
552
|
+
while n_iter_down > 0 and cs_down is not None:
|
553
|
+
if cs_down is self._cs:
|
554
|
+
break
|
555
|
+
cs_down._plot_only_cs_min_at_x0(fig = fig, ax= ax, style='dashed', label = _('Downstream {}').format(n_iter - n_iter_down + 1), col_ax='blue', lw = n_iter_down + 1, alpha= 1 - (n_iter - n_iter_down + 1) * 0.25)
|
556
|
+
cs_down = cs_down.down
|
557
|
+
n_iter_down -= 1
|
558
|
+
|
559
|
+
ax.legend(fontsize=6)
|
560
|
+
|
561
|
+
return fig, ax
|
562
|
+
|
563
|
+
def _complete_report(self, page:CSvsDEM_IndividualLayout):
|
564
|
+
|
565
|
+
"""
|
566
|
+
Complete the report with the arrays and histograms.
|
567
|
+
"""
|
568
|
+
useful = page.useful_part
|
569
|
+
|
570
|
+
# Plot reference array
|
571
|
+
key_fig = [('Maps_0-0', self.plot_position_around),
|
572
|
+
('Maps_1-0', self.plot_position_grey),
|
573
|
+
('Cross-Sections', self.plot_cs_limited),
|
574
|
+
('DEM', self.plot_dem_around),
|
575
|
+
('Comparison', self.plot_up_down_min_at_x0),
|
576
|
+
]
|
577
|
+
|
578
|
+
keys = page.keys
|
579
|
+
for key, fig_routine in key_fig:
|
580
|
+
if key in keys:
|
581
|
+
|
582
|
+
rect = page.layout[key]
|
583
|
+
|
584
|
+
fig, ax = fig_routine()
|
585
|
+
|
586
|
+
# set size to fit the rectangle
|
587
|
+
fig.set_size_inches(pt2inches(rect.width), pt2inches(rect.height))
|
588
|
+
|
589
|
+
if 'Histogram' in key:
|
590
|
+
fig.tight_layout()
|
591
|
+
|
592
|
+
# convert canvas to PNG and insert it into the PDF
|
593
|
+
temp_file = NamedTemporaryFile(delete=False, suffix='.png')
|
594
|
+
fig.savefig(temp_file, format='png', bbox_inches='tight', dpi=self._dpi)
|
595
|
+
page._page.insert_image(page.layout[key], filename = temp_file.name)
|
596
|
+
# delete the temporary file
|
597
|
+
temp_file.delete = True
|
598
|
+
temp_file.close()
|
599
|
+
|
600
|
+
# Force to delete fig
|
601
|
+
plt.close(fig)
|
602
|
+
else:
|
603
|
+
logging.warning(f"Key {key} not found in layout. Skipping plot.")
|
604
|
+
|
605
|
+
key = 'Arrays_1-2'
|
606
|
+
if key in keys:
|
607
|
+
text, css = list_to_html(self._summary_text, font_size='8pt')
|
608
|
+
page._page.insert_htmlbox(page.layout[key], text,
|
609
|
+
css=css)
|
610
|
+
|
611
|
+
def create_report(self, output_file: str | Path = None) -> Path:
|
612
|
+
""" Create a page report for the array difference. """
|
613
|
+
|
614
|
+
from time import sleep
|
615
|
+
if output_file is None:
|
616
|
+
output_file = Path(f"array_difference_{self.index_cs}.pdf")
|
617
|
+
|
618
|
+
if output_file.exists():
|
619
|
+
logging.warning(f"Output file {output_file} already exists. It will be overwritten.")
|
620
|
+
|
621
|
+
page = CSvsDEM_IndividualLayout(_("Group {} - Index {} - Cross-Section {}").format(self.index_group, self.index_cs, self._cs.myname))
|
622
|
+
page.create_report()
|
623
|
+
self._complete_report(page)
|
624
|
+
page.save_report(output_file)
|
625
|
+
sleep(0.2) # Ensure the file is saved before returning
|
626
|
+
|
627
|
+
return output_file
|
628
|
+
|
629
|
+
|
630
|
+
class CompareMultipleCSvsDEM:
|
631
|
+
|
632
|
+
def __init__(self, cross_sections:crosssections | Path | str,
|
633
|
+
dem: WolfArray | str | Path,
|
634
|
+
laz_directory: Path | str = None,
|
635
|
+
support: Path | str | vector = None,
|
636
|
+
threshold_z: float = 0.5,
|
637
|
+
distance_threshold: float = 50.):
|
638
|
+
""" Compare multiple cross-sections with a DEM.
|
639
|
+
|
640
|
+
:param cross_sections: Cross-sections to compare. Can be a crosssections object or a path to a vector file (.vecz).
|
641
|
+
:param dem: DEM to compare with. Can be a WolfArray object or a path to a raster file.
|
642
|
+
:param laz_directory: Directory where the LAZ files are stored (Numpy-Wolf format).
|
643
|
+
:param support: Support vector to sort the cross-sections along. Can be a path to a vector file (first vector in the first zone will be used) or a vector object.
|
644
|
+
"""
|
645
|
+
|
646
|
+
if isinstance(support, (str, Path)):
|
647
|
+
if not Path(support).exists():
|
648
|
+
logging.error(f"The support file {support} does not exist. Centers will be used.")
|
649
|
+
self._support = None
|
650
|
+
support = Zones(support)
|
651
|
+
self._support = support[(0,0)] # get the first zone
|
652
|
+
elif isinstance(support, vector):
|
653
|
+
self._support = support
|
654
|
+
else:
|
655
|
+
self._support = None
|
656
|
+
logging.warning("The support is not a valid file or vector. Centers will be used.")
|
657
|
+
|
658
|
+
self._dpi = 600
|
659
|
+
self.default_size_arrays = (10, 10)
|
660
|
+
self._fontsize = 6
|
661
|
+
|
662
|
+
if isinstance(dem, (str, Path)):
|
663
|
+
dem = WolfArray(dem)
|
664
|
+
|
665
|
+
assert isinstance(dem, WolfArray), "DEM must be a WolfArray instance."
|
666
|
+
|
667
|
+
if isinstance(cross_sections, (str, Path)):
|
668
|
+
cross_sections = crosssections(cross_sections, format='vecz', dirlaz=laz_directory)
|
669
|
+
|
670
|
+
assert isinstance(cross_sections, crosssections), "Cross-sections must be a crosssections instance."
|
671
|
+
|
672
|
+
if self._support is None:
|
673
|
+
self._support = cross_sections.create_vector_from_centers()
|
674
|
+
|
675
|
+
cross_sections.sort_along(self._support, 'support', downfirst=False)
|
676
|
+
|
677
|
+
assert cross_sections.check_left_right_coherence() == 0, "Cross-sections are not coherent in left/right orientation."
|
678
|
+
|
679
|
+
self._dirlaz = laz_directory
|
680
|
+
self._cs = cross_sections
|
681
|
+
|
682
|
+
if self._cs.dirlaz is None or self._cs.dirlaz != self._dirlaz:
|
683
|
+
logging.info(f"Setting cross-sections directory for LAZ files to {self._dirlaz}")
|
684
|
+
self._cs.dirlaz = self._dirlaz
|
685
|
+
|
686
|
+
self.dem:WolfArray
|
687
|
+
self.dem = dem
|
688
|
+
self._rebinned_dem:WolfArray = None
|
689
|
+
|
690
|
+
if self.dem.nbnotnull > 1_000_000:
|
691
|
+
logging.warning("The DEM has more than 1 million valid cells. Plotting a rebin one.")
|
692
|
+
self._rebinned_dem = WolfArray(mold=dem)
|
693
|
+
self._rebinned_dem.rebin(10)
|
694
|
+
self._rebinned_dem.mypal = dem.mypal
|
695
|
+
|
696
|
+
self.subpages:dict[int, CSvsDEM] = {}
|
697
|
+
|
698
|
+
self._pdf_path = None
|
699
|
+
|
700
|
+
self._background = 'IGN'
|
701
|
+
|
702
|
+
self._groups: list[list[dict['section_id':int, "x": float, "y": float, "diff_left": float, "diff_right": float, "s": float]]]
|
703
|
+
self._groups = []
|
704
|
+
|
705
|
+
self._threshold_z = threshold_z
|
706
|
+
self._distance_threshold = distance_threshold
|
707
|
+
|
708
|
+
self.find_differences(tolerance = self._threshold_z, distance_threshold = self._distance_threshold)
|
709
|
+
|
710
|
+
logging.info(f"Number of groups of differences: {self.count_groups}")
|
711
|
+
logging.info(f"Number of groups of differences greater than 3: {self.count_groups_greater_than(3)}")
|
712
|
+
|
713
|
+
def find_differences(self, tolerance:float = 0.5, distance_threshold:float = 50.):
|
714
|
+
""" Find differences between cross-sections and DEM.
|
715
|
+
|
716
|
+
Store the differences in self._diffs as a list of lists of dictionaries with keys: section_id, x, y, diff_left, diff_right.
|
717
|
+
|
718
|
+
We need to group the closest cross-sections that have differences.
|
719
|
+
So, we start from the upstream cross-section and go downstream, grouping cross-sections which have differences and are close to each other (less than distance_threshold m apart).
|
720
|
+
|
721
|
+
:param tolerance: Tolerance in meters to consider a difference. If the absolute difference between the cross-section and the DEM is greater than this value, it is considered a difference.
|
722
|
+
"""
|
723
|
+
|
724
|
+
all_profiles = []
|
725
|
+
|
726
|
+
loc_cs = self._cs.get_upstream()
|
727
|
+
loc_profile = loc_cs['cs']
|
728
|
+
|
729
|
+
while loc_profile.down is not loc_profile:
|
730
|
+
|
731
|
+
diff_left = abs(self.dem.get_value(loc_profile[0].x, loc_profile[0].y) - loc_profile[0].z)
|
732
|
+
diff_right = abs(self.dem.get_value(loc_profile[-1].x, loc_profile[-1].y) - loc_profile[-1].z)
|
733
|
+
|
734
|
+
if diff_left > tolerance or diff_right > tolerance:
|
735
|
+
all_profiles.append({'profile': loc_profile, 'diff_left': diff_left, 'diff_right': diff_right})
|
736
|
+
|
737
|
+
loc_profile = loc_profile.down
|
738
|
+
|
739
|
+
diff_left = abs(self.dem.get_value(loc_profile[0].x, loc_profile[0].y) - loc_profile[0].z)
|
740
|
+
diff_right = abs(self.dem.get_value(loc_profile[-1].x, loc_profile[-1].y) - loc_profile[-1].z)
|
741
|
+
|
742
|
+
if diff_left > tolerance or diff_right > tolerance:
|
743
|
+
all_profiles.append({'profile': loc_profile, 'diff_left': diff_left, 'diff_right': diff_right})
|
744
|
+
|
745
|
+
# grouped differences
|
746
|
+
self._groups = []
|
747
|
+
|
748
|
+
all_s = np.array([diff['profile'].s for diff in all_profiles])
|
749
|
+
|
750
|
+
delta_s = all_s[1:] - all_s[:-1]
|
751
|
+
|
752
|
+
# group are defined by a gap greater than distance_threshold
|
753
|
+
group = np.where(delta_s > distance_threshold)[0]
|
754
|
+
# add the last index
|
755
|
+
group = np.append(group, len(all_profiles)-1)
|
756
|
+
|
757
|
+
start = 0
|
758
|
+
for g in group:
|
759
|
+
new_group = all_profiles[start:g+1]
|
760
|
+
self._groups.append(new_group)
|
761
|
+
start = g+1
|
762
|
+
|
763
|
+
self._sort_groups_by_inverse_deltaz()
|
764
|
+
|
765
|
+
logging.info(f"Found {len(self._groups)} groups of differences on the left or right bank.")
|
766
|
+
|
767
|
+
|
768
|
+
@property
|
769
|
+
def count_groups(self) -> int:
|
770
|
+
""" How many groups of differences are there? """
|
771
|
+
return len(self._groups)
|
772
|
+
|
773
|
+
@property
|
774
|
+
def count_differences(self) -> int:
|
775
|
+
""" Count total number of differences. """
|
776
|
+
count = 0
|
777
|
+
for group in self._groups:
|
778
|
+
count += len(group)
|
779
|
+
return count
|
780
|
+
|
781
|
+
def count_groups_greater_than(self, threshold: int) -> int:
|
782
|
+
""" How many groups of differences are greater than a given threshold? """
|
783
|
+
count = 0
|
784
|
+
for group in self._groups:
|
785
|
+
if len(group) > threshold:
|
786
|
+
count += 1
|
787
|
+
return count
|
788
|
+
|
789
|
+
def _diff_to_dict(self):
|
790
|
+
""" Compile dict in list of lists to a single dictionary """
|
791
|
+
|
792
|
+
diff_dict = {}
|
793
|
+
for group in self._groups:
|
794
|
+
for sect in group:
|
795
|
+
prof = sect['profile']
|
796
|
+
# copy sect but exclude profile
|
797
|
+
diff_dict[prof.myname] = {k: v for k, v in sect.items() if k != 'profile'}
|
798
|
+
return diff_dict
|
799
|
+
|
800
|
+
def _diff_to_dataframe(self):
|
801
|
+
""" Compile dict in list of lists to a single pandas DataFrame.
|
802
|
+
|
803
|
+
Dataframe columns: x, y, diff
|
804
|
+
Dataframe index: profile
|
805
|
+
"""
|
806
|
+
|
807
|
+
import pandas as pd
|
808
|
+
|
809
|
+
rows_left = []
|
810
|
+
rows_right = []
|
811
|
+
for i_group, group in enumerate(self._groups):
|
812
|
+
for sect in group:
|
813
|
+
prof = sect['profile']
|
814
|
+
|
815
|
+
diff_left = sect['diff_left']
|
816
|
+
diff_right = sect['diff_right']
|
817
|
+
|
818
|
+
if diff_left > self._threshold_z:
|
819
|
+
left_vert = prof[0]
|
820
|
+
row = {'profile': prof.myname, 'x': round(left_vert.x,2), 'y': round(left_vert.y,2), 'diff': round(diff_left,2), 'group':i_group +1}
|
821
|
+
rows_left.append(row)
|
822
|
+
if diff_right > self._threshold_z:
|
823
|
+
right_vert = prof[-1]
|
824
|
+
row = {'profile': prof.myname, 'x': round(right_vert.x,2), 'y': round(right_vert.y,2), 'diff': round(diff_right,2), 'group':i_group +1}
|
825
|
+
rows_right.append(row)
|
826
|
+
|
827
|
+
# Sort by diff descending
|
828
|
+
rows_left = sorted(rows_left, key=lambda x: x['diff'], reverse=True)
|
829
|
+
rows_right = sorted(rows_right, key=lambda x: x['diff'], reverse=True)
|
830
|
+
|
831
|
+
return pd.DataFrame(rows_left[:min(10, len(rows_left))]), pd.DataFrame(rows_right[:min(10, len(rows_right))])
|
832
|
+
|
833
|
+
@property
|
834
|
+
def _all_XY_diff(self):
|
835
|
+
""" Get all X and Y coordinates of the differences. """
|
836
|
+
all_left = []
|
837
|
+
all_right = []
|
838
|
+
for group in self._groups:
|
839
|
+
for item in group:
|
840
|
+
prof = item["profile"]
|
841
|
+
left_vert = prof[0]
|
842
|
+
right_vert = prof[-1]
|
843
|
+
|
844
|
+
diff_left = item["diff_left"]
|
845
|
+
diff_right = item["diff_right"]
|
846
|
+
if diff_left > self._threshold_z:
|
847
|
+
all_left.append((left_vert.x, left_vert.y, diff_left))
|
848
|
+
if diff_right > self._threshold_z:
|
849
|
+
all_right.append((right_vert.x, right_vert.y, diff_right))
|
850
|
+
|
851
|
+
return all_left, all_right
|
852
|
+
|
853
|
+
@property
|
854
|
+
def _all_differences_as_np(self) -> np.ndarray:
|
855
|
+
""" Get all differences as a single array. """
|
856
|
+
all_diffs = []
|
857
|
+
for group in self._groups:
|
858
|
+
for item in group:
|
859
|
+
diff_left = item["diff_left"]
|
860
|
+
diff_right = item["diff_right"]
|
861
|
+
if diff_left > self._threshold_z:
|
862
|
+
all_diffs.append(item["diff_left"])
|
863
|
+
if diff_right > self._threshold_z:
|
864
|
+
all_diffs.append(item["diff_right"])
|
865
|
+
|
866
|
+
return np.array(all_diffs)
|
867
|
+
|
868
|
+
@property
|
869
|
+
def _all_left_differences_as_np(self) -> np.ndarray:
|
870
|
+
""" Get all left differences as a single array. """
|
871
|
+
all_diffs = []
|
872
|
+
for group in self._groups:
|
873
|
+
for item in group:
|
874
|
+
diff_left = item["diff_left"]
|
875
|
+
if diff_left > self._threshold_z:
|
876
|
+
all_diffs.append(item["diff_left"])
|
877
|
+
|
878
|
+
return np.array(all_diffs)
|
879
|
+
|
880
|
+
@property
|
881
|
+
def _all_right_differences_as_np(self) -> np.ndarray:
|
882
|
+
""" Get all right differences as a single array. """
|
883
|
+
all_diffs = []
|
884
|
+
for group in self._groups:
|
885
|
+
for item in group:
|
886
|
+
diff_right = item["diff_right"]
|
887
|
+
if diff_right > self._threshold_z:
|
888
|
+
all_diffs.append(item["diff_right"])
|
889
|
+
|
890
|
+
return np.array(all_diffs)
|
891
|
+
|
892
|
+
def plot_histogram_differences(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 0.3, **kwargs) -> tuple[plt.Figure, plt.Axes]:
|
893
|
+
"""
|
894
|
+
Plot histogram of all differences.
|
895
|
+
"""
|
896
|
+
if figax is None:
|
897
|
+
figax = plt.subplots(figsize=(8, 3))
|
898
|
+
|
899
|
+
fig, ax = figax
|
900
|
+
|
901
|
+
difference_data = self._all_differences_as_np
|
902
|
+
ax.hist(difference_data, bins= min(100, int(len(difference_data)/4)), density=density, alpha=alpha, **kwargs)
|
903
|
+
|
904
|
+
diff_left = self._all_left_differences_as_np
|
905
|
+
diff_right = self._all_right_differences_as_np
|
906
|
+
ax.hist(diff_left, bins= min(100, int(len(diff_left)/4)), density=density, alpha=alpha, color='green', label='Left bank', **kwargs)
|
907
|
+
ax.hist(diff_right, bins= min(100, int(len(diff_right)/4)), density=density, alpha=alpha, color='orange', label='Right bank', **kwargs)
|
908
|
+
|
909
|
+
# ax.set_xlabel("Value")
|
910
|
+
# ax.set_ylabel("Frequency")
|
911
|
+
|
912
|
+
# set font size of the labels
|
913
|
+
ax.tick_params(axis='both', which='major', labelsize=6)
|
914
|
+
for label in ax.get_xticklabels():
|
915
|
+
label.set_fontsize(self._fontsize)
|
916
|
+
for label in ax.get_yticklabels():
|
917
|
+
label.set_fontsize(self._fontsize)
|
918
|
+
# and gfor the label title
|
919
|
+
ax.set_xlabel(ax.get_xlabel(), fontsize=self._fontsize)
|
920
|
+
ax.set_ylabel(ax.get_ylabel(), fontsize=self._fontsize)
|
921
|
+
|
922
|
+
# Add median and mean lines
|
923
|
+
mean_val = np.mean(difference_data)
|
924
|
+
median_val = np.median(difference_data)
|
925
|
+
ax.axvline(mean_val, color='r', linestyle='dashed', linewidth=1, label=f'Mean: {mean_val:.2f}')
|
926
|
+
ax.axvline(median_val, color='g', linestyle='dashed', linewidth=1, label=f'Median: {median_val:.2f}')
|
927
|
+
|
928
|
+
ax.legend(fontsize=self._fontsize)
|
929
|
+
|
930
|
+
return fig, ax
|
931
|
+
|
932
|
+
def _read_differences_json(self, differences: Path | str) -> list[list[dict['section_id':int, "x": float, "y": float, "diff": float]]]:
|
933
|
+
""" Differences file is a JSON file with the following structure:
|
934
|
+
|
935
|
+
List of lists with: "section_id", "x", "y", "diff".
|
936
|
+
|
937
|
+
List of lists because we want to store groups of cross-sections that are close to each other.
|
938
|
+
"""
|
939
|
+
|
940
|
+
if not Path(differences).exists():
|
941
|
+
logging.error(f"The differences file {differences} does not exist.")
|
942
|
+
return []
|
943
|
+
|
944
|
+
import json
|
945
|
+
with open(differences, 'r', encoding='utf-8') as f:
|
946
|
+
data = json.load(f)
|
947
|
+
|
948
|
+
return data
|
949
|
+
|
950
|
+
def plot_dem_with_background(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
951
|
+
"""
|
952
|
+
Plot the reference array with a background.
|
953
|
+
"""
|
954
|
+
if figax is None:
|
955
|
+
figax = plt.subplots()
|
956
|
+
|
957
|
+
fig, ax = figax
|
958
|
+
|
959
|
+
h = self.dem.get_header()
|
960
|
+
width = h.nbx * h.dx
|
961
|
+
height = h.nby * h.dy
|
962
|
+
h.dx = width
|
963
|
+
h.dy = height
|
964
|
+
h.nbx = 1
|
965
|
+
h.nby = 1
|
966
|
+
|
967
|
+
new = WolfArray(srcheader=h)
|
968
|
+
new.array.mask[:,:] = True
|
969
|
+
|
970
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
971
|
+
|
972
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
973
|
+
first_mask_data=False, with_legend=False,
|
974
|
+
update_palette= False,
|
975
|
+
IGN= True,
|
976
|
+
cat = 'orthoimage_coverage',
|
977
|
+
)
|
978
|
+
elif self._background.upper() == 'WALONMAP':
|
979
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
980
|
+
first_mask_data=False, with_legend=False,
|
981
|
+
update_palette= False,
|
982
|
+
Walonmap= True,
|
983
|
+
cat = 'IMAGERIE/ORTHO_2022_ETE',
|
984
|
+
)
|
985
|
+
else:
|
986
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
987
|
+
first_mask_data=False, with_legend=False,
|
988
|
+
update_palette= False,
|
989
|
+
Walonmap= False,
|
990
|
+
)
|
991
|
+
return fig, ax
|
992
|
+
|
993
|
+
def plot_cartoweb(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
994
|
+
"""
|
995
|
+
Plot the reference array with a background.
|
996
|
+
"""
|
997
|
+
if figax is None:
|
998
|
+
figax = plt.subplots()
|
999
|
+
|
1000
|
+
fig, ax = figax
|
1001
|
+
|
1002
|
+
h = self.dem.get_header()
|
1003
|
+
width = h.nbx * h.dx
|
1004
|
+
height = h.nby * h.dy
|
1005
|
+
h.dx = width
|
1006
|
+
h.dy = height
|
1007
|
+
h.nbx = 1
|
1008
|
+
h.nby = 1
|
1009
|
+
|
1010
|
+
new = WolfArray(srcheader=h)
|
1011
|
+
new.array.mask[:,:] = True
|
1012
|
+
|
1013
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
1014
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
1015
|
+
first_mask_data=False, with_legend=False,
|
1016
|
+
update_palette= False,
|
1017
|
+
Cartoweb= True,
|
1018
|
+
cat = 'overlay',
|
1019
|
+
)
|
1020
|
+
else:
|
1021
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
1022
|
+
first_mask_data=False, with_legend=False,
|
1023
|
+
update_palette= False,
|
1024
|
+
Cartoweb= False,
|
1025
|
+
cat = 'overlay',
|
1026
|
+
)
|
1027
|
+
return fig, ax
|
1028
|
+
|
1029
|
+
def plot_background_grey(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
1030
|
+
"""
|
1031
|
+
Plot the reference array with a background.
|
1032
|
+
"""
|
1033
|
+
if figax is None:
|
1034
|
+
figax = plt.subplots()
|
1035
|
+
|
1036
|
+
fig, ax = figax
|
1037
|
+
|
1038
|
+
h = self.dem.get_header()
|
1039
|
+
width = h.nbx * h.dx
|
1040
|
+
height = h.nby * h.dy
|
1041
|
+
h.dx = width
|
1042
|
+
h.dy = height
|
1043
|
+
h.nbx = 1
|
1044
|
+
h.nby = 1
|
1045
|
+
|
1046
|
+
new = WolfArray(srcheader=h)
|
1047
|
+
new.array.mask[:,:] = True
|
1048
|
+
|
1049
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
1050
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
1051
|
+
first_mask_data=False, with_legend=False,
|
1052
|
+
update_palette= False,
|
1053
|
+
Cartoweb= True,
|
1054
|
+
cat = 'topo_grey',
|
1055
|
+
)
|
1056
|
+
else:
|
1057
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
1058
|
+
first_mask_data=False, with_legend=False,
|
1059
|
+
update_palette= False,
|
1060
|
+
Cartoweb= False,
|
1061
|
+
cat = 'topo_grey',
|
1062
|
+
)
|
1063
|
+
return fig, ax
|
1064
|
+
|
1065
|
+
def plot_dem(self, figax:tuple[plt.Figure, plt.Axes]=None, use_rebin_if_exists:bool=True) -> tuple[plt.Figure, plt.Axes]:
|
1066
|
+
"""
|
1067
|
+
Plot the reference array.
|
1068
|
+
"""
|
1069
|
+
if figax is None:
|
1070
|
+
figax = plt.subplots()
|
1071
|
+
|
1072
|
+
fig, ax = figax
|
1073
|
+
|
1074
|
+
if use_rebin_if_exists and self._rebinned_dem is not None:
|
1075
|
+
self._rebinned_dem.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
1076
|
+
else:
|
1077
|
+
self.dem.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
1078
|
+
|
1079
|
+
return fig, ax
|
1080
|
+
|
1081
|
+
def plot_XY(self, figax:tuple[plt.Figure, plt.Axes]=None,
|
1082
|
+
s:float=10, alpha:float=0.5,
|
1083
|
+
colorized_diff:bool=False,
|
1084
|
+
default_color=('blue', 'red'),
|
1085
|
+
which_ones:Literal['left', 'right', 'all'] = 'all') -> tuple[plt.Figure, plt.Axes]:
|
1086
|
+
"""
|
1087
|
+
Plot the XY of the differences.
|
1088
|
+
|
1089
|
+
:param figax: Tuple of (Figure, Axes) to plot on. If None, a new figure and axes will be created.
|
1090
|
+
:param s: Size of the points.
|
1091
|
+
:param alpha: Alpha of the points.
|
1092
|
+
:param colorized_diff: If True, the points will be colored based on the difference value.
|
1093
|
+
:param default_color: If colorized_diff is False, the points will be colored with these colors for left and right.
|
1094
|
+
:param which_ones: Which points to plot. Can be 'left', 'right', or 'all'.
|
1095
|
+
"""
|
1096
|
+
|
1097
|
+
if figax is None:
|
1098
|
+
figax = plt.subplots()
|
1099
|
+
|
1100
|
+
fig, ax = figax
|
1101
|
+
|
1102
|
+
lefts, rights = self._all_XY_diff
|
1103
|
+
|
1104
|
+
if colorized_diff:
|
1105
|
+
if which_ones == 'left' or which_ones == 'all':
|
1106
|
+
x,y,diff = zip(*lefts)
|
1107
|
+
ax.scatter(x, y, c=diff, cmap='bwr', s=s, alpha=alpha)
|
1108
|
+
|
1109
|
+
if which_ones == 'right' or which_ones == 'all':
|
1110
|
+
x,y,diff = zip(*rights)
|
1111
|
+
ax.scatter(x, y, c=diff, cmap='bwr', s=s, alpha=alpha)
|
1112
|
+
elif default_color is not None:
|
1113
|
+
left_color, right_color = default_color
|
1114
|
+
|
1115
|
+
if which_ones == 'left' or which_ones == 'all':
|
1116
|
+
x,y,_ = zip(*lefts)
|
1117
|
+
ax.scatter(x, y, color=left_color, s=s, alpha=alpha)
|
1118
|
+
if which_ones == 'right' or which_ones == 'all':
|
1119
|
+
x,y,_ = zip(*rights)
|
1120
|
+
ax.scatter(x, y, color=right_color, s=s, alpha=alpha)
|
1121
|
+
else:
|
1122
|
+
if which_ones == 'left' or which_ones == 'all':
|
1123
|
+
x,y,_ = zip(*lefts)
|
1124
|
+
ax.scatter(x, y, color='blue', s=s, alpha=alpha)
|
1125
|
+
if which_ones == 'right' or which_ones == 'all':
|
1126
|
+
x,y,_ = zip(*rights)
|
1127
|
+
ax.scatter(x, y, color='red', s=s, alpha=alpha)
|
1128
|
+
|
1129
|
+
ax.set_aspect('equal', 'box')
|
1130
|
+
fig.tight_layout()
|
1131
|
+
|
1132
|
+
return fig, ax
|
1133
|
+
|
1134
|
+
def plot_mainpage_map(self) -> tuple[plt.Figure, plt.Axes]:
|
1135
|
+
""" Plot the main page map with all differences. """
|
1136
|
+
|
1137
|
+
fig, ax = self.plot_background_grey()
|
1138
|
+
|
1139
|
+
self.plot_XY(figax=(fig, ax), colorized_diff=True)
|
1140
|
+
|
1141
|
+
return fig, ax
|
1142
|
+
|
1143
|
+
def _summary_dem(self) -> list:
|
1144
|
+
""" Summary of the DEM. """
|
1145
|
+
|
1146
|
+
return [
|
1147
|
+
f"Number of cells: {self.dem.nbnotnull}",
|
1148
|
+
f"Resolution (m): {self.dem.dx} x {self.dem.dy}",
|
1149
|
+
f"Extent: ({self.dem.origx}, {self.dem.origy}) - ({self.dem.origx + self.dem.nbx * self.dem.dx}, {self.dem.origy + self.dem.nby * self.dem.dy})",
|
1150
|
+
f"Width x Height (m): {self.dem.nbx * self.dem.dx} x {self.dem.nby * self.dem.dy}",
|
1151
|
+
]
|
1152
|
+
|
1153
|
+
def _summary_differences(self) -> list:
|
1154
|
+
""" Summary of the differences. """
|
1155
|
+
|
1156
|
+
all_diff = self._all_differences_as_np
|
1157
|
+
all_diff_left = self._all_left_differences_as_np
|
1158
|
+
all_diff_right = self._all_right_differences_as_np
|
1159
|
+
return [
|
1160
|
+
f"Number of groups: {self.count_groups}",
|
1161
|
+
f"Number of cross-sections: {self.count_differences}",
|
1162
|
+
f"Median difference (m): {np.median(all_diff):.3f}",
|
1163
|
+
f"Min difference (m): {np.min(all_diff):.3f}",
|
1164
|
+
f"Max difference (m): {np.max(all_diff):.3f}",
|
1165
|
+
f"Left - Median difference (m): {np.median(all_diff_left):.3f}",
|
1166
|
+
f"Left - Min difference (m): {np.min(all_diff_left):.3f}",
|
1167
|
+
f"Left - Max difference (m): {np.max(all_diff_left):.3f}",
|
1168
|
+
f"Right - Median difference (m): {np.median(all_diff_right):.3f}",
|
1169
|
+
f"Right - Min difference (m): {np.min(all_diff_right):.3f}",
|
1170
|
+
f"Right - Max difference (m): {np.max(all_diff_right):.3f}",
|
1171
|
+
]
|
1172
|
+
|
1173
|
+
def __str__(self):
|
1174
|
+
|
1175
|
+
|
1176
|
+
ret = [_("Array information :")]
|
1177
|
+
ret.extend(self._summary_dem())
|
1178
|
+
ret.append("")
|
1179
|
+
ret.append(_("Differences information :"))
|
1180
|
+
ret.extend(self._summary_differences())
|
1181
|
+
|
1182
|
+
return "\n".join(ret)
|
1183
|
+
|
1184
|
+
def get_group_info(self, i_group:int) -> str:
|
1185
|
+
""" Get information about a specific group of differences. """
|
1186
|
+
if i_group < 0 or i_group >= self.count_groups:
|
1187
|
+
raise IndexError(f"Group index {i_group} out of range. There are {self.count_groups} groups.")
|
1188
|
+
|
1189
|
+
group = self._groups[i_group]
|
1190
|
+
|
1191
|
+
ret = [f"Group {i_group + 1} - Number of cross-sections: {len(group)}"]
|
1192
|
+
for sect in group:
|
1193
|
+
prof = sect['profile']
|
1194
|
+
diff_left = sect['diff_left']
|
1195
|
+
diff_right = sect['diff_right']
|
1196
|
+
ret.append(f" - Cross-section {prof.myname}: Left difference = {diff_left:.3f} m, Right difference = {diff_right:.3f} m")
|
1197
|
+
|
1198
|
+
return "\n".join(ret)
|
1199
|
+
|
1200
|
+
def print_left_differences(self) -> str:
|
1201
|
+
""" Print all left differences. """
|
1202
|
+
|
1203
|
+
ret = [_("Left bank differences:")]
|
1204
|
+
|
1205
|
+
# Sort differences
|
1206
|
+
left_diffs = []
|
1207
|
+
for group in self._groups:
|
1208
|
+
for sect in group:
|
1209
|
+
prof = sect['profile']
|
1210
|
+
diff_left = sect['diff_left']
|
1211
|
+
if diff_left > self._threshold_z:
|
1212
|
+
left_diffs.append((prof.myname, diff_left))
|
1213
|
+
left_diffs.sort(key=lambda x: x[1], reverse=True)
|
1214
|
+
for name, diff in left_diffs:
|
1215
|
+
ret.append(f" - Cross-section {name}: {diff:.3f} m")
|
1216
|
+
|
1217
|
+
return "\n".join(ret)
|
1218
|
+
|
1219
|
+
def print_right_differences(self) -> str:
|
1220
|
+
""" Print all right differences. """
|
1221
|
+
|
1222
|
+
ret = [_("Right bank differences:")]
|
1223
|
+
|
1224
|
+
# Sort differences
|
1225
|
+
right_diffs = []
|
1226
|
+
for group in self._groups:
|
1227
|
+
for sect in group:
|
1228
|
+
prof = sect['profile']
|
1229
|
+
diff_right = sect['diff_right']
|
1230
|
+
if diff_right > self._threshold_z:
|
1231
|
+
right_diffs.append((prof.myname, diff_right))
|
1232
|
+
right_diffs.sort(key=lambda x: x[1], reverse=True)
|
1233
|
+
for name, diff in right_diffs:
|
1234
|
+
ret.append(f" - Cross-section {name}: {diff:.3f} m")
|
1235
|
+
|
1236
|
+
return "\n".join(ret)
|
1237
|
+
|
1238
|
+
def _complete_report_mainpage(self, page:CSvsDEM_MainLayout):
|
1239
|
+
""" Complete the report with the global summary and individual differences. """
|
1240
|
+
|
1241
|
+
key_fig = [('Map', self.plot_mainpage_map),
|
1242
|
+
('Histogram', self.plot_histogram_differences),
|
1243
|
+
]
|
1244
|
+
|
1245
|
+
key_list = [('Summary_0-0', self._summary_dem),
|
1246
|
+
('Summary_0-1', self._summary_differences),
|
1247
|
+
]
|
1248
|
+
|
1249
|
+
df_left, df_right = self._diff_to_dataframe()
|
1250
|
+
key_table = [('Tables_0-0', df_left),
|
1251
|
+
('Tables_1-0', df_right),
|
1252
|
+
]
|
1253
|
+
|
1254
|
+
keys = page.keys
|
1255
|
+
for key, fig_routine in key_fig:
|
1256
|
+
if key in keys:
|
1257
|
+
|
1258
|
+
rect = page.layout[key]
|
1259
|
+
|
1260
|
+
fig, ax = fig_routine()
|
1261
|
+
|
1262
|
+
# set size to fit the rectangle
|
1263
|
+
fig.set_size_inches(pt2inches(rect.width), pt2inches(rect.height))
|
1264
|
+
|
1265
|
+
# convert canvas to PNG and insert it into the PDF
|
1266
|
+
temp_file = NamedTemporaryFile(delete=False, suffix='.png')
|
1267
|
+
fig.savefig(temp_file, format='png', bbox_inches='tight', dpi=self._dpi)
|
1268
|
+
page._page.insert_image(page.layout[key], filename=temp_file.name)
|
1269
|
+
# delete the temporary file
|
1270
|
+
temp_file.delete = True
|
1271
|
+
temp_file.close()
|
1272
|
+
|
1273
|
+
# Force to delete fig
|
1274
|
+
plt.close(fig)
|
1275
|
+
else:
|
1276
|
+
logging.warning(f"Key {key} not found in layout. Skipping plot.")
|
1277
|
+
|
1278
|
+
for key, txt_routine in key_list:
|
1279
|
+
if key in keys:
|
1280
|
+
rect = page.layout[key]
|
1281
|
+
text, css = list_to_html(txt_routine(), font_size='8pt')
|
1282
|
+
page._page.insert_htmlbox(rect, text, css=css)
|
1283
|
+
else:
|
1284
|
+
logging.warning(f"Key {key} not found in layout. Skipping text.")
|
1285
|
+
|
1286
|
+
for key, df in key_table:
|
1287
|
+
if key in keys:
|
1288
|
+
rect = page.layout[key]
|
1289
|
+
text, css = dataframe_to_html(df, font_size='8pt')
|
1290
|
+
page._page.insert_htmlbox(rect, text, css=css)
|
1291
|
+
else:
|
1292
|
+
logging.warning(f"Key {key} not found in layout. Skipping text.")
|
1293
|
+
|
1294
|
+
def _sort_groups_by_inverse_deltaz(self):
|
1295
|
+
""" Sort the groups by the maximum difference in descending order. """
|
1296
|
+
self._groups.sort(key=lambda group: max(max(item['diff_left'], item['diff_right']) for item in group), reverse=True)
|
1297
|
+
|
1298
|
+
def _create_subpages(self):
|
1299
|
+
""" Complete the report with the individual sections. """
|
1300
|
+
|
1301
|
+
for i_group, group in enumerate(self._groups):
|
1302
|
+
for i_sect, sect in enumerate(group):
|
1303
|
+
self.subpages[(i_group, i_sect)] = CSvsDEM(group,
|
1304
|
+
i_sect,
|
1305
|
+
self.dem,
|
1306
|
+
title = _('Group n° {} - Section n° {}').format(i_group+1, sect['profile'].myname),
|
1307
|
+
index_group= i_group + 1,
|
1308
|
+
index_cs = i_sect + 1,
|
1309
|
+
rebinned_dem=self._rebinned_dem)
|
1310
|
+
|
1311
|
+
def create_report(self, output_file: str | Path = None,
|
1312
|
+
append_subpages: bool = True,
|
1313
|
+
nb_max_pages:int = -1) -> None:
|
1314
|
+
""" Create a page report for the array comparison. """
|
1315
|
+
|
1316
|
+
if output_file is None:
|
1317
|
+
output_file = Path(f"compare_cs_dem_report.pdf")
|
1318
|
+
|
1319
|
+
if output_file.exists():
|
1320
|
+
logging.warning(f"Output file {output_file} already exists. It will be overwritten.")
|
1321
|
+
|
1322
|
+
mainpage = CSvsDEM_MainLayout(_("Comparison Report - Cross-Sections vs DEM"))
|
1323
|
+
mainpage.create_report()
|
1324
|
+
self._complete_report_mainpage(mainpage)
|
1325
|
+
|
1326
|
+
if append_subpages:
|
1327
|
+
|
1328
|
+
if nb_max_pages < 0:
|
1329
|
+
nb_max_pages = self.count_differences
|
1330
|
+
elif nb_max_pages > self.count_differences:
|
1331
|
+
logging.warning(f"Requested {nb_max_pages} differences, but only {self.count_differences} are available. Using all available differences.")
|
1332
|
+
elif nb_max_pages < self.count_differences:
|
1333
|
+
logging.info(f"Limiting to {nb_max_pages} differences.")
|
1334
|
+
|
1335
|
+
# features_to_treat = [feature for feature in self._groups[:nb_max_pages]]
|
1336
|
+
|
1337
|
+
|
1338
|
+
self._create_subpages()
|
1339
|
+
|
1340
|
+
with TemporaryDirectory() as temp_dir:
|
1341
|
+
|
1342
|
+
all_pdfs = []
|
1343
|
+
|
1344
|
+
nbpages = 0
|
1345
|
+
for idx, onepage in tqdm(self.subpages.items(), desc="Preparing individual difference reports", total=nb_max_pages):
|
1346
|
+
if nbpages >= nb_max_pages:
|
1347
|
+
break
|
1348
|
+
|
1349
|
+
all_pdfs.extend([onepage.create_report(Path(temp_dir) / f"temp_report_{idx[0]}_{idx[1]}.pdf")])
|
1350
|
+
nbpages += 1
|
1351
|
+
|
1352
|
+
for pdf_file in tqdm(all_pdfs, desc="Compiling PDFs"):
|
1353
|
+
mainpage._doc.insert_file(pdf_file)
|
1354
|
+
|
1355
|
+
# create a TOC
|
1356
|
+
mainpage._doc.set_toc(mainpage._doc.get_toc())
|
1357
|
+
|
1358
|
+
mainpage.save_report(output_file)
|
1359
|
+
self._pdf_path = output_file
|
1360
|
+
|
1361
|
+
@property
|
1362
|
+
def pdf_path(self) -> Path:
|
1363
|
+
""" Return the path to the generated PDF report. """
|
1364
|
+
if hasattr(self, '_pdf_path'):
|
1365
|
+
return self._pdf_path
|
1366
|
+
else:
|
1367
|
+
raise AttributeError("PDF path not set. Please create the report first.")
|
1368
|
+
|
1369
|
+
def __getitem__(self, index: int | tuple):
|
1370
|
+
""" Get the group or a specific difference.
|
1371
|
+
"""
|
1372
|
+
|
1373
|
+
if isinstance(index, tuple):
|
1374
|
+
if len(index) != 2:
|
1375
|
+
raise IndexError("Tuple index must have exactly two elements: (group_index, difference_index).")
|
1376
|
+
group_index, diff_index = index
|
1377
|
+
if group_index < 0 or group_index >= self.count_groups:
|
1378
|
+
raise IndexError("Group index out of range.")
|
1379
|
+
group = self._groups[group_index]
|
1380
|
+
if diff_index < 0 or diff_index >= len(group):
|
1381
|
+
raise IndexError("Difference index out of range.")
|
1382
|
+
return group[diff_index]
|
1383
|
+
else:
|
1384
|
+
if index < 0 or index >= self.count_groups:
|
1385
|
+
raise IndexError("Group index out of range.")
|
1386
|
+
return self._groups[index]
|
1387
|
+
|
1388
|
+
class CompareCSvsDEM_wx(PDFViewer):
|
1389
|
+
|
1390
|
+
def __init__(self, cross_sections: crosssections | Path | str,
|
1391
|
+
dem: WolfArray | str | Path,
|
1392
|
+
laz_directory: Path | str = None,
|
1393
|
+
support: Path | str | vector = None,
|
1394
|
+
threshold_z: float = 0.5,
|
1395
|
+
distance_threshold: float = 50.0,
|
1396
|
+
dpi:int=150,
|
1397
|
+
nb_max_groups:int=-1,
|
1398
|
+
**kwargs):
|
1399
|
+
""" Initialize the Report Viewer for comparison """
|
1400
|
+
|
1401
|
+
super(CompareCSvsDEM_wx, self).__init__(None, **kwargs)
|
1402
|
+
|
1403
|
+
use('agg')
|
1404
|
+
|
1405
|
+
if isinstance(cross_sections, (str, Path)):
|
1406
|
+
if not Path(cross_sections).exists():
|
1407
|
+
logging.error(f"The cross-sections file {cross_sections} does not exist.")
|
1408
|
+
dlg = wx.MessageDialog(self,
|
1409
|
+
_("The cross-sections file does not exist."),
|
1410
|
+
_("Warning"),
|
1411
|
+
wx.OK | wx.ICON_WARNING)
|
1412
|
+
dlg.ShowModal()
|
1413
|
+
dlg.Destroy()
|
1414
|
+
return
|
1415
|
+
|
1416
|
+
self._report = CompareMultipleCSvsDEM(cross_sections=cross_sections,
|
1417
|
+
dem=dem,
|
1418
|
+
laz_directory=laz_directory,
|
1419
|
+
support=support,
|
1420
|
+
threshold_z=threshold_z,
|
1421
|
+
distance_threshold=distance_threshold)
|
1422
|
+
|
1423
|
+
pgbar = wx.ProgressDialog(_("Generating Report"),
|
1424
|
+
_("Please wait while the report is being generated..."),
|
1425
|
+
maximum=100,
|
1426
|
+
parent=self,
|
1427
|
+
style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME)
|
1428
|
+
|
1429
|
+
self._report._dpi = dpi
|
1430
|
+
self._report.create_report(nb_max_pages = nb_max_groups)
|
1431
|
+
|
1432
|
+
pgbar.Update(100, _("Report generation completed."))
|
1433
|
+
pgbar.Destroy()
|
1434
|
+
|
1435
|
+
# Load the PDF into the viewer
|
1436
|
+
if self._report.pdf_path is None:
|
1437
|
+
logging.error("No report created. Cannot load PDF.")
|
1438
|
+
return
|
1439
|
+
|
1440
|
+
self.load_pdf(self._report.pdf_path)
|
1441
|
+
self.viewer.SetZoom(-1) # Fit to width
|
1442
|
+
|
1443
|
+
# Set the title of the frame
|
1444
|
+
self.SetTitle("Comparison of cross-sections and DEM - Report")
|
1445
|
+
|
1446
|
+
self.Bind(wx.EVT_CLOSE, self.on_close)
|
1447
|
+
|
1448
|
+
use('wxagg')
|
1449
|
+
|
1450
|
+
def on_close(self, event):
|
1451
|
+
""" Handle the close event to clean up resources """
|
1452
|
+
self.viewer.pdfdoc.pdfdoc.close()
|
1453
|
+
self.Destroy()
|