From c51ebafddfd7780b8d9a0a968bcbb90c302349e7 Mon Sep 17 00:00:00 2001
From: Joeperdefloep <ikbendeleukstethuis@gmail.com>
Date: Wed, 25 May 2022 14:48:37 +0200
Subject: [PATCH] Added upslope cell finding

going to do some things

fixed?
---
 include/richdem/common/grid_cell.hpp          |   1 +
 include/richdem/methods/upslope_cells.hpp     |  29 +++
 .../methods/upslope_cells_functions.hpp       | 215 ++++++++++++++++++
 tests/tests.cpp                               | 183 ++++++++++++++-
 tests/upslope_cells/test_east.d8              |  16 ++
 tests/upslope_cells/test_east.dem             |  16 ++
 tests/upslope_cells/test_east.mask            |  16 ++
 tests/upslope_cells/test_east.mflow           |  16 ++
 tests/upslope_cells/test_east.quinn           |  16 ++
 wrappers/pyrichdem/richdem/__init__.py        |  63 +++++
 wrappers/pyrichdem/src/pywrapper.hpp          |  15 ++
 11 files changed, 585 insertions(+), 1 deletion(-)
 create mode 100644 include/richdem/methods/upslope_cells.hpp
 create mode 100644 include/richdem/methods/upslope_cells_functions.hpp
 create mode 100644 tests/upslope_cells/test_east.d8
 create mode 100644 tests/upslope_cells/test_east.dem
 create mode 100644 tests/upslope_cells/test_east.mask
 create mode 100644 tests/upslope_cells/test_east.mflow
 create mode 100644 tests/upslope_cells/test_east.quinn

diff --git a/include/richdem/common/grid_cell.hpp b/include/richdem/common/grid_cell.hpp
index 6a8dfafe..3aff1f44 100644
--- a/include/richdem/common/grid_cell.hpp
+++ b/include/richdem/common/grid_cell.hpp
@@ -22,6 +22,7 @@ struct GridCell {
   GridCell() = default;
   /// Initiate the grid cell to the coordinates (x0,y0)
   GridCell(int x0, int y0):x(x0),y(y0){}
+  bool operator == (const GridCell &a) const {return x == a.x && y == a.y;}
 };
 
 
diff --git a/include/richdem/methods/upslope_cells.hpp b/include/richdem/methods/upslope_cells.hpp
new file mode 100644
index 00000000..0fc58d18
--- /dev/null
+++ b/include/richdem/methods/upslope_cells.hpp
@@ -0,0 +1,29 @@
+#ifndef _richdem_upslope_cells_hpp_
+#define _richdem_upslope_cells_hpp_
+
+#include <richdem/common/Array2D.hpp>
+#include <richdem/common/Array3D.hpp>
+#include <richdem/methods/upslope_cells_functions.hpp>
+
+namespace richdem {
+
+template<class T, class U>
+void init(
+    Array2D<T> &to_init,
+    const Array2D<U> &example,
+    const int &val
+){
+    to_init.resize(example);
+    to_init.setAll(val);
+    to_init.setNoData(val);
+}
+
+
+// TODO: do we have to resize  and initialize the results array?
+template<class mask_t,               class result_t> void UC_mask_props(const Array2D<mask_t> &mask,    const Array3D<float> &props, Array2D<result_t> &upslope){init(upslope,mask,0);  std::queue<GridCell> expansion = queue_from_mask(mask,upslope)             ; upslope_cells_props<Topology::D8>(expansion, props, upslope);}
+template<class mask_t, class elev_t, class result_t> void UC_mask_mflow(const Array2D<mask_t> &mask,    const Array2D<elev_t> &elev, Array2D<result_t> &upslope){init(upslope,elev,0);  std::queue<GridCell> expansion = queue_from_mask(mask,upslope)             ; upslope_cells_mflow<Topology::D8>(expansion, elev,  upslope);}
+template<                            class result_t> void UC_line_props(int x0, int y0, int x1, int y1, const Array3D<float> &props, Array2D<result_t> &upslope){init(upslope,props,0); std::queue<GridCell> expansion = queue_from_linepoints(x0,y0,x1,y1,upslope); upslope_cells_props<Topology::D8>(expansion, props, upslope);}
+template<              class elev_t, class result_t> void UC_line_mflow(int x0, int y0, int x1, int y1, const Array2D<elev_t> &elev, Array2D<result_t> &upslope){init(upslope,elev,0);  std::queue<GridCell> expansion = queue_from_linepoints(x0,y0,x1,y1,upslope); upslope_cells_mflow<Topology::D8>(expansion, elev,  upslope);}
+
+}
+#endif //_richdem_upslope_cells_
\ No newline at end of file
diff --git a/include/richdem/methods/upslope_cells_functions.hpp b/include/richdem/methods/upslope_cells_functions.hpp
new file mode 100644
index 00000000..cb8a1ac8
--- /dev/null
+++ b/include/richdem/methods/upslope_cells_functions.hpp
@@ -0,0 +1,215 @@
+#ifndef _richdem_upslope_cells_functions_hpp_
+#define _richdem_upslope_cells_functions_hpp_
+
+#include <richdem/common/Array2D.hpp>
+#include <richdem/common/Array3D.hpp>
+#include <richdem/common/logger.hpp>
+#include <richdem/common/ProgressBar.hpp>
+#include <queue>
+
+namespace richdem {
+
+/**
+ * @brief Add all non-nodata cells to the return queue and sets the
+ * ``upslope_cells`` value to 2 there.
+ * 
+ * @tparam T upslope cells dtype
+ * @tparam U mask dtype
+ * @param[in] mask The mask
+ * @param[out] upslope_cells grid for indicating upslope cells
+ * @return std::queue<GridCell> 
+ */
+template<class T, class U>
+std::queue<GridCell> queue_from_mask(
+  const Array2D<U>  &mask,
+  Array2D<T>              &upslope_cells
+){
+  std::queue<GridCell> expansion;
+  // this cannot be parallelized, because it appends everything to the same queue
+  for (auto x=0;x<mask.width();x++){
+    for (auto y=0;y<mask.height();y++){
+      if(mask(x,y)!=mask.noData()){
+        upslope_cells(x,y)=2;
+        expansion.emplace(x,y);
+      }
+    }
+  }
+
+  return expansion;
+}
+
+/**
+ * @brief Draws a line between the points (x0,y0) and (x1,y1). Adds visited
+ * cells to the return queue and sets them to 2 in ``upslope_cells``.
+ * 
+ * @tparam T upslope cells dtype
+ * @param[in] x0 x coordinate of first point
+ * @param[in] y0 y coordinate of first point
+ * @param[in] x1 x coordinate of second point
+ * @param[in] y1 y coordinate of second point
+ * @param[out] upslope_cells grid for indicating upslope cells
+ * @return std::queue<GridCell> A queue with gridcells that were visited
+ */
+template<class T>
+std::queue<GridCell> queue_from_linepoints(
+  int x0,
+  int y0,
+  int x1,
+  int y1,
+  Array2D<T>       &upslope_cells
+){
+  std::queue<GridCell> expansion;
+
+  // if the slope>1, swap x and y coordinates.
+  // It works exatly the same, but points
+  // are drawn with swapped coordinates
+  bool swapxy = false;
+  if(std::abs(float(y0-y1)/float(x0-x1))>1){
+    std::cout << "swapped x and y" << std::endl;
+    std::swap(x0,y0);
+    std::swap(x1,y1);
+    swapxy= true;
+  }
+
+  if(x0>x1){
+    std::swap(x0,x1);
+    std::swap(y0,y1);
+  }
+
+  // modified Bresenham Line-Drawing Algorithm from
+  // https://www.geeksforgeeks.org/bresenhams-line-generation-algorithm/
+  int deltaerr = 2*(y1-y0);
+  int error = deltaerr - (x1-x0);
+
+  RDLOG_MISC<<"Line slope is "<<deltaerr;
+  int y=y0;
+  for(int x=x0;x<=x1;x++){
+    if (!swapxy){
+      expansion.emplace(x,y);
+      upslope_cells(x,y)=2;
+    } else {
+      expansion.emplace(y,x);
+      upslope_cells(y,x)=2;
+    }
+    error += deltaerr;
+    if (error>=0 && x!=x1) { //safeguard for segfault if we draw to the edge of the grid
+      // Draw the point above if we move to te next pixel
+      // the difference between a "solid" and non-solid line:
+      // o o X x x <--the X
+      // x x x 0 0 
+      if(!swapxy){
+        expansion.emplace(x+1,y);
+        upslope_cells(x+1,y) = 2;
+      } else {
+        expansion.emplace(y,x+1);
+        upslope_cells(y,x+1) = 2;
+      }
+      y++;
+      error -= 2*(x1-x0);
+    }
+  }
+  return expansion;
+}
+
+//upslope_from_props
+/**
+ * @brief Calculates uplsope cells for a given set of input cells, given a
+ * proportions matrix of flow. All cells that have flow into input cells will be added
+ * 
+ * @param[in]  &expansion      queue with starting cells
+ * @param[in]  &elevation      DEM
+ * @param[out] &upslope_cells  A grid of 1/2/NoData
+ */
+template<Topology topo, class T, class U>
+void upslope_cells_props(
+  std::queue<GridCell> &expansion,
+  const Array3D<T>     &props,
+  Array2D<U>           &upslope_cells
+){
+  //TODO: ALG NAME?
+  RDLOG_PROGRESS<<"Setting up the upslope_cells matrix..."<<std::flush;
+  // upslope_cells.resize(props.width(),props.height());
+  // upslope_cells.setAll(0);
+  // upslope_cells.setNoData(0);
+
+  static_assert(topo==Topology::D8 || topo==Topology::D4);
+  constexpr auto dx = get_dx_for_topology<topo>();
+  constexpr auto dy = get_dy_for_topology<topo>();
+  constexpr auto nmax = get_nmax_for_topology<topo>();
+
+  ProgressBar progress;
+
+  progress.start(props.size());
+  long int ccount=0;
+  while(expansion.size()>0){
+    GridCell c=expansion.front();
+    expansion.pop();
+
+    progress.update(ccount++);
+
+    for(int n=1;n<=8;n++)
+      if(!props.inGrid(c.x+dx[n],c.y+dy[n]))
+        continue;
+      else if(upslope_cells.isNoData(c.x+dx[n],c.y+dy[n]) && props(c.x+dx[n],c.y+dy[n],d8_inverse[n])>0) {
+        expansion.emplace(c.x+dx[n],c.y+dy[n]);
+        upslope_cells(c.x+dx[n],c.y+dy[n])=1;
+      }
+  }
+  RDLOG_TIME_USE<<"Succeeded in "<<progress.stop();
+  RDLOG_MISC<<"Found "<<ccount<<" up-slope cells."; //TODO
+}
+
+//upslope_cells multiple flow implementation
+/**
+ * @brief Calculates uplsope cells for a given set of input cells, assuming
+ * multiple flow. That is, all cells that are higher than the cell being
+ * processed will be added, regardless of whether the current cell is the lowest
+ * neighbour.
+ * 
+ * @param[in]  &expansion      queue with starting cells
+ * @param[in]  &elevation      DEM
+ * @param[out] &upslope_cells  A grid of 1/2/NoData
+ */
+template<Topology topo, class T, class U>
+void upslope_cells_mflow(
+  std::queue<GridCell> &expansion,
+  const Array2D<T>     &elevation,
+  Array2D<U>           &upslope_cells
+){
+  //TODO: ALG NAME?
+  RDLOG_PROGRESS<<"Setting up the upslope_cells matrix..."<<std::flush;
+  // upslope_cells.resize(elevation);
+  // upslope_cells.setAll(0);
+  // upslope_cells.setNoData(0);
+
+  static_assert(topo==Topology::D8 || topo==Topology::D4);
+  constexpr auto dx = get_dx_for_topology<topo>();
+  constexpr auto dy = get_dy_for_topology<topo>();
+  constexpr auto nmax = get_nmax_for_topology<topo>();
+
+  ProgressBar progress;
+
+  progress.start(elevation.size());
+  long int ccount=0;
+  while(expansion.size()>0){
+    GridCell c=expansion.front();
+    expansion.pop();
+
+    progress.update(ccount++);
+
+    for(int n=1;n<=8;n++)
+      if(!elevation.inGrid(c.x+dx[n],c.y+dy[n]))
+        continue;
+      else if(elevation.isNoData(c.x+dx[n],c.y+dy[n]))
+        continue;
+      else if(upslope_cells.isNoData(c.x+dx[n],c.y+dy[n]) && elevation(c.x,c.y)<elevation(c.x+dx[n],c.y+dy[n])) {
+        expansion.emplace(c.x+dx[n],c.y+dy[n]);
+        upslope_cells(c.x+dx[n],c.y+dy[n])=1;
+      }
+  }
+  RDLOG_TIME_USE<<"Succeeded in "<<progress.stop();
+  RDLOG_MISC<<"Found "<<ccount<<" up-slope cells."; //TODO
+}
+
+}
+#endif
\ No newline at end of file
diff --git a/tests/tests.cpp b/tests/tests.cpp
index e3bb4263..5eb77ef7 100644
--- a/tests/tests.cpp
+++ b/tests/tests.cpp
@@ -5,6 +5,8 @@
 #include <richdem/common/loaders.hpp>
 #include <richdem/misc/misc_methods.hpp>
 #include <richdem/richdem.hpp>
+#include <richdem/methods/upslope_cells_functions.hpp>
+#include <richdem/methods/upslope_cells.hpp>
 
 #include <filesystem>
 #include <queue>
@@ -455,4 +457,183 @@ TEST_CASE("DirectionsMatchExpectations"){
 
     CHECK(get_nmax_for_topology<Topology::D4>() == 4);
   }
-}
\ No newline at end of file
+}
+TEST_CASE("Checking Catchment Delineation Generic"){
+  SUBCASE("mask"){
+    SUBCASE("single"){
+    Array2D<uint8_t> mask = {
+      {0, 0, 0, 0, 0, 0},
+      {0, 0, 0, 0, 0, 0},
+      {0, 0, 1, 0, 0, 0},
+      {0, 0, 0, 0, 0, 0},
+      {0, 0, 0, 0, 0, 0},
+    };
+    mask.setNoData(0);
+    Array2D<uint8_t> upslope(mask,0);
+
+    std::queue<GridCell> q_expected;
+    q_expected.emplace(2,2);
+    const Array2D<uint8_t> upslope_e = {
+      {0, 0, 0, 0, 0, 0},
+      {0, 0, 0, 0, 0, 0},
+      {0, 0, 2, 0, 0, 0},
+      {0, 0, 0, 0, 0, 0},
+      {0, 0, 0, 0, 0, 0},
+    };
+
+    std::queue<GridCell> q = queue_from_mask(mask, upslope);
+    std::cout<< mask.noData() << std::endl;
+    CHECK(q.front().x == q_expected.front().x);
+    CHECK(q.front().y == q_expected.front().y);
+    CHECK(q.size() == 1);
+    CHECK(upslope(2,2) == 2);
+    }
+    SUBCASE("multi"){
+    Array2D<uint8_t> mask = {
+      {0, 0, 0, 0, 0, 0},
+      {0, 1, 0, 0, 0, 0},
+      {0, 0, 1, 0, 0, 0},
+      {0, 1, 0, 0, 0, 0},
+      {0, 0, 0, 0, 0, 0},
+    };
+    mask.setNoData(0);
+    Array2D<uint8_t> upslope(mask,0);
+
+    std::queue<GridCell> q_expected;
+    q_expected.emplace(1,1);
+    q_expected.emplace(1,3);
+    q_expected.emplace(2,2);
+
+    const Array2D<uint8_t> upslope_e = {
+      {0, 0, 0, 0, 0, 0},
+      {0, 2, 0, 0, 0, 0},
+      {0, 0, 2, 0, 0, 0},
+      {0, 2, 0, 0, 0, 0},
+      {0, 0, 0, 0, 0, 0},
+    };
+
+    std::queue q = queue_from_mask(mask, upslope);
+    CHECK(q.size() == 3);
+    CHECK(q == q_expected);
+    CHECK(upslope == upslope_e);
+    }
+    // TODO: make more tests?
+  }
+
+  SUBCASE("line"){
+    SUBCASE("horizontal"){
+      int x0=0;
+      int y0=0;
+      int x1=2;
+      int y1=0;
+
+      std::queue<GridCell> q_expected;
+      q_expected.emplace(0,0);
+      q_expected.emplace(1,0);
+      q_expected.emplace(2,0);
+
+      Array2D<uint8_t> upslope_e = {
+        {2, 2, 2},
+        {0, 0, 0},
+        {0, 0, 0},
+      };
+      upslope_e.setNoData(0);
+      Array2D<uint8_t> upslope(upslope_e,0);
+      upslope.setNoData(0);
+
+      std::queue<GridCell> q = queue_from_linepoints(x0,y0,x1,y1,upslope);
+
+      CHECK(upslope == upslope_e);
+      CHECK(q == q_expected);
+    }
+    //TODO: add more tests. definately for slope>1, so that swapping is
+    //routinely tested. (I now did it by inspection, and it works)
+  }
+
+  Array2D<int> dem = {
+    {1,2,3,4,5},
+    {1,2,3,4,5},
+    {1,2,3,4,5},
+    {1,2,3,4,5},
+    {1,2,3,4,5},
+  };
+  dem.setNoData(0);
+
+  SUBCASE("mflow"){
+    
+    Array2D<uint8_t> result(dem,0);
+    std::queue<GridCell> expansion;
+    expansion.emplace(0,2);
+
+    Array2D<uint8_t> expected = {
+      {0, 0, 1, 1, 1},
+      {0, 1, 1, 1, 1},
+      {0, 1, 1, 1, 1},
+      {0, 1, 1, 1, 1},
+      {0, 0, 1, 1, 1}
+    };
+    result.setNoData(0);
+    expected.setNoData(0); //the function sets nodata to 0
+
+    upslope_cells_mflow<Topology::D8>(expansion, dem, result);
+    CHECK(result == expected);
+  }
+
+  SUBCASE("props (take quinn)"){
+    //more methods is not necessary
+    Array2D<uint8_t> result(dem,0);
+    result.setNoData(0);
+    std::queue<GridCell> expansion;
+    expansion.emplace(0,2);
+
+    //construct the proportions array
+    Array3D<float> props(dem);
+    FM_Quinn(dem,props);
+
+    Array2D<uint8_t> expected = {
+      {0, 0, 0, 0, 0},
+      {0, 1, 1, 1, 0},
+      {0, 1, 1, 1, 0},
+      {0, 1, 1, 1, 0},
+      {0, 0, 0, 0, 0}      // TODO: maybe add edge cells if they are higher? is slightly complex though, cuz Array3D does not support that...
+    };                     // so flow always goes off the edge of the grid, hence the 0s.
+    expected.setNoData(0); //the function sets nodata to 0
+
+    upslope_cells_props<Topology::D8>(expansion, props, result);
+    CHECK(result == expected);
+  }
+}
+
+TEST_CASE("Checking upslope cells") {
+  for(auto p: fs::directory_iterator("upslope_cells")){
+    fs::path this_path = p.path();
+    if(this_path.extension()==".dem"){
+        Array2D<int>     dem      (this_path,                            false);
+        Array2D<uint8_t> mask     (this_path.replace_extension("mask"),  false);
+        Array2D<int32_t> ans_mflow(this_path.replace_extension("mflow"), false);
+        Array2D<int32_t> ans_quinn(this_path.replace_extension("quinn"), false);
+        Array2D<int32_t> ans_d8   (this_path.replace_extension("d8"),    false);
+        Array2D<int32_t> result;
+        Array3D<float>   props(dem);
+
+        UC_mask_mflow(mask,dem,result);
+        CHECK(result == ans_mflow);
+        result.printAll("mflow result");
+        ans_mflow.printAll("expected result");
+
+        FM_Quinn(dem, props);
+        UC_mask_props(mask,props,result);
+        CHECK(result == ans_quinn);
+        result.printAll("Quinn upslope result");
+        ans_quinn.printAll("expected result");
+
+        FM_D8(dem, props);
+        UC_mask_props(mask,props,result);
+        CHECK(result == ans_d8);
+        result.printAll("D8 upslope result");
+        ans_d8.printAll("expected result");
+
+
+      }
+    }
+  }
diff --git a/tests/upslope_cells/test_east.d8 b/tests/upslope_cells/test_east.d8
new file mode 100644
index 00000000..a62ba2a5
--- /dev/null
+++ b/tests/upslope_cells/test_east.d8
@@ -0,0 +1,16 @@
+ncols         10
+nrows         10
+xllcorner     421568
+yllcorner     4872699
+cellsize      3
+NODATA_value  0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+2 1 1 1 1 1 1 1 1 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/upslope_cells/test_east.dem b/tests/upslope_cells/test_east.dem
new file mode 100644
index 00000000..7e763b9b
--- /dev/null
+++ b/tests/upslope_cells/test_east.dem
@@ -0,0 +1,16 @@
+ncols         10
+nrows         10
+xllcorner     421568
+yllcorner     4872699
+cellsize      3
+NODATA_value  -1
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9 10
\ No newline at end of file
diff --git a/tests/upslope_cells/test_east.mask b/tests/upslope_cells/test_east.mask
new file mode 100644
index 00000000..5d5543fb
--- /dev/null
+++ b/tests/upslope_cells/test_east.mask
@@ -0,0 +1,16 @@
+ncols         10
+nrows         10
+xllcorner     421568
+yllcorner     4872699
+cellsize      3
+NODATA_value  0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+1 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/upslope_cells/test_east.mflow b/tests/upslope_cells/test_east.mflow
new file mode 100644
index 00000000..8f12c7aa
--- /dev/null
+++ b/tests/upslope_cells/test_east.mflow
@@ -0,0 +1,16 @@
+ncols         10
+nrows         10
+xllcorner     421568
+yllcorner     4872699
+cellsize      3
+NODATA_value  0
+0 0 0 0 1 1 1 1 1 1
+0 0 0 1 1 1 1 1 1 1
+0 0 1 1 1 1 1 1 1 1
+0 1 1 1 1 1 1 1 1 1
+2 1 1 1 1 1 1 1 1 1
+0 1 1 1 1 1 1 1 1 1
+0 0 1 1 1 1 1 1 1 1
+0 0 0 1 1 1 1 1 1 1
+0 0 0 0 1 1 1 1 1 1
+0 0 0 0 0 1 1 1 1 1
\ No newline at end of file
diff --git a/tests/upslope_cells/test_east.quinn b/tests/upslope_cells/test_east.quinn
new file mode 100644
index 00000000..1839a2d2
--- /dev/null
+++ b/tests/upslope_cells/test_east.quinn
@@ -0,0 +1,16 @@
+ncols         10
+nrows         10
+xllcorner     421568
+yllcorner     4872699
+cellsize      3
+NODATA_value  0
+0 0 0 0 0 0 0 0 0 0
+0 0 0 1 1 1 1 1 1 0
+0 0 1 1 1 1 1 1 1 0
+0 1 1 1 1 1 1 1 1 0
+2 1 1 1 1 1 1 1 1 0
+0 1 1 1 1 1 1 1 1 0
+0 0 1 1 1 1 1 1 1 0
+0 0 0 1 1 1 1 1 1 0
+0 0 0 0 1 1 1 1 1 0
+0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/wrappers/pyrichdem/richdem/__init__.py b/wrappers/pyrichdem/richdem/__init__.py
index fadc80a5..7e5b5399 100644
--- a/wrappers/pyrichdem/richdem/__init__.py
+++ b/wrappers/pyrichdem/richdem/__init__.py
@@ -897,3 +897,66 @@ def fill_spill_merge(dem: rdarray, labels: rdarray, flowdirs: rdarray, deps: Lis
     wtdw = wtd.wrap()
 
     dhret = depression_hierarchy.fill_spill_merge(demw, labelsw, flowdirsw, deps, wtdw)
+
+def UpslopeCells(
+  dem,
+  x0: int = None,
+  y0: int = None,
+  x1: int = None,
+  y1: int = None,
+  mask: rdarray = None,
+  method: str = None
+):
+  """
+  Delineates a catchment, based on input points, or a true/false raster.
+
+  Args: 
+    dem       (rdarray): A digital elevation model
+    x0        (int):     x coordinate of first point
+    y0        (int):     y coordinate of first point
+    x1        (int):     x coordinate of second point
+    y1        (int):     y coordinate of second point
+    init_mask (rdarray): Mask array with nonzero values for input cells
+    method    (string):  The method to use. Can be "mflow", "D8" or any flow metric, such as "Dinf"
+  """
+  if type(dem) is not rdarray:
+    raise Exception("A richdem.rdarray or numpy.ndarray is required!")
+
+  result = rdarray(np.zeros(shape=dem.shape,dtype="uint8"),meta_obj=dem,no_data=0)
+  resultw = result.wrap()
+
+  if method == "mflow":
+    demw = dem.wrap()
+    if x0 is not None and y0 is not None and x1 is not None and y1 is not None:
+      _richdem.UC_line_mflow(x0,y0,x1,y1,demw,resultw)
+    elif x0 is not None and y0 is not None:
+      _richdem.UC_line_mflow(x0,y0,x0,y0,demw,resultw)
+    elif mask is not None:
+      maskw = mask.wrap()
+      _richdem.UC_mask_mflow(maskw,demw,resultw)
+  else:
+    propsw = FlowProportions(dem,method).wrap()
+    if x0 is not None and y0 is not None and x1 is not None and y1 is not None:
+      _richdem.UC_line_props(x0,y0,x1,y1,propsw,resultw)
+    elif x0 is not None and y0 is not None:
+      _richdem.UC_line_props(x0,y0,x0,y0,propsw,resultw)
+    elif mask is not None:
+      maskw = mask.wrap()
+      _richdem.UC_mask_props(maskw,propsw,resultw)
+
+  #   init_maskw = init_mask.wrap()
+  #   initqueue = _richdem.InitQueueFromMask(demw,init_maskw)
+  # elif x0 is not None and y0 is not None and x1 is not None and y1 is not None:
+  #   initqueue = _richdem.InitQueueFromLinePoints(x0,y0,x1,y1,demw)
+  # elif x0 is not None and y0 is not None:
+  #   initqueue = _richdem.InitQueueFromLinePoints(x0,y0,x0,y0,demw)
+
+  # if a method is specified, calculate proportions
+  if method == "mflow":
+    method = "Quinn" #TODO: make this specialized
+  # else:
+
+  result.copyFromWrapped(resultw)
+
+  return result
+
diff --git a/wrappers/pyrichdem/src/pywrapper.hpp b/wrappers/pyrichdem/src/pywrapper.hpp
index c6419ef5..3cb8d9b1 100644
--- a/wrappers/pyrichdem/src/pywrapper.hpp
+++ b/wrappers/pyrichdem/src/pywrapper.hpp
@@ -79,6 +79,21 @@ void TemplatedFunctionsWrapper(pybind11::module &m, std::string tname){
   m.def("FM_OCallaghanD4",        &FM_OCallaghan        <Topology::D4,T>, "TODO");
   m.def("FM_D8",                  &FM_D8                <T>,              "TODO");
   m.def("FM_D4",                  &FM_D4                <T>,              "TODO");
+
+  m.def("UC_mask_props",          &UC_mask_props        <T,uint8_t>,         "TODO");
+  // TODO: make this work for different datatypes. Do we just make a big list?
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <float   ,T,uint8_t>, "TODO");
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <double  ,T,uint8_t>, "TODO");
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <int8_t  ,T,uint8_t>, "TODO");
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <int16_t ,T,uint8_t>, "TODO");
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <int32_t ,T,uint8_t>, "TODO");
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <int64_t ,T,uint8_t>, "TODO");
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <uint8_t ,T,uint8_t>, "TODO");
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <uint16_t,T,uint8_t>, "TODO");
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <uint32_t,T,uint8_t>, "TODO");
+  m.def("UC_mask_mflow",          &UC_mask_mflow        <uint64_t,T,uint8_t>, "TODO");
+  m.def("UC_line_props",          &UC_line_props        <uint8_t>,         "TODO");
+  m.def("UC_line_mflow",          &UC_line_mflow        <T,uint8_t>,         "TODO");
 }