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

Add Initial shift Capability to DEM Coregistration #650

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

vschaffn
Copy link
Contributor

@vschaffn vschaffn commented Nov 15, 2024

Resolves #603.

Description:

This PR introduces the ability to specify an initial shift (in X and Y) for DEM co-registration. The key changes include the integration of an estimated_initial_shift parameter in the dem_coregistration() function, allowing users to apply a custom translation (affine-based) to the source DEM before coregistration.

Key Modifications:

  1. dem_coregistration() function:

    • A new estimated_initial_shift parameter is added, allowing users to input a list representing the initial shifts along the X and Y axes.
    • If a shift is provided, the source DEM is translated using geoutils.translate() based on the calculated pixel resolution.
    • The shift_x and shift_y values in the coregistration method’s metadata (meta['outputs']['affine']) are updated to include the initial shifts.
  2. Raise TypeError if the coregistration method or one of the coregistration method in the coregistration pipeline is not affine when an initial shift is provided.

  3. Recursive Shift Update Logic:

    • A recursive update_shift() function is implemented to handle both Coreg and CoregPipeline objects, ensuring that the shift is propagated across all coregistration steps in a pipeline.
    • Metadata handling ensures shifts are correctly updated in all pipeline steps and for standalone Coreg objects.
  4. Unit Tests:

    • Added a test to verify the correct behavior of the estimated_initial_shift parameter.
    • Added a test to ensure TypeError is raised an initial shift is provided and the coregistration method is not affine.

How It Works:

  • The estimated_initial_shift input, given as a list [x_shift, y_shift], is multiplied by the DEM’s pixel resolution to calculate the translation in map units.
  • This shift is then applied before coregistration, and the resulting shift_x and shift_y in the meta are updated accordingly.
  • For CoregPipeline, this process is repeated for each coregistration step to ensure consistency.

Example:

dem_coregistration(
    src_dem_path="path/to/src_dem.tif",
    ref_dem_path="path/to/ref_dem.tif",
    estimated_initial_shift=[10, 5]
)

In this case, the source DEM is initially translated by 10 pixels in the X direction and 5 pixels in the Y direction before the coregistration process begins.

Documentation

  • Updated the function docstring to reflect the new estimated_initial_shift parameter and its usage.
  • Update API documentation.

xdem/coreg/workflows.py Show resolved Hide resolved
xdem/coreg/workflows.py Show resolved Hide resolved
@rhugonnet
Copy link
Contributor

Quick note, the tests you are adding are in a test_ function currently skipped because of an old segfault:

@pytest.mark.skip(reason="The test segfaults locally and in CI (2023-08-21)") # type: ignore

I see some code in this test that is now deprecated (for example using == to check raster equality was an old choice, now changed to raster_equal() to mirror NumPy and leave == for element-wise equality, as for arrays). I will update that quickly in a separate PR and see if I can make the original test pass 😉

@rhugonnet
Copy link
Contributor

@vschaffn I managed to update the code for the test and unskip it. I merged it to main so that you can update with upstream and run it in this PR 😉: #651.
(The segfault was likely not from us but a dependency, so I didn't have to do anything on this).

@vschaffn vschaffn force-pushed the 603-add_initial_shift branch 2 times, most recently from 72db165 to e7f3724 Compare November 19, 2024 09:10
Copy link

@adebardo adebardo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't remember if we talked about adding a section in the documentation.

xdem/coreg/workflows.py Outdated Show resolved Hide resolved
@vschaffn
Copy link
Contributor Author

@adebardo I added xdem.coreg.workflows.dem_coregistration in API documentation, and a raise when an estimated shift is provided but the coregistration method is not affine

@adebardo
Copy link

@duboise-cnes @rhugonnet it's good for you ?

@@ -173,7 +175,7 @@ def dem_coregistration(
:param src_dem_path: Path to the input DEM to be coregistered
:param ref_dem_path: Path to the reference DEM
:param out_dem_path: Path where to save the coregistered DEM. If set to None (default), will not save to file.
:param coreg_method: Coregistration method or pipeline.
:param coreg_method: The xdem coregistration method, or pipeline.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can state simply "Coregistration method" here, it's implicit we're working with xDEM 😉

# Add the initial shift to the calculated shift
if estimated_initial_shift:

def update_shift(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever the recursive function to take care of the pipelines!

tba_dem_shift, ref_dem, estimated_initial_shift=test_shift_list
)
tba_dem_shift_test = tba_dem.translate(test_shift_list[0] * tba_dem.res[0], test_shift_list[1] * tba_dem.res[1])
assert tba_dem_shift.raster_equal(tba_dem_shift_test)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this test (and looking again at the code in workflows), I realize that the input tba_dem is shifted in-place, which thus modifies the input from the user.
Until now our practice has been to "never authorize the modification of any user input", unless the function has a clear inplace argument (when returning same type as self) or is called set_xxx (when re-setting an attribute).

If that makes sense to you as well, we should return a copy in the workflows function instead of modifying the input, I'll tag where 🙂

shift_y = estimated_initial_shift[1] * src_dem.res[1]

# Apply the shift to the source dem
src_dem.translate(shift_x, shift_y, inplace=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As stated above, here we'd need to remove the inplace op to not modify user input.

tba_dem_shift = tba_dem.copy()
dem_coreg2, coreg_method2, coreg_stats2, inlier_mask2 = dem_coregistration(
tba_dem_shift, ref_dem, coreg_method=coreg_simple, estimated_initial_shift=test_shift_tuple
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might have to pass a random_state=42 everywhere (all the dem_coregistration calls) to avoid possible random failures in CI in the future... 🤔

This is because the output shifts depend on both the initial shift and the one optimized by DhMinimize, which doesn't use all the data but uses a random subsample instead, and can lead to slightly different output. Same for all other methods.
Here it is falling on the exact same value probably because of the internal tolerance of the algorithm, but that might change with a different subsample, or under a different OS.

@rhugonnet
Copy link
Contributor

This is great, thanks! Just a couple comments above that can be solved quickly 😄

(Can also remove "xdem" in the PR title, to echo the comment on the argument description!)

@rhugonnet
Copy link
Contributor

rhugonnet commented Nov 22, 2024

Additionally, I think this PR raises a question for future discussion (no need to answer this to merge this PR, just for the record):
Do we want to conserve both DEM.coregister_3d() and coreg.dem_coregistration() in the long term?

Recently we had been moving all functions to be only object method like DEM.slope(), DEM.coregister_3d() to mirror Xarray/GeoPandas behaviour, and naturally integrate them once the accessors for both are here (#656), removing others from the API (like xdem.terrain.slope()).

It's simply about moving the functionality elsewhere, so minimal effort once we have decided on a format.
Tagging @adehecq so that he sees this once he's back (as he didn't know about the recent coregister_3d option I think). I think I'd be in favour of moving dem_coregistration into DEM.coregister_3d.

@vschaffn vschaffn changed the title Add Initial shift Capability to DEM Coregistration in xdem Add Initial shift Capability to DEM Coregistration Nov 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[POC]: Add an initial shift for coregistration
3 participants