mapchete-eo 2025.7.0__py2.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.
- mapchete_eo/__init__.py +1 -0
- mapchete_eo/archives/__init__.py +0 -0
- mapchete_eo/archives/base.py +65 -0
- mapchete_eo/array/__init__.py +0 -0
- mapchete_eo/array/buffer.py +16 -0
- mapchete_eo/array/color.py +29 -0
- mapchete_eo/array/convert.py +157 -0
- mapchete_eo/base.py +528 -0
- mapchete_eo/blacklist.txt +175 -0
- mapchete_eo/cli/__init__.py +30 -0
- mapchete_eo/cli/bounds.py +22 -0
- mapchete_eo/cli/options_arguments.py +243 -0
- mapchete_eo/cli/s2_brdf.py +77 -0
- mapchete_eo/cli/s2_cat_results.py +146 -0
- mapchete_eo/cli/s2_find_broken_products.py +93 -0
- mapchete_eo/cli/s2_jp2_static_catalog.py +166 -0
- mapchete_eo/cli/s2_mask.py +71 -0
- mapchete_eo/cli/s2_mgrs.py +45 -0
- mapchete_eo/cli/s2_rgb.py +114 -0
- mapchete_eo/cli/s2_verify.py +129 -0
- mapchete_eo/cli/static_catalog.py +123 -0
- mapchete_eo/eostac.py +30 -0
- mapchete_eo/exceptions.py +87 -0
- mapchete_eo/geometry.py +271 -0
- mapchete_eo/image_operations/__init__.py +12 -0
- mapchete_eo/image_operations/color_correction.py +136 -0
- mapchete_eo/image_operations/compositing.py +247 -0
- mapchete_eo/image_operations/dtype_scale.py +43 -0
- mapchete_eo/image_operations/fillnodata.py +130 -0
- mapchete_eo/image_operations/filters.py +319 -0
- mapchete_eo/image_operations/linear_normalization.py +81 -0
- mapchete_eo/image_operations/sigmoidal.py +114 -0
- mapchete_eo/io/__init__.py +37 -0
- mapchete_eo/io/assets.py +492 -0
- mapchete_eo/io/items.py +147 -0
- mapchete_eo/io/levelled_cubes.py +228 -0
- mapchete_eo/io/path.py +144 -0
- mapchete_eo/io/products.py +413 -0
- mapchete_eo/io/profiles.py +45 -0
- mapchete_eo/known_catalogs.py +42 -0
- mapchete_eo/platforms/sentinel2/__init__.py +17 -0
- mapchete_eo/platforms/sentinel2/archives.py +190 -0
- mapchete_eo/platforms/sentinel2/bandpass_adjustment.py +104 -0
- mapchete_eo/platforms/sentinel2/brdf/__init__.py +8 -0
- mapchete_eo/platforms/sentinel2/brdf/config.py +32 -0
- mapchete_eo/platforms/sentinel2/brdf/correction.py +260 -0
- mapchete_eo/platforms/sentinel2/brdf/hls.py +251 -0
- mapchete_eo/platforms/sentinel2/brdf/models.py +44 -0
- mapchete_eo/platforms/sentinel2/brdf/protocols.py +27 -0
- mapchete_eo/platforms/sentinel2/brdf/ross_thick.py +136 -0
- mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py +76 -0
- mapchete_eo/platforms/sentinel2/config.py +181 -0
- mapchete_eo/platforms/sentinel2/driver.py +78 -0
- mapchete_eo/platforms/sentinel2/masks.py +325 -0
- mapchete_eo/platforms/sentinel2/metadata_parser.py +734 -0
- mapchete_eo/platforms/sentinel2/path_mappers/__init__.py +29 -0
- mapchete_eo/platforms/sentinel2/path_mappers/base.py +56 -0
- mapchete_eo/platforms/sentinel2/path_mappers/earthsearch.py +34 -0
- mapchete_eo/platforms/sentinel2/path_mappers/metadata_xml.py +135 -0
- mapchete_eo/platforms/sentinel2/path_mappers/sinergise.py +105 -0
- mapchete_eo/platforms/sentinel2/preprocessing_tasks.py +26 -0
- mapchete_eo/platforms/sentinel2/processing_baseline.py +160 -0
- mapchete_eo/platforms/sentinel2/product.py +669 -0
- mapchete_eo/platforms/sentinel2/types.py +109 -0
- mapchete_eo/processes/__init__.py +0 -0
- mapchete_eo/processes/config.py +51 -0
- mapchete_eo/processes/dtype_scale.py +112 -0
- mapchete_eo/processes/eo_to_xarray.py +19 -0
- mapchete_eo/processes/merge_rasters.py +235 -0
- mapchete_eo/product.py +278 -0
- mapchete_eo/protocols.py +56 -0
- mapchete_eo/search/__init__.py +14 -0
- mapchete_eo/search/base.py +222 -0
- mapchete_eo/search/config.py +42 -0
- mapchete_eo/search/s2_mgrs.py +314 -0
- mapchete_eo/search/stac_search.py +251 -0
- mapchete_eo/search/stac_static.py +236 -0
- mapchete_eo/search/utm_search.py +251 -0
- mapchete_eo/settings.py +24 -0
- mapchete_eo/sort.py +48 -0
- mapchete_eo/time.py +53 -0
- mapchete_eo/types.py +73 -0
- mapchete_eo-2025.7.0.dist-info/METADATA +38 -0
- mapchete_eo-2025.7.0.dist-info/RECORD +87 -0
- mapchete_eo-2025.7.0.dist-info/WHEEL +5 -0
- mapchete_eo-2025.7.0.dist-info/entry_points.txt +11 -0
- mapchete_eo-2025.7.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from PIL import Image, ImageFilter
|
|
5
|
+
from rasterio.plot import reshape_as_image, reshape_as_raster
|
|
6
|
+
from scipy import ndimage
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# filters for 8 bit data:
|
|
12
|
+
#########################
|
|
13
|
+
|
|
14
|
+
FILTERS = {
|
|
15
|
+
"blur": ImageFilter.BLUR,
|
|
16
|
+
"contour": ImageFilter.CONTOUR,
|
|
17
|
+
"detail": ImageFilter.DETAIL,
|
|
18
|
+
"edge_enhance": ImageFilter.EDGE_ENHANCE,
|
|
19
|
+
"edge_enhance_more": ImageFilter.EDGE_ENHANCE_MORE,
|
|
20
|
+
"emboss": ImageFilter.EMBOSS,
|
|
21
|
+
"find_edges": ImageFilter.FIND_EDGES,
|
|
22
|
+
"sharpen": ImageFilter.SHARPEN,
|
|
23
|
+
"smooth": ImageFilter.SMOOTH,
|
|
24
|
+
"smooth_more": ImageFilter.SMOOTH_MORE,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
FILTER_FUNCTIONS = {
|
|
28
|
+
"unsharp_mask": ImageFilter.UnsharpMask,
|
|
29
|
+
"median": ImageFilter.MedianFilter,
|
|
30
|
+
"gaussian_blur": ImageFilter.GaussianBlur,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _apply_filter(arr: np.ndarray, img_filter: str, **kwargs) -> np.ndarray:
|
|
35
|
+
if arr.dtype != "uint8":
|
|
36
|
+
raise TypeError("input array type must be uint8")
|
|
37
|
+
if arr.ndim != 3:
|
|
38
|
+
raise TypeError("input array must be 3-dimensional")
|
|
39
|
+
if arr.shape[0] != 3:
|
|
40
|
+
raise TypeError("input array must have exactly three bands")
|
|
41
|
+
if img_filter in FILTERS:
|
|
42
|
+
return np.clip(
|
|
43
|
+
reshape_as_raster(
|
|
44
|
+
Image.fromarray(reshape_as_image(arr)).filter(FILTERS[img_filter])
|
|
45
|
+
),
|
|
46
|
+
1,
|
|
47
|
+
255,
|
|
48
|
+
).astype("uint8", copy=False)
|
|
49
|
+
elif img_filter in FILTER_FUNCTIONS:
|
|
50
|
+
return np.clip(
|
|
51
|
+
reshape_as_raster(
|
|
52
|
+
Image.fromarray(reshape_as_image(arr)).filter(
|
|
53
|
+
FILTER_FUNCTIONS[img_filter](**kwargs)
|
|
54
|
+
)
|
|
55
|
+
),
|
|
56
|
+
1,
|
|
57
|
+
255,
|
|
58
|
+
).astype("uint8", copy=False)
|
|
59
|
+
else:
|
|
60
|
+
raise KeyError(f"{img_filter} not found")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def blur(arr: np.ndarray) -> np.ndarray:
|
|
64
|
+
"""
|
|
65
|
+
Apply PIL blur filter to array and return.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
arr : 3-dimensional uint8 NumPy array
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
NumPy array
|
|
74
|
+
"""
|
|
75
|
+
return _apply_filter(arr, "blur")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def contour(arr: np.ndarray) -> np.ndarray:
|
|
79
|
+
"""
|
|
80
|
+
Apply PIL contour filter to array and return.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
arr : 3-dimensional uint8 NumPy array
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
NumPy array
|
|
89
|
+
"""
|
|
90
|
+
return _apply_filter(arr, "contour")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def detail(arr: np.ndarray) -> np.ndarray:
|
|
94
|
+
"""
|
|
95
|
+
Apply PIL detail filter to array and return.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
arr : 3-dimensional uint8 NumPy array
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
NumPy array
|
|
104
|
+
"""
|
|
105
|
+
return _apply_filter(arr, "detail")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def edge_enhance(arr: np.ndarray) -> np.ndarray:
|
|
109
|
+
"""
|
|
110
|
+
Apply PIL edge_enhance filter to array and return.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
arr : 3-dimensional uint8 NumPy array
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
NumPy array
|
|
119
|
+
"""
|
|
120
|
+
return _apply_filter(arr, "edge_enhance")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def edge_enhance_more(arr: np.ndarray) -> np.ndarray:
|
|
124
|
+
"""
|
|
125
|
+
Apply PIL edge_enhance_more filter to array and return.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
arr : 3-dimensional uint8 NumPy array
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
NumPy array
|
|
134
|
+
"""
|
|
135
|
+
return _apply_filter(arr, "edge_enhance_more")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def emboss(arr: np.ndarray) -> np.ndarray:
|
|
139
|
+
"""
|
|
140
|
+
Apply PIL emboss filter to array and return.
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
arr : 3-dimensional uint8 NumPy array
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
NumPy array
|
|
149
|
+
"""
|
|
150
|
+
return _apply_filter(arr, "emboss")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def find_edges(arr: np.ndarray) -> np.ndarray:
|
|
154
|
+
"""
|
|
155
|
+
Apply PIL find_edges filter to array and return.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
arr : 3-dimensional uint8 NumPy array
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
NumPy array
|
|
164
|
+
"""
|
|
165
|
+
return _apply_filter(arr, "find_edges")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def sharpen(arr: np.ndarray) -> np.ndarray:
|
|
169
|
+
"""
|
|
170
|
+
Apply PIL sharpen filter to array and return.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
arr : 3-dimensional uint8 NumPy array
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
NumPy array
|
|
179
|
+
"""
|
|
180
|
+
return _apply_filter(arr, "sharpen")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def smooth(arr: np.ndarray) -> np.ndarray:
|
|
184
|
+
"""
|
|
185
|
+
Apply PIL smooth filter to array and return.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
arr : 3-dimensional uint8 NumPy array
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
NumPy array
|
|
194
|
+
"""
|
|
195
|
+
return _apply_filter(arr, "smooth")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def smooth_more(arr: np.ndarray) -> np.ndarray:
|
|
199
|
+
"""
|
|
200
|
+
Apply PIL smooth_more filter to array and return.
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
arr : 3-dimensional uint8 NumPy array
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
NumPy array
|
|
209
|
+
"""
|
|
210
|
+
return _apply_filter(arr, "smooth_more")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def unsharp_mask(
|
|
214
|
+
arr: np.ndarray, radius: int = 2, percent: float = 150, threshold: float = 3
|
|
215
|
+
) -> np.ndarray:
|
|
216
|
+
"""
|
|
217
|
+
Apply PIL UnsharpMask filter to array and return.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
arr : 3-dimensional uint8 NumPy array
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
NumPy array
|
|
226
|
+
"""
|
|
227
|
+
return _apply_filter(
|
|
228
|
+
arr, "unsharp_mask", radius=radius, percent=percent, threshold=threshold
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def median(arr: np.ndarray, size: int = 3) -> np.ndarray:
|
|
233
|
+
"""
|
|
234
|
+
Apply PIL MedianFilter to array and return.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
arr : 3-dimensional uint8 NumPy array
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
NumPy array
|
|
243
|
+
"""
|
|
244
|
+
return _apply_filter(arr, "median", size=size)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def gaussian_blur(arr: np.ndarray, radius: int = 2) -> np.ndarray:
|
|
248
|
+
"""
|
|
249
|
+
Apply PIL GaussianBlur to array and return.
|
|
250
|
+
|
|
251
|
+
Parameters
|
|
252
|
+
----------
|
|
253
|
+
arr : 3-dimensional uint8 NumPy array
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
NumPy array
|
|
258
|
+
"""
|
|
259
|
+
return _apply_filter(arr, "gaussian_blur", radius=radius)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# filters for 16 bit data:
|
|
263
|
+
##########################
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def sharpen_16bit(arr: np.ndarray) -> np.ndarray:
|
|
267
|
+
# kernel_3x3_highpass = np.array([
|
|
268
|
+
# 0, -1, 0,
|
|
269
|
+
# -1, 5, -1,
|
|
270
|
+
# 0, -1, 0
|
|
271
|
+
# ]).reshape((3, 3))
|
|
272
|
+
# kernel_3x3_highpass = np.array([
|
|
273
|
+
# 0, -1/4, 0,
|
|
274
|
+
# -1/4, 2, -1/4,
|
|
275
|
+
# 0, -1/4, 0
|
|
276
|
+
# ]).reshape((3, 3))
|
|
277
|
+
# kernel_5x5_highpass = np.array([
|
|
278
|
+
# 0, -1, -1, -1, 0,
|
|
279
|
+
# -1, -2, -4, 2, -1,
|
|
280
|
+
# -1, -4, 13, -4, -1,
|
|
281
|
+
# -1, 2, -4, 2, -1,
|
|
282
|
+
# 0, -1, -1, -1, 0
|
|
283
|
+
# ]).reshape((5, 5))
|
|
284
|
+
# kernel_mean = np.array([
|
|
285
|
+
# 1, 1, 1,
|
|
286
|
+
# 1, 1, 1,
|
|
287
|
+
# 1, 1, 1
|
|
288
|
+
# ]).reshape((3, 3))
|
|
289
|
+
# kernel = np.array([
|
|
290
|
+
# [1, 1, 1],
|
|
291
|
+
# [1, 1, 0],
|
|
292
|
+
# [1, 0, 0]
|
|
293
|
+
# ]).reshape((3, 3))
|
|
294
|
+
# kernel = np.array([
|
|
295
|
+
# 0, -1, 0,
|
|
296
|
+
# -1, 8, -1,
|
|
297
|
+
# 0, -1, 0
|
|
298
|
+
# ]).reshape((3, 3))
|
|
299
|
+
# Various High Pass Filters
|
|
300
|
+
# b = ndimage.minimum_filter(b, 3)
|
|
301
|
+
# b = ndimage.percentile_filter(b, 50, 3)
|
|
302
|
+
# imgsharp = ndimage.convolve(b_smoothed, kernel_3x3_highpass, mode='nearest')
|
|
303
|
+
# imgsharp = ndimage.median_filter(imgsharp, 2)
|
|
304
|
+
# imgsharp = reshape_as_raster(np.asarray(imgsharp))
|
|
305
|
+
# Official SciPy unsharpen mask filter not working
|
|
306
|
+
|
|
307
|
+
# Unshapen Mask Filter, working version as the one above is not working
|
|
308
|
+
return np.stack(
|
|
309
|
+
[
|
|
310
|
+
ndimage.percentile_filter(
|
|
311
|
+
b
|
|
312
|
+
+ (b - ndimage.percentile_filter(b, 35, arr.shape[0], mode="nearest")),
|
|
313
|
+
45,
|
|
314
|
+
2,
|
|
315
|
+
mode="nearest",
|
|
316
|
+
)
|
|
317
|
+
for b in arr
|
|
318
|
+
]
|
|
319
|
+
).astype(arr.dtype, copy=False)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import Optional, Tuple
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.ma as ma
|
|
5
|
+
from numpy.typing import DTypeLike
|
|
6
|
+
from rasterio.dtypes import dtype_ranges
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def linear_normalization(
|
|
10
|
+
bands: ma.MaskedArray,
|
|
11
|
+
bands_minmax_values: Tuple[Tuple[int, int], Tuple[int, int], Tuple[int, int]] = (
|
|
12
|
+
(5, 3350),
|
|
13
|
+
(0, 3150),
|
|
14
|
+
(0, 3200),
|
|
15
|
+
),
|
|
16
|
+
out_dtype: DTypeLike = np.uint8,
|
|
17
|
+
out_min: Optional[int] = None,
|
|
18
|
+
) -> ma.MaskedArray:
|
|
19
|
+
"""
|
|
20
|
+
Scale and normalize bands to individual minimum and maximum values.
|
|
21
|
+
|
|
22
|
+
From eox_preprocessing.image_utils
|
|
23
|
+
|
|
24
|
+
See: https://en.wikipedia.org/wiki/Normalization_(image_processing)
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
bands : np.ndarray
|
|
29
|
+
Input bands as a 3D array.
|
|
30
|
+
bands_minmax_values : list of lists
|
|
31
|
+
Individual minimum and maximum values for each band. Must have the same length as
|
|
32
|
+
number of bands.
|
|
33
|
+
out_min : float or int
|
|
34
|
+
Override dtype minimum. Useful when nodata value is equal to dtype minimum (e.g. 0
|
|
35
|
+
at uint8). In that case out_min can be set to 1.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
scaled bands : ma.MaskedArray
|
|
40
|
+
"""
|
|
41
|
+
if len(bands_minmax_values) != bands.shape[0]:
|
|
42
|
+
raise ValueError("bands and bands_minmax_values must have the same length")
|
|
43
|
+
try:
|
|
44
|
+
if isinstance(out_dtype, str):
|
|
45
|
+
dtype_str = out_dtype
|
|
46
|
+
else:
|
|
47
|
+
dtype_str = str(out_dtype).split(".")[1].split("'")[0]
|
|
48
|
+
if out_min is None:
|
|
49
|
+
out_min, out_max = dtype_ranges[dtype_str]
|
|
50
|
+
else:
|
|
51
|
+
out_max = dtype_ranges[dtype_str][1]
|
|
52
|
+
except KeyError:
|
|
53
|
+
raise KeyError(f"invalid out_dtype: {out_dtype}")
|
|
54
|
+
|
|
55
|
+
# Clip the Input values first to avoid awkward data
|
|
56
|
+
clipped_bands = np.stack(
|
|
57
|
+
[
|
|
58
|
+
np.where(
|
|
59
|
+
np.where(b > b_max, b_max, b) < b_min,
|
|
60
|
+
b_min,
|
|
61
|
+
np.where(b > b_max, b_max, b),
|
|
62
|
+
)
|
|
63
|
+
for b, (b_min, b_max) in zip(bands, bands_minmax_values)
|
|
64
|
+
]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
lin_normalized = np.clip(
|
|
68
|
+
np.stack(
|
|
69
|
+
[
|
|
70
|
+
(b - b_min) * (out_max / (b_max - b_min)) + out_min
|
|
71
|
+
for b, (b_min, b_max) in zip(clipped_bands, bands_minmax_values)
|
|
72
|
+
]
|
|
73
|
+
),
|
|
74
|
+
out_min,
|
|
75
|
+
out_max,
|
|
76
|
+
).astype(out_dtype, copy=False)
|
|
77
|
+
|
|
78
|
+
# (2) clip and return using the original nodata mask
|
|
79
|
+
return ma.MaskedArray(
|
|
80
|
+
data=lin_normalized, mask=bands.mask, fill_value=bands.fill_value
|
|
81
|
+
)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy.typing import DTypeLike
|
|
3
|
+
|
|
4
|
+
# The type to be used for all intermediate math
|
|
5
|
+
# operations. Should be a float because values will
|
|
6
|
+
# be scaled to the range 0..1 for all work.
|
|
7
|
+
|
|
8
|
+
math_type = np.float16
|
|
9
|
+
|
|
10
|
+
epsilon = float(np.finfo(math_type).eps)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def sigmoidal(
|
|
14
|
+
arr: np.ndarray,
|
|
15
|
+
contrast: int,
|
|
16
|
+
bias: float,
|
|
17
|
+
out_dtype: DTypeLike = np.float16,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Taken from rio-color (consult sevcikp for changes)
|
|
21
|
+
|
|
22
|
+
Sigmoidal contrast is type of contrast control that
|
|
23
|
+
adjusts the contrast without saturating highlights or shadows.
|
|
24
|
+
It allows control over two factors:
|
|
25
|
+
the contrast range from light to dark, and where the middle value
|
|
26
|
+
of the mid-tones falls. The result is a non-linear and smooth
|
|
27
|
+
contrast change.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
arr : ndarray, float, 0 .. 1
|
|
32
|
+
Array of color values to adjust
|
|
33
|
+
contrast : integer
|
|
34
|
+
Enhances the intensity differences between the lighter and darker
|
|
35
|
+
elements of the image. For example, 0 is none, 3 is typical and
|
|
36
|
+
20 is a lot.
|
|
37
|
+
bias : float, between 0 and 1
|
|
38
|
+
Threshold level for the contrast function to center on
|
|
39
|
+
(typically centered at 0.5)
|
|
40
|
+
|
|
41
|
+
Notes
|
|
42
|
+
----------
|
|
43
|
+
|
|
44
|
+
Sigmoidal contrast is based on the sigmoidal transfer function:
|
|
45
|
+
|
|
46
|
+
.. math:: g(u) = ( 1/(1 + e^{- \alpha * u + \beta)})
|
|
47
|
+
|
|
48
|
+
This sigmoid function is scaled so that the output is bound by
|
|
49
|
+
the interval [0, 1].
|
|
50
|
+
|
|
51
|
+
.. math:: ( 1/(1 + e^(\beta * (\alpha - u))) - 1/(1 + e^(\beta * \alpha)))/
|
|
52
|
+
( 1/(1 + e^(\beta*(\alpha - 1))) - 1/(1 + e^(\beta * \alpha)) )
|
|
53
|
+
|
|
54
|
+
Where :math: `\alpha` is the threshold level, and :math: `\beta` the
|
|
55
|
+
contrast factor to be applied.
|
|
56
|
+
|
|
57
|
+
References
|
|
58
|
+
----------
|
|
59
|
+
.. [CT] Hany Farid "Fundamentals of Image Processing"
|
|
60
|
+
http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
if isinstance(out_dtype, str):
|
|
64
|
+
out_dtype = np.dtype(getattr(np, out_dtype))
|
|
65
|
+
if not isinstance(out_dtype, np.dtype):
|
|
66
|
+
raise TypeError(
|
|
67
|
+
f"Harmonization dtype needs to be valid numpy dtype is: {type(out_dtype)}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if (arr.max() > 1.0 + epsilon) or (arr.min() < 0 - epsilon):
|
|
71
|
+
raise ValueError("Input array must have float values between 0 and 1")
|
|
72
|
+
|
|
73
|
+
if (bias > 1.0 + epsilon) or (bias < 0 - epsilon):
|
|
74
|
+
raise ValueError("bias must be a scalar float between 0 and 1")
|
|
75
|
+
|
|
76
|
+
alpha, beta = bias, contrast
|
|
77
|
+
# We use the names a and b to match documentation.
|
|
78
|
+
|
|
79
|
+
if alpha == 0.0:
|
|
80
|
+
alpha = epsilon
|
|
81
|
+
|
|
82
|
+
if beta == 0.0:
|
|
83
|
+
return arr.astype(out_dtype, copy=False)
|
|
84
|
+
|
|
85
|
+
np.seterr(divide="ignore", invalid="ignore")
|
|
86
|
+
if beta > 0:
|
|
87
|
+
numerator = 1 / (1 + np.exp(beta * (alpha - arr))) - 1 / (
|
|
88
|
+
1 + np.exp(beta * alpha)
|
|
89
|
+
)
|
|
90
|
+
denominator = 1 / (1 + np.exp(beta * (alpha - 1))) - 1 / (
|
|
91
|
+
1 + np.exp(beta * alpha)
|
|
92
|
+
)
|
|
93
|
+
return (numerator / denominator).astype(out_dtype, copy=False)
|
|
94
|
+
|
|
95
|
+
else:
|
|
96
|
+
# Inverse sigmoidal function:
|
|
97
|
+
# todo: account for 0s
|
|
98
|
+
return (
|
|
99
|
+
(
|
|
100
|
+
(beta * alpha)
|
|
101
|
+
- np.log(
|
|
102
|
+
(
|
|
103
|
+
1
|
|
104
|
+
/ (
|
|
105
|
+
(arr / (1 + np.exp(beta * alpha - beta)))
|
|
106
|
+
- (arr / (1 + np.exp(beta * alpha)))
|
|
107
|
+
+ (1 / (1 + np.exp(beta * alpha)))
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
- 1
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
/ beta
|
|
114
|
+
).astype(out_dtype, copy=False)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from mapchete_eo.io.assets import (
|
|
2
|
+
convert_asset,
|
|
3
|
+
copy_asset,
|
|
4
|
+
get_assets,
|
|
5
|
+
read_mask_as_raster,
|
|
6
|
+
)
|
|
7
|
+
from mapchete_eo.io.items import get_item_property, item_fix_footprint, item_to_np_array
|
|
8
|
+
from mapchete_eo.io.levelled_cubes import (
|
|
9
|
+
read_levelled_cube_to_np_array,
|
|
10
|
+
read_levelled_cube_to_xarray,
|
|
11
|
+
)
|
|
12
|
+
from mapchete_eo.io.path import get_product_cache_path, open_xml, path_in_paths
|
|
13
|
+
from mapchete_eo.io.products import (
|
|
14
|
+
merge_products,
|
|
15
|
+
products_to_np_array,
|
|
16
|
+
products_to_slices,
|
|
17
|
+
products_to_xarray,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"get_assets",
|
|
22
|
+
"convert_asset",
|
|
23
|
+
"copy_asset",
|
|
24
|
+
"item_to_np_array",
|
|
25
|
+
"products_to_xarray",
|
|
26
|
+
"products_to_np_array",
|
|
27
|
+
"products_to_slices",
|
|
28
|
+
"merge_products",
|
|
29
|
+
"get_item_property",
|
|
30
|
+
"item_fix_footprint",
|
|
31
|
+
"open_xml",
|
|
32
|
+
"get_product_cache_path",
|
|
33
|
+
"path_in_paths",
|
|
34
|
+
"read_mask_as_raster",
|
|
35
|
+
"read_levelled_cube_to_np_array",
|
|
36
|
+
"read_levelled_cube_to_xarray",
|
|
37
|
+
]
|