diff --git a/docs/_extension/api_admonition.py b/docs/_extension/api_admonition.py index 4b6c69d..7e09605 100644 --- a/docs/_extension/api_admonition.py +++ b/docs/_extension/api_admonition.py @@ -1,6 +1,5 @@ """A directive to generate an API admonition.""" - -from typing import Any, Dict +from __future__ import annotations from docutils import nodes from docutils.parsers.rst import directives @@ -56,7 +55,7 @@ def run(self) -> list[nodes.Node]: raise RuntimeError # never reached here -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, object]: """Add custom configuration to sphinx app. Args: diff --git a/docs/_extension/docstring.py b/docs/_extension/docstring.py index a509ef9..56dd243 100644 --- a/docs/_extension/docstring.py +++ b/docs/_extension/docstring.py @@ -1,8 +1,6 @@ """A docstring role to read the docstring from a Python method.""" from __future__ import annotations -from typing import Any, Dict, List, Tuple - from docutils import nodes from sphinx.application import Sphinx from sphinx.util import logging @@ -16,7 +14,7 @@ class DocstringRole(SphinxRole): """The docstring role interpreter.""" - def run(self) -> Tuple[List[nodes.Node], List[str]]: + def run(self) -> tuple[list[nodes.Node], list[str]]: """Setup the role in the builder context.""" members = self.text.split(".")[1:] o = geetools @@ -25,7 +23,7 @@ def run(self) -> Tuple[List[nodes.Node], List[str]]: return [nodes.Text(o.__doc__.splitlines()[0])], [] -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, object]: """Add custom configuration to sphinx application.""" app.add_role("docstring", DocstringRole()) diff --git a/geetools/Array.py b/geetools/Array.py index fa1c0d9..a666b77 100644 --- a/geetools/Array.py +++ b/geetools/Array.py @@ -4,11 +4,6 @@ import ee from .accessors import register_class_accessor -from .types import ee_int, ee_number - -# hack to have the generated Array class available -# it might create issues in the future with libs that have exotic init methods -# ee.Initialize() @register_class_accessor(ee.Array, "geetools") @@ -22,9 +17,9 @@ def __init__(self, obj: ee.Array): # -- alternative constructor ----------------------------------------------- def full( self, - width: ee_number, - height: ee_number, - value: ee_number, + width: float | int | ee.Number, + height: float | int | ee.Number, + value: float | int | ee.Number, ) -> ee.Array: """Create an array with the given dimensions, initialized to the given value. @@ -52,9 +47,9 @@ def full( # -- data maniputlation ---------------------------------------------------- def set( self, - x: ee_int, - y: ee_int, - value: ee_number, + x: int | ee.Number, + y: int | ee.Number, + value: float | int | ee.Number, ) -> ee.Array: """Set the value of a cell in an array. diff --git a/geetools/Asset.py b/geetools/Asset.py index 8e175ff..c6c53aa 100644 --- a/geetools/Asset.py +++ b/geetools/Asset.py @@ -4,12 +4,10 @@ import os import re from pathlib import PurePosixPath -from typing import Optional import ee from .accessors import _register_extention -from .types import pathlike from .utils import format_description @@ -34,23 +32,23 @@ def __repr__(self): """Return the asset object representation as a string.""" return f"ee.{type(self).__name__}('{self.as_posix()}')" - def __truediv__(self, other: pathlike) -> Asset: + def __truediv__(self, other: os.PathLike) -> Asset: """Override the division operator to join the asset with other paths.""" return Asset(self._path / str(other)) - def __lt__(self, other: pathlike) -> bool: + def __lt__(self, other: os.PathLike) -> bool: """Override the less than operator to compare the asset with other paths.""" return self._path < PurePosixPath(str(other)) - def __gt__(self, other: pathlike) -> bool: + def __gt__(self, other: os.PathLike) -> bool: """Override the greater than operator to compare the asset with other paths.""" return self._path > PurePosixPath(str(other)) - def __le__(self, other: pathlike) -> bool: + def __le__(self, other: os.PathLike) -> bool: """Override the less than or equal operator to compare the asset with other paths.""" return self._path <= PurePosixPath(str(other)) - def __ge__(self, other: pathlike) -> bool: + def __ge__(self, other: os.PathLike) -> bool: """Override the greater than or equal operator to compare the asset with other paths.""" return self._path >= PurePosixPath(str(other)) @@ -62,7 +60,7 @@ def __ne__(self, other: object) -> bool: """Override the not equal operator to compare the asset with other paths.""" return self._path != PurePosixPath(str(other)) - def __idiv__(self, other: pathlike) -> Asset: + def __idiv__(self, other: os.PathLike) -> Asset: """Override the in-place division operator to join the asset with other paths.""" return Asset(self._path / str(other)) @@ -271,7 +269,7 @@ def st_size(self): return int(ee.data.getAsset(self.as_posix())["sizeBytes"]) - def is_relative_to(self, other: pathlike) -> bool: + def is_relative_to(self, other: os.PathLike) -> bool: """Return True if the asset is relative to another asset. Args: @@ -569,7 +567,7 @@ def move(self, new_asset: Asset, overwrite: bool = False) -> Asset: return new_asset - def delete(self, recursive: bool = False, dry_run: Optional[bool] = None) -> list: + def delete(self, recursive: bool = False, dry_run: bool | None = None) -> list: """Remove the asset. This method will delete an asset (any type) asset and all its potential children. by default it is not recursive and will raise an error if the container is not empty. @@ -639,7 +637,7 @@ def unlink(self) -> list: self.exists(raised=True) return self.delete() - def rmdir(self, recursive: bool = False, dry_run: Optional[bool] = None) -> list: + def rmdir(self, recursive: bool = False, dry_run: bool | None = None) -> list: """``delete`` alias for containers.""" if not (self.is_project() or self.is_folder() or self.is_image_collection()): raise ValueError(f"Asset {self.as_posix()} is not a container, use unlink instead.") diff --git a/geetools/ComputedObject.py b/geetools/ComputedObject.py index d9534dd..abe34b4 100644 --- a/geetools/ComputedObject.py +++ b/geetools/ComputedObject.py @@ -2,18 +2,17 @@ from __future__ import annotations import json +import os from pathlib import Path -from typing import Type import ee from .accessors import _register_extention -from .types import pathlike # -- types management ---------------------------------------------------------- @_register_extention(ee.ComputedObject) -def isInstance(self, klass: Type) -> ee.Number: +def isInstance(self, klass: type) -> ee.Number: """Return 1 if the element is the passed type or 0 if not. Parameters: @@ -37,7 +36,7 @@ def isInstance(self, klass: Type) -> ee.Number: # -- .gee files ---------------------------------------------------------------- @_register_extention(ee.ComputedObject) # type: ignore -def save(self, path: pathlike) -> Path: +def save(self, path: os.PathLike) -> Path: """Save a ``ComputedObject`` to a .gee file. The file contains the JSON representation of the object. it still need to be computed via ``getInfo()`` to be used. @@ -70,7 +69,7 @@ def save(self, path: pathlike) -> Path: @staticmethod # type: ignore @_register_extention(ee.ComputedObject) # type: ignore -def open(path: pathlike) -> ee.ComputedObject: +def open(path: os.PathLike) -> ee.ComputedObject: """Open a .gee file as a ComputedObject. Parameters: diff --git a/geetools/DateRange.py b/geetools/DateRange.py index 9f84932..9fd0557 100644 --- a/geetools/DateRange.py +++ b/geetools/DateRange.py @@ -4,7 +4,6 @@ import ee from .accessors import register_class_accessor -from .types import ee_int @register_class_accessor(ee.DateRange, "geetools") @@ -16,7 +15,7 @@ def __init__(self, obj: ee.DateRange): self._obj = obj # -- date range operations ------------------------------------------------- - def split(self, interval: ee_int, unit: str = "day") -> ee.List: + def split(self, interval: int | ee.Number, unit: str = "day") -> ee.List: """Convert a ``ee.DateRange`` to a list of ``ee.DateRange``. The DateRange will be split in multiple DateRanges of the specified interval and Unit. diff --git a/geetools/Dictionary.py b/geetools/Dictionary.py index 5bec980..16dcba7 100644 --- a/geetools/Dictionary.py +++ b/geetools/Dictionary.py @@ -4,7 +4,6 @@ import ee from .accessors import register_class_accessor -from .types import ee_list @register_class_accessor(ee.Dictionary, "geetools") @@ -16,7 +15,7 @@ def __init__(self, obj: ee.Dictionary): self._obj = obj # -- alternative constructor ----------------------------------------------- - def fromPairs(self, list: ee_list) -> ee.Dictionary: + def fromPairs(self, list: list | ee.List) -> ee.Dictionary: """Create a dictionary from a list of [[key, value], ...]] pairs. Parameters: @@ -61,7 +60,7 @@ def sort(self) -> ee.Dictionary: values = orderededKeys.map(lambda key: self._obj.get(key)) return ee.Dictionary.fromLists(orderededKeys, values) - def getMany(self, list: ee_list) -> ee.List: + def getMany(self, list: list | ee.List) -> ee.List: """Extract values from a list of keys. Parameters: diff --git a/geetools/Export.py b/geetools/Export.py index 573cb34..206daba 100644 --- a/geetools/Export.py +++ b/geetools/Export.py @@ -1,8 +1,6 @@ """Toolbox for the ``ee.Export`` class.""" from __future__ import annotations -from typing import List, Optional - import ee from .accessors import register_class_accessor @@ -30,10 +28,10 @@ def __init__(self): def toAsset( imagecollection: ee.ImageCollection, index_property: str = "system:id", - description: Optional[str] = None, - assetId: Optional[str] = None, + description: str = "", + assetId: str = "", **kwargs, - ) -> List[ee.batch.Task]: + ) -> list[ee.batch.Task]: """Creates a task to export an EE ImageCollection to an EE Asset. The method will create the imagecollection asset beforehand and launching the task will @@ -68,8 +66,8 @@ def toAsset( """ # sanity check on parameters # renaming them for mypy type reassignment and compactness - desc = description or ee.Asset(assetId).name - aid = ee.Asset(assetId) or ee.Asset("~").expanduser() / description + desc = description if description else ee.Asset(assetId).name + aid = ee.Asset(assetId) if assetId else ee.Asset("~").expanduser() / description # create the ImageCollection asset ee.data.createAsset({"type": "IMAGE_COLLECTION"}, aid.as_posix()) @@ -97,10 +95,10 @@ def toAsset( def toDrive( imagecollection: ee.ImageCollection, index_property: str = "system:id", - description: Optional[str] = None, - folder: Optional[str] = None, + description: str = "", + folder: str = "", **kwargs, - ) -> List[ee.batch.Task]: + ) -> list[ee.batch.Task]: """Creates a list of tasks to export an EE ImageCollection to Google Drive. The method will create a folder in Google Drive with the description value and populate @@ -134,8 +132,8 @@ def toDrive( """ # sanity check on parameters # renaming them for mypy type reassignment and compactness - desc = description or folder - fid = folder or description + desc = description if description else folder + fid = folder if folder else description # loop over the collection and export each image nb_images = imagecollection.size().getInfo() @@ -161,10 +159,10 @@ def toDrive( def toCloudStorage( imagecollection: ee.ImageCollection, index_property: str = "system:id", - description: Optional[str] = None, - folder: Optional[str] = None, + description: str = "", + folder: str = "", **kwargs, - ) -> List[ee.batch.Task]: + ) -> list[ee.batch.Task]: """Creates a list of tasks to export an EE ImageCollection to Google cloud. The method will create a folder in Google cloud bucket with the description value and populate @@ -198,8 +196,8 @@ def toCloudStorage( """ # sanity check on parameters # renaming them for mypy type reassignment and compactness - desc = description or folder - fid = folder or description + desc = description if description else folder + fid = folder if folder else description # loop over the collection and export each image nb_images = imagecollection.size().getInfo() diff --git a/geetools/Feature.py b/geetools/Feature.py index b7be7e8..54ada23 100644 --- a/geetools/Feature.py +++ b/geetools/Feature.py @@ -4,7 +4,6 @@ import ee from .accessors import register_class_accessor -from .types import ee_list @register_class_accessor(ee.Feature, "geetools") @@ -38,7 +37,7 @@ def toFeatureCollection(self) -> ee.FeatureCollection: fc = geoms.map(lambda g: self._obj.setGeometry(g)) return ee.FeatureCollection(fc) - def removeProperties(self, properties: ee_list) -> ee.Feature: + def removeProperties(self, properties: list | ee.List) -> ee.Feature: """Remove properties from a feature. Args: diff --git a/geetools/FeatureCollection.py b/geetools/FeatureCollection.py index cd18507..adcd5b7 100644 --- a/geetools/FeatureCollection.py +++ b/geetools/FeatureCollection.py @@ -1,8 +1,6 @@ """Toolbox for the `ee.FeatureCollection` class.""" from __future__ import annotations -from typing import Optional, Union - import ee import geopandas as gpd import numpy as np @@ -11,7 +9,6 @@ from matplotlib.colors import to_rgba from .accessors import register_class_accessor -from .types import ee_int, ee_list, ee_str @register_class_accessor(ee.FeatureCollection, "geetools") @@ -24,8 +21,8 @@ def __init__(self, obj: ee.FeatureCollection): def toImage( self, - color: Union[ee_str, ee_int] = 0, - width: Union[ee_str, ee_int] = "", + color: str | ee.String | int | ee.Number = 0, + width: str | ee.String | int | ee.Number = "", ) -> ee.Image: """Paint the current FeatureCollection to an Image. @@ -39,7 +36,9 @@ def toImage( width == "" or params.update(width=width) return ee.Image().paint(self._obj, **params) - def addId(self, name: ee_str = "id", start: ee_int = 1) -> ee.FeatureCollection: + def addId( + self, name: str | ee.String = "id", start: int | ee.Number = 1 + ) -> ee.FeatureCollection: """Add a unique numeric identifier, starting from parameter ``start``. Returns: @@ -128,7 +127,10 @@ def removeNonPoly(feat): return self._obj.map(removeNonPoly) def byProperties( - self, featureId: ee_str = "system:index", properties: ee_list = [], labels: list = [] + self, + featureId: str | ee.String = "system:index", + properties: list | ee.List = [], + labels: list = [], ) -> ee.Dictionary: """Get a dictionary with all feature values for each properties. @@ -184,7 +186,10 @@ def byProperties( return ee.Dictionary.fromLists(labels, values) def byFeatures( - self, featureId: ee_str = "system:index", properties: ee_list = [], labels: list = [] + self, + featureId: str | ee.String = "system:index", + properties: list | ee.List = [], + labels: list = [], ) -> ee.Dictionary: """Get a dictionary with all property values for each feature. @@ -248,7 +253,7 @@ def plot_by_features( properties: list = [], labels: list = [], colors: list = [], - ax: Optional[Axes] = None, + ax: Axes | None = None, **kwargs, ) -> Axes: """Plot the values of a ``ee.FeatureCollection`` by feature. @@ -302,10 +307,10 @@ def plot_by_properties( self, type: str = "bar", featureId: str = "system:index", - properties: ee_list = [], + properties: list | ee.List = [], labels: list = [], colors: list = [], - ax: Optional[Axes] = None, + ax: Axes | None = None, **kwargs, ) -> Axes: """Plot the values of a FeatureCollection by property. @@ -355,7 +360,12 @@ def plot_by_properties( ) def plot_hist( - self, property: ee_str, label: str = "", ax: Optional[Axes] = None, color=None, **kwargs + self, + property: str | ee.String, + label: str = "", + ax: Axes | None = None, + color=None, + **kwargs, ) -> Axes: """Plot the histogram of a specific property. @@ -422,7 +432,7 @@ def _plot( data: dict, label_name: str, colors: list = [], - ax: Optional[Axes] = None, + ax: Axes | None = None, **kwargs, ) -> Axes: """Plotting mechanism used in all the plotting functions. diff --git a/geetools/Filter.py b/geetools/Filter.py index a04e237..7f73f44 100644 --- a/geetools/Filter.py +++ b/geetools/Filter.py @@ -1,8 +1,6 @@ """Extra method for the ``ee.Filter`` class.""" from __future__ import annotations -from typing import Any - import ee from .accessors import register_class_accessor @@ -17,7 +15,7 @@ def __init__(self, obj: ee.Filter): self._obj = obj # -- date filters ---------------------------------------------------------- - def dateRange(self, range: ee.DateRange) -> Any: + def dateRange(self, range: ee.DateRange) -> ee.Filter: """Filter by daterange. Parameters: diff --git a/geetools/Image.py b/geetools/Image.py index 8ab03c4..2391b36 100644 --- a/geetools/Image.py +++ b/geetools/Image.py @@ -1,8 +1,6 @@ """Toolbox for the ``ee.Image`` class.""" from __future__ import annotations -from typing import Optional - import ee import ee_extra import ee_extra.Algorithms.core @@ -15,15 +13,6 @@ from xee.ext import REQUEST_BYTE_LIMIT from .accessors import register_class_accessor -from .types import ( - ee_dict, - ee_geomlike, - ee_int, - ee_list, - ee_number, - ee_str, - number, -) @register_class_accessor(ee.Image, "geetools") @@ -35,7 +24,7 @@ def __init__(self, obj: ee.Image): self._obj = obj # -- band manipulation ----------------------------------------------------- - def addDate(self, format: ee_str = "") -> ee.Image: + def addDate(self, format: str | ee.String = "") -> ee.Image: """Add a band with the date of the image in the provided format. If no format is provided, the date is stored as a Timestamp in millisecond in a band "date". If format band is provided, the date is store in a int8 band with the date in the provided format. This format needs to be a string that can be converted to a number. @@ -72,7 +61,7 @@ def addDate(self, format: ee_str = "") -> ee.Image: return self._obj.addBands(dateBand) - def addSuffix(self, suffix: ee_str, bands: ee_list = []) -> ee.Image: + def addSuffix(self, suffix: str | ee.String, bands: list | ee.List = []) -> ee.Image: """Add a suffix to the image selected band. Add a suffix to the selected band. If no band is specified, the suffix is added to all bands. @@ -103,7 +92,7 @@ def addSuffix(self, suffix: ee_str, bands: ee_list = []) -> ee.Image: ) return self._obj.rename(bandNames) - def addPrefix(self, prefix: ee_str, bands: ee_list = []): + def addPrefix(self, prefix: str | ee.String, bands: list | ee.List = []): """Add a prefix to the image selected band. Add a prefix to the selected band. If no band is specified, the prefix is added to all bands. @@ -134,7 +123,7 @@ def addPrefix(self, prefix: ee_str, bands: ee_list = []): ) return self._obj.rename(bandNames) - def rename(self, names: ee_dict) -> ee.Image: + def rename(self, names: dict | ee.Dictionary) -> ee.Image: """Rename the bands of the image based on a dictionary. It's the same function as the one from GEE but it takes a dictionary as input. @@ -164,7 +153,7 @@ def rename(self, names: ee_dict) -> ee.Image: ) return self._obj.rename(bands) - def remove(self, bands: ee_list) -> ee.Image: + def remove(self, bands: list | ee.List) -> ee.Image: """Remove bands from the image. Parameters: @@ -191,8 +180,8 @@ def remove(self, bands: ee_list) -> ee.Image: def doyToDate( self, year, - dateFormat: ee_str = "yyyyMMdd", - band: ee_str = "", + dateFormat: str | ee.String = "yyyyMMdd", + band: str | ee.String = "", ) -> ee.Image: """Convert the DOY band to a date band. @@ -233,7 +222,7 @@ def doyToDate( # -- the rest -------------------------------------------------------------- - def getValues(self, point: ee.Geometry.Point, scale: ee_int = 0) -> ee.Dictionary: + def getValues(self, point: ee.Geometry.Point, scale: int | ee.Number = 0) -> ee.Dictionary: """Get the value of the image at the given point using specified geometry. The result is presented as a dictionary where the keys are the bands name and the value the mean value of the band at the given point. @@ -281,7 +270,7 @@ def minScale(self) -> ee.Number: scales = bandNames.map(lambda b: self._obj.select(ee.String(b)).projection().nominalScale()) return ee.Number(scales.sort().get(0)) - def merge(self, images: ee_list) -> ee.Image: + def merge(self, images: list | ee.List) -> ee.Image: """Merge images into a single image. Parameters: @@ -309,9 +298,9 @@ def merge(self, images: ee_list) -> ee.Image: def toGrid( self, - size: ee_int = 1, - band: ee_str = "", - geometry: Optional[ee.Geometry] = None, + size: int | ee.Number = 1, + band: str | ee.String = "", + geometry: ee.Geometry | None = None, ) -> ee.FeatureCollection: """Convert an image to a grid of polygons. @@ -368,7 +357,7 @@ def toGrid( return ee.FeatureCollection(features) def clipOnCollection( - self, fc: ee.FeatureCollection, keepProperties: ee_int = 1 + self, fc: ee.FeatureCollection, keepProperties: int | ee.Number = 1 ) -> ee.ImageCollection: """Clip an image to a FeatureCollection. @@ -405,9 +394,9 @@ def fcClip(feat): def bufferMask( self, - radius: ee_int = 1.5, - kernelType: ee_str = "square", - units: ee_str = "pixels", + radius: int | ee.Number = 1.5, + kernelType: str | ee.String = "square", + units: str | ee.String = "pixels", ) -> ee.Image: """Make a buffer around every masked pixel of the Image. @@ -442,8 +431,8 @@ def bufferMask( @classmethod def full( self, - values: ee_list = [0], - names: ee_list = ["constant"], + values: list | ee.List = [0], + names: list | ee.List = ["constant"], ) -> ee.Image: """Create an image with the given values and names. @@ -478,10 +467,10 @@ def full( def fullLike( self, - fillValue: ee_number, - copyProperties: ee_int = 0, - keepMask: ee_int = 0, - keepFootprint: ee_int = 1, + fillValue: float | int | ee.Number, + copyProperties: int | ee.Number = 0, + keepMask: int | ee.Number = 0, + keepFootprint: int | ee.Number = 1, ) -> ee.Image: """Create an image with the same band names, projection and scale as the original image. @@ -540,8 +529,8 @@ def fullLike( def reduceBands( self, reducer: str, - bands: ee_list = [], - name: ee_str = "", + bands: list | ee.List = [], + name: str | ee.String = "", ) -> ee.Image: """Reduce the image using the selected reducer and adding the result as a band using the selected name. @@ -574,7 +563,7 @@ def reduceBands( reduceImage = self._obj.select(ee.List(bands)).reduce(reducer).rename([name]) return self._obj.addBands(reduceImage) - def negativeClip(self, geometry: ee_geomlike) -> ee.Image: + def negativeClip(self, geometry: ee.Geometry | ee.Feature | ee.FeatureCollection) -> ee.Image: """The opposite of the clip method. The inside of the geometry will be masked from the image. @@ -603,8 +592,8 @@ def negativeClip(self, geometry: ee_geomlike) -> ee.Image: def format( self, - string: ee_str, - dateFormat: ee_str = "yyyy-MM-dd", + string: str | ee.String, + dateFormat: str | ee.String = "yyyy-MM-dd", ) -> ee.String: """Create a string from using the given pattern and using the image properties. @@ -642,7 +631,7 @@ def replaceProperties(p, s): return patternList.iterate(replaceProperties, string) - def gauss(self, band: ee_str = "") -> ee.Image: + def gauss(self, band: str | ee.String = "") -> ee.Image: """Apply a gaussian filter to the image. We apply the following function to the image: "exp(((val-mean)**2)/(-2*(std**2)))" @@ -683,7 +672,7 @@ def gauss(self, band: ee_str = "") -> ee.Image: }, ).rename(band.cat("_gauss")) - def repeat(self, band, repeats: ee_int) -> ee.image: + def repeat(self, band, repeats: int | ee.Number) -> ee.image: """Repeat a band of the image. Args: @@ -749,7 +738,7 @@ def remove(band): return ee.ImageCollection(bands.map(remove)).toBands().rename(bands) - def interpolateBands(self, src: ee_list, to: ee_list) -> ee.Image: + def interpolateBands(self, src: list | ee.List, to: list | ee.List) -> ee.Image: """Interpolate bands from the "src" value range to the "to" value range. The Interpolation is performed linearly using the "extrapolate" option of the "interpolate" method. @@ -784,7 +773,7 @@ def interpolate(band): return ee.ImageCollection(bands.map(interpolate)).toBands().rename(bands) - def isletMask(self, offset: ee_number) -> ee.Image: + def isletMask(self, offset: float | int | ee.Number) -> ee.Image: """Compute the islet mask from an image. An islet is a set of non-masked pixels connected together by their edges of very small surface. The user define the offset of the island size and we compute the max number of pixels to improve computation speed. The inpt Image needs to be a single band binary image. @@ -838,28 +827,28 @@ def index_list(cls) -> dict: def spectralIndices( self, index: str = "NDVI", - G: number = 2.5, - C1: number = 6.0, - C2: number = 7.5, - L: number = 1.0, - cexp: number = 1.16, - nexp: number = 2.0, - alpha: number = 0.1, - slope: number = 1.0, - intercept: number = 0.0, - gamma: number = 1.0, - omega: number = 2.0, - beta: number = 0.05, - k: number = 0.0, - fdelta: number = 0.581, + G: float | int = 2.5, + C1: float | int = 6.0, + C2: float | int = 7.5, + L: float | int = 1.0, + cexp: float | int = 1.16, + nexp: float | int = 2.0, + alpha: float | int = 0.1, + slope: float | int = 1.0, + intercept: float | int = 0.0, + gamma: float | int = 1.0, + omega: float | int = 2.0, + beta: float | int = 0.05, + k: float | int = 0.0, + fdelta: float | int = 0.581, kernel: str = "RBF", sigma: str = "0.5 * (a + b)", - p: number = 2.0, - c: number = 1.0, - lambdaN: number = 858.5, - lambdaR: number = 645.0, - lambdaG: number = 555.0, - online: number = False, + p: float | int = 2.0, + c: float | int = 1.0, + lambdaN: float | int = 858.5, + lambdaR: float | int = 645.0, + lambdaG: float | int = 555.0, + online: float | int = False, ) -> ee.Image: """Computes one or more spectral indices (indices are added as bands) for an image from the Awesome List of Spectral Indices. @@ -1136,7 +1125,7 @@ def matchHistogram( self, target: ee.Image, bands: dict, - geometry: Optional[ee.Geometry] = None, + geometry: ee.Geometry | None = None, maxBuckets: int = 256, ) -> ee.Image: """Adjust the image's histogram to match a target image. @@ -1185,7 +1174,7 @@ def maskClouds( dark: float = 0.15, cloudDist: int = 1000, buffer: int = 250, - cdi: Optional[int] = None, + cdi: int | None = None, ): """Masks clouds and shadows in an image (valid just for Surface Reflectance products). @@ -1234,7 +1223,7 @@ def maskClouds( cdi, ) - def removeProperties(self, properties: ee_list) -> ee.Image: + def removeProperties(self, properties: list | ee.List) -> ee.Image: """Remove a list of properties from an image. Args: @@ -1262,7 +1251,7 @@ def distanceToMask( mask: ee.Image, kernel: str = "euclidean", radius: int = 1000, - band_name: ee_str = "distance_to_mask", + band_name: str | ee.String = "distance_to_mask", ) -> ee.Image: """Compute the distance from each pixel to the nearest non-masked pixel. diff --git a/geetools/ImageCollection.py b/geetools/ImageCollection.py index b2e9015..47c652d 100644 --- a/geetools/ImageCollection.py +++ b/geetools/ImageCollection.py @@ -2,7 +2,6 @@ from __future__ import annotations import uuid -from typing import Any, Optional, Tuple, Union import ee import ee_extra @@ -13,7 +12,6 @@ from xee.ext import REQUEST_BYTE_LIMIT from geetools.accessors import register_class_accessor -from geetools.types import ee_list, ee_number, ee_str, number @register_class_accessor(ee.ImageCollection, "geetools") @@ -35,7 +33,7 @@ def maskClouds( dark: float = 0.15, cloudDist: int = 1000, buffer: int = 250, - cdi: Optional[int] = None, + cdi: int | None = None, ) -> ee.ImageCollection: """Masks clouds and shadows in each image of an ImageCollection (valid just for Surface Reflectance products). @@ -87,7 +85,7 @@ def maskClouds( ) def closest( - self, date: Union[ee.Date, str], tolerance: int = 1, unit: str = "month" + self, date: ee.Date | str, tolerance: int = 1, unit: str = "month" ) -> ee.ImageCollection: """Gets the closest image (or set of images if the collection intersects a region that requires multiple scenes) to the specified date. @@ -113,28 +111,28 @@ def closest( def spectralIndices( self, index: str = "NDVI", - G: number = 2.5, - C1: number = 6.0, - C2: number = 7.5, - L: number = 1.0, - cexp: number = 1.16, - nexp: number = 2.0, - alpha: number = 0.1, - slope: number = 1.0, - intercept: number = 0.0, - gamma: number = 1.0, - omega: number = 2.0, - beta: number = 0.05, - k: number = 0.0, - fdelta: number = 0.581, + G: float | int = 2.5, + C1: float | int = 6.0, + C2: float | int = 7.5, + L: float | int = 1.0, + cexp: float | int = 1.16, + nexp: float | int = 2.0, + alpha: float | int = 0.1, + slope: float | int = 1.0, + intercept: float | int = 0.0, + gamma: float | int = 1.0, + omega: float | int = 2.0, + beta: float | int = 0.05, + k: float | int = 0.0, + fdelta: float | int = 0.581, kernel: str = "RBF", sigma: str = "0.5 * (a + b)", - p: number = 2.0, - c: number = 1.0, - lambdaN: number = 858.5, - lambdaR: number = 645.0, - lambdaG: number = 555.0, - online: number = False, + p: float | int = 2.0, + c: float | int = 1.0, + lambdaN: float | int = 858.5, + lambdaR: float | int = 645.0, + lambdaG: float | int = 555.0, + online: bool = False, ) -> ee.ImageCollection: """Computes one or more spectral indices (indices are added as bands) for an image from the Awesome List of Spectral Indices. @@ -544,7 +542,7 @@ def computeIntegral(image, integral): return ee.Image(self._obj.iterate(computeIntegral, s)) def outliers( - self, bands: ee_list = [], sigma: ee_number = 2, drop: bool = False + self, bands: list | ee.List = [], sigma: float | int | ee.Number = 2, drop: bool = False ) -> ee.ImageCollection: """Compute the outlier for each pixel in the specified bands. @@ -622,22 +620,22 @@ def maskOutliers(i): def to_xarray( self, - drop_variables: Optional[Tuple[str, ...]] = None, - io_chunks: Optional[Any] = None, + drop_variables: tuple[str, ...] | None = None, + io_chunks: object = None, n_images: int = -1, mask_and_scale: bool = True, decode_times: bool = True, - decode_timedelta: Optional[bool] = None, - use_cftime: Optional[bool] = None, + decode_timedelta: bool | None = None, + use_cftime: bool | None = None, concat_characters: bool = True, decode_coords: bool = True, - crs: Optional[str] = None, - scale: Union[float, int, None] = None, - projection: Optional[ee.Projection] = None, - geometry: Optional[ee.Geometry] = None, - primary_dim_name: Optional[str] = None, - primary_dim_property: Optional[str] = None, - ee_mask_value: Optional[float] = None, + crs: str | None = None, + scale: float | int | None = None, + projection: ee.Projection | None = None, + geometry: ee.Geometry | None = None, + primary_dim_name: str | None = None, + primary_dim_property: str | None = None, + ee_mask_value: float | None = None, request_byte_limit: int = REQUEST_BYTE_LIMIT, ) -> Dataset: """Open an Earth Engine ImageCollection as an ``xarray.Dataset``. @@ -686,7 +684,7 @@ def to_xarray( request_byte_limit=request_byte_limit, ) - def validPixel(self, band: ee_str = "") -> ee.Image: + def validPixel(self, band: str | ee.String = "") -> ee.Image: """Compute the number of valid pixels in the specified band. Compute the number of valid pixels in the specified band. 2 bands will be created: @@ -715,7 +713,7 @@ def validPixel(self, band: ee_str = "") -> ee.Image: validPct = validPixel.divide(self._obj.size()).multiply(100).rename("pct_valid") return validPixel.addBands(validPct) - def containsBandNames(self, bandNames: ee_list, filter: str) -> ee.ImageCollection: + def containsBandNames(self, bandNames: list | ee.List, filter: str) -> ee.ImageCollection: """Filter the ImageCollection by band names using the provided filter. Args: @@ -760,7 +758,7 @@ def containsBandNames(self, bandNames: ee_list, filter: str) -> ee.ImageCollecti return ee.ImageCollection(ic) - def containsAllBands(self, bandNames: ee_list) -> ee.ImageCollection: + def containsAllBands(self, bandNames: list | ee.List) -> ee.ImageCollection: """Filter the ImageCollection keeping only the images with all the provided bands. Args: @@ -787,7 +785,7 @@ def containsAllBands(self, bandNames: ee_list) -> ee.ImageCollection: """ return self.containsBandNames(bandNames, "ALL") - def containsAnyBands(self, bandNames: ee_list) -> ee.ImageCollection: + def containsAnyBands(self, bandNames: list | ee.List) -> ee.ImageCollection: """Filter the ImageCollection keeping only the images with any of the provided bands. Args: @@ -814,7 +812,7 @@ def containsAnyBands(self, bandNames: ee_list) -> ee.ImageCollection: """ return self.containsBandNames(bandNames, "ANY") - def aggregateArray(self, properties: Optional[ee_list] = None) -> ee.Dict: + def aggregateArray(self, properties: list | ee.List | None = None) -> ee.Dict: """Aggregate the ImageCollection selected properties into a dictionary. Args: diff --git a/geetools/Initialize.py b/geetools/Initialize.py index 6909be2..08a5043 100644 --- a/geetools/Initialize.py +++ b/geetools/Initialize.py @@ -3,14 +3,13 @@ import json from pathlib import Path -from typing import Optional import ee from google.oauth2.credentials import Credentials from .accessors import register_function_accessor -_project_id: Optional[str] = None +_project_id: str | None = None "The project Id used by the current user." diff --git a/geetools/Join.py b/geetools/Join.py index 35a0c3f..a3b0c80 100644 --- a/geetools/Join.py +++ b/geetools/Join.py @@ -4,7 +4,6 @@ import ee from .accessors import register_class_accessor -from .types import ee_str @register_class_accessor(ee.Join, "geetools") @@ -19,7 +18,7 @@ def __init__(self, obj: ee.join): def byProperty( primary: ee.Collection, secondary: ee.Collection, - field: ee_str, + field: str | ee.String, outer: bool = False, ) -> ee.Collection: """Join 2 collections by a given property field. diff --git a/geetools/List.py b/geetools/List.py index e2d4351..724385f 100644 --- a/geetools/List.py +++ b/geetools/List.py @@ -4,7 +4,6 @@ import ee from .accessors import register_class_accessor -from .types import ee_dict, ee_int, ee_list, ee_str @register_class_accessor(ee.List, "geetools") @@ -15,7 +14,7 @@ def __init__(self, obj: ee.List): """Initialize the List class.""" self._obj = obj - def product(self, other: ee_list) -> ee.List: + def product(self, other: list | ee.List) -> ee.List: """Compute the cartesian product of 2 list. Values will all be considered as string and will be joined with **no spaces**. @@ -45,7 +44,7 @@ def product(self, other: ee_list) -> ee.List: ) return product.flatten() - def complement(self, other: ee_list) -> ee.List: + def complement(self, other: list | ee.List) -> ee.List: """Compute the complement of the current list and the ``other`` list. The mthematical complement is the list of elements that are in the current list but not in the ``other`` list and vice-versa. @@ -71,7 +70,7 @@ def complement(self, other: ee_list) -> ee.List: l1, l2 = ee.List(self._obj), ee.List(other) return l1.removeAll(l2).cat(l2.removeAll(l1)) - def intersection(self, other: ee_list) -> ee.List: + def intersection(self, other: list | ee.List) -> ee.List: """Compute the intersection of the current list and the ``other`` list. The intersection is the list of elements that are in both lists. @@ -97,7 +96,7 @@ def intersection(self, other: ee_list) -> ee.List: l1, l2 = ee.List(self._obj), ee.List(other) return l1.removeAll(l1.removeAll(l2)) - def union(self, other: ee_list) -> ee.List: + def union(self, other: list | ee.List) -> ee.List: """Compute the union of the current list and the ``other`` list. This list will drop duplicated items. @@ -124,7 +123,7 @@ def union(self, other: ee_list) -> ee.List: return l1.cat(l2).distinct() # this method is simply a del but the name is protected in the GEE context - def delete(self, index: ee_int) -> ee.List: + def delete(self, index: int | ee.Number) -> ee.List: """Delete an element from a list. Parameters: @@ -149,9 +148,9 @@ def delete(self, index: ee_int) -> ee.List: @classmethod def sequence( cls, - ini: ee_int, - end: ee_int, - step: ee_int = 1, + ini: int | ee.Number, + end: int | ee.Number, + step: int | ee.Number = 1, ) -> ee.List: """Create a sequence from ini to end by step. @@ -179,7 +178,7 @@ def sequence( step = ee.Number(step).toInt().max(1) return ee.List.sequence(ini, end, step).add(end.toFloat()).distinct() - def replaceMany(self, replace: ee_dict) -> ee.List: + def replaceMany(self, replace: dict | ee.Dictionary) -> ee.List: """Replace many values in a list. Parameters: @@ -205,7 +204,7 @@ def replaceMany(self, replace: ee_dict) -> ee.List: list = keys.iterate(lambda k, p: ee.List(p).replace(k, replace.get(k)), self._obj) return ee.List(list) # to avoid returning a COmputedObject - def join(self, separator: ee_str = ", ") -> ee.string: + def join(self, separator: str | ee.String = ", ") -> ee.string: """Format a list to a string. Same as the join method but elements that cannot be stringified will be returned as the object type. diff --git a/geetools/Number.py b/geetools/Number.py index 8ba6adf..00a6fca 100644 --- a/geetools/Number.py +++ b/geetools/Number.py @@ -4,7 +4,6 @@ import ee from .accessors import register_class_accessor -from .types import ee_int @register_class_accessor(ee.Number, "geetools") @@ -15,7 +14,7 @@ def __init__(self, obj: ee.Number): """Initialize the Number class.""" self._obj = obj - def truncate(self, nbDecimals: ee_int = 2) -> ee.Number: + def truncate(self, nbDecimals: int | ee.Number = 2) -> ee.Number: """Truncate a number to a given number of decimals. Parameters: diff --git a/geetools/String.py b/geetools/String.py index 76c3933..d156c5a 100644 --- a/geetools/String.py +++ b/geetools/String.py @@ -4,7 +4,6 @@ import ee from .accessors import register_class_accessor -from .types import ee_dict, ee_str @register_class_accessor(ee.String, "geetools") @@ -15,7 +14,7 @@ def __init__(self, obj: ee.String): """Initialize the String class.""" self._obj = obj - def eq(self, other: ee_str) -> ee.Number: + def eq(self, other: str | ee.String) -> ee.Number: """Compare two strings and return a ``ee.Number``. Parameters: @@ -36,7 +35,7 @@ def eq(self, other: ee_str) -> ee.Number: """ return self._obj.compareTo(ee.String(other)).Not() - def format(self, template: ee_dict) -> ee.String: + def format(self, template: dict | ee.Dictionary) -> ee.String: """Format a string with a dictionary. Replace the keys in the string using the values provided in the dictionary. Follow the same pattern: value format as Python string.format method. diff --git a/geetools/accessors.py b/geetools/accessors.py index 66ef1b0..de2b459 100644 --- a/geetools/accessors.py +++ b/geetools/accessors.py @@ -1,9 +1,10 @@ """Generic accessor to add extra function to the base GEE API classes.""" +from __future__ import annotations -from typing import Any, Callable, Type +from typing import Callable -def register_class_accessor(klass: Type, name: str) -> Callable: +def register_class_accessor(klass: type, name: str) -> Callable: """Create an accessor through the provided namespace to a given class. Parameters: @@ -14,12 +15,12 @@ def register_class_accessor(klass: Type, name: str) -> Callable: The accessor function to to the class. """ - def decorator(accessor: Any) -> Any: + def decorator(accessor: Callable) -> object: class ClassAccessor: - def __init__(self, name: str, accessor: Any): + def __init__(self, name: str, accessor: Callable): self.name, self.accessor = name, accessor - def __get__(self, obj: Any, *args) -> Any: + def __get__(self, obj: object, *args) -> object: return self.accessor(obj) # check if the accessor already exists for this class @@ -34,7 +35,7 @@ def __get__(self, obj: Any, *args) -> Any: return decorator -def register_function_accessor(func: Type, name: str) -> Callable: +def register_function_accessor(func: type, name: str) -> Callable: """Add a Accessor class to function through the provided namespace. Parameters: @@ -45,7 +46,7 @@ def register_function_accessor(func: Type, name: str) -> Callable: The accessor function to to the function. """ - def decorator(accessor: Any) -> Any: + def decorator(accessor: Callable) -> object: # check if the accessor already exists for this class if hasattr(func, name): @@ -61,6 +62,6 @@ def decorator(accessor: Any) -> Any: # this private method should not be exposed to end user as it perform 0 checks it can overwrite # existing methods/class/member. Only used in the lib for the Computed object as the method need # to be shared by every other child of the class. -def _register_extention(obj: Any) -> Callable: +def _register_extention(obj: object) -> Callable: """Add the function to any object.""" return lambda f: (setattr(obj, f.__name__, f) or f) # type: ignore diff --git a/geetools/types.py b/geetools/types.py deleted file mode 100644 index e59799e..0000000 --- a/geetools/types.py +++ /dev/null @@ -1,19 +0,0 @@ -"""A set of custom mixin types to use in the package when dealing with Python/GEE functions.""" -from __future__ import annotations - -import os -from pathlib import Path -from typing import Union - -import ee - -ee_str = Union[str, ee.String] -ee_int = Union[int, ee.Number] -ee_float = Union[float, ee.Number] -ee_number = Union[float, int, ee.Number] -ee_list = Union[list, ee.List] -ee_dict = Union[dict, ee.Dictionary] -ee_geomlike = Union[ee.Geometry, ee.Feature, ee.FeatureCollection] - -pathlike = Union[os.PathLike, Path] -number = Union[float, int] diff --git a/tests/test_ImageCollection.py b/tests/test_ImageCollection.py index 1941e52..65eee8f 100644 --- a/tests/test_ImageCollection.py +++ b/tests/test_ImageCollection.py @@ -1,5 +1,5 @@ """Test the ImageCollection class.""" -from typing import Optional +from __future__ import annotations import ee import numpy as np @@ -10,7 +10,7 @@ def reduce( - collection: ee.ImageCollection, geometry: Optional[ee.Geometry] = None, reducer: str = "first" + collection: ee.ImageCollection, geometry: ee.Geometry | None = None, reducer: str = "first" ) -> ee.Dictionary: """Compute the mean reduction on the first image of the imageCollection.""" image = getattr(collection, reducer)()