mapchete-eo 2026.2.0__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.
Files changed (89) hide show
  1. mapchete_eo/__init__.py +1 -0
  2. mapchete_eo/array/__init__.py +0 -0
  3. mapchete_eo/array/buffer.py +16 -0
  4. mapchete_eo/array/color.py +29 -0
  5. mapchete_eo/array/convert.py +163 -0
  6. mapchete_eo/base.py +653 -0
  7. mapchete_eo/blacklist.txt +175 -0
  8. mapchete_eo/cli/__init__.py +30 -0
  9. mapchete_eo/cli/bounds.py +22 -0
  10. mapchete_eo/cli/options_arguments.py +227 -0
  11. mapchete_eo/cli/s2_brdf.py +77 -0
  12. mapchete_eo/cli/s2_cat_results.py +130 -0
  13. mapchete_eo/cli/s2_find_broken_products.py +77 -0
  14. mapchete_eo/cli/s2_jp2_static_catalog.py +166 -0
  15. mapchete_eo/cli/s2_mask.py +71 -0
  16. mapchete_eo/cli/s2_mgrs.py +45 -0
  17. mapchete_eo/cli/s2_rgb.py +114 -0
  18. mapchete_eo/cli/s2_verify.py +129 -0
  19. mapchete_eo/cli/static_catalog.py +82 -0
  20. mapchete_eo/eostac.py +30 -0
  21. mapchete_eo/exceptions.py +87 -0
  22. mapchete_eo/image_operations/__init__.py +12 -0
  23. mapchete_eo/image_operations/blend_functions.py +579 -0
  24. mapchete_eo/image_operations/color_correction.py +136 -0
  25. mapchete_eo/image_operations/compositing.py +266 -0
  26. mapchete_eo/image_operations/dtype_scale.py +43 -0
  27. mapchete_eo/image_operations/fillnodata.py +130 -0
  28. mapchete_eo/image_operations/filters.py +319 -0
  29. mapchete_eo/image_operations/linear_normalization.py +81 -0
  30. mapchete_eo/image_operations/sigmoidal.py +114 -0
  31. mapchete_eo/io/__init__.py +37 -0
  32. mapchete_eo/io/assets.py +496 -0
  33. mapchete_eo/io/items.py +162 -0
  34. mapchete_eo/io/levelled_cubes.py +259 -0
  35. mapchete_eo/io/path.py +155 -0
  36. mapchete_eo/io/products.py +423 -0
  37. mapchete_eo/io/profiles.py +45 -0
  38. mapchete_eo/platforms/sentinel2/__init__.py +17 -0
  39. mapchete_eo/platforms/sentinel2/_mapper_registry.py +89 -0
  40. mapchete_eo/platforms/sentinel2/bandpass_adjustment.py +104 -0
  41. mapchete_eo/platforms/sentinel2/brdf/__init__.py +8 -0
  42. mapchete_eo/platforms/sentinel2/brdf/config.py +32 -0
  43. mapchete_eo/platforms/sentinel2/brdf/correction.py +260 -0
  44. mapchete_eo/platforms/sentinel2/brdf/hls.py +251 -0
  45. mapchete_eo/platforms/sentinel2/brdf/models.py +44 -0
  46. mapchete_eo/platforms/sentinel2/brdf/protocols.py +27 -0
  47. mapchete_eo/platforms/sentinel2/brdf/ross_thick.py +136 -0
  48. mapchete_eo/platforms/sentinel2/brdf/sun_angle_arrays.py +76 -0
  49. mapchete_eo/platforms/sentinel2/config.py +241 -0
  50. mapchete_eo/platforms/sentinel2/driver.py +43 -0
  51. mapchete_eo/platforms/sentinel2/masks.py +329 -0
  52. mapchete_eo/platforms/sentinel2/metadata_parser/__init__.py +6 -0
  53. mapchete_eo/platforms/sentinel2/metadata_parser/base.py +56 -0
  54. mapchete_eo/platforms/sentinel2/metadata_parser/default_path_mapper.py +135 -0
  55. mapchete_eo/platforms/sentinel2/metadata_parser/models.py +78 -0
  56. mapchete_eo/platforms/sentinel2/metadata_parser/s2metadata.py +639 -0
  57. mapchete_eo/platforms/sentinel2/preconfigured_sources/__init__.py +57 -0
  58. mapchete_eo/platforms/sentinel2/preconfigured_sources/guessers.py +108 -0
  59. mapchete_eo/platforms/sentinel2/preconfigured_sources/item_mappers.py +171 -0
  60. mapchete_eo/platforms/sentinel2/preconfigured_sources/metadata_xml_mappers.py +217 -0
  61. mapchete_eo/platforms/sentinel2/preprocessing_tasks.py +50 -0
  62. mapchete_eo/platforms/sentinel2/processing_baseline.py +163 -0
  63. mapchete_eo/platforms/sentinel2/product.py +747 -0
  64. mapchete_eo/platforms/sentinel2/source.py +114 -0
  65. mapchete_eo/platforms/sentinel2/types.py +114 -0
  66. mapchete_eo/processes/__init__.py +0 -0
  67. mapchete_eo/processes/config.py +51 -0
  68. mapchete_eo/processes/dtype_scale.py +112 -0
  69. mapchete_eo/processes/eo_to_xarray.py +19 -0
  70. mapchete_eo/processes/merge_rasters.py +239 -0
  71. mapchete_eo/product.py +323 -0
  72. mapchete_eo/protocols.py +61 -0
  73. mapchete_eo/search/__init__.py +14 -0
  74. mapchete_eo/search/base.py +285 -0
  75. mapchete_eo/search/config.py +113 -0
  76. mapchete_eo/search/s2_mgrs.py +313 -0
  77. mapchete_eo/search/stac_search.py +278 -0
  78. mapchete_eo/search/stac_static.py +197 -0
  79. mapchete_eo/search/utm_search.py +251 -0
  80. mapchete_eo/settings.py +25 -0
  81. mapchete_eo/sort.py +60 -0
  82. mapchete_eo/source.py +109 -0
  83. mapchete_eo/time.py +62 -0
  84. mapchete_eo/types.py +76 -0
  85. mapchete_eo-2026.2.0.dist-info/METADATA +91 -0
  86. mapchete_eo-2026.2.0.dist-info/RECORD +89 -0
  87. mapchete_eo-2026.2.0.dist-info/WHEEL +4 -0
  88. mapchete_eo-2026.2.0.dist-info/entry_points.txt +11 -0
  89. mapchete_eo-2026.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1 @@
1
+ __version__ = "2026.2.0"
File without changes
@@ -0,0 +1,16 @@
1
+ from typing import Optional
2
+
3
+ import numpy as np
4
+ from numpy.typing import DTypeLike
5
+ from scipy.ndimage import binary_dilation
6
+
7
+
8
+ def buffer_array(
9
+ array: np.ndarray, buffer: int = 0, out_array_dtype: Optional[DTypeLike] = None
10
+ ) -> np.ndarray:
11
+ if out_array_dtype is None:
12
+ out_array_dtype = array.dtype
13
+ if buffer == 0:
14
+ return array.astype(out_array_dtype, copy=False)
15
+
16
+ return binary_dilation(array, iterations=buffer).astype(out_array_dtype, copy=False)
@@ -0,0 +1,29 @@
1
+ import numpy as np
2
+ import numpy.ma as ma
3
+
4
+
5
+ def color_array(shape: tuple, hex_color: str):
6
+ colors = hex_to_rgb(hex_color)
7
+ return ma.masked_array(
8
+ [np.full(shape, color, dtype=np.uint8) for color in colors],
9
+ mask=ma.zeros((len(colors), *shape)),
10
+ )
11
+
12
+
13
+ def hex_to_rgb(hex_color):
14
+ """
15
+ Convert hex color to tuple of RGB(A) colors.
16
+
17
+ e.g. "#FFFFFF" --> (255, 255, 255) or "#00FF00FF" --> (0, 255, 0, 255)
18
+ """
19
+ channels = iter(hex_color.lstrip("#"))
20
+ return tuple(int("".join(channel), 16) for channel in zip(channels, channels))
21
+
22
+
23
+ def outlier_pixels(
24
+ arr: np.ndarray,
25
+ axis: int = 0,
26
+ range_threshold: int = 100,
27
+ ) -> np.ndarray:
28
+ """Detect outlier pixels containing extreme colors."""
29
+ return arr.max(axis=axis) - arr.min(axis=axis) >= range_threshold
@@ -0,0 +1,163 @@
1
+ from typing import List, Optional, Union
2
+
3
+ import numpy as np
4
+ import numpy.ma as ma
5
+ from numpy.typing import DTypeLike
6
+ import xarray as xr
7
+ from mapchete.types import NodataVal
8
+
9
+ # dtypes from https://numpy.org/doc/stable/user/basics.types.html
10
+ _NUMPY_FLOAT_DTYPES = [
11
+ np.half,
12
+ np.float16,
13
+ np.single,
14
+ np.double,
15
+ np.longdouble,
16
+ np.csingle,
17
+ np.cdouble,
18
+ np.clongdouble,
19
+ ]
20
+
21
+
22
+ def to_masked_array(
23
+ xarr: Union[xr.Dataset, xr.DataArray],
24
+ copy: bool = False,
25
+ out_dtype: Optional[DTypeLike] = None,
26
+ ) -> ma.MaskedArray:
27
+ """Convert xr.DataArray to ma.MaskedArray."""
28
+ if isinstance(xarr, xr.Dataset):
29
+ xarr = xarr.to_array()
30
+
31
+ fill_value = xarr.attrs.get("_FillValue")
32
+ if fill_value is None:
33
+ raise ValueError(
34
+ "Cannot create masked_array because DataArray fill value is None"
35
+ )
36
+
37
+ if out_dtype:
38
+ xarr = xarr.astype(out_dtype, copy=False)
39
+
40
+ if xarr.dtype in _NUMPY_FLOAT_DTYPES:
41
+ return ma.masked_values(xarr, fill_value, copy=copy, shrink=False)
42
+ else:
43
+ out = ma.masked_equal(xarr, fill_value, copy=copy)
44
+ # in case of a shrinked mask we have to expand it to the full array shape
45
+ if not isinstance(out.mask, np.ndarray):
46
+ out.mask = np.full(out.mask.shape, out.mask, dtype=bool)
47
+ return out
48
+
49
+
50
+ def to_dataarray(
51
+ masked_arr: ma.MaskedArray,
52
+ nodataval: NodataVal = None,
53
+ name: Optional[str] = None,
54
+ band_names: Optional[List[str]] = None,
55
+ band_axis_name: str = "bands",
56
+ x_axis_name: str = "x",
57
+ y_axis_name: str = "y",
58
+ attrs: Optional[dict] = None,
59
+ ) -> xr.DataArray:
60
+ """
61
+ Convert ma.MaskedArray to xr.DataArray.
62
+
63
+ Depending on whether the array is 2D or 3D, the axes will be named accordingly.
64
+
65
+ A 2-dimensional array indicates that we only have a spatial x- and y-axis. A
66
+ 3rd dimension will be interpreted as bands.
67
+ """
68
+ # nodata handling is weird.
69
+ #
70
+ # xr.DataArray cannot hold a masked_array but will turn it into
71
+ # a usual NumPy array, replacing the masked values with np.nan.
72
+ # However, this also seems to change the dtype to float32 which
73
+ # is not desirable.
74
+ nodataval = masked_arr.fill_value if nodataval is None else nodataval
75
+ attrs = attrs or dict()
76
+
77
+ if masked_arr.ndim == 2:
78
+ dims = [x_axis_name, y_axis_name]
79
+ coords = None
80
+ elif masked_arr.ndim == 3:
81
+ bands_count = masked_arr.shape[0]
82
+ band_names = band_names or [f"{band_axis_name}-{i}" for i in range(bands_count)]
83
+ dims = [band_axis_name, x_axis_name, y_axis_name]
84
+ coords = {band_axis_name: band_names}
85
+ else: # pragma: no cover
86
+ raise TypeError("only a 2D or 3D ma.MaskedArray is allowed.")
87
+
88
+ return xr.DataArray(
89
+ data=masked_arr.filled(nodataval),
90
+ dims=dims,
91
+ name=name,
92
+ attrs=dict(attrs, _FillValue=nodataval),
93
+ coords=coords,
94
+ )
95
+
96
+
97
+ def to_dataset(
98
+ masked_arr: ma.MaskedArray,
99
+ nodataval: NodataVal = None,
100
+ slice_names: Optional[List[str]] = None,
101
+ band_names: Optional[List[str]] = None,
102
+ slices_attrs: Optional[List[Union[dict, None]]] = None,
103
+ slice_axis_name: str = "time",
104
+ band_axis_name: str = "bands",
105
+ x_axis_name: str = "x",
106
+ y_axis_name: str = "y",
107
+ attrs: Optional[dict] = None,
108
+ ):
109
+ """Convert a 3D or 4D ma.MaskedArray to an xarray.Dataset."""
110
+ attrs = attrs or dict()
111
+ nodataval = masked_arr.fill_value if nodataval is None else nodataval
112
+
113
+ if masked_arr.ndim == 3:
114
+ bands = masked_arr.shape[0]
115
+ band_names = band_names or [f"{band_axis_name}-{i}" for i in range(bands)]
116
+ raise NotImplementedError()
117
+ elif masked_arr.ndim == 4:
118
+ slices, bands = masked_arr.shape[:2]
119
+ band_names = band_names or [f"{band_axis_name}-{i}" for i in range(bands)]
120
+ slice_names = slice_names or [f"{slice_axis_name}-{i}" for i in range(slices)]
121
+ slices_attrs = (
122
+ [None for _ in range(slices)] if slices_attrs is None else slices_attrs
123
+ )
124
+ coords = {slice_axis_name: slice_names}
125
+ return xr.Dataset(
126
+ data_vars={
127
+ # every slice gets its own xarray Dataset
128
+ slice_name: to_dataarray(
129
+ slice_array,
130
+ nodataval=nodataval,
131
+ band_names=band_names,
132
+ name=slice_name,
133
+ attrs=slice_attrs,
134
+ band_axis_name=band_axis_name,
135
+ x_axis_name=x_axis_name,
136
+ y_axis_name=y_axis_name,
137
+ )
138
+ for slice_name, slice_attrs, slice_array in zip(
139
+ slice_names,
140
+ slices_attrs,
141
+ masked_arr,
142
+ )
143
+ },
144
+ coords=coords,
145
+ attrs=dict(attrs, _FillValue=nodataval),
146
+ ).transpose(slice_axis_name, band_axis_name, x_axis_name, y_axis_name)
147
+
148
+ else: # pragma: no cover
149
+ raise TypeError("only a 3D or 4D ma.MaskedArray is allowed.")
150
+
151
+
152
+ def to_bands_mask(arr: np.ndarray, bands: int = 1) -> np.ndarray:
153
+ """Expands a 2D mask to a full band mask."""
154
+ if arr.ndim != 2:
155
+ raise TypeError("input array has to have exactly 2 dimensions.")
156
+ return np.repeat(
157
+ np.expand_dims(
158
+ arr,
159
+ axis=0,
160
+ ),
161
+ bands,
162
+ axis=0,
163
+ )