riverarchitect.sharc

SHArC: habitat suitability and Seasonal Habitat Area.

The open-source replacement for the ArcGIS SHArC module. It turns 2D hydrodynamic results into a map of how good the habitat is for a given fish species and lifestage, and then into a single number a project can be judged on.

The chain

  1. Habitat suitability curves come from Fish.xlsx: for each species and lifestage, a piecewise-linear curve mapping flow depth, flow velocity, substrate size or cover radius onto a suitability index between 0 and 1.

  2. Hydraulic HSI rasters apply those curves to the depth and velocity rasters of a discharge, giving dsi (depth) and vsi (velocity).

  3. Composite HSI (cHSI) combines them, by geometric mean or product, optionally including a cover HSI, and is masked to the wetted area.

  4. Usable area at a discharge is the area where cHSI exceeds a threshold, optionally weighted by the mean cHSI there.

  5. SHArea integrates usable area over the flow duration curve, so a habitat area that only exists at a rare discharge counts for little.

Relation to the original

cHSI.nested_con_raster_calc built one Con() raster per curve segment and took their cell-wise maximum. That is a piecewise-linear interpolation written the only way arcpy map algebra allowed; here it is apply_curve(), which is numpy.interp() with the same end behaviour - hold the first value below the curve, drop to zero above it. The SHArea integration reproduces the =(E5-E4)/100*F5 column of the original CONDITION_sharea_template.xlsx.

class riverarchitect.sharc.FishDatabase(path=None)[source]

Bases: object

Habitat suitability curves, read from a Fish.xlsx in the original layout.

Parameters:

path (str) – workbook path. Defaults to the one shipped with the package.

Variables:

species (list) – species common names, in workbook order.

resolve_species(species)[source]

Match a species name against the workbook, ignoring case and spacing.

The workbook writes "Chinook Salmon"; papers, file names and the rest of this package write "Chinook salmon". Matching exactly would make a caller’s choice of capitalisation decide whether an analysis runs, so it does not.

Parameters:

species (str) – the name to look up.

Returns:

the name as the workbook spells it.

Return type:

str

Raises:

KeyError – when no species matches.

resolve_lifestage(species, lifestage)[source]

Match a lifestage of a species, ignoring case and spacing.

Returns:

the label as the workbook spells it.

Return type:

str

Raises:

KeyError – when the species has no such lifestage.

lifestages(species)[source]

Lifestages available for a species, as the workbook labels them.

pairs()[source]

Yield every (species, lifestage) the database defines.

static shortname(species, lifestage)[source]

Four-letter code the original used in file names: chsp, chju, …

curve(species, lifestage, parameter)[source]

Habitat suitability curve for one species, lifestage and parameter.

Parameters:
  • species (str) – common name, as in the workbook.

  • lifestage (str) – "spawning", "fry", "juvenile" or "adult".

  • parameter (str) – "u", "h" or "substrate".

Returns:

(x, y) arrays, or None when the workbook defines no curve.

Return type:

tuple

cover_value(species, lifestage, cover_type)[source]

Radius and suitability of a cover type, or None when it is not defined.

season_dates(species, lifestage)[source]

Start and end of the lifestage’s season, as (month, day) pairs.

Rows 6 and 7 of the lifestage’s suitability column, which is where cFish.get_season_dates read them. The year in the cell is a placeholder and is ignored; a season that ends before it starts wraps into the next year.

Returns:

((start_month, start_day), (end_month, end_day)), or None when the workbook gives no season for this lifestage.

Return type:

tuple

travel_thresholds(species, lifestage)[source]

{"h_min": ..., "u_max": ...} swimming thresholds, where defined.

riverarchitect.sharc.apply_curve(array, curve)[source]

Map raster values onto a habitat suitability index through a piecewise-linear curve.

Replaces cHSI.HHSI.nested_con_raster_calc. Below the first curve point the first suitability is held; above the last point the suitability is zero, not held - a depth or velocity beyond the curve is unsuitable, not maximally suitable, and clamping there would invent habitat at the highest discharges.

Parameters:
Returns:

suitability index, NoData preserved.

Return type:

numpy.ndarray

riverarchitect.sharc.cover_hsi(species, lifestage, layers, profile, unit='us', fish=None, depth=None)[source]

Cover habitat suitability: shelter from substrate, cobbles, boulders, plants and wood.

Depth and velocity say whether a fish can be somewhere. Cover says whether it is safe to be there - a fry in open water at the right depth and velocity is still a meal. The original offered this as the cover option of SHArC and it is what separates a hydraulic habitat model from an ecohydraulic one.

Two kinds of layer, dispatched as cHSI.CovHSI.call_analysis did:

substrate

a grain size raster, mapped through its suitability curve like depth or velocity.

cobbles, boulders, plants, wood

presence layers. cobbles and boulders are cut out of a grain size raster by the diameter ranges in GRAIN_SIZE_LIMITS; plants and wood are given directly. Each element then shelters everything within its radius, taken from the first x value of its curve, and those cells take the curve’s suitability.

The result is the cell-wise maximum across the types present: the best shelter available at a cell is what counts, not the sum of every kind of it.

Parameters:
  • species (str) – common name; case and spacing are ignored.

  • lifestage (str) – lifestage; case and spacing are ignored.

  • layers (dict) – {cover type: array}. cobbles and boulders may be omitted and derived from a substrate grain size raster instead.

  • profile (dict) – the raster profile the layers are on, for the cell size.

  • unit (str) – "us" or "si"; the grain limits are converted accordingly.

  • fish (FishDatabase) – the curve database. Built by default.

  • depth (numpy.ndarray) – flow depth. When given, cover is cropped to cells at least as deep as the first point of the depth curve, as the original’s crop_input_raster did - cover a fish cannot reach shelters nothing.

Returns:

(cover, used) - the suitability array with NoData where no cover applies, and the list of cover types that contributed. (None, []) when none did.

Return type:

tuple

class riverarchitect.sharc.SHArC(condition, unit='us', combine_method='geometric_mean', fish=None, threshold=0.4)[source]

Bases: object

Habitat suitability analysis for one condition.

Parameters:
  • condition (Condition or str) – the condition, or its name.

  • unit (str) – "us" or "si"; must match the condition’s rasters.

  • combine_method (str) – "geometric_mean" or "product".

  • fish (FishDatabase) – the curve database. Defaults to the packaged Fish.xlsx.

  • threshold (float) – cHSI above which a cell counts as usable habitat.

Variables:

error (bool) – True when at least one discharge could not be processed.

property discharges

Discharges with both a depth and a velocity raster, ascending.

composite_hsi(species, lifestage, discharge, reference=None, cover=None, cover_layers=None)[source]

Composite habitat suitability at one discharge.

Parameters:
  • species (str) – common name; case and spacing are ignored.

  • lifestage (str) – lifestage; case and spacing are ignored.

  • discharge (float) – the discharge to evaluate.

  • reference (dict) – profile every raster is aligned onto.

  • cover (numpy.ndarray) – a ready-made cover suitability array.

  • cover_layers (dict) – cover inputs, from which one is built with cover_hsi(). Ignored when cover is given.

Returns:

(chsi, profile), or (None, None) when the curves are missing.

Return type:

tuple

usable_area(chsi, profile, weighted=False)[source]

Area where cHSI exceeds the threshold, optionally weighted by the mean cHSI.

cover_layers(extra=None)[source]

Cover layers available in the condition, on its own grid.

Looks for the rasters the cover analysis can use: the grain size raster named by input_definitions.inp for substrate (and, through it, cobbles and boulders), and plants.tif / wood.tif beside the condition.

Parameters:

extra (dict) – {cover type: path or array} to add or override.

Returns:

{cover type: array}, empty when the condition has no cover data.

Return type:

dict

run(species, lifestage, discharges=None, output_dir=None, weighted=False, write_rasters=True, flow_duration=None, cover=False)[source]

Map habitat suitability across discharges and compute SHArea.

Parameters:
  • species (str) – common name from the fish database.

  • lifestage (str) – lifestage from the fish database.

  • discharges (list) – discharges to evaluate. Defaults to all available.

  • output_dir (str) – where the cHSI rasters go.

  • weighted (bool) – weight usable area by the mean cHSI, as the original’s apply weighting option did.

  • write_rasters (bool) – write one csi_<code><Q>.tif per discharge.

  • flow_duration (str or dict) – a flow duration workbook, or a {discharge: % exceedance} mapping. Without it SHArea is not computed.

  • cover (bool or dict) – include a cover habitat suitability index. True uses whatever cover_layers() finds in the condition; a dict supplies or overrides layers. See cover_hsi().

Returns:

per_discharge rows, sharea when a flow duration was given, and the paths written.

Return type:

dict

static sharea(rows, flow_duration)[source]

Integrate usable area over the flow duration curve.

Reproduces the (E_i - E_{i-1}) / 100 * F_i column of the original workbook: a Riemann sum of usable area over cumulative exceedance probability, so habitat that exists only at a rare discharge contributes little.

Parameters:
  • rows (list) – {"discharge": ..., "usable_area": ...} entries.

  • flow_duration (dict) – {discharge: cumulative % exceedance}.

Returns:

seasonal habitat area, in the area unit of the usable areas.

Return type:

float

riverarchitect.sharc.COMBINE_METHODS = ('geometric_mean', 'product')

Ways of combining the individual suitability indices into a composite one.

riverarchitect.sharc.COVER_TYPES = ('substrate', 'cobbles', 'boulders', 'plants', 'wood')

Cover types the habitat database defines a curve for, in the order the original combined them. substrate is a suitability curve on grain size; the rest are presence layers whose influence spreads over a radius.

riverarchitect.sharc.GRAIN_SIZE_LIMITS = {'boulders': (0.256, 100.0), 'cobbles': (0.064, 0.256)}

Grain diameter range that defines each grain-based cover type, in metres, as cHSI.CovHSI.define_grain_size had them. Converted for a condition in U.S. customary units.