diff --git a/CMakeLists.txt b/CMakeLists.txt index ea08aa3..2643c81 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ include(FetchContent) FetchContent_Declare( topotoolbox GIT_REPOSITORY https://github.com/TopoToolbox/libtopotoolbox.git - GIT_TAG 2024-W40 + GIT_TAG main ) FetchContent_MakeAvailable(topotoolbox) diff --git a/src/lib/grid.cpp b/src/lib/grid.cpp index 5bb92cf..79c7843 100755 --- a/src/lib/grid.cpp +++ b/src/lib/grid.cpp @@ -19,15 +19,17 @@ namespace py = pybind11; // dem: A NumPy array representing the digital elevation model. // dims: A tuple containing the number of rows and columns. -void wrap_fillsinks(py::array_t output, py::array_t dem, +void wrap_fillsinks(py::array_t output, py::array_t dem, + py::array_t bc, std::tuple dims){ float *output_ptr = output.mutable_data(); float *dem_ptr = dem.mutable_data(); + uint8_t *bc_ptr = bc.mutable_data(); std::array dims_array = {std::get<0>(dims), std::get<1>(dims)}; ptrdiff_t *dims_ptr = dims_array.data(); - fillsinks(output_ptr, dem_ptr, dims_ptr); + fillsinks(output_ptr, dem_ptr, bc_ptr, dims_ptr); } // wrap_identifyflats: diff --git a/src/topotoolbox/flow_object.py b/src/topotoolbox/flow_object.py index 07b56e6..3bf218a 100644 --- a/src/topotoolbox/flow_object.py +++ b/src/topotoolbox/flow_object.py @@ -15,7 +15,8 @@ class FlowObject(): digital elevation model (DEM). """ - def __init__(self, grid: GridObject): + def __init__(self, grid: GridObject, + bc: np.ndarray | GridObject | None = None): """The constructor for the FlowObject. Takes a GridObject as input, computes flow direction information and saves them as an FlowObject. @@ -23,6 +24,12 @@ def __init__(self, grid: GridObject): ---------- grid : GridObject The GridObject that will be the basis of the computation. + bc : ndarray or GridObject, optional + Boundary conditions for sink filling. `bc` should be an array + of np.uint8 that matches the shape of the DEM. Values of 1 + indicate pixels that should be fixed to their values in the + original DEM and values of 0 indicate pixels that should be + filled. Notes ----- @@ -33,7 +40,28 @@ def __init__(self, grid: GridObject): dem = grid.z filled_dem = np.zeros_like(dem, dtype=np.float32, order='F') - _grid.fillsinks(filled_dem, dem, dims) + restore_nans = False + if bc is None: + bc = np.ones_like(dem, dtype=np.uint8) + bc[1:-1, 1:-1] = 0 # Set interior pixels to 0 + + nans = np.isnan(dem) + dem[nans] = -np.inf + bc[nans] = 1 + restore_nans = True + + if bc.shape != dims: + err = ("The shape of the provided boundary conditions does not " + f"match the shape of the DEM. {dims}") + raise ValueError(err)from None + + if isinstance(bc, GridObject): + bc = bc.z + + _grid.fillsinks(filled_dem, dem, bc, dims) + + if restore_nans: + filled_dem[nans] = np.nan flats = np.zeros_like(dem, dtype=np.int32, order='F') _grid.identifyflats(flats, filled_dem, dims) diff --git a/src/topotoolbox/grid_object.py b/src/topotoolbox/grid_object.py index 82bdf62..6e249c7 100755 --- a/src/topotoolbox/grid_object.py +++ b/src/topotoolbox/grid_object.py @@ -38,19 +38,52 @@ def __init__(self) -> None: self.transform = None self.crs = None - def fillsinks(self) -> 'GridObject': + def fillsinks(self, + bc: 'np.ndarray | GridObject | None' = None) -> 'GridObject': """Fill sinks in the digital elevation model (DEM). + Parameters + ---------- + bc : ndarray or GridObject, optional + Boundary conditions for sink filling. `bc` should be an array + of np.uint8 that matches the shape of the DEM. Values of 1 + indicate pixels that should be fixed to their values in the + original DEM and values of 0 indicate pixels that should be + filled. + Returns ------- GridObject The filled DEM. + """ dem = self.z.astype(np.float32, order='F') output = np.zeros_like(dem) - _grid.fillsinks(output, dem, self.shape) + restore_nans = False + + if bc is None: + bc = np.ones_like(dem, dtype=np.uint8) + bc[1:-1, 1:-1] = 0 # Set interior pixels to 0 + + nans = np.isnan(dem) + dem[nans] = -np.inf + bc[nans] = 1 # Set NaNs to 1 + restore_nans = True + + if bc.shape != self.shape: + err = ("The shape of the provided boundary conditions does not " + f"match the shape of the DEM. {self.shape}") + raise ValueError(err)from None + + if isinstance(bc, GridObject): + bc = bc.z + + _grid.fillsinks(output, dem, bc, self.shape) + + if restore_nans: + output[nans] = np.nan result = copy.copy(self) result.z = output