easy-eo 0.1.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.
@@ -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,40 @@
1
+ eeo/__init__.py,sha256=Mfax2GYSEmqvaoLGLuuU3XGIn8LAL1HEn3NKgFLILOc,338
2
+ eeo/common.py,sha256=nFVIvcTv1QtEw2sRRbmb3O1JIh_kSkKmla4TT6E3cBA,1438
3
+ eeo/analysis/__init__.py,sha256=dMdhRbd-DFteo-buxAUAw-JRjEnNByylyElPyCqmigk,346
4
+ eeo/analysis/indices.py,sha256=HWezvp22vwBbpaKV-uJYMkOWbcmysKaWq9eZ7FFmF0Q,1740
5
+ eeo/analysis/indices.pyi,sha256=VHXJz0G73TLZkeqPoTK31SzMVxsvGv_shFXlilH1MIU,1022
6
+ eeo/analysis/stats.py,sha256=OePUexlDqtHm5Yw-W6fUI4ywbH6CyCgMokVmwz77E8U,3366
7
+ eeo/analysis/stats.pyi,sha256=gadY8wnxI4tcmDk1frqT6oi2pkSyOLa4zwss9BXiJqM,3109
8
+ eeo/core/__init__.py,sha256=vGae3Ge5jD-FtlaKSH4DApmao8O610PATfFMw7QquCE,197
9
+ eeo/core/core.py,sha256=PGAT_EiP5x5tBKUlnwkOhYA28frJgVgUacjo7wInsEE,5154
10
+ eeo/core/core.pyi,sha256=-1kgxHcyyh0cMik4lMQdbK7-3gBmwtggnPO_9R5phBY,30352
11
+ eeo/core/decorators.py,sha256=ihRkRe5AbwID29_iVTnYaEnuFhMyUnsjonwreeUZILQ,1581
12
+ eeo/core/loader.py,sha256=zuuM5MyclPlFbbJ336LrYU5gIGIbau8NRg_jow2fpGE,1156
13
+ eeo/core/loader.pyi,sha256=ZHFkWqVd7B_5VE2zUxOMdIWhqwGdPVwKfpKJwHhhGxM,1356
14
+ eeo/core/plugins.py,sha256=p1Iwd8T4xWkNLY8k-Lliuz_EuZW6EMncwVULpz0KVW8,478
15
+ eeo/core/types.py,sha256=N6O7-oonAeQP44oZDQ1C3lVbyfTX8LnaPjbtt-ec6ck,273
16
+ eeo/core/adapters/__init__.py,sha256=DcVkSYDDrhey07SBaeE1-tXuzyXTzwLL37sk5YQ35xk,206
17
+ eeo/core/adapters/base.py,sha256=rA5TlG5e9e1TAI3rtd-VmLPp0SxCbNtDV2pCLoroOrs,2055
18
+ eeo/core/adapters/numpy.py,sha256=jxPKF8WjCaHfP62uPwE1DAuCEPlmg57PXDFrC0z_F14,2724
19
+ eeo/core/adapters/rasterio.py,sha256=cphA4i1iaBbP_ly4xYaxSvUfZ5hIUJxlusx-FmJWJy8,3353
20
+ eeo/ops/__init__.py,sha256=WYkRfGRu0_memC-jCJAIGb1RbK1SJvLy6P5zHpA1kMU,264
21
+ eeo/ops/algebra.py,sha256=HivIT9e9rg4Ifmqh-X8x6mAAV-5I9BKLSDksRsNnC-0,5686
22
+ eeo/ops/algebra.pyi,sha256=3GKjNxz6c5eEu7S4aiKyV7mUBbgy8cMJcnQBi9xjXMg,3906
23
+ eeo/ops/merge.py,sha256=i6ajEEwyYjbwcbIp8mm9N3QkomQkz-q3Po02ySAqOGE,3789
24
+ eeo/ops/merge.pyi,sha256=KDN5hkpBPVfJmpork02glmSIZ-4YAhuGUqU1vMCNb60,1197
25
+ eeo/preprocessing/__init__.py,sha256=pCT2etB3U5LJUwvYaiqC-J-MvlKZio3Mx_MwvPFiUWs,399
26
+ eeo/preprocessing/clip.py,sha256=9NWHF9XOGiAhHo3OJ-Kkfr9ney2xpaYeTRx7QRfYo4A,3657
27
+ eeo/preprocessing/clip.pyi,sha256=twOgFrKReyqBNl_ny8cxkHYbf0iYNc1fQBtGrH6eZ8U,3234
28
+ eeo/preprocessing/normalize.py,sha256=uCktIlMxdlWiC6IARAlBoZGKETuri6avHcjED3EUZM4,1829
29
+ eeo/preprocessing/normalize.pyi,sha256=uX_jMCd2M-nyqAwE5pLXh9rY43cMn5Y7lZdIxKiOlWU,1499
30
+ eeo/preprocessing/reproject.py,sha256=amHfZ438fY36u8OE1CaZ-zeHK1BqV841lIiSL5lBuz4,2163
31
+ eeo/preprocessing/reproject.pyi,sha256=7ptnJGj9ynA2j1VCCBxODufNfpydCoWTGpR3UiUJSYc,1020
32
+ eeo/preprocessing/resample.py,sha256=jFhNvUOZDrtLNNUlH2CI_BXi3SU0dmbVaoYvxgRfx1s,2726
33
+ eeo/preprocessing/resample.pyi,sha256=nEqtsNnwv4TqNi49RXuG6NWYitCJ6jeL0TOCg-r_nNw,2143
34
+ eeo/viz/__init__.py,sha256=-b320FRePvx2D7T-E0QX8zItgzICsCdPQtZjOs_bdAE,241
35
+ eeo/viz/plot.py,sha256=2fgFm8grI1DR91cwAN1CcGoLw3eoO0qs3TbYbvox-l8,8078
36
+ eeo/viz/plot.pyi,sha256=0zsNc8YYOwHJGrQlhSUvNcm_vSw9opAMr2f94IR3gaE,5272
37
+ easy_eo-0.1.0.dist-info/METADATA,sha256=qdNvabcqefd0xjbV93uiKeMXf6zxCGUz4DjUE9ghfNs,4335
38
+ easy_eo-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
+ easy_eo-0.1.0.dist-info/licenses/LICENSE,sha256=Swjd_zi9gT_xRe3AfsAE4r7aMoCl_B5hGJNiEcNHmow,1078
40
+ easy_eo-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.
eeo/__init__.py ADDED
@@ -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
+ ...
eeo/analysis/stats.py ADDED
@@ -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
+
eeo/analysis/stats.pyi ADDED
@@ -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
+ ...
eeo/common.py ADDED
@@ -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
eeo/core/__init__.py ADDED
@@ -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
+ ]