CLI reference: lsdtt-divide-migration
Quantifies drainage‑divide migration proxies from user‑supplied outlet points. The tool builds flow routing and a junction/channel network, delineates a basin from each outlet, groups those basins into comparison units, extracts the divides separating each pair of adjacent units, then measures every channel head against the nearest divide: elevation, chi, relief, and gradient.
The workflow follows DivideTools (Forte & Whipple), reimplemented natively in C++. Interactive selection (ginput) is deliberately replaced by outlet files and attribute fields, so a run is fully reproducible and testable.
The interpretation is the standard one: across a divide, the side whose channel heads sit at lower elevation, with higher gradient and relief and lower chi, is the aggressor — the divide is expected to migrate away from it.
Usage
lsdtt-divide-migration <path> <parameter_file>
lsdtt-divide-migration <parameter_file> # path defaults to ./
lsdtt-divide-migration -h # print help and exit
lsdtt-divide-migration -h <parameter> # full guidance on one parameter
lsdtt-divide-migration -v # print version and exit
lsdtt-divide-migration -c # print citation and exit
Parameter file format
# Lines beginning with # are comments.
key: value
Status of this tool
lsdtt-divide-migration is experimental. Every parameter it accepts is live — there are no declared‑but‑inert parameters. The four pair‑summary controls (write_pair_summary_csv, pair_uncertainty_method, bootstrap_replicates, bootstrap_seed) became active on 2026‑07‑31 and are documented under Pair‑summary statistics below.
Divide fragments are merged into continuous polylines (see Outputs below). Two units that meet along two genuinely separate stretches of divide still produce two features sharing one pair_id — that is correct, not a failure to merge.
Unit pairs are found from basin adjacency (2026‑07‑31): a single pass over the basin cells records which basins touch on the grid, and only those pairs are handed to the divide extraction, so the cost no longer grows with the square of the outlet count. Contact is tested eight‑connected, which is how the divide tracing itself sees the grid — basins that meet only at a cell corner can still share a divide — and it is a superset of the pairs that can yield a divide, so the results are exactly what the previous all‑vs‑all loop produced.
One design limit is worth knowing: nearest_divide_method has only one implementation (euclidean).
Pipeline overview
| Phase | Description |
|---|---|
| 1 | Open DEM; pit‑fill unless raster_is_filled |
| 2 | Build flow routing and the junction/channel network |
| 3 | Load outlet points, reproject into the raster CRS, snap them to channels |
| 4 | Resolve each outlet to a unit (cluster label, or one unit per outlet) |
| 5 | Delineate a basin per outlet; apply outlet_control_mode |
| 6 | Extract the interfluvial divide between every adjacent pair of units |
| 7 | Extract channel heads and integrate chi with chi = 0 at each outlet |
| 8 | Match each head to the nearest point on a divide bounding its own unit; measure relief and gradient |
| 9 | Optionally summarise each pair side against side: means, A − B deltas, uncertainty |
| 10 | Write divide vectors, channel‑head vectors, the metrics CSV and the pair summary |
Units, pairs, and what actually gets compared
A unit is the thing a divide is measured between:
- with
outlet_cluster_fieldset, all outlets sharing a label form one unit; - without it, every outlet is its own unit.
Pairs are formed only where two units genuinely share a divide in the DEM; units that do not touch produce no record. Each divide feature carries pair_id as "<lower_unit>|<higher_unit>", e.g. 1|2.
A channel head is only ever matched to a divide that bounds its own unit. A head in unit 3 will never be assigned to the 1|2 divide, even if that divide is far closer in space.
Input / output parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
read_path |
string | ./ |
Directory containing the input DEM. |
read_fname |
string | (required) | Input raster filename, with extension (e.g. my_dem.tif). |
write_path |
string | same as read_path |
Directory for output files. |
write_fname |
string | input stem | Output filename prefix (no path, no extension). |
Threading and flow routing
| Parameter | Type | Default | Description |
|---|---|---|---|
n_threads |
int | 0 |
Number of OpenMP threads. 0 uses all available hardware threads. |
boundary_conditions |
string | oooo |
Four‑character N, E, S, W string. o = open, n = no‑flux, p = periodic, b = forced outlet. |
raster_is_filled |
bool | false |
If false, fill_pits() runs before flow routing. |
fill_min_slope |
float | 0.0001 |
Minimum slope imposed by pit filling. Used only when raster_is_filled is false. |
threshold_contributing_pixels |
int | 1000 |
Contributing‑pixel threshold defining channel heads. Must be > 0. Lower it for a denser network and more heads — this is the main control on how many rows the metrics CSV has. |
Outlets and clusters
| Parameter | Type | Default | Description |
|---|---|---|---|
basin_outlet_fname |
string | NULL |
Required. Point dataset of outlet locations: CSV with latitude/longitude columns, or a point vector (FGB, GPKG, shapefile). Points are reprojected into the raster CRS on load. At least two must land inside the DEM. |
outlet_cluster_field |
string | NULL |
Attribute field holding cluster labels. NULL or empty means one unit per outlet. |
outlet_cluster_mode |
string | auto |
How labels are interpreted: auto groups numerically when every label parses as an integer and as text otherwise; integer requires integer labels and rejects the run if any is not; string always groups on the raw text. auto and string differ only when labels look numeric — e.g. 01 and 1 are one group under auto, two under string. |
basin_outlet_snapping_method |
string | downstream |
none (use the point as given), downstream (walk downstream to the first cell above the threshold), expanding_square (search outward for the nearest such cell), or largest_area (the cell with the most contributing pixels in the square of half-width basin_outlet_snap_max_search_radius_pixels, if it is above the threshold). |
basin_outlet_contributing_pixels_threshold |
int | 1000 |
Contributing‑pixel threshold used when snapping. Higher values snap outlets onto larger channels. Must be > 0. |
basin_outlet_snap_min_search_radius_pixels |
int | 0 |
Minimum expanding‑square search radius. expanding_square only. |
basin_outlet_snap_max_search_radius_pixels |
int | 25 |
Maximum expanding‑square search radius, and the square's half-width for largest_area. Must be ≥ the minimum. expanding_square and largest_area only. |
Outlets that fail to snap are dropped with a message naming the point and the reason; the run continues on the remainder.
Chi and base‑level control
| Parameter | Type | Default | Description |
|---|---|---|---|
chi_A0 |
float | 1.0 |
Reference drainage area A₀ (m²) in chi = ∫(A₀/A)^(m/n) ds. Leave at 1.0 unless you know why: the gradient of a chi-elevation profile equals k_sn only when A0 = 1 m²; any other value silently rescales it by A0^(m/n) (≈631× at A0 = 1e6, m/n = 0.45). |
m_over_n |
float | 0.45 |
Reference concavity θ = m/n. Must be > 0. Constrain it with lsdtt-chi-analysis and supply the result here. |
outlet_control_mode |
string | none |
Base‑level treatment — see below. |
outlet_control_min_elevation |
float | -9999 |
Elevation floor (m). Used only when outlet_control_mode: elevation. |
Chi is always integrated with chi = 0 at each outlet. Chi values are only comparable across a divide when the basins share a base level, which is what the control modes are for.
| Mode | Behaviour |
|---|---|
none (default) |
Every delineated basin is kept, including basins whose upslope area touches the DEM edge. |
elevation |
Outlets below outlet_control_min_elevation are dropped before basin selection. |
max_out_elevation |
The highest selected outlet elevation becomes a common datum: each network is truncated below it and chi is re‑zeroed at each retained fragment. A basin can legitimately break into several fragments. |
complete_only |
Basins whose upslope area touches nodata or the DEM edge are dropped. |
Prefer complete_only or max_out_elevation for any cross‑divide chi comparison on a real DEM. Under none, a basin clipped by the DEM edge has a truncated network and its chi is not comparable with a complete neighbour's.
Divide metrics
| Parameter | Type | Default | Description |
|---|---|---|---|
nearest_divide_method |
string | euclidean |
How heads are linked to divide geometry. Only euclidean is implemented: straight‑line distance in map units to the nearest point on a divide segment (projected onto the segment, not snapped to a vertex). |
divide_elevation_m is sampled from the routed (pit‑filled) DEM, as is head_elevation_m, so relief_m never mixes surfaces. Both are in the raster's vertical units.
Edge cases are reported rather than hidden:
- a head lying on its divide gives
distance_to_divide_m≈ 0 andgradient_to_divide= NaN, never an infinity; - negative relief (a head above the nearest divide point) is passed through unchanged — it is a real signal, not an error;
- a nearest point falling on nodata still reports
distance_to_divide_mandpair_id_nearest, with the elevation‑derived fields NaN; - a head whose unit borders no divide keeps NaN divide fields and an empty
pair_id_nearest.
Outputs
| Parameter | Type | Default | Output file |
|---|---|---|---|
write_divide_vectors |
bool | true |
<write_fname>_divides.fgb | .gpkg — divide segments as polylines. |
write_channel_heads |
bool | true |
<write_fname>_channel_heads.fgb | .gpkg — channel heads as points, with the full metric set. |
write_metrics_csv |
bool | true |
<write_fname>_divide_migration_metrics.csv — the same metrics as a flat table. |
write_pair_summary_csv |
bool | false |
<write_fname>_divide_pair_summary.csv — one side‑vs‑side row per divide pair. See Pair‑summary statistics below. |
write_vector_format |
string | flatgeobuf |
flatgeobuf (or fgb) or gpkg. Applies to both vector outputs. |
Three provenance files are always written regardless of the switches above: <write_fname>_README.html and <write_fname>_README.csv (the parameter reference for this run) and <write_fname>_ingested_params.param (every parameter as actually parsed, including defaults you did not set — useful for reproducing a run exactly).
Divide vector attributes
| Field | Type | Description |
|---|---|---|
pair_id |
string | "<unit_a>\|<unit_b>", units in ascending order. |
unit_a, unit_b |
int | The two units this divide separates. |
junc_a, junc_b |
int | Junction numbers of the two source basins, or -1 when the feature was merged from more than one fragment (see below). |
segment_id |
int | Unique per segment within the run. |
n_fragments |
int | How many extracted fragments were merged into this feature. 1 for a divide between a single pair of basins. |
n_vertices |
int | Vertex count of the polyline. |
length_m |
double | Segment length in map units, measured on the polyline as written. |
A unit made of several basins borders a neighbouring unit through one fragment
per adjacent basin pair. Fragments that are contiguous in space — endpoints
within one cell diagonal of each other — are merged into a single polyline, so
one feature is one divide and length_m is the whole along‑divide length. Two
units that meet along two genuinely separate stretches of divide (wrapping
around a third unit, say) still get one feature per stretch: sharing a pair_id
does not merge anything on its own.
Because junc_a / junc_b describe one basin pair, they are only meaningful on
a feature with n_fragments = 1; a merged feature reports -1 for both rather
than one contributing basin's junction as though it described the whole line.
Channel‑head attributes (vector and CSV)
| Field | Description |
|---|---|
head_id |
Unique per head within the run. |
outlet_id |
point_id of the outlet whose basin contains this head. |
unit_id |
Unit that basin belongs to. |
pair_id_nearest |
pair_id of the nearest divide; empty if unmatched. |
head_x, head_y |
Head position in the raster CRS. |
head_elevation_m |
Head elevation, from the routed DEM. |
chi |
Chi at the head, zeroed at its outlet. |
nearest_divide_x, nearest_divide_y |
Nearest point on the divide. |
divide_elevation_m |
Elevation there, from the routed DEM. |
distance_to_divide_m |
Straight‑line distance, map units. |
relief_m |
divide_elevation_m − head_elevation_m. May be negative. |
gradient_to_divide |
relief_m / distance_to_divide_m. NaN at zero distance. |
Pair‑summary statistics
Set write_pair_summary_csv: true and the run writes <write_fname>_divide_pair_summary.csv: one row per divide pair, comparing the channel heads on one side against those on the other. This is the table you interpret; the per‑head CSV is the evidence behind it.
The row set comes from the divides, not from the heads: every pair present in the _divides layer gets exactly one row, so the two outputs always list the same pairs. A pair whose divide was extracted but which attracted no channel heads on either side still gets a row, with n_heads_a and n_heads_b at 0 and every mean, delta and uncertainty nan — the same convention a single empty side follows. Two separate stretches of divide between the same unit pair share one pair_id and are summarised together, in one row.
| Parameter | Type | Default | Description |
|---|---|---|---|
write_pair_summary_csv |
bool | false |
Write the pair‑summary CSV. |
pair_uncertainty_method |
string | std_err |
std_dev, std_err, bootstrap or ttest — see below. |
bootstrap_replicates |
int | 1000 |
Resamples per side. Read only when the method is bootstrap. Must be > 0. |
bootstrap_seed |
int | 5489 |
Seed for the bootstrap. Read only when the method is bootstrap. |
The sign convention: side A is the lower‑numbered unit
Side A is always the lower‑numbered unit of the pair — the same ordering the pair_id itself uses, so pair 3|7 has unit_a = 3 and unit_b = 7. Every delta is
delta_<metric> = mean_<metric>_a − mean_<metric>_b
This is fixed by the unit ids, never by the order heads happen to arrive in, so a column cannot silently change sign between runs. The run prints a reminder on stdout.
Rows, grouping and sample sizes
Heads are grouped by pair_id_nearest. A head that matched no divide (empty pair_id_nearest) takes no part in any row, and a head is only ever counted on the side its own unit_id names.
n_heads_a / n_heads_b count the heads assigned to each side. They are not the sample size of every mean: relief_m and gradient_to_divide are legitimately NaN for some heads (the nearest divide point fell on nodata, or the head sits on its divide), and those heads are skipped rather than allowed to turn a side mean into NaN. Each metric therefore carries its own valid‑value count, n_<metric>_a / n_<metric>_b, which is the n its mean and its uncertainty were computed from. If n_relief_a is well below n_heads_a, treat that side's relief with caution.
A pair in which one side has no heads still gets a row, with that side's counts at 0 and its mean, the delta and the uncertainty NaN. The divide exists in the vector output, so a row saying one side is empty is more useful than a divide that vanishes from the table.
What each uncertainty method reports
The column is uncertainty_<metric> — one number per metric — and the row's uncertainty_method field always records which of the four produced it.
DivideTools' AcrossDivide.m reports a dispersion for each side and then asks whether the two intervals overlap. This tool has one column, so each option is the equivalent statistic of the delta. The per‑side ingredients are DivideTools' (the n−1 sample standard deviation, s/√n, a bootstrap of the mean, and Welch's unequal‑variance t‑test); combining them into a single number per metric is this tool's choice, not something inherited from DivideTools.
pair_uncertainty_method |
The uncertainty_* columns hold |
Compare it with |
|---|---|---|
std_dev |
Pooled standard deviation of the two sides, sqrt(((na−1)sa² + (nb−1)sb²)/(na+nb−2)). A spread of the head populations, not of the delta — it does not shrink as you add heads. |
Nothing directly; use it to see how variable the two sides are. |
std_err (default) |
Standard error of the difference of means, sqrt(sa²/na + sb²/nb). |
The delta. \|delta\| > 2 × this is roughly a 95 % separation of the two side means. |
bootstrap |
Half‑width of the 95 % confidence interval of the bootstrap distribution of the delta. Both sides are resampled with replacement bootstrap_replicates times and the delta recorded each time; the reported value is the mean of the distances from the bootstrap mean to the 2.5th and 97.5th percentiles — the statistic AcrossDivide.m's bootCI helper returns. |
The delta directly: \|delta\| > this is a ~95 % separation. Roughly 1.96 × std_err for a large, well‑behaved sample, so never compare a bootstrap column against a std_err column from another run. |
ttest |
The two‑sided p‑value of Welch's unequal‑variance t‑test on the difference of means, matching ttest2(...,'Vartype','unequal'). This column is a probability, not a dispersion. |
0.05 (or your own threshold). Small means the two sides genuinely differ. |
Small samples. Every method needs a within‑side spread, so all four require at least two valid values on both sides of the metric concerned and return NaN otherwise. A side with one head, or none, gives a NaN uncertainty rather than a meaningless zero.
Reproducibility. The bootstrap uses an explicitly seeded std::mt19937; the same seed and the same input always give bit‑identical output. The seed is combined with the pair id and the metric rather than with a loop counter, so adding an unrelated pair to a run cannot change an existing pair's numbers, and the result does not depend on thread count.
Pair‑summary CSV columns
| Field | Description |
|---|---|
pair_id |
"<unit_a>\|<unit_b>", matching the divide vector output. |
unit_a, unit_b |
The two units. unit_a < unit_b always. |
n_heads_a, n_heads_b |
Channel heads assigned to each side. |
mean_head_elevation_a, mean_head_elevation_b, delta_head_elevation |
Head elevation (m), and A − B. |
n_head_elevation_a, n_head_elevation_b |
Valid values behind those means. |
mean_gradient_a, mean_gradient_b, delta_gradient |
Gradient to divide, and A − B. |
n_gradient_a, n_gradient_b |
Valid values behind those means. |
mean_relief_a, mean_relief_b, delta_relief |
Relief to divide (m), and A − B. |
n_relief_a, n_relief_b |
Valid values behind those means. |
mean_chi_a, mean_chi_b, delta_chi |
Chi, and A − B. |
n_chi_a, n_chi_b |
Valid values behind those means. |
uncertainty_method |
std_dev, std_err, bootstrap or ttest. |
uncertainty_head_elevation, uncertainty_gradient, uncertainty_relief, uncertainty_chi |
As defined in the table above. |
Reading a row
Across a divide, the aggressor is the side whose channel heads sit at lower elevation, with higher gradient and relief, and lower chi. The divide is expected to migrate away from the aggressor, into the victim.
With A − B deltas, side A is the aggressor when:
| Column | Points to A as aggressor when |
|---|---|
delta_head_elevation |
negative (A's heads are lower) |
delta_gradient |
positive (A is steeper towards the divide) |
delta_relief |
positive (more relief between A's heads and the divide) |
delta_chi |
negative (A's heads have lower chi) |
Reverse every sign and B is the aggressor.
Judge each metric against its uncertainty before believing it. Under std_err or bootstrap, a metric whose |delta| does not clearly exceed its uncertainty column is not evidence of anything; under ttest, one whose p‑value is above your threshold is not either. A divide with all four metrics small relative to their uncertainties is best read as stable, which is a real and common result, not a failed analysis.
The four metrics can and do disagree. That is informative rather than a problem — chi responds to the drainage‑network geometry and is sensitive to base‑level and to m_over_n, while gradient and relief respond to the present hillslope form. Cross‑divide chi is only comparable at all when the basins share a base level, so a chi delta from a run with outlet_control_mode: none on a DEM whose basins are clipped at the edge should be discounted. Agreement between elevation, gradient and relief, with chi consistent, is the strong case.
Example: minimal run, no clusters
Every outlet is its own unit, so a divide is measured between each adjacent pair of basins.
read_path: /data/santa_cruz/
read_fname: santa_cruz.tif
write_path: /data/santa_cruz/divides/
write_fname: sc
# Required: the outlet points
basin_outlet_fname: outlets.csv
# Channel network density: lower for more heads
threshold_contributing_pixels: 500
# Chi settings -- constrain m_over_n with lsdtt-chi-analysis first
chi_A0: 1.0
m_over_n: 0.45
# Only compare basins that are complete within the DEM
outlet_control_mode: complete_only
Example: clustered outlets
Here outlets.gpkg carries a range_side attribute with values such as west and east. All outlets sharing a value collapse into one unit, so the run measures the divide between the two sides of the range rather than between individual catchments.
read_path: /data/santa_cruz/
read_fname: santa_cruz.tif
write_path: /data/santa_cruz/divides/
write_fname: sc_sides
basin_outlet_fname: outlets.gpkg
outlet_cluster_field: range_side
outlet_cluster_mode: string
# Snap outlets onto trunk channels
basin_outlet_snapping_method: downstream
basin_outlet_contributing_pixels_threshold: 5000
threshold_contributing_pixels: 500
m_over_n: 0.45
# Impose a common base level so chi is comparable across the divide
outlet_control_mode: max_out_elevation
write_metrics_csv: true
write_channel_heads: true
write_vector_format: gpkg
# The side-vs-side table, with a bootstrap uncertainty on each delta
write_pair_summary_csv: true
pair_uncertainty_method: bootstrap
bootstrap_replicates: 2000
bootstrap_seed: 5489
This writes sc_sides_divide_pair_summary.csv with one row per pair of range sides. If units are 1 (west) and 2 (east), then side A is west and a negative delta_head_elevation with a positive delta_gradient and delta_relief and a negative delta_chi says the west side is the aggressor and the divide should be migrating east — provided each |delta| clearly exceeds its uncertainty_* column. Because the uncertainty method here is bootstrap, those columns are 95 % interval half‑widths, so compare |delta| against them directly.
If you would rather do the statistics yourself, the per‑head CSV has everything: group by unit_id, restrict to the rows whose pair_id_nearest names the divide of interest, and take the means of head_elevation_m, gradient_to_divide, relief_m and chi.
Troubleshooting
"basin_outlet_fname must provide at least two points that map into the DEM extent"
Almost always a CRS or extent mismatch. Outlet points are reprojected into the raster CRS on load, so this means the reprojected points fall outside the DEM, not that the file is unreadable. Check:
- the DEM has a valid CRS (
gdalinfo my_dem.tif— a missing or wrong projection is the usual cause); - for a CSV, that the columns really are latitude/longitude in decimal degrees and are not swapped (a lat/long transposition puts points in the wrong hemisphere);
- for a vector, that the file's own CRS is declared correctly;
- that the outlets genuinely lie inside the DEM footprint — compare
gdalinfocorner coordinates againstogrinfo -al -so outlets.gpkg.
"fewer than two valid snapped outlets in two distinct units remain for unit mapping (outlets=N, units=M)"
Divide extraction needs two units to compare. The message gives both counts, which tells you which problem you have:
units=1withoutlets≥ 2: every outlet resolved to the same cluster. Checkoutlet_cluster_fieldnames the column you meant and that its values actually differ.- both counts low: outlets were dropped during snapping. Look for the per‑point snap messages above the error and lower
basin_outlet_contributing_pixels_threshold, or switchbasin_outlet_snapping_methodtoexpanding_square(ornoneif your outlets are already on channel cells).
"could not load outlet clusters from field ''"
The field name was not found. Matching differs by file type, which matters if the name looks right:
- CSV: an exact match on the header is tried first, then a case‑insensitive one. So
Cluster_IDwill findcluster_id. - Vector (FGB/GPKG/shapefile): matching is exact and case‑sensitive.
Cluster_IDwill not findcluster_id.
Other causes are leading/trailing whitespace in a CSV header, or pointing at a file that does not carry the attribute at all. For a vector file, ogrinfo -al -so <file> lists the fields with their exact spelling; for a CSV, check the header row. Note that shapefiles truncate field names to 10 characters.
"outlet cluster value '' is not an integer (point_id=N)"
Raised only under outlet_cluster_mode: integer. Either clean the labels or use auto / string, which accept arbitrary text.
"empty outlet cluster value for point_id=N"
A blank cell in the cluster column. Every outlet must carry a label — there is no implicit "unassigned" group.
"two outlets from different units snap to the same flat index"
Two outlets in different clusters snapped onto the same cell, so the basin cannot be attributed to a unit. Move one outlet, or lower the snapping threshold so they resolve to distinct channel cells.
"fewer than two unique basins remain after selection"
Filtering removed too much. Under outlet_control_mode: complete_only this usually means the basins touch the DEM edge — fetch a larger DEM, or fall back to none and accept that chi is not strictly comparable. Under elevation, the floor is probably above your outlets.
The metrics CSV has rows but every divide field is NaN
The heads were not matched to any divide. Either only one unit had basins that survived filtering, or the units are not adjacent in the DEM (units separated by a third basin share no divide). Check divide segments in the run summary — if it is 0, no divides were extracted at all.
The pair‑summary CSV has a row but the uncertainties are NaN
Every uncertainty method needs at least two valid values on both sides of the metric. Check n_<metric>_a and n_<metric>_b on the row:
- one of them is
0or1: that side has too few heads. Lowerthreshold_contributing_pixelsfor a denser network, or check that side's basins survivedoutlet_control_modefiltering. n_heads_ais healthy butn_relief_aorn_gradient_ais small: most of that side's heads have NaN relief, which means their nearest divide point landed on nodata — see the all‑NaN case above.
A NaN here is deliberate: a standard error from one value would be meaningless, and reporting 0 would be worse than reporting nothing.
The pair‑summary CSV is missing but the metrics CSV is there
write_pair_summary_csv defaults to false. Set it to true.
The pair‑summary CSV has a header but no rows
No head was matched to any divide, so there was nothing to summarise. The file is still written so the absence is visible rather than silent. Check divide segments and heads with divide in the run summary — see The metrics CSV has rows but every divide field is NaN above for the usual causes.
Too few or too many channel heads
threshold_contributing_pixels is the control. It is a contributing‑pixel count, so its meaning in area depends on cell size: at 30 m, 1000 pixels ≈ 0.9 km². Lower it for a denser network.
Parameters
This section is generated from the executable's -h json registry. Do not edit it manually.
Status: experimental. This CLI is not yet fully tested.
File I/O
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
read_path |
string |
./ |
io |
- | Directory from which input files are read. | Defaults to the directory holding the parameter file. |
read_fname |
string |
- | io |
- | Input filename, with or without extension (e.g. my_dem.tif). | Read from read_path. lsdtt3 uses GDAL, so any GDAL-readable raster format works. If empty, defaults to the parameter filename stem. |
write_path |
string |
- | io |
- | Directory to which output files are written. | Defaults to read_path when not set. |
write_fname |
string |
- | io |
- | Output filename prefix (no path, no extension). | All outputs are named <write_fname>_<descriptor>.<ext>. Defaults to the stem of read_fname when not set. |
Routing / DEM
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
boundary_conditions |
string |
oooo |
param |
- | Boundary conditions for flow routing. | Four-character string for north, east, south, west. o = open, n = no-flux, p = periodic, b = forced outlet. |
fill_min_slope |
float |
0.0001 |
param |
raster_is_filled |
Minimum slope used when pit filling is enabled. | Only used when raster_is_filled is false. |
n_threads |
int |
0 |
param |
- | Number of OpenMP threads. | 0 = all available hardware threads. |
Pit filling
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
raster_is_filled |
bool |
false |
switch |
- | Assert that the input raster has ALREADY been pit-filled. | ASKS: has the input already been filled? Filling is REQUIRED for this analysis, so this switch does not choose whether to fill -- it only lets the tool skip filling a surface that is filled already. Setting it true for a DEM that still contains pits gives BAD routing, not different routing. Do not confuse this with fill_before_routing, which asks whether to fill at all. |
Channel network
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
threshold_contributing_pixels |
int |
1000 |
param |
- | Contributing-pixel threshold defining a channel head. | The total number of upslope contributing pixels from the flow-routing object, not the number of direct donors to a pixel. Reduce this value to extract a denser network. This is a PIXEL COUNT and therefore depends on the grid resolution: the equivalent drainage area is threshold_contributing_pixels x cell area. Where a genuine area is wanted instead, the convention is <what>_area_threshold_m2. |
Outlets / clusters
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
basin_outlet_fname |
string |
NULL |
param |
- | Point dataset of outlet locations. | Required. Supports CSV latitude/longitude or point vectors (FGB/GPKG/etc.). |
outlet_cluster_field |
string |
NULL |
param |
- | Optional attribute field naming outlet clusters. | If NULL/empty, each outlet is treated as its own unit. |
outlet_cluster_mode |
string |
auto |
param |
outlet_cluster_field |
How to interpret cluster field values. | Options: auto (numeric grouping if every label is an integer, otherwise string grouping), integer (labels must be integers, grouped numerically), string (labels always grouped as raw text, so 01 and 1 are different units). |
basin_outlet_snapping_method |
string |
downstream |
param |
- | Outlet snapping method. | Options: none, downstream, expanding_square, largest_area. largest_area takes the pixel with the most contributing pixels within basin_outlet_snap_max_search_radius_pixels. |
basin_outlet_contributing_pixels_threshold |
int |
1000 |
param |
basin_outlet_snapping_method |
Contributing-pixel threshold used in outlet snapping. | Higher values snap outlets onto larger channels. |
basin_outlet_snap_min_search_radius_pixels |
int |
0 |
param |
basin_outlet_snapping_method |
Minimum expanding-square snap radius (pixels). | Used only for basin_outlet_snapping_method=expanding_square. |
basin_outlet_snap_max_search_radius_pixels |
int |
25 |
param |
basin_outlet_snapping_method |
Maximum expanding-square snap radius (pixels). | Used only for basin_outlet_snapping_method=expanding_square or largest_area (the half-width of the square searched). |
Chi
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
chi_A0 |
float |
1.0 |
param |
- | Reference area A0 for chi integration (m^2). | Used in chi = integral((A0/A)^(m/n) ds). |
m_over_n |
float |
0.45 |
param |
- | Reference m/n (theta) for chi integration. | Must be > 0. |
outlet_control_mode |
string |
none |
param |
- | Outlet/base-level control mode. | Options: none, elevation, max_out_elevation, complete_only. |
outlet_control_min_elevation |
float |
-9999 |
param |
outlet_control_mode |
Minimum base-level elevation for outlet_control_mode=elevation. | Ignored for other outlet_control_mode values. |
Divide metrics
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
nearest_divide_method |
string |
euclidean |
param |
- | Method to link channel heads to divide geometry. | Current phase supports: euclidean. |
Output
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
write_divide_vectors |
bool |
true |
io |
- | Write divide segment vectors. | Output path stem: <write_fname>_divides. |
write_channel_heads |
bool |
true |
io |
- | Write channel-head point vectors. | Output path stem: <write_fname>_channel_heads. |
write_metrics_csv |
bool |
true |
io |
- | Write per-channel-head metrics CSV. | Output path: <write_fname>_divide_migration_metrics.csv. |
write_vector_format |
string |
flatgeobuf |
io |
- | Vector output format. | Options: flatgeobuf/fgb or gpkg. |
write_pair_summary_csv |
bool |
false |
io |
- | Write the side-vs-side pair summary CSV. | One row per divide pair with side means, A-minus-B deltas (A is the LOWER-numbered unit) and an uncertainty per metric. Output path: <write_fname>_divide_pair_summary.csv. |
pair_uncertainty_method |
string |
std_err |
param |
write_pair_summary_csv |
Uncertainty method for pair summaries. | Options: std_dev (pooled standard deviation of the two sides), std_err (standard error of the difference of means), bootstrap (95% CI half-width of the bootstrapped delta), ttest (two-sided Welch p-value). Needs two valid values on both sides, else NaN. |
bootstrap_replicates |
int |
1000 |
param |
pair_uncertainty_method |
Bootstrap replicate count for pair summary uncertainty. | Used when pair_uncertainty_method is bootstrap. Must be > 0. |
bootstrap_seed |
int |
5489 |
param |
pair_uncertainty_method |
Random seed for bootstrap uncertainty. | Used when pair_uncertainty_method is bootstrap. The same seed and the same input always give bit-identical output. |
Interior nodata
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
interior_nodata_method |
string |
sink |
param |
- | How nodata pixels inside the DEM are treated: sink or fill. | Interior nodata are nodata pixels not connected to the raster edge through other nodata (isolated voids, moulins, masked lakes). sink (the default, unchanged behaviour): they are outlets, so flow that reaches them ends there, as at the DEM edge; use this for real internal drainage (e.g. moulins on an ice sheet). fill: they are interpolated with nodata_fill_method_choice before any filling or routing, so flow passes across them; use this for data voids. Nodata connected to the edge (e.g. sea removed by min_elevation) is never filled and always acts as an open boundary. |
nodata_fill_method_choice |
string |
laplacian |
param |
interior_nodata_method=fill |
Method for filling interior nodata pixels. | laplacian: smooth Dirichlet interpolation (recommended). iterative_weighted_average: fast, well-suited to thin voids. Used when interior_nodata_method is fill. |
nodata_fill_max_iter |
int |
10000 |
param |
interior_nodata_method=fill |
Maximum iterations for iterative nodata fill methods. | Used when interior_nodata_method is fill. |