Georeferencing (GCPs & RPCs)#
flowchart LR
GE(("Georef<br/>ds.georef"))
GE --> GC["<b>ground-control points</b><br/>gcps · gcp_count · gcp_projection<br/>has_gcps · set_gcps"]
GE --> RP["<b>rational-polynomial coeffs</b><br/>rpcs · has_rpcs · set_rpcs"]
GE --> WA["<b>warp to a map grid</b><br/>orthorectify · georeference"]
Read, attach, and warp-from ground-control points and rational-polynomial coefficients to
georeference raw / un-orthorectified imagery — the one case where a raster is not described by a simple
affine geotransform. Accessed as ds.georef, with same-named facades on Dataset (ds.gcps,
ds.set_gcps, ds.georeference, ds.rpcs, ds.set_rpcs, ds.orthorectify).
Everything routes through GDAL — pyramids does not implement any sensor model itself.
pyramids.dataset.engines.Georef
#
Bases: _Engine['Dataset']
Ground-control-point and RPC georeferencing for a :class:Dataset.
A normal raster is georeferenced by an affine geotransform. Raw imagery (scanned maps, drone mosaics, un-orthorectified satellite scenes) instead carries ground-control points (pixel↔map tie points) or rational polynomial coefficients (a vendor sensor model). This engine reads and attaches both, and warps from them into an affine-geotransform raster.
Source code in src/pyramids/dataset/engines/georef.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | |
gcp_count
property
#
Number of ground-control points attached (0 when none).
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
the GCP count. |
gcp_projection
property
#
WKT of the GCPs' CRS, or None when the dataset has no GCPs.
Returns:
| Type | Description |
|---|---|
str | None
|
str | None: the GCP-projection WKT, else |
gcps
property
#
The dataset's ground-control points (empty list when it has none).
Returns:
| Type | Description |
|---|---|
list[GroundControlPoint]
|
list[GroundControlPoint]: one per attached GCP, in GDAL order. |
Examples:
- Read back the points attached with :meth:
set_gcps:>>> import numpy as np >>> from pyramids.dataset import Dataset >>> from pyramids.dataset._gcp import GroundControlPoint >>> ds = Dataset.create_from_array( ... np.ones((4, 4), "float32"), top_left_corner=(0.0, 4.0), cell_size=1.0 ... ) >>> ds.set_gcps([GroundControlPoint(row=0, col=0, x=10.0, y=50.0)], 4326) >>> (ds.gcps[0].col, ds.gcps[0].x, ds.gcps[0].y) (0.0, 10.0, 50.0)
has_gcps
property
#
True when the dataset carries at least one ground-control point.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
whether any GCP is attached. |
has_rpcs
property
#
True when the dataset carries RPC metadata.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
whether an RPC sensor model is attached. |
rpcs
property
#
The dataset's rational-polynomial coefficients, or None if absent.
RPCs live in GDAL's "RPC" metadata domain — a vendor sensor model of
~90 string coefficients (LINE_NUM_COEFF, HEIGHT_OFF, ...) shipped
with raw high-resolution satellite imagery.
Returns:
| Type | Description |
|---|---|
dict[str, str] | None
|
dict[str, str] | None: the RPC coefficient mapping, or |
dict[str, str] | None
|
the dataset has no RPC metadata. |
Examples:
- A plain raster has no RPCs:
georeference(*, to_epsg=None, method='nearest neighbor', transform='polynomial', order=1, cell_size=None, lazy=False)
#
Warp the dataset from its GCPs into an affine-geotransform raster.
Fits a transform through the attached ground-control points and resamples
the pixels onto a regular grid — the step that turns a GCP-tagged scan or
mosaic into a normal georeferenced raster. The GCPs are read from the
source automatically (attach them first with :meth:set_gcps).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to_epsg
|
int | str | None
|
Target CRS for the output. |
None
|
method
|
str
|
Resampling method (see :meth: |
'nearest neighbor'
|
transform
|
str
|
|
'polynomial'
|
order
|
int
|
Polynomial degree, one of |
1
|
cell_size
|
float | None
|
Output pixel size in target-CRS units (both axes). |
None
|
lazy
|
bool
|
|
False
|
Returns:
| Name | Type | Description |
|---|---|---|
Dataset |
Dataset
|
the georeferenced raster. |
Raises:
| Type | Description |
|---|---|
ValueError
|
the dataset has no GCPs, or |
RuntimeError
|
GDAL could not build the warp. |
Examples:
- Georeference an 8x8 image from four corner GCPs in EPSG:4326:
>>> import numpy as np >>> from pyramids.dataset import Dataset >>> from pyramids.dataset._gcp import GroundControlPoint >>> ds = Dataset.create_from_array( ... np.ones((8, 8), "float32"), top_left_corner=(0.0, 8.0), cell_size=1.0 ... ) >>> ds.set_gcps([ ... GroundControlPoint(row=0, col=0, x=10.0, y=50.0), ... GroundControlPoint(row=0, col=8, x=11.0, y=50.0), ... GroundControlPoint(row=8, col=0, x=10.0, y=49.0), ... GroundControlPoint(row=8, col=8, x=11.0, y=49.0), ... ], 4326) >>> out = ds.georeference() >>> out.epsg 4326
Source code in src/pyramids/dataset/engines/georef.py
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | |
orthorectify(*, dem=None, to_epsg=None, method='bilinear', rpc_height=None, cell_size=None, lazy=False)
#
Orthorectify the dataset from its RPC sensor model onto a map grid.
Uses the attached rational-polynomial coefficients (attach them first
with :meth:set_rpcs) and, ideally, a digital elevation model to remove
terrain-induced distortion and produce a map-projected raster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dem
|
str | Path | Dataset | None
|
Elevation model used to evaluate the RPCs — a raster path or a
:class: |
None
|
to_epsg
|
int | str | None
|
Target CRS. |
None
|
method
|
str
|
Resampling method. Default |
'bilinear'
|
rpc_height
|
float | None
|
Constant elevation (map units) to use when no |
None
|
cell_size
|
float | None
|
Output pixel size in target-CRS units. |
None
|
lazy
|
bool
|
|
False
|
Returns:
| Name | Type | Description |
|---|---|---|
Dataset |
Dataset
|
the orthorectified raster. |
Raises:
| Type | Description |
|---|---|
ValueError
|
the dataset has no RPC metadata. |
RuntimeError
|
GDAL could not build the warp. |
Source code in src/pyramids/dataset/engines/georef.py
set_gcps(gcps, projection)
#
Attach ground-control points (and their CRS) to the dataset.
Replaces any existing GCPs. The dataset must be opened writable
(read_only=False); a MEM-backed dataset (e.g. from
:meth:Dataset.create_from_array) is always writable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gcps
|
Sequence[GroundControlPoint]
|
One or more :class: |
required |
projection
|
int | str
|
The GCPs' CRS, in any form
:func: |
required |
Raises:
| Type | Description |
|---|---|
ReadOnlyError
|
The dataset is opened read-only. |
ValueError
|
|
Examples:
- Attach four corner points in EPSG:4326 to an in-memory raster:
>>> import numpy as np >>> from pyramids.dataset import Dataset >>> from pyramids.dataset._gcp import GroundControlPoint >>> ds = Dataset.create_from_array( ... np.ones((8, 8), "float32"), top_left_corner=(0.0, 8.0), cell_size=1.0 ... ) >>> pts = [ ... GroundControlPoint(row=0, col=0, x=10.0, y=50.0), ... GroundControlPoint(row=0, col=8, x=11.0, y=50.0), ... GroundControlPoint(row=8, col=0, x=10.0, y=49.0), ... GroundControlPoint(row=8, col=8, x=11.0, y=49.0), ... ] >>> ds.set_gcps(pts, 4326) >>> ds.raster.GetGCPCount() 4
Source code in src/pyramids/dataset/engines/georef.py
set_rpcs(rpc)
#
Attach rational-polynomial coefficients (an RPC sensor model).
Values are stringified before being written to GDAL's "RPC" metadata
domain. The dataset must be writable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rpc
|
Mapping[str, str | float]
|
The RPC coefficient mapping. Must contain every required key:
the five |
required |
Raises:
| Type | Description |
|---|---|
ReadOnlyError
|
The dataset is opened read-only. |
ValueError
|
One or more required RPC keys are missing (the error lists them). |
Examples:
- Round-trip a coefficient set through the RPC domain:
>>> import numpy as np >>> from pyramids.dataset import Dataset >>> rpc = {k: "0" for k in ( ... "LINE_OFF", "SAMP_OFF", "LAT_OFF", "LONG_OFF", "HEIGHT_OFF", ... "LINE_SCALE", "SAMP_SCALE", "LAT_SCALE", "LONG_SCALE", ... "HEIGHT_SCALE", "LINE_NUM_COEFF", "LINE_DEN_COEFF", ... "SAMP_NUM_COEFF", "SAMP_DEN_COEFF", ... )} >>> ds = Dataset.create_from_array( ... np.ones((4, 4), "float32"), top_left_corner=(0.0, 4.0), cell_size=1.0 ... ) >>> ds.set_rpcs(rpc) >>> ds.rpcs["HEIGHT_OFF"] '0'
Source code in src/pyramids/dataset/engines/georef.py
pyramids.dataset._gcp.GroundControlPoint
dataclass
#
An immutable ground-control point: a pixel mapped to a map coordinate.
A GCP states "pixel (col, row) is at map coordinate (x, y[, z])". The
pixel axes follow GDAL: col is the pixel (x) index and row is the line
(y) index, both measured from the raster's top-left corner.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
float
|
Pixel-space line (y / row) index of the control point. |
required |
col
|
float
|
Pixel-space pixel (x / column) index of the control point. |
required |
x
|
float
|
Map-space X (easting / longitude) at that pixel. |
required |
y
|
float
|
Map-space Y (northing / latitude) at that pixel. |
required |
z
|
float
|
Map-space Z (elevation). Defaults to |
0.0
|
id
|
str | None
|
Optional point identifier. Defaults to |
None
|
info
|
str | None
|
Optional free-text label. Defaults to |
None
|
Examples:
- Build a point and read its fields:
- Round-trip through the GDAL representation:
Source code in src/pyramids/dataset/_gcp.py
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
from_gdal(gcp)
classmethod
#
Build a :class:GroundControlPoint from an :class:osgeo.gdal.GCP.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gcp
|
GCP
|
A GDAL GCP (fields |
required |
Returns:
| Name | Type | Description |
|---|---|---|
GroundControlPoint |
GroundControlPoint
|
the equivalent value object. |
Examples:
- Convert a GDAL GCP into the value object:
Source code in src/pyramids/dataset/_gcp.py
to_gdal()
#
Convert to an :class:osgeo.gdal.GCP.
Returns:
| Type | Description |
|---|---|
GCP
|
gdal.GCP: a GDAL GCP whose |
GCP
|
|
Examples:
- The GDAL GCP carries the pixel and map coordinates: