lsdtt-fluvial-surfaces
Sorts flat ground into fluvial levels — floodplain, terrace treads, fan lobes — by banding a height reference and labelling connected components within each band.
Where this sits
Three tools, each doing one job:
| tool | question | needs |
|---|---|---|
lsdtt-flat-surface-finder |
which ground is flat? | a DEM |
lsdtt-fluvial-surfaces |
which fluvial level is each flat surface on? | a channel, or a supplied height field |
lsdtt-valley-centreline |
trace a centreline through this valley | a valley mask |
The split is at the point where a channel network becomes necessary.
lsdtt-flat-surface-finder is pure morphometry: no flow routing, no channel, no
height reference. This tool does everything that needs one, and writes the
valley mask lsdtt-valley-centreline consumes.
The normal two-step workflow:
lsdtt-flat-surface-finder → <stem>_combined_flat_mask.tif
↓ flat_mask_fname
lsdtt-fluvial-surfaces → <stem>_valley_mask.tif, _band_index.tif, …
↓ valley_mask_fname
lsdtt-valley-centreline → <stem>_valley_centreline.fgb
You can run this tool on its own — leave flat_mask_fname empty and it falls
back to a built-in relief or slope admission. That is for a first look, not for
a result you intend to publish; the texture metrics in
lsdtt-flat-surface-finder are much better at deciding what is flat.
Methods
The tool works in stages, and a method preset,
fluvial_surface_algorithm_choice, sets all of them at once. Any stage named
in the parameter file overrides its preset.
| Stage | Parameter | ladder (default) |
clubb2017 |
cross_section_levels |
|---|---|---|---|---|
| Which ground is considered | admission_algorithm_choice |
flat_mask |
floodplain_thresholds |
flat_mask |
| How the floodplain bounds are chosen | floodplain_threshold_algorithm_choice |
fixed |
qq_gaussian |
fixed |
| Which pixels count as channel | channel_definition_algorithm_choice |
contributing_pixels |
stream_order |
contributing_pixels |
| How the ground is split into levels | level_algorithm_choice |
ladder |
fixed_thresholds |
supplied_levels |
| What terrace height is measured from | terrace_reference_field_algorithm_choice |
same |
swath_baseline |
same |
| How ground becomes patches | segmentation_algorithm_choice |
within_bands |
whole_mask |
within_bands |
ladderfinds the level breaks in the data (see The idea).clubb2017is the method of Clubb et al. (2017): a floodplain bound on HAND and one on gradient, both read from quantile-quantile plots (floodplain_qq_*). The floodplain must touch the channel, and one terrace class lies above the floodplain bound (min_terrace_height_above_floodplain_metres,max_terrace_height_metres). Channel pixels are Strahler ordermin_channel_stream_order(3) and up.floodplain_second_pass_algorithm_choice: qq_upper_departurere-reads the HAND bound from the first-pass floodplain, separating the valley floor from wide flats above it.cross_section_levelsbands with levels read fromsupplied_levels_fname, normally the_cross_section_levels.csvfromlsdtt-valley-centreline.
The other options:
- Several presets: list them comma-separated with no spaces
(ladder,clubb2017) to run each under <write_fname>_<preset> and write
the comparison outputs.
- Training polygons: training_surfaces_fname takes polygons classed
floodplain or terraceN, which set the HAND breaks in each reach window.
- Mapped polygons: mapped_surfaces_fname takes mapped polygons to score
each method against. Keep the scoring polygons separate from the training
polygons.
_method_record.csv lists every stage, where its value came from (preset,
parameter file or measured), and each data-chosen bound. The
design notes
describe each method and what it is sensitive to.
The idea
Band first, then label
The obvious way to segment flat surfaces is to label connected components and then work out how high each one is. That forces the riser network between terraces to be continuous: a road, a gully or a stock track cutting through a riser lets two treads at different heights merge into a single component, and no amount of repair afterwards can separate them again.
This tool inverts the order. It bands the height reference first, then labels components within each band. Two pixels can only share a label if they share a band, and two treads at different heights are by construction in different bands — so the merge cannot happen. The riser never has to be found, and never has to be continuous.
That is the whole reason the tool exists. Everything else is in service of it.
There is no valley mode and no terrace mode
Finding a valley floor and segmenting a terrace flight are the same computation
at different band counts. The valley floor is the lowest band of a two-band cut;
a terrace flight is an n-band ladder on the same population. So there is one
parameter, ladder_level_count:
2— the valley cut. Band 0 is the valley floor.n— an n-level ladder.0(default) — automatic: the largest level count the DEM can actually resolve, see below.
Which reference field
hand (default) — height above the in-scope channel network. Terraces along
a reach sit at a roughly constant height above the channel, so HAND makes a
terrace flight a step function. This is the field for fluvial terraces and
floodplains.
relative_elevation — height above the baseline of the valley
centreline that owns each cell, as lsdtt-valley-centreline computes its
relative elevation raster. The centreline network is built here from the
valley-floor mask in relative_elevation_floor_mask_fname. Unlike HAND, it
measures a tread that drains sideways into another reach against the valley
it sits in. It can leave more reach windows unresolved, and training
polygons need hand.
fitted_surface — the signed residual to a surface fitted to the flat
ground. A fan is not a step function in HAND: it climbs continuously from toe
to apex, so a HAND ladder cuts one surface into arcs banded by distance from the
apex rather than by age. What is roughly constant on a fan is the depth of an
inset lobe below the projection of the older surface — which is what the
residual measures.
Choose the fitted surface with fitted_surface_algorithm_choice:
| value | when |
|---|---|
cone (default) |
a fan or a debris cone. z falls linearly with distance from an apex. |
plane_ransac |
a piedmont, a terrace tread, a broadly planar surface. RANSAC because incised channels and inset lobes are large-amplitude outliers that would tilt an OLS fit. |
poly2 |
a broadly curved surface with no single centre. |
Use cone for a fan. A fan is radial, and a cone is not a low-order
polynomial in x and y. Measured on a synthetic fan (2 km radius, 0.03 gradient,
lobes inset 4 m), the spread of the older surface's own residual — which should
be flat, so its spread is pure misfit — was about 34 m under a RANSAC plane and
10 m under a poly2, against a 4 m signal. Under a cone it was 0.3 m.
The cone's apex is found automatically as the centroid of the highest 0.1 % of
fit-mask cells, or you can state it with fitted_surface_apex_x /
_apex_y. It is supplied rather than solved for because it is a strongly
non-linear parameter and you can usually point at a fan head more reliably than
a solver can find it.
How many levels, and when to believe them
A level count is admissible when the tightest gap between consecutive band
centres clears ladder_resolvability_kappa * vertical_accuracy_metres. The
answer is the largest admissible count.
vertical_accuracy_metres is metadata you must supply — it cannot be derived
from the DEM. Leaving it at 0 disables the check, and then every window
reports resolved and the ladder carries no instrument guarantee at all. The
tool warns when you do. (Note that a DEM whose vertical units are feet on a
metre grid has an effective accuracy 3.28 times larger than it looks.)
Where no count qualifies, the window reports unresolved rather than returning
a number. Read <stem>_ladder_windows.csv before describing your levels:
whether they may be called resolved levels at all depends on which branch the
ladder took, and that is not visible in any raster.
An explicit ladder_level_count is honoured and reported honestly — if that
partition is not admissible the window still records the count you asked for,
with status unresolved, rather than silently substituting a different one.
Composability: hand_fname
HAND is the expensive stage, and the level count is what you iterate on. So:
# once
write_hand_raster: true
# then, as often as you like
hand_fname: <stem>_hand.tif
ladder_level_count: 5
The supplied-HAND path produces bit-identical bands and labels to the computed path, which is pinned by a test.
One limit worth knowing: reach-relative windowing needs each pixel's HAND donor node, and a raster cannot carry those. A supplied HAND is therefore always banded globally. Iterate on the level count with a supplied HAND, then do the final run computing HAND if you want the windowed ladder.
Outputs
| file | what |
|---|---|
_band_index.tif |
Int16, nodata −1. Which level each pixel is on. Authoritative. |
_banded_labels.tif |
UInt32. Patch label, a global running counter 1..N; 0 is background. |
_valley_mask.tif |
Band 0. The input to lsdtt-valley-centreline. |
_surface_class.tif |
floodplain / terrace class. hand field only. |
_method_record.csv |
Every stage, its source (preset, parameter file or measured), each data-chosen bound and the counts. |
_mapped_surface_class.tif, _mapped_surface_scores.csv |
Mapped polygons burned to cells, and per-method scores against them (mapped_surfaces_fname). |
_method_summary.csv, _method_comparison.csv, _method_agreement_*.tif |
Comparison outputs when several presets run in one go. |
_surface_patches.csv |
One row per patch: label, band, class, source_id, n_pixels, area, median field value, aspect. The join key for everything downstream. |
_ladder_windows.csv |
The resolvability report. Read it first. |
_reference_field.tif |
The field that was actually banded, whichever it was. |
_hand.tif |
The HAND raster, for feeding back as hand_fname. |
_floodplain_thresholds.csv, _floodplain_<hand\|gradient>_qq.csv |
Each data-chosen floodplain bound and the QQ (or CDF) table it was read from (write_floodplain_threshold_csv). |
_floodplain_first_pass_mask.tif, _floodplain_hand_qq_second_pass.csv |
The first-pass floodplain and the second-pass QQ table (floodplain_second_pass_algorithm_choice: qq_upper_departure). |
_terrace_field.tif, _floodplain_gradient.tif |
The terrace reference field and the gradient the floodplain bound was applied to (fixed_thresholds). |
_patch_level_clusters.csv |
Whether the patches' median HAND values cluster into levels more than random heights would, with a p-value. |
_training_polygons.csv, _training_fit.csv, _training_levels.tif |
Per-polygon statistics, the per-window breaks fitted from them, and the levels they define (training_surfaces_fname). |
_surface_class_polygons, _surface_patch_polygons, _floodplain_polygons |
The classes, the patches and the floodplain as polygons (write_*_polygons, in write_vector_format). |
The band is not packed into the label. The label is a plain running counter
and the band travels in _band_index.tif and in the patch table, so nothing has
to divide a label to learn its level.
_surface_class.tif and _surface_class.csv are written only for the hand
field. Floodplain and terrace are statements about height above a channel; they
mean nothing on a fitted-surface residual, so no class is invented for one.
Things that will bite you
antimode cannot band a fitted surface. The antimode break finder builds its
histogram on log10 of the field, which suits HAND (non-negative by
construction) and destroys a signed one: every negative residual is floored into
the lowest bin, so the entire below-surface population — the inset lobes, the
thing you are looking for — collapses into a single spike. Selecting
fitted_surface switches the break finder to jenks automatically if you did
not name one, and rejects an explicit antimode with that explanation.
A terrace flight often wants jenks, not the default antimode. A flight is
spaced roughly linearly in height above the channel, and the log10 binning
compresses the upper treads until they merge.
min_band_component_pixels is per band: a floor on the size of one surface
on one level, not the whole-mask filter that min_component_pixels is in
lsdtt-flat-surface-finder - which is why the two names differ.
Cleaning the valley mask can make it bigger. clean_valley_mask erodes,
drops patches that do not reach the channel, then regrows — and the regrow fills
enclosed holes. It is not a filter.
The tool needs flow routing unless you supply hand_fname, so the
single-command case is not cheap. That is what the two-step workflow above is
for.
Usage
lsdtt-fluvial-surfaces <path> <parameter_file>
lsdtt-fluvial-surfaces <parameter_file> # path defaults to ./
lsdtt-fluvial-surfaces -h # parameter help, then exit
lsdtt-fluvial-surfaces -h <parameter_name> # full guidance on one parameter
lsdtt-fluvial-surfaces -h html # write an HTML/CSV reference
lsdtt-fluvial-surfaces --help-json # machine-readable parameter list
lsdtt-fluvial-surfaces -v # version
lsdtt-fluvial-surfaces -c # citation
Parameter file lines are key: value; # starts a comment. Every run also
writes <write_fname>_README.csv and .html alongside its outputs, recording
exactly what it was given.
Reading the parameter tables
The generated tables below list every parameter with its type, default and
guidance, grouped as the CLI's -h output groups them. They are regenerated
from the CLI on every change and checked by CTest, so they are the
authority. Some cautions they do not stress:
vertical_accuracy_metresdefaults to 0, which disables the resolvability floor: every window then reportsresolvedwith no instrument guarantee.min_band_component_pixelsis per band, not over the whole mask.- The valley-mask cleaning options (
fill_valley_mask_holes,clean_valley_mask,valley_mask_closing_*,valley_dilation_pixels,restrict_valley_to_*) change_valley_mask.tifonly. The band index, the labels and the patch table keep their holes and speckle. restrict_valley_to_line_of_sightis a strong filter: on a 10 m DEM it can remove a large share of band 0 on its own. Use it where two valleys meet around a col and connectivity alone merges them.max_floodplain_hand_metresis a hard cap underladder, where the ladder decides where a level ends; underfixed_thresholdsit is the floodplain bound itself.
See also
- Floodplains, terraces and valleys — the band-then-label ordering, the three break finders, the resolvability floor, and where the methods come from
- CLI reference: lsdtt-flat-surface-finder
- CLI reference: lsdtt-valley-centreline
Renamed parameters (2026-10-26)
Old keys still run, with a warning naming the replacement; converted values are
written to _ingested_params.param.
| Old key | Now |
|---|---|
fallback_admission_algorithm_choice |
admission_algorithm_choice — still only applies when flat_mask_fname is empty; slope is now gradient |
fallback_relief_window_radius_pixels, fallback_relief_window_pixels |
relief_window_radius_metres — converted with the cell size |
fallback_relief_threshold_metres |
max_relief_metres |
fallback_slope_threshold |
max_admission_gradient |
min_component_pixels |
min_band_component_pixels |
vertical_accuracy_mode |
vertical_accuracy_algorithm_choice |
vertical_accuracy_break_degrees |
vertical_accuracy_break_gradient — converted with gradient = tan(angle) |
vertical_accuracy_slope_window_radius_metres |
vertical_accuracy_gradient_window_radius_metres |
floodplain_max_height_metres |
max_floodplain_hand_metres |
terrace_min_height_metres |
min_terrace_hand_metres |
The _ladder_windows.csv column median_slope_degrees is now median_gradient
(m/m).
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. |
General
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
n_threads |
int |
0 |
io |
- | Number of threads. 0 = all available. | OpenMP thread count for every parallel stage. |
write_output_format |
string |
cog |
io |
- | Raster output format: cog, gtiff or same_as_input. | cog is a Cloud Optimised GeoTIFF and is the project default. |
write_vector_format |
string |
flatgeobuf |
io |
- | Vector output format: flatgeobuf, gpkg or csv. | Governs every vector layer this tool writes, including the channel network. FlatGeobuf is preferred: it is compact, carries its own georeferencing, and does not have GeoPackage's problems writing across drives (a common WSL2 situation). |
Method
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
fluvial_surface_algorithm_choice |
string |
ladder |
switch |
- | Method preset: ladder, clubb2017 or cross_section_levels. | Fills every stage parameter left at preset. ladder (default): the admitted ground (flat_mask_fname, or the built-in relief admission) is banded by a data-driven ladder on HAND, components labelled within each band. clubb2017: Clubb et al. (2017, Earth Surf. Dynam. 5:369-385) - floodplain where HAND above a Strahler-order channel and gradient are both below bounds read off quantile-quantile plots, floodplain patches must contain a channel pixel, terraces above the floodplain bound measured from the nearest channel (swath baseline). cross_section_levels: the ladder, but with the levels read from supplied_levels_fname (normally <stem>cross_section_levels.csv from lsdtt-valley-centreline) instead of found. Any stage named in the parameter file overrides the preset, so e.g. clubb2017 with level_algorithm_choice: ladder bands the Clubb admission with the ladder. Stage values (ladder / clubb2017 / cross_section_levels): admission_algorithm_choice flat_mask / floodplain_thresholds / flat_mask; floodplain_threshold_algorithm_choice fixed / qq_gaussian / fixed; channel_definition_algorithm_choice contributing_pixels / stream_order / contributing_pixels; level_algorithm_choice ladder / fixed_thresholds / supplied_levels; terrace_reference_field_algorithm_choice same / swath_baseline / same; segmentation_algorithm_choice within_bands / whole_mask / within_bands. Several presets in one run: list them comma-separated with no spaces (ladder,clubb2017); each writes under <write_fname><preset>, then the comparison outputs (write_method_record_csv). |
level_algorithm_choice |
string |
preset |
param |
fluvial_surface_algorithm_choice |
How surfaces are put on levels: preset, ladder, fixed_thresholds or supplied_levels. | ladder: data-driven breaks in the reference field, as many levels as the DEM can resolve (the ladder_* parameters). fixed_thresholds: two levels from two bounds - floodplain below max_floodplain_hand_metres (and touching the channel), terrace above it by min_terrace_height_above_floodplain_metres - with the bounds chosen by floodplain_threshold_algorithm_choice. fixed_thresholds needs reference_field_algorithm_choice hand. supplied_levels: the ladder's machinery with the levels read from supplied_levels_fname - band breaks at the midpoints between consecutive levels, per channel - instead of found; band centres and the resolvability floor are still measured and reported (status supplied in _ladder_windows.csv). |
supplied_levels_fname |
string |
- | io |
level_algorithm_choice |
Levels file for level_algorithm_choice supplied_levels, in read_path. | CSV with at least source_id and level_hand_m columns (heights above the channel, m): <stem>_cross_section_levels.csv from lsdtt-valley-centreline, or a file written by hand from survey. A source_id of -1 applies to every channel. Each channel's source_id must match this run's channel scope - use the same source points and threshold_contributing_pixels as the run that wrote it - and a channel with no levels stops the run, naming it. |
segmentation_algorithm_choice |
string |
preset |
param |
fluvial_surface_algorithm_choice |
How admitted ground becomes patches: preset, within_bands or whole_mask. | within_bands (ladder and cross_section_levels presets): connected components inside each level, so two treads at different heights can never share a patch. whole_mask (clubb2017 preset): components of the admitted ground minus the risers in riser_mask_fname, each taking its majority level. whole_mask is the riser segmentation, kept so it can be compared: where a riser is broken (a road, a gully) the two treads either side join into one patch on one level. Under fixed_thresholds there is one terrace level, so whole_mask only removes the riser pixels. |
riser_mask_fname |
string |
- | io |
segmentation_algorithm_choice |
Riser raster for segmentation_algorithm_choice whole_mask, in read_path. | Cells > 0.5 are risers and split the admitted ground. Normally <stem>_riser_centreline.tif from lsdtt-feature-finder (feature_type: find_risers), on the DEM grid; gap closing there (riser_gap_closing_algorithm_choice: astar) decides whether broken risers still separate treads. Empty = no risers: whole_mask then labels the admitted ground as it stands. |
channel_definition_algorithm_choice |
string |
preset |
param |
fluvial_surface_algorithm_choice |
What counts as a channel: preset, contributing_pixels or stream_order. | contributing_pixels: the whole network down from heads at threshold_contributing_pixels. stream_order: only its pixels of Strahler order >= min_channel_stream_order; HAND, the floodplain contact test and the swath baseline then all use that channel. The threshold still decides which first-order channels exist. |
min_channel_stream_order |
int |
3 |
param |
channel_definition_algorithm_choice |
Lowest Strahler order counted as channel (stream_order only). | Clubb et al. (2017) used third order at all eight of their sites. LSDTopoTools2 called this threshold_SO. |
terrace_reference_field_algorithm_choice |
string |
preset |
param |
fluvial_surface_algorithm_choice |
What terrace height is measured from: preset, same or swath_baseline. | same: the main reference field (HAND). swath_baseline: elevation above the NEAREST channel pixel within swath_half_width_metres, which does not follow drainage, so a terrace above a confluence is referenced to the nearest channel rather than the one its flow path reaches (Clubb et al. 2017). fixed_thresholds only. |
swath_half_width_metres |
float |
500 |
param |
terrace_reference_field_algorithm_choice |
Half-width [m] of the swath baseline: terraces further than this from the channel are not measured. | A HALF-width, as half_width_metres in lsdtt-swath-profile. LSDTopoTools2's swath_width (default 1000) was a full width. |
Admission
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
flat_mask_fname |
string |
- | io |
- | Flat mask raster from lsdtt-flat-surface-finder, in read_path. | Cells > 0.5 are admitted. This is normally <stem>_combined_flat_mask.tif from lsdtt-flat-surface-finder, which is the intended two-step workflow: detect flat ground there, sort it into levels here. Leave empty to use the built-in relief or gradient admission (admission_algorithm_choice) instead, which is coarser and is meant for a quick look rather than for a result you intend to publish. |
admission_algorithm_choice |
string |
preset |
param |
- | Which ground is admitted: preset, flat_mask, relief, gradient or floodplain_thresholds. | floodplain_thresholds (the clubb2017 preset): cells whose gradient, read over floodplain_gradient_window_radius_metres, is below max_floodplain_gradient as chosen by floodplain_threshold_algorithm_choice, among cells with a defined HAND. flat_mask (the ladder preset) reads flat_mask_fname, normally <stem>_combined_flat_mask.tif from lsdtt-flat-surface-finder. relief and gradient are a coarse built-in admission for a quick look when there is no flat mask: relief admits cells whose local relief over relief_window_radius_metres is below max_relief_metres; gradient admits cells whose gradient is below max_admission_gradient. flat_mask with flat_mask_fname empty falls back to relief, as earlier versions did. Replaces fallback_admission_algorithm_choice (whose slope is now gradient). |
relief_window_radius_metres |
float |
0 |
param |
admission_algorithm_choice |
Radius (m) of the square window local relief (max - min) is measured over. 0 = 5 cells. | Converted to the nearest whole number of cells, at least one; the run log states the radius actually used. A radius r cells is a (2r+1) x (2r+1) window, so the default is a 11 x 11 window. Replaces relief_window_side_pixels (a full side) and fallback_relief_window_radius_pixels (a radius in cells), which spelled the same idea two ways. |
max_relief_metres |
float |
1 |
param |
admission_algorithm_choice |
Local relief (m) below which a pixel is admitted as flat. | Read a value off the relief CDF the tool writes. Where the tool has relief_threshold_algorithm_choice, this is the value used by fixed. |
max_admission_gradient |
float |
0.05 |
param |
admission_algorithm_choice |
Gradient (m/m) below which a pixel is admitted as flat. | A gradient, not an angle: 0.087 is 5 degrees, 0.176 is 10, 0.268 is 15 (gradient = tan(angle)). Measured from a fitted polynomial surface rather than a 3x3 difference. |
fill_admitted_holes |
bool |
false |
param |
- | Fill enclosed holes in the admitted ground (admission flat_mask, relief or gradient). | Roughness - DEM noise, crops, buildings - leaves enclosed gaps in a relief- or gradient-based admission, which become holes in every surface built on it. A gap is filled only if it is entirely surrounded by admitted ground. Not applied to the floodplain_thresholds admission, whose floodplain has fill_floodplain_holes. |
min_band_component_pixels |
int |
50 |
param |
- | Minimum patch size in pixels, applied PER BAND. | Components are formed inside each band, so this is a floor on the size of one surface on one level - not the whole-mask filter that min_component_pixels is in lsdtt-flat-surface-finder, which is why the name differs. Replaces min_component_pixels here. |
Training
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
training_surfaces_fname |
string |
- | param |
- | Polygons (gpkg, fgb, shp) of floodplain and terrace drawn by hand, used to choose the HAND thresholds. | Class values: floodplain or fp; terrace, terraceN, tN or 'terrace N' (case and spaces ignored). Terrace numbers are ignored: the levels come from the HAND inside the polygons. Each polygon is summarised by its median and its 25-75 percentile core; per reach window (the ladder's windows) the floodplain / terrace bound and the breaks between terrace levels are chosen by best separation (Youden's J) of those cores. clubb2017: the bound replaces the data-chosen HAND bound per window (and the second pass is skipped). ladder: the trained breaks are the lowest breaks and the ladder's own search runs above the top one. A window with too little training takes the fit over all training cells. Writes <stem>_training_levels.tif, <stem>_training_fit.csv and <stem>_training_polygons.csv. Needs reference_field_algorithm_choice hand. |
training_surfaces_layer_name |
string |
- | param |
- | Layer of training_surfaces_fname; empty = the first layer. | - |
training_surfaces_class_field_name |
string |
class |
param |
- | Field of training_surfaces_fname holding each polygon's class. | - |
training_spread_factor |
float |
3.0 |
param |
- | Absolute limit on a training polygon's HAND interquartile range, in required gaps (kappa x E_v). | A polygon is summarised by its 25-75th percentile core, so a gully crossed while drawing does not move it. It is 'not a single level' (a sloping surface, or one drawn across a riser) when its interquartile range exceeds both this limit and training_max_relative_spread x its median HAND. Such a polygon still counts as terrace for the floodplain / terrace bound but takes no part in the terrace level breaks. Flagged in <stem>_training_polygons.csv. |
training_max_relative_spread |
float |
0.25 |
param |
- | Relative limit on a training polygon's HAND interquartile range, as a fraction of its median HAND. | See training_spread_factor: a polygon is flagged only when it exceeds both. High terraces vary more in metres; this keeps them single levels. 0 = the absolute limit alone. |
min_training_cells_per_class |
int |
20 |
param |
- | Core cells a training population needs in a window to take part. | Below it the window falls back to the fit over all training cells, and below it there the threshold is the data-chosen one. |
Mapped surfaces
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
mapped_surfaces_fname |
string |
- | param |
- | Mapped floodplain and terrace polygons (gpkg, fgb, shp) to score every method against. | Class values as for training_surfaces_fname: floodplain or fp; terrace, terraceN, tN or 'terrace N' (levels are not scored yet). Any other value stops the run before any work. Burned by cell centre only (no edge growth), reprojected to the DEM CRS. Writes <write_fname>_mapped_surface_class.tif (0 surveyed but neither, 1 floodplain, 2 terrace) and <write_fname>_mapped_surface_scores.csv: per method the 3 x 3 confusion (mapped x method) in cells, and floodplain and terrace precision, recall and intersection-over-union. Scored only where the DEM is valid, the method ran on HAND and the cell is inside mapped_surfaces_extent_fname. |
mapped_surfaces_layer_name |
string |
- | param |
- | Layer of mapped_surfaces_fname; empty = the first layer. | - |
mapped_surfaces_class_field_name |
string |
class |
param |
- | Field of mapped_surfaces_fname holding each polygon's class. | - |
mapped_surfaces_extent_fname |
string |
- | param |
- | Polygon(s) of the area the mapper actually surveyed. | No terrace polygon is evidence of no terrace only where someone looked. Cells whose centre is outside these polygons are not scored. Empty: the whole DEM counts as surveyed (the log says so). |
Reference field
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
reference_field_algorithm_choice |
string |
hand |
switch |
- | Field to band: hand, relative_elevation or fitted_surface. | hand = height above the in-scope channel network; the right field for a terrace flight, where treads sit at a constant height above the channel. relative_elevation = height above a valley centreline's baseline instead of above the channel the cell's flow reaches (lsdtt-valley-centreline write_relative_elevation_raster): the centrelines run through relative_elevation_floor_mask_fname, the field reaches out over the whole DEM (relative_elevation_max_distance_metres), and a terrace drained by a small local stream is still measured from the main valley rather than from that stream. Used wherever HAND is (bounds, ladder, training), with reach windows from the channel pixel of each cell's centreline node. fitted_surface = the signed residual to a surface fitted to the flat ground; the right field for a fan, which climbs continuously in HAND and would be cut into meaningless arcs by a HAND ladder. |
relative_elevation_floor_mask_fname |
string |
- | io |
reference_field_algorithm_choice |
Valley-floor mask the relative elevation's centrelines run through (cells > 0.5), for reference_field_algorithm_choice relative_elevation. | Normally <stem>_floodplain_mask.tif from a clubb2017 run of this tool. It must be the valley FLOOR: traced through a mask that includes the terraces, a medial centreline runs across the terrace plain instead of down the valley. One centreline per channel segment of the in-scope network (longest first, tributaries ending at their parent), with the floor half-width deciding how far each reaches up the valley sides. |
network_centreline_algorithm_choice |
string |
medial_path |
param |
reference_field_algorithm_choice |
Line each channel segment's valley centreline follows: medial_path or smoothed_channel. | Not the raw D8 channel: sections cast from a meandering channel swing with every bend and cross one another, and a baseline tied to its planform imprints the meanders on the relative elevation. medial_path: the least-cost walk along the valley's medial axis (cost = largest distance-to-wall minus distance-to-wall), head to confluence, confined to the valley mask plus the segment's own channel pixels. smoothed_channel: the D8 channel smoothed by a Gaussian-smoothed cubic spline whose sigma scales with the valley width; it may not leave the valley mask, so where it would, it runs along the mask edge until it comes back in. |
network_centreline_window_margin_multiple |
float |
3 |
param |
network_centreline_algorithm_choice |
medial_path: each walk runs in the segment's bounding box padded by this multiple of its largest distance-to-wall (plus 10 cells). | Bounds the work per segment. The medial axis lies within a valley width of the channel, so 3 leaves room on both sides. |
network_centreline_smoothing_width_multiple |
float |
1 |
param |
network_centreline_algorithm_choice |
smoothed_channel: Gaussian sigma (along-channel metres) as a multiple of the segment's valley width. | Valley width = 2 x the 90th percentile of distance-to-wall over the segment's channel pixels; sigma is never below two cells. Larger values remove longer meanders but cut more corners. |
centreline_baseline_algorithm_choice |
string |
projected_monotonic |
param |
reference_field_algorithm_choice |
How a centreline's baseline is pinned to its channel: projected_monotonic or channel_crossings. | projected_monotonic: every channel pixel pins the centreline node nearest it (the lowest elevation winning on a shared node), then each pin is lowered to the lowest pin upstream of it and the baseline is linear between pins - it never rises downstream and is never above the channel. channel_crossings (LSDTopoTools2): pins only where the channel crosses the centreline in plan view, at the channel elevation interpolated at the crossing (and at centreline nodes that are channel pixels), linear between crossings and flat beyond the first and last, with no other constraint. |
relative_elevation_max_distance_metres |
float |
0 |
param |
reference_field_algorithm_choice |
Farthest a cell may be, along the ground, from the centreline node it is measured against [m]. 0 = no limit. | Ground distance through the relative-elevation domain, in metres (not the floor-width-scaled distance that decides which node a cell belongs to). Cells farther away are left out. Use it to keep a whole-DEM domain to the valley sides. |
hand_fname |
string |
- | io |
- | Precomputed height-above-channel raster, in read_path. | THE COMPOSABILITY LEVER. HAND is the expensive stage of this tool and the level count is what you iterate on, so compute HAND once, write it with write_hand_raster, then re-run with hand_fname set and try different ladder settings for the cost of the ladder alone. Flow routing is still built when a channel scope or network output is requested, but the HAND computation is skipped. |
fitted_surface_algorithm_choice |
string |
cone |
param |
reference_field_algorithm_choice |
Surface to fit: cone, plane_ransac or poly2. | cone is the fan option and the default: z falling linearly with distance from an apex. A fan is radial, and a cone is not a low-order polynomial in x and y - on the synthetic fan fixture the older surface's own residual spread is about 34 m under a RANSAC plane and 10 m under a poly2, against a 4 m lobe inset, so the misfit swamps the signal; under a cone it is about 0.3 m. Use plane_ransac for a piedmont or a terrace tread and poly2 for a broadly curved surface with no single centre. |
fitted_surface_mask_fname |
string |
- | io |
reference_field_algorithm_choice |
Optional raster restricting where the surface is fitted, in read_path. | Cells > 0.5 take part in the fit. Use it to fit on the older fan surface alone. Optional: a cone fitted to everything, inset lobes included, still separates the populations - the lobes drag the whole fit down rather than blurring the boundary, and the ladder does not care about the absolute offset. Empty = fit over the admitted mask. |
fitted_surface_ransac_tolerance_metres |
float |
5.0 |
param |
reference_field_algorithm_choice |
RANSAC inlier tolerance for plane_ransac [m]. | Set this to roughly the roughness of the surface being fitted, NOT to the depth of the features being isolated: a tolerance as large as a lobe inset lets the lobes join the fit and flattens the signal. |
fitted_surface_ransac_iterations |
int |
1000 |
param |
reference_field_algorithm_choice |
Number of RANSAC hypotheses for plane_ransac. | Increase when the inlier fraction is expected to be below about 30%. |
fitted_surface_seed |
int |
1 |
param |
reference_field_algorithm_choice |
RANSAC random seed, for reproducibility. | Same seed and same input give the same plane. |
fitted_surface_apex_x |
float |
0.0 |
param |
reference_field_algorithm_choice |
Fan apex easting in the DEM's projected CRS [m]; cone only. | Leave both apex coordinates at 0 to locate the apex automatically, as the centroid of the highest 0.1% of fit-mask cells. The apex is supplied rather than solved for because it is a strongly non-linear parameter and a user can point at a fan head more reliably than a solver can find it. |
fitted_surface_apex_y |
float |
0.0 |
param |
reference_field_algorithm_choice |
Fan apex northing in the DEM's projected CRS [m]; cone only. | See fitted_surface_apex_x. |
Ladder
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
ladder_level_count |
int |
0 |
param |
- | Number of levels: 0 = auto, 2 = the valley cut, n = an n-level ladder. | 0 lets the resolvability floor choose the largest level count the DEM can actually carry. 2 gives the valley/wall separation that lsdtt-valley-centreline consumes. An explicit count is honoured and reported honestly: if that partition is not resolvable the window still records the count you asked for, with status unresolved, rather than silently substituting a different one. |
ladder_max_level_count |
int |
12 |
param |
- | Ceiling on the level count considered under auto selection. | A ceiling on what one reach can plausibly carry, not a storage limit. A reach resolving more than a dozen distinct fluvial levels is more likely over-fitting noise than recording twelve terraces. Raise it freely for a genuine long Quaternary staircase; there is no numeric cliff behind it. |
ladder_break_algorithm_choice |
string |
antimode |
param |
- | Break finder: gap, antimode or jenks. | antimode finds dips in a smoothed log10 density. jenks is Fisher-Jenks on the linear values; it takes the level count directly and is the source of the reported goodness-of-variance fit. gap finds empty spans between clusters. Here: antimode bins on log10 and therefore cannot band a field with negative values, which every fitted_surface residual has. This tool switches to jenks automatically when you select fitted_surface without naming a break algorithm, and rejects the combination if you ask for it explicitly. |
vertical_accuracy_metres |
float |
0.0 |
param |
- | Vertical accuracy of the DEM, E_v [m]. 0 disables the floor. | Two levels are only claimed as distinct when their band centres are at least ladder_resolvability_kappa * E_v apart. This is metadata you must supply: it cannot be derived from the DEM. 0 disables the check and every window then reports resolved, carrying no instrument guarantee at all. Note that a DEM whose vertical units are feet on a metre grid has an effective E_v 3.28 times larger than you think. |
ladder_resolvability_kappa |
float |
3.0 |
param |
- | Multiplier on E_v in the resolvability floor. | Required band-centre gap = kappa * vertical_accuracy_metres. |
ladder_gap_break_position |
string |
midpoint |
param |
ladder_break_algorithm_choice |
Where in the empty span between clusters the break goes: lower, midpoint or upper. | The span is nearly empty by construction, so any position separates the same two clusters - but the sparse pixels INSIDE it (the bank, the riser, the floodplain margin) go to whichever side the break leaves them on. Use upper when the low band IS the valley floor and you want the whole margin in it; lower for the strictest reading of the active floodplain; midpoint splits the difference. |
ladder_gap_rank |
string |
lowest |
param |
ladder_break_algorithm_choice |
Order the gap finder offers its candidate breaks in: lowest or width. | gap algorithm only, and it decides which break wins when more than one is admissible. lowest walks the ladder from the bottom, so the valley cut is taken before any terrace or hillslope cut. width prefers the widest empty span, which is only a good guide where the whole field range is a few metres: in a mountain catchment the widest empty span sits in the sparse tail a hundred metres above the river, and taking it bands the entire terrace staircase as valley floor. |
ladder_gap_trough_max_ratio |
float |
0.6 |
param |
ladder_break_algorithm_choice |
Offer a local minimum as a candidate break when its floor is below this fraction of the smaller flanking peak. 0 = empty spans only. | gap algorithm only. ladder_gap_empty_fraction measures emptiness against the TALLEST bin, so where one level dominates the population a real dip above it never reads as empty and the finder has no candidate anywhere near the valley floor. This offers those dips as candidates; ladder_max_separation_ratio still has to accept them before any becomes a break, which is why this is the looser of the two numbers. |
ladder_gap_empty_fraction |
float |
0.02 |
param |
ladder_break_algorithm_choice |
A histogram bin below this fraction of the tallest bin counts as empty. | gap algorithm only. Raise it to treat sparsely-populated ground as a gap; lower it to require a near-total absence of pixels before calling a space between clusters. |
ladder_gap_min_width_metres |
float |
0.5 |
param |
ladder_break_algorithm_choice |
Narrowest empty span the gap finder will call a separation [m]. | gap algorithm only. Below this a run of empty bins is noise, not the space between two surfaces. |
ladder_gap_hist_bins |
int |
80 |
param |
ladder_break_algorithm_choice |
Linear histogram bins the gap finder searches for empty runs. | Too few and a real gap is bridged by a single populated bin; too many and ordinary sampling noise fragments a cluster into false gaps. |
ladder_max_separation_ratio |
float |
0.0 |
param |
- | Reject a partition whose worst break sits where the density is above this fraction of the populations either side. 0 disables. | A SECOND CONSTRAINT alongside the resolvability floor, never blended with it into a score. The floor asks whether the DEM can MEASURE a separation; this asks whether there IS one. Both are necessary and neither implies the other: two lidar terraces 30 cm apart pass this and need a small vertical_accuracy_metres to pass the floor, while a 26 m cut through one broad mode passes any floor and fails this outright. 0.6 matches the antimode finder's own acceptance limit. Off by default because turning it on changes which partitions are admissible. |
ladder_min_break_metres |
float |
0.0 |
param |
- | Reject any break below this height [m]. 0 = unbounded. | A hard bound applied to every candidate before selection, for when the lowest plausible level boundary is known independently. |
ladder_max_break_metres |
float |
0.0 |
param |
- | Reject any break above this height [m]. 0 = unbounded. | The companion to ladder_min_break_metres, and the direct lever against a break landing far above the valley in a landscape whose flat uplands dominate the population. |
ladder_k_bootstrap |
int |
0 |
param |
- | Resample each window and re-run the break search this many times, to report how stable the chosen level count is. 0 = off. | k_selected on its own says nothing about whether a different draw of the same landscape would have given the same answer, and a ladder that returns k = 7 every time is not the same claim as one that returns it once in fifteen. This costs B times the break search per window, and the break search is where the cost sits, not the pixel count - every finder bins before it searches. Measured on a 3.58 M cell DEM with 41 windows, 15 replicates added 4.9 s under gap (16.4 to 21.3 s). The same DEM's whole ladder is 6.2 s under antimode and 37.7 s under jenks, so scale accordingly: affordable on gap, think first on jenks. Results go to _ladder_windows.csv as k_bootstrap_mode, k_stability, k_ci_lo, k_ci_hi and n_bootstrap. |
ladder_k_bootstrap_seed |
int |
1 |
param |
ladder_k_bootstrap |
Seed for the k bootstrap, so a stability figure is reproducible. | Each window is seeded from this plus its own source and index, so a window's stability does not shift because a neighbouring reach changed. The generator is specified in the source rather than taken from the standard library, so the number reproduces across platforms. |
ladder_unresolved_window_algorithm_choice |
string |
nearest_resolved |
param |
- | What an unresolved ladder window bands with: nearest_resolved or single_band. | An unresolved window found no admissible levels. single_band bands all of it as one level, so band 0 (and the floodplain, the lowest level touching the channel) can span hundreds of metres of HAND there. nearest_resolved borrows the breaks of the nearest window on the same channel that found levels, so band 0 means a comparable HAND range along the channel; the window stays 'unresolved' in _ladder_windows.csv and breaks_from_window_m names the lender. |
vertical_accuracy_algorithm_choice |
string |
fixed |
param |
- | How E_v is arrived at: fixed or slope_dependent. | Photogrammetric and InSAR DEMs degrade on slopes - TanDEM-X relative LE90 is roughly 2 m on gentle ground and 4 m on steep - so a single number for a raster spanning a valley floor and a 40-degree wall states an accuracy that is wrong in both places. Under slope_dependent each along-channel WINDOW reads one E_v off the median gradient of its own admitted cells. Per window, not per pixel: the resolvability floor constrains the separation between band CENTRES, and a band centre is a property of a partition rather than of a place, so it has no slope of its own. The value each window used is written to _ladder_windows.csv. |
vertical_accuracy_gentle_metres |
float |
0.0 |
param |
- | E_v [m] for windows at or below the break gradient. slope_dependent only. | For TanDEM-X this is the quoted relative LE90 on gentle ground, about 2 m. |
vertical_accuracy_steep_metres |
float |
0.0 |
param |
- | E_v [m] for windows above the break gradient. slope_dependent only. | About 4 m for TanDEM-X. It must be the LARGER of the two or the mode is doing nothing useful. |
vertical_accuracy_break_gradient |
float |
0.2 |
param |
- | Gradient [m/m] separating the gentle and steep E_v. | 0.2 is a 20 percent grade, which is where the TanDEM-X accuracy specification splits. Replaces vertical_accuracy_break_degrees; an old value is converted with gradient = tan(angle). |
vertical_accuracy_gradient_window_radius_metres |
float |
0 |
param |
- | Window radius [m] of the polynomial fit the window gradient is measured over. 0 = five cells. slope_dependent only. | Stated in cells by default because a length tuned on lidar is sub-pixel on 30 m COP30. This is the same measurement lsdtt-flat-surface-finder makes for max_admission_gradient, and it is worth using the same window in both so the gradient a surface was admitted on is the gradient its E_v was read from. |
ladder_reach_window_metres |
float |
0.0 |
param |
- | Along-channel window length for reach-relative breaks [m]. | 0 = auto: the larger of 2 km and an eighth of each channel's length. Negative = one global partition over the whole population. Windowing lets a break level track downstream changes in incision instead of being averaged into a single basin-wide ladder. Ignored for the fitted_surface field, which has no along-channel coordinate. |
ladder_min_window_pixels |
int |
1000 |
param |
- | Minimum admitted pixels for a window to find its own breaks. | A sparser window falls back to the global partition rather than inventing structure from too few pixels. |
ladder_hist_bins |
int |
64 |
param |
- | Histogram bin count for the antimode break finder. | Clamped to at least 16. |
ladder_max_valley_fraction |
float |
0.6 |
param |
- | Antimode acceptance: dip depth relative to the smaller flanking peak. | A dip is accepted as a break when its smoothed density is below this fraction of the smaller of its two flanking modal peaks. |
ladder_min_field_metres |
float |
0.05 |
param |
- | Log floor for the antimode histogram [m]. | Field values below this are clamped before taking log10. |
Outputs
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
write_patch_terrain_aspect |
bool |
false |
output |
- | Add the terrain aspect and its resultant length to the patch table. | The existing aspect_degrees column is the direction the BANDED FIELD climbs, fitted as a plane through it - on a HAND field that is roughly away from the channel, not the direction the ground faces. This adds the topographic aspect as a CIRCULAR mean (the arithmetic mean of 350 and 10 degrees is 180, the opposite way) together with aspect_r_bar, the mean resultant length in [0, 1]. R_bar is the number that says whether the mean direction is worth quoting: 1 means every cell in the patch faces the same way, 0 means they face every way equally and the aspect is meaningless. Costs one polynomial fit. |
patch_aspect_window_radius_metres |
float |
0 |
output |
write_patch_terrain_aspect |
Window radius [m] of the polynomial fit the terrain aspect is measured over. 0 = five cells. | Stated in cells by default because a length tuned on lidar is sub-pixel on 30 m COP30. |
write_floodplain_polygons |
bool |
false |
output |
level_algorithm_choice |
Write the floodplain as polygons (fixed_thresholds). | Output file: <stem>_floodplain_polygons.<fgb|gpkg> (write_vector_format; one polygon per connected floodplain component, 8-connected), and <stem>_floodplain_first_pass_polygons.<fgb|gpkg> when a second pass ran. |
write_floodplain_threshold_csv |
bool |
true |
output |
floodplain_threshold_algorithm_choice |
Write the table each data-chosen floodplain bound was read from. | Output files: <stem>_floodplain_hand_qq.csv and <stem>_floodplain_gradient_qq.csv (qq_gaussian), or _adaptive_cdf.csv (cdf_divergence). A threshold is only interpretable against the distribution it was cut from. |
write_terrace_field_raster |
bool |
false |
output |
terrace_reference_field_algorithm_choice |
Write the terrace reference field (fixed_thresholds). | Output file: <stem>_terrace_field.tif - swath-baseline relief, or HAND under same. |
write_floodplain_gradient_raster |
bool |
false |
output |
admission_algorithm_choice |
Write the gradient raster (m/m) the floodplain bound was applied to. | Output file: <stem>_floodplain_gradient.tif. |
write_reference_field_raster |
bool |
true |
switch |
- | Write the field the ladder actually ran on. | Output file: <stem>_reference_field.tif. Whichever field was selected, so a run is self-documenting. |
write_hand_raster |
bool |
false |
switch |
- | Write the height-above-channel raster. | Output file: <stem>_hand.tif. Feed it back as hand_fname to iterate on the ladder without recomputing HAND. |
write_band_index_raster |
bool |
true |
switch |
- | Write the per-pixel band index. | Output file: <stem>_band_index.tif (Int16, nodata -1). The authoritative statement of which level each pixel is on; the band is NOT packed into the patch label. |
write_banded_labels_raster |
bool |
true |
switch |
- | Write the per-pixel patch label. | Output file: <stem>_banded_labels.tif (UInt32). A global running counter 1..N assigned band by band; 0 is background. Join it to <stem>_surface_patches.csv on the label. |
write_valley_mask_raster |
bool |
true |
switch |
- | Write band 0 as a valley mask. | Output file: <stem>_valley_mask.tif. This is the input lsdtt-valley-centreline expects as valley_mask_fname. With ladder_level_count 2 it is exactly the valley/wall cut. |
valley_mask_closing_width_fraction |
float |
0.0 |
param |
clean_valley_mask |
Set the closing radius as a fraction of the measured valley width. 0 = use valley_mask_closing_radius_metres instead. | A radius in metres does not travel: a value that seals the slivers in a 400 m floodplain erases a 100 m one, so it has to be re-guessed for every DEM. This measures the width instead - cross sections cast perpendicular to each channel, the CUMULATIVE length of valley cells along each (so a bar or an island does not count as floor), median across sections - and takes the radius as this fraction of it. About 0.2 is a reasonable starting point. Overrides valley_mask_closing_radius_metres when set. |
valley_width_section_spacing_metres |
float |
200.0 |
param |
valley_mask_closing_width_fraction |
Spacing of the cross sections used to measure the valley width [m]. | Only used when valley_mask_closing_width_fraction is set. Closer spacing gives a better-sampled median at more cost. |
valley_width_max_half_length_metres |
float |
3000.0 |
param |
valley_mask_closing_width_fraction |
How far each cross section looks either side before giving up [m]. | A section reaching this on BOTH sides is discarded rather than counted, because it is measuring the search limit and not the valley. Set it comfortably wider than the widest valley expected. |
valley_mask_closing_radius_metres |
float |
0.0 |
param |
clean_valley_mask |
Seal narrow gaps in the valley mask with a morphological closing [m]. 0 = off. | A dilation followed by an erosion of the same radius. It removes thin non-valley features that reach into the valley - a road, a riser, a levee, scroll bars, a field boundary a little above the floor - which hole filling cannot touch, because a finger open at one end is not an enclosed hole. Because the erosion undoes the dilation, the outer extent stays about where it was. Set it to roughly half the width of the widest intrusion to close; too large and it bridges a genuine terrace riser. Applied before the opening and the hole filling. |
valley_mask_opening_radius_metres |
float |
0.0 |
param |
clean_valley_mask |
Remove narrow tendrils of the valley mask with a morphological opening [m]. 0 = off. | An erosion followed by a dilation of the same radius: valley that is narrower than about twice the radius - a thread running up a gully or along a ditch - disappears and the rest is restored to its extent. The converse of the closing, which removes thin NON-valley features. Applied after the closing and before the hole filling. A reach of real valley narrower than twice the radius is removed too. |
fill_valley_mask_holes |
bool |
true |
switch |
write_valley_mask_raster |
Fill enclosed holes in the valley mask before writing it. | A hole in the valley floor - a rise the ladder put in a higher band, a patch the size filter dropped - is a hole in the DOMAIN the centreline is traced through, and the least-cost walk goes around it. The centreline then hugs one wall instead of running down the middle. Filling enclosed holes is what makes the mask a valley rather than a valley with islands. Only affects <stem>_valley_mask.tif; the band index and the patch table keep the holes, because there the rise is a real observation. |
clean_valley_mask |
bool |
true |
switch |
write_valley_mask_raster |
Tidy band 0 before writing it as the valley mask. | Band 0 straight off the ladder is speckled and contains flat patches that have nothing to do with the channel, so it is not a drop-in for lsdtt-valley-centreline until it has been cleaned the way that tool used to clean its own. Erode, drop patches that do not reach the channel, then regrow. Only affects <stem>_valley_mask.tif; the band index, the labels and the patch table are untouched. |
valley_dilation_pixels |
int |
0 |
param |
clean_valley_mask |
Erosion / regrow radius used when cleaning the valley mask [pixels]. 0 = no erosion. | DEFAULT 0, and erosion is usually the wrong tool here. It exists to SEVER thin threads of flat ground joining one valley floor to a neighbour's across a col, so that connected-component filtering can tell them apart - it is a means of making connectivity discriminating, not a way to shrink the mask. The cost is that a reach narrower than 2 * this + 1 pixels erodes to nothing and cannot regrow: on the 10 m DEM a value of 4 (64 m) can destroy a large part of a valley floor that connectivity alone had got right. Raise it only when two valleys are genuinely merging through a col. |
valley_max_detour_ratio |
float |
0.0 |
param |
clean_valley_mask |
Maximum along-ground path to the channel, as a multiple of the straight-line distance. | A flat that sits NEAR the channel but is only reachable the long way round is a different landform, not this channel's valley floor. 0 or negative disables the test, which is the DEFAULT: on a meandering floodplain the along-ground path around a bend legitimately exceeds the straight line several times over, and the test removes real valley floor. Turn it on where flats from separate drainages are connected by a long corridor. |
restrict_valley_to_line_of_sight |
bool |
false |
param |
clean_valley_mask |
Require a straight line from each valley pixel to the channel to stay inside the mask. | A STRONG filter, written for separating one valley floor from a neighbouring one across a broad col, where connectivity alone is not enough. Default FALSE here: on a meandering floodplain a great deal of genuine valley floor has no straight line to the channel that stays inside the mask. On a 10 m DEM it can remove a large share of band 0 on its own, with every other filter disabled - which made the valley mask SMALLER than the floodplain class computed from the same field. Turn it on where two valleys meet around a col and connectivity alone merges them. |
restrict_valley_to_channel_connected |
bool |
true |
param |
clean_valley_mask |
Drop valley patches with no pixel on or beside the channel. | The valley floor of a channel touches that channel. A patch that touches none is flat ground somewhere else. |
write_surface_class_raster |
bool |
true |
switch |
- | Write the floodplain / terrace class raster. | Output file: <stem>_surface_class.tif. Only written for the hand field: floodplain and terrace are statements about height above a channel, and mean nothing on a fitted-surface residual. |
write_floodplain_mask_raster |
bool |
false |
switch |
- | Write the floodplain class on its own. | Output file: <stem>_floodplain_mask.tif. hand field only. |
write_method_record_csv |
bool |
true |
output |
- | Write the method record: every stage, every data-chosen bound, and the measured counts. | Output file: <stem>_method_record.csv (method, item, value, source). source says whether a stage came from the preset or the parameter file, or that a value was measured. With several methods in fluvial_surface_algorithm_choice (comma-separated, no spaces) each has its own, and <write_fname>_method_summary.csv, _method_comparison.csv (per pair: floodplain and terrace intersection-over-union, agreement on classified cells, the 3 x 3 confusion in cells and m^2) and _method_agreement_floodplain.tif / _terrace.tif (UInt8, bit k = method k in list order) are written. |
write_surface_class_polygons |
bool |
false |
output |
- | Write the floodplain and terrace classes as polygons (hand field). | Output file: <stem>_surface_class_polygons.<fgb|gpkg> (write_vector_format): one polygon per connected area of one class (8-connected), field class (1 floodplain, 2 terrace), label_id, pixel_count, area_m2. Outer boundaries only. |
write_surface_patch_polygons |
bool |
false |
output |
- | Write every labelled patch as a polygon, with its level. | Output file: <stem>_surface_patch_polygons.<fgb|gpkg> (write_vector_format). Fields: label_id (joins _surface_patches.csv and _banded_labels.tif), pixel_count, area_m2, band (the ladder level, or 0 floodplain / 1 terrace under fixed_thresholds). Outer boundaries only: a patch enclosing another level's patch is drawn without the hole. |
write_surface_patches_csv |
bool |
true |
switch |
- | Write one row per labelled patch. | Output file: <stem>_surface_patches.csv with label, band, class, source_id, n_pixels, area_m2, median_field_m, aspect_degrees and window_centre_m. The join key for everything downstream. |
write_patch_level_clusters_csv |
bool |
true |
switch |
- | Test whether the ladder's floodplain and terrace patches cluster in HAND. | Each classified patch is one value, its median HAND. Sorted, they split into clusters wherever neighbours are at least the required gap (kappa x E_v) apart. The statistic is the empty fraction - the share of the HAND range taken by such gaps - compared with the same number of patches at uniformly random heights over the same range (999 draws, fixed seed): p = (1 + #null >= observed) / 1000. Per ladder window with at least 5 patches, and over all patches. Ladder only (the hand field). Output file: <stem>_patch_level_clusters.csv. |
write_surface_class_csv |
bool |
true |
switch |
- | Write one row per classification record. | Output file: <stem>_surface_class.csv. hand field only. |
write_reference_field_profile_csv |
bool |
false |
switch |
- | Write a per-pixel table of the reference field along the channel. | Output file: <stem>_reference_field_profile.csv with x, y, field_m, channel_flow_distance_m, source_id, class, label and band. One row per labelled pixel: the input for a height-against-distance profile figure, and the table that proves the raster labels join to the patch and class tables. Requires the hand field with a computed HAND, since the along-channel distance comes from the pixel's HAND donor node. |
write_nearest_source_id_raster |
bool |
false |
switch |
- | Write which channel each pixel's height is measured to. | Output file: <stem>_nearest_source_id.tif (Int32, -1 off-network). The value is the source id of the pixel's HAND DONOR - the channel node it actually drains to - not the nearest channel by straight-line distance, so it partitions the DEM the same way the height reference does. Use it to look at one channel's results in isolation. Needs a computed HAND: a supplied hand_fname carries no donor nodes. |
write_ladder_break_metric_csv |
bool |
false |
switch |
- | Write the break DECISION, not just the break. | Output file: <stem>_ladder_break_metric.csv, one row per histogram bin per window: the raw count, the smoothed density the separation ratio is measured on, which detected gap the bin falls in, and whether a candidate or the chosen break lands there. The chosen break on a raw histogram looks arbitrary, and whether it beat several candidates or was the only one on offer cannot be recovered from the answer alone. |
write_ladder_window_id_raster |
bool |
false |
switch |
- | Write which along-channel window each pixel's breaks came from. | Output file: <stem>_ladder_window_id.tif (Int16, -1 where the global partition was used or the pixel is not banded). The value is the window's ordinal within its channel, counting downstream - a reach number. Use it to trace a break that looks wrong back to the ground that produced it. |
write_ladder_histogram_csv |
bool |
false |
switch |
- | Write the reference-field density each window's breaks were chosen from. | Output file: <stem>_ladder_histogram.csv, one row per histogram bin per window, with the bin containing each break flagged. THIS is the diagnostic that says whether a break separates two populations or merely cuts through one - a deep, wide dip between two modes means two surfaces; a shallow scoop through one broad mode means one surface and an arbitrary cut. Neither the resolvability floor nor the chosen break value can tell you which you have. Plot it with lsdviz3-ladder-density. |
ladder_hist_plot_bins |
int |
80 |
param |
write_ladder_histogram_csv |
Bin count for the per-window density in write_ladder_histogram_csv. | Linear in field units, not log10, so it can be read directly against the breaks. Distinct from ladder_hist_bins, which is the log10 histogram the antimode finder searches for dips. |
write_ladder_windows_csv |
bool |
true |
switch |
- | Write the per-window resolvability report. | Output file: <stem>_ladder_windows.csv with the level count chosen, the status, the breaks, the band centres and the gap that had to be cleared. READ THIS FIRST: what may be claimed about a ladder depends on whether its windows resolved, and that is not visible in any raster. |
write_channel_scope_raster |
bool |
false |
switch |
- | Write the in-scope channel network as a raster. | Output file: <stem>_channel_scope.tif (UInt32, source_id + 1). |
write_channel_scope_nodes_csv |
bool |
false |
switch |
- | Write the in-scope channel nodes as a table. | Output file: <stem>_channel_scope_nodes.csv. |
write_point_snap_csv |
bool |
false |
switch |
- | Write where each supplied point snapped to. | Outputs: <stem>_point_snaps.csv plus raw and snapped FlatGeobuf point layers, so both can be overlaid in one QC figure. |
write_channel_network |
bool |
false |
switch |
- | Write the channel network as vector layers. | Master switch for the three layers below. The network this tool measured its surfaces against is written here so you do not have to chain a second CLI to see it. |
write_channel_network_lines |
bool |
true |
switch |
write_channel_network |
Write channel links as lines. | Lines are the compact default representation of the network used to fit the surfaces; per-node points remain opt-in. Output file: <stem>_channel_network_lines.fgb. |
write_channel_network_points |
bool |
false |
switch |
write_channel_network |
Write channel nodes as points. | Output file: <stem>_channel_network_points.fgb. |
write_junction_network_points |
bool |
false |
switch |
write_channel_network |
Write junctions as points. | Output file: <stem>_junction_network_points.fgb. |
Floodplain
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
floodplain_threshold_algorithm_choice |
string |
preset |
param |
- | How the floodplain bounds are chosen: preset, fixed, qq_gaussian or cdf_divergence. | fixed uses max_floodplain_hand_metres and max_floodplain_gradient as given. qq_gaussian is Clubb et al. (2017): for each quantity, the lowest value at which the data rejoin a Gaussian drawn through two percentiles of the quantile-quantile plot (floodplain_qq_*), and stay on it. cdf_divergence fits a family of distributions and takes the value at which the empirical CDF most exceeds the best fit. The chosen values are printed and written to the ingested-parameters file, so a run can be repeated with fixed. |
max_floodplain_gradient |
float |
0.1 |
param |
floodplain_threshold_algorithm_choice |
Gradient (m/m) above which ground is not floodplain. | A gradient, not an angle (gradient = tan(angle)). The gradient bound of admission floodplain_thresholds, used as given under floodplain_threshold_algorithm_choice fixed. |
floodplain_bound_algorithm_choice |
string |
auto |
param |
admission_algorithm_choice |
Which bounds the floodplain admission uses: auto, hand_and_gradient or hand_only (admission floodplain_thresholds). | hand_and_gradient: HAND below its bound AND gradient below its bound, as Clubb et al. (2017). hand_only: HAND alone - for low-relief landscapes, where the floodplain is cut into a surface that is itself nearly flat, and field-, road- and levee-scale relief puts much of the true floodplain above a data-chosen gradient bound. auto (default): hand_only when more than low_relief_min_area_fraction of the DEM's floodplain gradient is below low_relief_max_gradient, else hand_and_gradient. The fraction and the branch are in the run log, and the branch is recorded in _ingested_params.param so a rerun repeats it. On low-relief ground the fraction (below 0.1) can approach 1, and the gradient bound can then remove the floodplain. |
low_relief_max_gradient |
float |
0.1 |
param |
floodplain_bound_algorithm_choice |
Gradient [m/m] below which a cell counts as low relief (floodplain_bound_algorithm_choice auto). | Read on the floodplain gradient (floodplain_gradient_window_radius_metres). Gradient depends on resolution and window, so the fraction is too. |
low_relief_min_area_fraction |
float |
0.8 |
param |
floodplain_bound_algorithm_choice |
Fraction of the DEM below low_relief_max_gradient above which auto drops the gradient bound. | - |
floodplain_gradient_window_radius_metres |
float |
0 |
param |
admission_algorithm_choice |
Window radius [m] of the polynomial fit the floodplain gradient is read over. 0 = three cells. | LSDTopoTools2's surface_fitting_radius (30 m by default, three cells on 10 m data). |
run_terrace_classification |
bool |
true |
switch |
level_algorithm_choice |
Find terraces as well as the floodplain (fixed_thresholds). | false = the floodplain alone, as LSDTopoTools2 allowed: the terrace field (and its swath baseline, the most memory- and time-hungry stage on a large DEM) is never built and no terrace class is assigned. The floodplain mask is identical either way: it is decided before the terraces and independently of them. |
min_terrace_height_above_floodplain_metres |
float |
0 |
param |
level_algorithm_choice |
How far above the floodplain bound a terrace starts [m] (fixed_thresholds). Must be >= 0. | Terraces are ground whose terrace field exceeds max_floodplain_hand_metres plus this, so the two never overlap. LSDTopoTools2's minimum_terrace_height (whose window could overlap the floodplain). Different from min_terrace_hand_metres, the ladder's absolute bound. |
max_terrace_height_metres |
float |
0 |
param |
level_algorithm_choice |
Highest a terrace may stand on the terrace field [m] (fixed_thresholds). 0 = no ceiling. | With no ceiling the swath half-width and the gradient bound are the only limits on how high terraces reach. |
fill_floodplain_holes |
bool |
false |
param |
level_algorithm_choice |
Fill enclosed holes in the floodplain (fixed_thresholds). | LSDTopoTools2's fill_floodplain (lsdtt-valley-metrics), done with binary_fill_holes rather than OpenCV: a background region not connected to the outside of the valid DEM is a hole. Default false, but TRUE under the clubb2017 preset unless the parameter file names it; the value used is in the run log's stage table. |
floodplain_second_pass_algorithm_choice |
string |
none |
param |
level_algorithm_choice |
A second HAND bound read off the floodplain itself: none or qq_upper_departure (fixed_thresholds). | qq_upper_departure: the QQ plot of HAND over the first-pass floodplain cells, against the same Gaussian line, and the HIGHEST quantile at which the data still agree with it (the mirror of the Clubb rule, same floodplain_qq_* settings). Above it HAND departs upward: wide flats standing above the valley floor - fans at tributary mouths, upland basins. The floodplain is then rebuilt with that HAND bound (same gradient bound, channel contact and size filter) and its holes are always filled; terraces are measured from the second-pass bound. Not part of Clubb et al. (2017). The first pass is still written (_floodplain_first_pass_mask.tif, _floodplain_first_pass_polygons) and the second QQ table is <stem>_floodplain_hand_qq_second_pass.csv. |
max_floodplain_hand_metres |
float |
1 |
param |
- | HAND (m) above which ground is not floodplain. | Under level_algorithm_choice fixed_thresholds (clubb2017) the floodplain HAND bound when it is not chosen from the data. Under the ladder it is not used: the floodplain is the lowest level touching an in-scope channel in each reach window. Only meaningful for the hand field. Replaces floodplain_max_height_metres. |
Threshold selection
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
floodplain_qq_lower_percentile |
float |
25 |
param |
floodplain_threshold_algorithm_choice |
Lower percentile of the QQ cloud the Gaussian reference line is drawn through (qq_gaussian). | 25 and 75 (the IQR) are the values Clubb et al. (2017) used. |
floodplain_qq_upper_percentile |
float |
75 |
param |
floodplain_threshold_algorithm_choice |
Upper percentile of the QQ cloud the Gaussian reference line is drawn through (qq_gaussian). | Must be above the lower percentile. |
floodplain_qq_threshold_fraction |
float |
0.01 |
param |
floodplain_threshold_algorithm_choice |
Fraction of the data range within which a quantile counts as on the Gaussian line (qq_gaussian). | 0.01 (1%) is the paper's value. The threshold is the lowest quantile that is on the line and stays on it for the next 200 quantiles of 10000. Larger values admit more ground. |
Classification
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
min_terrace_hand_metres |
float |
0.0 |
param |
- | Lower HAND bound of the terrace class [m]. | As max_floodplain_hand_metres, at the other end. Only meaningful for the hand field. Replaces terrace_min_height_metres. |
channel_adjacency_radius_metres |
float |
0.0 |
param |
- | Distance within which a surface counts as channel-adjacent [m]. | 0 = touching only. |
valley_corridor_half_width_metres |
float |
5000.0 |
param |
- | Valley-corridor half-width [m]. 0 or negative = no limit. | A terrace must have at least one pixel within this Euclidean distance of an in-scope channel. This is what keeps the regional upland out of the terrace class: an interfluve is also flat and also elevated above the trunk channel, but it is not a fluvial terrace. |
clip_corridor_per_pixel |
bool |
true |
param |
valley_corridor_half_width_metres |
Apply the corridor limit per pixel as well as per surface. | Stops a surface that merely touches the valley edge from carrying its class across the whole interfluve. |
max_band_spread_metres |
float |
50.0 |
param |
- | QC tripwire on the field spread within one surface [m]. 0 disables. | A surface whose robust field spread still exceeds this cannot take the terrace class. Under band-then-label a band's spread is bounded by its own breaks, so this should almost never fire; it is retained as a tripwire, not as a mechanism. |
Pit filling
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
fill_before_routing |
bool |
true |
switch |
- | Choose whether to pit-fill the DEM before building the flow network. | ASKS: should the DEM be filled at all? Filling is OPTIONAL here and this is a genuine analytical choice. True (the default) routes over a pit-free surface. False builds the flow network on the UNFILLED surface, so flow paths terminate at sinks instead of being carried through them. Do not confuse this with raster_is_filled, which is an assertion that the input is already filled. |
fill_flat_resolution_choice |
string |
inline_gradient |
param |
- | How flats are made drainable: inline_gradient, barnes2014b_gradient or barnes2014b_float_step. | inline_gradient (default, unchanged): the Priority-Flood fill imposes fill_min_slope_for_routing during the flood. The barnes2014b options make flow converge across flats (Barnes et al. 2014, doi:10.1016/j.cageo.2013.01.009). barnes2014b_gradient uses fill_min_slope_for_routing (m/m, must be > 0) as the minimum gradient; flats are raised about 3-4x more, capped below their rims, and capped flats are reported. barnes2014b_float_step is equivalent to RichDEM ResolveFlats on float32 in its first pass: it ignores fill_min_slope_for_routing and its slopes depend on elevation (of order ULP(z) / cell size). With either barnes2014b option the result is always routable: cells the first pass leaves without a lower neighbour are refilled and resolved again (up to 4 rounds), and any still left are raised by a final priority-flood pass by one float32 step each; the report gives the rounds and the cells changed after the first pass. |
Flow routing
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
fill_min_slope_for_routing |
float |
0.0001 |
param |
fill_before_routing |
Minimum imposed slope when filling for routing [m/m]. | Used only when fill_before_routing is true. inline_gradient (default): imposed during the Barnes et al. 2014 Priority-Flood. barnes2014b_gradient: the minimum gradient across flats. barnes2014b_float_step: ignored. |
boundary_conditions |
string |
oooo |
param |
- | Edge boundary codes, north east south west. | o = open, n = no-flux, p = periodic, b = forced outlet. |
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. |
Channel scope
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
break_flats_with_channels |
bool |
false |
switch |
- | Remove in-scope channel pixels from the admitted set before banding. | Default FALSE, changed from the old flat-surface-finder behaviour. It used to be needed to stop one surface spanning a channel; banding by height does that on its own, since the two banks of an incised channel are not in the same band as the channel. All it does now is keep the channel itself out of a surface, which matters for area statistics and little else. |
connect_surfaces_to_channels |
bool |
false |
switch |
- | Record which in-scope channel each surface is linked to. | Adds the connection diagnostics used to attribute a surface to a reach. The patch table already carries source_id from the height reference's donor, so this is for the cases where the linkage itself is the question. |
drainage_injection_points_fname |
string |
- | io |
- | Vector of points injecting extra drainage area, in read_path. | For a channel entering the DEM from outside it, where the contributing-pixel threshold alone would not start a channel at the boundary. Empty = no injection. |
source_points_fname |
string |
- | io |
- | Channel source points (CSV or vector), in read_path. | Empty = every channel above the threshold is in scope. Supplying sources restricts the analysis to those channels, which is how you measure terraces along one trunk rather than every rill in the DEM. |
source_points_input_format |
string |
auto_detect |
io |
source_points_fname |
Source point format: auto_detect, csv or ogr_vector_layer. | auto_detect infers from the file extension. |
source_points_layer_name |
string |
- | io |
source_points_fname |
Layer name within a source point vector file. | Empty = the first layer. |
source_points_latitude_field_name |
string |
latitude |
io |
source_points_fname |
Latitude column name in a source point CSV. | CSV points are read as EPSG:4326 and transformed to the DEM CRS. |
source_points_longitude_field_name |
string |
longitude |
io |
source_points_fname |
Longitude column name in a source point CSV. | See source_points_latitude_field_name. |
outlet_points_fname |
string |
- | io |
- | Channel outlet points (CSV or vector), in read_path. | Truncates each traced channel at its matched outlet, so the analysis covers a reach rather than everything down to the DEM edge. |
outlet_points_input_format |
string |
auto_detect |
io |
outlet_points_fname |
Outlet point format: auto_detect, csv or ogr_vector_layer. | As source_points_input_format. |
outlet_points_layer_name |
string |
- | io |
outlet_points_fname |
Layer name within an outlet point vector file. | Empty = the first layer. |
outlet_points_latitude_field_name |
string |
latitude |
io |
outlet_points_fname |
Latitude column name in an outlet point CSV. | See source_points_latitude_field_name. |
outlet_points_longitude_field_name |
string |
longitude |
io |
outlet_points_fname |
Longitude column name in an outlet point CSV. | See source_points_latitude_field_name. |
auto_detect_outlet_points |
bool |
true |
switch |
source_points_fname |
Infer source and outlet roles from a single combined point file. | A supplied point that snaps strictly downstream of another is treated as that one's outlet. Ignored when outlet_points_fname is given, because then the roles are stated directly. |
snap_sources_to_channel_threshold |
bool |
true |
switch |
source_points_fname |
Snap supplied source points onto the threshold channel network. | A hand-digitised point rarely lands exactly on the routed channel. |
source_snap_max_search_radius_pixels |
int |
20 |
param |
snap_sources_to_channel_threshold |
Search radius when snapping a source point [pixels]. | A point that finds no threshold channel within this radius is reported as unsnapped in the point snap diagnostics. |
outlet_snap_max_distance_metres |
float |
500.0 |
param |
outlet_points_fname |
Maximum distance an outlet may be moved onto a traced channel [m]. | Outlets snap onto the traced flow paths, not onto the channel network, so a slightly misplaced outlet still truncates the intended channel. |
Valley floor
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
force_valley_hand_metres |
float |
0.0 |
param |
- | Define the valley floor directly as height above channel below this [m], instead of using the ladder's break. 0 = use the ladder. | The ladder finds a break from the data; sometimes you already know the answer - from field mapping, a flood stage, a published terrace height - and want it applied rather than rediscovered. Combined with force_valley_max_distance_metres this defines the valley floor outright: HAND below the threshold AND within the distance. The band index, labels and patch table are UNAFFECTED and still come from the ladder; only <stem>_valley_mask.tif is forced, so the two can be compared. |
force_valley_max_distance_metres |
float |
0.0 |
param |
- | Euclidean distance to an in-scope channel bounding the forced valley floor [m]. 0 = no distance limit. | The companion to force_valley_hand_metres, and the reason the pair is useful: a HAND threshold alone admits every low patch in the DEM, including ground belonging to other drainages entirely. The distance limit is what confines it to this channel's valley. Usable on its own as well, if the ladder is finding the break but reaching too far. |
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. |
Orientations
| Parameter | Type | Default | Role | Parent | Description | Guidance |
|---|---|---|---|---|---|---|
bearings_relative_to_true_north |
bool |
false |
param |
- | Write bearings, aspects and orientations clockwise from true north. | Off (default): they are clockwise from grid north (grid up), so in an oblique frame with the feature along +x, 90 degrees means along the feature. On: each value is converted with the grid convergence at its own location (each cell of a raster, each row of a table), which also removes the up to about 3 degree difference between UTM grid north and true north. Angles between two directions (junction angles, deflections, contrasts) are unchanged, and hillshade illumination always stays relative to grid up. Needs a raster with a CRS. |