wolfhece 2.2.26__py3-none-any.whl → 2.2.28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,844 @@
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
8
+ import pymupdf as pdf
9
+
10
+ from .common import A4_rect, rect_cm, list_to_html, list_to_html_aligned, get_rect_from_text
11
+ from .common import inches2cm, pts2cm, cm2pts, cm2inches, DefaultLayoutA4, NamedTemporaryFile, pt2inches, TemporaryDirectory
12
+ from ..wolf_array import WolfArray, header_wolf, vector, zone, Zones, wolfvertex as wv, wolfpalette
13
+ from ..PyTranslate import _
14
+
15
+ class ArrayDifferenceLayout(DefaultLayoutA4):
16
+ """
17
+ Layout for comparing two arrays in a report.
18
+
19
+ 1 cadre pour la zone traitée avec photo de fond ign + contour vectoriel
20
+ 1 cadre avec zoom plus large min 250m
21
+ 1 cadre avec matrice ref + contour vectoriel
22
+ 1 cadre avec matrice à comparer + contour vectoriel
23
+ 1 cadre avec différence
24
+ 1 cadre avec valeurs de synthèse
25
+
26
+ 1 cadre avec histogramme
27
+ 1 cadre avec histogramme des différences
28
+ """
29
+
30
+ 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):
31
+ super().__init__(title, filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx, bbox, find_minmax, shared, colors)
32
+
33
+ useful = self.useful_part
34
+
35
+ width = useful.xmax - useful.xmin
36
+ height = useful.ymax - useful.ymin
37
+
38
+ self._hitograms = self.add_element_repeated(_("Histogram"), width=width, height=2.5,
39
+ first_x=useful.xmin, first_y=useful.ymax,
40
+ count_x=1, count_y=-2, padding=0.5)
41
+
42
+ self._arrays = self.add_element_repeated(_("Arrays"), width= (width-self.padding) / 2, height=5.5,
43
+ first_x=useful.xmin, first_y=self._hitograms.ymin - self.padding,
44
+ count_x=2, count_y=-3, padding=0.5)
45
+
46
+ class CompareArraysLayout(DefaultLayoutA4):
47
+
48
+ 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):
49
+ super().__init__(title, filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx, bbox, find_minmax, shared, colors)
50
+
51
+ useful = self.useful_part
52
+
53
+ width = useful.xmax - useful.xmin
54
+ height = useful.ymax - useful.ymin
55
+
56
+ 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)
57
+
58
+ 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)
59
+ self._diff_rect = self.add_element(_("Difference"), width= width, height=11.5, x=useful.xmin, y=useful.ymin)
60
+
61
+
62
+ class CompareArraysLayout2(DefaultLayoutA4):
63
+
64
+ 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):
65
+ super().__init__(title, filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx, bbox, find_minmax, shared, colors)
66
+
67
+ useful = self.useful_part
68
+
69
+ width = useful.xmax - useful.xmin
70
+ height = useful.ymax - useful.ymin
71
+
72
+ 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)
73
+
74
+ 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)
75
+ self._diff_rect = self.add_element(_("Position"), width= width, height=11.5, x=useful.xmin, y=useful.ymin)
76
+
77
+
78
+ class ArrayDifference():
79
+ """
80
+ Class to manage the difference between two WolfArray objects.
81
+ """
82
+
83
+ def __init__(self, reference:WolfArray, to_compare:WolfArray, index:int, label:np.ndarray):
84
+
85
+ self._dpi = 600
86
+ self.default_size_hitograms = (12, 6)
87
+ self.default_size_arrays = (10, 10)
88
+ self._fontsize = 6
89
+
90
+ self.reference = reference
91
+ self.to_compare = to_compare
92
+
93
+ self.reference.updatepalette()
94
+ self.to_compare.mypal = self.reference.mypal
95
+
96
+ self.index = index
97
+ self.label = label
98
+
99
+ self._background = 'IGN'
100
+
101
+ @property
102
+ def contour(self) -> vector:
103
+ ret = self.reference.suxsuy_contour(abs=True)
104
+ ret = ret[2]
105
+
106
+ ret.myprop.color = (0, 0, 255)
107
+ ret.myprop.width = 2
108
+
109
+ return ret
110
+
111
+ @property
112
+ def external_border(self) -> vector:
113
+ """
114
+ Get the bounds of the reference array.
115
+ """
116
+ ret = vector(name=_("External border"))
117
+ (xmin, xmax), (ymin, ymax) = self.reference.get_bounds()
118
+ ret.add_vertex(wv(xmin, ymin))
119
+ ret.add_vertex(wv(xmax, ymin))
120
+ ret.add_vertex(wv(xmax, ymax))
121
+ ret.add_vertex(wv(xmin, ymax))
122
+ ret.force_to_close()
123
+
124
+ ret.myprop.color = (255, 0, 0)
125
+ ret.myprop.width = 3
126
+
127
+ return ret
128
+
129
+ def __str__(self):
130
+
131
+ assert self.reference.nbnotnull == self.to_compare.nbnotnull, "The number of non-null cells in both arrays must be the same."
132
+
133
+ ret = self.reference.__str__() + '\n'
134
+
135
+ ret += _("Index : ") + str(self.index) + '\n'
136
+ ret += _("Number of cells : ") + str(self.reference.nbnotnull) + '\n'
137
+
138
+ return ret
139
+
140
+ @property
141
+ def _summary_text(self):
142
+ """
143
+ Generate a summary text for the report.
144
+ """
145
+ diff = self.difference.array.compressed()
146
+ text = [
147
+ _("Index: ") + str(self.index),
148
+ _("Number of cells: ") + str(self.reference.nbnotnull),
149
+ _('Resolution: ') + f"{self.reference.dx} m x {self.reference.dy} m",
150
+ _('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})",
151
+ _('Width x Height: ') + f"{self.reference.nbx * self.reference.dx} m x {self.reference.nby * self.reference.dy} m",
152
+ _('Excavation: ') + f"{np.sum(diff[diff < 0.]) * self.reference.dx * self.reference.dy:.3f} m³",
153
+ _('Deposit/Backfill: ') + f"{np.sum(diff[diff > 0.]) * self.reference.dx * self.reference.dy:.3f} m³",
154
+ _('Net volume: ') + f"{np.sum(diff) * self.reference.dx * self.reference.dy:.3f} m³",
155
+ ]
156
+ return text
157
+
158
+ def set_palette_distribute(self, minval:float, maxval:float, step:int=0):
159
+ """
160
+ Set the palette for both arrays.
161
+ """
162
+ self.reference.mypal.distribute_values(minval, maxval, step)
163
+
164
+ def set_palette(self, values:list[float], colors:list[tuple[int, int, int]]):
165
+ """
166
+ Set the palette for both arrays based on specific values.
167
+ """
168
+ self.reference.mypal.set_values_colors(values, colors)
169
+
170
+ def plot_position(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
171
+ """
172
+ Plot the reference array.
173
+ """
174
+ if figax is None:
175
+ figax = plt.subplots()
176
+
177
+ fig, ax = figax
178
+
179
+ old_mask = self.reference.array.mask.copy()
180
+ self.reference.array.mask[:,:] = True
181
+
182
+ if self._background.upper() == 'IGN' or self._background.upper() == 'NGI':
183
+ self.reference.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
184
+ first_mask_data=False, with_legend=False,
185
+ update_palette= False,
186
+ IGN= True,
187
+ cat = 'orthoimage_coverage',
188
+ )
189
+
190
+ elif self._background.upper() == 'WALONMAP':
191
+ self.reference.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
192
+ first_mask_data=False, with_legend=False,
193
+ update_palette= False,
194
+ Walonmap= True,
195
+ cat = 'IMAGERIE/ORTHO_2022_ETE',
196
+ )
197
+
198
+ self.reference.array.mask[:,:] = old_mask
199
+
200
+ self.external_border.plot_matplotlib(ax=ax)
201
+ self.contour.plot_matplotlib(ax=ax)
202
+
203
+ return fig, ax
204
+
205
+ def plot_position_scaled(self, scale = 4, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
206
+ """
207
+ Plot the reference array.
208
+ """
209
+ if figax is None:
210
+ figax = plt.subplots()
211
+
212
+ fig, ax = figax
213
+
214
+ h = self.reference.get_header()
215
+ width = h.nbx * h.dx
216
+ height = h.nby * h.dy
217
+
218
+ h.origx += -width * scale / 2
219
+ h.origy += -height *scale / 2
220
+ h.nbx = 1
221
+ h.nby = 1
222
+ h.dx = width *(scale + 1)
223
+ h.dy = height *(scale + 1)
224
+
225
+ new = WolfArray(srcheader=h)
226
+ new.array.mask[:,:] = True
227
+
228
+ new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
229
+ first_mask_data=False, with_legend=False,
230
+ update_palette= False,
231
+ Walonmap= True,
232
+ cat = 'IMAGERIE/ORTHO_2022_ETE')
233
+
234
+ self.external_border.plot_matplotlib(ax=ax)
235
+
236
+ return fig, ax
237
+
238
+ def plot_reference(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
239
+ """
240
+ Plot the reference array.
241
+ """
242
+ if figax is None:
243
+ figax = plt.subplots()
244
+
245
+ fig, ax = figax
246
+
247
+ self.reference.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
248
+ return fig, ax
249
+
250
+ def plot_to_compare(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
251
+ """
252
+ Plot the array to compare.
253
+ """
254
+ if figax is None:
255
+ figax = plt.subplots()
256
+
257
+ fig, ax = figax
258
+
259
+ self.to_compare.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
260
+ return fig, ax
261
+
262
+ @property
263
+ def difference(self) -> WolfArray:
264
+ """
265
+ Get the difference between the two arrays.
266
+ """
267
+ if not isinstance(self.reference, WolfArray) or not isinstance(self.to_compare, WolfArray):
268
+ raise TypeError("Both inputs must be instances of WolfArray")
269
+
270
+ return self.to_compare - self.reference
271
+
272
+ def plot_difference(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
273
+ """
274
+ Plot the array to compare.
275
+ """
276
+ if figax is None:
277
+ figax = plt.subplots()
278
+
279
+ fig, ax = figax
280
+
281
+ pal = wolfpalette()
282
+ pal.default_difference3()
283
+
284
+ diff = self.difference
285
+ diff.mypal = pal
286
+ diff.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
287
+ return fig, ax
288
+
289
+ def _plot_histogram_reference(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 0.5, **kwargs) -> tuple[plt.Figure, plt.Axes]:
290
+ """
291
+ Plot histogram of the reference array.
292
+ """
293
+ if figax is None:
294
+ figax = plt.subplots()
295
+
296
+ fig, ax = figax
297
+
298
+ ax.hist(self.reference.array.compressed(), density=density, alpha = alpha, **kwargs)
299
+ # ax.set_xlabel("Value")
300
+ # ax.set_ylabel("Frequency")
301
+ return fig, ax
302
+
303
+ def _plot_histogram_to_compare(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 0.5, **kwargs) -> tuple[plt.Figure, plt.Axes]:
304
+ """
305
+ Plot histogram of the array to compare.
306
+ """
307
+ if figax is None:
308
+ figax = plt.subplots()
309
+
310
+ fig, ax = figax
311
+
312
+ ax.hist(self.to_compare.array.compressed(), density=density, alpha = alpha, **kwargs)
313
+ # ax.set_xlabel("Value")
314
+ # ax.set_ylabel("Frequency")
315
+ return fig, ax
316
+
317
+ def plot_histograms(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 0.5, **kwargs) -> tuple[plt.Figure, plt.Axes]:
318
+ """
319
+ Plot histograms of both arrays.
320
+ """
321
+ if figax is None:
322
+ figax = plt.subplots(1, 1, figsize=self.default_size_hitograms)
323
+
324
+ fig, ax = figax
325
+
326
+ self._plot_histogram_reference((fig, ax), density = density, alpha=alpha, **kwargs)
327
+ self._plot_histogram_to_compare((fig, ax), density = density, alpha=alpha, **kwargs)
328
+
329
+ # set font size of the labels
330
+ ax.tick_params(axis='both', which='major', labelsize=6)
331
+ for label in ax.get_xticklabels():
332
+ label.set_fontsize(self._fontsize)
333
+ for label in ax.get_yticklabels():
334
+ label.set_fontsize(self._fontsize)
335
+ # and gfor the label title
336
+ ax.set_xlabel(ax.get_xlabel(), fontsize=self._fontsize)
337
+ ax.set_ylabel(ax.get_ylabel(), fontsize=self._fontsize)
338
+
339
+ fig.tight_layout()
340
+ return fig, ax
341
+
342
+ def plot_histograms_difference(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 1.0, **kwargs) -> tuple[plt.Figure, plt.Axes]:
343
+ """
344
+ Plot histogram of the difference between the two arrays.
345
+ """
346
+ if figax is None:
347
+ figax = plt.subplots(figsize=self.default_size_hitograms)
348
+
349
+ fig, ax = figax
350
+
351
+ difference_data = self.difference.array.compressed()
352
+ ax.hist(difference_data, density=density, alpha=alpha, **kwargs)
353
+
354
+ # ax.set_xlabel("Value")
355
+ # ax.set_ylabel("Frequency")
356
+
357
+ # set font size of the labels
358
+ ax.tick_params(axis='both', which='major', labelsize=6)
359
+ for label in ax.get_xticklabels():
360
+ label.set_fontsize(self._fontsize)
361
+ for label in ax.get_yticklabels():
362
+ label.set_fontsize(self._fontsize)
363
+ # and gfor the label title
364
+ ax.set_xlabel(ax.get_xlabel(), fontsize=self._fontsize)
365
+ ax.set_ylabel(ax.get_ylabel(), fontsize=self._fontsize)
366
+
367
+ return fig, ax
368
+
369
+ def _complete_report(self, layout:ArrayDifferenceLayout):
370
+
371
+ """
372
+ Complete the report with the arrays and histograms.
373
+ """
374
+ useful = layout.useful_part
375
+
376
+ # Plot reference array
377
+ key_fig = [('Histogram_0-0', self.plot_histograms),
378
+ ('Histogram_0-1', self.plot_histograms_difference),
379
+ ('Arrays_0-0', self.plot_position),
380
+ ('Arrays_1-0', self.plot_position_scaled),
381
+ ('Arrays_0-1', self.plot_reference),
382
+ ('Arrays_1-1', self.plot_to_compare),
383
+ ('Arrays_0-2', self.plot_difference),]
384
+
385
+ keys = layout.keys
386
+ for key, fig_routine in key_fig:
387
+ if key in keys:
388
+
389
+ rect = layout._layout[key]
390
+
391
+ fig, ax = fig_routine()
392
+
393
+ # set size to fit the rectangle
394
+ fig.set_size_inches(pt2inches(rect.width), pt2inches(rect.height))
395
+
396
+ if 'Histogram' in key:
397
+ fig.tight_layout()
398
+
399
+
400
+ # convert canvas to PNG and insert it into the PDF
401
+ temp_file = NamedTemporaryFile(delete=False, suffix='.png')
402
+ fig.savefig(temp_file, format='png', bbox_inches='tight', dpi=self._dpi)
403
+ layout._page.insert_image(layout._layout[key], filename = temp_file.name)
404
+ # delete the temporary file
405
+ temp_file.delete = True
406
+ temp_file.close()
407
+
408
+ # Force to delete fig
409
+ plt.close(fig)
410
+ else:
411
+ logging.warning(f"Key {key} not found in layout. Skipping plot.")
412
+
413
+ key = 'Arrays_1-2'
414
+ if key in keys:
415
+ text, css = list_to_html(self._summary_text, font_size='8pt')
416
+ layout._page.insert_htmlbox(layout._layout[key], text,
417
+ css=css)
418
+
419
+ def create_report(self, output_file: str | Path = None) -> Path:
420
+ """ Create a page report for the array difference. """
421
+
422
+ if output_file is None:
423
+ output_file = Path(f"array_difference_{self.index}.pdf")
424
+
425
+ if output_file.exists():
426
+ logging.warning(f"Output file {output_file} already exists. It will be overwritten.")
427
+
428
+ layout = ArrayDifferenceLayout(f"Differences - Index n°{self.index}")
429
+ layout.create_report()
430
+ self._complete_report(layout)
431
+ layout.save_report(output_file)
432
+
433
+ return output_file
434
+
435
+ class CompareArrays:
436
+
437
+ def __init__(self, reference: WolfArray | str | Path, to_compare: WolfArray | str | Path):
438
+
439
+ self._dpi = 600
440
+ self.default_size_arrays = (10, 10)
441
+ self._fontsize = 6
442
+
443
+ if isinstance(reference, (str, Path)):
444
+ reference = WolfArray(reference)
445
+ if isinstance(to_compare, (str, Path)):
446
+ to_compare = WolfArray(to_compare)
447
+
448
+ if not reference.is_like(to_compare):
449
+ raise ValueError("Arrays are not compatible for comparison")
450
+
451
+ self.array_reference:WolfArray
452
+ self.array_to_compare:WolfArray
453
+ self.array_reference = reference
454
+ self.array_to_compare = to_compare
455
+
456
+ self.labeled_array: np.ndarray = None
457
+ self.num_features: int = 0
458
+ self.nb_cells: list = []
459
+
460
+ self.difference_parts:dict[int, ArrayDifference] = {}
461
+
462
+ @property
463
+ def difference(self) -> WolfArray:
464
+
465
+ if not isinstance(self.array_reference, WolfArray) or not isinstance(self.array_to_compare, WolfArray):
466
+ raise TypeError("Both inputs must be instances of WolfArray")
467
+
468
+ return self.array_to_compare - self.array_reference
469
+
470
+ def plot_position(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
471
+ """
472
+ Plot the reference array with a background.
473
+ """
474
+ if figax is None:
475
+ figax = plt.subplots()
476
+
477
+ fig, ax = figax
478
+
479
+ h = self.array_reference.get_header()
480
+ width = h.nbx * h.dx
481
+ height = h.nby * h.dy
482
+ h.dx = width
483
+ h.dy = height
484
+ h.nbx = 1
485
+ h.nby = 1
486
+
487
+ new = WolfArray(srcheader=h)
488
+ new.array.mask[:,:] = True
489
+
490
+
491
+ new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
492
+ first_mask_data=False, with_legend=False,
493
+ update_palette= False,
494
+ IGN= True,
495
+ cat = 'orthoimage_coverage',
496
+ )
497
+
498
+ return fig, ax
499
+
500
+ def plot_cartoweb(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
501
+ """
502
+ Plot the reference array with a background.
503
+ """
504
+ if figax is None:
505
+ figax = plt.subplots()
506
+
507
+ fig, ax = figax
508
+
509
+ h = self.array_reference.get_header()
510
+ width = h.nbx * h.dx
511
+ height = h.nby * h.dy
512
+ h.dx = width
513
+ h.dy = height
514
+ h.nbx = 1
515
+ h.nby = 1
516
+
517
+ new = WolfArray(srcheader=h)
518
+ new.array.mask[:,:] = True
519
+
520
+
521
+ new.plot_matplotlib(figax=figax, figsize = self.default_size_arrays,
522
+ first_mask_data=False, with_legend=False,
523
+ update_palette= False,
524
+ Cartoweb= True,
525
+ cat = 'overlay',
526
+ )
527
+
528
+ return fig, ax
529
+
530
+ def plot_topo_grey(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
531
+ """
532
+ Plot the reference array with a background.
533
+ """
534
+ if figax is None:
535
+ figax = plt.subplots()
536
+
537
+ fig, ax = figax
538
+
539
+ h = self.array_reference.get_header()
540
+ width = h.nbx * h.dx
541
+ height = h.nby * h.dy
542
+ h.dx = width
543
+ h.dy = height
544
+ h.nbx = 1
545
+ h.nby = 1
546
+
547
+ new = WolfArray(srcheader=h)
548
+ new.array.mask[:,:] = True
549
+
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
+ Cartoweb= True,
555
+ cat = 'topo_grey',
556
+ )
557
+
558
+ return fig, ax
559
+
560
+ def plot_reference(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
561
+ """
562
+ Plot the reference array.
563
+ """
564
+ if figax is None:
565
+ figax = plt.subplots()
566
+
567
+ fig, ax = figax
568
+
569
+ self.array_reference.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
570
+
571
+ for diff in self.difference_parts.values():
572
+ diff.external_border.plot_matplotlib(ax=ax)
573
+
574
+ return fig, ax
575
+
576
+ def plot_to_compare(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
577
+ """
578
+ Plot the array to compare.
579
+ """
580
+ if figax is None:
581
+ figax = plt.subplots()
582
+
583
+ fig, ax = figax
584
+
585
+ self.array_to_compare.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
586
+
587
+ for diff in self.difference_parts.values():
588
+ diff.contour.plot_matplotlib(ax=ax)
589
+
590
+ return fig, ax
591
+
592
+ def plot_difference(self, figax:tuple[plt.Figure, plt.Axes]=None) -> tuple[plt.Figure, plt.Axes]:
593
+ """
594
+ Plot the difference between the two arrays.
595
+ """
596
+ if figax is None:
597
+ figax = plt.subplots()
598
+
599
+ fig, ax = figax
600
+
601
+ pal = wolfpalette()
602
+ pal.default_difference3()
603
+
604
+ diff = self.difference
605
+ diff.mypal = pal
606
+ diff.plot_matplotlib(figax=figax, figsize = self.default_size_arrays, first_mask_data=False, with_legend=True, update_palette= False)
607
+ return fig, ax
608
+
609
+ def localize_differences(self, threshold: float = 0.0) -> np.ndarray:
610
+
611
+ labeled_array = self.difference.array.data.copy()
612
+ labeled_array[self.array_reference.array.mask] = 0
613
+
614
+ self.labeled_array, self.num_features = label(labeled_array)
615
+
616
+ self.nb_cells = []
617
+
618
+ self.nb_cells = list(sum_labels(np.ones(self.labeled_array.shape, dtype=np.int32), self.labeled_array, range(1, self.num_features+1)))
619
+ self.nb_cells = [[self.nb_cells[j], j+1] for j in range(0, self.num_features)]
620
+
621
+ self.nb_cells.sort(key=lambda x: x[0], reverse=True)
622
+
623
+ # find xmin, ymin, xmax, ymax for each feature
624
+ for idx_feature in range(1, self.num_features + 1):
625
+ mask = self.labeled_array == idx_feature
626
+ i, j = np.where(mask)
627
+
628
+ imin, imax = i.min(), i.max()
629
+ jmin, jmax = j.min(), j.max()
630
+
631
+ imin = int(max(imin - 1, 0))
632
+ imax = int(min(imax + 1, self.labeled_array.shape[0] - 1))
633
+ jmin = int(max(jmin - 1, 0))
634
+ jmax = int(min(jmax + 1, self.labeled_array.shape[1] - 1))
635
+
636
+ ref_crop = self.array_reference.crop(imin, jmin, imax-imin+1, jmax-jmin+1)
637
+ to_compare_crop = self.array_to_compare.crop(imin, jmin, imax-imin+1, jmax-jmin+1)
638
+
639
+ ref_crop.array.mask[:,:] = ~mask[imin:imax+1, jmin:jmax+1]
640
+ ref_crop.set_nullvalue_in_mask()
641
+
642
+ to_compare_crop.array.mask[:,:] = ~mask[imin:imax+1, jmin:jmax+1]
643
+ to_compare_crop.set_nullvalue_in_mask()
644
+
645
+ ref_crop.count()
646
+ to_compare_crop.nbnotnull = ref_crop.nbnotnull
647
+
648
+ self.difference_parts[idx_feature] = ArrayDifference(ref_crop, to_compare_crop, idx_feature, self.labeled_array[imin:imax+1, jmin:jmax+1].copy())
649
+
650
+ return self.labeled_array
651
+
652
+ @property
653
+ def summary_text(self) -> list[str]:
654
+ """
655
+ Generate a summary text for the report.
656
+ """
657
+
658
+ diff = self.difference.array.compressed()
659
+ text_left = [
660
+ _("Number of features: ") + str(self.num_features),
661
+ _('Resolution: ') + f"{self.array_reference.dx} m x {self.array_reference.dy} m",
662
+ _('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})",
663
+ _('Width x Height: ') + f"{self.array_reference.nbx * self.array_reference.dx} m x {self.array_reference.nby * self.array_reference.dy} m",
664
+ ]
665
+ text_right = [
666
+ _('Excavation: ') + f"{np.sum(diff[diff < 0.]) * self.array_reference.dx * self.array_reference.dy:.3f} m³",
667
+ _('Deposit/Backfill: ') + f"{np.sum(diff[diff > 0.]) * self.array_reference.dx * self.array_reference.dy:.3f} m³",
668
+ _('Net volume: ') + f"{np.sum(diff) * self.array_reference.dx * self.array_reference.dy:.3f} m³",
669
+ ]
670
+ return text_left, text_right
671
+
672
+ def _complete_report(self, layout:CompareArraysLayout):
673
+ """ Complete the report with the global summary and individual differences. """
674
+
675
+ key_fig = [('Arrays_0-0', self.plot_reference),
676
+ ('Arrays_1-0', self.plot_to_compare),
677
+ ('Difference', self.plot_difference),]
678
+
679
+ keys = layout.keys
680
+ for key, fig_routine in key_fig:
681
+ if key in keys:
682
+
683
+ rect = layout._layout[key]
684
+
685
+ fig, ax = fig_routine()
686
+
687
+ # set size to fit the rectangle
688
+ fig.set_size_inches(pt2inches(rect.width), pt2inches(rect.height))
689
+
690
+ # convert canvas to PNG and insert it into the PDF
691
+ temp_file = NamedTemporaryFile(delete=False, suffix='.png')
692
+ fig.savefig(temp_file, format='png', bbox_inches='tight', dpi=self._dpi)
693
+ layout._page.insert_image(layout._layout[key], filename=temp_file.name)
694
+ # delete the temporary file
695
+ temp_file.delete = True
696
+ temp_file.close()
697
+
698
+ # Force to delete fig
699
+ plt.close(fig)
700
+ else:
701
+ logging.warning(f"Key {key} not found in layout. Skipping plot.")
702
+
703
+ tleft, tright = self.summary_text
704
+
705
+ rect = layout._layout['Summary_0-0']
706
+ text_left, css_left = list_to_html(tleft, font_size='8pt')
707
+ layout._page.insert_htmlbox(rect, text_left, css=css_left)
708
+ rect = layout._layout['Summary_1-0']
709
+ text_right, css_right = list_to_html(tright, font_size='8pt')
710
+ layout._page.insert_htmlbox(rect, text_right, css=css_right)
711
+
712
+ def plot_histogram_features(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 0.5, **kwargs) -> tuple[plt.Figure, plt.Axes]:
713
+ """
714
+ Plot histogram of the number of cells in each feature.
715
+ """
716
+ if figax is None:
717
+ figax = plt.subplots()
718
+
719
+ fig, ax = figax
720
+
721
+ surf = self.array_reference.dx * self.array_reference.dy
722
+
723
+ # Extract the number of cells for each feature
724
+ nb_cells = [item[0] * surf for item in self.nb_cells]
725
+
726
+ ax.hist(nb_cells, density=density, alpha=alpha, **kwargs)
727
+
728
+ ax.set_title(_("Histogram of surface in each feature [m²]"))
729
+
730
+ # set font size of the labels
731
+ ax.tick_params(axis='both', which='major', labelsize=6)
732
+ for label in ax.get_xticklabels():
733
+ label.set_fontsize(self._fontsize)
734
+ for label in ax.get_yticklabels():
735
+ label.set_fontsize(self._fontsize)
736
+ # and gfor the label title
737
+ ax.set_xlabel(ax.get_xlabel(), fontsize=self._fontsize)
738
+ ax.set_ylabel(ax.get_ylabel(), fontsize=self._fontsize)
739
+
740
+ return fig, ax
741
+
742
+ def plot_histogram_features_difference(self, figax:tuple[plt.Figure, plt.Axes]=None, density = True, alpha = 1.0, **kwargs) -> tuple[plt.Figure, plt.Axes]:
743
+ """
744
+ Plot histogram of the volume in each feature for the difference.
745
+ """
746
+ if figax is None:
747
+ figax = plt.subplots()
748
+
749
+ fig, ax = figax
750
+
751
+ # Calculate the difference between the two arrays
752
+ diff = self.difference
753
+
754
+ volumes = np.bincount(self.labeled_array.ravel(), weights=diff.array.ravel()) * self.array_reference.dx * self.array_reference.dy
755
+
756
+ # Create a histogram of the differences
757
+ ax.hist(volumes, density=density, alpha=alpha, **kwargs)
758
+
759
+ ax.set_title(_("Histogram of net volumes [m³]"))
760
+
761
+ # set font size of the labels
762
+ ax.tick_params(axis='both', which='major', labelsize=6)
763
+ for label in ax.get_xticklabels():
764
+ label.set_fontsize(self._fontsize)
765
+ for label in ax.get_yticklabels():
766
+ label.set_fontsize(self._fontsize)
767
+ # and gfor the label title
768
+ ax.set_xlabel(ax.get_xlabel(), fontsize=self._fontsize)
769
+ ax.set_ylabel(ax.get_ylabel(), fontsize=self._fontsize)
770
+
771
+ fig.tight_layout()
772
+
773
+ return fig, ax
774
+
775
+ def _complete_report2(self, layout:CompareArraysLayout):
776
+ """ Complete the report with the individual differences. """
777
+
778
+ key_fig = [('Histogram_0-0', self.plot_histogram_features),
779
+ ('Histogram_1-0', self.plot_histogram_features_difference),
780
+ ('Arrays_0-0', self.plot_position),
781
+ ('Arrays_1-0', self.plot_cartoweb),
782
+ ('Position', self.plot_topo_grey),
783
+ ]
784
+
785
+ keys = layout.keys
786
+ for key, fig_routine in key_fig:
787
+ if key in keys:
788
+
789
+ rect = layout._layout[key]
790
+
791
+ fig, ax = fig_routine()
792
+
793
+ # set size to fit the rectangle
794
+ fig.set_size_inches(pt2inches(rect.width), pt2inches(rect.height))
795
+ fig.tight_layout()
796
+
797
+ # convert canvas to PNG and insert it into the PDF
798
+ temp_file = NamedTemporaryFile(delete=False, suffix='.png')
799
+ fig.savefig(temp_file, format='png', bbox_inches='tight', dpi=self._dpi)
800
+ layout._page.insert_image(layout._layout[key], filename=temp_file.name)
801
+ # delete the temporary file
802
+ temp_file.delete = True
803
+ temp_file.close()
804
+
805
+ # Force to delete fig
806
+ plt.close(fig)
807
+ else:
808
+ logging.warning(f"Key {key} not found in layout. Skipping plot.")
809
+
810
+
811
+
812
+ def create_report(self, output_file: str | Path = None, append_all_differences: bool = True) -> None:
813
+ """ Create a page report for the array comparison. """
814
+
815
+ if output_file is None:
816
+ output_file = Path(f"compare_arrays_report.pdf")
817
+
818
+ if output_file.exists():
819
+ logging.warning(f"Output file {output_file} already exists. It will be overwritten.")
820
+
821
+ layout = CompareArraysLayout("Comparison Report")
822
+ layout.create_report()
823
+ self._complete_report(layout)
824
+
825
+
826
+ with TemporaryDirectory() as temp_dir:
827
+
828
+ layout2 = CompareArraysLayout2("Distribution of Differences")
829
+ layout2.create_report()
830
+ self._complete_report2(layout2)
831
+ layout2.save_report(Path(temp_dir) / "distribution_of_differences.pdf")
832
+ all_pdfs = [Path(temp_dir) / "distribution_of_differences.pdf"]
833
+
834
+ if append_all_differences:
835
+ # Add each difference report to the main layout
836
+ all_pdfs.extend([diff.create_report(Path(temp_dir) / f"array_difference_{idx}.pdf") for idx, diff in self.difference_parts.items()])
837
+
838
+ for pdf_file in all_pdfs:
839
+ layout._doc.insert_file(pdf_file)
840
+
841
+ # create a TOC
842
+ layout._doc.set_toc(layout._doc.get_toc())
843
+
844
+ layout.save_report(output_file)