Skip to content

Commit

Permalink
Reduce number of tasks (#701)
Browse files Browse the repository at this point in the history
* deprecate deepcopytask

* deprecate some interpolation tasks

* deprecate superpixel tasks
  • Loading branch information
zigaLuksic authored Jul 4, 2023
1 parent c7d05df commit 7024aed
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 41 deletions.
9 changes: 7 additions & 2 deletions core/eolearn/core/core_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
from fs.base import FS

from sentinelhub import SHConfig
from sentinelhub.exceptions import deprecated_class

from .constants import FeatureType, OverwritePermission
from .eodata import EOPatch
from .eodata_merge import merge_eopatches
from .eotask import EOTask
from .exceptions import EODeprecationWarning
from .types import EllipsisType, FeatureSpec, FeaturesSpecification, SingleFeatureSpec
from .utils.fs import get_filesystem, pickle_fs, unpickle_fs

Expand All @@ -33,16 +35,19 @@ class CopyTask(EOTask):
It copies feature type dictionaries but not the data itself.
"""

def __init__(self, features: FeaturesSpecification = ...):
def __init__(self, features: FeaturesSpecification = ..., *, deep: bool = False):
"""
:param features: A collection of features or feature types that will be copied into a new EOPatch.
:param deep: Whether the copy should be a deep or shallow copy.
"""
self.features = features
self.deep = deep

def execute(self, eopatch: EOPatch) -> EOPatch:
return eopatch.copy(features=self.features)
return eopatch.copy(features=self.features, deep=self.deep)


@deprecated_class(EODeprecationWarning, "Use `CopyTask` with the configuration `deep=True`.")
class DeepCopyTask(CopyTask):
"""Makes a deep copy of the given EOPatch."""

Expand Down
15 changes: 7 additions & 8 deletions core/eolearn/tests/test_core_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
AddFeatureTask,
CopyTask,
CreateEOPatchTask,
DeepCopyTask,
DuplicateFeatureTask,
EOPatch,
EOTask,
Expand Down Expand Up @@ -72,13 +71,13 @@ def eopatch_to_explode_fixture() -> EOPatch:
return generate_eopatch((FeatureType.DATA, "bands"), config=PatchGeneratorConfig(depth_range=(8, 9)))


@pytest.mark.parametrize("task", [DeepCopyTask, CopyTask])
def test_copy(task: type[CopyTask], patch: EOPatch) -> None:
patch_copy = task().execute(patch)
@pytest.mark.parametrize("deep", [True, False])
def test_copy(patch: EOPatch, deep: bool) -> None:
patch_copy = CopyTask(deep=deep).execute(patch)
assert patch_copy == patch

patch_copy.data["bands"][0, 0, 0, 0] += 1
assert (patch_copy != patch) if task == DeepCopyTask else (patch_copy == patch)
assert (patch_copy != patch) if deep else (patch_copy == patch)

patch_copy.data["new"] = np.ones_like(patch_copy.data["bands"])
assert "new" not in patch.data
Expand All @@ -91,9 +90,9 @@ def test_copy(task: type[CopyTask], patch: EOPatch) -> None:
[(FeatureType.TIMESTAMPS, None), (FeatureType.SCALAR, "values")],
],
)
@pytest.mark.parametrize("task", [DeepCopyTask, CopyTask])
def test_partial_copy(features: list[FeatureSpec], task: type[CopyTask], patch: EOPatch) -> None:
patch_copy = task(features=features)(patch)
@pytest.mark.parametrize("deep", [True, False])
def test_partial_copy(features: list[FeatureSpec], deep: bool, patch: EOPatch) -> None:
patch_copy = CopyTask(features=features, deep=deep)(patch)

assert set(patch_copy.get_features()) == {(FeatureType.BBOX, None), *features}
for feature in features:
Expand Down
38 changes: 37 additions & 1 deletion features/eolearn/features/interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
import scipy.interpolate
from sklearn.gaussian_process import GaussianProcessRegressor

from sentinelhub.exceptions import deprecated_class

from eolearn.core import EOPatch, EOTask, FeatureType
from eolearn.core.exceptions import EOUserWarning
from eolearn.core.exceptions import EODeprecationWarning, EOUserWarning
from eolearn.core.types import FeaturesSpecification, SingleFeatureSpec
from eolearn.core.utils.parsing import parse_renamed_feature

Expand Down Expand Up @@ -92,6 +94,12 @@ class InterpolationTask(EOTask):
In the process the interpolated feature is overwritten and so are the timestamps. After the execution of the task
the feature will contain interpolated and resampled values and corresponding new timestamps.
Examples of `interpolation_object:
- `scipy.interpolate.interp1d`, supply the kind as a kwarg, e.g. `kind="cubic"`
- `scipy.interpolate.UnivariateSpline`
- `scipy.interpolate.make_interp_spline`
- `scipy.interpolate.Akima1DInterpolator`
:param feature: A feature to be interpolated with optional new feature name
:param interpolation_object: Interpolation class which is initialized with `interpolation_parameters`
:param resample_range: If None the data will be only interpolated over existing timestamps and NaN values will be
Expand Down Expand Up @@ -434,13 +442,20 @@ def interpolate_data(self, data: np.ndarray, times: np.ndarray, resampled_times:
return interpolation_function(data, times, resampled_times)


@deprecated_class(
EODeprecationWarning,
"Use `InterpolationTask` with `interpolation_object=scipy.interpolate.interp1d` and `kind='cubic'`",
)
class CubicInterpolationTask(InterpolationTask):
"""Implements `eolearn.features.InterpolationTask` by using `scipy.interpolate.interp1d(kind='cubic')`"""

def __init__(self, feature: SingleFeatureSpec, **kwargs: Any):
super().__init__(feature, scipy.interpolate.interp1d, kind="cubic", **kwargs)


@deprecated_class(
EODeprecationWarning, "Use `InterpolationTask` with `interpolation_object=scipy.interpolate.UnivariateSpline`"
)
class SplineInterpolationTask(InterpolationTask):
"""Implements `eolearn.features.InterpolationTask` by using `scipy.interpolate.UnivariateSpline`"""

Expand All @@ -450,13 +465,19 @@ def __init__(
super().__init__(feature, scipy.interpolate.UnivariateSpline, k=spline_degree, s=smoothing_factor, **kwargs)


@deprecated_class(
EODeprecationWarning, "Use `InterpolationTask` with `interpolation_object=scipy.interpolate.make_interp_spline`"
)
class BSplineInterpolationTask(InterpolationTask):
"""Implements `eolearn.features.InterpolationTask` by using `scipy.interpolate.BSpline`"""

def __init__(self, feature: SingleFeatureSpec, *, spline_degree: int = 3, **kwargs: Any):
super().__init__(feature, scipy.interpolate.make_interp_spline, k=spline_degree, **kwargs)


@deprecated_class(
EODeprecationWarning, "Use `InterpolationTask` with `interpolation_object=scipy.interpolate.Akima1DInterpolator`"
)
class AkimaInterpolationTask(InterpolationTask):
"""Implements `eolearn.features.InterpolationTask` by using `scipy.interpolate.Akima1DInterpolator`"""

Expand Down Expand Up @@ -501,6 +522,9 @@ class ResamplingTask(InterpolationTask):
"""A subclass of InterpolationTask task that works only with data with no missing, masked or invalid values.
It always resamples timeseries to different timestamps.
For example, to perform interpolation with the 'nearest' resampling use the values
`interpolation_object=scipy.interpolate.interp1d` with `kind="nearest"`.
"""

def __init__(
Expand Down Expand Up @@ -552,20 +576,32 @@ def get_interpolation_function(self, times: np.ndarray, series: np.ndarray) -> C
return self.interpolation_object(times, series, axis=0, **self.interpolation_parameters)


@deprecated_class(
EODeprecationWarning,
"Use `ResamplingTask` with `interpolation_object=scipy.interpolate.interp1d` and `kind='nearest'`.",
)
class NearestResamplingTask(ResamplingTask):
"""Implements `eolearn.features.ResamplingTask` by using `scipy.interpolate.interp1d(kind='nearest')`"""

def __init__(self, feature: SingleFeatureSpec, resample_range: ResampleRangeType, **kwargs: Any):
super().__init__(feature, scipy.interpolate.interp1d, resample_range, kind="nearest", **kwargs)


@deprecated_class(
EODeprecationWarning,
"Use `ResamplingTask` with `interpolation_object=scipy.interpolate.interp1d` and `kind='linear'`.",
)
class LinearResamplingTask(ResamplingTask):
"""Implements `eolearn.features.ResamplingTask` by using `scipy.interpolate.interp1d(kind='linear')`"""

def __init__(self, feature: SingleFeatureSpec, resample_range: ResampleRangeType, **kwargs: Any):
super().__init__(feature, scipy.interpolate.interp1d, resample_range, kind="linear", **kwargs)


@deprecated_class(
EODeprecationWarning,
"Use `ResamplingTask` with `interpolation_object=scipy.interpolate.interp1d` and `kind='cubic'`.",
)
class CubicResamplingTask(ResamplingTask):
"""Implements `eolearn.features.ResamplingTask` by using `scipy.interpolate.interp1d(kind='cubic')`"""

Expand Down
66 changes: 42 additions & 24 deletions features/eolearn/tests/test_interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,16 @@

import numpy as np
import pytest
import scipy.interpolate

from sentinelhub.testing_utils import assert_statistics_match

from eolearn.core import EOPatch, EOTask, FeatureType
from eolearn.features import (
AkimaInterpolationTask,
BSplineInterpolationTask,
CubicInterpolationTask,
CubicResamplingTask,
InterpolationTask,
KrigingInterpolationTask,
LinearInterpolationTask,
LinearResamplingTask,
NearestResamplingTask,
SplineInterpolationTask,
ResamplingTask,
)


Expand Down Expand Up @@ -106,8 +102,10 @@ def execute(self, eopatch):
),
InterpolationTestCase(
"cubic",
CubicInterpolationTask(
InterpolationTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.interp1d,
kind="cubic",
result_interval=(0.0, 1.0),
mask_feature=(FeatureType.MASK, "IS_VALID"),
resample_range=("2015-01-01", "2018-01-01", 16),
Expand All @@ -119,37 +117,40 @@ def execute(self, eopatch):
),
InterpolationTestCase(
"spline",
SplineInterpolationTask(
InterpolationTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.UnivariateSpline,
result_interval=(-0.3, 1.0),
mask_feature=(FeatureType.MASK, "IS_VALID"),
resample_range=("2016-01-01", "2018-01-01", 5),
spline_degree=3,
smoothing_factor=0,
k=3,
s=0,
unknown_value=0,
),
result_len=147,
expected_statistics=dict(exp_min=-0.1781458, exp_max=1.0, exp_mean=0.49738, exp_median=0.556853),
),
InterpolationTestCase(
"bspline",
BSplineInterpolationTask(
InterpolationTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.make_interp_spline,
unknown_value=-3,
mask_feature=(FeatureType.MASK, "IS_VALID"),
resample_range=("2017-01-01", "2017-02-01", 50),
spline_degree=5,
k=5,
),
result_len=1,
expected_statistics=dict(exp_min=-0.0162962, exp_max=0.62323, exp_mean=0.319117, exp_median=0.3258836),
),
InterpolationTestCase(
"bspline-p",
BSplineInterpolationTask(
InterpolationTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.make_interp_spline,
unknown_value=-3,
mask_feature=(FeatureType.MASK, "IS_VALID"),
spline_degree=5,
k=5,
resample_range=("2017-01-01", "2017-02-01", 50),
interpolate_pixel_wise=True,
),
Expand All @@ -158,8 +159,11 @@ def execute(self, eopatch):
),
InterpolationTestCase(
"akima",
AkimaInterpolationTask(
(FeatureType.DATA, "NDVI"), unknown_value=0, mask_feature=(FeatureType.MASK, "IS_VALID")
InterpolationTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.Akima1DInterpolator,
unknown_value=0,
mask_feature=(FeatureType.MASK, "IS_VALID"),
),
result_len=68,
expected_statistics=dict(exp_min=-0.091035, exp_max=0.8283603, exp_mean=0.51427454, exp_median=0.59095883),
Expand All @@ -174,26 +178,36 @@ def execute(self, eopatch):
),
InterpolationTestCase(
"nearest resample",
NearestResamplingTask(
(FeatureType.DATA, "NDVI"), result_interval=(0.0, 1.0), resample_range=("2016-01-01", "2018-01-01", 5)
ResamplingTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.interp1d,
kind="nearest",
result_interval=(0.0, 1.0),
resample_range=("2016-01-01", "2018-01-01", 5),
),
result_len=147,
expected_statistics=dict(exp_min=-0.2, exp_max=0.8283603, exp_mean=0.32318678, exp_median=0.2794411),
nan_replace=-0.2,
),
InterpolationTestCase(
"linear resample",
LinearResamplingTask(
(FeatureType.DATA, "NDVI"), result_interval=(0.0, 1.0), resample_range=("2016-01-01", "2018-01-01", 5)
ResamplingTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.interp1d,
kind="linear",
result_interval=(0.0, 1.0),
resample_range=("2016-01-01", "2018-01-01", 5),
),
result_len=147,
expected_statistics=dict(exp_min=-0.2, exp_max=0.82643485, exp_mean=0.32218185, exp_median=0.29093677),
nan_replace=-0.2,
),
InterpolationTestCase(
"cubic resample",
CubicResamplingTask(
ResamplingTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.interp1d,
kind="cubic",
result_interval=(-0.2, 1.0),
resample_range=("2015-01-01", "2018-01-01", 16),
unknown_value=5,
Expand Down Expand Up @@ -236,8 +250,10 @@ def execute(self, eopatch):
(
InterpolationTestCase(
"cubic_copy_success",
CubicInterpolationTask(
InterpolationTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.interp1d,
kind="cubic",
result_interval=(0.0, 1.0),
mask_feature=(FeatureType.MASK, "IS_VALID"),
resample_range=("2015-01-01", "2018-01-01", 16),
Expand All @@ -257,8 +273,10 @@ def execute(self, eopatch):
(
InterpolationTestCase(
"cubic_copy_fail",
CubicInterpolationTask(
InterpolationTask(
(FeatureType.DATA, "NDVI"),
interpolation_object=scipy.interpolate.interp1d,
kind="cubic",
result_interval=(0.0, 1.0),
mask_feature=(FeatureType.MASK, "IS_VALID"),
resample_range=("2015-01-01", "2018-01-01", 16),
Expand Down
16 changes: 15 additions & 1 deletion geometry/eolearn/geometry/superpixel.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import numpy as np
import skimage.segmentation

from sentinelhub.exceptions import deprecated_class

from eolearn.core import EOPatch, EOTask, FeatureType
from eolearn.core.exceptions import EORuntimeWarning
from eolearn.core.exceptions import EODeprecationWarning, EORuntimeWarning
from eolearn.core.types import SingleFeatureSpec

LOGGER = logging.getLogger(__name__)
Expand All @@ -27,6 +29,10 @@ class SuperpixelSegmentationTask(EOTask):
Given a raster feature it will segment data into super-pixels. Representation of super-pixels will be returned as
a mask timeless feature where all pixels with the same value belong to one super-pixel
Examples of `segmentation_object` values:
- `skimage.segmentation.felzenszwalb` (the defalt)
- `skimage.segmentation.slic`
"""

def __init__(
Expand Down Expand Up @@ -78,6 +84,10 @@ def execute(self, eopatch: EOPatch) -> EOPatch:
return eopatch


@deprecated_class(
EODeprecationWarning,
"Use `SuperpixelSegmentationTask` with `segmentation_object=skimage.segmentation.felzenszwalb`.",
)
class FelzenszwalbSegmentationTask(SuperpixelSegmentationTask):
"""Super-pixel segmentation which uses Felzenszwalb's method of segmentation
Expand All @@ -90,6 +100,10 @@ def __init__(self, feature: SingleFeatureSpec, superpixel_feature: SingleFeature
super().__init__(feature, superpixel_feature, segmentation_object=skimage.segmentation.felzenszwalb, **kwargs)


@deprecated_class(
EODeprecationWarning,
"Use `SuperpixelSegmentationTask` with `segmentation_object=skimage.segmentation.slic` and `start_label=0`.",
)
class SlicSegmentationTask(SuperpixelSegmentationTask):
"""Super-pixel segmentation which uses SLIC method of segmentation
Expand Down
Loading

0 comments on commit 7024aed

Please sign in to comment.