easy-eo 0.1.0__tar.gz

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 (41) hide show
  1. easy_eo-0.1.0/.gitignore +28 -0
  2. easy_eo-0.1.0/LICENSE +21 -0
  3. easy_eo-0.1.0/PKG-INFO +114 -0
  4. easy_eo-0.1.0/README.md +67 -0
  5. easy_eo-0.1.0/eeo/__init__.py +20 -0
  6. easy_eo-0.1.0/eeo/analysis/__init__.py +13 -0
  7. easy_eo-0.1.0/eeo/analysis/indices.py +62 -0
  8. easy_eo-0.1.0/eeo/analysis/indices.pyi +35 -0
  9. easy_eo-0.1.0/eeo/analysis/stats.py +127 -0
  10. easy_eo-0.1.0/eeo/analysis/stats.pyi +99 -0
  11. easy_eo-0.1.0/eeo/common.py +35 -0
  12. easy_eo-0.1.0/eeo/core/__init__.py +11 -0
  13. easy_eo-0.1.0/eeo/core/adapters/__init__.py +10 -0
  14. easy_eo-0.1.0/eeo/core/adapters/base.py +94 -0
  15. easy_eo-0.1.0/eeo/core/adapters/numpy.py +102 -0
  16. easy_eo-0.1.0/eeo/core/adapters/rasterio.py +131 -0
  17. easy_eo-0.1.0/eeo/core/core.py +198 -0
  18. easy_eo-0.1.0/eeo/core/core.pyi +955 -0
  19. easy_eo-0.1.0/eeo/core/decorators.py +52 -0
  20. easy_eo-0.1.0/eeo/core/loader.py +42 -0
  21. easy_eo-0.1.0/eeo/core/loader.pyi +41 -0
  22. easy_eo-0.1.0/eeo/core/plugins.py +15 -0
  23. easy_eo-0.1.0/eeo/core/types.py +17 -0
  24. easy_eo-0.1.0/eeo/ops/__init__.py +15 -0
  25. easy_eo-0.1.0/eeo/ops/algebra.py +170 -0
  26. easy_eo-0.1.0/eeo/ops/algebra.pyi +175 -0
  27. easy_eo-0.1.0/eeo/ops/merge.py +133 -0
  28. easy_eo-0.1.0/eeo/ops/merge.pyi +46 -0
  29. easy_eo-0.1.0/eeo/preprocessing/__init__.py +14 -0
  30. easy_eo-0.1.0/eeo/preprocessing/clip.py +136 -0
  31. easy_eo-0.1.0/eeo/preprocessing/clip.pyi +83 -0
  32. easy_eo-0.1.0/eeo/preprocessing/normalize.py +50 -0
  33. easy_eo-0.1.0/eeo/preprocessing/normalize.pyi +67 -0
  34. easy_eo-0.1.0/eeo/preprocessing/reproject.py +73 -0
  35. easy_eo-0.1.0/eeo/preprocessing/reproject.pyi +36 -0
  36. easy_eo-0.1.0/eeo/preprocessing/resample.py +81 -0
  37. easy_eo-0.1.0/eeo/preprocessing/resample.pyi +44 -0
  38. easy_eo-0.1.0/eeo/viz/__init__.py +9 -0
  39. easy_eo-0.1.0/eeo/viz/plot.py +284 -0
  40. easy_eo-0.1.0/eeo/viz/plot.pyi +163 -0
  41. easy_eo-0.1.0/pyproject.toml +62 -0
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.pyd
6
+ *.egg-info/
7
+ .env
8
+ .venv
9
+
10
+ # Builds
11
+ build/
12
+ dist/
13
+
14
+ # Data
15
+ data/
16
+
17
+ # Docs
18
+ docs/build/
19
+
20
+ # Tests
21
+ .pytest_cache/
22
+
23
+ # IDE
24
+ .vscode/
25
+ .idea/
26
+
27
+ # OS
28
+ .DS_Store
easy_eo-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Thomas Burns Botchwey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
easy_eo-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: easy-eo
3
+ Version: 0.1.0
4
+ Summary: A lightweight Earth Observation utilities library for chainable raster operations
5
+ Project-URL: Homepage, https://github.com/tommy-burns/easy-eo
6
+ Project-URL: Repository, https://github.com/tommy-burns/easy-eo
7
+ Project-URL: Documentation, https://easy-eo.readthedocs.io
8
+ Project-URL: Issues, https://github.com/tommy-burns/easy-eo/issues
9
+ Author: Thomas Burns Botchwey
10
+ License: MIT License
11
+
12
+ Copyright (c) 2025 Thomas Burns Botchwey
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: earth observation,gis,raster,remote sensing
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Scientific/Engineering :: GIS
41
+ Requires-Python: >=3.10
42
+ Requires-Dist: geopandas<1.2,>=1.1
43
+ Requires-Dist: matplotlib<3.11,>=3.10
44
+ Requires-Dist: numpy<2.2,>=1.26
45
+ Requires-Dist: rasterio<1.5,>=1.4
46
+ Description-Content-Type: text/markdown
47
+
48
+ # EasyEO
49
+
50
+ **EasyEO** is a lightweight, extensible Python library for raster-based Earth Observation (EO) analysis which allows for chainable raster processing, algebra, and visualization.
51
+ It provides high-level abstractions over libraries such as Rasterio, NumPy, and Matplotlib, enabling users to perform common earth-observation analyses and visualization tasks efficiently, without dealing with the underlying complexity.
52
+ ---
53
+
54
+ ## Features
55
+
56
+ - Raster operations with spatial awareness
57
+ - Algebraic operations (`add`, `subtract`, `multiply`, `divide`)
58
+ - Resampling, reprojection, and alignment
59
+ - Clip rasters using vectors or bounding boxes
60
+ - Normalization (min-max, percentile, z-score)
61
+ - Visualization helpers (bands, composites, histograms)
62
+ - Backend-aware design (NumPy ↔ Rasterio)
63
+
64
+ ---
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ conda create -n env_name python=3.10
70
+ conda activate env_name
71
+ pip install easy-eo
72
+ ```
73
+
74
+ ## Quick Example
75
+ ```python
76
+ from eeo import load_raster
77
+
78
+ ds_nir = load_raster("path/to/nir.tif")
79
+ ds_red = load_raster("path/to/red.tif")
80
+
81
+ # Chainable example: clip -> resample -> compute NDVI -> multiply
82
+ result = ds_nir.clip_raster_with_bbox((0,0,1000,1000))
83
+ .resample(scale_factor=2)
84
+ .normalized_difference(ds_red)
85
+ .multiply(100)
86
+ ```
87
+
88
+ ## Supported Backends
89
+ | Backend | Description |
90
+ |----------|------------------------------------------------------|
91
+ | NumPy | Fast, in-memory arrays without I/O |
92
+ | Rasterio | Full geospatial support (CRS, transform, resampling) |
93
+
94
+
95
+ ## Documentation
96
+
97
+ 📚 Full documentation is available at:
98
+
99
+ 👉 https://easyeo.readthedocs.io
100
+
101
+ ## Project Status
102
+ 🚧 Active development
103
+ The API is stabilizing but may change before v1.0.
104
+
105
+ ## Contributing
106
+ Contributions are welcome!
107
+ - Bug reports
108
+ - Feature requests
109
+ - Documentation improvements
110
+
111
+ Please open an issue or pull request on GitHub.
112
+
113
+ ## License
114
+ MIT License © 2025 Thomas Burns Botchwey
@@ -0,0 +1,67 @@
1
+ # EasyEO
2
+
3
+ **EasyEO** is a lightweight, extensible Python library for raster-based Earth Observation (EO) analysis which allows for chainable raster processing, algebra, and visualization.
4
+ It provides high-level abstractions over libraries such as Rasterio, NumPy, and Matplotlib, enabling users to perform common earth-observation analyses and visualization tasks efficiently, without dealing with the underlying complexity.
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - Raster operations with spatial awareness
10
+ - Algebraic operations (`add`, `subtract`, `multiply`, `divide`)
11
+ - Resampling, reprojection, and alignment
12
+ - Clip rasters using vectors or bounding boxes
13
+ - Normalization (min-max, percentile, z-score)
14
+ - Visualization helpers (bands, composites, histograms)
15
+ - Backend-aware design (NumPy ↔ Rasterio)
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ conda create -n env_name python=3.10
23
+ conda activate env_name
24
+ pip install easy-eo
25
+ ```
26
+
27
+ ## Quick Example
28
+ ```python
29
+ from eeo import load_raster
30
+
31
+ ds_nir = load_raster("path/to/nir.tif")
32
+ ds_red = load_raster("path/to/red.tif")
33
+
34
+ # Chainable example: clip -> resample -> compute NDVI -> multiply
35
+ result = ds_nir.clip_raster_with_bbox((0,0,1000,1000))
36
+ .resample(scale_factor=2)
37
+ .normalized_difference(ds_red)
38
+ .multiply(100)
39
+ ```
40
+
41
+ ## Supported Backends
42
+ | Backend | Description |
43
+ |----------|------------------------------------------------------|
44
+ | NumPy | Fast, in-memory arrays without I/O |
45
+ | Rasterio | Full geospatial support (CRS, transform, resampling) |
46
+
47
+
48
+ ## Documentation
49
+
50
+ 📚 Full documentation is available at:
51
+
52
+ 👉 https://easyeo.readthedocs.io
53
+
54
+ ## Project Status
55
+ 🚧 Active development
56
+ The API is stabilizing but may change before v1.0.
57
+
58
+ ## Contributing
59
+ Contributions are welcome!
60
+ - Bug reports
61
+ - Feature requests
62
+ - Documentation improvements
63
+
64
+ Please open an issue or pull request on GitHub.
65
+
66
+ ## License
67
+ MIT License © 2025 Thomas Burns Botchwey
@@ -0,0 +1,20 @@
1
+ """
2
+ Easy-EO
3
+ A lightweight Earth Observation utilities library for chainable raster operations
4
+ """
5
+
6
+
7
+ from .core import load_raster, load_array
8
+ from .analysis import *
9
+ from .ops import *
10
+ from .viz import *
11
+ from .preprocessing import *
12
+ from .core.adapters import *
13
+
14
+
15
+ __all__ = [
16
+ "load_raster",
17
+ "load_array",
18
+ ]
19
+
20
+ __version__ = "0.1.0"
@@ -0,0 +1,13 @@
1
+ from .stats import extract_value_at_coordinate, get_maximum_pixel, get_mean_pixel, get_minimum_pixel, get_percentile_pixel
2
+
3
+ from .indices import normalized_difference
4
+
5
+
6
+ __all__ = [
7
+ "extract_value_at_coordinate",
8
+ "normalized_difference",
9
+ "get_minimum_pixel",
10
+ "get_percentile_pixel",
11
+ "get_mean_pixel",
12
+ "get_maximum_pixel"
13
+ ]
@@ -0,0 +1,62 @@
1
+ """
2
+ This library exposes algebraic primitives instead of predefined indices.
3
+ Most vegetation and water indices can be expressed directly using
4
+ ``normalized_difference`` or raster arithmetic.
5
+ """
6
+
7
+ from typing import Union
8
+
9
+ import numpy as np
10
+ import rasterio as rio
11
+
12
+ from eeo.common import align_raster_to_target
13
+ from eeo.core.core import EEORasterDataset
14
+ from eeo.core.decorators import eeo_raster_op
15
+
16
+
17
+ @eeo_raster_op
18
+ def normalized_difference(
19
+ ds: EEORasterDataset,
20
+ other: EEORasterDataset,
21
+ *,
22
+ auto_align: bool = True,
23
+ method: str = "bilinear",
24
+ return_as_ndarray: bool = False
25
+ ) -> Union[np.ndarray, EEORasterDataset]:
26
+ # Ensure reprojection for only rasterio-backend datasets
27
+ backend = ds._adapter.backend
28
+ if not isinstance(backend, rio.DatasetReader):
29
+ ds = ds.to_rasterio()
30
+ if ds.get_shape() != other.get_shape() or ds.get_transform() != other.get_transform():
31
+ if auto_align:
32
+ other = align_raster_to_target(other, ds, method=method)
33
+ else:
34
+ raise ValueError("Rasters must have the same shape and alignment")
35
+
36
+
37
+ a = ds.read().astype(rio.float32)
38
+ b = other.read().astype(rio.float32)
39
+
40
+ with np.errstate(divide="ignore", invalid="ignore"):
41
+ nd = (a - b) / (a + b)
42
+ nd[np.isnan(nd)] = 0
43
+
44
+ if return_as_ndarray:
45
+ return nd
46
+
47
+ meta = ds.get_metadata().copy()
48
+
49
+ # Ensure correct metadata for writing
50
+ meta.update(
51
+ driver="GTiff",
52
+ dtype="float32",
53
+ height=nd.shape[-2],
54
+ width=nd.shape[-1],
55
+ count=nd.shape[0],
56
+ )
57
+
58
+ memfile = rio.io.MemoryFile()
59
+ out_ds = memfile.open(**meta)
60
+ out_ds.write(nd)
61
+
62
+ return EEORasterDataset.from_rasterio(out_ds)
@@ -0,0 +1,35 @@
1
+ from typing import Union
2
+
3
+ import numpy as np
4
+
5
+ from eeo.core.core import EEORasterDataset
6
+ from eeo.core.decorators import eeo_raster_op
7
+
8
+
9
+ @eeo_raster_op
10
+ def normalized_difference(
11
+ ds: EEORasterDataset,
12
+ other: EEORasterDataset,
13
+ *,
14
+ auto_align: bool = True,
15
+ method: str = "bilinear",
16
+ return_as_ndarray: bool = False
17
+ ) -> Union[np.ndarray, EEORasterDataset]:
18
+ """
19
+ Compute the normalized difference index: (ds - other) / (ds + other)
20
+
21
+
22
+ :param ds: EEORasterDataset
23
+ First raster (e.g. NIR).
24
+ :param other: EEORasterDataset
25
+ Second raster (e.g. Red).
26
+ :param auto_align: bool, optional
27
+ Automatically resample `other` to match `ds`.
28
+ :param method: str, optional
29
+ Resampling method used if auto-aligning.
30
+ :param return_as_ndarray: bool, optional
31
+ If True, return a NumPy array instead of an EEORasterDataset.
32
+
33
+ :return: Union[np.ndarray, EEORasterDataset]
34
+ """
35
+ ...
@@ -0,0 +1,127 @@
1
+ from typing import Union
2
+
3
+ import numpy as np
4
+ import rasterio as rio
5
+ from eeo.common import mask_nodata
6
+ from eeo.core.core import EEORasterDataset
7
+ from eeo.core.decorators import eeo_raster_op
8
+
9
+ Coordinate = Union[tuple[float, float], list[float]]
10
+
11
+
12
+ @eeo_raster_op
13
+ def extract_value_at_coordinate(
14
+ ds: EEORasterDataset,
15
+ coordinates: Coordinate,
16
+ band_idx: int = 1
17
+ ) -> Union[int, float]:
18
+
19
+ if len(coordinates) != 2:
20
+ raise ValueError(f"Expected 2 coordinates, got {len(coordinates)}")
21
+
22
+ backend = ds._adapter.backend
23
+ if not isinstance(backend, rio.DatasetReader):
24
+ ds = ds.to_rasterio()
25
+ backend = ds._adapter.backend
26
+
27
+ x, y = coordinates
28
+ row, col = backend.index(x, y)
29
+
30
+ return ds.get_band(band_idx)[row, col]
31
+
32
+
33
+
34
+ @eeo_raster_op
35
+ def get_maximum_pixel(
36
+ ds: EEORasterDataset,
37
+ band_idx: int = 1,
38
+ *,
39
+ return_position_as_pixel_coordinate: bool = False,
40
+ ) -> dict:
41
+ band = ds.read() if ds.get_count() == 1 else ds.get_band(band_idx)
42
+ band = mask_nodata(ds, band)
43
+
44
+ # get max value
45
+ value = float(np.nanmax(band))
46
+ _, row, col = np.unravel_index(np.nanargmax(band), band.shape)
47
+
48
+ if return_position_as_pixel_coordinate:
49
+ position = (row, col)
50
+ else:
51
+ transform = ds.get_transform()
52
+ position = transform * (col, row)
53
+
54
+ return {"value": value, "position": position}
55
+
56
+
57
+ @eeo_raster_op
58
+ def get_minimum_pixel(
59
+ ds: EEORasterDataset,
60
+ band_idx: int = 1,
61
+ *,
62
+ return_position_as_pixel_coordinate: bool = False,
63
+ ) -> dict:
64
+ band = ds.read() if ds.get_count() == 1 else ds.get_band(band_idx)
65
+ band = mask_nodata(ds, band)
66
+
67
+ # get min value
68
+ value = float(np.nanmin(band))
69
+ _, row, col = np.unravel_index(np.nanargmin(band), band.shape)
70
+
71
+ if return_position_as_pixel_coordinate:
72
+ position = (row, col)
73
+ else:
74
+ transform = ds.get_transform()
75
+ position = transform * (col, row)
76
+
77
+ return {"value": value, "position": position}
78
+
79
+
80
+ @eeo_raster_op
81
+ def get_mean_pixel(
82
+ ds: EEORasterDataset,
83
+ band_idx: int = 1,
84
+ *,
85
+ return_position_as_pixel_coordinate: bool = False,
86
+ ) -> dict:
87
+ band = ds.read() if ds.get_count() == 1 else ds.get_band(band_idx)
88
+ band = mask_nodata(ds, band)
89
+
90
+ mean_value = float(np.nanmean(band))
91
+ diff = np.abs(band - mean_value)
92
+
93
+ _, row, col = np.unravel_index(np.nanargmin(diff), diff.shape)
94
+
95
+ if return_position_as_pixel_coordinate:
96
+ position = (row, col)
97
+ else:
98
+ transform = ds.get_transform()
99
+ position = transform * (col, row)
100
+
101
+ return {"value": mean_value, "position": position}
102
+
103
+
104
+ @eeo_raster_op
105
+ def get_percentile_pixel(
106
+ ds: EEORasterDataset,
107
+ percentile: float,
108
+ band_idx: int = 1,
109
+ *,
110
+ return_position_as_pixel_coordinate: bool = False,
111
+ ) -> dict:
112
+ band = ds.read() if ds.get_count() == 1 else ds.get_band(band_idx)
113
+ band = mask_nodata(ds, band)
114
+
115
+ perc_value = float(np.nanpercentile(band, percentile))
116
+ diff = np.abs(band - perc_value)
117
+
118
+ _, row, col = np.unravel_index(np.nanargmin(diff), band.shape)
119
+
120
+ if return_position_as_pixel_coordinate:
121
+ position = (row, col)
122
+ else:
123
+ transform = ds.get_transform()
124
+ position = transform * (col, row)
125
+
126
+ return {"value": perc_value, "position": position}
127
+
@@ -0,0 +1,99 @@
1
+ from typing import Union
2
+
3
+ from eeo.core.core import EEORasterDataset
4
+
5
+ Coordinate = Union[tuple[float, float], list[float]]
6
+
7
+ def extract_value_at_coordinate(
8
+ ds: EEORasterDataset,
9
+ coordinates: Coordinate,
10
+ band_idx: int = 1,
11
+ ) -> Union[int, float]:
12
+ """
13
+ Extract the pixel value from an EEORasterDataset at a given geographic coordinate.
14
+
15
+ :param ds: The raster dataset from which to extract a value.
16
+ :type ds: EEORasterDataset
17
+ :param coordinates: A tuple of (x, y) coordinates. Must contain exactly two values.
18
+ :type coordinates: Coordinate
19
+ :param band_idx: The index of the raster band to sample from in case of a multiband raster.
20
+ :type band_idx: int
21
+
22
+ :return: The extracted pixel value from the specified band and coordinate.
23
+ :rtype: int or float
24
+
25
+ :raises ValueError: If the provided coordinates do not contain exactly two elements.
26
+ """
27
+ ...
28
+
29
+
30
+ def get_maximum_pixel(
31
+ ds: EEORasterDataset,
32
+ band_idx: int = 1,
33
+ *,
34
+ return_position_as_pixel_coordinate: bool = False,
35
+ ) -> dict:
36
+ """
37
+ Get the maximum pixel value and its location from a raster band.
38
+
39
+ :param ds: Input raster dataset.
40
+ :param band_idx: Band index to evaluate (1-based).
41
+ :param return_position_as_pixel_coordinate: If True, return (row, col) instead of spatial coordinates.
42
+
43
+ :return: Dictionary containing the maximum value and its position.
44
+ :rtype: dict[str, float | tuple]
45
+ """
46
+ ...
47
+
48
+ def get_minimum_pixel(
49
+ ds: EEORasterDataset,
50
+ band_idx: int = 1,
51
+ *,
52
+ return_position_as_pixel_coordinate: bool = False,
53
+ ) -> dict:
54
+ """
55
+ Get the minimum pixel value and its location from a raster band.
56
+
57
+ :param ds: Input raster dataset.
58
+ :param band_idx: Band index to evaluate (1-based).
59
+ :param return_position_as_pixel_coordinate: If True, return (row, col) instead of spatial coordinates.
60
+
61
+ :return: Dictionary containing the minimum value and its position.
62
+ :rtype: dict[str, float | tuple]
63
+ """
64
+ ...
65
+
66
+ def get_mean_pixel(
67
+ ds: EEORasterDataset,
68
+ band_idx: int = 1,
69
+ *,
70
+ return_position_as_pixel_coordinate: bool = False,
71
+ ) -> dict:
72
+ """
73
+ Get the pixel whose value is closest to the mean of the raster band.
74
+
75
+ :param ds: Input raster dataset.
76
+ :param band_idx: Band index to evaluate (1-based).
77
+ :param return_position_as_pixel_coordinate: If True, return (row, col) instead of spatial coordinates.
78
+
79
+ :return: Dictionary containing the mean value and its position.
80
+ """
81
+ ...
82
+
83
+ def get_percentile_pixel(
84
+ ds: EEORasterDataset,
85
+ percentile: float,
86
+ band_idx: int = 1,
87
+ *,
88
+ return_position_as_pixel_coordinate: bool = False,
89
+ ) -> dict:
90
+ """
91
+ Get the pixel corresponding to a given percentile of raster values.
92
+
93
+ :param ds: Input raster dataset.
94
+ :param percentile: Percentile in the range [0, 100].
95
+ :param band_idx: Band index to evaluate (1-based).
96
+ :param return_position_as_pixel_coordinate: If True, return (row, col) instead of spatial coordinates.
97
+ :return: Dictionary containing the percentile value and its position.
98
+ """
99
+ ...
@@ -0,0 +1,35 @@
1
+ import numpy as np
2
+ from rasterio.enums import Resampling
3
+
4
+ from eeo.core.core import EEORasterDataset
5
+
6
+
7
+ def normalize_resampling_method(value):
8
+ """Function to normalize resampling method either user passes a rasterio.enums.Resampling or a string"""
9
+ if isinstance(value, Resampling):
10
+ return value
11
+ if isinstance(value, str):
12
+ name = value.lower().strip()
13
+ try:
14
+ return Resampling[name]
15
+ except KeyError:
16
+ valid = ", ".join([r.name for r in Resampling])
17
+ raise ValueError(f"Invalid resampling method {value}. Valid values are: {valid}")
18
+ raise TypeError("resampling method must be one from rasterio.enums.Resampling or a string")
19
+
20
+
21
+ # Helper function for raster auto-alignment
22
+ def align_raster_to_target(ds: EEORasterDataset, target: EEORasterDataset,
23
+ method: str = "bilinear") -> EEORasterDataset:
24
+ """Resample dataset to match target raster's shape and transform"""
25
+ if ds.get_shape() == target.get_shape() or ds.get_transform() == target.get_transform():
26
+ return ds
27
+ return ds.resample(size=target.get_shape(), resampling_method=method)
28
+
29
+
30
+ # helper to mask nodata values from an EEORasterDataset
31
+ def mask_nodata(ds: EEORasterDataset, array: np.ndarray) -> np.ndarray:
32
+ nodata = ds.get_metadata().get("nodata", None)
33
+ if nodata is not None:
34
+ array = np.where(array == nodata, np.nan, array)
35
+ return array
@@ -0,0 +1,11 @@
1
+ from .core import EEORasterDataset
2
+ from .loader import load_raster, load_array
3
+ from .plugins import load_ops
4
+
5
+ load_ops()
6
+
7
+ __all__ = [
8
+ "EEORasterDataset",
9
+ "load_raster",
10
+ "load_array",
11
+ ]
@@ -0,0 +1,10 @@
1
+ from .base import BaseRasterAdapter
2
+ from .rasterio import RasterioAdapter
3
+ from .numpy import NumpyRasterioAdapter
4
+
5
+
6
+ __all__ = [
7
+ "BaseRasterAdapter",
8
+ "RasterioAdapter",
9
+ "NumpyRasterioAdapter",
10
+ ]