wolfhece 2.2.3__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/PyDraw.py +65 -8
- wolfhece/PyPalette.py +26 -2
- wolfhece/PyVertex.py +15 -2
- wolfhece/PyWMS.py +13 -2
- wolfhece/apps/version.py +1 -1
- wolfhece/drowning_victims/{Class.py → drowning_class.py} +80 -121
- wolfhece/lifewatch.py +398 -63
- wolfhece/models/Lifewatch.pal +61 -0
- wolfhece/wolf_array.py +116 -14
- {wolfhece-2.2.3.dist-info → wolfhece-2.2.5.dist-info}/METADATA +1 -1
- {wolfhece-2.2.3.dist-info → wolfhece-2.2.5.dist-info}/RECORD +15 -14
- /wolfhece/drowning_victims/{Functions.py → drowning_functions.py} +0 -0
- {wolfhece-2.2.3.dist-info → wolfhece-2.2.5.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.3.dist-info → wolfhece-2.2.5.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.3.dist-info → wolfhece-2.2.5.dist-info}/top_level.txt +0 -0
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
|