georay
Render georeferenced image and DSM references for calibrated camera poses.
georay connects camera geometry with geospatial raster data. Given a
calibrated camera, a 6D pose, or an explicit UTM footprint, it computes the
needed reference area, selects suitable source tiles, builds mosaics, and
returns image/DSM cutouts together with coordinate mappings and tile metadata.
Features
- Pluggable raster sources: German state data via
georaffer, or any local GeoTIFF/COG/VRT files in any projected CRS - Camera-footprint computation from calibrated pinhole cameras and
Pose6D - DSM-backed ray marching for terrain-aware footprint estimation
- Rendering from explicit
BoundingBox2Dfootprints - Orthophoto and DSM tile selection by year, season, and region preference
- Tile retrieval through
georaffer, with quiet external output by default - Mosaic construction, axis-aligned slicing, perspective warping, and metadata
- Rich-based level logging and progress spinners for longer operations
Installation
Install from GitHub:
python3 -m pip install "georay @ git+https://github.com/hlzmnhnry/georay.git"
or in editable mode from a clone:
python3 -m pip install -e .
For development and tests:
python3 -m pip install -e ".[dev]"
python3 -m pytest
For official German state data install the germany extra, which pulls in
georaffer:
python3 -m pip install -e ".[germany]"
Raster Sources
georay renders from a regular grid of square tiles in a projected CRS; tile
(x, y) covers [x * size, (x + 1) * size) in easting and northing. A raster
source provides the tiles:
GeorafferSource(regions, imagery_from, dsm_from)downloads and converts orthophotos and surface models of NRW, RLP, BW, BY, TH (ETRS89 / UTM 32N) or BB (UTM 33N); TH needs georaffer > 0.2.0 through georaffer. Passingregions=...to the renderers creates it implicitly.RasterFileSource(image_files, dsm_files, crs=None, tile_size_m=1000)uses your own files anywhere in the world. They are reprojected into the tile grid (default CRS: that of the first image file) and cached as GeoTIFFs. DSM heights are used as they are, so poses must use the same vertical reference.
from georay.raster.sources import RasterFileSource
source = RasterFileSource(["naip_ortho.tif"], ["dsm.tif"], crs="EPSG:32617")
renderer = ReferenceViewRenderer(camera=camera, source=source)
Any object with crs, tile_size_m, query(coords, tile_type) and
get_many(tiles, resolution) works as a source (georay.raster.sources.RasterSource).
When mosaicking, georay checks every tile's georeferencing against its grid cell.
Quickstart
The high-level entry point is ReferenceViewRenderer. It can render both an
orthophoto reference and a DSM reference from a pose:
from datetime import date
from georay import configure_logging
from georay.geometry.pinhole_camera import PinholeCamera
from georay.raster.reference_tile_selector import ClosestReferenceTileSelector
from georay.reference_view_renderer import ReferenceViewRenderer
from georay.spatial.bounding_box_transform import NorthAlignedBoundingBoxTransform
from georay.types.pose import Pose6D
from georay.types.tile import RankingOrder
configure_logging("INFO")
camera = PinholeCamera(
image_width=1600,
image_height=1100,
fx=1320.0,
fy=1319.3,
cx=801.0,
cy=560.6,
)
selector = ClosestReferenceTileSelector(
ranking_order=RankingOrder.YEAR_STATE_SEASON,
)
renderer = ReferenceViewRenderer(
camera=camera,
regions=["nrw", "rlp"],
imagery_from=(2015, None),
dsm_from=(2015, None),
tile_selector=selector,
)
pose = Pose6D(rotation_matrix=rotation_world_from_camera, translation=utm_xyz)
result = renderer.render(
pose=pose,
pixels_per_meter=2,
output_size=(640, 480),
bbox_transform=NorthAlignedBoundingBoxTransform(enlargement=0.1),
preferred_date=date(2022, 2, 23),
preferred_regions="nrw",
render_image=True,
render_dsm=True,
)
image = result.image.rasters[0] if result.image else None
dsm = result.dsm.rasters[0] if result.dsm else None
For a complete runnable example, see
examples/simple_reference_from_bbox.py.
Rendering From A Footprint
If the footprint is already known, bypass pose-based footprint computation and
render directly from a BoundingBox2D:
reference = renderer.render_from_bbox(
bbox=bbox,
pose=pose,
pixels_per_meter=2,
bbox_transform=NorthAlignedBoundingBoxTransform(enlargement=0.1),
preferred_regions="nrw",
render_image=True,
render_dsm=True,
dsm_interpolation="nearest",
)
render_from_bbox() is useful when footprints were precomputed, manually
curated, or loaded from a sequence file.
Loading Explicit Tiles
Load one or more tile coordinates directly without building a mosaic:
from georay.reference_tile_renderer import ReferenceTileRenderer
tile_renderer = ReferenceTileRenderer(
regions=["nrw", "rlp"],
imagery_from=(2015, None),
dsm_from=(2015, None),
tile_selector=selector,
)
tiles = tile_renderer.render_tiles(
tile_coords=[(357, 5612), (400, 5700)],
pixels_per_meter=1,
preferred_regions="nrw",
)
image = tiles[(357, 5612)].image.rasters[0]
dsm = tiles[(357, 5612)].dsm.rasters[0]
Tile coordinates are the 1 km grid indices, so (357, 5612) covers eastings
357000..358000 and northings 5612000..5613000. Explicit tiles are loaded
individually and may be disconnected.
Create a colored NumPy point cloud directly from selected image and DSM tiles:
cloud = tile_renderer.render_tile_point_cloud(
tile_coords=[(357, 5612), (400, 5700)],
pixels_per_meter=1,
)
The returned array has shape (N, 6) with columns x, y, z, r, g, b.
Point coordinates represent raster pixel centers in UTM coordinates. Pixels
with non-finite DSM elevations are omitted. Point-cloud generation requires
the tile selector to select exactly one image and one DSM variant per tile.
ReferenceViewRenderer still exposes render_tiles() and
render_tile_point_cloud() as compatibility helpers, but direct tile workflows
do not require a camera model.
Logging And Progress
Logging is quiet by default. Enable Rich-formatted logs and progress spinners explicitly:
from georay import configure_logging
configure_logging("INFO") # DEBUG, INFO, WARNING, ERROR, CRITICAL
configure_logging("DEBUG") # includes suppressed georaffer output
configure_logging("OFF") # disables georay logs and spinners
You can also configure logging through the renderer:
renderer = ReferenceViewRenderer(
camera=camera,
regions=["nrw", "rlp"],
imagery_from=(2015, None),
tile_selector=selector,
log_level="INFO",
quiet_georaffer=True,
)
Or from the environment before importing georay:
export GEORAY_LOG_LEVEL=INFO
quiet_georaffer=True is the default. Direct stdout, stderr, and warnings
from georaffer calls are suppressed during tile queries and retrievals; with
DEBUG logging they are emitted through the normal logger for diagnostics.
Core Concepts
PinholeCamera
Represents an OpenCV-style calibrated pinhole camera. Distortion models are pluggable; no distortion is used when none is supplied.
Pose6D
Stores a 3x3 rotation matrix and 3D translation (camera to world, OpenCV camera axes). Translations are expected in the source's CRS, heights in the DSM's vertical reference.
BoundingBox2D
Stores a quadrilateral footprint in world coordinates, ordered as upper-left,
upper-right, lower-right, lower-left. Its utm_zone accepts UTMZone,
"utm32" or any "EPSG:<code>"; bbox.crs returns the EPSG code.
ReferenceTileSelector
Chooses source tile variants for each tile coordinate. The default
ClosestReferenceTileSelector can rank by acquisition year, season, and region.
BoundingBoxTransform
Adapts footprints before rendering. Common transforms include direct use, proportional enlargement, north-aligned envelopes, and yaw-aligned rectangles.
Outputs
ReferenceViewRenderer returns a ReferenceViewResult with optional image
and dsm fields. Each rendered layer contains:
rasters: one raster per selected reference variant; missing DSM values areNaNpixel_to_utm/utm_to_pixel: convert single points between cutout pixels and world coordinatespixel_to_utm_many/utm_to_pixel_many: the same for(N, 2)arrayspixel_to_utm_matrix: the mapping as a 3x3 homogeneous matrixmeta: source tile metadata for each variantfootprint: the rendered footprintmosaic_pixel_bbox: the footprint in mosaic pixel coordinates
Pixel coordinates are continuous with pixel (i, j) covering [i, i+1) x [j, j+1),
so its value belongs to the centre (i + 0.5, j + 0.5).
By default, raster dimensions are derived from the footprint size and
pixels_per_meter. Pass output_size=(width, height) to
ReferenceViewRenderer.render() or render_from_bbox() to resize rendered
layers to a fixed pixel resolution while keeping the coordinate mappers
consistent with the resized output.
Examples
examples/local_rasters_reference.py: render references from your own orthophoto/DSM files (no German data needed).examples/simple_reference_from_bbox.py: render image and DSM references from a known bounding box (German state data via georaffer).examples/footprints_csv_reference.py: iterate over a CSV of camera footprints (four UTM corners per image) and render orthophoto and DSM for each; runs out of the box on 10 bundled LTC footprints (examples/data/footprints_sample.csv).
Development
Run tests:
python3 -m pytest
Optional quality checks when the tools are installed:
python3 -m ruff check .
python3 -m black --check .
The package supports Python 3.10 and newer.
License
MIT, see LICENSE. Reference data downloaded through georaffer keep the licenses of their providers.
Metadata
Release files for georay 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| georay-0.1.0.tar.gz | 51.0 kB | With provenance | Details for georay-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| georay-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for georay-0.1.0-py3-none-any.whl |
Total release size: 112.0 kB
Release files / georay-0.1.0.tar.gz
| Download URL | georay-0.1.0.tar.gz |
|---|---|
| Size | 51.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0a5e67fa0a2454ca4e140068169b948c69b3544e64aee58b9e89e9f8891b8a6f
|
|
BLAKE2b-256 checksum How to use checksums |
2f7c4802797c3d7a2012b1cebb7e40bce3e592f92694ce1f5be9859ab15c41bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / georay-0.1.0-py3-none-any.whl
| Download URL | georay-0.1.0-py3-none-any.whl |
|---|---|
| Size | 61.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2e842c3ca21a6fe9a960ef6d2a3b8a709ce712fe6335747106cba9af40ab0724
|
|
BLAKE2b-256 checksum How to use checksums |
ad7ba50648fd0ad2e60a873f15ac1184cfa912450c0e46fe7f97929a8d1ac1de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logSecurity for georay 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for georay-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.