wolfhece 2.2.4__py3-none-any.whl → 2.2.5__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/PyPalette.py CHANGED
@@ -96,7 +96,10 @@ class wolfpalette(wx.Frame, LinearSegmentedColormap):
96
96
  self.set_over(tuple(self.colormax))
97
97
 
98
98
  def get_rgba(self, x: np.ndarray):
99
- """Récupération de la couleur en fonction de la valeur x"""
99
+ """Récupération de la couleur en fonction de la valeur x
100
+
101
+ :param x: tableau de valeurs
102
+ """
100
103
 
101
104
  dval = self.values[-1]-self.values[0]
102
105
  if dval == 0.:
@@ -260,6 +263,7 @@ class wolfpalette(wx.Frame, LinearSegmentedColormap):
260
263
  if self.interval_cst:
261
264
  discrete_cmap = ListedColormap(self.colorsflt[:, :3])
262
265
  colorbar = ScalarMappable(BoundaryNorm(self.values, ncolors=self.nb-1), cmap=discrete_cmap)
266
+ return colorbar
263
267
  else:
264
268
  return ScalarMappable(Normalize(self.values[0], self.values[-1]), cmap=self)
265
269
 
@@ -453,6 +457,26 @@ class wolfpalette(wx.Frame, LinearSegmentedColormap):
453
457
  ax.set_xticks(pos)
454
458
  ax.set_xticklabels(txt, rotation=30, fontsize=6)
455
459
 
460
+ @property
461
+ def cmap(self):
462
+ """ Récupération de la palette de couleurs """
463
+ return self
464
+
465
+ @property
466
+ def vmin(self):
467
+ """ Récupération de la valeur minimale """
468
+ return self.values[0]
469
+
470
+ @property
471
+ def vmax(self):
472
+ """ Récupération de la valeur maximale """
473
+ return self.values[-1]
474
+
475
+ @property
476
+ def norm(self):
477
+ """ Récupération de la normalisation """
478
+ return Normalize(self.values[0], self.values[-1])
479
+
456
480
  def fillgrid(self, gridto: CpGrid):
457
481
  """ Remplissage d'une grille avec les valeurs de la palette """
458
482
 
@@ -692,7 +716,7 @@ class wolfpalette(wx.Frame, LinearSegmentedColormap):
692
716
  normval = np.ones([len(self.values)])
693
717
 
694
718
  if dval > 0.:
695
- normval = (self.values-self.values[0])/(self.values[-1]-self.values[0])
719
+ normval = (self.values-self.values[0])/dval
696
720
 
697
721
  normval[0] = 0.
698
722
  normval[-1] = 1.
wolfhece/PyWMS.py CHANGED
@@ -202,7 +202,8 @@ def getLifeWatch(cat:Literal['None'],
202
202
  yr:float,
203
203
  w:int = None,
204
204
  h:int = None,
205
- tofile=True) -> BytesIO:
205
+ tofile=True,
206
+ format:Literal['image/png', 'image/png; mode=8bit']='image/png') -> BytesIO:
206
207
 
207
208
  wms=WebMapService(f'https://maps.elie.ucl.ac.be/cgi-bin/mapserv72?map=/maps_server/lifewatch/mapfiles/LW_Ecotopes/latest/{cat}.map&SERVICE=wms',
208
209
  version='1.3.0')
@@ -226,6 +227,16 @@ def getLifeWatch(cat:Literal['None'],
226
227
  # w = int(real_w * ppkm)
227
228
  h = int(real_h * ppkm)
228
229
 
230
+ MAXSIZE = 2048
231
+ if w > MAXSIZE:
232
+ pond = w / MAXSIZE
233
+ w = MAXSIZE
234
+ h = int(h / pond)
235
+ if h > MAXSIZE:
236
+ pond = h / MAXSIZE
237
+ h = MAXSIZE
238
+ w = int(w / pond)
239
+
229
240
  if tofile:
230
241
  img=wms.getmap(layers=['lc_hr_raster'],
231
242
  # styles=['default'],
@@ -250,7 +261,7 @@ def getLifeWatch(cat:Literal['None'],
250
261
  srs='EPSG:31370',
251
262
  bbox=(xl,yl,xr,yr),
252
263
  size=(w,h),
253
- format='image/png',
264
+ format=format,
254
265
  transparent=False)
255
266
  return BytesIO(img.read())
256
267
  except:
wolfhece/apps/version.py CHANGED
@@ -5,7 +5,7 @@ class WolfVersion():
5
5
 
6
6
  self.major = 2
7
7
  self.minor = 2
8
- self.patch = 4
8
+ self.patch = 5
9
9
 
10
10
  def __str__(self):
11
11
 
wolfhece/lifewatch.py CHANGED
@@ -1,6 +1,20 @@
1
1
  from enum import Enum
2
2
  from PIL import Image
3
+ import numpy as np
4
+ from typing import Literal
5
+ import matplotlib.pyplot as plt
6
+ from matplotlib.colors import LinearSegmentedColormap, Normalize, ListedColormap, BoundaryNorm
3
7
 
8
+
9
+ from .PyTranslate import _
10
+ from .PyWMS import getLifeWatch
11
+ from .wolf_array import WolfArray, header_wolf, WOLF_ARRAY_FULL_INTEGER8, WOLF_ARRAY_FULL_INTEGER, wolfpalette
12
+
13
+
14
+ YEARS = [2006, 2010, 2015, 2018, 2019, 2020, 2021, 2022]
15
+ PIXEL_SIZE = 2 # in meters
16
+ MAP_LW = 'LW_ecotopes_lc_hr_raster'
17
+ MAX_PIXELS = 2048 # Maximum number of pixels in one direction (x or y)
4
18
  class LifeWatch_Legend(Enum):
5
19
  """
6
20
  https://www.mdpi.com/2306-5729/8/1/13
@@ -21,68 +35,389 @@ class LifeWatch_Legend(Enum):
21
35
  Small broadleaved trees (<3 m) and shrubs 56 LCC-2_1_2 & LCH-3_1_2 1.75
22
36
 
23
37
  Color Table (RGB with 256 entries) from tiff file
24
- 10: 10,10,210,255
25
- 11: 254,254,254,255
26
- 15: 215,215,215,255
27
- 20: 20,20,20,255
28
- 21: 210,0,0,255
29
- 30: 230,230,130,255
30
- 35: 235,170,0,255
31
- 40: 240,40,240,255
32
- 45: 145,245,245,255
38
+ 10: 10,10,210
39
+ 15: 210,210,210
40
+ 20: 20,20,20
41
+ 21: 210,0,0
42
+ 30: 230,230,130
43
+ 35: 235,170,0
44
+ 40: 240,40,240
45
+ 45: 145,245,245
46
+ 48: 148,118,0
47
+ 50: 50,150,50
48
+ 51: 0,151,151
49
+ 55: 55,255,0
50
+ 56: 156,255,156
51
+
33
52
  46: 246,146,246,255
34
- 48: 148,112,0,255
35
- 50: 50,150,50,255
36
- 51: 0,151,151,255
37
- 55: 55,255,0,255
38
- 56: 156,255,156,255
53
+ 11: 254,254,254,255
54
+ """
55
+ NODATA_WHITE = (0, (255, 255, 255), 'Nodata', '') # Outside Belgium/Wallonia
56
+
57
+ WATER = (10, (10, 10, 210), _("Water"), 'LCC-3')
58
+ NATURAL_MATERIAL_SURFACES = (15, (210, 210, 210), _("Natural Material Surfaces with less than 10% vegetation"), 'LCC-1_2')
59
+ ARTIFICIALLY_SEALED_GROUND_SURFACE = (20, (20, 20, 20), _("Artificially sealed ground surface"), 'LCC-1_1_1_3')
60
+ BUILDING = (21, (210, 0, 0), _("Building, specific structures and facilities"), 'LCC-1_1_1_1 || LCC-1_1_1_2')
61
+ HERBACEOUS_ROTATION = (30, (230, 230, 130), _("Herbaceous in rotation during the year (e.g., crops)"), 'LCC-2_2')
62
+ GRASSLAND_INTENSIVE_MANAGEMENT = (35, (235, 170, 0), _("Grassland with intensive management"), 'LCC-2_2')
63
+ GRASSLAND_SCRUB_BIOLOGICAL_INTEREST = (40, (240, 40, 240), _("Grassland and scrub of biological interest"), 'LCC-2_2')
64
+ INUNDATED_GRASSLAND_SCRUB_BIOLOGICAL_INTEREST = (45, (145, 245, 245), _("Inundated grassland and scrub of biological interest"), 'LCC-2_2 & LCH-4_4_2')
65
+ VEGETATION_RECENTLY_DISTURBED_AREA = (48, (148, 118, 0), _("Vegetation of recently disturbed area (e.g., clear cut)"), 'LCC-2_2 & LCH-3_8')
66
+ CONIFEROUS_TREES = (50, (50, 150, 50), _("Coniferous trees (≥3 m)"), 'LCC-2_1_1 & LCH-3_1_1')
67
+ SMALL_CONIFEROUS_TREES = (51, (0, 151, 151), _("Small coniferous trees (<3 m)"), 'LCC-2_1_2 & LCH-3_1_1')
68
+ BROADLEAVED_TREES = (55, (55, 255, 0), _("Broadleaved trees (≥3 m)"), 'LCC-2_1_1 & LCH-3_1_2')
69
+ SMALL_BROADLEAVED_TREES_SHRUBS = (56, (156, 255, 156), _("Small broadleaved trees (<3 m) and shrubs"), 'LCC-2_1_2 & LCH-3_1_2')
70
+
71
+ # NODATA11 = (11, (254,254,254,255)) # Not used
72
+ # NODATA46 = (46, (246,146,246,255)) # Not used
73
+ NODATA_BLACK = (100, (0, 0, 0), 'Nodata', '') # Outside Belgium/Wallonia
74
+
75
+ @classmethod
76
+ def reference(cls) -> str:
77
+ """
78
+ Return the reference
79
+ """
80
+ return 'https://www.mdpi.com/2306-5729/8/1/13'
81
+
82
+ @classmethod
83
+ def colors(cls, rgba:bool = False) -> list[tuple[int, int, int] | tuple[int, int, int, int]]:
84
+ """
85
+ Return the color of the class as a tuple (R, G, B)
86
+ """
87
+ if rgba:
88
+ return [leg.value[1] + (255,) for leg in cls]
89
+ else:
90
+ return [leg.value[1] for leg in cls]
91
+
92
+ @classmethod
93
+ def codes(cls):
94
+ """
95
+ Return the code of the class as integer
96
+ """
97
+ return [leg.value[0] for leg in cls]
98
+
99
+ @classmethod
100
+ def plot_legend(cls, figax = None):
101
+ """
102
+ Return the color of the class as a tuple (R, G, B)
103
+ """
104
+
105
+ colors = cls.colors()
106
+ codes = cls.codes()
107
+ texts = cls.texts()
108
+
109
+ if figax is None:
110
+ fig, ax = plt.subplots(figsize=(1, 6))
111
+ else:
112
+ fig, ax = figax
113
+
114
+ for i, color in enumerate(colors):
115
+ ax.fill([0,1,1,0,0],[i,i,i+1,i+1,i], color=np.array(color)/255.0)
116
+ ax.text(1.05, i + 0.5, f"{codes[i]}: {texts[i]}", va='center', fontsize=12)
117
+ ax.axis('off')
118
+
119
+ return fig, ax
120
+
121
+ @classmethod
122
+ def cmap(cls) -> plt.cm:
123
+ """
124
+ Return the colormap of the class
125
+ """
126
+ colors = np.asarray(cls.colors()).astype(float)/255.
127
+ codes = np.asarray(cls.codes()).astype(float)
128
+
129
+ normval = codes/100.
130
+
131
+ normval[0] = 0.
132
+ normval[-1] = 1.
133
+ segmentdata = {"red": np.column_stack([normval, colors[:, 0], colors[:, 0]]),
134
+ "green": np.column_stack([normval, colors[:, 1], colors[:, 1]]),
135
+ "blue": np.column_stack([normval, colors[:, 2], colors[:, 2]]),
136
+ "alpha": np.column_stack([normval, np.ones(len(colors)) * 255., np.ones(len(colors)) * 255.])}
137
+
138
+ return LinearSegmentedColormap('LifeWatch', segmentdata, 256)
139
+
140
+ @classmethod
141
+ def norm(cls) -> BoundaryNorm:
142
+ """
143
+ Return the norm of the class
144
+ """
145
+ return Normalize(0, 100)
146
+
147
+ @classmethod
148
+ def texts(cls):
149
+ """
150
+ Return the text of the class as a string
151
+ """
152
+ return [leg.value[2] for leg in cls]
153
+
154
+ @classmethod
155
+ def EAGLE_codes(cls):
156
+ """
157
+ Return the EAGLE code of the class as string
158
+ """
159
+ return [leg.value[3] for leg in cls]
160
+
161
+ @classmethod
162
+ def colors2codes(cls, array: np.ndarray | Image.Image,
163
+ aswolf:bool = True) -> np.ndarray:
164
+ """
165
+ Convert the color of the class to the code of the class
166
+ :param array: numpy array or PIL image
167
+ """
168
+
169
+ if isinstance(array, Image.Image):
170
+ mode = array.mode
171
+ if mode == 'RGB':
172
+ array = np.array(array)
173
+ elif mode == 'RGBA':
174
+ array = np.array(array)[:, :, :3]
175
+ elif mode == 'P':
176
+ array = np.array(array.convert('RGB'))
177
+ else:
178
+ raise ValueError(f"Unsupported image mode: {mode}")
179
+
180
+ elif isinstance(array, np.ndarray):
181
+ if array.ndim == 3 and array.shape[2] == 4:
182
+ array = array[:, :, :3]
183
+ elif array.ndim == 2:
184
+ pass
185
+ else:
186
+ raise ValueError(f"Unsupported array shape: {array.shape}")
187
+
188
+ unique_colors = np.unique(array.reshape(-1, array.shape[2]), axis=0)
189
+
190
+ # check if the colors are in the legend
191
+ for color in unique_colors:
192
+ if not any(np.array_equal(color, leg.value[1]) for leg in cls):
193
+ raise ValueError(f"Color {color} not found in legend")
194
+
195
+ # convert the color to the code
196
+ color_to_code = {leg.value[1]: leg.value[0] for leg in cls}
197
+ code_array = np.zeros(array.shape[:2], dtype=np.uint8)
198
+ for color, code in color_to_code.items():
199
+ mask = np.all(array == color, axis=-1)
200
+ code_array[mask] = code
201
+
202
+ if aswolf:
203
+ return np.asfortranarray(np.fliplr(code_array.T))
204
+ else:
205
+ return code_array
206
+
207
+ @classmethod
208
+ def codes2colors(cls, array: np.ndarray, asimage:bool = False) -> np.ndarray | Image.Image:
209
+ """
210
+ Convert the code of the class to the color of the class
211
+ :param array: numpy array or PIL image
212
+ """
213
+
214
+ if isinstance(array, np.ndarray):
215
+ if array.ndim == 2:
216
+ pass
217
+ else:
218
+ raise ValueError(f"Unsupported array shape: {array.shape}")
219
+ else:
220
+ raise ValueError(f"Unsupported array type: {type(array)}")
221
+
222
+ # check if the codes are in the legend
223
+ for code in np.unique(array):
224
+ if code not in cls.codes():
225
+ raise ValueError(f"Code {code} not found in legend")
226
+
227
+ # convert the code to the color
228
+ code_to_color = {leg.value[0]: leg.value[1] for leg in cls}
229
+ color_array = np.zeros((*array.shape, 3), dtype=np.uint8)
230
+ for code, color in code_to_color.items():
231
+ mask = (array == code)
232
+ color_array[mask] = color
233
+
234
+ if asimage:
235
+ color_array = Image.fromarray(color_array, mode='RGB')
236
+ color_array = color_array.convert('RGBA')
237
+ color_array.putalpha(255)
238
+ return color_array
239
+ else:
240
+ return color_array
241
+
242
+ return color_array
243
+
244
+ @classmethod
245
+ def getwolfpalette(cls) -> wolfpalette:
246
+ """
247
+ Get the wolf palette for the class
248
+ """
249
+ palette = wolfpalette()
250
+
251
+ palette.set_values_colors(cls.codes(), cls.colors())
252
+ palette.automatic = False
253
+ palette.interval_cst = True
254
+
255
+ return palette
256
+
257
+ def get_LifeWatch_bounds(year:int,
258
+ xmin:float,
259
+ ymin:float,
260
+ xmax:float,
261
+ ymax:float,
262
+ format:Literal['WolfArray',
263
+ 'NUMPY',
264
+ 'RGB',
265
+ 'RGBA',
266
+ 'Palette'] = 'WolfArray',
267
+ force_size:bool= True,
268
+ ) -> WolfArray | np.ndarray | Image.Image:
269
+
270
+
271
+ if year not in YEARS:
272
+ raise ValueError(f"Year {year} not found in LifeWatch years")
273
+
274
+ dx = xmax - xmin
275
+ dy = ymax - ymin
276
+
277
+ if force_size:
278
+ w = dx / PIXEL_SIZE
279
+ h = dy / PIXEL_SIZE
280
+
281
+ if w > MAX_PIXELS or h > MAX_PIXELS:
282
+ raise ValueError(f"Map size is too large: {w}x{h} pixels (max. {MAX_PIXELS}x{MAX_PIXELS})")
283
+ else:
284
+
285
+ if dx > dy:
286
+ w = MAX_PIXELS
287
+ h = int(dy * w / dx)
288
+ else:
289
+ h = MAX_PIXELS
290
+ w = int(dx * h / dy)
291
+
292
+ # Get the map from the WMS server
293
+ mybytes = getLifeWatch(f'{MAP_LW}_{year}',
294
+ xmin, ymin, # Lower left corner
295
+ xmax, ymax, # Upper right corner
296
+ w=MAX_PIXELS, h=None, # Width and height of the image [pixels]
297
+ tofile=False, # Must be False to get bytes --> save the image to ".\Lifewatch.png" if True
298
+ format='image/png; mode=8bit')
299
+
300
+ # Check if the map is empty
301
+ if mybytes is None:
302
+ raise ValueError(f"Error getting LifeWatch map for year {year} -- Check you internet connection or the resolution of the map (max. 2048x2048 pixels or 2mx2m)")
303
+
304
+ image = Image.open(mybytes) # Convert bytes to Image
305
+
306
+ if format in ['RGB', 'RGBA', 'Palette']:
307
+ if format == 'RGB':
308
+ image = image.convert('RGB')
309
+ elif format == 'RGBA':
310
+ image = image.convert('RGBA')
311
+ elif format == 'Palette':
312
+ image = image.convert('P')
313
+ else:
314
+ raise ValueError(f"Unsupported format: {format}")
315
+
316
+ return image, (xmin, ymin, xmax, ymax)
317
+
318
+ elif format == 'NUMPY':
319
+ return LifeWatch_Legend.colors2codes(image, aswolf=False), (xmin, ymin, xmax, ymax)
320
+
321
+ elif format in ['WolfArray', 'WOLF']:
322
+ h = header_wolf()
323
+ h.set_origin(xmin, ymin)
324
+ h.shape = image.size[0], image.size[1]
325
+ h.set_resolution((xmax-xmin)/h.nbx, (ymax-ymin)/h.nby)
326
+ wolf = WolfArray(srcheader=h, whichtype=WOLF_ARRAY_FULL_INTEGER)
327
+ wolf.array[:,:] = LifeWatch_Legend.colors2codes(image, aswolf=True).astype(int)
328
+ wolf.mask_data(0)
329
+ wolf.mypal = LifeWatch_Legend.getwolfpalette()
330
+
331
+ return wolf, (xmin, ymin, xmax, ymax)
332
+ else:
333
+ raise ValueError(f"Unsupported format: {format}")
334
+
335
+ def get_LifeWatch_Wallonia(year: int,
336
+ format:Literal['WolfArray',
337
+ 'NUMPY',
338
+ 'RGB',
339
+ 'RGBA',
340
+ 'Palette'] = 'WolfArray') -> WolfArray | np.ndarray | Image.Image:
341
+ """
342
+ Get the Wallonia LifeWatch map for the given year
343
+ :param year: year of the map
344
+ :param asimage: if True, return the image as PIL image, else return numpy array
345
+ :return: numpy array or PIL image
39
346
  """
40
- WATER = (10, (10, 210, 255))
41
- NATURAL_MATERIAL_SURFACES = (15, (215, 215, 215, 255))
42
- ARTIFICIALLY_SEALED_GROUND_SURFACE = (20, (20, 20, 20, 255))
43
- BUILDING = (21, (210, 0, 0, 255))
44
- HERBACEOUS_ROTATION = (30, (230, 230, 130, 255))
45
- GRASSLAND_INTENSIVE_MANAGEMENT = (35, (235, 170, 0, 255))
46
- GRASSLAND_SCRUB_BIOLOGICAL_INTEREST = (40, (240, 40, 240, 255))
47
- INUNDATED_GRASSLAND_SCRUB_BIOLOGICAL_INTEREST = (45, (145, 245, 245, 255))
48
- VEGETATION_RECENTLY_DISTURBED_AREA = (48, (148, 112, 0, 255))
49
- CONIFEROUS_TREES = (50, (50, 150, 50, 255))
50
- SMALL_CONIFEROUS_TREES = (51, (0, 151, 151, 255))
51
- BROADLEAVED_TREES = (55, (55, 255, 0, 255))
52
- SMALL_BROADLEAVED_TREES_SHRUBS = (56, (156, 255, 156, 255))
53
-
54
- NODATA11 = (11, (254,254,254,255)) # Not used
55
- NODATA46 = (46, (246,146,246,255)) # Not used
56
- NODATA100 = (100, (0, 0, 0, 255)) # Outside Belgium/Wallonia
57
-
58
- if __name__ == "__main__":
59
- import numpy as np
60
- n = 4
61
-
62
- DIR = r'E:\MODREC-Vesdre\vesdre-data\LifeWatch'
63
-
64
- # Tif file is very large, so we need to use PIL to open it
65
- Image.MAX_IMAGE_PIXELS = 15885900000
66
- img = Image.open(DIR + r'\lifewatch_LC2018_vx19_2mLB08cog.tif',)
67
- img = np.asarray(img)
68
-
69
- ij11 = np.where(img == 11)
70
- ij46 = np.where(img == 46)
71
-
72
- print(ij11[0].shape) # must be 0
73
- print(ij11[1].shape) # must be 0
74
-
75
- img = img[::n,:-img.shape[1]//2:n]
76
- print(np.unique(img))
77
-
78
- img = Image.open(DIR +r'\lifewatch_LC2022_vx20_2mLB08cog.tif',)
79
- img = np.asarray(img)
80
-
81
- ij11 = np.where(img == 11) # must be 0
82
- ij46 = np.where(img == 46) # must be 0
83
-
84
- print(ij11[0].shape)
85
- print(ij11[1].shape)
86
-
87
- img = img[::n,:-img.shape[1]//2:n]
88
- print(np.unique(img))
347
+
348
+ # Whole Wallonia
349
+ xmin = 40_000
350
+ xmax = 300_000
351
+ ymin = 10_000
352
+ ymax = 175_000
353
+
354
+ return get_LifeWatch_bounds(year, xmin, ymin, xmax, ymax, format, force_size=False)
355
+
356
+ def get_LifeWatch_center_width_height(year: int,
357
+ x: float,
358
+ y: float,
359
+ width: float = 2000,
360
+ height: float = 2000,
361
+ format:Literal['WolfArray',
362
+ 'NUMPY',
363
+ 'RGB',
364
+ 'RGBA',
365
+ 'Palette'] = 'WolfArray') -> WolfArray | np.ndarray | Image.Image:
366
+ """
367
+ Get the LifeWatch map for the given year and center
368
+ :param year: year of the map
369
+ :param x: x coordinate of the center
370
+ :param y: y coordinate of the center
371
+ :param asimage: if True, return the image as PIL image, else return numpy array
372
+ :return: numpy array or PIL image
373
+ """
374
+
375
+ # compute bounds
376
+ xmin = x - width / 2
377
+ xmax = x + width / 2
378
+ ymin = y - height / 2
379
+ ymax = y + height / 2
380
+
381
+ return get_LifeWatch_bounds(year, xmin, ymin, xmax, ymax, format)
382
+
383
+ def count_pixels(array:np.ndarray | WolfArray) -> dict[int, int]:
384
+ """
385
+ Count the number of pixels for each code in the array
386
+ :param array: numpy array or WolfArray
387
+ :return: dictionary with the code as key and the number of pixels as value
388
+ """
389
+ if isinstance(array, WolfArray):
390
+ array = array.array[~array.array.mask]
391
+ elif isinstance(array, np.ndarray):
392
+ pass
393
+ else:
394
+ raise ValueError(f"Unsupported array type: {type(array)}")
395
+
396
+ unique_codes, counts = np.unique(array, return_counts=True)
397
+
398
+ for code in unique_codes:
399
+ if code not in LifeWatch_Legend.codes():
400
+ raise ValueError(f"Code {code} not found in legend")
401
+
402
+ return {int(code): int(count) for code, count in zip(unique_codes, counts)}
403
+
404
+ def get_areas(array:np.ndarray | WolfArray) -> dict[int, float]:
405
+ """
406
+ Get the areas of each code in the array
407
+ :param array: numpy array or WolfArray
408
+ :return: dictionary with the code as key and the area in m² as value
409
+ """
410
+ if isinstance(array, WolfArray):
411
+ array = array.array[~array.array.mask]
412
+ elif isinstance(array, np.ndarray):
413
+ pass
414
+ else:
415
+ raise ValueError(f"Unsupported array type: {type(array)}")
416
+
417
+ unique_codes, counts = np.unique(array, return_counts=True)
418
+
419
+ for code in unique_codes:
420
+ if code not in LifeWatch_Legend.codes():
421
+ raise ValueError(f"Code {code} not found in legend")
422
+
423
+ return {int(code): float(count) * PIXEL_SIZE**2 for code, count in zip(unique_codes, counts)}
@@ -0,0 +1,61 @@
1
+ 15
2
+ 0.0
3
+ 255
4
+ 255
5
+ 255
6
+ 10.0
7
+ 10
8
+ 10
9
+ 210
10
+ 15.0
11
+ 210
12
+ 210
13
+ 210
14
+ 20.0
15
+ 20
16
+ 20
17
+ 20
18
+ 21.0
19
+ 210
20
+ 0
21
+ 0
22
+ 30.0
23
+ 230
24
+ 230
25
+ 130
26
+ 35.0
27
+ 235
28
+ 170
29
+ 0
30
+ 40.0
31
+ 240
32
+ 40
33
+ 240
34
+ 45.0
35
+ 145
36
+ 245
37
+ 245
38
+ 48.0
39
+ 148
40
+ 118
41
+ 0
42
+ 50.0
43
+ 50
44
+ 150
45
+ 50
46
+ 51.0
47
+ 0
48
+ 151
49
+ 151
50
+ 55.0
51
+ 55
52
+ 255
53
+ 0
54
+ 56.0
55
+ 156
56
+ 255
57
+ 156
58
+ 100.0
59
+ 0
60
+ 0
61
+ 0
wolfhece/wolf_array.py CHANGED
@@ -8,6 +8,13 @@ This script and its content are protected by copyright law. Unauthorized
8
8
  copying or distribution of this file, via any medium, is strictly prohibited.
9
9
  """
10
10
 
11
+ try:
12
+ from osgeo import gdal, osr
13
+ gdal.UseExceptions()
14
+ except ImportError as e:
15
+ print(e)
16
+ raise Exception(_('Error importing GDAL library'))
17
+
11
18
  import os
12
19
  import sys
13
20
 
@@ -127,13 +134,6 @@ VERSION_RGB = 3
127
134
 
128
135
  from numba import jit
129
136
 
130
- try:
131
- from osgeo import gdal, osr
132
- gdal.UseExceptions()
133
- except ImportError as e:
134
- print(e)
135
- raise Exception(_('Error importing GDAL library'))
136
-
137
137
  @jit(nopython=True)
138
138
  def custom_gradient(array: np.ndarray):
139
139
  """ Calculate the gradient manually """
@@ -5495,10 +5495,10 @@ class WolfArray(Element_To_Draw, header_wolf):
5495
5495
  return self.alpha
5496
5496
 
5497
5497
  # def find_minmax(self, update=False):
5498
-
5498
+
5499
5499
  # if update:
5500
5500
  # [self.xmin, self.xmax], [self.ymin, self.ymax] = self.get_bounds()
5501
-
5501
+
5502
5502
  @property
5503
5503
  def memory_usage(self):
5504
5504
  """
@@ -8568,6 +8568,40 @@ class WolfArray(Element_To_Draw, header_wolf):
8568
8568
 
8569
8569
  return self.get_values_underpoly(myvect, usemask, getxy)
8570
8570
 
8571
+ def count_unique_pixels(self) -> dict[int, int]:
8572
+ """
8573
+ Count the number of pixels for each unique value in the array
8574
+ :return: dictionary with the code as key and the number of pixels as value
8575
+ """
8576
+ if self.wolftype not in [WOLF_ARRAY_FULL_INTEGER, WOLF_ARRAY_FULL_INTEGER16, WOLF_ARRAY_FULL_INTEGER8, WOLF_ARRAY_FULL_UINTEGER8]:
8577
+ logging.error("Bad wolftype")
8578
+ return {}
8579
+
8580
+ counts = np.bincount(self.array[~self.array.mask].ravel())
8581
+ ret = {i: int(val) for i, val in enumerate(counts) if val > 0}
8582
+
8583
+ return ret
8584
+
8585
+ def get_unique_areas(self, format:Literal['m2', 'ha', 'km2'] = 'm2') -> dict[int, float]:
8586
+ """
8587
+ Get the areas of each code in the array
8588
+ :param array: numpy array or WolfArray
8589
+ :return: dictionary with the code as key and the area in m² as value
8590
+ """
8591
+ ret = self.count_unique_pixels()
8592
+
8593
+ fact = 1.0
8594
+ if format in ['ha', 'Ha']:
8595
+ fact = 0.0001
8596
+ elif format in ['km2', 'km²']:
8597
+ fact = 1e-6
8598
+ elif format not in ['m2', 'm²']:
8599
+ logging.error("Bad format")
8600
+ return {}
8601
+
8602
+ # Convert counts to areas in m²
8603
+ return {code: float(count) * self.dx * self.dy * fact for code, count in ret.items()}
8604
+
8571
8605
  def count_insidepoly(self, myvect: vector,
8572
8606
  usemask:bool=True,
8573
8607
  method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict',
@@ -9427,6 +9461,59 @@ class WolfArray(Element_To_Draw, header_wolf):
9427
9461
  y, x = np.meshgrid(y_discr, x_discr)
9428
9462
  return x, y
9429
9463
 
9464
+ def convolve(self,
9465
+ filter,
9466
+ method:Literal['scipyfft',
9467
+ 'jaxfft',
9468
+ 'classic']='scipyfft',
9469
+ inplace:bool=True) -> "WolfArray":
9470
+ """
9471
+ Convolve the array with a filter
9472
+
9473
+ :param filter: filter to convolve with
9474
+ :param method: method to use for convolution ('scipyfft', 'jaxfft', 'classic')
9475
+ :param inplace: if True, the array is modified in place, otherwise a new array is returned
9476
+ :return: convolved array, WolfArray instance
9477
+
9478
+ """
9479
+
9480
+ if isinstance(filter, WolfArray):
9481
+ _filter = filter.array.data.copy()
9482
+ _filter[filter.array.mask] = 0.0
9483
+ elif isinstance(filter, np.ndarray):
9484
+ _filter = filter.copy()
9485
+ _filter[np.isnan(_filter)] = 0.0
9486
+ else:
9487
+ logging.error("Filter must be a WolfArray or a numpy array")
9488
+ return None
9489
+
9490
+ if method == 'classic':
9491
+ from scipy.signal import convolve2d
9492
+ convolved = convolve2d(self.array.data, _filter, mode='same', fillvalue=0.0)
9493
+ elif method == 'scipyfft':
9494
+ from scipy.signal import fftconvolve
9495
+ convolved = fftconvolve(self.array.data, _filter, mode='same')
9496
+ elif method == 'jaxfft':
9497
+ import jax.numpy as jnp
9498
+ from jax.scipy.signal import fftconvolve as jax_fftconvolve
9499
+ jax_filter = jnp.array(_filter)
9500
+ jax_array = jnp.array(self.array.data)
9501
+ convolved = jax_fftconvolve(jax_array, jax_filter, mode='same')
9502
+ convolved = jnp.asarray(convolved)
9503
+ else:
9504
+ logging.error("Method not supported")
9505
+ return None
9506
+
9507
+ if inplace:
9508
+ self.array.data[:,:] = convolved
9509
+ return self
9510
+ else:
9511
+ newWolfArray = WolfArray(mold=self)
9512
+ newWolfArray.array.data[:,:] = convolved
9513
+ newWolfArray.array.mask[:,:] = self.array.mask[:,:]
9514
+ newWolfArray.count()
9515
+ return newWolfArray
9516
+
9430
9517
  def crop_masked_at_edges(self):
9431
9518
 
9432
9519
  """
@@ -9933,11 +10020,26 @@ class WolfArray(Element_To_Draw, header_wolf):
9933
10020
  logging.error(e)
9934
10021
 
9935
10022
  if (vmin is None) and (vmax is None):
9936
- im = ax.imshow(self.array.transpose(), origin='lower', cmap=self.mypal,
9937
- extent=(self.origx, self.origx + self.dx * self.nbx, self.origy, self.origy + self.dy * self.nby))
9938
- else:
9939
- im = ax.imshow(self.array.transpose(), origin='lower', cmap=self.mypal,
9940
- extent=(self.origx, self.origx + self.dx * self.nbx, self.origy, self.origy + self.dy * self.nby), vmin=vmin, vmax=vmax)
10023
+ # im = ax.imshow(self.array.transpose(), origin='lower', cmap=self.mypal,
10024
+ # extent=(self.origx, self.origx + self.dx * self.nbx, self.origy, self.origy + self.dy * self.nby))
10025
+ im = ax.imshow(self.array.transpose(),
10026
+ origin='lower',
10027
+ norm = self.mypal.norm,
10028
+ cmap=self.mypal.cmap,
10029
+ interpolation='none',
10030
+ extent=(self.origx,
10031
+ self.origx + self.dx * self.nbx,
10032
+ self.origy,
10033
+ self.origy + self.dy * self.nby))
10034
+ else:
10035
+ im = ax.imshow(self.array.transpose(),
10036
+ origin='lower',
10037
+ cmap=self.mypal,
10038
+ extent=(self.origx,
10039
+ self.origx + self.dx * self.nbx,
10040
+ self.origy,
10041
+ self.origy + self.dy * self.nby),
10042
+ vmin=vmin, vmax=vmax)
9941
10043
  ax.set_aspect('equal')
9942
10044
 
9943
10045
  if getdata_im:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wolfhece
3
- Version: 2.2.4
3
+ Version: 2.2.5
4
4
  Author-email: Pierre Archambeau <pierre.archambeau@uliege.be>
5
5
  Project-URL: Homepage, https://uee.uliege.be/hece
6
6
  Project-URL: Issues, https://uee.uliege.be/hece
@@ -12,13 +12,13 @@ wolfhece/PyDraw.py,sha256=mCIcXgtTT3vvixO_bvo76HlgK9p1MKk1GljGGIuIosw,631732
12
12
  wolfhece/PyGui.py,sha256=IU97wVlmer3Q2MpWbJv4MQWH7nYbc5uN4pRzhr4jdlM,145197
13
13
  wolfhece/PyGuiHydrology.py,sha256=sKafpOopBg50L5llZCI_fZtbebVTDtxvoRI6-osUwhg,14745
14
14
  wolfhece/PyHydrographs.py,sha256=1P5XAURNqCvtSsMQXhOn1ihjTpr725sRsZdlCEhhk6M,3730
15
- wolfhece/PyPalette.py,sha256=SnzMfzpVblbvq1kItLp52jufk6-R1b0QX3fF6RUKKT4,34842
15
+ wolfhece/PyPalette.py,sha256=k9b_95GYD0USQ8DS5zGXeZ577712U6772kmhEbJtlXw,35406
16
16
  wolfhece/PyParams.py,sha256=Dh9C_WYICMjo3m9roRySsu8ZgFzzYhSr6RpbaXZni0M,99423
17
17
  wolfhece/PyPictures.py,sha256=m1kY0saW6Y9Q0bDCo47lW6XxDkBrbQG-Fd8uVn8G5ic,2514
18
18
  wolfhece/PyTranslate.py,sha256=4appkmNeHHZLFmUtaA_k5_5QL-5ymxnbVN4R2OblmtE,622
19
19
  wolfhece/PyVertex.py,sha256=Ym42pHWwEVv6Fu5v-OzhlHiQB46DnvLf9MUe_c3lbR0,45610
20
20
  wolfhece/PyVertexvectors.py,sha256=0lt0YyHIz_IxgXqdqPlTDruDwjeP6L1Dw6B2Q35a8kQ,325801
21
- wolfhece/PyWMS.py,sha256=_HwJh3WVc0eHNCOPlQx40TVCdfXQF86Lb-_qpbzTC2M,8829
21
+ wolfhece/PyWMS.py,sha256=LWkQk3R7miiVal-n5K5P5ClSQJA_vi5ImBxYGuxCx9A,9122
22
22
  wolfhece/RatingCurve.py,sha256=bUjIrQjvIjkD4V-z8bZmA6pe1ILtYNM0-3fT6YUY1RU,22498
23
23
  wolfhece/RatingCurveData.py,sha256=5UvnIm89BwqjnEbLCcY3CA8WoFd_xHJbooNy62fX5iY,57660
24
24
  wolfhece/RatingCurve_xml.py,sha256=cUjReVMHFKtakA2wVey5zz6lCgHlSr72y7ZfswZDvTM,33891
@@ -41,7 +41,7 @@ wolfhece/ins.py,sha256=uUeLMS1n3GPnfJhxl0Z2l-UXpmPUgthuwct282OOEzk,36184
41
41
  wolfhece/irm_qdf.py,sha256=DMdDEAYbgYxApObm6w-dZbBmA8ec6PghBLXR2lUEZLc,27457
42
42
  wolfhece/ismember.py,sha256=fkLvaH9fhx-p0QrlEzqa6ySO-ios3ysjAgXVXzLgSpY,2482
43
43
  wolfhece/lagrange_multiplier.py,sha256=0G-M7b2tGzLx9v0oNYYq4_tLAiHcs_39B4o4W3TUVWM,6567
44
- wolfhece/lifewatch.py,sha256=TOqmbD_fuxXLvrnx401z5OxMqOBBSn-7Yg6flndWXFE,3324
44
+ wolfhece/lifewatch.py,sha256=AszCItQCq3Z3d-gznsjtYU9FIKJaWAcr1gafBxb-qHs,16265
45
45
  wolfhece/matplotlib_fig.py,sha256=vnFI6sghw9N9jKhR8X1Z4aWli_5fPNylZQtFuujFJDY,84075
46
46
  wolfhece/multiprojects.py,sha256=Sd6Bl6YP33jlR79A6rvSLu23vq8sqbFYL8lWuVPkEpE,21549
47
47
  wolfhece/picc.py,sha256=0X_pzhSBoVxgtTfJ37pkOQO3Vbr9yurPaD1nVeurx8k,8531
@@ -58,7 +58,7 @@ wolfhece/rain_SPWMI.py,sha256=qCfcmF7LajloOaCwnTrrSMzyME03YyilmRUOqrPrv3U,13846
58
58
  wolfhece/textpillow.py,sha256=map7HsGYML_o5NHRdFg2s_TVQed_lDnpYNDv27MM0Vw,14130
59
59
  wolfhece/tools2d_dll.py,sha256=oU0m9XYAf4CZsMoB68IuKeE6SQh-AqY7O5NVED8r9uw,13125
60
60
  wolfhece/tools_mpl.py,sha256=gQ3Jg1iuZiecmMqa5Eli2ZLSkttu68VXL8YmMDBaEYU,564
61
- wolfhece/wolf_array.py,sha256=t6-gLy1Op3Lx6Nmxv-Yo7cQIW62BinFDRP7x6Qpb5u8,486606
61
+ wolfhece/wolf_array.py,sha256=YtrTYzYhJKMewxDkkaiVjFfd7XBq6DNBE3uHdIpgmJY,490755
62
62
  wolfhece/wolf_hist.py,sha256=7jeVrgSkM3ErJO6SRMH_PGzfLjIdw8vTy87kesldggk,3582
63
63
  wolfhece/wolf_texture.py,sha256=IvFtekT5iLU2sivZOOlJXpE4CevjTQYSxHaOp4cH_wI,17723
64
64
  wolfhece/wolf_tiles.py,sha256=v-HohqaWuMYdn75XLnA22dlloAG90iwnIqrgnB0ASQ4,10488
@@ -86,7 +86,7 @@ wolfhece/apps/curvedigitizer.py,sha256=lEJJwgAfulrrWQc-U6ij6sj59hWN3SZl4Yu1kQxVz
86
86
  wolfhece/apps/hydrometry.py,sha256=lhhJsFeb4zGL4bNQTs0co85OQ_6ssL1Oy0OUJCzhfYE,656
87
87
  wolfhece/apps/isocurrent.py,sha256=dagmGR8ja9QQ1gwz_8fU-N052hIw-W0mWGVkzLu6C7I,4247
88
88
  wolfhece/apps/splashscreen.py,sha256=eCPAUYscZPWDYKBHDBWum_VIcE7WXOCBe1GLHL3KUmU,3088
89
- wolfhece/apps/version.py,sha256=ZQEA7v7GBHHT9_GNdNPgeQvXG9KlV9xxGk8ae-2UVaU,387
89
+ wolfhece/apps/version.py,sha256=imjv2iLmDitvnvE91s2oHHPHEwKdSfXQkxnrkAddHV4,387
90
90
  wolfhece/apps/wolf.py,sha256=j_CgvsL8rwixbVvVD5Z0s7m7cHZ86gmFLojKGuetMls,729
91
91
  wolfhece/apps/wolf2D.py,sha256=4z_OPQ3IgaLtjexjMKX9ppvqEYyjFLt1hcfFABy3-jU,703
92
92
  wolfhece/apps/wolf_logo.bmp,sha256=ruJ4MA51CpGO_AYUp_dB4SWKHelvhOvd7Q8NrVOjDJk,3126
@@ -249,6 +249,7 @@ wolfhece/models/6_coul.pal,sha256=z7NK2dg0tAQBUweRQV54dIwJbPM1U5y1AR2LLw19Idw,14
249
249
  wolfhece/models/7_coul.pal,sha256=XTnnUyCE8ONokScB2YzYDnSTft7E6sppmr7P-XwMsCE,205
250
250
  wolfhece/models/Froude.pal,sha256=FmrvUI01fv4k0ygqDJEYdC1M9Fn72Sr6Sn2IlZAm0KA,138
251
251
  wolfhece/models/HECE_169.pptx,sha256=OWJtsWz504A-REFaaxw8lwStHyQU2l7KEeiE7IZvtbk,3396930
252
+ wolfhece/models/Lifewatch.pal,sha256=ZSmJtTvwU37kDlNgtc6ohh2M19EhVSbdeyiJIS4wId8,292
252
253
  wolfhece/models/Risque_TAF.pal,sha256=Tnbx-_gIyCszzwmwo0I9vvFfVVKmATjFXS89AoZwTOs,135
253
254
  wolfhece/models/blue.pal,sha256=s9-wEPzSiKMMHuKofUB2FPjmyO7HfGM2xWaUJwsKAY8,39
254
255
  wolfhece/models/blues.pal,sha256=0ZEU1p9EcKwsCvNjU0s9_9aVakqQ71LDNoddVBAluEs,509
@@ -313,8 +314,8 @@ wolfhece/ui/wolf_multiselection_collapsiblepane.py,sha256=8PlMYrb_8jI8h9F0_EagpM
313
314
  wolfhece/ui/wolf_times_selection_comparison_models.py,sha256=ORy7fz4dcp691qKzaOZHrRLZ0uXNhL-LIHxmpDGL6BI,5007
314
315
  wolfhece/wintab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
315
316
  wolfhece/wintab/wintab.py,sha256=8A-JNONV6ujgsgG3lM5Uw-pVgglPATwKs86oBzzljoc,7179
316
- wolfhece-2.2.4.dist-info/METADATA,sha256=JJuO3xTYTfqTWVbcVcF-dLDr89VgsykNuEikAt6elpk,2744
317
- wolfhece-2.2.4.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
318
- wolfhece-2.2.4.dist-info/entry_points.txt,sha256=ZZ-aSfbpdcmo-wo84lRFzBN7LaSnD1XRGSaAKVX-Gpc,522
319
- wolfhece-2.2.4.dist-info/top_level.txt,sha256=EfqZXMVCn7eILUzx9xsEu2oBbSo9liWPFWjIHik0iCI,9
320
- wolfhece-2.2.4.dist-info/RECORD,,
317
+ wolfhece-2.2.5.dist-info/METADATA,sha256=9v84rbmTr0ngAwq3vgROTfRNq7plNxUkXPxWVWhoq-E,2744
318
+ wolfhece-2.2.5.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
319
+ wolfhece-2.2.5.dist-info/entry_points.txt,sha256=ZZ-aSfbpdcmo-wo84lRFzBN7LaSnD1XRGSaAKVX-Gpc,522
320
+ wolfhece-2.2.5.dist-info/top_level.txt,sha256=EfqZXMVCn7eILUzx9xsEu2oBbSo9liWPFWjIHik0iCI,9
321
+ wolfhece-2.2.5.dist-info/RECORD,,