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,129 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional
4
+
5
+ import click
6
+ import numpy as np
7
+ import pystac
8
+ from mapchete.cli.options import opt_debug
9
+ from mapchete.io import copy
10
+ from mapchete.io.raster import read_raster_no_crs
11
+ from mapchete.path import MPath
12
+ from tqdm import tqdm
13
+
14
+ from mapchete_eo.array.color import outlier_pixels
15
+ from mapchete_eo.cli import options_arguments
16
+ from mapchete_eo.exceptions import AssetKeyError
17
+ from mapchete_eo.platforms.sentinel2.product import asset_mpath
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ @dataclass
23
+ class Report:
24
+ item: pystac.Item
25
+ missing_asset_entries: List[str]
26
+ missing_assets: List[MPath]
27
+ color_artefacts: bool = False
28
+
29
+ def product_broken(self) -> bool:
30
+ return any(
31
+ [
32
+ bool(self.missing_asset_entries),
33
+ bool(self.missing_assets),
34
+ bool(self.color_artefacts),
35
+ ]
36
+ )
37
+
38
+
39
+ @click.command()
40
+ @options_arguments.arg_stac_items
41
+ @options_arguments.opt_assets
42
+ @opt_debug
43
+ def s2_verify(
44
+ stac_items: List[MPath],
45
+ assets: List[str] = [],
46
+ asset_exists_check: bool = True,
47
+ **_,
48
+ ):
49
+ """Verify Sentinel-2 products."""
50
+ assets = assets or []
51
+ for item_path in tqdm(stac_items):
52
+ report = verify_item(
53
+ pystac.Item.from_file(item_path),
54
+ assets=assets,
55
+ asset_exists_check=asset_exists_check,
56
+ )
57
+ for asset in report.missing_asset_entries:
58
+ tqdm.write(f"[ERROR] {report.item.id} has no asset named '{asset}")
59
+ for path in report.missing_assets:
60
+ tqdm.write(
61
+ f"[ERROR] {report.item.id} asset '{asset}' with path {str(path)} does not exist"
62
+ )
63
+ if report.color_artefacts:
64
+ tqdm.write(
65
+ f"[ERROR] {report.item.id} thumbnail ({report.item.assets['thumbnail'].href}) indicates that there are some color artefacts"
66
+ )
67
+
68
+
69
+ def verify_item(
70
+ item: pystac.Item,
71
+ assets: List[str],
72
+ asset_exists_check: bool = False,
73
+ check_thumbnail: bool = True,
74
+ thumbnail_dir: Optional[MPath] = None,
75
+ ):
76
+ missing_asset_entries = []
77
+ missing_assets = []
78
+ color_artefacts = False
79
+ for asset in assets:
80
+ logger.debug("verify asset %s is available", asset)
81
+ if asset not in item.assets:
82
+ missing_asset_entries.append(asset)
83
+ if asset_exists_check:
84
+ try:
85
+ path = asset_mpath(item=item, asset=asset)
86
+ logger.debug("check if asset %s (%s) exists", asset, str(path))
87
+ if not path.exists():
88
+ missing_assets.append(path)
89
+ except AssetKeyError:
90
+ missing_asset_entries.append(asset)
91
+ if check_thumbnail:
92
+ thumbnail_href = MPath.from_inp(item.assets["thumbnail"].href)
93
+ logger.debug("check thumbnail %s for artefacts ...", thumbnail_href)
94
+ if thumbnail_dir:
95
+ thumbnail_path = thumbnail_dir / item.id + ".jpg"
96
+ copy(thumbnail_href, thumbnail_path)
97
+ else:
98
+ thumbnail_path = thumbnail_href
99
+ color_artefacts = outlier_pixels_detected(read_raster_no_crs(thumbnail_href))
100
+ return Report(
101
+ item,
102
+ missing_asset_entries=missing_asset_entries,
103
+ missing_assets=missing_assets,
104
+ color_artefacts=color_artefacts,
105
+ )
106
+
107
+
108
+ def outlier_pixels_detected(
109
+ arr: np.ndarray,
110
+ axis: int = 0,
111
+ range_threshold: int = 100,
112
+ allowed_error_percentage: float = 1,
113
+ ) -> bool:
114
+ """
115
+ Checks whether number of outlier pixels is larger than allowed.
116
+
117
+ An outlier pixel is a pixel, where the value range between bands exceeds
118
+ the range_threshold.
119
+ """
120
+ _, width, height = arr.shape
121
+ pixels = width * height
122
+ outliers = outlier_pixels(arr, axis=axis, range_threshold=range_threshold).sum()
123
+ outlier_percent = outliers / pixels * 100
124
+ logger.debug(
125
+ "%s (%s %%) suspicious pixels detected",
126
+ outliers,
127
+ round(outlier_percent, 2),
128
+ )
129
+ return outlier_percent > allowed_error_percentage
@@ -0,0 +1,82 @@
1
+ from datetime import datetime
2
+ from typing import List, Optional
3
+
4
+ import click
5
+ from mapchete.cli.options import opt_bounds, opt_debug
6
+ from mapchete.path import MPath
7
+ from mapchete.types import Bounds
8
+ from rasterio.profiles import Profile
9
+
10
+ from mapchete_eo.cli import options_arguments
11
+ from mapchete_eo.platforms.sentinel2 import S2Metadata
12
+ from mapchete_eo.platforms.sentinel2.source import Sentinel2Source
13
+ from mapchete_eo.platforms.sentinel2.types import Resolution
14
+ from mapchete_eo.types import TimeRange
15
+
16
+
17
+ @click.command()
18
+ @options_arguments.arg_dst_path
19
+ @opt_bounds
20
+ @options_arguments.opt_mgrs_tile
21
+ @options_arguments.opt_start_time
22
+ @options_arguments.opt_end_time
23
+ @options_arguments.opt_source
24
+ @options_arguments.opt_name
25
+ @options_arguments.opt_description
26
+ @options_arguments.opt_assets
27
+ @options_arguments.opt_assets_dst_resolution
28
+ @options_arguments.opt_assets_dst_rio_profile
29
+ @options_arguments.opt_copy_metadata
30
+ @options_arguments.opt_overwrite
31
+ @opt_debug
32
+ def static_catalog(
33
+ dst_path: MPath,
34
+ start_time: datetime,
35
+ end_time: datetime,
36
+ bounds: Optional[Bounds] = None,
37
+ mgrs_tile: Optional[str] = None,
38
+ source: Sentinel2Source = Sentinel2Source(collection="EarthSearch"),
39
+ name: Optional[str] = None,
40
+ description: Optional[str] = None,
41
+ assets: Optional[List[str]] = None,
42
+ assets_dst_resolution: Resolution = Resolution.original,
43
+ assets_dst_rio_profile: Optional[Profile] = None,
44
+ copy_metadata: bool = False,
45
+ overwrite: bool = False,
46
+ **__,
47
+ ):
48
+ """Write a static STAC catalog for selected area."""
49
+ if any([start_time is None, end_time is None]): # pragma: no cover
50
+ raise click.ClickException("--start-time and --end-time are mandatory")
51
+ if all([bounds is None, mgrs_tile is None]): # pragma: no cover
52
+ raise click.ClickException("--bounds or --mgrs-tile are required")
53
+ catalog = source.get_catalog()
54
+ if hasattr(catalog, "write_static_catalog"):
55
+ with options_arguments.TqdmUpTo(
56
+ unit="products", unit_scale=True, miniters=1, disable=opt_debug
57
+ ) as progress:
58
+ catalog_json = catalog.write_static_catalog(
59
+ dst_path,
60
+ name=name,
61
+ bounds=bounds,
62
+ time=TimeRange(
63
+ start=start_time,
64
+ end=end_time,
65
+ ),
66
+ search_kwargs=dict(mgrs_tile=mgrs_tile),
67
+ description=description,
68
+ assets=assets,
69
+ assets_dst_resolution=assets_dst_resolution.value,
70
+ assets_convert_profile=assets_dst_rio_profile,
71
+ copy_metadata=copy_metadata,
72
+ metadata_parser_classes=(S2Metadata,),
73
+ overwrite=overwrite,
74
+ progress_callback=progress.update_to,
75
+ )
76
+
77
+ click.echo(f"Catalog successfully written to {catalog_json}")
78
+
79
+ else:
80
+ raise AttributeError(
81
+ f"catalog {catalog} does not support writing a static version"
82
+ )
mapchete_eo/eostac.py ADDED
@@ -0,0 +1,30 @@
1
+ """
2
+ Driver class for EOSTAC static STAC catalogs.
3
+ """
4
+
5
+ from mapchete_eo import base
6
+
7
+ METADATA: dict = {
8
+ "driver_name": "EOSTAC",
9
+ "data_type": None,
10
+ "mode": "r",
11
+ "file_extensions": [],
12
+ }
13
+
14
+
15
+ class InputTile(base.EODataCube):
16
+ """
17
+ Target Tile representation of input data.
18
+
19
+ Parameters
20
+ ----------
21
+ tile : ``Tile``
22
+ kwargs : keyword arguments
23
+ driver specific parameters
24
+ """
25
+
26
+
27
+ class InputData(base.InputData):
28
+ """In case this driver is used when being a readonly input to another process."""
29
+
30
+ input_tile_cls = InputTile
@@ -0,0 +1,87 @@
1
+ """Custom exceptions."""
2
+
3
+ from mapchete.errors import MapcheteNodataTile
4
+
5
+
6
+ class EmptyFootprintException(Exception):
7
+ """Raised when footprint is empty."""
8
+
9
+
10
+ class EmptySliceException(Exception):
11
+ """Raised when slice is empty."""
12
+
13
+
14
+ class EmptyProductException(EmptySliceException):
15
+ """Raised when product is empty."""
16
+
17
+
18
+ class EmptyStackException(MapcheteNodataTile):
19
+ """Raised when whole stack is empty."""
20
+
21
+
22
+ class EmptyFileException(Exception):
23
+ """Raised when no bytes are downloaded."""
24
+
25
+
26
+ class IncompleteDownloadException(Exception):
27
+ """ "Raised when the file is not downloaded completely."""
28
+
29
+
30
+ class InvalidMapcheteEOCollectionError(Exception):
31
+ """ "Raised for unsupported collections of Mapchete EO package."""
32
+
33
+
34
+ class EmptyCatalogueResponse(Exception):
35
+ """Raised when catalogue response is empty."""
36
+
37
+
38
+ class CorruptedGTiffError(Exception):
39
+ """Raised when GTiff validation fails."""
40
+
41
+
42
+ class BRDFError(Exception):
43
+ """Raised when BRDF grid cannot be calculated."""
44
+
45
+
46
+ class AssetError(Exception):
47
+ """Generic Exception class for Assets."""
48
+
49
+
50
+ class AssetMissing(AssetError, FileNotFoundError):
51
+ """Raised when a product asset should be there but isn't."""
52
+
53
+
54
+ class AssetEmpty(AssetError):
55
+ """Raised when a product asset should contain data but is empty."""
56
+
57
+
58
+ class AssetKeyError(AssetError, KeyError):
59
+ """Raised when an asset name cannot be found in item."""
60
+
61
+
62
+ class PreprocessingNotFinished(Exception):
63
+ """Raised when preprocessing tasks have not been fully executed."""
64
+
65
+
66
+ class AllMasked(Exception):
67
+ """Raised when an array is fully masked."""
68
+
69
+
70
+ class NoSourceProducts(MapcheteNodataTile, ValueError):
71
+ """Raised when no products are available."""
72
+
73
+
74
+ class CorruptedProduct(Exception):
75
+ """Raised when product is damaged and cannot be read."""
76
+
77
+
78
+ class CorruptedProductMetadata(CorruptedProduct):
79
+ """Raised when EOProduct cannot be parsed due to a metadata issue."""
80
+
81
+
82
+ class CorruptedSlice(Exception):
83
+ """Raised when all products in a slice are damaged and cannot be read."""
84
+
85
+
86
+ class ItemGeometryError(Exception):
87
+ """Raised when STAC item geometry cannot be resolved."""
@@ -0,0 +1,12 @@
1
+ from mapchete_eo.image_operations.color_correction import color_correct
2
+ from mapchete_eo.image_operations.dtype_scale import dtype_scale
3
+ from mapchete_eo.image_operations.fillnodata import FillSelectionMethod, fillnodata
4
+ from mapchete_eo.image_operations.linear_normalization import linear_normalization
5
+
6
+ __all__ = [
7
+ "color_correct",
8
+ "dtype_scale",
9
+ "fillnodata",
10
+ "FillSelectionMethod",
11
+ "linear_normalization",
12
+ ]