From 6e9c375f49da56b249875cf03a47f97ff1910db9 Mon Sep 17 00:00:00 2001 From: William Kearney Date: Thu, 5 Dec 2024 13:59:36 +0100 Subject: [PATCH 1/6] Wrap fillsinks_hybrid from libtopotoolbox --- src/lib/grid.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib/grid.cpp b/src/lib/grid.cpp index 79c7843..a0d3890 100755 --- a/src/lib/grid.cpp +++ b/src/lib/grid.cpp @@ -32,6 +32,22 @@ void wrap_fillsinks(py::array_t output, py::array_t dem, fillsinks(output_ptr, dem_ptr, bc_ptr, dims_ptr); } +void wrap_fillsinks_hybrid(py::array_t output, + py::array_t queue, + py::array_t dem, + py::array_t bc, + std::tuple dims){ + + float *output_ptr = output.mutable_data(); + float *dem_ptr = dem.mutable_data(); + ptrdiff_t *queue_ptr = queue.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_hybrid(output_ptr, queue_ptr, dem_ptr, bc_ptr, dims_ptr); +} + // wrap_identifyflats: // Parameters: // output: A NumPy array to store the output, where flats, sill amd presills will be marked. @@ -227,6 +243,7 @@ void wrap_gradient8( PYBIND11_MODULE(_grid, m) { m.def("fillsinks", &wrap_fillsinks); + m.def("fillsinks_hybrid", &wrap_fillsinks_hybrid); m.def("identifyflats", &wrap_identifyflats); m.def("excesstopography_fsm2d", &wrap_excesstopography_fsm2d); m.def("excesstopography_fmm2d", &wrap_excesstopography_fmm2d); From b14e026b38d88bdfb2919a1036c25457a468c065 Mon Sep 17 00:00:00 2001 From: William Kearney Date: Thu, 5 Dec 2024 14:03:09 +0100 Subject: [PATCH 2/6] Dispatch to fillsinks_hybrid using a boolean argument to fillsinks The default is to use the hybrid algorithm, which should be faster in most cases. --- src/topotoolbox/grid_object.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/topotoolbox/grid_object.py b/src/topotoolbox/grid_object.py index 5216ec1..f025efc 100755 --- a/src/topotoolbox/grid_object.py +++ b/src/topotoolbox/grid_object.py @@ -105,7 +105,8 @@ def reproject(self, return dst def fillsinks(self, - bc: 'np.ndarray | GridObject | None' = None) -> 'GridObject': + bc: 'np.ndarray | GridObject | None' = None, + hybrid: bool = True) -> 'GridObject': """Fill sinks in the digital elevation model (DEM). Parameters @@ -146,7 +147,11 @@ def fillsinks(self, if isinstance(bc, GridObject): bc = bc.z - _grid.fillsinks(output, dem, bc, self.shape) + if hybrid: + queue = np.zeros_like(dem, dtype=np.int64) + _grid.fillsinks_hybrid(output, queue, dem, bc, self.shape) + else: + _grid.fillsinks(output, dem, bc, self.shape) if restore_nans: dem[nans] = np.nan From bba13ca2f1ccc4011e8d4b44855f20d97b37b30c Mon Sep 17 00:00:00 2001 From: William Kearney Date: Thu, 5 Dec 2024 14:03:58 +0100 Subject: [PATCH 3/6] Test that sequential and hybrid fillsinks produce the same result --- tests/test_grid_object.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_grid_object.py b/tests/test_grid_object.py index f08f16d..8da6ef6 100755 --- a/tests/test_grid_object.py +++ b/tests/test_grid_object.py @@ -25,11 +25,16 @@ def test_fillsinks(square_dem, wide_dem, tall_dem): # since grid is a fixture, it has to be assigned/called first dem = grid original_dem = dem.z.copy() - filled_dem = dem.fillsinks() + filled_dem = dem.fillsinks(hybrid=False) + filled_dem_hybrid = dem.fillsinks(hybrid=True) # Ensure that DEM has not been modified by fillsinks assert np.all(original_dem == dem.z) + # The sequential and hybrid reconstruction algorithms should + # produce the same result + assert np.all(filled_dem.z == filled_dem_hybrid.z) + # Loop over all cells of the DEM for i in range(dem.shape[0]): for j in range(dem.shape[1]): From cba261d5fd423bc535955d17ec53ce99b5740402 Mon Sep 17 00:00:00 2001 From: William Kearney Date: Thu, 5 Dec 2024 14:10:47 +0100 Subject: [PATCH 4/6] Document the hybrid option --- src/topotoolbox/grid_object.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/topotoolbox/grid_object.py b/src/topotoolbox/grid_object.py index f025efc..e70d07e 100755 --- a/src/topotoolbox/grid_object.py +++ b/src/topotoolbox/grid_object.py @@ -117,6 +117,10 @@ def fillsinks(self, indicate pixels that should be fixed to their values in the original DEM and values of 0 indicate pixels that should be filled. + hybrid: bool, optional + Should hybrid reconstruction algorithm be used? Defaults to True. Hybrid + reconstruction is faster but requires additional memory be allocated + for a queue. Returns ------- From 36405d1a5ac3f601ef48dd9c4ce7f6d4376d9ae0 Mon Sep 17 00:00:00 2001 From: William Kearney Date: Thu, 5 Dec 2024 14:25:58 +0100 Subject: [PATCH 5/6] Use fillsinks_hybrid in FlowObject This is otherwise identical to the implementation in GridObject, but it also uses the queue array that it allocates as the heap for gwdt, so it allocates the queue regardless of the value of hybrid. --- src/topotoolbox/flow_object.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/topotoolbox/flow_object.py b/src/topotoolbox/flow_object.py index 4ff453e..579540d 100644 --- a/src/topotoolbox/flow_object.py +++ b/src/topotoolbox/flow_object.py @@ -16,7 +16,8 @@ class FlowObject(): """ def __init__(self, grid: GridObject, - bc: np.ndarray | GridObject | None = None): + bc: np.ndarray | GridObject | None = None, + hybrid : bool = True): """The constructor for the FlowObject. Takes a GridObject as input, computes flow direction information and saves them as an FlowObject. @@ -58,7 +59,11 @@ def __init__(self, grid: GridObject, if isinstance(bc, GridObject): bc = bc.z - _grid.fillsinks(filled_dem, dem, bc, dims) + queue = np.zeros_like(dem, dtype=np.int64, order='F') + if hybrid: + _grid.fillsinks_hybrid(filled_dem, queue, dem, bc, dims) + else: + _grid.fillsinks(filled_dem, dem, bc, dims) if restore_nans: dem[nans] = np.nan @@ -73,7 +78,7 @@ def __init__(self, grid: GridObject, dist = np.zeros_like(flats, dtype=np.float32, order='F') prev = conncomps # prev: dtype=np.int64 - heap = np.zeros_like(flats, dtype=np.int64, order='F') + heap = queue # heap: dtype=np.int64 back = np.zeros_like(flats, dtype=np.int64, order='F') _grid.gwdt(dist, prev, costs, flats, heap, back, dims) From 1f1580ae42b047efd90b12b0ff5d7b3d8ef48538 Mon Sep 17 00:00:00 2001 From: William Kearney Date: Fri, 6 Dec 2024 10:04:55 +0100 Subject: [PATCH 6/6] Document the hybrid parameter of FlowObject --- src/topotoolbox/flow_object.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/topotoolbox/flow_object.py b/src/topotoolbox/flow_object.py index 579540d..2079a52 100644 --- a/src/topotoolbox/flow_object.py +++ b/src/topotoolbox/flow_object.py @@ -31,6 +31,10 @@ def __init__(self, grid: GridObject, indicate pixels that should be fixed to their values in the original DEM and values of 0 indicate pixels that should be filled. + hybrid: bool, optional + Should hybrid reconstruction algorithm be used to fill + sinks? Defaults to True. Hybrid reconstruction is faster + but requires additional memory be allocated for a queue. Notes -----