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.
- easy_eo-0.1.0.dist-info/METADATA +114 -0
- easy_eo-0.1.0.dist-info/RECORD +40 -0
- easy_eo-0.1.0.dist-info/WHEEL +4 -0
- easy_eo-0.1.0.dist-info/licenses/LICENSE +21 -0
- eeo/__init__.py +20 -0
- eeo/analysis/__init__.py +13 -0
- eeo/analysis/indices.py +62 -0
- eeo/analysis/indices.pyi +35 -0
- eeo/analysis/stats.py +127 -0
- eeo/analysis/stats.pyi +99 -0
- eeo/common.py +35 -0
- eeo/core/__init__.py +11 -0
- eeo/core/adapters/__init__.py +10 -0
- eeo/core/adapters/base.py +94 -0
- eeo/core/adapters/numpy.py +102 -0
- eeo/core/adapters/rasterio.py +131 -0
- eeo/core/core.py +198 -0
- eeo/core/core.pyi +955 -0
- eeo/core/decorators.py +52 -0
- eeo/core/loader.py +42 -0
- eeo/core/loader.pyi +41 -0
- eeo/core/plugins.py +15 -0
- eeo/core/types.py +17 -0
- eeo/ops/__init__.py +15 -0
- eeo/ops/algebra.py +170 -0
- eeo/ops/algebra.pyi +175 -0
- eeo/ops/merge.py +133 -0
- eeo/ops/merge.pyi +46 -0
- eeo/preprocessing/__init__.py +14 -0
- eeo/preprocessing/clip.py +136 -0
- eeo/preprocessing/clip.pyi +83 -0
- eeo/preprocessing/normalize.py +50 -0
- eeo/preprocessing/normalize.pyi +67 -0
- eeo/preprocessing/reproject.py +73 -0
- eeo/preprocessing/reproject.pyi +36 -0
- eeo/preprocessing/resample.py +81 -0
- eeo/preprocessing/resample.pyi +44 -0
- eeo/viz/__init__.py +9 -0
- eeo/viz/plot.py +284 -0
- eeo/viz/plot.pyi +163 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from rasterio.coords import BoundingBox
|
|
8
|
+
from rasterio.crs import CRS
|
|
9
|
+
from rasterio.transform import Affine
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseRasterAdapter(ABC):
|
|
13
|
+
###########################
|
|
14
|
+
# METADATA
|
|
15
|
+
##########################
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def get_crs(self) -> CRS:
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def get_transform(self) -> Affine:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_bounds(self) -> BoundingBox:
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def get_shape(self) -> Tuple[int, int]:
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def get_width(self) -> int:
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def get_height(self) -> int:
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def get_count(self) -> int:
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def get_nodata(self) -> Optional[float]:
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def get_metadata(self) -> dict[Any, Any]:
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
###########################
|
|
53
|
+
# DATA ACCESS
|
|
54
|
+
##########################
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def read(self, *args, **kwargs) -> np.ndarray:
|
|
58
|
+
"""
|
|
59
|
+
Read the entire raster as a NumPy array
|
|
60
|
+
For multiband rasters, returns an array of shape (bands, height, width)
|
|
61
|
+
"""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def read_band(self, idx: int) -> np.ndarray:
|
|
66
|
+
"""
|
|
67
|
+
Read a single band (1-based index).
|
|
68
|
+
"""
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
###########################
|
|
72
|
+
# Persistence
|
|
73
|
+
##########################
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def write(self, path: str, driver: str = "GTiff") -> None:
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def close(self) -> None:
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
###########################
|
|
84
|
+
# BACKEND ACCESS - RETURNING THE UNDERLYING DATASET
|
|
85
|
+
##########################
|
|
86
|
+
@property
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def backend(self) -> Any:
|
|
89
|
+
"""
|
|
90
|
+
Return the underlying backend object (rasterio.DatasetReader or numpy.ndarray)
|
|
91
|
+
|
|
92
|
+
WARNING: This bypasses Easy-EO abstractions
|
|
93
|
+
"""
|
|
94
|
+
...
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import rasterio as rio
|
|
5
|
+
from rasterio.crs import CRS
|
|
6
|
+
from rasterio.transform import Affine
|
|
7
|
+
|
|
8
|
+
from eeo.core.adapters import BaseRasterAdapter, RasterioAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NumpyRasterioAdapter(BaseRasterAdapter):
|
|
12
|
+
""" Numpy backend adapter for EEORasterDataset """
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
array: np.ndarray,
|
|
16
|
+
transform: Affine,
|
|
17
|
+
crs: CRS,
|
|
18
|
+
driver: str = "GTiff",
|
|
19
|
+
nodata: Optional[float] = None,
|
|
20
|
+
):
|
|
21
|
+
if array.ndim == 2:
|
|
22
|
+
array = array[np.newaxis, ...]
|
|
23
|
+
|
|
24
|
+
self._array = array
|
|
25
|
+
self._transform = transform
|
|
26
|
+
self._crs = crs
|
|
27
|
+
self._nodata = nodata
|
|
28
|
+
self._driver = driver
|
|
29
|
+
|
|
30
|
+
# ========================
|
|
31
|
+
# Metadata
|
|
32
|
+
# ========================
|
|
33
|
+
def get_crs(self) -> CRS:
|
|
34
|
+
return self._crs
|
|
35
|
+
|
|
36
|
+
def get_transform(self) -> Affine:
|
|
37
|
+
return self._transform
|
|
38
|
+
|
|
39
|
+
def get_bounds(self):
|
|
40
|
+
h, w = self.get_shape()
|
|
41
|
+
return rio.transform.array_bounds(h, w, self._transform)
|
|
42
|
+
|
|
43
|
+
def get_shape(self) -> tuple[int, int]:
|
|
44
|
+
return self._array.shape[-2:]
|
|
45
|
+
|
|
46
|
+
def get_width(self) -> int:
|
|
47
|
+
return self.get_shape()[1]
|
|
48
|
+
|
|
49
|
+
def get_height(self) -> int:
|
|
50
|
+
return self.get_shape()[0]
|
|
51
|
+
|
|
52
|
+
def get_count(self) -> int:
|
|
53
|
+
return self._array.shape[0]
|
|
54
|
+
|
|
55
|
+
def get_nodata(self) -> Optional[float]:
|
|
56
|
+
return self._nodata
|
|
57
|
+
|
|
58
|
+
def get_metadata(self) -> dict:
|
|
59
|
+
return {
|
|
60
|
+
"dtype": self._array.dtype,
|
|
61
|
+
"nodata": self._nodata,
|
|
62
|
+
"transform": self._transform,
|
|
63
|
+
"crs": self._crs,
|
|
64
|
+
"driver": self._driver,
|
|
65
|
+
"count": self.get_count(),
|
|
66
|
+
"width": self.get_width(),
|
|
67
|
+
"height": self.get_height(),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ========================
|
|
71
|
+
# Data Access
|
|
72
|
+
# ========================
|
|
73
|
+
def read(self, *args, **kwargs) -> np.ndarray:
|
|
74
|
+
return self._array
|
|
75
|
+
|
|
76
|
+
def read_band(self, idx: int) -> np.ndarray:
|
|
77
|
+
if idx < 1 or idx > self.get_count():
|
|
78
|
+
raise IndexError(f"Band index {idx} out of range")
|
|
79
|
+
return self._array[idx - 1]
|
|
80
|
+
|
|
81
|
+
# ========================
|
|
82
|
+
# Persistence
|
|
83
|
+
# ========================
|
|
84
|
+
def write(self, path: str, driver: str = "GTiff") -> None:
|
|
85
|
+
adapter = RasterioAdapter.from_array(
|
|
86
|
+
array=self._array,
|
|
87
|
+
transform=self._transform,
|
|
88
|
+
crs=self._crs,
|
|
89
|
+
nodata=self._nodata,
|
|
90
|
+
)
|
|
91
|
+
adapter.write(path, driver=driver)
|
|
92
|
+
adapter.close()
|
|
93
|
+
|
|
94
|
+
def close(self) -> None:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
# ========================
|
|
98
|
+
# Backend Access
|
|
99
|
+
# ========================
|
|
100
|
+
@property
|
|
101
|
+
def backend(self) -> np.ndarray:
|
|
102
|
+
return self._array
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import rasterio as rio
|
|
7
|
+
from rasterio.io import DatasetReader, MemoryFile
|
|
8
|
+
|
|
9
|
+
from .base import BaseRasterAdapter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RasterioAdapter(BaseRasterAdapter):
|
|
13
|
+
"""
|
|
14
|
+
Rasterio-backend adapter for EEORasterDataset
|
|
15
|
+
"""
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
dataset: DatasetReader,
|
|
19
|
+
*,
|
|
20
|
+
memory_file: MemoryFile | None = None,
|
|
21
|
+
) -> None:
|
|
22
|
+
self._ds = dataset
|
|
23
|
+
self._memory_file = memory_file
|
|
24
|
+
|
|
25
|
+
# ========================
|
|
26
|
+
# Factories
|
|
27
|
+
# ========================
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_path(cls, path: str) -> "RasterioAdapter":
|
|
30
|
+
try:
|
|
31
|
+
dataset = rio.open(path)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
raise RuntimeError(f"Failed to open raster: {path}") from e
|
|
34
|
+
return cls(dataset)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_array(
|
|
38
|
+
cls,
|
|
39
|
+
array: np.ndarray,
|
|
40
|
+
*,
|
|
41
|
+
transform,
|
|
42
|
+
crs,
|
|
43
|
+
nodata: Optional[float] = None,
|
|
44
|
+
dtype: Optional[str] = None,
|
|
45
|
+
) -> "RasterioAdapter":
|
|
46
|
+
"""
|
|
47
|
+
Create an in-memory adapter from a NumPy array
|
|
48
|
+
"""
|
|
49
|
+
if array.ndim == 2:
|
|
50
|
+
array = array[np.newaxis, ...]
|
|
51
|
+
|
|
52
|
+
count, height, width = array.shape
|
|
53
|
+
memfile = MemoryFile()
|
|
54
|
+
dataset = memfile.open(
|
|
55
|
+
driver="GTiff",
|
|
56
|
+
height=height,
|
|
57
|
+
width=width,
|
|
58
|
+
count=count,
|
|
59
|
+
transform=transform,
|
|
60
|
+
crs=crs,
|
|
61
|
+
nodata=nodata,
|
|
62
|
+
dtype=dtype or array.dtype,
|
|
63
|
+
)
|
|
64
|
+
dataset.write(array)
|
|
65
|
+
return cls(dataset, memory_file=memfile)
|
|
66
|
+
|
|
67
|
+
# ========================
|
|
68
|
+
# Metadata
|
|
69
|
+
# ========================
|
|
70
|
+
def get_crs(self):
|
|
71
|
+
return self._ds.crs
|
|
72
|
+
|
|
73
|
+
def get_transform(self):
|
|
74
|
+
return self._ds.transform
|
|
75
|
+
|
|
76
|
+
def get_bounds(self):
|
|
77
|
+
return self._ds.bounds
|
|
78
|
+
|
|
79
|
+
def get_shape(self):
|
|
80
|
+
return self._ds.shape
|
|
81
|
+
|
|
82
|
+
def get_width(self):
|
|
83
|
+
return self._ds.width
|
|
84
|
+
|
|
85
|
+
def get_height(self):
|
|
86
|
+
return self._ds.height
|
|
87
|
+
|
|
88
|
+
def get_count(self):
|
|
89
|
+
return self._ds.count
|
|
90
|
+
|
|
91
|
+
def get_nodata(self):
|
|
92
|
+
return self._ds.nodata
|
|
93
|
+
|
|
94
|
+
def get_metadata(self):
|
|
95
|
+
return self._ds.meta.copy()
|
|
96
|
+
|
|
97
|
+
# ========================
|
|
98
|
+
# Data Access
|
|
99
|
+
# ========================
|
|
100
|
+
def read(self, *args, **kwargs) -> np.ndarray:
|
|
101
|
+
return self._ds.read(*args, **kwargs)
|
|
102
|
+
|
|
103
|
+
def read_band(self, idx: int) -> np.ndarray:
|
|
104
|
+
if idx < 1 or idx > self._ds.count:
|
|
105
|
+
raise IndexError(f"Band index {idx} out of range")
|
|
106
|
+
return self._ds.read(idx)
|
|
107
|
+
|
|
108
|
+
# ========================
|
|
109
|
+
# Persistence
|
|
110
|
+
# ========================
|
|
111
|
+
def write(self, path: str, driver: str = "GTiff") -> None:
|
|
112
|
+
meta = self._ds.meta.copy()
|
|
113
|
+
meta.update(driver=driver)
|
|
114
|
+
|
|
115
|
+
with rio.open(path, "w", **meta) as dst:
|
|
116
|
+
for i in range(1, self._ds.count + 1):
|
|
117
|
+
dst.write(self._ds.read(i), i)
|
|
118
|
+
|
|
119
|
+
def close(self) -> None:
|
|
120
|
+
try:
|
|
121
|
+
self._ds.close()
|
|
122
|
+
finally:
|
|
123
|
+
if self._memory_file is not None:
|
|
124
|
+
self._memory_file.close()
|
|
125
|
+
|
|
126
|
+
# ========================
|
|
127
|
+
# Backend Access
|
|
128
|
+
# ========================
|
|
129
|
+
@property
|
|
130
|
+
def backend(self) -> DatasetReader:
|
|
131
|
+
return self._ds
|
eeo/core/core.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core functionalities for easy-eo
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import rasterio as rio
|
|
10
|
+
from rasterio import CRS
|
|
11
|
+
from rasterio.transform import Affine
|
|
12
|
+
|
|
13
|
+
from eeo.core.adapters import NumpyRasterioAdapter
|
|
14
|
+
from eeo.core.adapters import RasterioAdapter, BaseRasterAdapter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# IO helper
|
|
18
|
+
def _save_raster(dataset: rio.DatasetReader, path: str, driver: str = "GTiff") -> None:
|
|
19
|
+
profile = dataset.profile.copy()
|
|
20
|
+
if driver != "GTiff":
|
|
21
|
+
profile.update(driver=driver)
|
|
22
|
+
with rio.open(path, mode="w", **profile) as dst:
|
|
23
|
+
dst.write(dataset.read())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Core class
|
|
27
|
+
class EEORasterDataset:
|
|
28
|
+
def __init__(self, adapter: BaseRasterAdapter, path: str | None = None):
|
|
29
|
+
self._adapter = adapter
|
|
30
|
+
self.path = path
|
|
31
|
+
|
|
32
|
+
# ========================
|
|
33
|
+
# Constructors
|
|
34
|
+
# ========================
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_path(cls, path: str) -> "EEORasterDataset":
|
|
37
|
+
adapter = RasterioAdapter.from_path(path)
|
|
38
|
+
return cls(adapter=adapter, path=path)
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_rasterio(cls, dataset: rio.DatasetReader) -> "EEORasterDataset":
|
|
42
|
+
return cls(adapter=RasterioAdapter(dataset))
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_array(
|
|
46
|
+
cls,
|
|
47
|
+
array: np.ndarray,
|
|
48
|
+
transform: Affine,
|
|
49
|
+
crs: Union[CRS, str, int],
|
|
50
|
+
driver: str = "GTiff",
|
|
51
|
+
nodata=None,
|
|
52
|
+
) -> "EEORasterDataset":
|
|
53
|
+
adapter = NumpyRasterioAdapter(
|
|
54
|
+
array=array,
|
|
55
|
+
transform=transform,
|
|
56
|
+
crs=crs,
|
|
57
|
+
nodata=nodata,
|
|
58
|
+
driver=driver,
|
|
59
|
+
)
|
|
60
|
+
return cls(adapter=adapter)
|
|
61
|
+
|
|
62
|
+
# ========================
|
|
63
|
+
# Conversion between adapters
|
|
64
|
+
# ========================
|
|
65
|
+
|
|
66
|
+
def to_rasterio(self) -> "EEORasterDataset":
|
|
67
|
+
backend = self._adapter.backend
|
|
68
|
+
|
|
69
|
+
# already a rasterio backend
|
|
70
|
+
if isinstance(backend, rio.DatasetReader):
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
array = self.read()
|
|
74
|
+
transform = self.get_transform()
|
|
75
|
+
crs = self.get_crs()
|
|
76
|
+
nodata = self._adapter.get_nodata()
|
|
77
|
+
|
|
78
|
+
adapter = RasterioAdapter.from_array(
|
|
79
|
+
array=array,
|
|
80
|
+
transform=transform,
|
|
81
|
+
crs=crs,
|
|
82
|
+
nodata=nodata,
|
|
83
|
+
)
|
|
84
|
+
return EEORasterDataset(adapter=adapter)
|
|
85
|
+
|
|
86
|
+
def to_array(self) -> np.ndarray:
|
|
87
|
+
return self.read()
|
|
88
|
+
|
|
89
|
+
# ========================
|
|
90
|
+
# Metadata
|
|
91
|
+
# ========================
|
|
92
|
+
|
|
93
|
+
def read(self, *args, **kwargs):
|
|
94
|
+
"""Forward rasterio.read"""
|
|
95
|
+
return self._adapter.read(*args, **kwargs)
|
|
96
|
+
|
|
97
|
+
def get_crs(self):
|
|
98
|
+
return self._adapter.get_crs()
|
|
99
|
+
|
|
100
|
+
def get_transform(self):
|
|
101
|
+
return self._adapter.get_transform()
|
|
102
|
+
|
|
103
|
+
def get_shape(self):
|
|
104
|
+
return self._adapter.get_shape()
|
|
105
|
+
|
|
106
|
+
def get_bounds(self):
|
|
107
|
+
return self._adapter.get_bounds()
|
|
108
|
+
|
|
109
|
+
def get_metadata(self):
|
|
110
|
+
return self._adapter.get_metadata()
|
|
111
|
+
|
|
112
|
+
def get_width(self):
|
|
113
|
+
return self._adapter.get_width()
|
|
114
|
+
|
|
115
|
+
def get_height(self):
|
|
116
|
+
return self._adapter.get_height()
|
|
117
|
+
|
|
118
|
+
def get_count(self):
|
|
119
|
+
return self._adapter.get_count()
|
|
120
|
+
|
|
121
|
+
def get_index(self):
|
|
122
|
+
return self.ds.index
|
|
123
|
+
|
|
124
|
+
def get_band(self, idx: int) -> np.ndarray:
|
|
125
|
+
return self._adapter.read_band(idx)
|
|
126
|
+
|
|
127
|
+
# ========================
|
|
128
|
+
# Saving
|
|
129
|
+
# ========================
|
|
130
|
+
def save_raster(self, path: str, driver: str="GTiff") -> None:
|
|
131
|
+
self._adapter.write(path=path, driver=driver)
|
|
132
|
+
|
|
133
|
+
# ========================
|
|
134
|
+
# Lifecycle
|
|
135
|
+
# ========================
|
|
136
|
+
def close(self) -> None:
|
|
137
|
+
self._adapter.close()
|
|
138
|
+
|
|
139
|
+
def __del__(self):
|
|
140
|
+
try:
|
|
141
|
+
self.close()
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
# ========================
|
|
146
|
+
# Constructors
|
|
147
|
+
# ========================
|
|
148
|
+
def _bind(self, func):
|
|
149
|
+
"""Helper that wraps external functions as bound methods"""
|
|
150
|
+
def method(*args, **kwargs):
|
|
151
|
+
result = func(*args, **kwargs)
|
|
152
|
+
return self if result is None else result
|
|
153
|
+
return method
|
|
154
|
+
|
|
155
|
+
# ========================
|
|
156
|
+
# Adapter access
|
|
157
|
+
# ========================
|
|
158
|
+
@property
|
|
159
|
+
def ds(self):
|
|
160
|
+
return self._adapter.backend
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ========================
|
|
164
|
+
# Arithmetic Operators
|
|
165
|
+
# ========================
|
|
166
|
+
def __add__(self, other):
|
|
167
|
+
return self.add(other)
|
|
168
|
+
|
|
169
|
+
def __radd__(self, other):
|
|
170
|
+
return self.add(other)
|
|
171
|
+
|
|
172
|
+
def __sub__(self, other):
|
|
173
|
+
return self.subtract(other)
|
|
174
|
+
|
|
175
|
+
def __rsub__(self, other):
|
|
176
|
+
# implement for only raster - scalar
|
|
177
|
+
if isinstance(other, (int, float)):
|
|
178
|
+
return self.multiply(-1).add(other)
|
|
179
|
+
return NotImplemented
|
|
180
|
+
|
|
181
|
+
def __mul__(self, other):
|
|
182
|
+
return self.multiply(other)
|
|
183
|
+
|
|
184
|
+
def __rmul__(self, other):
|
|
185
|
+
return self.multiply(other)
|
|
186
|
+
|
|
187
|
+
def __truediv__(self, other):
|
|
188
|
+
return self.divide(other)
|
|
189
|
+
|
|
190
|
+
def __rtruediv__(self, other):
|
|
191
|
+
# implement scalar / raster
|
|
192
|
+
if isinstance(other, (int, float)):
|
|
193
|
+
return self.power(-1).multiply(other)
|
|
194
|
+
return NotImplemented
|
|
195
|
+
|
|
196
|
+
def __pow__(self, exponent):
|
|
197
|
+
return self.power(exponent)
|
|
198
|
+
|