Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Bias1D/2D/ND classes in favor of an nd metadata attribute #517

Merged
merged 6 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions doc/source/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,17 +244,6 @@ To build and pass your coregistration pipeline to {func}`~xdem.DEM.coregister_3d
xdem.coreg.BiasCorr
```

**Classes for any 1-, 2- and N-D biases:**

```{eval-rst}
.. autosummary::
:toctree: gen_modules/

xdem.coreg.BiasCorr1D
xdem.coreg.BiasCorr2D
xdem.coreg.BiasCorrND
```

**Convenience classes for specific corrections:**

```{eval-rst}
Expand Down
13 changes: 0 additions & 13 deletions doc/source/biascorr.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,3 @@ terbias.fit(ref_dem, tba_dem, inlier_mask=inlier_mask)
# Apply the transformation
corrected_dem = terbias.apply(tba_dem)
```

## Generic 1-D, 2-D and N-D classes

All bias-corrections methods are inherited from generic classes that perform corrections in 1-, 2- or N-D. Having these
separate helps the user navigating the dimensionality of the functions, optimizer, binning or variables used.

{class}`xdem.coreg.BiasCorr1D`
{class}`xdem.coreg.BiasCorr2D`
{class}`xdem.coreg.BiasCorrND`

- **Performs:** Correct biases with any function and optimizer, or any binning, in 1-, 2- or N-D.
- **Supports weights** Yes.
- **Recommended for:** Anything.
20 changes: 10 additions & 10 deletions tests/test_coreg/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,8 @@ def test_pipeline_combinations__nobiasvar(self, coreg1: Coreg, coreg2: Coreg) ->
@pytest.mark.parametrize(
"coreg2",
[
coreg.BiasCorr1D(bias_var_names=["slope"], fit_or_bin="bin"),
coreg.BiasCorr2D(bias_var_names=["slope", "aspect"], fit_or_bin="bin"),
coreg.BiasCorr(bias_var_names=["slope"], fit_or_bin="bin"),
coreg.BiasCorr(bias_var_names=["slope", "aspect"], fit_or_bin="bin"),
],
) # type: ignore
def test_pipeline_combinations__biasvar(self, coreg1: Coreg, coreg2: Coreg) -> None:
Expand All @@ -588,24 +588,24 @@ def test_pipeline_combinations__biasvar(self, coreg1: Coreg, coreg2: Coreg) -> N
def test_pipeline__errors(self) -> None:
"""Test pipeline raises proper errors."""

pipeline = coreg.CoregPipeline([coreg.NuthKaab(), coreg.BiasCorr1D()])
pipeline = coreg.CoregPipeline([coreg.NuthKaab(), coreg.BiasCorr()])
with pytest.raises(
ValueError,
match=re.escape(
"No `bias_vars` passed to .fit() for bias correction step "
"<class 'xdem.coreg.biascorr.BiasCorr1D'> of the pipeline."
"<class 'xdem.coreg.biascorr.BiasCorr'> of the pipeline."
),
):
pipeline.fit(**self.fit_params)

pipeline2 = coreg.CoregPipeline([coreg.NuthKaab(), coreg.BiasCorr1D(), coreg.BiasCorr1D()])
pipeline2 = coreg.CoregPipeline([coreg.NuthKaab(), coreg.BiasCorr(), coreg.BiasCorr()])
with pytest.raises(
ValueError,
match=re.escape(
"No `bias_vars` passed to .fit() for bias correction step <class 'xdem.coreg.biascorr.BiasCorr1D'> "
"No `bias_vars` passed to .fit() for bias correction step <class 'xdem.coreg.biascorr.BiasCorr'> "
"of the pipeline. As you are using several bias correction steps requiring"
" `bias_vars`, don't forget to explicitly define their `bias_var_names` "
"during instantiation, e.g. BiasCorr1D(bias_var_names=['slope'])."
"during instantiation, e.g. BiasCorr(bias_var_names=['slope'])."
),
):
pipeline2.fit(**self.fit_params)
Expand All @@ -615,17 +615,17 @@ def test_pipeline__errors(self) -> None:
match=re.escape(
"When using several bias correction steps requiring `bias_vars` in a pipeline,"
"the `bias_var_names` need to be explicitly defined at each step's "
"instantiation, e.g. BiasCorr1D(bias_var_names=['slope'])."
"instantiation, e.g. BiasCorr(bias_var_names=['slope'])."
),
):
pipeline2.fit(**self.fit_params, bias_vars={"slope": xdem.terrain.slope(self.ref)})

pipeline3 = coreg.CoregPipeline([coreg.NuthKaab(), coreg.BiasCorr1D(bias_var_names=["slope"])])
pipeline3 = coreg.CoregPipeline([coreg.NuthKaab(), coreg.BiasCorr(bias_var_names=["slope"])])
with pytest.raises(
ValueError,
match=re.escape(
"Not all keys of `bias_vars` in .fit() match the `bias_var_names` defined during "
"instantiation of the bias correction step <class 'xdem.coreg.biascorr.BiasCorr1D'>: ['slope']."
"instantiation of the bias correction step <class 'xdem.coreg.biascorr.BiasCorr'>: ['slope']."
),
):
pipeline3.fit(**self.fit_params, bias_vars={"ncc": xdem.terrain.slope(self.ref)})
Expand Down
123 changes: 42 additions & 81 deletions tests/test_coreg/test_biascorr.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,48 @@ def test_biascorr__errors(self) -> None:
):
biascorr.BiasCorr(fit_or_bin="bin", bin_apply_method=1) # type: ignore

# When wrong number of parameters are passed

# Copy fit parameters
fit_args = self.fit_args_rst_rst.copy()
with pytest.raises(
ValueError,
match=re.escape("A number of 1 variable(s) has to be provided through the argument 'bias_vars', " "got 2."),
):
bias_vars_dict = {"elevation": self.ref, "slope": xdem.terrain.slope(self.ref)}
bcorr1d = biascorr.BiasCorr(bias_var_names=["elevation"])
bcorr1d.fit(**fit_args, bias_vars=bias_vars_dict)

with pytest.raises(
ValueError,
match=re.escape("A number of 2 variable(s) has to be provided through the argument " "'bias_vars', got 1."),
):
bias_vars_dict = {"elevation": self.ref}
bcorr2d = biascorr.BiasCorr(bias_var_names=["elevation", "slope"])
bcorr2d.fit(**fit_args, bias_vars=bias_vars_dict)

# When variables don't match
with pytest.raises(
ValueError,
match=re.escape(
"The keys of `bias_vars` do not match the `bias_var_names` defined during " "instantiation: ['ncc']."
),
):
bcorr1d2 = biascorr.BiasCorr(bias_var_names=["ncc"])
bias_vars_dict = {"elevation": self.ref}
bcorr1d2.fit(**fit_args, bias_vars=bias_vars_dict)

with pytest.raises(
ValueError,
match=re.escape(
"The keys of `bias_vars` do not match the `bias_var_names` defined during "
"instantiation: ['elevation', 'ncc']."
),
):
bcorr2d2 = biascorr.BiasCorr(bias_var_names=["elevation", "ncc"])
bias_vars_dict = {"elevation": self.ref, "slope": xdem.terrain.slope(self.ref)}
bcorr2d2.fit(**fit_args, bias_vars=bias_vars_dict)

@pytest.mark.parametrize("fit_args", all_fit_args) # type: ignore
@pytest.mark.parametrize(
"fit_func", ("norder_polynomial", "nfreq_sumsin", lambda x, a, b: x[0] * a + b)
Expand Down Expand Up @@ -354,87 +396,6 @@ def test_biascorr__bin_and_fit_2d(self, fit_args, fit_func, fit_optimizer, bin_s
# Apply the correction
bcorr.apply(elev=self.tba, bias_vars=bias_vars_dict)

@pytest.mark.parametrize("fit_args", [fit_args_rst_pts, fit_args_rst_rst]) # type: ignore
def test_biascorr1d(self, fit_args) -> None:
"""
Test the subclass BiasCorr1D, which defines default parameters for 1D.
The rest is already tested in test_biascorr.
"""

# Try default "fit" parameters instantiation
bcorr1d = biascorr.BiasCorr1D()

assert bcorr1d._meta["fit_func"] == biascorr.fit_workflows["norder_polynomial"]["func"]
assert bcorr1d._meta["fit_optimizer"] == biascorr.fit_workflows["norder_polynomial"]["optimizer"]
assert bcorr1d._needs_vars is True

# Try default "bin" parameter instantiation
bcorr1d = biascorr.BiasCorr1D(fit_or_bin="bin")

assert bcorr1d._meta["bin_sizes"] == 10
assert bcorr1d._meta["bin_statistic"] == np.nanmedian
assert bcorr1d._meta["bin_apply_method"] == "linear"

elev_fit_args = fit_args.copy()
# Raise error when wrong number of parameters are passed
with pytest.raises(
ValueError, match="A single variable has to be provided through the argument 'bias_vars', " "got 2."
):
bias_vars_dict = {"elevation": self.ref, "slope": xdem.terrain.slope(self.ref)}
bcorr1d.fit(**elev_fit_args, bias_vars=bias_vars_dict)

# Raise error when variables don't match
with pytest.raises(
ValueError,
match=re.escape(
"The keys of `bias_vars` do not match the `bias_var_names` defined during " "instantiation: ['ncc']."
),
):
bcorr1d2 = biascorr.BiasCorr1D(bias_var_names=["ncc"])
bias_vars_dict = {"elevation": self.ref}
bcorr1d2.fit(**elev_fit_args, bias_vars=bias_vars_dict)

@pytest.mark.parametrize("fit_args", all_fit_args) # type: ignore
def test_biascorr2d(self, fit_args) -> None:
"""
Test the subclass BiasCorr2D, which defines default parameters for 2D.
The rest is already tested in test_biascorr.
"""

# Try default "fit" parameters instantiation
bcorr2d = biascorr.BiasCorr2D()

assert bcorr2d._meta["fit_func"] == polynomial_2d
assert bcorr2d._meta["fit_optimizer"] == scipy.optimize.curve_fit
assert bcorr2d._needs_vars is True

# Try default "bin" parameter instantiation
bcorr2d = biascorr.BiasCorr2D(fit_or_bin="bin")

assert bcorr2d._meta["bin_sizes"] == 10
assert bcorr2d._meta["bin_statistic"] == np.nanmedian
assert bcorr2d._meta["bin_apply_method"] == "linear"

elev_fit_args = fit_args.copy()
# Raise error when wrong number of parameters are passed
with pytest.raises(
ValueError, match="Exactly two variables have to be provided through the argument " "'bias_vars', got 1."
):
bias_vars_dict = {"elevation": self.ref}
bcorr2d.fit(**elev_fit_args, bias_vars=bias_vars_dict)

# Raise error when variables don't match
with pytest.raises(
ValueError,
match=re.escape(
"The keys of `bias_vars` do not match the `bias_var_names` defined during "
"instantiation: ['elevation', 'ncc']."
),
):
bcorr2d2 = biascorr.BiasCorr2D(bias_var_names=["elevation", "ncc"])
bias_vars_dict = {"elevation": self.ref, "slope": xdem.terrain.slope(self.ref)}
bcorr2d2.fit(**elev_fit_args, bias_vars=bias_vars_dict)

def test_directionalbias(self) -> None:
"""Test the subclass DirectionalBias."""

Expand Down
10 changes: 1 addition & 9 deletions xdem/coreg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,5 @@
apply_matrix,
invert_matrix,
)
from xdem.coreg.biascorr import ( # noqa
BiasCorr,
BiasCorr1D,
BiasCorr2D,
BiasCorrND,
Deramp,
DirectionalBias,
TerrainBias,
)
from xdem.coreg.biascorr import BiasCorr, Deramp, DirectionalBias, TerrainBias # noqa
from xdem.coreg.workflows import dem_coregistration # noqa
1 change: 1 addition & 0 deletions xdem/coreg/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,7 @@ class CoregDict(TypedDict, total=False):
bin_statistic: Callable[[NDArrayf], np.floating[Any]]
bin_apply_method: Literal["linear"] | Literal["per_bin"]
bias_var_names: list[str]
nd: int | None

# 2/ Outputs
fit_params: NDArrayf
Expand Down
Loading
Loading