Visualization Module

Diagnostic and debug visualizations for terrain processing.

This subpackage provides visualization tools for flow analysis, edge extrusion debugging, bounds pipeline transformations, and linear feature layers (streams, roads, trails).

Flow Diagnostics

Standardized visualizations for flow accumulation analysis: DEM views, ocean masks, water bodies, drainage networks, and validation summaries.

Flow diagnostic visualization functions.

This module provides standardized visualizations for flow accumulation analysis, including DEM views, ocean masks, water bodies, drainage networks, and validation summaries.

Example

from src.terrain.visualization.flow_diagnostics import create_flow_diagnostics

# Generate all diagnostic plots create_flow_diagnostics(

dem=dem, dem_conditioned=dem_conditioned, ocean_mask=ocean_mask, flow_dir=flow_dir, drainage_area=drainage_area, upstream_rainfall=upstream_rainfall, precip=precip, output_dir=Path(“output/diagnostics”), lake_mask=lake_mask, lake_outlets=lake_outlets,

)

src.terrain.visualization.flow_diagnostics.save_flow_plot(data, title, output_path, cmap, label='', log_scale=False, mask=None, overlay_data=None, overlay_cmap=None, overlay_alpha=0.7, vmin=None, vmax=None, figsize=(12, 10), dpi=150, pixel_perfect=True)[source]

Save a single flow diagnostic plot to file.

Parameters:
  • data (np.ndarray) – 2D array of data to plot

  • title (str) – Plot title

  • output_path (Path) – Output file path

  • cmap (str) – Matplotlib colormap name

  • label (str, optional) – Colorbar label

  • log_scale (bool, optional) – Apply log10 transformation

  • mask (np.ndarray, optional) – Boolean mask for data (True = mask out)

  • overlay_data (np.ndarray, optional) – Boolean array for overlay

  • overlay_cmap (str, optional) – Colormap for overlay

  • overlay_alpha (float, optional) – Alpha for overlay (default: 0.7)

  • vmin (float, optional) – Colorbar limits

  • vmax (float, optional) – Colorbar limits

  • figsize (tuple, optional) – Figure size in inches

  • dpi (int, optional) – Output resolution

  • pixel_perfect (bool, optional) – If True, save pixel-perfect raw image + annotated thumbnail (default: True)

Returns:

Path to saved file (raw version if pixel_perfect=True)

Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_dem(dem, output_path, title=None)[source]

Plot original DEM elevation.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_ocean_mask(ocean_mask, output_path)[source]

Plot ocean mask with coverage percentage.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_water_bodies(dem, lake_mask, output_path, flow_dir=None, lake_outlets=None, lake_inlets=None)[source]

Plot water bodies with optional flow arrows for outlets/inlets.

Shows DEM as background with lakes overlaid in blue. If flow_dir is provided, draws quiver arrows showing: - Red arrows: outlets (water leaving lakes) - Green arrows: inlets (water entering lakes)

Saves pixel-perfect raw image + annotated thumbnail with arrows.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_endorheic_basins(dem, basin_mask, output_path)[source]

Plot endorheic basins overlaid on DEM. Pixel-perfect output.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_conditioned_dem(dem_conditioned, output_path)[source]

Plot conditioned (depression-filled) DEM.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_breach_depth(dem, breached_dem, ocean_mask, output_path)[source]

Plot breach depth (how much elevation was lowered during breaching).

Breach depth shows where and how much the DEM was lowered to create flow paths. Positive values indicate breaching occurred (elevation was lowered).

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_fill_depth(dem, dem_conditioned, ocean_mask, output_path, breached_dem=None)[source]

Plot depression fill depth.

Fill depth represents how much water was added to fill depressions. If breached_dem is provided, computes fill depth as (conditioned - breached), which correctly shows only the FILLING operation (not breaching). Otherwise falls back to (conditioned - original), which includes both operations.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_flow_direction(flow_dir, output_path)[source]

Plot D8 flow direction codes.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_drainage_area(drainage_area, output_path, lake_mask=None)[source]

Plot drainage area (log scale) with optional lake overlay.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_drainage_area_comparison(dem, dem_conditioned, ocean_mask, output_path)[source]

Create comparison plot of drainage area from raw vs conditioned DEM.

Shows three panels: - Drainage area from raw DEM (left) - Drainage area from conditioned DEM (middle) - Absolute difference between them (right)

This verifies that conditioning (breaching + filling) affects flow routing.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_stream_network(dem, drainage_area, output_path, lake_mask=None, percentile=95, base_dim=0.4)[source]

Plot stream network extracted from drainage area. Pixel-perfect output.

Streams are defined as cells with drainage area >= percentile threshold.

Parameters:
  • base_dim (float) – Dim factor for base DEM (0.0 = black, 1.0 = full brightness). Default 0.4.

  • dem (ndarray)

  • drainage_area (ndarray)

  • output_path (Path)

  • lake_mask (ndarray | None)

  • percentile (float)

Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_stream_overlay(base_data, stream_threshold_data, stream_color_data, output_path, base_cmap='viridis', stream_cmap='plasma', percentile=95, stream_alpha=1.0, base_label='Base Metric', stream_label='Stream Metric', title='Stream Network Overlay', lake_mask=None, base_log_scale=True, stream_log_scale=True, variable_width=False, min_width=1, max_width=4, base_dim=0.5)[source]

Plot stream network colored by a metric, overlaid on a base map.

Creates visualization with streams extracted from one metric and colored by another (or the same) metric, over a full-coverage base map.

Parameters:
  • base_data (np.ndarray) – Data for background map coloring (e.g., discharge_potential, elevation)

  • stream_threshold_data (np.ndarray) – Data for extracting streams (usually drainage_area for percentile threshold)

  • stream_color_data (np.ndarray) – Data for coloring stream pixels (e.g., discharge_potential, upstream_rainfall)

  • output_path (Path) – Output file path

  • base_cmap (str) – Matplotlib colormap for base map (default: viridis)

  • stream_cmap (str) – Matplotlib colormap for streams (default: plasma)

  • percentile (float) – Percentile threshold for stream extraction (default: 95 = top 5%)

  • stream_alpha (float) – Stream transparency: 1.0 = opaque, 0.7 = semi-transparent (default: 1.0)

  • base_label (str) – Colorbar label for base map

  • stream_label (str) – Label describing stream metric (used in title)

  • title (str) – Plot title

  • lake_mask (np.ndarray, optional) – Lake mask for overlay (integer IDs or boolean)

  • base_log_scale (bool) – Apply log10 to base data (default: True)

  • stream_log_scale (bool) – Apply log10 to stream color data (default: True)

  • variable_width (bool) – Scale stream line width by metric value (default: False)

  • min_width (int) – Minimum stream width in pixels when variable_width=True (default: 1)

  • max_width (int) – Maximum stream width in pixels when variable_width=True (default: 4)

  • base_dim (float) – Dim factor for base map (0.0 = black, 1.0 = full brightness). Default 0.5. Dimming the base makes streams more visible.

Returns:

Path to saved raw pixel-perfect file

Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_precipitation(precip, output_path, is_real=False)[source]

Plot precipitation data.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_upstream_rainfall(upstream_rainfall, output_path, lake_mask=None)[source]

Plot upstream accumulated rainfall (log scale).

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_discharge_potential(drainage_area, upstream_rainfall, output_path, lake_mask=None, log_scale=True)[source]

Plot discharge potential (drainage × rainfall-weighted).

Combines drainage area with upstream rainfall to show where the biggest flows occur.

Parameters:
Return type:

Path

src.terrain.visualization.flow_diagnostics.plot_validation_summary(output_path, dem_shape, ocean_cells, max_drainage, max_upstream, num_lakes=0, num_outlets=0, num_basins=0, basin_cells=0, cycles=0, sample_size=1000, mass_balance=100.0, drainage_violations=0, is_real_precip=False)[source]

Plot validation summary as a text table.

Parameters are all the statistics to display in the summary.

Parameters:
  • output_path (Path)

  • dem_shape (tuple)

  • ocean_cells (int)

  • max_drainage (float)

  • max_upstream (float)

  • num_lakes (int)

  • num_outlets (int)

  • num_basins (int)

  • basin_cells (int)

  • cycles (int)

  • sample_size (int)

  • mass_balance (float)

  • drainage_violations (int)

  • is_real_precip (bool)

Return type:

Path

src.terrain.visualization.flow_diagnostics.create_flow_diagnostics(dem, dem_conditioned, ocean_mask, flow_dir, drainage_area, upstream_rainfall, precip, output_dir, lake_mask=None, lake_outlets=None, lake_inlets=None, basin_mask=None, breached_dem=None, num_basins=0, is_real_precip=False, cycles=0, sample_size=1000, mass_balance=100.0, drainage_violations=0)[source]

Generate all flow diagnostic visualizations.

Creates a comprehensive set of plots for flow accumulation analysis: 1. Original DEM 2. Ocean mask 3. Water bodies (if lake_mask provided) 3b. Endorheic basins (if basin_mask provided) 4. Conditioned DEM 5. Fill depth 6. Flow direction 7. Drainage area 8. Stream network with lakes 9. Precipitation 10. Upstream rainfall 10b. Discharge potential (log scale) 10c. Discharge potential (linear scale) 11. Validation summary

Parameters:
  • dem (np.ndarray) – Original DEM

  • dem_conditioned (np.ndarray) – Depression-filled DEM

  • ocean_mask (np.ndarray) – Boolean ocean mask

  • flow_dir (np.ndarray) – D8 flow direction codes

  • drainage_area (np.ndarray) – Accumulated drainage area (cells)

  • upstream_rainfall (np.ndarray) – Accumulated upstream rainfall

  • precip (np.ndarray) – Precipitation data

  • output_dir (Path) – Directory for output images

  • lake_mask (np.ndarray, optional) – Lake mask (integer IDs)

  • lake_outlets (np.ndarray, optional) – Boolean mask of lake outlet cells

  • lake_inlets (np.ndarray, optional) – Boolean mask of lake inlet cells

  • basin_mask (np.ndarray, optional) – Boolean mask of endorheic basins

  • num_basins (int, optional) – Number of endorheic basins

  • is_real_precip (bool, optional) – Whether precipitation is real (WorldClim) or synthetic

  • cycles (int, optional) – Number of flow cycles detected (validation)

  • sample_size (int, optional) – Sample size for cycle detection

  • mass_balance (float, optional) – Mass balance percentage

  • drainage_violations (int, optional) – Number of drainage violations

  • breached_dem (ndarray | None)

Returns:

Output directory path

Return type:

Path

src.terrain.visualization.flow_diagnostics.vectorize_stream_network(stream_mask, simplify_tolerance=2.0)[source]

Convert stream raster to vector polylines using topology-aware path extraction.

Uses skan library for proper skeleton-to-vector conversion that preserves stream network topology and handles branch points correctly.

Parameters:
  • stream_mask (ndarray) – Boolean array of stream pixels

  • simplify_tolerance (float) – Douglas-Peucker tolerance for simplification (pixels). Set to 0 to disable simplification.

Returns:

List of polylines, each as (N, 2) array of [y, x] coordinates. Each polyline represents a stream segment between junctions/endpoints.

Return type:

list

Example

>>> stream_mask = stream_raster > 0
>>> polylines = vectorize_stream_network(stream_mask, simplify_tolerance=2.0)
>>> print(f"Extracted {len(polylines)} stream segments")
src.terrain.visualization.flow_diagnostics.polyline_to_variable_width_polygon(polyline, widths)[source]

Convert a polyline to a variable-width polygon.

Creates a filled polygon outline by offsetting perpendicular to the polyline at each point based on the width at that point.

Parameters:
  • polyline (ndarray) – (N, 2) array of [y, x] coordinates

  • widths (ndarray) – (N,) array of half-widths at each point

Returns:

(M, 2) array of polygon vertices [y, x]

Return type:

ndarray

src.terrain.visualization.flow_diagnostics.plot_vectorized_streams(stream_raster, base_data, output_path, base_cmap='terrain', base_label='Elevation', title='Vectorized Stream Network', simplify_tolerance=2.0, base_log_scale=False, variable_width=True, max_width=3.0)[source]

Plot vectorized stream network overlaid on base map (TEMPORARY/EXPERIMENTAL).

Creates diagnostic showing: 1. Left: Original stream raster 2. Right: Vectorized polylines overlaid on base map

Parameters:
  • stream_raster (ndarray) – Stream metric values (0 = no stream)

  • base_data (ndarray) – Base map data (e.g., elevation, drainage)

  • output_path (Path) – Where to save plot

  • base_cmap (str) – Matplotlib colormap for base map

  • base_label (str) – Label for base map colorbar

  • title (str) – Plot title

  • simplify_tolerance (float) – Douglas-Peucker tolerance (pixels)

  • base_log_scale (bool) – Apply log scale to base data

  • variable_width (bool)

  • max_width (float)

Returns:

Path to saved plot

Return type:

Path

Line Layers

Linear feature overlay creation (streams, roads, trails, power lines) from raster data. Supports variable-width lines with smooth gaussian tapering.

Linear feature layer creation for visualization.

This module provides functions to create linear feature overlay layers (streams, roads, trails, power lines, etc.) from raster data. Line layers are preprocessed rasters where: - Line pixels contain metric values (for coloring) - Non-line pixels are 0

Variable-width lines are created by expanding line pixels based on their metric values using smooth gaussian tapering and maximum_filter expansion. This produces beautiful, gradually-tapered lines matching the diagnostic plot quality.

src.terrain.visualization.line_layers.get_metric_data(metric_choice, drainage, rainfall, discharge)[source]

Get metric data array based on user choice.

Parameters:
  • metric_choice – One of “drainage”, “rainfall”, or “discharge”

  • drainage – Drainage area data array

  • rainfall – Upstream rainfall data array

  • discharge – Discharge potential data array

Returns:

The selected metric data array

src.terrain.visualization.line_layers.expand_lines_variable_width_sparse(line_mask, metric_data, max_width, min_width=1, width_gamma=1.0)[source]

Ultra-fast sparse expansion using numba JIT.

10-50x faster and 100x less memory than dense approaches for sparse networks. Uses sparse representation (only stream pixels) + numba JIT compilation.

Parameters:
  • line_mask – Boolean array of initial line pixels

  • metric_data – Metric values (higher = wider)

  • max_width – Maximum width in pixels

  • min_width – Minimum width in pixels (default: 1)

  • width_gamma – Gamma correction for width scale (default: 1.0 = linear)

Returns:

Tuple of (expanded_mask, expanded_values)

src.terrain.visualization.line_layers.expand_lines_variable_width_fast(line_mask, metric_data, max_width, min_width=1, width_gamma=1.0)[source]

Fast variable-width expansion using distance transforms.

This is ~10-40x faster than the iterative dilation approach (O(N log N) vs O(max_width × N)). Uses distance transforms to find nearest line pixel, then applies smoothed width values.

Trade-off: In overlapping regions, uses nearest line pixel (not max value from all nearby). For most visualizations, this produces visually identical results.

Parameters:
  • line_mask – Boolean array of initial line pixels

  • metric_data – Metric values (higher = wider)

  • max_width – Maximum width in pixels

  • min_width – Minimum width in pixels (default: 1)

  • width_gamma – Gamma correction for width scale (default: 1.0 = linear)

Returns:

  • expanded_mask: Boolean array of expanded line pixels

  • expanded_values: Metric values propagated to expanded pixels

Return type:

Tuple of (expanded_mask, expanded_values)

src.terrain.visualization.line_layers.expand_lines_variable_width(line_mask, metric_data, max_width, min_width=1, width_gamma=1.0, fast=True, sparse=False, method=None)[source]

Expand line mask with variable width using smooth tapering.

Higher metric values → wider lines. Uses gaussian smoothing and maximum_filter for gradual, smooth tapering. This is the same algorithm used in diagnostic plots.

Parameters:
  • line_mask – Boolean array of initial line pixels

  • metric_data – Metric values (higher = wider)

  • max_width – Maximum width in pixels

  • min_width – Minimum width in pixels (default: 1)

  • width_gamma – Gamma correction for width scale (default: 1.0 = linear) < 1.0 makes more streams wider, > 1.0 makes fewer streams wider

  • fast – If True, use O(N log N) distance-transform algorithm (default). If False, use O(max_width × N) iterative dilation (slower, max-value semantics). Deprecated: use method parameter instead.

  • sparse – If True, use sparse + numba JIT (10-100x faster for sparse networks). Deprecated: use method parameter instead.

  • method – Algorithm choice: “sparse”, “fast”, “slow”. Overrides fast/sparse parameters. - “sparse”: Numba JIT circles (10-100x faster, requires numba) - “fast”: Distance transform (13-311x faster than slow) - “slow”: Iterative dilation (best quality, max-value semantics)

Returns:

  • expanded_mask: Boolean array of expanded line pixels

  • expanded_values: Metric values propagated to expanded pixels

Return type:

Tuple of (expanded_mask, expanded_values)

Examples

# Expand stream network by discharge values (fast distance transform) >>> mask, values = expand_lines_variable_width(stream_mask, discharge, max_width=3)

# Expand with sparse + numba (fastest for sparse networks) >>> mask, values = expand_lines_variable_width(stream_mask, discharge, max_width=3, method=”sparse”)

# Expand with slow algorithm (best quality, max-value semantics) >>> mask, values = expand_lines_variable_width(road_mask, lane_count, max_width=5, method=”slow”)

src.terrain.visualization.line_layers.create_line_layer(metric_data, selection_metric_data, percentile, variable_width=False, max_width=3, width_gamma=1.0, sparse=False, method=None)[source]

Create linear feature overlay layer.

Creates a preprocessed raster where line pixels have metric values and non-line pixels are 0. Optionally expands lines with variable width based on metric values.

Works for any linear features: streams, roads, trails, power lines, pipelines, etc.

Parameters:
  • metric_data – Metric values to assign to line pixels (for coloring)

  • selection_metric_data – Metric values to threshold (for line selection)

  • percentile – Percentile threshold (e.g., 95.0 = top 5%)

  • variable_width – If True, expand lines based on metric values

  • max_width – Maximum expansion width in pixels (only used if variable_width=True)

  • width_gamma – Gamma correction for width scale (default: 1.0 = linear)

  • sparse – If True, use sparse + numba algorithm (deprecated, use method instead)

  • method – Algorithm choice: “sparse”, “fast”, or “slow” (default: “fast”)

Returns:

line pixels have metric_data values, others are 0

Return type:

Line layer raster

Examples

# Stream network colored by discharge >>> streams = create_line_layer( … metric_data=discharge_log, … selection_metric_data=drainage_area, … percentile=95.0 … )

# Road network colored by traffic, width by lanes >>> roads = create_line_layer( … metric_data=traffic_volume, … selection_metric_data=lane_count, … percentile=90.0, … variable_width=True, … max_width=5 … )

# Trail network colored by difficulty >>> trails = create_line_layer( … metric_data=difficulty_score, … selection_metric_data=usage_frequency, … percentile=80.0 … )

src.terrain.visualization.line_layers.create_stream_network_layer(metric_data, selection_metric_data, percentile, variable_width=False, max_width=3, width_gamma=1.0, sparse=False, method=None)

Create linear feature overlay layer.

Creates a preprocessed raster where line pixels have metric values and non-line pixels are 0. Optionally expands lines with variable width based on metric values.

Works for any linear features: streams, roads, trails, power lines, pipelines, etc.

Parameters:
  • metric_data – Metric values to assign to line pixels (for coloring)

  • selection_metric_data – Metric values to threshold (for line selection)

  • percentile – Percentile threshold (e.g., 95.0 = top 5%)

  • variable_width – If True, expand lines based on metric values

  • max_width – Maximum expansion width in pixels (only used if variable_width=True)

  • width_gamma – Gamma correction for width scale (default: 1.0 = linear)

  • sparse – If True, use sparse + numba algorithm (deprecated, use method instead)

  • method – Algorithm choice: “sparse”, “fast”, or “slow” (default: “fast”)

Returns:

line pixels have metric_data values, others are 0

Return type:

Line layer raster

Examples

# Stream network colored by discharge >>> streams = create_line_layer( … metric_data=discharge_log, … selection_metric_data=drainage_area, … percentile=95.0 … )

# Road network colored by traffic, width by lanes >>> roads = create_line_layer( … metric_data=traffic_volume, … selection_metric_data=lane_count, … percentile=90.0, … variable_width=True, … max_width=5 … )

# Trail network colored by difficulty >>> trails = create_line_layer( … metric_data=difficulty_score, … selection_metric_data=usage_frequency, … percentile=80.0 … )

Bounds Pipeline

Multi-stage coordinate transformation pipeline for bounds visualization. Handles WGS84 to UTM projection, flipping, and downsampling to mesh grid.

Transformation pipeline for bounds visualization.

Encapsulates the multi-stage transformation from WGS84 original DEM through reprojection, flipping, and downsampling to final mesh grid.

IMPORTANT: The WGS84 → UTM transformation is NON-LINEAR (Transverse Mercator projection involves trigonometric relationships). A rectangular boundary in WGS84 becomes CURVED when projected to UTM. This module uses pyproj to handle the proper projection math, not linear approximations.

This module provides reusable components for both visualization and testing.

class src.terrain.visualization.bounds_pipeline.SimpleAffine(c, d, e, f, a, b)[source]

Bases: object

Minimal affine transform for mapping between coordinate spaces.

Represents the affine transform equation:

x_world = c + pixel_x * a + pixel_y * b y_world = f + pixel_x * d + pixel_y * e

Where (c, f) is the top-left corner, (a, e) are pixel scales, and (b, d) handle rotation/skew.

Parameters:
__init__(c, d, e, f, a, b)[source]

Initialize affine transform coefficients.

Parameters:
  • c (float) – X-coordinate of top-left corner (easting/longitude)

  • d (float) – Pixel spacing in x direction for y (usually 0 for aligned grid)

  • e (float) – Y-coordinate scale (usually negative for top-to-bottom scanning)

  • f (float) – Y-coordinate of top-left corner (northing/latitude)

  • a (float) – Pixel spacing in x direction (meters or degrees per pixel)

  • b (float) – Pixel spacing in y direction for x (usually 0 for aligned grid)

map_pixel_to_world(y, x)[source]

Map grid pixel coordinates to world coordinates.

Parameters:
  • y (int) – Row index (0 = top)

  • x (int) – Column index (0 = left)

Returns:

Tuple of (world_x, world_y) coordinates

Return type:

Tuple[float, float]

class src.terrain.visualization.bounds_pipeline.TransformationPipeline(original_shape, distortion_factor, target_vertices, bbox_wgs84=None, bbox_utm=None, src_crs='EPSG:4326', dst_crs='EPSG:32617')[source]

Bases: object

Manages the transformation pipeline from WGS84 to final mesh grid.

Pipeline stages: 1. Original: WGS84 coordinates, full resolution 2. Reprojected: UTM, distorted by NON-LINEAR projection (Transverse Mercator) 3. Flipped: Same shape as reprojected, but horizontally flipped 4. Final: Downsampled mesh grid (20×49 pixels for Detroit)

IMPORTANT: The WGS84 → UTM transformation is non-linear. A rectangular boundary in WGS84 becomes curved in UTM space due to the Transverse Mercator projection’s trigonometric relationships.

Parameters:
__init__(original_shape, distortion_factor, target_vertices, bbox_wgs84=None, bbox_utm=None, src_crs='EPSG:4326', dst_crs='EPSG:32617')[source]

Initialize transformation pipeline.

Parameters:
  • original_shape (Tuple[int, int]) – (height, width) of original DEM in WGS84

  • distortion_factor (float) – Height compression ratio from projection (e.g., 0.87) Used for shape estimation; actual projection uses pyproj

  • target_vertices (int) – Target number of vertices for final mesh

  • bbox_wgs84 (Tuple[float, float, float, float] | None) – Optional (min_lon, min_lat, max_lon, max_lat) in WGS84

  • bbox_utm (Tuple[float, float, float, float] | None) – Optional (min_easting, min_northing, max_easting, max_northing) in UTM

  • src_crs (str) – Source coordinate reference system (default: EPSG:4326 / WGS84)

  • dst_crs (str) – Destination coordinate reference system (default: EPSG:32617 / UTM 17N)

get_shape(stage)[source]

Get grid shape at a transformation stage.

Parameters:

stage (str) – One of ‘original’, ‘reprojected’, ‘flipped’, ‘final’

Returns:

(height, width) tuple for that stage

Return type:

Tuple[int, int]

get_affine(stage)[source]

Get affine transform for a stage.

Parameters:

stage (str) – One of ‘original’, ‘reprojected’, ‘final’

Returns:

SimpleAffine transform for mapping pixels to world coordinates

Return type:

SimpleAffine

class src.terrain.visualization.bounds_pipeline.EdgeTransformer(pipeline, use_pyproj=True)[source]

Bases: object

Transforms edge pixels through the transformation pipeline.

Applies the sequence of transformations (reprojection, flip, downsample) to edge pixel coordinates.

IMPORTANT: The WGS84 → UTM reprojection uses pyproj for proper non-linear Transverse Mercator projection. This means rectangular edges in WGS84 become curved in UTM space.

Parameters:
__init__(pipeline, use_pyproj=True)[source]

Initialize with a transformation pipeline.

Parameters:
  • pipeline (TransformationPipeline) – TransformationPipeline instance

  • use_pyproj (bool) – If True, use pyproj for proper non-linear projection. If False, fall back to linear approximation (for testing).

transform_stage(pixels, from_stage, to_stage)[source]

Transform pixels from one stage to the next.

Parameters:
  • pixels (List[Tuple[int, int]]) – List of (y, x) pixel coordinates

  • from_stage (str) – Source stage name

  • to_stage (str) – Destination stage name

Returns:

List of (y, x) coordinates in destination stage

Return type:

List[Tuple[int, int]]

transform_to_mesh_space(pixels, from_stage='original', clamp=True)[source]

Transform edge pixels to mesh coordinate space with FRACTIONAL precision.

Unlike transform_full_pipeline() which snaps to integer grid cells, this method preserves fractional coordinates for smooth edge extrusion.

Parameters:
  • pixels (List[Tuple[int, int]]) – Edge pixel coordinates (y, x) in the source stage

  • from_stage (str) – Source stage (‘original’, ‘reprojected’, or ‘flipped’)

  • clamp (bool) – If True, clamp coordinates to [0, dim-1]. If False, return true projected coordinates which may exceed grid bounds due to non-linear projection curvature.

Returns:

List of (y, x) coordinates in mesh space with fractional precision. If clamp=True, values range from 0.0 to (height-1) and 0.0 to (width-1). If clamp=False, values may exceed these bounds.

Return type:

List[Tuple[float, float]]

transform_full_pipeline(pixels)[source]

Apply full transformation from original to final.

Parameters:

pixels (List[Tuple[int, int]]) – Edge pixel coordinates in original WGS84 space

Returns:

Edge pixels in final mesh grid coordinates

Return type:

List[Tuple[int, int]]

Edge Debug

Diagnostic plots for rectangle edge sampling and coordinate transformations.

Edge Extrusion Debug Visualizations

Diagnostic plots for understanding rectangle edge sampling and coordinate transformations. Helps visualize: - Original rectangle sampling in DEM pixel space - Coordinate transformations (original → geographic → reprojected → final mesh) - Boundary point distributions before/after deduplication/sorting - Edge distribution analysis (north/south/east/west)

src.terrain.visualization.edge_debug.plot_rectangle_edge_sampling(dem_shape, edge_pixels_dense, edge_pixels_sparse, output_path)[source]

Plot rectangle edge sampling at different spacings.

Shows the difference between dense (1px spacing) and sparse (5px spacing) edge sampling in the original DEM pixel coordinate space.

Parameters:
  • dem_shape (Tuple[int, int]) – (height, width) of DEM

  • edge_pixels_dense (List[Tuple[float, float]]) – Edge pixels sampled at 1px spacing

  • edge_pixels_sparse (List[Tuple[float, float]]) – Edge pixels sampled at 5px spacing

  • output_path (Path) – Where to save the plot

Return type:

None

src.terrain.visualization.edge_debug.plot_edge_distribution(boundary_points, output_path, title='Edge Distribution', margin=0.05)[source]

Plot boundary points colored by which edge they’re on.

Classifies points as North (top), South (bottom), East (right), or West (left) based on their position relative to the bounding box.

Parameters:
  • boundary_points (List[Tuple[float, float]]) – List of (y, x) boundary coordinates

  • output_path (Path) – Where to save the plot

  • title (str) – Plot title

  • margin (float) – Fraction of range to use for edge classification (default 5%)

Return type:

None

src.terrain.visualization.edge_debug.plot_deduplication_comparison(before, after, output_path)[source]

Plot before/after comparison of deduplication.

Shows how many points were removed as duplicates and where the remaining points are distributed.

Parameters:
Return type:

None

src.terrain.visualization.edge_debug.plot_sorting_effect(before, after, output_path)[source]

Plot the effect of angular sorting on boundary points.

Shows how points are reordered from shuffled state into a proper closed loop around the boundary.

Parameters:
  • before (ndarray) – Points before sorting (may be in any order)

  • after (ndarray) – Points after angular sorting

  • output_path (Path) – Where to save the plot

Return type:

None

src.terrain.visualization.edge_debug.plot_transformation_pipeline(terrain, output_dir, edge_sample_spacing=1.0)[source]

Create a series of plots showing each stage of coordinate transformation.

Visualizes: 1. Original rectangle in DEM pixel space (EPSG:4326) 2. Geographic coordinates after Affine transform 3. Reprojected coordinates (if CRS changed) 4. Final mesh pixel space

Parameters:
  • terrain – Terrain object with transforms configured

  • output_dir (Path) – Directory to save stage plots

  • edge_sample_spacing (float) – Spacing for edge sampling

Return type:

None

src.terrain.visualization.edge_debug.create_full_pipeline_debug_plot(terrain, output_path, edge_sample_spacing=1.0)[source]

Create a comprehensive debug visualization of the entire rectangle edge pipeline.

Single plot showing: - Original rectangle sampling - Edge distribution before/after transforms - Deduplication statistics - Final boundary distribution

Parameters:
  • terrain – Terrain object with configured transforms

  • output_path (Path) – Where to save the comprehensive plot

  • edge_sample_spacing (float) – Spacing for edge sampling

Return type:

None

See Also