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 +26 -2
- wolfhece/PyWMS.py +13 -2
- wolfhece/apps/version.py +1 -1
- wolfhece/lifewatch.py +398 -63
- wolfhece/models/Lifewatch.pal +61 -0
- wolfhece/wolf_array.py +116 -14
- {wolfhece-2.2.4.dist-info → wolfhece-2.2.5.dist-info}/METADATA +1 -1
- {wolfhece-2.2.4.dist-info → wolfhece-2.2.5.dist-info}/RECORD +11 -10
- {wolfhece-2.2.4.dist-info → wolfhece-2.2.5.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.4.dist-info → wolfhece-2.2.5.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.4.dist-info → wolfhece-2.2.5.dist-info}/top_level.txt +0 -0
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])/
|
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
|
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=
|
264
|
+
format=format,
|
254
265
|
transparent=False)
|
255
266
|
return BytesIO(img.read())
|
256
267
|
except:
|
wolfhece/apps/version.py
CHANGED
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
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
9938
|
-
|
9939
|
-
|
9940
|
-
|
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:
|
@@ -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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
317
|
-
wolfhece-2.2.
|
318
|
-
wolfhece-2.2.
|
319
|
-
wolfhece-2.2.
|
320
|
-
wolfhece-2.2.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|