mapchete-eo 2025.8.2__py2.py3-none-any.whl → 2025.8.3__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 -1
- mapchete_eo/image_operations/blend_functions.py +579 -0
- mapchete_eo/image_operations/compositing.py +33 -28
- mapchete_eo/image_operations/fillnodata.py +1 -1
- {mapchete_eo-2025.8.2.dist-info → mapchete_eo-2025.8.3.dist-info}/METADATA +5 -1
- {mapchete_eo-2025.8.2.dist-info → mapchete_eo-2025.8.3.dist-info}/RECORD +9 -10
- mapchete_eo/image_operations/blend_modes/blending_functions.py +0 -198
- mapchete_eo/image_operations/blend_modes/type_checks.py +0 -99
- {mapchete_eo-2025.8.2.dist-info → mapchete_eo-2025.8.3.dist-info}/WHEEL +0 -0
- {mapchete_eo-2025.8.2.dist-info → mapchete_eo-2025.8.3.dist-info}/entry_points.txt +0 -0
- {mapchete_eo-2025.8.2.dist-info → mapchete_eo-2025.8.3.dist-info}/licenses/LICENSE +0 -0
mapchete_eo/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2025.8.
|
|
1
|
+
__version__ = "2025.8.3"
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
Original LICENSE:
|
|
4
|
+
|
|
5
|
+
MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2016 Florian Roscheck
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
10
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation th
|
|
11
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
12
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
|
15
|
+
Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
18
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
21
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _compose_alpha(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
29
|
+
"""Calculate alpha composition ratio between two images."""
|
|
30
|
+
|
|
31
|
+
comp_alpha = np.minimum(fg[:, :, 3], bg[:, :, 3]) * opacity
|
|
32
|
+
new_alpha = fg[:, :, 3] + (1.0 - fg[:, :, 3]) * comp_alpha
|
|
33
|
+
np.seterr(divide="ignore", invalid="ignore")
|
|
34
|
+
ratio = comp_alpha / new_alpha
|
|
35
|
+
ratio[np.isnan(ratio)] = 0.0
|
|
36
|
+
return ratio
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def normal(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
40
|
+
"""Apply "normal" blending mode of a layer on an image.
|
|
41
|
+
|
|
42
|
+
See Also:
|
|
43
|
+
Find more information on `Wikipedia <https://en.wikipedia.org/wiki/Alpha_compositing#Description>`__.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
47
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
48
|
+
opacity(float): Desired opacity of layer for blending
|
|
49
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
50
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
51
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Extract alpha-channels and apply opacity
|
|
58
|
+
fg_alp = np.expand_dims(fg[:, :, 3], 2) # alpha of b, prepared for broadcasting
|
|
59
|
+
bg_alp = (
|
|
60
|
+
np.expand_dims(bg[:, :, 3], 2) * opacity
|
|
61
|
+
) # alpha of a, prepared for broadcasting
|
|
62
|
+
|
|
63
|
+
# Blend images
|
|
64
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
65
|
+
img_out = (bg[:, :, :3] * bg_alp + fg[:, :, :3] * fg_alp * (1 - bg_alp)) / (
|
|
66
|
+
bg_alp + fg_alp * (1 - bg_alp)
|
|
67
|
+
)
|
|
68
|
+
img_out[np.isnan(img_out)] = 0 # replace NaNs with 0
|
|
69
|
+
|
|
70
|
+
# Blend alpha
|
|
71
|
+
cout_alp = bg_alp + fg_alp * (1 - bg_alp)
|
|
72
|
+
|
|
73
|
+
# Combine image and alpha
|
|
74
|
+
img_out = np.dstack((img_out, cout_alp))
|
|
75
|
+
|
|
76
|
+
np.nan_to_num(img_out, copy=False)
|
|
77
|
+
|
|
78
|
+
return img_out
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def soft_light(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
82
|
+
"""Apply soft light blending mode of a layer on an image.
|
|
83
|
+
|
|
84
|
+
See Also:
|
|
85
|
+
Find more information on
|
|
86
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Soft_Light>`__.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
90
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
91
|
+
opacity(float): Desired opacity of layer for blending
|
|
92
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
93
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
94
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
102
|
+
|
|
103
|
+
# The following code does this:
|
|
104
|
+
# multiply = fg[:, :, :3]*bg[:, :, :3]
|
|
105
|
+
# screen = 1.0 - (1.0-fg[:, :, :3])*(1.0-bg[:, :, :3])
|
|
106
|
+
# comp = (1.0 - fg[:, :, :3]) * multiply + fg[:, :, :3] * screen
|
|
107
|
+
# ratio_rs = np.reshape(np.repeat(ratio,3),comp.shape)
|
|
108
|
+
# img_out = comp*ratio_rs + fg[:, :, :3] * (1.0-ratio_rs)
|
|
109
|
+
|
|
110
|
+
comp = (1.0 - fg[:, :, :3]) * fg[:, :, :3] * bg[:, :, :3] + fg[:, :, :3] * (
|
|
111
|
+
1.0 - (1.0 - fg[:, :, :3]) * (1.0 - bg[:, :, :3])
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
ratio_rs = np.reshape(
|
|
115
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
116
|
+
)
|
|
117
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
118
|
+
img_out = np.nan_to_num(
|
|
119
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
120
|
+
) # add alpha channel and replace nans
|
|
121
|
+
return img_out
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def lighten_only(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
125
|
+
"""Apply lighten only blending mode of a layer on an image.
|
|
126
|
+
|
|
127
|
+
See Also:
|
|
128
|
+
Find more information on
|
|
129
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Lighten_Only>`__.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
133
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
134
|
+
opacity(float): Desired opacity of layer for blending
|
|
135
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
136
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
137
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
141
|
+
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
145
|
+
|
|
146
|
+
comp = np.maximum(fg[:, :, :3], bg[:, :, :3])
|
|
147
|
+
|
|
148
|
+
ratio_rs = np.reshape(
|
|
149
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
150
|
+
)
|
|
151
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
152
|
+
img_out = np.nan_to_num(
|
|
153
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
154
|
+
) # add alpha channel and replace nans
|
|
155
|
+
return img_out
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def screen(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
159
|
+
"""Apply screen blending mode of a layer on an image.
|
|
160
|
+
|
|
161
|
+
See Also:
|
|
162
|
+
Find more information on
|
|
163
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Screen>`__.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
167
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
168
|
+
opacity(float): Desired opacity of layer for blending
|
|
169
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
170
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
171
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
175
|
+
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
179
|
+
|
|
180
|
+
comp = 1.0 - (1.0 - fg[:, :, :3]) * (1.0 - bg[:, :, :3])
|
|
181
|
+
|
|
182
|
+
ratio_rs = np.reshape(
|
|
183
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
184
|
+
)
|
|
185
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
186
|
+
img_out = np.nan_to_num(
|
|
187
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
188
|
+
) # add alpha channel and replace nans
|
|
189
|
+
return img_out
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def dodge(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
193
|
+
"""Apply dodge blending mode of a layer on an image.
|
|
194
|
+
|
|
195
|
+
See Also:
|
|
196
|
+
Find more information on
|
|
197
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Dodge_and_burn>`__.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
201
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
202
|
+
opacity(float): Desired opacity of layer for blending
|
|
203
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
204
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
205
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
213
|
+
|
|
214
|
+
comp = np.minimum(fg[:, :, :3] / (1.0 - bg[:, :, :3]), 1.0)
|
|
215
|
+
|
|
216
|
+
ratio_rs = np.reshape(
|
|
217
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
218
|
+
)
|
|
219
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
220
|
+
img_out = np.nan_to_num(
|
|
221
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
222
|
+
) # add alpha channel and replace nans
|
|
223
|
+
return img_out
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def addition(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
227
|
+
"""Apply addition blending mode of a layer on an image.
|
|
228
|
+
|
|
229
|
+
See Also:
|
|
230
|
+
Find more information on
|
|
231
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Addition>`__.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
235
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
236
|
+
opacity(float): Desired opacity of layer for blending
|
|
237
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
238
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
239
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
243
|
+
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
247
|
+
|
|
248
|
+
comp = fg[:, :, :3] + bg[:, :, :3]
|
|
249
|
+
|
|
250
|
+
ratio_rs = np.reshape(
|
|
251
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
252
|
+
)
|
|
253
|
+
img_out = np.clip(comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs), 0.0, 1.0)
|
|
254
|
+
img_out = np.nan_to_num(
|
|
255
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
256
|
+
) # add alpha channel and replace nans
|
|
257
|
+
return img_out
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def darken_only(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
261
|
+
"""Apply darken only blending mode of a layer on an image.
|
|
262
|
+
|
|
263
|
+
See Also:
|
|
264
|
+
Find more information on
|
|
265
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Darken_Only>`__.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
269
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
270
|
+
opacity(float): Desired opacity of layer for blending
|
|
271
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
272
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
273
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
277
|
+
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
281
|
+
|
|
282
|
+
comp = np.minimum(fg[:, :, :3], bg[:, :, :3])
|
|
283
|
+
|
|
284
|
+
ratio_rs = np.reshape(
|
|
285
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
286
|
+
)
|
|
287
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
288
|
+
img_out = np.nan_to_num(
|
|
289
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
290
|
+
) # add alpha channel and replace nans
|
|
291
|
+
return img_out
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def multiply(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
295
|
+
"""Apply multiply blending mode of a layer on an image.
|
|
296
|
+
|
|
297
|
+
See Also:
|
|
298
|
+
Find more information on
|
|
299
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Multiply>`__.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
303
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
304
|
+
opacity(float): Desired opacity of layer for blending
|
|
305
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
306
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
307
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
311
|
+
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
315
|
+
|
|
316
|
+
comp = np.clip(bg[:, :, :3] * fg[:, :, :3], 0.0, 1.0)
|
|
317
|
+
|
|
318
|
+
ratio_rs = np.reshape(
|
|
319
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
320
|
+
)
|
|
321
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
322
|
+
img_out = np.nan_to_num(
|
|
323
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
324
|
+
) # add alpha channel and replace nans
|
|
325
|
+
return img_out
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def hard_light(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
329
|
+
"""Apply hard light blending mode of a layer on an image.
|
|
330
|
+
|
|
331
|
+
See Also:
|
|
332
|
+
Find more information on
|
|
333
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Hard_Light>`__.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
337
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
338
|
+
opacity(float): Desired opacity of layer for blending
|
|
339
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
340
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
341
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
345
|
+
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
349
|
+
|
|
350
|
+
comp = np.greater(bg[:, :, :3], 0.5) * np.minimum(
|
|
351
|
+
1.0 - ((1.0 - fg[:, :, :3]) * (1.0 - (bg[:, :, :3] - 0.5) * 2.0)),
|
|
352
|
+
1.0,
|
|
353
|
+
) + np.logical_not(np.greater(bg[:, :, :3], 0.5)) * np.minimum(
|
|
354
|
+
fg[:, :, :3] * (bg[:, :, :3] * 2.0), 1.0
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
ratio_rs = np.reshape(
|
|
358
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
359
|
+
)
|
|
360
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
361
|
+
img_out = np.nan_to_num(
|
|
362
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
363
|
+
) # add alpha channel and replace nans
|
|
364
|
+
return img_out
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def difference(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
368
|
+
"""Apply difference blending mode of a layer on an image.
|
|
369
|
+
|
|
370
|
+
See Also:
|
|
371
|
+
Find more information on
|
|
372
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Difference>`__.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
376
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
377
|
+
opacity(float): Desired opacity of layer for blending
|
|
378
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
379
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
380
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
384
|
+
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
388
|
+
|
|
389
|
+
comp = fg[:, :, :3] - bg[:, :, :3]
|
|
390
|
+
comp[comp < 0.0] *= -1.0
|
|
391
|
+
|
|
392
|
+
ratio_rs = np.reshape(
|
|
393
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
394
|
+
)
|
|
395
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
396
|
+
img_out = np.nan_to_num(
|
|
397
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
398
|
+
) # add alpha channel and replace nans
|
|
399
|
+
return img_out
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def subtract(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
403
|
+
"""Apply subtract blending mode of a layer on an image.
|
|
404
|
+
|
|
405
|
+
See Also:
|
|
406
|
+
Find more information on
|
|
407
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Subtract>`__.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
411
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
412
|
+
opacity(float): Desired opacity of layer for blending
|
|
413
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
414
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
415
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
419
|
+
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
423
|
+
|
|
424
|
+
comp = fg[:, :, :3] - bg[:, :, :3]
|
|
425
|
+
|
|
426
|
+
ratio_rs = np.reshape(
|
|
427
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
428
|
+
)
|
|
429
|
+
img_out = np.clip(comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs), 0.0, 1.0)
|
|
430
|
+
img_out = np.nan_to_num(
|
|
431
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
432
|
+
) # add alpha channel and replace nans
|
|
433
|
+
return img_out
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def grain_extract(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
437
|
+
"""Apply grain extract blending mode of a layer on an image.
|
|
438
|
+
|
|
439
|
+
See Also:
|
|
440
|
+
Find more information in the `GIMP Documentation <https://docs.gimp.org/en/gimp-concepts-layer-modes.html>`__.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
444
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
445
|
+
opacity(float): Desired opacity of layer for blending
|
|
446
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
447
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
448
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
452
|
+
|
|
453
|
+
"""
|
|
454
|
+
|
|
455
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
456
|
+
|
|
457
|
+
comp = np.clip(fg[:, :, :3] - bg[:, :, :3] + 0.5, 0.0, 1.0)
|
|
458
|
+
|
|
459
|
+
ratio_rs = np.reshape(
|
|
460
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
461
|
+
)
|
|
462
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
463
|
+
img_out = np.nan_to_num(
|
|
464
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
465
|
+
) # add alpha channel and replace nans
|
|
466
|
+
return img_out
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def grain_merge(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
470
|
+
"""Apply grain merge blending mode of a layer on an image.
|
|
471
|
+
|
|
472
|
+
See Also:
|
|
473
|
+
Find more information in the `GIMP Documentation <https://docs.gimp.org/en/gimp-concepts-layer-modes.html>`__.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
477
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
478
|
+
opacity(float): Desired opacity of layer for blending
|
|
479
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
480
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
481
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
485
|
+
|
|
486
|
+
"""
|
|
487
|
+
|
|
488
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
489
|
+
|
|
490
|
+
comp = np.clip(fg[:, :, :3] + bg[:, :, :3] - 0.5, 0.0, 1.0)
|
|
491
|
+
|
|
492
|
+
ratio_rs = np.reshape(
|
|
493
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
494
|
+
)
|
|
495
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
496
|
+
img_out = np.nan_to_num(
|
|
497
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
498
|
+
) # add alpha channel and replace nans
|
|
499
|
+
return img_out
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def divide(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
503
|
+
"""Apply divide blending mode of a layer on an image.
|
|
504
|
+
|
|
505
|
+
See Also:
|
|
506
|
+
Find more information on
|
|
507
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=747749280#Divide>`__.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
511
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
512
|
+
opacity(float): Desired opacity of layer for blending
|
|
513
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
514
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
515
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
519
|
+
|
|
520
|
+
"""
|
|
521
|
+
|
|
522
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
523
|
+
|
|
524
|
+
comp = np.minimum(
|
|
525
|
+
(256.0 / 255.0 * fg[:, :, :3]) / (1.0 / 255.0 + bg[:, :, :3]),
|
|
526
|
+
1.0,
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
ratio_rs = np.reshape(
|
|
530
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
531
|
+
)
|
|
532
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
533
|
+
img_out = np.nan_to_num(
|
|
534
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
535
|
+
) # add alpha channel and replace nans
|
|
536
|
+
return img_out
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def overlay(fg: np.ndarray, bg: np.ndarray, opacity: float):
|
|
540
|
+
"""Apply overlay blending mode of a layer on an image.
|
|
541
|
+
|
|
542
|
+
Note:
|
|
543
|
+
The implementation of this method was changed in version 2.0.0. Previously, it would be identical to the
|
|
544
|
+
soft light blending mode. Now, it resembles the implementation on Wikipedia. You can still use the soft light
|
|
545
|
+
blending mode if you are looking for backwards compatibility.
|
|
546
|
+
|
|
547
|
+
See Also:
|
|
548
|
+
Find more information on
|
|
549
|
+
`Wikipedia <https://en.wikipedia.org/w/index.php?title=Blend_modes&oldid=868545948#Overlay>`__.
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
fg(3-dimensional numpy array of floats (r/g/b/a) in range 0-255.0): Image to be blended upon
|
|
553
|
+
bg(3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0): Layer to be blended with image
|
|
554
|
+
opacity(float): Desired opacity of layer for blending
|
|
555
|
+
disable_type_checks(bool): Whether type checks within the function should be disabled. Disabling the checks may
|
|
556
|
+
yield a slight performance improvement, but comes at the cost of user experience. If you are certain that
|
|
557
|
+
you are passing in the right arguments, you may set this argument to 'True'. Defaults to 'False'.
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
3-dimensional numpy array of floats (r/g/b/a) in range 0.0-255.0: Blended image
|
|
561
|
+
|
|
562
|
+
"""
|
|
563
|
+
|
|
564
|
+
ratio = _compose_alpha(fg, bg, opacity)
|
|
565
|
+
|
|
566
|
+
comp = np.less(fg[:, :, :3], 0.5) * (
|
|
567
|
+
2 * fg[:, :, :3] * bg[:, :, :3]
|
|
568
|
+
) + np.greater_equal(fg[:, :, :3], 0.5) * (
|
|
569
|
+
1 - (2 * (1 - fg[:, :, :3]) * (1 - bg[:, :, :3]))
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
ratio_rs = np.reshape(
|
|
573
|
+
np.repeat(ratio, 3), [comp.shape[0], comp.shape[1], comp.shape[2]]
|
|
574
|
+
)
|
|
575
|
+
img_out = comp * ratio_rs + fg[:, :, :3] * (1.0 - ratio_rs)
|
|
576
|
+
img_out = np.nan_to_num(
|
|
577
|
+
np.dstack((img_out, fg[:, :, 3]))
|
|
578
|
+
) # add alpha channel and replace nans
|
|
579
|
+
return img_out
|
|
@@ -8,7 +8,7 @@ import numpy.ma as ma
|
|
|
8
8
|
from mapchete import Timer
|
|
9
9
|
from rasterio.plot import reshape_as_image, reshape_as_raster
|
|
10
10
|
|
|
11
|
-
from mapchete_eo.image_operations
|
|
11
|
+
from mapchete_eo.image_operations import blend_functions
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
@@ -55,14 +55,16 @@ def to_rgba(arr: np.ndarray) -> np.ndarray:
|
|
|
55
55
|
def _blend_base(
|
|
56
56
|
bg: np.ndarray, fg: np.ndarray, opacity: float, operation: Callable
|
|
57
57
|
) -> ma.MaskedArray:
|
|
58
|
-
# generate RGBA output and run compositing
|
|
58
|
+
# generate RGBA output and run compositing and normalize by dividing by 255
|
|
59
59
|
out_arr = reshape_as_raster(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
(
|
|
61
|
+
operation(
|
|
62
|
+
reshape_as_image(to_rgba(bg) / 255),
|
|
63
|
+
reshape_as_image(to_rgba(fg) / 255),
|
|
64
|
+
opacity,
|
|
65
|
+
)
|
|
66
|
+
* 255
|
|
67
|
+
).astype(np.uint8, copy=False)
|
|
66
68
|
)
|
|
67
69
|
# generate mask from alpha band
|
|
68
70
|
out_mask = np.where(out_arr[3] == 0, True, False)
|
|
@@ -70,63 +72,63 @@ def _blend_base(
|
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
def normal(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
73
|
-
return _blend_base(bg, fg, opacity,
|
|
75
|
+
return _blend_base(bg, fg, opacity, blend_functions.normal)
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
def soft_light(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
77
|
-
return _blend_base(bg, fg, opacity,
|
|
79
|
+
return _blend_base(bg, fg, opacity, blend_functions.soft_light)
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
def lighten_only(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
81
|
-
return _blend_base(bg, fg, opacity,
|
|
83
|
+
return _blend_base(bg, fg, opacity, blend_functions.lighten_only)
|
|
82
84
|
|
|
83
85
|
|
|
84
86
|
def screen(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
85
|
-
return _blend_base(bg, fg, opacity,
|
|
87
|
+
return _blend_base(bg, fg, opacity, blend_functions.screen)
|
|
86
88
|
|
|
87
89
|
|
|
88
90
|
def dodge(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
89
|
-
return _blend_base(bg, fg, opacity,
|
|
91
|
+
return _blend_base(bg, fg, opacity, blend_functions.dodge)
|
|
90
92
|
|
|
91
93
|
|
|
92
94
|
def addition(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
93
|
-
return _blend_base(bg, fg, opacity,
|
|
95
|
+
return _blend_base(bg, fg, opacity, blend_functions.addition)
|
|
94
96
|
|
|
95
97
|
|
|
96
98
|
def darken_only(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
97
|
-
return _blend_base(bg, fg, opacity,
|
|
99
|
+
return _blend_base(bg, fg, opacity, blend_functions.darken_only)
|
|
98
100
|
|
|
99
101
|
|
|
100
102
|
def multiply(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
101
|
-
return _blend_base(bg, fg, opacity,
|
|
103
|
+
return _blend_base(bg, fg, opacity, blend_functions.multiply)
|
|
102
104
|
|
|
103
105
|
|
|
104
106
|
def hard_light(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
105
|
-
return _blend_base(bg, fg, opacity,
|
|
107
|
+
return _blend_base(bg, fg, opacity, blend_functions.hard_light)
|
|
106
108
|
|
|
107
109
|
|
|
108
110
|
def difference(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
109
|
-
return _blend_base(bg, fg, opacity,
|
|
111
|
+
return _blend_base(bg, fg, opacity, blend_functions.difference)
|
|
110
112
|
|
|
111
113
|
|
|
112
114
|
def subtract(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
113
|
-
return _blend_base(bg, fg, opacity,
|
|
115
|
+
return _blend_base(bg, fg, opacity, blend_functions.subtract)
|
|
114
116
|
|
|
115
117
|
|
|
116
118
|
def grain_extract(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
117
|
-
return _blend_base(bg, fg, opacity,
|
|
119
|
+
return _blend_base(bg, fg, opacity, blend_functions.grain_extract)
|
|
118
120
|
|
|
119
121
|
|
|
120
122
|
def grain_merge(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
121
|
-
return _blend_base(bg, fg, opacity,
|
|
123
|
+
return _blend_base(bg, fg, opacity, blend_functions.grain_merge)
|
|
122
124
|
|
|
123
125
|
|
|
124
126
|
def divide(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
125
|
-
return _blend_base(bg, fg, opacity,
|
|
127
|
+
return _blend_base(bg, fg, opacity, blend_functions.divide)
|
|
126
128
|
|
|
127
129
|
|
|
128
130
|
def overlay(bg: np.ndarray, fg: np.ndarray, opacity: float = 1) -> ma.MaskedArray:
|
|
129
|
-
return _blend_base(bg, fg, opacity,
|
|
131
|
+
return _blend_base(bg, fg, opacity, blend_functions.overlay)
|
|
130
132
|
|
|
131
133
|
|
|
132
134
|
METHODS = {
|
|
@@ -212,11 +214,14 @@ def fuzzy_alpha_mask(
|
|
|
212
214
|
gradient_position=GradientPosition.outside,
|
|
213
215
|
) -> np.ndarray:
|
|
214
216
|
"""Return an RGBA array with a fuzzy alpha mask."""
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
try:
|
|
218
|
+
gradient_position = (
|
|
219
|
+
GradientPosition[gradient_position]
|
|
220
|
+
if isinstance(gradient_position, str)
|
|
221
|
+
else gradient_position
|
|
222
|
+
)
|
|
223
|
+
except KeyError:
|
|
224
|
+
raise ValueError(f"unknown gradient_position: {gradient_position}")
|
|
220
225
|
|
|
221
226
|
if arr.shape[0] != 3:
|
|
222
227
|
raise TypeError("input array must have exactly three bands")
|
|
@@ -6,7 +6,7 @@ import numpy.ma as ma
|
|
|
6
6
|
from mapchete import Timer
|
|
7
7
|
from rasterio.features import rasterize, shapes
|
|
8
8
|
from rasterio.fill import fillnodata as rio_fillnodata
|
|
9
|
-
from scipy.ndimage
|
|
9
|
+
from scipy.ndimage import convolve
|
|
10
10
|
from shapely.geometry import shape
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mapchete-eo
|
|
3
|
-
Version: 2025.8.
|
|
3
|
+
Version: 2025.8.3
|
|
4
4
|
Summary: mapchete EO data reader
|
|
5
5
|
Project-URL: Homepage, https://gitlab.eox.at/maps/mapchete_eo
|
|
6
6
|
Author-email: Joachim Ungar <joachim.ungar@eox.at>, Petr Sevcik <petr.sevcik@eox.at>
|
|
@@ -12,6 +12,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
16
|
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
16
17
|
Requires-Dist: click
|
|
17
18
|
Requires-Dist: croniter
|
|
@@ -43,6 +44,9 @@ Earth Observation–specific driver extensions for `Mapchete <https://github.com
|
|
|
43
44
|
.. image:: https://img.shields.io/pypi/v/mapchete-eo.svg
|
|
44
45
|
:target: https://pypi.org/project/mapchete-eo/
|
|
45
46
|
|
|
47
|
+
.. image:: https://img.shields.io/conda/v/conda-forge/mapchete-eo
|
|
48
|
+
:target: https://anaconda.org/conda-forge/mapchete-eo
|
|
49
|
+
|
|
46
50
|
.. image:: https://img.shields.io/pypi/l/mapchete-eo.svg
|
|
47
51
|
:target: https://github.com/mapchete/mapchete-eo/blob/main/LICENSE
|
|
48
52
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
mapchete_eo/__init__.py,sha256=
|
|
1
|
+
mapchete_eo/__init__.py,sha256=poKZZLAyfwEUXxc4xuORKXHhPmoth643BeBL6d8_ttg,25
|
|
2
2
|
mapchete_eo/base.py,sha256=-bwBYkKCthr7Q48Y10KbExw7i9sXY8lc0UGo-fe08WA,18907
|
|
3
3
|
mapchete_eo/blacklist.txt,sha256=6KhBY0jNjXgRpKRvKJoOTswvNUoP56IrIcNeCYnd2qo,17471
|
|
4
4
|
mapchete_eo/eostac.py,sha256=5K08Mr4wm-VOXTEvTB6DQHX7rbFLYw8mjW1wlpPltC0,574
|
|
@@ -30,15 +30,14 @@ mapchete_eo/cli/s2_rgb.py,sha256=nzK5m4s0m2VOXgtit0uUOOhNJ14wquikWmbEQON5WJw,374
|
|
|
30
30
|
mapchete_eo/cli/s2_verify.py,sha256=atfJA5luHZjDSqiM2udaufmnQ2B4uW2Tur_OgtYLa2g,4080
|
|
31
31
|
mapchete_eo/cli/static_catalog.py,sha256=zq20fc0phjfjWMjSao-L6VVI-o2DPrMapvDmy6i30YQ,4291
|
|
32
32
|
mapchete_eo/image_operations/__init__.py,sha256=c36Vs53mT5UQR9Iwd2Ln_vnT4K_Ni8g-K7J9Fl6UsJo,432
|
|
33
|
+
mapchete_eo/image_operations/blend_functions.py,sha256=YaKLOEapMbsUR-_mkBatXwPcmim8GKpIAVua_GOdaHs,24131
|
|
33
34
|
mapchete_eo/image_operations/color_correction.py,sha256=vMXxcdyH3qOJDSKcXGU3FgXWIcgQxMy-QVe2fGQTbdk,4447
|
|
34
|
-
mapchete_eo/image_operations/compositing.py,sha256=
|
|
35
|
+
mapchete_eo/image_operations/compositing.py,sha256=K0bGCZqJuS39IBsjrRiJz5zowItI6RsiA-EBUVlbboo,8449
|
|
35
36
|
mapchete_eo/image_operations/dtype_scale.py,sha256=_CCdTBkMLDkZdjV_PsVEkqLWT6jWuTJPeMzCcRwBOns,1243
|
|
36
|
-
mapchete_eo/image_operations/fillnodata.py,sha256=
|
|
37
|
+
mapchete_eo/image_operations/fillnodata.py,sha256=Xdd-kacfo7VKqX7HRP0hqnnvV5OwNqnHLRTW64QhG24,4856
|
|
37
38
|
mapchete_eo/image_operations/filters.py,sha256=sa_Igv0zMIANHLEALVi8mxohZMoSrdQc6XVjXDYwzy8,7201
|
|
38
39
|
mapchete_eo/image_operations/linear_normalization.py,sha256=-eQX3WLCUYWUv-um3s1u33rgjAwCz84Ht8gcPsMWtJE,2468
|
|
39
40
|
mapchete_eo/image_operations/sigmoidal.py,sha256=IKU8F89HhQJWGUVmIrnkuhqzn_ztlGrTf8xXZaVQWzU,3575
|
|
40
|
-
mapchete_eo/image_operations/blend_modes/blending_functions.py,sha256=hv3oWFwzha0aAfG0X9CTKc1Y4rfIC6J4v_xVSGu4XAE,5955
|
|
41
|
-
mapchete_eo/image_operations/blend_modes/type_checks.py,sha256=KVJyE1jLsZ4qDMlylWMh40X9POcJy_e5aZYhhqgXH88,3445
|
|
42
41
|
mapchete_eo/io/__init__.py,sha256=1-1g4cESZZREvyumEUABZhDwgVuSxtxdqbNlLuVKlow,950
|
|
43
42
|
mapchete_eo/io/assets.py,sha256=8OrqvrCzJOY0pExh_vKRL7Bz_RGk6M_LfayQqxHo8ag,17014
|
|
44
43
|
mapchete_eo/io/items.py,sha256=FDDMRtFvNNL3Iwf0xiaSI8G3s-54vhqfYKo7xzCQq-w,5657
|
|
@@ -82,8 +81,8 @@ mapchete_eo/search/s2_mgrs.py,sha256=5LWl9c7WzFvXODr9f8m37cv-8yyf-LvnN0-TSSPcXKM
|
|
|
82
81
|
mapchete_eo/search/stac_search.py,sha256=YnISHpdvMJfrY_wfM1dHOtHYqgsbXsYLMYrRdJlAZTo,9551
|
|
83
82
|
mapchete_eo/search/stac_static.py,sha256=P1C2OC33UopV80xIvFeL-HbOuaxxe7qsPDdnkU8IXcA,8613
|
|
84
83
|
mapchete_eo/search/utm_search.py,sha256=wKrG2KwqL2fjSrkDUTZuRayiVAR27BtWpX-PFab0Dng,9646
|
|
85
|
-
mapchete_eo-2025.8.
|
|
86
|
-
mapchete_eo-2025.8.
|
|
87
|
-
mapchete_eo-2025.8.
|
|
88
|
-
mapchete_eo-2025.8.
|
|
89
|
-
mapchete_eo-2025.8.
|
|
84
|
+
mapchete_eo-2025.8.3.dist-info/METADATA,sha256=U1m-qUKfGvm1CGV8RmUtAc8X_DuR9gMolvp2IJsR3NM,3235
|
|
85
|
+
mapchete_eo-2025.8.3.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
86
|
+
mapchete_eo-2025.8.3.dist-info/entry_points.txt,sha256=ewk6R4FGdAclOnUpikhlPZGWI40MWeksVIIwu4jVebk,324
|
|
87
|
+
mapchete_eo-2025.8.3.dist-info/licenses/LICENSE,sha256=TC5JwvBnFrUgsSQSCDFPc3cqlbth2N0q8MWrhY1EVd0,1089
|
|
88
|
+
mapchete_eo-2025.8.3.dist-info/RECORD,,
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
Original LICENSE:
|
|
4
|
-
|
|
5
|
-
MIT License
|
|
6
|
-
|
|
7
|
-
Copyright (c) 2016 Florian Roscheck
|
|
8
|
-
|
|
9
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
10
|
-
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
|
11
|
-
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
12
|
-
persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
13
|
-
|
|
14
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
|
15
|
-
Software.
|
|
16
|
-
|
|
17
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
18
|
-
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
-
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
20
|
-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
21
|
-
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
-
|
|
23
|
-
Overview
|
|
24
|
-
--------
|
|
25
|
-
|
|
26
|
-
.. currentmodule:: blend_modes.blending_functions
|
|
27
|
-
|
|
28
|
-
.. autosummary::
|
|
29
|
-
:nosignatures:
|
|
30
|
-
|
|
31
|
-
addition
|
|
32
|
-
darken_only
|
|
33
|
-
difference
|
|
34
|
-
divide
|
|
35
|
-
dodge
|
|
36
|
-
grain_extract
|
|
37
|
-
grain_merge
|
|
38
|
-
hard_light
|
|
39
|
-
lighten_only
|
|
40
|
-
multiply
|
|
41
|
-
normal
|
|
42
|
-
overlay
|
|
43
|
-
screen
|
|
44
|
-
soft_light
|
|
45
|
-
subtract
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
import numpy as np
|
|
49
|
-
from typing import Callable
|
|
50
|
-
from mapchete_eo.image_operations.blend_modes.type_checks import (
|
|
51
|
-
assert_image_format,
|
|
52
|
-
assert_opacity,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class BlendBase:
|
|
57
|
-
def __init__(
|
|
58
|
-
self,
|
|
59
|
-
opacity=1.0,
|
|
60
|
-
disable_type_checks=False,
|
|
61
|
-
dtype=np.float16,
|
|
62
|
-
fcn_name="BlendBase",
|
|
63
|
-
):
|
|
64
|
-
self.opacity = opacity
|
|
65
|
-
self.disable_type_checks = disable_type_checks
|
|
66
|
-
self.dtype = dtype
|
|
67
|
-
self.fcn_name = fcn_name
|
|
68
|
-
|
|
69
|
-
def _prepare(self, src: np.ndarray, dst: np.ndarray):
|
|
70
|
-
if not self.disable_type_checks:
|
|
71
|
-
assert_image_format(src, fcn_name=self.fcn_name, arg_name="src")
|
|
72
|
-
assert_image_format(dst, fcn_name=self.fcn_name, arg_name="dst")
|
|
73
|
-
assert_opacity(self.opacity, fcn_name=self.fcn_name)
|
|
74
|
-
if src.dtype != self.dtype:
|
|
75
|
-
src = src.astype(self.dtype)
|
|
76
|
-
if dst.dtype != self.dtype:
|
|
77
|
-
dst = dst.astype(self.dtype)
|
|
78
|
-
return src, dst
|
|
79
|
-
|
|
80
|
-
def blend(self, src: np.ndarray, dst: np.ndarray, blend_func: Callable):
|
|
81
|
-
src, dst = self._prepare(src, dst)
|
|
82
|
-
blended = blend_func(src, dst)
|
|
83
|
-
result = (blended * self.opacity) + (dst * (1 - self.opacity))
|
|
84
|
-
return np.clip(result, 0, 1).astype(self.dtype)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def make_blend_function(blend_func: Callable):
|
|
88
|
-
# This function returns a wrapper that uses a shared BlendBase instance
|
|
89
|
-
base = BlendBase()
|
|
90
|
-
|
|
91
|
-
def func(
|
|
92
|
-
src: np.ndarray,
|
|
93
|
-
dst: np.ndarray,
|
|
94
|
-
opacity: float = 1.0,
|
|
95
|
-
disable_type_checks: bool = False,
|
|
96
|
-
dtype: np.dtype = np.float16,
|
|
97
|
-
) -> np.ndarray:
|
|
98
|
-
# If parameters differ from base, create new BlendBase (rare)
|
|
99
|
-
if (
|
|
100
|
-
opacity != base.opacity
|
|
101
|
-
or disable_type_checks != base.disable_type_checks
|
|
102
|
-
or dtype != base.dtype
|
|
103
|
-
):
|
|
104
|
-
base_local = BlendBase(opacity, disable_type_checks, dtype)
|
|
105
|
-
return base_local.blend(src, dst, blend_func)
|
|
106
|
-
return base.blend(src, dst, blend_func)
|
|
107
|
-
|
|
108
|
-
return func
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
normal = make_blend_function(lambda s, d: s)
|
|
112
|
-
multiply = make_blend_function(lambda s, d: s * d)
|
|
113
|
-
screen = make_blend_function(lambda s, d: 1 - (1 - s) * (1 - d))
|
|
114
|
-
darken_only = make_blend_function(lambda s, d: np.minimum(s, d))
|
|
115
|
-
lighten_only = make_blend_function(lambda s, d: np.maximum(s, d))
|
|
116
|
-
difference = make_blend_function(lambda s, d: np.abs(d - s))
|
|
117
|
-
subtract = make_blend_function(lambda s, d: np.clip(d - s, 0, 1))
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def divide_blend(s: np.ndarray, d: np.ndarray) -> np.ndarray:
|
|
121
|
-
with np.errstate(divide="ignore", invalid="ignore"):
|
|
122
|
-
res = np.true_divide(d, s)
|
|
123
|
-
res[~np.isfinite(res)] = 0
|
|
124
|
-
return np.clip(res, 0, 1)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
divide = make_blend_function(divide_blend)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def grain_extract_blend(s: np.ndarray, d: np.ndarray) -> np.ndarray:
|
|
131
|
-
return np.clip(d - s + 0.5, 0, 1)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
grain_extract = make_blend_function(grain_extract_blend)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def grain_merge_blend(s: np.ndarray, d: np.ndarray) -> np.ndarray:
|
|
138
|
-
return np.clip(d + s - 0.5, 0, 1)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
grain_merge = make_blend_function(grain_merge_blend)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def overlay_blend(s: np.ndarray, d: np.ndarray) -> np.ndarray:
|
|
145
|
-
mask = d <= 0.5
|
|
146
|
-
result = np.empty_like(d)
|
|
147
|
-
result[mask] = 2 * s[mask] * d[mask]
|
|
148
|
-
result[~mask] = 1 - 2 * (1 - s[~mask]) * (1 - d[~mask])
|
|
149
|
-
return np.clip(result, 0, 1)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
overlay = make_blend_function(overlay_blend)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def hard_light_blend(s: np.ndarray, d: np.ndarray) -> np.ndarray:
|
|
156
|
-
mask = s <= 0.5
|
|
157
|
-
result = np.empty_like(d)
|
|
158
|
-
result[mask] = 2 * s[mask] * d[mask]
|
|
159
|
-
result[~mask] = 1 - 2 * (1 - s[~mask]) * (1 - d[~mask])
|
|
160
|
-
return np.clip(result, 0, 1)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
hard_light = make_blend_function(hard_light_blend)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def soft_light_blend(s: np.ndarray, d: np.ndarray) -> np.ndarray:
|
|
167
|
-
result = (1 - 2 * s) * d**2 + 2 * s * d
|
|
168
|
-
return np.clip(result, 0, 1)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
soft_light = make_blend_function(soft_light_blend)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def dodge_blend(s: np.ndarray, d: np.ndarray) -> np.ndarray:
|
|
175
|
-
with np.errstate(divide="ignore", invalid="ignore"):
|
|
176
|
-
res = np.true_divide(d, 1 - s)
|
|
177
|
-
res[~np.isfinite(res)] = 1
|
|
178
|
-
return np.clip(res, 0, 1)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
dodge = make_blend_function(dodge_blend)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def burn_blend(s: np.ndarray, d: np.ndarray) -> np.ndarray:
|
|
185
|
-
with np.errstate(divide="ignore", invalid="ignore"):
|
|
186
|
-
res = 1 - np.true_divide(1 - d, s)
|
|
187
|
-
res[~np.isfinite(res)] = 0
|
|
188
|
-
return np.clip(res, 0, 1)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
burn = make_blend_function(burn_blend)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def addition_blend(s: np.ndarray, d: np.ndarray) -> np.ndarray:
|
|
195
|
-
return np.clip(s + d, 0, 1)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
addition = make_blend_function(addition_blend)
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
"""This module includes functions to check if variable types match expected formats."""
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def assert_image_format(image, fcn_name: str, arg_name: str, force_alpha: bool = True):
|
|
7
|
-
"""Assert if image arguments have the expected format.
|
|
8
|
-
|
|
9
|
-
Checks:
|
|
10
|
-
- Image is a numpy array
|
|
11
|
-
- Array is of floating-point type
|
|
12
|
-
- Array is 3D (height x width x channels)
|
|
13
|
-
- Array has the correct number of layers (3 or 4)
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
image: The image to be checked.
|
|
17
|
-
fcn_name (str): Calling function name for display in error messages.
|
|
18
|
-
arg_name (str): Relevant argument name for display in error messages.
|
|
19
|
-
force_alpha (bool): Whether the image must include an alpha layer.
|
|
20
|
-
|
|
21
|
-
Raises:
|
|
22
|
-
TypeError: If type or shape are incorrect.
|
|
23
|
-
"""
|
|
24
|
-
if not isinstance(image, np.ndarray):
|
|
25
|
-
raise TypeError(
|
|
26
|
-
f"\n[Invalid Type]\n"
|
|
27
|
-
f"Function: {fcn_name}\n"
|
|
28
|
-
f"Argument: {arg_name}\n"
|
|
29
|
-
f"Expected: numpy.ndarray\n"
|
|
30
|
-
f"Got: {type(image).__name__}\n"
|
|
31
|
-
f'Hint: Pass a numpy.ndarray for "{arg_name}".'
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
if image.dtype.kind != "f":
|
|
35
|
-
raise TypeError(
|
|
36
|
-
f"\n[Invalid Data Type]\n"
|
|
37
|
-
f"Function: {fcn_name}\n"
|
|
38
|
-
f"Argument: {arg_name}\n"
|
|
39
|
-
f'Expected dtype kind: "f" (floating-point)\n'
|
|
40
|
-
f'Got dtype kind: "{image.dtype.kind}"\n'
|
|
41
|
-
f"Hint: Convert the array to float, e.g., image.astype(float)."
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
if len(image.shape) != 3:
|
|
45
|
-
raise TypeError(
|
|
46
|
-
f"\n[Invalid Dimensions]\n"
|
|
47
|
-
f"Function: {fcn_name}\n"
|
|
48
|
-
f"Argument: {arg_name}\n"
|
|
49
|
-
f"Expected: 3D array (height x width x channels)\n"
|
|
50
|
-
f"Got: {len(image.shape)}D array\n"
|
|
51
|
-
f"Hint: Ensure the array has three dimensions."
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
if force_alpha and image.shape[2] != 4:
|
|
55
|
-
raise TypeError(
|
|
56
|
-
f"\n[Invalid Channel Count]\n"
|
|
57
|
-
f"Function: {fcn_name}\n"
|
|
58
|
-
f"Argument: {arg_name}\n"
|
|
59
|
-
f"Expected: 4 layers (R, G, B, Alpha)\n"
|
|
60
|
-
f"Got: {image.shape[2]} layers\n"
|
|
61
|
-
f"Hint: Include all four channels if force_alpha=True."
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def assert_opacity(opacity, fcn_name: str, arg_name: str = "opacity"):
|
|
66
|
-
"""Assert if opacity has the expected format.
|
|
67
|
-
|
|
68
|
-
Checks:
|
|
69
|
-
- Opacity is float or int
|
|
70
|
-
- Opacity is within 0.0 <= x <= 1.0
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
opacity: The opacity value to be checked.
|
|
74
|
-
fcn_name (str): Calling function name for display in error messages.
|
|
75
|
-
arg_name (str): Argument name for display in error messages.
|
|
76
|
-
|
|
77
|
-
Raises:
|
|
78
|
-
TypeError: If type is not float or int.
|
|
79
|
-
ValueError: If opacity is out of range.
|
|
80
|
-
"""
|
|
81
|
-
if not isinstance(opacity, (float, int)):
|
|
82
|
-
raise TypeError(
|
|
83
|
-
f"\n[Invalid Type]\n"
|
|
84
|
-
f"Function: {fcn_name}\n"
|
|
85
|
-
f"Argument: {arg_name}\n"
|
|
86
|
-
f"Expected: float (or int)\n"
|
|
87
|
-
f"Got: {type(opacity).__name__}\n"
|
|
88
|
-
f"Hint: Pass a float between 0.0 and 1.0."
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
if not 0.0 <= opacity <= 1.0:
|
|
92
|
-
raise ValueError(
|
|
93
|
-
f"\n[Out of Range]\n"
|
|
94
|
-
f"Function: {fcn_name}\n"
|
|
95
|
-
f"Argument: {arg_name}\n"
|
|
96
|
-
f"Expected: value in range 0.0 <= x <= 1.0\n"
|
|
97
|
-
f"Got: {opacity}\n"
|
|
98
|
-
f"Hint: Clamp or normalize the value to the valid range."
|
|
99
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|