From a stereo pair to a geocoded DSM — on one desk.

Feed in two overlapping satellite images that carry RPC sensor models and the in-process pipeline extracts a dense Digital Surface Model: pure-C# RPC triangulation does the photogrammetry, a pluggable OpenCV StereoSGBM sidecar does the dense matching, and the produced height map auto-registers as both a served COG imagery layer and a terrain DemSource so viewshed, line-of-sight, and slope analysis consume it immediately. No cloud, no per-tile wait.

RPC triangulationOpenCV StereoSGBMPluggable matcher Dual COG + DemSourceAir-gappedSOCET GXP ATE analog
Automatic Terrain Extraction

The full stereo → DSM pipeline, server-side

Seven stages run as an async job you poll — from RPC extraction through a math-correct triangulation that fixes height by minimizing the two-image reprojection residual.

  1. RPC extraction — both images opened via GDAL; their rational polynomial sensor models read from the inline GeoTIFF domain or .rpc/.rpb sidecar.
  2. Epipolar grid — the rectifier seeds the triangulator's ground-frame mapping.
  3. Dense match — the pluggable matcher produces a per-pixel disparity map (NaN where unmatched).
  4. RPC triangulation — each valid disparity pixel → (lon, lat, height) via a bounded height search with golden-section refinement.
  5. DSM write — a tiled DEFLATE Float32 GeoTIFF with NaN NoData.
  6. Register COG — the DSM becomes an ImageServer layer.
  7. Register DemSource — the DSM also enters the terrain stack so viewshed / LOS / slope use it at once.
/api/stereo
# probe the matcher sidecar
GET  /api/stereo/capabilities
     → { matcher:{name,available}, pipeline:[…] }

# start a stereo → DSM job
POST /api/stereo/dsm
     { "leftCogId":"wv3-left",
       "rightCogId":"wv3-right",
       "options":{"HeightMax":9000} }
     → { "jobId":"a1b2c3…" }

# poll → produced DSM
GET  /api/stereo/jobs/{jobId}
     → { "status":"succeeded",
         "dsmCogId":"ate-dsm-a1b2",
         "demSourceId":"ate-dsm-a1b2",
         "validPixelFraction":0.83 }
Math-correct, not a toy

Proper RPC stereo photogrammetry

The triangulator implements the standard RPC forward-intersection (Grodecki & Dial 2003): a 1-D height search that projects a candidate ground point into both images and minimizes the combined reprojection RMS — the math that fixes elevation from two rays.

📐

Height search + golden-section

At each candidate height the reprojection residual is measured in both images; a coarse sweep brackets the minimum, golden-section refines to 0.05 m convergence. The output height is the residual minimum.

🎯

Newton lon/lat refinement

At the winning height, a single Newton step on (lon, lat) against the left image's Jacobian nudges the ground point onto the left ray — the least-squares intersection of the two RPC pencils.

🧪

17 unit tests

The triangulator is covered by pure-math tests: a synthetic parallax pair recovers a known height, the reprojection RMS is zero at the exact ground point and grows for wrong heights, and the rectifier homography round-trips through its inverse.

Pluggable matcher seam

Swap the dense-matching engine without touching the pipeline

The compute-heavy matching step is decoupled behind IMatcherStrategy. The default ships as a capability-gated Python/OpenCV sidecar; a future pure-C# Semi-Global Matching engine drops in with zero service or route changes.

🐍

Default: OpenCV StereoSGBM

A reference Python script (stereo_matcher.py) shells OpenCV's Semi-Global Block Matching. Three-tier resolve: explicit config → PATH scan for python → bundled runtimes/win-x64/stereo/. Clear 503 + install hint when absent.

🔌

Any tool that honors the contract

The sidecar contract is two rasters in → one Float32 disparity GeoTIFF out. Point Stereo:MatcherScript at any matcher (a custom SGM build, a research model) and it runs.

Roadmap: pure-C# SGM

A native Hirschmüller SGM implementation will plug in behind IMatcherStrategy — same shape as the SAR local-vs-cloud InSAR backends. No Python dependency at all when it lands.

The triangulation math, the orchestration, the DSM write, and the dual registration are all pure C#. Only the dense-matching step needs the optional sidecar.

Extract terrain from stereo — without leaving the workstation.

Pair it with IMINT exploitation, virtual mosaic, and viewshed / line-of-sight for a complete exploitation loop.

Get Started