wolfhece 2.2.27__py3-none-any.whl → 2.2.29__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/PyConfig.py +27 -3
- wolfhece/PyDraw.py +202 -22
- wolfhece/PyPalette.py +18 -0
- wolfhece/PyVertexvectors.py +156 -25
- wolfhece/PyWMS.py +6 -3
- wolfhece/__init__.py +27 -0
- wolfhece/acceptability/acceptability.py +25 -20
- wolfhece/acceptability/acceptability_gui.py +150 -92
- wolfhece/acceptability/func.py +169 -82
- wolfhece/apps/version.py +1 -1
- wolfhece/irm_qdf.py +71 -7
- wolfhece/lb7208_ntv2/__init__.py +0 -0
- wolfhece/lb7208_ntv2/be_ign_README.txt +36 -0
- wolfhece/lb7208_ntv2/be_ign_bd72lb72_etrs89lb08.tif +0 -0
- wolfhece/lb7208_ntv2/be_ign_hBG18.tif +0 -0
- wolfhece/mesh2d/gpu_2d.py +11 -2
- wolfhece/report/common.py +496 -0
- wolfhece/report/compare_arrays.py +1054 -0
- wolfhece/report/simplesimgpu.py +26 -101
- wolfhece/report/tools.py +121 -16
- wolfhece/scenario/config_manager.py +265 -8
- wolfhece/ui/wolf_multiselection_collapsiblepane.py +153 -1
- wolfhece/wolf_array.py +116 -64
- wolfhece/wolf_texture.py +4 -0
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.29.dist-info}/METADATA +1 -1
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.29.dist-info}/RECORD +29 -23
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.29.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.29.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.29.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1054 @@
|
|
1
|
+
import logging
|
2
|
+
import numpy as np
|
3
|
+
import numpy.ma as ma
|
4
|
+
from pathlib import Path
|
5
|
+
import matplotlib.pyplot as plt
|
6
|
+
from enum import Enum
|
7
|
+
from scipy.ndimage import label, sum_labels, find_objects
|
8
|
+
import pymupdf as pdf
|
9
|
+
import wx
|
10
|
+
from tqdm import tqdm
|
11
|
+
from matplotlib import use, get_backend
|
12
|
+
|
13
|
+
from .common import A4_rect, rect_cm, list_to_html, list_to_html_aligned, get_rect_from_text
|
14
|
+
from .common import inches2cm, pts2cm, cm2pts, cm2inches, DefaultLayoutA4, NamedTemporaryFile, pt2inches, TemporaryDirectory
|
15
|
+
from ..wolf_array import WolfArray, header_wolf, vector, zone, Zones, wolfvertex as wv, wolfpalette
|
16
|
+
from ..PyTranslate import _
|
17
|
+
from .pdf import PDFViewer
|
18
|
+
|
19
|
+
class ArrayDifferenceLayout(DefaultLayoutA4):
|
20
|
+
"""
|
21
|
+
Layout for comparing two arrays in a report.
|
22
|
+
|
23
|
+
1 cadre pour la zone traitée avec photo de fond ign + contour vectoriel
|
24
|
+
1 cadre avec zoom plus large min 250m
|
25
|
+
1 cadre avec matrice ref + contour vectoriel
|
26
|
+
1 cadre avec matrice à comparer + contour vectoriel
|
27
|
+
1 cadre avec différence
|
28
|
+
1 cadre avec valeurs de synthèse
|
29
|
+
|
30
|
+
1 cadre avec histogramme
|
31
|
+
1 cadre avec histogramme des différences
|
32
|
+
"""
|
33
|
+
|
34
|
+
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):
|
35
|
+
super().__init__(title, filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx, bbox, find_minmax, shared, colors)
|
36
|
+
|
37
|
+
useful = self.useful_part
|
38
|
+
|
39
|
+
width = useful.xmax - useful.xmin
|
40
|
+
height = useful.ymax - useful.ymin
|
41
|
+
|
42
|
+
self._hitograms = self.add_element_repeated(_("Histogram"), width=width, height=2.5,
|
43
|
+
first_x=useful.xmin, first_y=useful.ymax,
|
44
|
+
count_x=1, count_y=-2, padding=0.5)
|
45
|
+
|
46
|
+
self._arrays = self.add_element_repeated(_("Arrays"), width= (width-self.padding) / 2, height=5.5,
|
47
|
+
first_x=useful.xmin, first_y=self._hitograms.ymin - self.padding,
|
48
|
+
count_x=2, count_y=-3, padding=0.5)
|
49
|
+
|
50
|
+
class CompareArraysLayout(DefaultLayoutA4):
|
51
|
+
|
52
|
+
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):
|
53
|
+
super().__init__(title, filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx, bbox, find_minmax, shared, colors)
|
54
|
+
|
55
|
+
useful = self.useful_part
|
56
|
+
|
57
|
+
width = useful.xmax - useful.xmin
|
58
|
+
height = useful.ymax - useful.ymin
|
59
|
+
|
60
|
+
self._summary = self.add_element_repeated(_("Summary"), width=(width-self.padding) / 2, height=3, first_x=useful.xmin, first_y=useful.ymax-3, count_x=2, count_y=1)
|
61
|
+
|
62
|
+
self._arrays = self.add_element_repeated(_("Arrays"), width= (width-self.padding) / 2, height=9., count_x=2, count_y=1, first_x=useful.xmin, first_y=14)
|
63
|
+
self._diff_rect = self.add_element(_("Difference"), width= width, height=11.5, x=useful.xmin, y=useful.ymin)
|
64
|
+
|
65
|
+
|
66
|
+
class CompareArraysLayout2(DefaultLayoutA4):
|
67
|
+
|
68
|
+
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):
|
69
|
+
super().__init__(title, filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx, bbox, find_minmax, shared, colors)
|
70
|
+
|
71
|
+
useful = self.useful_part
|
72
|
+
|
73
|
+
width = useful.xmax - useful.xmin
|
74
|
+
height = useful.ymax - useful.ymin
|
75
|
+
|
76
|
+
self._summary = self.add_element_repeated(_("Histogram"), width=(width-self.padding) / 2, height=6., first_x=useful.xmin, first_y=useful.ymax-6, count_x=2, count_y=1)
|
77
|
+
|
78
|
+
self._arrays = self.add_element_repeated(_("Arrays"), width= (width-self.padding) / 2, height=6., count_x=2, count_y=1, first_x=useful.xmin, first_y=14)
|
79
|
+
self._diff_rect = self.add_element(_("Position"), width= width, height=11.5, x=useful.xmin, y=useful.ymin)
|
80
|
+
|
81
|
+
|
82
|
+
class ArrayDifference():
|
83
|
+
"""
|
84
|
+
Class to manage the difference between two WolfArray objects.
|
85
|
+
"""
|
86
|
+
|
87
|
+
def __init__(self, reference:WolfArray, to_compare:WolfArray, index:int, label:np.ndarray):
|
88
|
+
|
89
|
+
self._dpi = 600
|
90
|
+
self.default_size_hitograms = (12, 6)
|
91
|
+
self.default_size_arrays = (10, 10)
|
92
|
+
self._fontsize = 6
|
93
|
+
|
94
|
+
self.reference = reference
|
95
|
+
self.to_compare = to_compare
|
96
|
+
|
97
|
+
self.reference.updatepalette()
|
98
|
+
self.to_compare.mypal = self.reference.mypal
|
99
|
+
|
100
|
+
self.index = index
|
101
|
+
self.label = label
|
102
|
+
|
103
|
+
self._background = 'IGN'
|
104
|
+
|
105
|
+
self._contour = None
|
106
|
+
self._external_border = None
|
107
|
+
|
108
|
+
@property
|
109
|
+
def contour(self) -> vector:
|
110
|
+
""" Get the contour of the difference part. """
|
111
|
+
|
112
|
+
if self._contour is not None and isinstance(self._contour, vector):
|
113
|
+
return self._contour
|
114
|
+
|
115
|
+
ret = self.reference.suxsuy_contour(abs=True)
|
116
|
+
ret = ret[2]
|
117
|
+
|
118
|
+
ret.myprop.color = (0, 0, 255)
|
119
|
+
ret.myprop.width = 2
|
120
|
+
|
121
|
+
return ret
|
122
|
+
|
123
|
+
@property
|
124
|
+
def external_border(self) -> vector:
|
125
|
+
"""
|
126
|
+
Get the bounds of the difference part.
|
127
|
+
"""
|
128
|
+
if self._external_border is not None and isinstance(self._external_border, vector):
|
129
|
+
return self._external_border
|
130
|
+
|
131
|
+
ret = vector(name=_("External border"))
|
132
|
+
(xmin, xmax), (ymin, ymax) = self.reference.get_bounds()
|
133
|
+
ret.add_vertex(wv(xmin, ymin))
|
134
|
+
ret.add_vertex(wv(xmax, ymin))
|
135
|
+
ret.add_vertex(wv(xmax, ymax))
|
136
|
+
ret.add_vertex(wv(xmin, ymax))
|
137
|
+
ret.force_to_close()
|
138
|
+
|
139
|
+
ret.myprop.color = (255, 0, 0)
|
140
|
+
ret.myprop.width = 3
|
141
|
+
|
142
|
+
return ret
|
143
|
+
|
144
|
+
def __str__(self):
|
145
|
+
|
146
|
+
assert self.reference.nbnotnull == self.to_compare.nbnotnull, "The number of non-null cells in both arrays must be the same."
|
147
|
+
|
148
|
+
ret = self.reference.__str__() + '\n'
|
149
|
+
|
150
|
+
ret += _("Index : ") + str(self.index) + '\n'
|
151
|
+
ret += _("Number of cells : ") + str(self.reference.nbnotnull) + '\n'
|
152
|
+
|
153
|
+
return ret
|
154
|
+
|
155
|
+
@property
|
156
|
+
def _summary_text(self):
|
157
|
+
"""
|
158
|
+
Generate a summary text for the report.
|
159
|
+
"""
|
160
|
+
diff = self.difference.array.compressed()
|
161
|
+
text = [
|
162
|
+
_("Index: ") + str(self.index),
|
163
|
+
_("Number of cells: ") + str(self.reference.nbnotnull),
|
164
|
+
_('Resolution: ') + f"{self.reference.dx} m x {self.reference.dy} m",
|
165
|
+
_('Extent: ') + f"({self.reference.origx}, {self.reference.origy})" + f" - ({self.reference.origx + self.reference.nbx * self.reference.dx}, {self.reference.origy + self.reference.nby * self.reference.dy})",
|
166
|
+
_('Width x Height: ') + f"{self.reference.nbx * self.reference.dx} m x {self.reference.nby * self.reference.dy} m",
|
167
|
+
_('Excavation: ') + f"{np.sum(diff[diff < 0.]) * self.reference.dx * self.reference.dy:.3f} m³",
|
168
|
+
_('Deposit/Backfill: ') + f"{np.sum(diff[diff > 0.]) * self.reference.dx * self.reference.dy:.3f} m³",
|
169
|
+
_('Net volume: ') + f"{np.sum(diff) * self.reference.dx * self.reference.dy:.3f} m³",
|
170
|
+
]
|
171
|
+
return text
|
172
|
+
|
173
|
+
def set_palette_distribute(self, minval:float, maxval:float, step:int=0):
|
174
|
+
"""
|
175
|
+
Set the palette for both arrays.
|
176
|
+
"""
|
177
|
+
self.reference.mypal.distribute_values(minval, maxval, step)
|
178
|
+
|
179
|
+
def set_palette(self, values:list[float], colors:list[tuple[int, int, int]]):
|
180
|
+
"""
|
181
|
+
Set the palette for both arrays based on specific values.
|
182
|
+
"""
|
183
|
+
self.reference.mypal.set_values_colors(values, colors)
|
184
|
+
|
185
|
+
def plot_position(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
186
|
+
"""
|
187
|
+
Plot the reference array.
|
188
|
+
"""
|
189
|
+
if figax is None:
|
190
|
+
figax = plt.subplots()
|
191
|
+
|
192
|
+
fig, ax = figax
|
193
|
+
|
194
|
+
old_mask = self.reference.array.mask.copy()
|
195
|
+
self.reference.array.mask[:,:] = True
|
196
|
+
|
197
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
198
|
+
self.reference.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
199
|
+
first_mask_data=False, with_legend=False,
|
200
|
+
update_palette= False,
|
201
|
+
IGN= True,
|
202
|
+
cat = 'orthoimage_coverage',
|
203
|
+
)
|
204
|
+
|
205
|
+
elif self._background.upper() == 'WALONMAP':
|
206
|
+
self.reference.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
207
|
+
first_mask_data=False, with_legend=False,
|
208
|
+
update_palette= False,
|
209
|
+
Walonmap= True,
|
210
|
+
cat = 'IMAGERIE/ORTHO_2022_ETE',
|
211
|
+
)
|
212
|
+
else:
|
213
|
+
self.reference.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
214
|
+
first_mask_data=False, with_legend=False,
|
215
|
+
update_palette= False,
|
216
|
+
Walonmap= False,
|
217
|
+
)
|
218
|
+
|
219
|
+
|
220
|
+
self.reference.array.mask[:,:] = old_mask
|
221
|
+
|
222
|
+
self.external_border.plot_matplotlib(ax=ax)
|
223
|
+
self.contour.plot_matplotlib(ax=ax)
|
224
|
+
|
225
|
+
return fig, ax
|
226
|
+
|
227
|
+
def plot_position_scaled(self, scale = 4, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
228
|
+
"""
|
229
|
+
Plot the reference array.
|
230
|
+
"""
|
231
|
+
if figax is None:
|
232
|
+
figax = plt.subplots()
|
233
|
+
|
234
|
+
fig, ax = figax
|
235
|
+
|
236
|
+
h = self.reference.get_header()
|
237
|
+
width = h.nbx * h.dx
|
238
|
+
height = h.nby * h.dy
|
239
|
+
|
240
|
+
h.origx += -width * scale / 2
|
241
|
+
h.origy += -height *scale / 2
|
242
|
+
h.nbx = 1
|
243
|
+
h.nby = 1
|
244
|
+
h.dx = width *(scale + 1)
|
245
|
+
h.dy = height *(scale + 1)
|
246
|
+
|
247
|
+
new = WolfArray(srcheader=h)
|
248
|
+
new.array.mask[:,:] = True
|
249
|
+
|
250
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
251
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
252
|
+
first_mask_data=False, with_legend=False,
|
253
|
+
update_palette= False,
|
254
|
+
IGN= True,
|
255
|
+
cat = 'orthoimage_coverage')
|
256
|
+
elif self._background.upper() == 'WALONMAP':
|
257
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
258
|
+
first_mask_data=False, with_legend=False,
|
259
|
+
update_palette= False,
|
260
|
+
Walonmap= True,
|
261
|
+
cat = 'IMAGERIE/ORTHO_2022_ETE')
|
262
|
+
else:
|
263
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
264
|
+
first_mask_data=False, with_legend=False,
|
265
|
+
update_palette= False,
|
266
|
+
Walonmap= False)
|
267
|
+
|
268
|
+
self.external_border.plot_matplotlib(ax=ax)
|
269
|
+
|
270
|
+
return fig, ax
|
271
|
+
|
272
|
+
def plot_reference(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
273
|
+
"""
|
274
|
+
Plot the reference array.
|
275
|
+
"""
|
276
|
+
if figax is None:
|
277
|
+
figax = plt.subplots()
|
278
|
+
|
279
|
+
fig, ax = figax
|
280
|
+
|
281
|
+
self.reference.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
282
|
+
return fig, ax
|
283
|
+
|
284
|
+
def plot_to_compare(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
285
|
+
"""
|
286
|
+
Plot the array to compare.
|
287
|
+
"""
|
288
|
+
if figax is None:
|
289
|
+
figax = plt.subplots()
|
290
|
+
|
291
|
+
fig, ax = figax
|
292
|
+
|
293
|
+
self.to_compare.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
294
|
+
return fig, ax
|
295
|
+
|
296
|
+
@property
|
297
|
+
def difference(self) -> WolfArray:
|
298
|
+
"""
|
299
|
+
Get the difference between the two arrays.
|
300
|
+
"""
|
301
|
+
if not isinstance(self.reference, WolfArray) or not isinstance(self.to_compare, WolfArray):
|
302
|
+
raise TypeError("Both inputs must be instances of WolfArray")
|
303
|
+
|
304
|
+
return self.to_compare - self.reference
|
305
|
+
|
306
|
+
def plot_difference(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
307
|
+
"""
|
308
|
+
Plot the array to compare.
|
309
|
+
"""
|
310
|
+
if figax is None:
|
311
|
+
figax = plt.subplots()
|
312
|
+
|
313
|
+
fig, ax = figax
|
314
|
+
|
315
|
+
pal = wolfpalette()
|
316
|
+
pal.default_difference3()
|
317
|
+
|
318
|
+
diff = self.difference
|
319
|
+
diff.mypal = pal
|
320
|
+
diff.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
321
|
+
return fig, ax
|
322
|
+
|
323
|
+
def _plot_histogram_reference(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 0.5, **kwargs) -> tuple[plt.Figure, plt.Axes]:
|
324
|
+
"""
|
325
|
+
Plot histogram of the reference array.
|
326
|
+
"""
|
327
|
+
if figax is None:
|
328
|
+
figax = plt.subplots()
|
329
|
+
|
330
|
+
fig, ax = figax
|
331
|
+
|
332
|
+
data = self.reference.array.compressed()
|
333
|
+
ax.hist(data, bins = min(100, int(len(data)/4)), density=density, alpha = alpha, **kwargs)
|
334
|
+
# ax.set_xlabel("Value")
|
335
|
+
# ax.set_ylabel("Frequency")
|
336
|
+
return fig, ax
|
337
|
+
|
338
|
+
def _plot_histogram_to_compare(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 0.5, **kwargs) -> tuple[plt.Figure, plt.Axes]:
|
339
|
+
"""
|
340
|
+
Plot histogram of the array to compare.
|
341
|
+
"""
|
342
|
+
if figax is None:
|
343
|
+
figax = plt.subplots()
|
344
|
+
|
345
|
+
fig, ax = figax
|
346
|
+
|
347
|
+
data = self.to_compare.array.compressed()
|
348
|
+
ax.hist(data, bins= min(100, int(len(data)/4)), density=density, alpha = alpha, **kwargs)
|
349
|
+
# ax.set_xlabel("Value")
|
350
|
+
# ax.set_ylabel("Frequency")
|
351
|
+
return fig, ax
|
352
|
+
|
353
|
+
def plot_histograms(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 0.5, **kwargs) -> tuple[plt.Figure, plt.Axes]:
|
354
|
+
"""
|
355
|
+
Plot histograms of both arrays.
|
356
|
+
"""
|
357
|
+
if figax is None:
|
358
|
+
figax = plt.subplots(1, 1, figsize=self.default_size_hitograms)
|
359
|
+
|
360
|
+
fig, ax = figax
|
361
|
+
|
362
|
+
self._plot_histogram_reference((fig, ax), density = density, alpha=alpha, **kwargs)
|
363
|
+
self._plot_histogram_to_compare((fig, ax), density = density, alpha=alpha, **kwargs)
|
364
|
+
|
365
|
+
# set font size of the labels
|
366
|
+
ax.tick_params(axis='both', which='major', labelsize=6)
|
367
|
+
for label in ax.get_xticklabels():
|
368
|
+
label.set_fontsize(self._fontsize)
|
369
|
+
for label in ax.get_yticklabels():
|
370
|
+
label.set_fontsize(self._fontsize)
|
371
|
+
# and gfor the label title
|
372
|
+
ax.set_xlabel(ax.get_xlabel(), fontsize=self._fontsize)
|
373
|
+
ax.set_ylabel(ax.get_ylabel(), fontsize=self._fontsize)
|
374
|
+
|
375
|
+
fig.tight_layout()
|
376
|
+
return fig, ax
|
377
|
+
|
378
|
+
def plot_histograms_difference(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 1.0, **kwargs) -> tuple[plt.Figure, plt.Axes]:
|
379
|
+
"""
|
380
|
+
Plot histogram of the difference between the two arrays.
|
381
|
+
"""
|
382
|
+
if figax is None:
|
383
|
+
figax = plt.subplots(figsize=self.default_size_hitograms)
|
384
|
+
|
385
|
+
fig, ax = figax
|
386
|
+
|
387
|
+
difference_data = self.difference.array.compressed()
|
388
|
+
ax.hist(difference_data, bins= min(100, int(len(difference_data)/4)), density=density, alpha=alpha, **kwargs)
|
389
|
+
|
390
|
+
# ax.set_xlabel("Value")
|
391
|
+
# ax.set_ylabel("Frequency")
|
392
|
+
|
393
|
+
# set font size of the labels
|
394
|
+
ax.tick_params(axis='both', which='major', labelsize=6)
|
395
|
+
for label in ax.get_xticklabels():
|
396
|
+
label.set_fontsize(self._fontsize)
|
397
|
+
for label in ax.get_yticklabels():
|
398
|
+
label.set_fontsize(self._fontsize)
|
399
|
+
# and gfor the label title
|
400
|
+
ax.set_xlabel(ax.get_xlabel(), fontsize=self._fontsize)
|
401
|
+
ax.set_ylabel(ax.get_ylabel(), fontsize=self._fontsize)
|
402
|
+
|
403
|
+
return fig, ax
|
404
|
+
|
405
|
+
def _complete_report(self, layout:ArrayDifferenceLayout):
|
406
|
+
|
407
|
+
"""
|
408
|
+
Complete the report with the arrays and histograms.
|
409
|
+
"""
|
410
|
+
useful = layout.useful_part
|
411
|
+
|
412
|
+
# Plot reference array
|
413
|
+
key_fig = [('Histogram_0-0', self.plot_histograms),
|
414
|
+
('Histogram_0-1', self.plot_histograms_difference),
|
415
|
+
('Arrays_0-0', self.plot_position),
|
416
|
+
('Arrays_1-0', self.plot_position_scaled),
|
417
|
+
('Arrays_0-1', self.plot_reference),
|
418
|
+
('Arrays_1-1', self.plot_to_compare),
|
419
|
+
('Arrays_0-2', self.plot_difference),]
|
420
|
+
|
421
|
+
keys = layout.keys
|
422
|
+
for key, fig_routine in key_fig:
|
423
|
+
if key in keys:
|
424
|
+
|
425
|
+
rect = layout._layout[key]
|
426
|
+
|
427
|
+
fig, ax = fig_routine()
|
428
|
+
|
429
|
+
# set size to fit the rectangle
|
430
|
+
fig.set_size_inches(pt2inches(rect.width), pt2inches(rect.height))
|
431
|
+
|
432
|
+
if 'Histogram' in key:
|
433
|
+
fig.tight_layout()
|
434
|
+
|
435
|
+
# convert canvas to PNG and insert it into the PDF
|
436
|
+
temp_file = NamedTemporaryFile(delete=False, suffix='.png')
|
437
|
+
fig.savefig(temp_file, format='png', bbox_inches='tight', dpi=self._dpi)
|
438
|
+
layout._page.insert_image(layout._layout[key], filename = temp_file.name)
|
439
|
+
# delete the temporary file
|
440
|
+
temp_file.delete = True
|
441
|
+
temp_file.close()
|
442
|
+
|
443
|
+
# Force to delete fig
|
444
|
+
plt.close(fig)
|
445
|
+
else:
|
446
|
+
logging.warning(f"Key {key} not found in layout. Skipping plot.")
|
447
|
+
|
448
|
+
key = 'Arrays_1-2'
|
449
|
+
if key in keys:
|
450
|
+
text, css = list_to_html(self._summary_text, font_size='8pt')
|
451
|
+
layout._page.insert_htmlbox(layout._layout[key], text,
|
452
|
+
css=css)
|
453
|
+
|
454
|
+
def create_report(self, output_file: str | Path = None) -> Path:
|
455
|
+
""" Create a page report for the array difference. """
|
456
|
+
|
457
|
+
from time import sleep
|
458
|
+
if output_file is None:
|
459
|
+
output_file = Path(f"array_difference_{self.index}.pdf")
|
460
|
+
|
461
|
+
if output_file.exists():
|
462
|
+
logging.warning(f"Output file {output_file} already exists. It will be overwritten.")
|
463
|
+
|
464
|
+
layout = ArrayDifferenceLayout(f"Differences - Index n°{self.index}")
|
465
|
+
layout.create_report()
|
466
|
+
self._complete_report(layout)
|
467
|
+
layout.save_report(output_file)
|
468
|
+
sleep(0.2) # Ensure the file is saved before returning
|
469
|
+
|
470
|
+
return output_file
|
471
|
+
|
472
|
+
class CompareArrays:
|
473
|
+
|
474
|
+
def __init__(self, reference: WolfArray | str | Path, to_compare: WolfArray | str | Path):
|
475
|
+
|
476
|
+
self._dpi = 600
|
477
|
+
self.default_size_arrays = (10, 10)
|
478
|
+
self._fontsize = 6
|
479
|
+
|
480
|
+
if isinstance(reference, (str, Path)):
|
481
|
+
reference = WolfArray(reference)
|
482
|
+
if isinstance(to_compare, (str, Path)):
|
483
|
+
to_compare = WolfArray(to_compare)
|
484
|
+
|
485
|
+
if not reference.is_like(to_compare):
|
486
|
+
raise ValueError("Arrays are not compatible for comparison")
|
487
|
+
|
488
|
+
self.array_reference:WolfArray
|
489
|
+
self.array_to_compare:WolfArray
|
490
|
+
self.array_reference = reference
|
491
|
+
self.array_to_compare = to_compare
|
492
|
+
|
493
|
+
self.labeled_array: np.ndarray = None
|
494
|
+
self.num_features: int = 0
|
495
|
+
self.nb_cells: list = []
|
496
|
+
|
497
|
+
self.difference_parts:dict[int, ArrayDifference] = {}
|
498
|
+
|
499
|
+
self._pdf_path = None
|
500
|
+
|
501
|
+
self._background = 'IGN'
|
502
|
+
|
503
|
+
@property
|
504
|
+
def difference(self) -> WolfArray:
|
505
|
+
|
506
|
+
if not isinstance(self.array_reference, WolfArray) or not isinstance(self.array_to_compare, WolfArray):
|
507
|
+
raise TypeError("Both inputs must be instances of WolfArray")
|
508
|
+
|
509
|
+
return self.array_to_compare - self.array_reference
|
510
|
+
|
511
|
+
def get_zones(self):
|
512
|
+
"""
|
513
|
+
Get a Zones object containing the differences.
|
514
|
+
"""
|
515
|
+
|
516
|
+
ret_zones = Zones()
|
517
|
+
exterior = zone(name=_("External border"))
|
518
|
+
contours = zone(name=_("Contours"))
|
519
|
+
|
520
|
+
ret_zones.add_zone(exterior, forceparent=True)
|
521
|
+
ret_zones.add_zone(contours, forceparent=True)
|
522
|
+
|
523
|
+
for diff in self.difference_parts.values():
|
524
|
+
exterior.add_vector(diff.external_border, forceparent=True)
|
525
|
+
contours.add_vector(diff.contour, forceparent=True)
|
526
|
+
|
527
|
+
return ret_zones
|
528
|
+
|
529
|
+
def plot_position(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
530
|
+
"""
|
531
|
+
Plot the reference array with a background.
|
532
|
+
"""
|
533
|
+
if figax is None:
|
534
|
+
figax = plt.subplots()
|
535
|
+
|
536
|
+
fig, ax = figax
|
537
|
+
|
538
|
+
h = self.array_reference.get_header()
|
539
|
+
width = h.nbx * h.dx
|
540
|
+
height = h.nby * h.dy
|
541
|
+
h.dx = width
|
542
|
+
h.dy = height
|
543
|
+
h.nbx = 1
|
544
|
+
h.nby = 1
|
545
|
+
|
546
|
+
new = WolfArray(srcheader=h)
|
547
|
+
new.array.mask[:,:] = True
|
548
|
+
|
549
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
550
|
+
|
551
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
552
|
+
first_mask_data=False, with_legend=False,
|
553
|
+
update_palette= False,
|
554
|
+
IGN= True,
|
555
|
+
cat = 'orthoimage_coverage',
|
556
|
+
)
|
557
|
+
elif self._background.upper() == 'WALONMAP':
|
558
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
559
|
+
first_mask_data=False, with_legend=False,
|
560
|
+
update_palette= False,
|
561
|
+
Walonmap= True,
|
562
|
+
cat = 'IMAGERIE/ORTHO_2022_ETE',
|
563
|
+
)
|
564
|
+
else:
|
565
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
566
|
+
first_mask_data=False, with_legend=False,
|
567
|
+
update_palette= False,
|
568
|
+
Walonmap= False,
|
569
|
+
)
|
570
|
+
return fig, ax
|
571
|
+
|
572
|
+
def plot_cartoweb(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
573
|
+
"""
|
574
|
+
Plot the reference array with a background.
|
575
|
+
"""
|
576
|
+
if figax is None:
|
577
|
+
figax = plt.subplots()
|
578
|
+
|
579
|
+
fig, ax = figax
|
580
|
+
|
581
|
+
h = self.array_reference.get_header()
|
582
|
+
width = h.nbx * h.dx
|
583
|
+
height = h.nby * h.dy
|
584
|
+
h.dx = width
|
585
|
+
h.dy = height
|
586
|
+
h.nbx = 1
|
587
|
+
h.nby = 1
|
588
|
+
|
589
|
+
new = WolfArray(srcheader=h)
|
590
|
+
new.array.mask[:,:] = True
|
591
|
+
|
592
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
593
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
594
|
+
first_mask_data=False, with_legend=False,
|
595
|
+
update_palette= False,
|
596
|
+
Cartoweb= True,
|
597
|
+
cat = 'overlay',
|
598
|
+
)
|
599
|
+
else:
|
600
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
601
|
+
first_mask_data=False, with_legend=False,
|
602
|
+
update_palette= False,
|
603
|
+
Cartoweb= False,
|
604
|
+
cat = 'overlay',
|
605
|
+
)
|
606
|
+
return fig, ax
|
607
|
+
|
608
|
+
def plot_topo_grey(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
609
|
+
"""
|
610
|
+
Plot the reference array with a background.
|
611
|
+
"""
|
612
|
+
if figax is None:
|
613
|
+
figax = plt.subplots()
|
614
|
+
|
615
|
+
fig, ax = figax
|
616
|
+
|
617
|
+
h = self.array_reference.get_header()
|
618
|
+
width = h.nbx * h.dx
|
619
|
+
height = h.nby * h.dy
|
620
|
+
h.dx = width
|
621
|
+
h.dy = height
|
622
|
+
h.nbx = 1
|
623
|
+
h.nby = 1
|
624
|
+
|
625
|
+
new = WolfArray(srcheader=h)
|
626
|
+
new.array.mask[:,:] = True
|
627
|
+
|
628
|
+
if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
|
629
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
630
|
+
first_mask_data=False, with_legend=False,
|
631
|
+
update_palette= False,
|
632
|
+
Cartoweb= True,
|
633
|
+
cat = 'topo_grey',
|
634
|
+
)
|
635
|
+
else:
|
636
|
+
new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
|
637
|
+
first_mask_data=False, with_legend=False,
|
638
|
+
update_palette= False,
|
639
|
+
Cartoweb= False,
|
640
|
+
cat = 'topo_grey',
|
641
|
+
)
|
642
|
+
return fig, ax
|
643
|
+
|
644
|
+
def plot_reference(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
645
|
+
"""
|
646
|
+
Plot the reference array.
|
647
|
+
"""
|
648
|
+
if figax is None:
|
649
|
+
figax = plt.subplots()
|
650
|
+
|
651
|
+
fig, ax = figax
|
652
|
+
|
653
|
+
self.array_reference.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
654
|
+
|
655
|
+
for diff in tqdm(self.difference_parts.values(), desc="Plotting external borders"):
|
656
|
+
diff.external_border.plot_matplotlib(ax=ax)
|
657
|
+
|
658
|
+
return fig, ax
|
659
|
+
|
660
|
+
def plot_to_compare(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
661
|
+
"""
|
662
|
+
Plot the array to compare.
|
663
|
+
"""
|
664
|
+
if figax is None:
|
665
|
+
figax = plt.subplots()
|
666
|
+
|
667
|
+
fig, ax = figax
|
668
|
+
|
669
|
+
self.array_to_compare.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
670
|
+
|
671
|
+
for diff in tqdm(self.difference_parts.values(), desc="Plotting contours"):
|
672
|
+
diff.contour.plot_matplotlib(ax=ax)
|
673
|
+
|
674
|
+
return fig, ax
|
675
|
+
|
676
|
+
def plot_difference(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
|
677
|
+
"""
|
678
|
+
Plot the difference between the two arrays.
|
679
|
+
"""
|
680
|
+
if figax is None:
|
681
|
+
figax = plt.subplots()
|
682
|
+
|
683
|
+
fig, ax = figax
|
684
|
+
|
685
|
+
pal = wolfpalette()
|
686
|
+
pal.default_difference3()
|
687
|
+
|
688
|
+
diff = self.difference
|
689
|
+
diff.mypal = pal
|
690
|
+
diff.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
|
691
|
+
return fig, ax
|
692
|
+
|
693
|
+
def localize_differences(self, threshold: float = 0.0,
|
694
|
+
ignored_patche_area:float = 1.) -> np.ndarray:
|
695
|
+
""" Localize the differences between the two arrays and label them.
|
696
|
+
|
697
|
+
:param threshold: The threshold value to consider a difference significant.
|
698
|
+
:param ignored_patche_area: The area of patches to ignore (in m²).
|
699
|
+
"""
|
700
|
+
|
701
|
+
assert threshold >= 0, "Threshold must be a non-negative value."
|
702
|
+
|
703
|
+
labeled_array = self.difference.array.data.copy()
|
704
|
+
labeled_array[self.array_reference.array.mask] = 0
|
705
|
+
|
706
|
+
# apply threshold
|
707
|
+
labeled_array[np.abs(labeled_array) < threshold] = 0
|
708
|
+
|
709
|
+
self.labeled_array, self.num_features = label(labeled_array)
|
710
|
+
|
711
|
+
self.nb_cells = []
|
712
|
+
|
713
|
+
self.nb_cells = list(sum_labels(np.ones(self.labeled_array.shape, dtype=np.int32), self.labeled_array, range(1, self.num_features+1)))
|
714
|
+
self.nb_cells = [[self.nb_cells[j], j+1] for j in range(0, self.num_features)]
|
715
|
+
|
716
|
+
self.nb_cells.sort(key=lambda x: x[0], reverse=True)
|
717
|
+
|
718
|
+
# find features where nb_cells is lower than ignored_patche_area / (dx * dy)
|
719
|
+
ignored_patche_cells = int(ignored_patche_area / (self.array_reference.dx * self.array_reference.dy))
|
720
|
+
self.last_features = self.num_features
|
721
|
+
for idx, (nb_cell, idx_feature) in enumerate(self.nb_cells):
|
722
|
+
if nb_cell <= ignored_patche_cells:
|
723
|
+
self.last_features = idx
|
724
|
+
break
|
725
|
+
|
726
|
+
all_slices = find_objects(self.labeled_array)
|
727
|
+
|
728
|
+
logging.info(f"Total number of features found: {self.last_features}")
|
729
|
+
|
730
|
+
# find xmin, ymin, xmax, ymax for each feature
|
731
|
+
for idx_feature, slices in tqdm(zip(range(1, self.num_features+1), all_slices), desc="Processing features", unit="feature"):
|
732
|
+
mask = self.labeled_array[slices] == idx_feature
|
733
|
+
nb_in_patch = np.count_nonzero(mask)
|
734
|
+
|
735
|
+
if nb_in_patch <= ignored_patche_cells:
|
736
|
+
logging.debug(f"Feature {idx_feature} has too few cells ({np.count_nonzero(mask)}) and will be ignored.")
|
737
|
+
continue
|
738
|
+
|
739
|
+
imin, imax = slices[0].start, slices[0].stop - 1
|
740
|
+
jmin, jmax = slices[1].start, slices[1].stop - 1
|
741
|
+
|
742
|
+
imin = int(max(imin - 1, 0))
|
743
|
+
imax = int(min(imax + 1, self.labeled_array.shape[0] - 1))
|
744
|
+
jmin = int(max(jmin - 1, 0))
|
745
|
+
jmax = int(min(jmax + 1, self.labeled_array.shape[1] - 1))
|
746
|
+
|
747
|
+
ref_crop = self.array_reference.crop(imin, jmin, imax-imin+1, jmax-jmin+1)
|
748
|
+
to_compare_crop = self.array_to_compare.crop(imin, jmin, imax-imin+1, jmax-jmin+1)
|
749
|
+
label_crop = self.labeled_array[imin:imax+1, jmin:jmax+1].copy()
|
750
|
+
|
751
|
+
|
752
|
+
to_compare_crop.array.mask[:,:] = ref_crop.array.mask[:,:] = self.labeled_array[imin:imax+1, jmin:jmax+1] != idx_feature
|
753
|
+
|
754
|
+
ref_crop.set_nullvalue_in_mask()
|
755
|
+
to_compare_crop.set_nullvalue_in_mask()
|
756
|
+
label_crop[label_crop != idx_feature] = 0
|
757
|
+
|
758
|
+
ref_crop.nbnotnull = nb_in_patch
|
759
|
+
to_compare_crop.nbnotnull = nb_in_patch
|
760
|
+
|
761
|
+
self.difference_parts[idx_feature] = ArrayDifference(ref_crop, to_compare_crop, idx_feature, label_crop)
|
762
|
+
|
763
|
+
assert self.last_features == len(self.difference_parts), \
|
764
|
+
f"Last feature index {self.last_features} does not match the number of differences found"
|
765
|
+
|
766
|
+
self.num_features = self.last_features
|
767
|
+
logging.info(f"Number of features after filtering: {self.num_features}")
|
768
|
+
|
769
|
+
return self.labeled_array
|
770
|
+
|
771
|
+
@property
|
772
|
+
def summary_text(self) -> list[str]:
|
773
|
+
"""
|
774
|
+
Generate a summary text for the report.
|
775
|
+
"""
|
776
|
+
|
777
|
+
diff = self.difference.array.compressed()
|
778
|
+
text_left = [
|
779
|
+
_("Number of features: ") + str(self.num_features),
|
780
|
+
_('Resolution: ') + f"{self.array_reference.dx} m x {self.array_reference.dy} m",
|
781
|
+
_('Extent: ') + f"({self.array_reference.origx}, {self.array_reference.origy})" + f" - ({self.array_reference.origx + self.array_reference.nbx * self.array_reference.dx}, {self.array_reference.origy + self.array_reference.nby * self.array_reference.dy})",
|
782
|
+
_('Width x Height: ') + f"{self.array_reference.nbx * self.array_reference.dx} m x {self.array_reference.nby * self.array_reference.dy} m",
|
783
|
+
]
|
784
|
+
text_right = [
|
785
|
+
_('Excavation: ') + f"{np.sum(diff[diff < 0.]) * self.array_reference.dx * self.array_reference.dy:.3f} m³",
|
786
|
+
_('Deposit/Backfill: ') + f"{np.sum(diff[diff > 0.]) * self.array_reference.dx * self.array_reference.dy:.3f} m³",
|
787
|
+
_('Net volume: ') + f"{np.sum(diff) * self.array_reference.dx * self.array_reference.dy:.3f} m³",
|
788
|
+
]
|
789
|
+
return text_left, text_right
|
790
|
+
|
791
|
+
def _complete_report(self, layout:CompareArraysLayout):
|
792
|
+
""" Complete the report with the global summary and individual differences. """
|
793
|
+
|
794
|
+
key_fig = [('Arrays_0-0', self.plot_reference),
|
795
|
+
('Arrays_1-0', self.plot_to_compare),
|
796
|
+
('Difference', self.plot_difference),]
|
797
|
+
|
798
|
+
keys = layout.keys
|
799
|
+
for key, fig_routine in key_fig:
|
800
|
+
if key in keys:
|
801
|
+
|
802
|
+
rect = layout._layout[key]
|
803
|
+
|
804
|
+
fig, ax = fig_routine()
|
805
|
+
|
806
|
+
# set size to fit the rectangle
|
807
|
+
fig.set_size_inches(pt2inches(rect.width), pt2inches(rect.height))
|
808
|
+
|
809
|
+
# convert canvas to PNG and insert it into the PDF
|
810
|
+
temp_file = NamedTemporaryFile(delete=False, suffix='.png')
|
811
|
+
fig.savefig(temp_file, format='png', bbox_inches='tight', dpi=self._dpi)
|
812
|
+
layout._page.insert_image(layout._layout[key], filename=temp_file.name)
|
813
|
+
# delete the temporary file
|
814
|
+
temp_file.delete = True
|
815
|
+
temp_file.close()
|
816
|
+
|
817
|
+
# Force to delete fig
|
818
|
+
plt.close(fig)
|
819
|
+
else:
|
820
|
+
logging.warning(f"Key {key} not found in layout. Skipping plot.")
|
821
|
+
|
822
|
+
tleft, tright = self.summary_text
|
823
|
+
|
824
|
+
rect = layout._layout['Summary_0-0']
|
825
|
+
text_left, css_left = list_to_html(tleft, font_size='8pt')
|
826
|
+
layout._page.insert_htmlbox(rect, text_left, css=css_left)
|
827
|
+
rect = layout._layout['Summary_1-0']
|
828
|
+
text_right, css_right = list_to_html(tright, font_size='8pt')
|
829
|
+
layout._page.insert_htmlbox(rect, text_right, css=css_right)
|
830
|
+
|
831
|
+
def plot_histogram_features(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 0.5, **kwargs) -> tuple[plt.Figure, plt.Axes]:
|
832
|
+
"""
|
833
|
+
Plot histogram of the number of cells in each feature.
|
834
|
+
"""
|
835
|
+
if figax is None:
|
836
|
+
figax = plt.subplots()
|
837
|
+
|
838
|
+
fig, ax = figax
|
839
|
+
|
840
|
+
surf = self.array_reference.dx * self.array_reference.dy
|
841
|
+
|
842
|
+
# Extract the number of cells for each feature
|
843
|
+
nb_cells = [item[0] * surf for item in self.nb_cells[:self.last_features]]
|
844
|
+
|
845
|
+
if len(nb_cells) > 0:
|
846
|
+
ax.hist(nb_cells, bins= min(100, int(len(nb_cells)/4)), density=density, alpha=alpha, **kwargs)
|
847
|
+
|
848
|
+
ax.set_title(_("Histogram of surface in each feature [m²]"))
|
849
|
+
|
850
|
+
# set font size of the labels
|
851
|
+
ax.tick_params(axis='both', which='major', labelsize=6)
|
852
|
+
for label in ax.get_xticklabels():
|
853
|
+
label.set_fontsize(self._fontsize)
|
854
|
+
for label in ax.get_yticklabels():
|
855
|
+
label.set_fontsize(self._fontsize)
|
856
|
+
# and gfor the label title
|
857
|
+
ax.set_xlabel(ax.get_xlabel(), fontsize=self._fontsize)
|
858
|
+
ax.set_ylabel(ax.get_ylabel(), fontsize=self._fontsize)
|
859
|
+
|
860
|
+
return fig, ax
|
861
|
+
|
862
|
+
def plot_histogram_features_difference(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 1.0, **kwargs) -> tuple[plt.Figure, plt.Axes]:
|
863
|
+
"""
|
864
|
+
Plot histogram of the volume in each feature for the difference.
|
865
|
+
"""
|
866
|
+
if figax is None:
|
867
|
+
figax = plt.subplots()
|
868
|
+
|
869
|
+
fig, ax = figax
|
870
|
+
|
871
|
+
# # Calculate the difference between the two arrays
|
872
|
+
# diff = self.difference
|
873
|
+
|
874
|
+
volumes = []
|
875
|
+
for idx in tqdm(self.nb_cells[:self.last_features], desc="Calculating volumes"):
|
876
|
+
# Get the feature index
|
877
|
+
feature_index = idx[1]
|
878
|
+
part = self.difference_parts[feature_index]
|
879
|
+
# Create a mask for the feature
|
880
|
+
mask = part.label == feature_index
|
881
|
+
# Calculate the volume for this feature
|
882
|
+
volumes.append(np.ma.sum(part.difference.array[mask]) * self.array_reference.dx * self.array_reference.dy)
|
883
|
+
|
884
|
+
# Create a histogram of the differences
|
885
|
+
if len(volumes) > 0:
|
886
|
+
ax.hist(volumes, bins= min(100, int(len(volumes)/4)), density=density, alpha=alpha, **kwargs)
|
887
|
+
|
888
|
+
ax.set_title(_("Histogram of net volumes [m³]"))
|
889
|
+
|
890
|
+
# set font size of the labels
|
891
|
+
ax.tick_params(axis='both', which='major', labelsize=6)
|
892
|
+
for label in ax.get_xticklabels():
|
893
|
+
label.set_fontsize(self._fontsize)
|
894
|
+
for label in ax.get_yticklabels():
|
895
|
+
label.set_fontsize(self._fontsize)
|
896
|
+
# and gfor the label title
|
897
|
+
ax.set_xlabel(ax.get_xlabel(), fontsize=self._fontsize)
|
898
|
+
ax.set_ylabel(ax.get_ylabel(), fontsize=self._fontsize)
|
899
|
+
|
900
|
+
fig.tight_layout()
|
901
|
+
|
902
|
+
return fig, ax
|
903
|
+
|
904
|
+
def _complete_report2(self, layout:CompareArraysLayout):
|
905
|
+
""" Complete the report with the individual differences. """
|
906
|
+
|
907
|
+
key_fig = [('Histogram_0-0', self.plot_histogram_features),
|
908
|
+
('Histogram_1-0', self.plot_histogram_features_difference),
|
909
|
+
('Arrays_0-0', self.plot_position),
|
910
|
+
('Arrays_1-0', self.plot_cartoweb),
|
911
|
+
('Position', self.plot_topo_grey),
|
912
|
+
]
|
913
|
+
|
914
|
+
keys = layout.keys
|
915
|
+
for key, fig_routine in key_fig:
|
916
|
+
if key in keys:
|
917
|
+
|
918
|
+
rect = layout._layout[key]
|
919
|
+
|
920
|
+
fig, ax = fig_routine()
|
921
|
+
|
922
|
+
# set size to fit the rectangle
|
923
|
+
fig.set_size_inches(pt2inches(rect.width), pt2inches(rect.height))
|
924
|
+
fig.tight_layout()
|
925
|
+
|
926
|
+
# convert canvas to PNG and insert it into the PDF
|
927
|
+
temp_file = NamedTemporaryFile(delete=False, suffix='.png')
|
928
|
+
fig.savefig(temp_file, format='png', bbox_inches='tight', dpi=self._dpi)
|
929
|
+
layout._page.insert_image(layout._layout[key], filename=temp_file.name)
|
930
|
+
# delete the temporary file
|
931
|
+
temp_file.delete = True
|
932
|
+
temp_file.close()
|
933
|
+
|
934
|
+
# Force to delete fig
|
935
|
+
plt.close(fig)
|
936
|
+
else:
|
937
|
+
logging.warning(f"Key {key} not found in layout. Skipping plot.")
|
938
|
+
|
939
|
+
|
940
|
+
|
941
|
+
def create_report(self, output_file: str | Path = None,
|
942
|
+
append_all_differences: bool = True,
|
943
|
+
nb_max_differences:int = -1) -> None:
|
944
|
+
""" Create a page report for the array comparison. """
|
945
|
+
|
946
|
+
if output_file is None:
|
947
|
+
output_file = Path(f"compare_arrays_report.pdf")
|
948
|
+
|
949
|
+
if output_file.exists():
|
950
|
+
logging.warning(f"Output file {output_file} already exists. It will be overwritten.")
|
951
|
+
|
952
|
+
layout = CompareArraysLayout("Comparison Report")
|
953
|
+
layout.create_report()
|
954
|
+
self._complete_report(layout)
|
955
|
+
|
956
|
+
if nb_max_differences < 0:
|
957
|
+
nb_max_differences = len(self.difference_parts)
|
958
|
+
elif nb_max_differences > len(self.difference_parts):
|
959
|
+
logging.warning(f"Requested {nb_max_differences} differences, but only {len(self.difference_parts)} are available. Using all available differences.")
|
960
|
+
elif nb_max_differences < len(self.difference_parts):
|
961
|
+
logging.info(f"Limiting to {nb_max_differences} differences.")
|
962
|
+
|
963
|
+
features_to_treat = [feature[1] for feature in self.nb_cells[:nb_max_differences]]
|
964
|
+
|
965
|
+
with TemporaryDirectory() as temp_dir:
|
966
|
+
|
967
|
+
layout2 = CompareArraysLayout2("Distribution of Differences")
|
968
|
+
layout2.create_report()
|
969
|
+
self._complete_report2(layout2)
|
970
|
+
layout2.save_report(Path(temp_dir) / "distribution_of_differences.pdf")
|
971
|
+
all_pdfs = [Path(temp_dir) / "distribution_of_differences.pdf"]
|
972
|
+
|
973
|
+
if append_all_differences:
|
974
|
+
# Add each difference report to the main layout
|
975
|
+
all_pdfs.extend([self.difference_parts[idx].create_report(Path(temp_dir) / f"array_difference_{idx}.pdf") for idx in tqdm(features_to_treat, desc="Creating individual difference reports")])
|
976
|
+
|
977
|
+
for pdf_file in tqdm(all_pdfs, desc="Compiling PDFs"):
|
978
|
+
layout._doc.insert_file(pdf_file)
|
979
|
+
|
980
|
+
# create a TOC
|
981
|
+
layout._doc.set_toc(layout._doc.get_toc())
|
982
|
+
|
983
|
+
layout.save_report(output_file)
|
984
|
+
self._pdf_path = output_file
|
985
|
+
|
986
|
+
@property
|
987
|
+
def pdf_path(self) -> Path:
|
988
|
+
""" Return the path to the generated PDF report. """
|
989
|
+
if hasattr(self, '_pdf_path'):
|
990
|
+
return self._pdf_path
|
991
|
+
else:
|
992
|
+
raise AttributeError("PDF path not set. Please create the report first.")
|
993
|
+
|
994
|
+
class CompareArrays_wx(PDFViewer):
|
995
|
+
|
996
|
+
def __init__(self, reference: WolfArray | str | Path,
|
997
|
+
to_compare: WolfArray | str | Path,
|
998
|
+
ignored_patche_area:float = 2.0,
|
999
|
+
nb_max_patches:int = 10,
|
1000
|
+
threshold: float = 0.01,
|
1001
|
+
dpi=200, **kwargs):
|
1002
|
+
""" Initialize the Simple Simulation GPU Report Viewer for comparison """
|
1003
|
+
|
1004
|
+
super(CompareArrays_wx, self).__init__(None, **kwargs)
|
1005
|
+
|
1006
|
+
use('agg')
|
1007
|
+
|
1008
|
+
if isinstance(reference, WolfArray) and isinstance(to_compare, WolfArray):
|
1009
|
+
if np.any(reference.array.mask != to_compare.array.mask):
|
1010
|
+
logging.warning("The masks of the two arrays are not identical. This may lead to unexpected results.")
|
1011
|
+
dlg = wx.MessageDialog(self,
|
1012
|
+
_("The masks of the two arrays are not identical.\nThis may lead to unexpected results.\n\nWe will use the reference mask for the comparison."),
|
1013
|
+
_("Warning"),
|
1014
|
+
wx.OK | wx.ICON_WARNING)
|
1015
|
+
dlg.ShowModal()
|
1016
|
+
dlg.Destroy()
|
1017
|
+
to_compare = WolfArray(mold=to_compare)
|
1018
|
+
to_compare.array.mask[:,:] = reference.array.mask[:,:]
|
1019
|
+
|
1020
|
+
self._report = CompareArrays(reference, to_compare)
|
1021
|
+
|
1022
|
+
self._report._dpi = dpi
|
1023
|
+
self._report.localize_differences(threshold=threshold,
|
1024
|
+
ignored_patche_area=ignored_patche_area)
|
1025
|
+
self._report.create_report(nb_max_differences=nb_max_patches)
|
1026
|
+
|
1027
|
+
# Load the PDF into the viewer
|
1028
|
+
if self._report.pdf_path is None:
|
1029
|
+
logging.error("No report created. Cannot load PDF.")
|
1030
|
+
return
|
1031
|
+
|
1032
|
+
self.load_pdf(self._report.pdf_path)
|
1033
|
+
self.viewer.SetZoom(-1) # Fit to width
|
1034
|
+
|
1035
|
+
# Set the title of the frame
|
1036
|
+
self.SetTitle("Simple Simulation GPU Comparison Report")
|
1037
|
+
|
1038
|
+
self.Bind(wx.EVT_CLOSE, self.on_close)
|
1039
|
+
|
1040
|
+
use('wxagg')
|
1041
|
+
|
1042
|
+
def on_close(self, event):
|
1043
|
+
""" Handle the close event to clean up resources """
|
1044
|
+
self.viewer.pdfdoc.pdfdoc.close()
|
1045
|
+
self.Destroy()
|
1046
|
+
|
1047
|
+
def get_zones(self) -> Zones:
|
1048
|
+
"""
|
1049
|
+
Get the zones from the report.
|
1050
|
+
"""
|
1051
|
+
ret = self._report.get_zones()
|
1052
|
+
ret.prep_listogl()
|
1053
|
+
|
1054
|
+
return ret
|