Skip to content

r.dem.coregister

Co-register post-event DSM to reference DEM using PGCPs

r.dem.coregister [-v] dem=name reference=name pgcp=name output=name [method=string] [buffer=float] [min_points=integer] [stable_mask=name] [bias_output=name] [transform_output=name] [apply_transform=name] [--overwrite] [--verbose] [--quiet] [--qq] [--ui]

Example:

r.dem.coregister dem=name reference=name pgcp=name output=name

grass.tools.Tools.r_dem_coregister(dem, reference, pgcp, output, method="pgcp_vertical", buffer=2.0, min_points=30, stable_mask=None, bias_output=None, transform_output=None, apply_transform=None, flags=None, overwrite=None, verbose=None, quiet=None, superquiet=None)

Example:

tools = Tools()
tools.r_dem_coregister(dem="name", reference="name", pgcp="name", output="name")

This grass.tools API is experimental in version 8.5 and expected to be stable in version 8.6.

grass.script.run_command("r.dem.coregister", dem, reference, pgcp, output, method="pgcp_vertical", buffer=2.0, min_points=30, stable_mask=None, bias_output=None, transform_output=None, apply_transform=None, flags=None, overwrite=None, verbose=None, quiet=None, superquiet=None)

Example:

gs.run_command("r.dem.coregister", dem="name", reference="name", pgcp="name", output="name")

Parameters

dem=name [required]
    Post-event DSM to co-register
reference=name [required]
    Reference DEM (e.g., pre-event LiDAR DTM/DSM)
pgcp=name [required]
    Pseudo ground control points (PGCPs) (e.g. roads, buildings, fire hydrants) vector for PGCP extraction
    Or data source for direct OGR access
output=name [required]
    Co-registered DSM output name
method=string
    Co-registration method
    Allowed values: pgcp_vertical, nk, nk_icp, icp
    Default: pgcp_vertical
buffer=float
    Buffer distance (m) around PGCP features for residual sampling
    Default: 2.0
min_points=integer
    Minimum number of PGCPs required to proceed
    Default: 30
stable_mask=name
    Stable-terrain mask (1=stable); required by method nk and nk_icp
bias_output=name
    CSV file path for per-PGCP residual statistics
transform_output=name
    Write the solved transform (PGCP, N&K, ICP components) to a file
apply_transform=name
    Replay a saved transform onto dem (shared horizontal, per-surface vertical) instead of solving
-v
    Verbose: write per-PGCP residuals to bias_output CSV
--overwrite
    Allow output files to overwrite existing files
--help
    Print usage summary
--verbose
    Verbose module output
--quiet
    Quiet module output
--qq
    Very quiet module output
--ui
    Force launching GUI dialog

dem : str | np.ndarray, required
    Post-event DSM to co-register
    Used as: input, raster, name
reference : str | np.ndarray, required
    Reference DEM (e.g., pre-event LiDAR DTM/DSM)
    Used as: input, raster, name
pgcp : str, required
    Pseudo ground control points (PGCPs) (e.g. roads, buildings, fire hydrants) vector for PGCP extraction
    Or data source for direct OGR access
    Used as: input, vector, name
output : str | type(np.ndarray) | type(np.array) | type(gs.array.array), required
    Co-registered DSM output name
    Used as: output, raster, name
method : str, optional
    Co-registration method
    Allowed values: pgcp_vertical, nk, nk_icp, icp
    Default: pgcp_vertical
buffer : float, optional
    Buffer distance (m) around PGCP features for residual sampling
    Default: 2.0
min_points : int, optional
    Minimum number of PGCPs required to proceed
    Default: 30
stable_mask : str | np.ndarray, optional
    Stable-terrain mask (1=stable); required by method nk and nk_icp
    Used as: input, raster, name
bias_output : str, optional
    CSV file path for per-PGCP residual statistics
    Used as: output, file, name
transform_output : str, optional
    Write the solved transform (PGCP, N&K, ICP components) to a file
    Used as: output, file, name
apply_transform : str | io.StringIO, optional
    Replay a saved transform onto dem (shared horizontal, per-surface vertical) instead of solving
    Used as: input, file, name
flags : str, optional
    Allowed values: v
    v
        Verbose: write per-PGCP residuals to bias_output CSV
overwrite : bool, optional
    Allow output files to overwrite existing files
    Default: None
verbose : bool, optional
    Verbose module output
    Default: None
quiet : bool, optional
    Quiet module output
    Default: None
superquiet : bool, optional
    Very quiet module output
    Default: None

Returns:

result : grass.tools.support.ToolResult | np.ndarray | tuple[np.ndarray] | None
If the tool produces text as standard output, a ToolResult object will be returned. Otherwise, None will be returned. If an array type (e.g., np.ndarray) is used for one of the raster outputs, the result will be an array and will have the shape corresponding to the computational region. If an array type is used for more than one raster output, the result will be a tuple of arrays.

Raises:

grass.tools.ToolError: When the tool ended with an error.

dem : str, required
    Post-event DSM to co-register
    Used as: input, raster, name
reference : str, required
    Reference DEM (e.g., pre-event LiDAR DTM/DSM)
    Used as: input, raster, name
pgcp : str, required
    Pseudo ground control points (PGCPs) (e.g. roads, buildings, fire hydrants) vector for PGCP extraction
    Or data source for direct OGR access
    Used as: input, vector, name
output : str, required
    Co-registered DSM output name
    Used as: output, raster, name
method : str, optional
    Co-registration method
    Allowed values: pgcp_vertical, nk, nk_icp, icp
    Default: pgcp_vertical
buffer : float, optional
    Buffer distance (m) around PGCP features for residual sampling
    Default: 2.0
min_points : int, optional
    Minimum number of PGCPs required to proceed
    Default: 30
stable_mask : str, optional
    Stable-terrain mask (1=stable); required by method nk and nk_icp
    Used as: input, raster, name
bias_output : str, optional
    CSV file path for per-PGCP residual statistics
    Used as: output, file, name
transform_output : str, optional
    Write the solved transform (PGCP, N&K, ICP components) to a file
    Used as: output, file, name
apply_transform : str, optional
    Replay a saved transform onto dem (shared horizontal, per-surface vertical) instead of solving
    Used as: input, file, name
flags : str, optional
    Allowed values: v
    v
        Verbose: write per-PGCP residuals to bias_output CSV
overwrite : bool, optional
    Allow output files to overwrite existing files
    Default: None
verbose : bool, optional
    Verbose module output
    Default: None
quiet : bool, optional
    Quiet module output
    Default: None
superquiet : bool, optional
    Very quiet module output
    Default: None

DESCRIPTION

r.dem.coregister co-registers a post-event DSM to a reference DEM using pseudo ground control points (PGCPs) sampled from stable features, terrain assumed unchanged between surveys. Roads are the typical source, but any stable feature works: buildings, parking lots, or fire hydrants. It removes a robust vertical bias and can optionally chain horizontal (Nuth and Kaab) and ICP refinement.

method=pgcp_vertical (default)

The pgcp vector is buffered by buffer metres and rasterized, the elevation residual dem - reference is sampled within that PGCP mask, and a robust median vertical bias is estimated. The bias is removed with output = dem - median_bias. Robust statistics (median bias, NMAD, RMSE) are reported, and with the -v flag per-PGCP residuals are written to bias_output as CSV.

If fewer than min_points PGCP samples are found the tool warns and proceeds.

method=nk and method=nk_icp

These chain additional alignment after the PGCP vertical step, operating on the PGCP-corrected DSM: nk adds a horizontal and vertical Nuth and Kaab correction (r.dem.nk), and nk_icp further adds a point-to-plane ICP refinement (r.dem.icp). The full chain is PGCP vertical, then N&K, then ICP.

Both methods require a stable_mask raster (1=stable). The Nuth and Kaab method regresses the elevation difference against slope and aspect, so the mask must cover broad, sloped, unchanged terrain. The flat PGCP features used for the PGCP step are unsuitable here (they are filtered out by the N&K slope limits), so a separate stable mask must be supplied. The same mask is passed to r.dem.icp to restrict ICP to stable terrain.

method=icp

This chains a point-to-plane ICP refinement (r.dem.icp) directly onto the PGCP-corrected DSM, skipping the Nuth and Kaab step. The chain is PGCP vertical, then ICP. Unlike nk and nk_icp, stable_mask is optional for this method: when supplied it is passed to r.dem.icp to restrict the alignment to unchanged terrain (recommended in change-detection contexts); when omitted, ICP aligns over all valid terrain.

Solving once and replaying onto another surface

transform_output writes the composed PGCP, N&K, and ICP transform to a file. apply_transform replays a saved transform onto another DEM, skipping the solve. This is intended for surfaces from the same acquisition (for example a DSM and a DTM): solve the alignment on the cleaner bare-earth DTM, then replay it onto the DSM so both share the same horizontal alignment. On replay the horizontal components (N&K dx, dy and ICP dx, dy, yaw) are shared, while the vertical bias is re-estimated per surface with the PGCP step, since the DSM and DTM vertical offsets differ. Replay therefore still needs pgcp but not a stable_mask.

NOTES

The PGCP approach assumes the supplied features are stable reference surfaces. When using roads, choose buffer to stay within the paved surface and avoid curbs, vegetation, and vehicles. The computational region should match the input DEM resolution.

EXAMPLES

The commands below use the example scene built in the r.dem toolset manual, which is derived from the North Carolina sample dataset. Build it there first.

Remove the vertical bias from road PGCPs alone, writing the per-PGCP residuals to CSV:

g.region raster=elev_lid792_1m

r.dem.coregister dem=dsm_offset reference=elev_lid792_1m \
    pgcp=pgcp_roads output=dsm_pgcp method=pgcp_vertical \
    buffer=2.0 bias_output=pgcp_residuals.csv -v

Roads are flat, so the horizontal shift contributes almost nothing to the elevation residual there and the median bias recovers the applied 1.32 m:

N samples: 9725
Median bias: 1.3150 m
NMAD: 0.1014 m

Chain the Nuth and Kääb step to remove the horizontal offset as well. This needs a stable_mask of broad sloped terrain, separate from the flat PGCP features:

r.dem.coregister dem=dsm_offset reference=elev_lid792_1m \
    pgcp=pgcp_roads stable_mask=stable_terrain \
    output=dsm_coreg method=nk

r.dem.coregister example
Figure: Stable-terrain residual before co-registration, after r.dem.nk, and after r.dem.icp.

SEE ALSO

r.dem, r.dem.change, r.dem.icp, r.dem.nk, v.buffer, v.to.rast

AUTHORS

Corey T. White, Center for Geospatial Analytics, NC State University

SOURCE CODE

Available at: r.dem.coregister source code (history)
Latest change: Thursday Aug 20 19:32:37 2026 in commit d08d385