Repository: natcap/invest
Pull request #896 (2022-03-01 23:53:43)

Replace "upstream"/"downstream" with "upslope"/"downslope"
## Description
@jagoldstein requested via Slack that we replace "upstream" and "downstream" with "upslope" and "downslope" in the documentation, because the terms apply whether or not a pixel is actually in a stream. I also replaced these throughout the code and comments since that distinction might be helpful when we're thinking about the algorithms.

## Checklist
- [x] Updated HISTORY.rst (if these changes are user-facing)
- [ ] ~Updated the user's guide (if needed)~
- [x] Tested the affected models' UIs (if relevant)


jagoldstein: thanks @emlys ! To be clear, there may be a small handful of mentions of "downstream" that are correct in the UG. Here's an example from [SDR](http://releases.naturalcapitalproject.org/invest-userguide/latest/sdr.html#sediment-retention-services): "_This quantifies the difference in erosion reaching a stream, based on the changes in land cover/climate/etc present in the scenario, which provides a way of evaluating impacts to downstream uses such as reservoirs and drinking water._"

I was specifically referring to the definitions in `ARG_SPECS`, such as for Threshold Flow Accumulation. But either way, a sweeping replacement of these terms would still make the UG more accurate than before.
emlys: thanks @jagoldstein! I did not make any changes to the user's guide content yet. I meant to ask if you would like to make those UG edits since you're familiar with the appropriate contexts for using "slope" vs "stream"? All the changes I made here in the code & comments seem pretty clearly a "slope" context.
jagoldstein: no problem @emlys! I will take care of those terms in the body of the UG (which are NOT pulled from `ARG_SPECS`)

Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 81ba17516..d3f75298d 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -32,9 +32,12 @@
 
 .. :changelog:
 
-..
-  Unreleased Changes
-  ------------------
+Unreleased Changes
+------------------
+* RouteDEM
+    * Rename the arg ``calculate_downstream_distance`` to
+      ``calculate_downslope_distance``. This is meant to clarify that it
+      applies to pixels that are not part of a stream.
 
 3.10.2 (2022-02-08)
 -------------------
diff --git a/Makefile b/Makefile
index dd5d91b53..dfbd0f265 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ GIT_TEST_DATA_REPO_REV      := ac7023d684478485fea89c68f8f4154163541e1d
 
 GIT_UG_REPO                 := https://github.com/natcap/invest.users-guide
 GIT_UG_REPO_PATH            := doc/users-guide
-GIT_UG_REPO_REV             := cc1a43b46cc6ffb1de814553a8306d145e228394
+GIT_UG_REPO_REV             := 53f8eb03396ea2bd8daee167f0bae1243fe325e4
 
 ENV = "./env"
 ifeq ($(OS),Windows_NT)
diff --git a/src/natcap/invest/delineateit/delineateit.py b/src/natcap/invest/delineateit/delineateit.py
index 0ea64d9ec..fb614f213 100644
--- a/src/natcap/invest/delineateit/delineateit.py
+++ b/src/natcap/invest/delineateit/delineateit.py
@@ -151,8 +151,8 @@ def execute(args):
         args['snap_points'] (bool): Whether to snap point geometries to the
             nearest stream pixel.  If ``True``, ``args['flow_threshold']``
             and ``args['snap_distance']`` must also be defined.
-        args['flow_threshold'] (int):  The number of upstream cells that must
-            into a cell before it's considered part of a stream such that
+        args['flow_threshold'] (int):  The number of upslope cells that must
+            flow into a cell before it's considered part of a stream such that
             retention stops and the remaining export is exported to the stream.
             Used to define streams from the DEM.
         args['snap_distance'] (int):  Pixel Distance to Snap Outlet Points
diff --git a/src/natcap/invest/ndr/ndr.py b/src/natcap/invest/ndr/ndr.py
index 1eeaf1e3a..ebebfb15f 100644
--- a/src/natcap/invest/ndr/ndr.py
+++ b/src/natcap/invest/ndr/ndr.py
@@ -55,7 +55,7 @@ ARGS_SPEC = {
             }},
             "about": _(
                 "Map of runoff potential, the capacity to transport "
-                "nutrients downstream. This can be a quickflow index "
+                "nutrients downslope. This can be a quickflow index "
                 "or annual precipitation. Any units are allowed since "
                 "the values will be normalized by their average."),
             "name": _("nutrient runoff proxy")
@@ -257,7 +257,7 @@ def execute(args):
         args['results_suffix'] (string): (optional) a text field to append to
             all output files
         args['threshold_flow_accumulation']: a number representing the flow
-            accumulation in terms of upstream pixels.
+            accumulation in terms of upslope pixels.
         args['k_param'] (number): The Borselli k parameter. This is a
             calibration parameter that determines the shape of the
             relationship between hydrologic connectivity.
@@ -1198,7 +1198,7 @@ def s_bar_calculate(
 
 
 def d_up_calculation(s_bar_path, flow_accum_path, target_d_up_path):
-    """Calculate d_up = s_bar * sqrt(upstream area)."""
+    """Calculate d_up = s_bar * sqrt(upslope area)."""
     s_bar_info = pygeoprocessing.get_raster_info(s_bar_path)
     s_bar_nodata = s_bar_info['nodata'][0]
     flow_accum_nodata = pygeoprocessing.get_raster_info(
diff --git a/src/natcap/invest/ndr/ndr_core.pyx b/src/natcap/invest/ndr/ndr_core.pyx
index fd99032db..e0e8a3505 100644
--- a/src/natcap/invest/ndr/ndr_core.pyx
+++ b/src/natcap/invest/ndr/ndr_core.pyx
@@ -548,7 +548,7 @@ def ndr_eff_calculation(
                 else:
                     LOGGER.error('outflow_weight_sum %s', outflow_weight_sum)
                     raise Exception("got to a cell that has no outflow!")
-            # search upstream to see if we need to push a cell on the stack
+            # search upslope to see if we need to push a cell on the stack
             for i in range(8):
                 neighbor_col = col_offsets[i] + global_col
                 if neighbor_col < 0 or neighbor_col >= n_cols:
@@ -572,8 +572,8 @@ def ndr_eff_calculation(
                 to_process_flow_directions_raster.set(
                     neighbor_col, neighbor_row, neighbor_process_flow_dir)
                 if neighbor_process_flow_dir == 0:
-                    # if 0 then all downstream have been processed,
-                    # push on stack, otherwise another downstream pixel will
+                    # if 0 then all downslope have been processed,
+                    # push on stack, otherwise another downslope pixel will
                     # pick it up
                     processing_stack.push(neighbor_row*n_cols + neighbor_col)
     to_process_flow_directions_raster.close()
diff --git a/src/natcap/invest/routedem.py b/src/natcap/invest/routedem.py
index 5aca7ebe3..721cc081d 100644
--- a/src/natcap/invest/routedem.py
+++ b/src/natcap/invest/routedem.py
@@ -48,7 +48,7 @@ ARGS_SPEC = {
                     "display_name": "MFD",
                     "description": _(
                         "Flow off a pixel is modeled fractionally so that "
-                        "water is split among multiple downstream pixels")}
+                        "water is split among multiple downslope pixels")}
             },
             "about": _("The routing algorithm to use."),
             "name": _("routing algorithm")
@@ -80,7 +80,7 @@ ARGS_SPEC = {
                 f"{spec_utils.THRESHOLD_FLOW_ACCUMULATION['about']} "
                 "Required if Calculate Streams is selected.")
         },
-        "calculate_downstream_distance": {
+        "calculate_downslope_distance": {
             "type": "boolean",
             "required": False,
             "about": _(
@@ -104,7 +104,7 @@ _TARGET_SLOPE_FILE_PATTERN = 'slope%s.tif'
 _TARGET_FLOW_DIRECTION_FILE_PATTERN = 'flow_direction%s.tif'
 _FLOW_ACCUMULATION_FILE_PATTERN = 'flow_accumulation%s.tif'
 _STREAM_MASK_FILE_PATTERN = 'stream_mask%s.tif'
-_DOWNSTREAM_DISTANCE_FILE_PATTERN = 'downstream_distance%s.tif'
+_DOWNSLOPE_DISTANCE_FILE_PATTERN = 'downslope_distance%s.tif'
 
 _ROUTING_FUNCS = {
     'D8': {
@@ -127,7 +127,7 @@ def _threshold_flow(flow_accum_pixels, threshold, in_nodata, out_nodata):
 
     Args:
         flow_accum_pixels (numpy.ndarray): Array representing the number of
-            pixels upstream of a given pixel.
+            pixels upslope of a given pixel.
         threshold (int or float): The threshold above which we have a stream.
         in_nodata (int or float): The nodata value of the flow accumulation
             raster.
@@ -170,7 +170,7 @@ def execute(args):
             If not provided, band index 1 is assumed.
         args['algorithm'] (string): The routing algorithm to use.  Must be
             one of 'D8' or 'MFD' (case-insensitive). Required when calculating
-            flow direction, flow accumulation, stream threshold, and downstream
+            flow direction, flow accumulation, stream threshold, and downslope
             distance.
         args['calculate_flow_direction'] (bool): If True, model will calculate
             flow direction for the filled DEM.
@@ -183,11 +183,11 @@ def execute(args):
             ``args['threshold_flow_accumulation']``.  Only applies when
             args['calculate_flow_accumulation'] and
             args['calculate_flow_direction'] are True.
-        args['threshold_flow_accumulation'] (int): The number of upstream
+        args['threshold_flow_accumulation'] (int): The number of upslope
             cells that must flow into a cell before it's classified as a
             stream.
-        args['calculate_downstream_distance'] (bool): If True, and a stream
-            threshold is calculated, model will calculate a downstream
+        args['calculate_downslope_distance'] (bool): If True, and a stream
+            threshold is calculated, model will calculate a downslope
             distance raster in units of pixels. Only applies when
             args['calculate_flow_accumulation'],
             args['calculate_flow_direction'], and
@@ -322,18 +322,18 @@ def execute(args):
                         task_name=['stream_extraction_MFD'],
                         dependent_task_list=[flow_accum_task])
 
-                if ('calculate_downstream_distance' in args and
-                        bool(args['calculate_downstream_distance'])):
+                if ('calculate_downslope_distance' in args and
+                        bool(args['calculate_downslope_distance'])):
                     distance_path = os.path.join(
                         args['workspace_dir'],
-                        _DOWNSTREAM_DISTANCE_FILE_PATTERN % file_suffix)
+                        _DOWNSLOPE_DISTANCE_FILE_PATTERN % file_suffix)
                     graph.add_task(
                         routing_funcs['distance_to_channel'],
                         args=((flow_dir_path, 1),
                               (stream_mask_path, 1),
                               distance_path),
                         target_path_list=[distance_path],
-                        task_name='downstream_distance_%s' % algorithm,
+                        task_name='downslope_distance_%s' % algorithm,
                         dependent_task_list=[stream_threshold_task])
     graph.join()
 
diff --git a/src/natcap/invest/sdr/sdr.py b/src/natcap/invest/sdr/sdr.py
index 348a4c8a4..3ef82974d 100644
--- a/src/natcap/invest/sdr/sdr.py
+++ b/src/natcap/invest/sdr/sdr.py
@@ -229,7 +229,7 @@ def execute(args):
         args['biophysical_table_path'] (string): path to CSV file with
             biophysical information of each land use classes.  contain the
             fields 'usle_c' and 'usle_p'
-        args['threshold_flow_accumulation'] (number): number of upstream pixels
+        args['threshold_flow_accumulation'] (number): number of upslope pixels
             on the dem to threshold to a stream.
         args['k_param'] (number): k calibration parameter
         args['sdr_max'] (number): max value the SDR
@@ -736,7 +736,7 @@ def _calculate_ls_factor(
 
     Args:
         flow_accumulation_path (string): path to raster, pixel values are the
-            contributing upstream area at that cell. Pixel size is square.
+            contributing upslope area at that cell. Pixel size is square.
         slope_path (string): path to slope raster as a percent
         avg_aspect_path (string): The path to to raster of the weighted average
             of aspects based on proportional flow.
@@ -765,7 +765,7 @@ def _calculate_ls_factor(
 
         Args:
             percent_slope (numpy.ndarray): slope in percent
-            flow_accumulation (numpy.ndarray): upstream pixels
+            flow_accumulation (numpy.ndarray): upslope pixels
             avg_aspect (numpy.ndarray): the weighted average aspect from MFD
             l_max (float): max L factor, clamp to this value if L exceeds it
 
@@ -1137,7 +1137,7 @@ def _calculate_d_up(
     def d_up_op(w_bar, s_bar, flow_accumulation):
         """Calculate the d_up index.
 
-        w_bar * s_bar * sqrt(upstream area)
+        w_bar * s_bar * sqrt(upslope area)
 
         """
         valid_mask = (
@@ -1169,7 +1169,7 @@ def _calculate_d_up_bare(
     def d_up_op(s_bar, flow_accumulation):
         """Calculate the bare d_up index.
 
-        s_bar * sqrt(upstream area)
+        s_bar * sqrt(upslope area)
 
         """
         valid_mask = (
diff --git a/src/natcap/invest/sdr/sdr_core.pyx b/src/natcap/invest/sdr/sdr_core.pyx
index 74eed3c28..71116cbb4 100644
--- a/src/natcap/invest/sdr/sdr_core.pyx
+++ b/src/natcap/invest/sdr/sdr_core.pyx
@@ -372,22 +372,22 @@ def calculate_sediment_deposition(
     where:
 
     - ``p(i,j)`` is the proportion of flow from pixel ``i`` into pixel ``j``
-    - ``J`` is the set of pixels that are immediate upstream neighbors of
+    - ``J`` is the set of pixels that are immediate upslope neighbors of
       pixel ``i``
-    - ``K`` is the set of pixels that are immediate downstream neighbors of
+    - ``K`` is the set of pixels that are immediate downslope neighbors of
       pixel ``i``
     - ``E'`` is ``USLE * (1 - SDR)``, the amount of sediment loss from pixel
       ``i`` that doesn't reach a stream (``e_prime_path``)
     - ``SDR`` is the sediment delivery ratio (``sdr_path``)
 
-    ``f_i`` is recursively defined in terms of ``i``'s upstream neighbors.
+    ``f_i`` is recursively defined in terms of ``i``'s upslope neighbors.
     The algorithm begins from seed pixels that are local high points and so
-    have no upstream neighbors. It works downstream from each seed pixel,
-    only adding a pixel to the stack when all its upstream neighbors are
+    have no upslope neighbors. It works downslope from each seed pixel,
+    only adding a pixel to the stack when all its upslope neighbors are
     already calculated.
 
     Note that this function is designed to be used in the context of the SDR
-    model. Because the algorithm is recursive upstream and downstream of each
+    model. Because the algorithm is recursive upslope and downslope of each
     pixel, nodata values in the SDR input would propagate along the flow path.
     This case is not handled because we assume the SDR and flow dir inputs
     will come from the SDR model and have nodata in the same places.
@@ -451,7 +451,7 @@ def calculate_sediment_deposition(
     cdef int flow_val, neighbor_flow_val, ds_neighbor_flow_val
     cdef int flow_weight, neighbor_flow_weight
     cdef float flow_sum, neighbor_flow_sum
-    cdef float downstream_sdr_weighted_sum, sdr_i, sdr_j
+    cdef float downslope_sdr_weighted_sum, sdr_i, sdr_j
     cdef float p_j, p_val
 
     for offset_dict in pygeoprocessing.iterblocks(
@@ -501,16 +501,16 @@ def calculate_sediment_deposition(
                     processing_stack.push(seed_row * n_cols + seed_col)
 
                 while processing_stack.size() > 0:
-                    # loop invariant: cell has all upstream neighbors
+                    # loop invariant: cell has all upslope neighbors
                     # processed. this is true for seed pixels because they
-                    # have no upstream neighbors.
+                    # have no upslope neighbors.
                     flat_index = processing_stack.top()
                     processing_stack.pop()
                     global_row = flat_index // n_cols
                     global_col = flat_index % n_cols
 
                     # (sum over j ∈ J of f_j * p(i,j) in the equation for r_i)
-                    # calculate the upstream f_j contribution to this pixel,
+                    # calculate the upslope f_j contribution to this pixel,
                     # the weighted sum of flux flowing onto this pixel from
                     # all neighbors
                     f_j_weighted_sum = 0
@@ -547,11 +547,11 @@ def calculate_sediment_deposition(
                             # flow proportion
                             f_j_weighted_sum += p_val * f_j
 
-                    # calculate sum of SDR values of immediate downstream 
+                    # calculate sum of SDR values of immediate downslope
                     # neighbors, weighted by proportion of flow into each
                     # neighbor
                     # (sum over k ∈ K of SDR_k * p(i,k) in the equation above)
-                    downstream_sdr_weighted_sum = 0
+                    downslope_sdr_weighted_sum = 0
                     flow_val = <int>mfd_flow_direction_raster.get(
                         global_col, global_row)
                     flow_sum = 0
@@ -567,7 +567,7 @@ def calculate_sediment_deposition(
                         neighbor_col = global_col + COL_OFFSETS[j]
                         if neighbor_col < 0 or neighbor_col >= n_cols:
                             continue
-                        # if it is a downstream neighbor, add to the sum and
+                        # if it is a downslope neighbor, add to the sum and
                         # check if it can be pushed onto the stack yet
                         flow_weight = (flow_val >> (j*4)) & 0xF
                         if flow_weight > 0:
@@ -580,15 +580,15 @@ def calculate_sediment_deposition(
                                 # is the last step on which to retain sediment
                                 sdr_j = 1
                             p_j = flow_weight / flow_sum
-                            downstream_sdr_weighted_sum += sdr_j * p_j
+                            downslope_sdr_weighted_sum += sdr_j * p_j
 
                             # check if we can add neighbor j to the stack yet
                             #
-                            # if there is a downstream neighbor it
+                            # if there is a downslope neighbor it
                             # couldn't have been pushed on the processing
-                            # stack yet, because the upstream was just
+                            # stack yet, because the upslope was just
                             # completed
-                            upstream_neighbors_processed = 1
+                            upslope_neighbors_processed = 1
                             # iterate over each neighbor-of-neighbor
                             for k in range(8):
                                 # no need to push the one we're currently
@@ -605,7 +605,7 @@ def calculate_sediment_deposition(
                                     neighbor_col + COL_OFFSETS[k])
                                 if ds_neighbor_col < 0 or ds_neighbor_col >= n_cols:
                                     continue
-                                # if any upstream neighbor of j hasn't been
+                                # if any upslope neighbor of j hasn't been
                                 # calculated, we can't push j onto the stack
                                 # yet
                                 ds_neighbor_flow_val = (
@@ -616,11 +616,11 @@ def calculate_sediment_deposition(
                                     if (sediment_deposition_raster.get(
                                             ds_neighbor_col, ds_neighbor_row) ==
                                             target_nodata):
-                                        upstream_neighbors_processed = 0
+                                        upslope_neighbors_processed = 0
                                         break
-                            # if all upstream neighbors of neighbor j are
+                            # if all upslope neighbors of neighbor j are
                             # processed, we can push j onto the stack.
-                            if upstream_neighbors_processed:
+                            if upslope_neighbors_processed:
                                 processing_stack.push(
                                     neighbor_row * n_cols +
                                     neighbor_col)
@@ -633,14 +633,14 @@ def calculate_sediment_deposition(
                     if e_prime_i == e_prime_nodata:
                         continue
 
-                    if downstream_sdr_weighted_sum < sdr_i:
+                    if downslope_sdr_weighted_sum < sdr_i:
                         # i think this happens because of our low resolution
                         # flow direction, it's okay to zero out.
-                        downstream_sdr_weighted_sum = sdr_i
+                        downslope_sdr_weighted_sum = sdr_i
 
                     # these correspond to the full equations for
                     # dr_i, r_i, and f_i given in the docstring
-                    dr_i = (downstream_sdr_weighted_sum - sdr_i) / (1 - sdr_i)
+                    dr_i = (downslope_sdr_weighted_sum - sdr_i) / (1 - sdr_i)
                     r_i = dr_i * (e_prime_i + f_j_weighted_sum)
                     f_i = (1 - dr_i) * (e_prime_i + f_j_weighted_sum)
 
diff --git a/src/natcap/invest/seasonal_water_yield/seasonal_water_yield.py b/src/natcap/invest/seasonal_water_yield/seasonal_water_yield.py
index 441eaf100..072f61c5f 100644
--- a/src/natcap/invest/seasonal_water_yield/seasonal_water_yield.py
+++ b/src/natcap/invest/seasonal_water_yield/seasonal_water_yield.py
@@ -329,7 +329,7 @@ def execute(args):
         args['results_suffix'] (string): (optional) string to append to any
             output files
         args['threshold_flow_accumulation'] (number): used when classifying
-            stream pixels from the DEM by thresholding the number of upstream
+            stream pixels from the DEM by thresholding the number of upslope
             cells that must flow into a cell before it's considered
             part of a stream.
         args['et0_dir'] (string): required if
diff --git a/src/natcap/invest/seasonal_water_yield/seasonal_water_yield_core.pyx b/src/natcap/invest/seasonal_water_yield/seasonal_water_yield_core.pyx
index d5b2f79bf..1ed1e9f46 100644
--- a/src/natcap/invest/seasonal_water_yield/seasonal_water_yield_core.pyx
+++ b/src/natcap/invest/seasonal_water_yield/seasonal_water_yield_core.pyx
@@ -403,7 +403,7 @@ cpdef calculate_local_recharge(
         target_li_avail_path (str): created by this call, path to raster
             indicating available recharge to a pixel.
         target_l_sum_avail_path (str): created by this call, the recursive
-            upstream accumulation of target_li_avail_path.
+            upslope accumulation of target_li_avail_path.
         target_aet_path (str): created by this call, the annual actual
             evapotranspiration.
 
@@ -560,13 +560,13 @@ cpdef calculate_local_recharge(
 
                     # Equation 7, calculate L_sum_avail_i if possible, skip
                     # otherwise
-                    upstream_defined = 1
+                    upslope_defined = 1
                     # initialize to 0 so we indicate we haven't tracked any
                     # mfd values yet
                     j_neighbor_end_index = 0
                     mfd_dir_sum = 0
                     for n_dir in xrange(8):
-                        if not upstream_defined:
+                        if not upslope_defined:
                             break
                         # searching around the pattern:
                         # 321
@@ -581,11 +581,11 @@ cpdef calculate_local_recharge(
                                 4 * FLOW_DIR_REVERSE_DIRECTION[n_dir])) & 0xF
                         if p_ij_base:
                             mfd_dir_sum += p_ij_base
-                            # pixel flows inward, check upstream
+                            # pixel flows inward, check upslope
                             l_sum_avail_j = target_l_sum_avail_raster.get(
                                 xj, yj)
                             if is_close(l_sum_avail_j, target_nodata):
-                                upstream_defined = 0
+                                upslope_defined = 0
                                 break
                             l_avail_j = target_li_avail_raster.get(
                                 xj, yj)
@@ -596,11 +596,11 @@ cpdef calculate_local_recharge(
                     # calculate l_sum_avail_i by summing all the valid
                     # directions then normalizing by the sum of the mfd
                     # direction weights (Equation 8)
-                    if upstream_defined:
+                    if upslope_defined:
                         l_sum_avail_i = 0.0
                         # Equation 7
                         if j_neighbor_end_index > 0:
-                            # we can have no upstream, and then why would we
+                            # we can have no upslope, and then why would we
                             # divide?
                             for index in range(j_neighbor_end_index):
                                 l_sum_avail_i += mfd_direction_array[index]
@@ -689,12 +689,12 @@ def route_baseflow_sum(
         l_path (string): path to local recharge raster.
         l_avail_path (string): path to local recharge raster that shows
             recharge available to the pixel.
-        l_sum_path (string): path to upstream sum of l_path.
+        l_sum_path (string): path to upslope sum of l_path.
         stream_path (string): path to stream raster, 1 stream, 0 no stream,
             and nodata.
         target_b_path (string): path to created raster for per-pixel baseflow.
         target_b_sum_path (string): path to created raster for per-pixel
-            upstream sum of baseflow.
+            upslope sum of baseflow.
 
     Returns:
         None.
@@ -792,13 +792,13 @@ def route_baseflow_sum(
 
                     b_sum_i = 0.0
                     mfd_dir_sum = 0
-                    downstream_defined = 1
+                    downslope_defined = 1
                     flow_dir_i = <int>flow_dir_mfd_raster.get(xi, yi)
                     if flow_dir_i == flow_dir_nodata:
                         LOGGER.error("flow dir nodata? this makes no sense")
                         continue
                     for n_dir in xrange(8):
-                        if not downstream_defined:
+                        if not downslope_defined:
                             break
                         # searching around the pattern:
                         # 321
@@ -819,7 +819,7 @@ def route_baseflow_sum(
                             else:
                                 b_sum_j = target_b_sum_raster.get(xj, yj)
                                 if is_close(b_sum_j, target_nodata):
-                                    downstream_defined = 0
+                                    downslope_defined = 0
                                     break
                                 l_j = l_raster.get(xj, yj)
                                 l_avail_j = l_avail_raster.get(xj, yj)
@@ -832,7 +832,7 @@ def route_baseflow_sum(
                                 else:
                                     b_sum_i += p_ij_base
 
-                    if not downstream_defined:
+                    if not downslope_defined:
                         continue
                     l_sum_i = l_sum_raster.get(xi, yi)
                     if mfd_dir_sum > 0:
@@ -848,7 +848,7 @@ def route_baseflow_sum(
                     current_pixel += 1
 
                     for n_dir in xrange(8):
-                        # searching upstream for pixels that flow in
+                        # searching upslope for pixels that flow in
                         # 321
                         # 4x0
                         # 567
diff --git a/src/natcap/invest/spec_utils.py b/src/natcap/invest/spec_utils.py
index 416bc5997..c97c677d4 100644
--- a/src/natcap/invest/spec_utils.py
+++ b/src/natcap/invest/spec_utils.py
@@ -119,7 +119,7 @@ THRESHOLD_FLOW_ACCUMULATION = {
     "type": "number",
     "units": u.pixel,
     "about": _(
-        "The number of upstream pixels that must flow into a pixel "
+        "The number of upslope pixels that must flow into a pixel "
         "before it is classified as a stream."),
     "name": _("threshold flow accumulation")
 }
diff --git a/src/natcap/invest/ui/delineateit.py b/src/natcap/invest/ui/delineateit.py
index fa4e165b8..1f4369ed4 100644
--- a/src/natcap/invest/ui/delineateit.py
+++ b/src/natcap/invest/ui/delineateit.py
@@ -62,7 +62,7 @@ class Delineateit(model.InVESTModel):
         self.flow_threshold = inputs.Text(
             args_key='flow_threshold',
             helptext=(
-                "The number of upstream cells that must flow into a "
+                "The number of upslope cells that must flow into a "
                 "cell before it's considered part of a stream such "
                 "that retention stops and the remaining export is "
                 "exported to the stream.  Used to define streams from "
diff --git a/src/natcap/invest/ui/ndr.py b/src/natcap/invest/ui/ndr.py
index 93dfd58fa..1bd0dd2f1 100644
--- a/src/natcap/invest/ui/ndr.py
+++ b/src/natcap/invest/ui/ndr.py
@@ -82,7 +82,7 @@ class Nutrient(model.InVESTModel):
         self.threshold_flow_accumulation = inputs.Text(
             args_key='threshold_flow_accumulation',
             helptext=(
-                "The number of upstream cells that must flow into a "
+                "The number of upslope cells that must flow into a "
                 "cell before it's considered part of a stream such "
                 "that retention stops and the remaining export is "
                 "exported to the stream.  Used to define streams from "
diff --git a/src/natcap/invest/ui/routedem.py b/src/natcap/invest/ui/routedem.py
index bdf7890f5..dec4e0fa6 100644
--- a/src/natcap/invest/ui/routedem.py
+++ b/src/natcap/invest/ui/routedem.py
@@ -68,21 +68,21 @@ class RouteDEM(model.InVESTModel):
         self.threshold_flow_accumulation = inputs.Text(
             args_key='threshold_flow_accumulation',
             helptext=(
-                "The number of upstream cells that must flow into a "
+                "The number of upslope cells that must flow into a "
                 "cell before it's classified as a stream."),
             interactive=False,
             label='Threshold Flow Accumulation Limit',
             validator=self.validator)
         self.add_input(self.threshold_flow_accumulation)
-        self.calculate_downstream_distance = inputs.Checkbox(
-            args_key='calculate_downstream_distance',
+        self.calculate_downslope_distance = inputs.Checkbox(
+            args_key='calculate_downslope_distance',
             helptext=(
-                "If selected, creates a downstream distance raster "
+                "If selected, creates a downslope distance raster "
                 "based on the thresholded flow accumulation stream "
                 "classification."),
             interactive=False,
             label='Calculate Distance to stream')
-        self.add_input(self.calculate_downstream_distance)
+        self.add_input(self.calculate_downslope_distance)
 
         # Set interactivity, requirement as input sufficiency changes
         self.calculate_flow_direction.sufficiency_changed.connect(
@@ -92,7 +92,7 @@ class RouteDEM(model.InVESTModel):
         self.calculate_stream_threshold.sufficiency_changed.connect(
             self.threshold_flow_accumulation.set_interactive)
         self.calculate_stream_threshold.sufficiency_changed.connect(
-            self.calculate_downstream_distance.set_interactive)
+            self.calculate_downslope_distance.set_interactive)
 
     def assemble_args(self):
         args = {
@@ -110,7 +110,7 @@ class RouteDEM(model.InVESTModel):
                 self.calculate_stream_threshold.value(),
             self.threshold_flow_accumulation.args_key:
                 self.threshold_flow_accumulation.value(),
-            self.calculate_downstream_distance.args_key:
-                self.calculate_downstream_distance.value(),
+            self.calculate_downslope_distance.args_key:
+                self.calculate_downslope_distance.value(),
         }
         return args
diff --git a/src/natcap/invest/ui/sdr.py b/src/natcap/invest/ui/sdr.py
index 98d9786a2..d5eae3acc 100644
--- a/src/natcap/invest/ui/sdr.py
+++ b/src/natcap/invest/ui/sdr.py
@@ -86,7 +86,7 @@ class SDR(model.InVESTModel):
         self.threshold_flow_accumulation = inputs.Text(
             args_key='threshold_flow_accumulation',
             helptext=(
-                "The number of upstream cells that must flow into a "
+                "The number of upslope cells that must flow into a "
                 "cell before it's considered part of a stream such "
                 "that retention stops and the remaining export is "
                 "exported to the stream.  Used to define streams from "
diff --git a/src/natcap/invest/ui/seasonal_water_yield.py b/src/natcap/invest/ui/seasonal_water_yield.py
index 383174e13..79c122f43 100644
--- a/src/natcap/invest/ui/seasonal_water_yield.py
+++ b/src/natcap/invest/ui/seasonal_water_yield.py
@@ -17,7 +17,7 @@ class SeasonalWaterYield(model.InVESTModel):
         self.threshold_flow_accumulation = inputs.Text(
             args_key='threshold_flow_accumulation',
             helptext=(
-                "The number of upstream cells that must flow into a "
+                "The number of upslope cells that must flow into a "
                 "cell before it's considered part of a stream such "
                 "that retention stops and the remaining export is "
                 "exported to the stream.  Used to define streams from "
diff --git a/workbench/src/renderer/ui_config.js b/workbench/src/renderer/ui_config.js
index 8472705c6..7cbf2b58e 100644
--- a/workbench/src/renderer/ui_config.js
+++ b/workbench/src/renderer/ui_config.js
@@ -255,13 +255,13 @@ const uiSpec = {
       ['algorithm'],
       ['calculate_flow_direction'],
       ['calculate_flow_accumulation'],
-      ['calculate_stream_threshold', 'threshold_flow_accumulation', 'calculate_downstream_distance'],
+      ['calculate_stream_threshold', 'threshold_flow_accumulation', 'calculate_downslope_distance'],
     ],
     enabledFunctions: {
       calculate_flow_accumulation: isSufficient.bind(null, 'calculate_flow_direction'),
       calculate_stream_threshold: isSufficient.bind(null, 'calculate_flow_accumulation'),
       threshold_flow_accumulation: isSufficient.bind(null, 'calculate_stream_threshold'),
-      calculate_downstream_distance: isSufficient.bind(null, 'calculate_stream_threshold'),
+      calculate_downslope_distance: isSufficient.bind(null, 'calculate_stream_threshold'),
     },
   },
   scenario_generator_proximity: {


Test patch:
diff --git a/tests/test_routedem.py b/tests/test_routedem.py
index 4324a44cc..bb1a915e0 100644
--- a/tests/test_routedem.py
+++ b/tests/test_routedem.py
@@ -181,7 +181,7 @@ class RouteDEMTests(unittest.TestCase):
             'calculate_flow_direction': True,
             'calculate_flow_accumulation': True,
             'calculate_stream_threshold': True,
-            'calculate_downstream_distance': True,
+            'calculate_downslope_distance': True,
             'calculate_slope': True,
             'threshold_flow_accumulation': 4,
         }
@@ -190,7 +190,7 @@ class RouteDEMTests(unittest.TestCase):
         routedem.execute(args)
 
         for expected_file in (
-                'downstream_distance_foo.tif',
+                'downslope_distance_foo.tif',
                 'filled_foo.tif',
                 'flow_accumulation_foo.tif',
                 'flow_direction_foo.tif',
@@ -248,19 +248,19 @@ class RouteDEMTests(unittest.TestCase):
                 'flow_direction_foo.tif')).ReadAsArray(),
             rtol=0, atol=1e-6)
 
-        expected_downstream_distance = numpy.empty(
+        expected_downslope_distance = numpy.empty(
             (10, 9), dtype=numpy.float64)
-        expected_downstream_distance[:, 0:5] = numpy.flipud(numpy.arange(5))
-        expected_downstream_distance[2:, 5:] = numpy.arange(1, 5)
-        expected_downstream_distance[0, 5:] = numpy.arange(4)
-        expected_downstream_distance[1, 5] = 1
-        expected_downstream_distance[1, 6:] = numpy.arange(1, 4) + 0.41421356
+        expected_downslope_distance[:, 0:5] = numpy.flipud(numpy.arange(5))
+        expected_downslope_distance[2:, 5:] = numpy.arange(1, 5)
+        expected_downslope_distance[0, 5:] = numpy.arange(4)
+        expected_downslope_distance[1, 5] = 1
+        expected_downslope_distance[1, 6:] = numpy.arange(1, 4) + 0.41421356
 
         numpy.testing.assert_allclose(
-            expected_downstream_distance,
+            expected_downslope_distance,
             gdal.OpenEx(os.path.join(
                 args['workspace_dir'], 
-                'downstream_distance_foo.tif')).ReadAsArray(),
+                'downslope_distance_foo.tif')).ReadAsArray(),
             rtol=0, atol=1e-6)
 
     def test_routedem_mfd(self):
@@ -275,7 +275,7 @@ class RouteDEMTests(unittest.TestCase):
             'calculate_flow_direction': True,
             'calculate_flow_accumulation': True,
             'calculate_stream_threshold': True,
-            'calculate_downstream_distance': True,
+            'calculate_downslope_distance': True,
             'calculate_slope': False,
             'threshold_flow_accumulation': 4,
         }
@@ -305,7 +305,7 @@ class RouteDEMTests(unittest.TestCase):
         for filename, expected_sum in (
                 ('flow_accumulation_foo.tif', 678.94551294),
                 ('flow_direction_foo.tif', 40968303668.0),
-                ('downstream_distance_foo.tif', 162.28624753707527)):
+                ('downslope_distance_foo.tif', 162.28624753707527)):
             raster_path = os.path.join(args['workspace_dir'], filename)
             raster = gdal.OpenEx(raster_path)
             if raster is None: