diff --git a/geetools/ee_authenticate.py b/geetools/ee_authenticate.py index f5d5ce83..350558e0 100644 --- a/geetools/ee_authenticate.py +++ b/geetools/ee_authenticate.py @@ -1,6 +1,7 @@ """Toolbox for the :py:func:`ee.Authenticate` function.""" from __future__ import annotations +import os from contextlib import suppress from pathlib import Path from shutil import move @@ -16,14 +17,14 @@ class AuthenticateAccessor: """Create an accessor for the :py:func:`ee.Authenticate` function.""" @staticmethod - def new_user(name: str = "", credential_pathname: str = "") -> None: + def new_user(name: str = "", credential_pathname: str | os.PathLike = ""): """Authenticate the user and save the credentials in a specific folder. - Equivalent to :py:func:`ee.Authenticate` but where the registered user will not be the default one (the one you get when running :py:func:`ee.Initialize`) + Equivalent to :py:func:`ee.Authenticate` but where the registered user will not be the default one (the one you get when running :py:func:`ee.Initialize`). Args: name: The name of the user. If not set, it will reauthenticate default. - credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path + credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path. Example: .. code-block:: python @@ -55,12 +56,12 @@ def new_user(name: str = "", credential_pathname: str = "") -> None: move(Path(dir) / default.name, default) @staticmethod - def delete_user(name: str = "", credential_pathname: str = "") -> None: + def delete_user(name: str = "", credential_pathname: str | os.PathLike = ""): """Delete a user credential file. Args: - name: The name of the user. If not set, it will delete the default user - credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path + name: The name of the user. If not set, it will delete the default user. + credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path. Example: .. code-block:: python @@ -80,16 +81,16 @@ def delete_user(name: str = "", credential_pathname: str = "") -> None: (credential_path / name).unlink() @staticmethod - def list_user(credential_pathname: str = "") -> list[str]: + def list_user(credential_pathname: str | os.PathLike = "") -> list[str]: """return all the available users in the set folder. - To reach "default" simply omit the ``name`` parameter in the User methods + To reach "default" simply omit the ``name`` parameter in the User methods. Args: - credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path + credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path. Returns: - A list of strings with the names of the users + A list of strings with the names of the users. Example: .. code-block:: python @@ -105,13 +106,13 @@ def list_user(credential_pathname: str = "") -> list[str]: return [f.name.replace("credentials", "") or "default" for f in files] @staticmethod - def rename_user(new: str, old: str = "", credential_pathname: str = "") -> None: + def rename_user(new: str, old: str = "", credential_pathname: str | os.PathLike = ""): """Rename a user without changing the credentials. Args: - new: The new name of the user - old: The name of the user to rename - credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path + new: The new name of the user. + old: The name of the user to rename. + credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path. Example: .. code-block:: python diff --git a/geetools/ee_feature.py b/geetools/ee_feature.py index 75694be8..696cf72e 100644 --- a/geetools/ee_feature.py +++ b/geetools/ee_feature.py @@ -18,7 +18,7 @@ def toFeatureCollection(self) -> ee.FeatureCollection: """Convert a :py:class:`ee.Feature` composed of a multiGeometry geometry into a :py:class:`ee.FeatureCollection`. Returns: - The :py:class:`ee.FeatureCollection` + The :py:class:`ee.FeatureCollection`. Example: .. jupyter-execute:: @@ -37,14 +37,14 @@ def toFeatureCollection(self) -> ee.FeatureCollection: fc = geoms.map(lambda g: self._obj.setGeometry(g)) return ee.FeatureCollection(fc) - def removeProperties(self, properties: list | ee.List) -> ee.Feature: + def removeProperties(self, properties: list[str] | ee.List) -> ee.Feature: """Remove properties from a feature. Args: - properties : List of properties to remove + properties : List of properties to remove. Returns: - The feature without the properties + The feature without the properties. Example: .. jupyter-execute:: diff --git a/geetools/ee_feature_collection.py b/geetools/ee_feature_collection.py index e67e4ba3..7c430156 100644 --- a/geetools/ee_feature_collection.py +++ b/geetools/ee_feature_collection.py @@ -93,7 +93,9 @@ def toImage( return ee.Image().paint(self._obj, **params) def toDictionary( - self, keyColumn: str | ee.String = "system:index", selectors: list | ee.List = [] + self, + keyColumn: str | ee.String = "system:index", + selectors: list[str] | ee.List | None = None, ) -> ee.Dictionary: """Convert to Dictionary. @@ -125,7 +127,9 @@ def toDictionary( print(json.dumps(countries.getInfo(), indent=2)) """ uniqueIds = self._obj.aggregate_array(keyColumn) - selectors = ee.List(selectors) if selectors else self._obj.first().propertyNames() + selectors = ( + ee.List(selectors) if selectors is not None else self._obj.first().propertyNames() + ) keyColumn = ee.String(keyColumn) features = self._obj.toList(self._obj.size()) @@ -270,8 +274,8 @@ def removeNonPoly(feat): def byProperties( self, featureId: str | ee.String = "system:index", - properties: list | ee.List = [], - labels: list = [], + properties: list[str] | ee.List | None = None, + labels: list[str] | ee.List | None = None, ) -> ee.Dictionary: """Get a dictionary with all feature values for each property. @@ -289,7 +293,7 @@ def byProperties( Args: featureId: The property used to label features. Defaults to ``"system:index"``. - properties: A list of properties to get the values from. + properties: A list of properties to get the values from. By default, all properties will be used. labels: A list of names to replace properties names. Default to the properties names. Returns: @@ -319,22 +323,24 @@ def byProperties( features = features.map(lambda i: ee.Algorithms.If(isString(i), i, ee.Number(i).format())) # retrieve properties for each feature - properties = ee.List(properties) if properties else self._obj.first().propertyNames() + properties = ( + ee.List(properties) if properties is not None else self._obj.first().propertyNames() + ) properties = properties.remove(featureId) values = properties.map( lambda p: ee.Dictionary.fromLists(features, self._obj.aggregate_array(p)) ) # get the label to use in the dictionary if requested - labels = ee.List(labels) if labels else properties + labels = ee.List(labels) if labels is not None else properties return ee.Dictionary.fromLists(labels, values) def byFeatures( self, featureId: str | ee.String = "system:index", - properties: list | ee.List = [], - labels: list = [], + properties: list[str] | ee.List | None = None, + labels: list[str] | ee.List | None = None, ) -> ee.Dictionary: """Get a dictionary with all property values for each feature. @@ -352,7 +358,7 @@ def byFeatures( Args: featureId: The property to use as the feature id. Defaults to ``"system:index"``. This property needs to be a string property. - properties: A list of properties to get the values from. + properties: A list of properties to get the values from. By default, all properties will be used. labels: A list of names to replace properties names. Default to the properties names. Returns: @@ -378,9 +384,9 @@ def byFeatures( """ # compute the properties and their labels - props = ee.List(properties) if properties else self._obj.first().propertyNames() + props = ee.List(properties) if properties is not None else self._obj.first().propertyNames() props = props.remove(featureId) - labels = ee.List(labels) if labels else props + labels = ee.List(labels) if labels is not None else props # create a function to get the properties of a feature # we need to map the featureCollection into a list as it's not possible to return something else than a @@ -401,9 +407,9 @@ def plot_by_features( self, type: str = "bar", featureId: str = "system:index", - properties: list = [], - labels: list = [], - colors: list = [], + properties: list[str] | None = None, + labels: list[str] | None = None, + colors: list[str] | None = None, ax: Axes | None = None, **kwargs, ) -> Axes: @@ -453,14 +459,18 @@ def plot_by_features( label.set_rotation(45) """ # Get the features and properties - props = ee.List(properties) if properties else self._obj.first().propertyNames().getInfo() + props = ( + ee.List(properties) + if properties is not None + else self._obj.first().propertyNames().getInfo() + ) props = props.remove(featureId) # get the data from server data = self.byProperties(featureId, props, labels).getInfo() # reorder the data according to the labels or properties set by the user - labels = labels if labels else props.getInfo() + labels = labels if labels is not None else props.getInfo() data = {k: data[k] for k in labels} return plot_data(type=type, data=data, label_name=featureId, colors=colors, ax=ax, **kwargs) @@ -469,9 +479,9 @@ def plot_by_properties( self, type: str = "bar", featureId: str = "system:index", - properties: list | ee.List = [], - labels: list = [], - colors: list = [], + properties: list[str] | ee.List | None = None, + labels: list[str] | None = None, + colors: list[str] | None = None, ax: Axes | None = None, **kwargs, ) -> Axes: @@ -516,14 +526,14 @@ def plot_by_properties( """ # Get the features and properties fc = self._obj - props = ee.List(properties) if properties else fc.first().propertyNames() + props = ee.List(properties) if properties is not None else fc.first().propertyNames() props = props.remove(featureId) # get the data from server data = self.byFeatures(featureId, props, labels).getInfo() # reorder the data according to the lapbes or properties set by the user - labels = labels if labels else props.getInfo() + labels = labels if labels is not None else props.getInfo() data = {f: {k: data[f][k] for k in labels} for f in data.keys()} return plot_data(type=type, data=data, label_name=featureId, colors=colors, ax=ax, **kwargs) @@ -533,7 +543,7 @@ def plot_hist( property: str | ee.String, label: str = "", ax: Axes | None = None, - color=None, + color: str | None = None, **kwargs, ) -> Axes: """Plot the histogram of a specific property. diff --git a/geetools/ee_image.py b/geetools/ee_image.py index 00d98a87..6f93fa39 100644 --- a/geetools/ee_image.py +++ b/geetools/ee_image.py @@ -6,6 +6,10 @@ import ee import ee_extra import ee_extra.Algorithms.core +import ee_extra.QA.clouds +import ee_extra.QA.pipelines +import ee_extra.Spectral.core +import ee_extra.STAC.core import geopandas as gpd import numpy as np import requests @@ -66,7 +70,9 @@ def addDate(self, format: str | ee.String = "") -> ee.Image: return self._obj.addBands(dateBand) - def addSuffix(self, suffix: str | ee.String, bands: list | ee.List = []) -> ee.Image: + def addSuffix( + self, suffix: str | ee.String, bands: list[str] | ee.List | None = None + ) -> 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. @@ -90,14 +96,16 @@ def addSuffix(self, suffix: str | ee.String, bands: list | ee.List = []) -> ee.I print(image.bandNames().getInfo()) """ suffix = ee.String(suffix) - bands = self._obj.bandNames() if bands == [] else ee.List(bands) + bands = ee.List(bands) if bands is not None else self._obj.bandNames() bandNames = bands.iterate( lambda b, n: ee.List(n).replace(b, ee.String(b).cat(suffix)), self._obj.bandNames(), ) return self._obj.rename(bandNames) - def addPrefix(self, prefix: str | ee.String, bands: list | ee.List = []) -> ee.Image: + def addPrefix( + self, prefix: str | ee.String, bands: list[str] | ee.List | None = None + ) -> ee.Image: """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. @@ -121,7 +129,7 @@ def addPrefix(self, prefix: str | ee.String, bands: list | ee.List = []) -> ee.I print(image.bandNames().getInfo()) """ prefix = ee.String(prefix) - bands = self._obj.bandNames() if bands == [] else ee.List(bands) + bands = ee.List(bands) if bands is not None else self._obj.bandNames() bandNames = bands.iterate( lambda b, n: ee.List(n).replace(b, prefix.cat(ee.String(b))), self._obj.bandNames(), @@ -158,7 +166,7 @@ def rename(self, names: dict | ee.Dictionary) -> ee.Image: ) return self._obj.rename(bands) - def remove(self, bands: list | ee.List) -> ee.Image: + def remove(self, bands: list[str] | ee.List) -> ee.Image: """Remove bands from the image. Parameters: @@ -275,7 +283,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: list | ee.List) -> ee.Image: + def merge(self, images: list[ee.Image] | ee.List) -> ee.Image: """Merge images into a single image. Parameters: @@ -438,8 +446,8 @@ def bufferMask( @classmethod def full( self, - values: list | ee.List = [0], - names: list | ee.List = ["constant"], + values: list[float | int] | ee.List | None = None, + names: list[str] | ee.List | None = None, ) -> ee.Image: """Create an image with the given values and names. @@ -460,7 +468,8 @@ def full( image = ee.Image.geetools.full([1, 2, 3], ['a', 'b', 'c']) print(image.bandNames().getInfo()) """ - values, names = ee.List(values), ee.List(names) + values = ee.List(values) if values is not None else ee.List([0]) + names = ee.List(names) if names is not None else ee.List(["constant"]) # resize value to the same length as names values = ee.List( @@ -536,7 +545,7 @@ def fullLike( def reduceBands( self, reducer: str | ee.Reducer, - bands: list | ee.List = [], + bands: list[str] | ee.List | None = None, name: str | ee.String = "", ) -> ee.Image: """Reduce the image using the selected reducer and adding the result as a band using the selected name. @@ -564,7 +573,8 @@ def reduceBands( if not isinstance(reducer, str): raise TypeError("reducer must be a Python string") - bands, name = ee.List(bands), ee.String(name) + bands = ee.List(bands) if bands is not None else ee.List([]) + name = ee.String(name) bands = ee.Algorithms.If(bands.size().eq(0), self._obj.bandNames(), bands) name = ee.Algorithms.If(name.equals(ee.String("")), reducer, name) red = getattr(ee.Reducer, reducer)() if isinstance(reducer, str) else reducer @@ -684,7 +694,7 @@ def gauss(self, band: str | ee.String = "") -> ee.Image: }, ).rename(band.cat("_gauss")) - def repeat(self, band, repeats: int | ee.Number) -> ee.Image: + def repeat(self, band: str | ee.String, repeats: int | ee.Number) -> ee.Image: """Repeat a band of the image. Args: @@ -750,7 +760,11 @@ def remove(band): return ee.ImageCollection(bands.map(remove)).toBands().rename(bands) - def interpolateBands(self, src: list | ee.List, to: list | ee.List) -> ee.Image: + def interpolateBands( + self, + src: list[float | int | ee.Number] | ee.List, + to: list[float | int | ee.Number] | 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 :py:meth:`ee.Image.interpolate` method. @@ -818,7 +832,8 @@ def isletMask(self, offset: float | int | ee.Number) -> ee.Image: return isletArea.lt(offset).rename("mask").selfMask() # -- ee-extra wrapper ------------------------------------------------------ - def index_list(cls) -> dict[str, dict]: + @staticmethod + def index_list() -> dict[str, dict]: """Return the list of indices implemented in this module. Returns: @@ -841,7 +856,7 @@ def index_list(cls) -> dict[str, dict]: def spectralIndices( self, - index: str = "NDVI", + index: str | list[str] = "NDVI", G: float | int = 2.5, C1: float | int = 6.0, C2: float | int = 7.5, @@ -1197,7 +1212,7 @@ def tasseledCap(self) -> ee.Image: def matchHistogram( self, target: ee.Image, - bands: dict, + bands: dict[str, str], geometry: ee.Geometry | None = None, maxBuckets: int = 256, ) -> ee.Image: @@ -1296,7 +1311,7 @@ def maskClouds( cdi, ) - def removeProperties(self, properties: list | ee.List) -> ee.Image: + def removeProperties(self, properties: list[str] | ee.List) -> ee.Image: """Remove a list of properties from an image. Args: @@ -1533,7 +1548,7 @@ def maskCover( def plot( self, - bands: list, + bands: list[str], region: ee.Geometry, ax: Axes | None = None, fc: ee.FeatureCollection = None, @@ -1624,8 +1639,8 @@ def plot( return ax - @classmethod - def fromList(cls, images: ee.List | list) -> ee.Image: + @staticmethod + def fromList(images: ee.List | list[ee.Image]) -> ee.Image: """Create a single image by passing a list of images. Warning: The bands cannot have repeated names, if so, it will throw an error (see examples). @@ -1667,9 +1682,9 @@ def byBands( self, regions: ee.FeatureCollection, reducer: str | ee.Reducer = "mean", - bands: list = [], + bands: list[str] | None = None, regionId: str = "system:index", - labels: list = [], + labels: list[str] | None = None, scale: int = 10000, crs: str | None = None, crsTransform: list | None = None, @@ -1724,10 +1739,10 @@ def byBands( features = features.map(lambda i: ee.Algorithms.If(isString(i), i, ee.Number(i).format())) # get the bands to be used in the reducer - eeBands = ee.List(bands) if len(bands) else self._obj.bandNames() + eeBands = ee.List(bands) if bands is not None else self._obj.bandNames() # retrieve the label to use for each bands if provided - eeLabels = ee.List(labels) if len(labels) else eeBands + eeLabels = ee.List(labels) if labels is not None else eeBands # by default for 1 band image, the reducers are renaming the output band. To ensure it keeps # the original band name we add setOutputs that is ignored for multi band images. @@ -1757,9 +1772,9 @@ def byRegions( self, regions: ee.FeatureCollection, reducer: str | ee.Reducer = "mean", - bands: list = [], + bands: list[str] | None = None, regionId: str = "system:index", - labels: list = [], + labels: list[str] | None = None, scale: int = 10000, crs: str | None = None, crsTransform: list | None = None, @@ -1815,10 +1830,10 @@ def byRegions( features = features.map(lambda i: ee.Algorithms.If(isString(i), i, ee.Number(i).format())) # get the bands to be used in the reducer - bands = ee.List(bands) if len(bands) else self._obj.bandNames() + bands = ee.List(bands) if bands is not None else self._obj.bandNames() # retrieve the label to use for each bands if provided - labels = ee.List(labels) if len(labels) else bands + labels = ee.List(labels) if labels is not None else bands # by default for 1 band image, the reducers are renaming the output band. To ensure it keeps # the original band name we add setOutputs that is ignored for multi band images. @@ -1851,10 +1866,10 @@ def plot_by_regions( type: str, regions: ee.FeatureCollection, reducer: str | ee.Reducer = "mean", - bands: list = [], + bands: list[str] | None = None, regionId: str = "system:index", - labels: list = [], - colors: list = [], + labels: list[str] | None = None, + colors: list[str] | None = None, ax: Axes | None = None, scale: int = 10000, crs: str | None = None, @@ -1927,8 +1942,8 @@ def plot_by_regions( features = features.getInfo() # extract the labels from the parameters - eeBands = ee.List(bands) if len(bands) else self._obj.bandNames() - labels = labels if len(labels) else eeBands.getInfo() + eeBands = ee.List(bands) if bands is not None else self._obj.bandNames() + labels = labels if labels is not None else eeBands.getInfo() # reorder the data according to the labels id set by the user data = {b: {f: data[b][f] for f in features} for b in labels} @@ -1942,10 +1957,10 @@ def plot_by_bands( type: str, regions: ee.FeatureCollection, reducer: str | ee.Reducer = "mean", - bands: list = [], + bands: list[str] | None = None, regionId: str = "system:index", - labels: list = [], - colors: list = [], + labels: list[str] | None = None, + colors: list[str] | None = None, ax: Axes | None = None, scale: int = 10000, crs: str | None = None, @@ -2018,8 +2033,8 @@ def plot_by_bands( features = features.getInfo() # extract the labels from the parameters - eeBands = ee.List(bands) if len(bands) else self._obj.bandNames() - labels = labels if len(labels) else eeBands.getInfo() + eeBands = ee.List(bands) if bands is not None else self._obj.bandNames() + labels = labels if labels is not None else eeBands.getInfo() # reorder the data according to the labels id set by the user data = {f: {b: data[f][b] for b in labels} for f in features} @@ -2032,9 +2047,9 @@ def plot_hist( self, bins: int = 30, region: ee.Geometry | None = None, - bands: list = [], - labels: list = [], - colors: list = [], + bands: list[str] | None = None, + labels: list[str] | None = None, + colors: list[str] | None = None, precision: int = 2, ax: Axes | None = None, scale: int = 10000, @@ -2084,12 +2099,13 @@ def plot_hist( normClim.geetools.plot_hist() """ # extract the bands from the image - eeBands = ee.List(bands) if len(bands) == 0 else self._obj.bandNames() - eeLabels = ee.List(labels).flatten() if len(labels) == 0 else eeBands - labels = eeLabels.getInfo() + eeBands = ee.List(bands) if bands is not None else self._obj.bandNames() + eeLabels = ee.List(labels).flatten() if labels is not None else eeBands + new_labels: list[str] = eeLabels.getInfo() + new_colors: list[str] = colors if colors is not None else plt.get_cmap("tab10").colors # retrieve the region from the parameters - region = region if region else self._obj.geometry() + region = region if region is not None else self._obj.geometry() # extract the data from the server image = self._obj.select(eeBands).rename(eeLabels).clip(region) @@ -2122,17 +2138,17 @@ def plot_hist( # every value is duplicated but the first one to create a scale like display. # the values are treated the same way we simply drop the last duplication to get the same size. p = 10**precision # multiplier use to truncate the float values - x = [int(d[0] * p) / p for d in raw_data[labels[0]] for _ in range(2)][1:] - data = {l: [int(d[1]) for d in raw_data[l] for _ in range(2)][:-1] for l in labels} + x = [int(d[0] * p) / p for d in raw_data[new_labels[0]] for _ in range(2)][1:] + data = {l: [int(d[1]) for d in raw_data[l] for _ in range(2)][:-1] for l in new_labels} # create the graph objcet if not provided if ax is None: fig, ax = plt.subplots() # display the histogram as a fill_between plot to respect GEE lib design - for i, label in enumerate(labels): - kwargs["facecolor"] = to_rgba(colors[i], 0.2) - kwargs["edgecolor"] = to_rgba(colors[i], 1) + for i, label in enumerate(new_labels): + kwargs["facecolor"] = to_rgba(new_colors[i], 0.2) + kwargs["edgecolor"] = to_rgba(new_colors[i], 1) ax.fill_between(x, data[label], label=label, **kwargs) # customize the layout of the axis diff --git a/geetools/ee_image_collection.py b/geetools/ee_image_collection.py index d09311be..8225654a 100644 --- a/geetools/ee_image_collection.py +++ b/geetools/ee_image_collection.py @@ -7,6 +7,12 @@ import ee import ee_extra +import ee_extra.Algorithms.core +import ee_extra.ImageCollection.core +import ee_extra.QA.clouds +import ee_extra.QA.pipelines +import ee_extra.Spectral.core +import ee_extra.STAC.core import requests import xarray from ee import apifunction @@ -127,7 +133,7 @@ def closest( def spectralIndices( self, - index: str = "NDVI", + index: str | list[str] = "NDVI", G: float | int = 2.5, C1: float | int = 6.0, C2: float | int = 7.5, @@ -611,7 +617,10 @@ def computeIntegral(image, integral): return ee.Image(self._obj.iterate(computeIntegral, s)) def outliers( - self, bands: list | ee.List = [], sigma: float | int | ee.Number = 2, drop: bool = False + self, + bands: list[str] | ee.List | None = None, + sigma: float | int | ee.Number = 2, + drop: bool = False, ) -> ee.ImageCollection: """Compute the outlier for each pixel in the specified bands. @@ -660,7 +669,7 @@ def outliers( """ # cast parameters and compute the outlier band names initBands = self._obj.first().bandNames() - statBands = ee.List(bands) if bands else initBands + statBands = ee.List(bands) if bands is not None else initBands outBands = statBands.map(lambda b: ee.String(b).cat("_outlier")) # compute the mean and std dev for each band @@ -784,12 +793,12 @@ def validPixel(self, band: str | ee.String = "") -> ee.Image: validPct = validPixel.divide(self._obj.size()).multiply(100).rename("pct_valid") return validPixel.addBands(validPct) - def containsBandNames(self, bandNames: list | ee.List, filter: str) -> ee.ImageCollection: + def containsBandNames(self, bandNames: list[str] | ee.List, filter: str) -> ee.ImageCollection: """Filter the :py:class:`ee.ImageCollection` by band names using the provided filter. Args: - bandNames: list of band names to filter - filter: type of filter to apply. To keep images that contains all the specified bands use ``"ALL"``. To get the images including at least one of the specified band use ``"ANY"``. + bandNames: List of band names to filter. + filter: Type of filter to apply. To keep images that contains all the specified bands use ``"ALL"``. To get the images including at least one of the specified band use ``"ANY"``. Returns: A filtered :py:class:`ee.ImageCollection` @@ -829,11 +838,11 @@ def containsBandNames(self, bandNames: list | ee.List, filter: str) -> ee.ImageC return ee.ImageCollection(ic) - def containsAllBands(self, bandNames: list | ee.List) -> ee.ImageCollection: + def containsAllBands(self, bandNames: list[str] | ee.List) -> ee.ImageCollection: """Filter the :py:class:`ee.ImageCollection` keeping only the images with all the provided bands. Args: - bandNames: list of band names to filter + bandNames: List of band names to filter. Returns: A filtered :py:class:`ee.ImageCollection` @@ -856,11 +865,11 @@ def containsAllBands(self, bandNames: list | ee.List) -> ee.ImageCollection: """ return self.containsBandNames(bandNames, "ALL") - def containsAnyBands(self, bandNames: list | ee.List) -> ee.ImageCollection: + def containsAnyBands(self, bandNames: list[str] | ee.List) -> ee.ImageCollection: """Filter the :py:class:`ee.ImageCollection` keeping only the images with any of the provided bands. Args: - bandNames: list of band names to filter + bandNames: List of band names to filter. Returns: A filtered :py:class:`ee.ImageCollection` @@ -883,11 +892,11 @@ def containsAnyBands(self, bandNames: list | ee.List) -> ee.ImageCollection: """ return self.containsBandNames(bandNames, "ANY") - def aggregateArray(self, properties: list | ee.List | None = None) -> ee.Dictionary: + def aggregateArray(self, properties: list[str] | ee.List | None = None) -> ee.Dictionary: """Aggregate the :py:class:`ee.ImageCollection` selected properties into a dictionary. Args: - properties: list of properties to aggregate. If None, all properties are aggregated. + properties: List of properties to aggregate. If None, all properties are aggregated. Returns: A dictionary with the properties as keys and the aggregated values as values. @@ -1145,8 +1154,8 @@ def datesByBands( region: ee.Geometry, reducer: str | ee.Reducer = "mean", dateProperty: str = "system:time_start", - bands: list = [], - labels: list = [], + bands: list[str] | None = None, + labels: list[str] | None = None, scale: int = 10000, crs: str | None = None, crsTransform: list | None = None, @@ -1205,8 +1214,8 @@ def datesByBands( print(reduced.getInfo()) """ # cast parameters - eeBands = ee.List(bands) if len(bands) else self._obj.first().bandNames() - eeLabels = ee.List(labels) if len(labels) else eeBands + eeBands = ee.List(bands) if bands is not None else self._obj.first().bandNames() + eeLabels = ee.List(labels) if labels is not None else eeBands # recast band names as labels in the source collection ic = self._obj.select(eeBands).map(lambda i: i.rename(eeLabels)) @@ -1327,8 +1336,8 @@ def doyByBands( spatialReducer: str | ee.Reducer = "mean", timeReducer: str | ee.Reducer = "mean", dateProperty: str = "system:time_start", - bands: list = [], - labels: list = [], + bands: list[str] | None = None, + labels: list[str] | None = None, scale: int = 10000, crs: str | None = None, crsTransform: list | None = None, @@ -1375,8 +1384,8 @@ def doyByBands( - :docstring:`ee.ImageCollection.geetools.plot_doy_by_years` """ # cast parameters - bands = ee.List(bands) if len(bands) else self._obj.first().bandNames() - labels = ee.List(labels) if len(labels) else bands + bands = ee.List(bands) if bands is not None else self._obj.first().bandNames() + labels = ee.List(labels) if labels is not None else bands # recast band names as labels in the source collection ic = self._obj.select(bands).map(lambda i: i.rename(labels)) @@ -1407,7 +1416,7 @@ def filter_doy(d: ee.Number) -> ee.ImageCollection: getattr(ee.Reducer, timeReducer)() if isinstance(timeReducer, str) else timeReducer ) - def timeReduce(c: ee.imageCollection) -> ee.image: + def timeReduce(c: ee.ImageCollection) -> ee.image: c = ee.ImageCollection(c) i = c.reduce(timeRed).rename(labels) i = i.set(size_metadata, c.get(size_metadata)) @@ -1513,7 +1522,7 @@ def filter_doy(d: ee.Number) -> ee.ImageCollection: getattr(ee.Reducer, timeReducer)() if isinstance(timeReducer, str) else timeReducer ) - def timeReduce(c: ee.imageCollection) -> ee.image: + def timeReduce(c: ee.ImageCollection) -> ee.image: c = ee.ImageCollection(c) i = c.reduce(timeRed).rename([band]) i = i.set(size_metadata, c.get(size_metadata)) @@ -1771,9 +1780,9 @@ def plot_dates_by_bands( region: ee.Geometry, reducer: str | ee.Reducer = "mean", dateProperty: str = "system:time_start", - bands: list = [], - labels: list = [], - colors: list = [], + bands: list[str] | None = None, + labels: list[str] | None = None, + colors: list[str] | None = None, ax: Axes | None = None, scale: int = 10000, crs: str | None = None, @@ -1863,7 +1872,7 @@ def plot_dates_by_regions( label: str = "system:index", reducer: str | ee.Reducer = "mean", dateProperty: str = "system:time_start", - colors: list = [], + colors: list[str] | None = None, ax: Axes | None = None, scale: int = 10000, crs: str | None = None, @@ -1949,9 +1958,9 @@ def plot_doy_by_bands( spatialReducer: str | ee.Reducer = "mean", timeReducer: str | ee.Reducer = "mean", dateProperty: str = "system:time_start", - bands: list = [], - labels: list = [], - colors: list = [], + bands: list[str] | None = None, + labels: list[str] | None = None, + colors: list[str] | None = None, ax: Axes | None = None, scale: int = 10000, crs: str | None = None, @@ -2043,7 +2052,7 @@ def plot_doy_by_regions( spatialReducer: str | ee.Reducer = "mean", timeReducer: str | ee.Reducer = "mean", dateProperty: str = "system:time_start", - colors: list = [], + colors: list[str] | None = None, ax: Axes | None = None, scale: int = 10000, crs: str | None = None, @@ -2133,7 +2142,7 @@ def plot_doy_by_seasons( seasonEnd: int | ee.Number, reducer: str | ee.Reducer = "mean", dateProperty: str = "system:time_start", - colors: list = [], + colors: list[str] | None = None, ax: Axes | None = None, scale: int = 10000, crs: str | None = None, @@ -2240,7 +2249,7 @@ def plot_doy_by_years( region: ee.Geometry, reducer: str | ee.Reducer = "mean", dateProperty: str = "system:time_start", - colors: list = [], + colors: list[str] | None = None, ax: Axes | None = None, scale: int = 10000, crs: str | None = None, diff --git a/geetools/ee_initialize.py b/geetools/ee_initialize.py index dbf3433d..a2793137 100644 --- a/geetools/ee_initialize.py +++ b/geetools/ee_initialize.py @@ -2,6 +2,7 @@ from __future__ import annotations import json +import os import tempfile from pathlib import Path @@ -20,7 +21,7 @@ class InitializeAccessor: """Toolbox for the ``ee.Initialize`` function.""" @staticmethod - def from_user(name: str = "", credential_pathname: str = "", project: str = "") -> None: + def from_user(name: str = "", credential_pathname: str | os.PathLike = "", project: str = ""): """Initialize Earthengine API using a specific user. Equivalent to the :py:func:`ee.Initialize` function but with a specific credential file stored in @@ -71,7 +72,7 @@ def from_user(name: str = "", credential_pathname: str = "", project: str = "") _project_id = project or tokens["project_id"] @staticmethod - def from_service_account(private_key: str) -> None: + def from_service_account(private_key: str): """Initialize Earthengine API using a service account. Equivalent to the :py:func:`ee.Initialize` function but with a specific service account json key. diff --git a/geetools/utils.py b/geetools/utils.py index a6c7646f..82d7bc2a 100644 --- a/geetools/utils.py +++ b/geetools/utils.py @@ -74,7 +74,7 @@ def plot_data( type: str, data: dict, label_name: str, - colors: list = [], + colors: list[str] | None = None, ax: Axes | None = None, **kwargs, ) -> Axes: