openrelief — red-shaded terrain relief from DEMs
openrelief turns any Digital Elevation Model into a red-shaded relief image
that combines topographic slope with positive/negative topographic openness —
the visualization widely known as the Red Relief Image Map (RRIM). Give it a
DEM (or a folder/glob of DEM tiles) and it produces a seamless georeferenced RGB
GeoTIFF plus a PNG preview.
Note on naming and IP. This is an independent, open-source reimplementation of a published method. The package is intentionally named
openreliefand avoids the RRIM / Red Relief Image Map trademarks as branding. The RRIM method was developed and patented by Asia Air Survey Co., Ltd.; those patents appear to have expired, but you should verify this yourself before publishing or commercializing. See NOTICE for attribution, citation, trademark and patent details, and LICENSE for the code license.
- Any DEM rasterio can read: GeoTIFF, USGS
.dem, ERDAS.img, ArcInfo.asc,.bil/.flt, SRTM.hgt,.vrt, … - Seamless mosaics — multiple tiles are stitched through an in-memory VRT and processed in overlapping windows (a halo equal to the openness radius), so there are no seams at tile boundaries.
- Parallel on the CPU — a single-pass Numba kernel threaded across cores when available, joblib processes otherwise — and GPU-accelerated automatically when CuPy + a CUDA device are present.
- Fully tunable — every visual knob (openness radius, slope clip, gammas, blend opacity, …) is exposed, with defaults matching the classic look.
How the image is built
For every cell the package computes:
- Slope (degrees) → red saturation. Flat = white
(255,255,255), steep = pure red(255,0,0). - Topographic openness (Yokoyama et al., 2002) in
--directionsazimuths out to--radiuspixels: positive openness (high on ridges) and negative openness (high in valleys). - Differential openness
(positive − negative) / 2→ brightness. Ridges render light, valleys dark — this gives the floating-3D relief. - The grey openness layer is multiply-blended onto the red slope layer at
--opacity(0.5 by default). Method reference: Chiba, Kaneta & Suzuki (2008).
Horizontal distances are converted to ground metres automatically (linear
unit for projected CRSs; degrees→metres at the scene latitude for geographic
CRSs), so the relief is geometrically correct for any projection. If your
elevation unit differs from the horizontal unit, set --z-factor (e.g. 0.3048
for feet-Z over metre-XY).
Install
pip install openrelief # from this folder
Core deps: numpy, rasterio, scipy, joblib, Pillow.
Faster CPU (optional, recommended):
pip install "openrelief[performance]" # adds numba
With numba installed, openness is computed by a single-pass compiled kernel
parallelised across cores — typically several times faster than the NumPy
path, with bit-identical output (the equivalence is covered by the test
suite). Set OPENRELIEF_NO_NUMBA=1 to opt out.
GPU (optional): install the CuPy wheel for your CUDA toolkit, e.g.
pip install cupy-cuda12x # CUDA 12.x (or cupy-cuda11x)
When CuPy and a GPU are present, the GPU is used automatically. Check with:
openrelief --check-gpu
Command line
# Single DEM
openrelief dem.tif -o relief.tif
# A whole folder of tiles -> one seamless mosaic + preview
openrelief /path/to/dems/ -o area_relief.tif
# A glob, tuned for flat terrain, forced GPU
openrelief "tiles/*.dem" -o relief.tif --slope-max 15 --openness-range 12 --radius 40 --gpu
Useful options (openrelief -h for all):
| Option | Meaning | Default |
|---|---|---|
--radius |
openness search length (px) | 30 |
--directions |
azimuths sampled (4/8/16) | 8 |
--openness-range |
diff-openness deg → black..white | 30 |
--slope-max |
slope deg → full red | 50 |
--slope-gamma / --openness-gamma |
tone curves | 1.0 |
--opacity |
openness layer blend (0..1) | 0.5 |
--z-factor |
elevation→ground unit multiplier | 1.0 |
--downsize K |
reduce DEM resolution K× before processing (~K² faster) | 1 |
--fused-kernel |
GPU: single fused CUDA openness kernel (experimental) | off |
--window |
processing window (px) | 2048 |
--jobs |
CPU workers (-1 = all cores) | -1 |
--auto-window |
size windows from the core count | off |
--tiles-per-core |
with --auto-window, tiles/core (1 = one tile per processor) |
4 |
--gpu / --cpu |
force a backend | auto |
--gpus N |
use N GPUs (devices 0..N-1), one worker each | — |
--gpu-ids 0,1,2,3 |
specific CUDA devices for multi-GPU | — |
--compress |
deflate/lzw/zstd/none | deflate |
--split N |
split output into ≥N seamless GeoTIFF pieces + a .vrt |
1 |
--no-preview / --preview-size |
PNG quick-look | on / 4000 |
Tuning tip: flatter terrain wants smaller --slope-max and
--openness-range (more contrast); rugged terrain wants larger values. A larger
--radius broadens the relief but costs ~linearly more compute.
Python API
from openrelief import ReliefConfig, relief_from_dems, relief_array
# High-level: DEM(s) -> GeoTIFF + PNG
relief_from_dems("tiles/", "out.tif",
ReliefConfig(openness_radius=40, slope_max=20))
# Low-level: a NumPy elevation array -> (3, H, W) uint8 relief
import numpy as np
rgb = relief_array(dem_array, res_x=1.0, res_y=1.0,
cfg=ReliefConfig(openness_radius=24))
ReliefConfig carries every parameter above; use_gpu=None/True/False controls
the backend. You can also force a backend globally with the
OPENRELIEF_BACKEND=cpu|gpu environment variable.
How it scales
Cost is roughly O(8 · radius · pixels). The raster is split into independent
--window-sized tiles with a radius-pixel halo:
- CPU with numba (installed via
[performance]): windows stream through a double-buffered pipeline — a reader thread prefetches the next window from disk and a writer thread flushes the previous result while the kernel (parallelised across cores, capped by--jobs) computes the current one. No process pool, no pickling, no duplicate reads. - CPU without numba: windows are distributed over processes with joblib and
streamed to the output as they finish, so memory stays bounded regardless of
mosaic size. By default there are more windows than cores so a freed worker
immediately picks up the next one (dynamic load balancing).
--auto-windowinstead sizes the windows from the core count:--tiles-per-core 1gives one tile per processor (simplest, but prone to stragglers and high per-worker memory), while the default of 4 keeps every core fed. The pixels produced are identical either way — only the scheduling changes. - GPU: windows stream through the same double-buffered pipeline (disk I/O overlaps the GPU compute), uploads go through pinned memory, and only each window's interior — not its halo — is downloaded. The per-window size keeps GPU memory in check while still giving a large speed-up.
- Multi-GPU: on a node with several GPUs,
--gpus N(or--gpu-ids 0,1,2,3) runs one worker process pinned to each device and distributes windows across them, scaling roughly linearly with the number of GPUs. Each device only ever holds one window at a time, so per-GPU memory is bounded by--window.
Output GeoTIFFs are tiled and compressed (BIGTIFF=IF_SAFER), so very large
mosaics are handled gracefully. Empty windows in a sparse mosaic are skipped.
Speed at scale
For large areas the biggest levers are resolution, the numba kernel
(pip install -e ".[performance]" — see Install), and the GPU:
# 5x coarser -> ~25x less work; looks identical at state-wide zoom
openrelief /path/to/state_dems/ -o relief.tif --downsize 5
# two GPUs + fused kernel + large windows (validate fused kernel first, below)
openrelief /path/to/state_dems/ -o relief.tif --gpus 2 --window 8192 --fused-kernel
--downsize K reads the DEM resampled by K, cutting cost by roughly K². The
openness math also defers its arctan to once per direction (instead of once
per radius step), which alone is ~4× faster on CPU. The optional
--fused-kernel replaces the array-based openness with a single CUDA kernel
that reads the elevation grid from VRAM once — a big win on bandwidth-rich GPUs.
It is experimental and OFF by default; before a large run, confirm it agrees
with the default path on a small tile, e.g.:
openrelief tile.tif -o a.tif --gpu # default GPU path
openrelief tile.tif -o b.tif --gpu --fused-kernel # fused kernel
# then compare a.tif and b.tif (they should match within rounding)
Very large areas (state-scale) — split the output
A single GeoTIFF over an area like a whole state can reach 100+ GB, which is
unwieldy to write, open and share. Use --split N to write the result as at
least N separate GeoTIFF pieces arranged in a near-square grid, alongside a
small .vrt that re-unites them into one logical layer for GIS:
openrelief /path/to/state_dems/ -o texas_relief.tif --split 16
# -> texas_relief_r0c0.tif ... texas_relief_r3c3.tif + texas_relief.vrt
The pieces are seamless — every pixel is still computed with a full halo read
from the source mosaic, so piece boundaries match a single-file render exactly.
Load texas_relief.vrt in QGIS/ArcGIS to see the whole area, or hand out
individual pieces. Combine with --compress to shrink each piece further.
Attribution & citation
This package reimplements published methods. Please credit the original authors; full details and references are in NOTICE.
- Chiba, T., Kaneta, S., & Suzuki, Y. (2008). Red relief image map: new visualization method for three-dimensional data. ISPRS Archives, XXXVII(B2), 1071–1076.
- Yokoyama, R., Shirasawa, M., & Pike, R. J. (2002). Visualizing topography by openness. PE&RS 68(3), 257–265.
- Asia Air Survey — https://www.rrim.jp/en/
License
Source code: MIT (see LICENSE). The MIT license covers this code only and is not a patent grant; see NOTICE for the intellectual-property note.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openrelief-0.2.0.tar.gz.
File metadata
- Download URL: openrelief-0.2.0.tar.gz
- Upload date:
- Size: 36.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e12fed69a82f976bf650ad56110dc82cfab01ba52e80fc4e9e815602a841b238
|
|
| MD5 |
24b5f16ca66d2176d2a4894cac14663e
|
|
| BLAKE2b-256 |
509bc880760b25ba60766ae0ffef8ce9588fd064af03d1b3782c4611df7c66ff
|
Provenance
The following attestation bundles were made for openrelief-0.2.0.tar.gz:
Publisher:
publish.yml on nvnsudharsan/openrelief
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openrelief-0.2.0.tar.gz -
Subject digest:
e12fed69a82f976bf650ad56110dc82cfab01ba52e80fc4e9e815602a841b238 - Sigstore transparency entry: 1793370975
- Sigstore integration time:
-
Permalink:
nvnsudharsan/openrelief@b52f8c62f0343277019e04c764010a09a76834f2 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/nvnsudharsan
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b52f8c62f0343277019e04c764010a09a76834f2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file openrelief-0.2.0-py3-none-any.whl.
File metadata
- Download URL: openrelief-0.2.0-py3-none-any.whl
- Upload date:
- Size: 33.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b636b4fc3aaf157a4cb6b0d14526056257b96c19fae005f39c919e5ed718bbfe
|
|
| MD5 |
bac8771c8cbe8af5a309042ccfed75d9
|
|
| BLAKE2b-256 |
292db88835a33ee32d1440777d7ee09d7ddab39d7434d4e24fc04d9058ea17e5
|
Provenance
The following attestation bundles were made for openrelief-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on nvnsudharsan/openrelief
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openrelief-0.2.0-py3-none-any.whl -
Subject digest:
b636b4fc3aaf157a4cb6b0d14526056257b96c19fae005f39c919e5ed718bbfe - Sigstore transparency entry: 1793371669
- Sigstore integration time:
-
Permalink:
nvnsudharsan/openrelief@b52f8c62f0343277019e04c764010a09a76834f2 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/nvnsudharsan
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b52f8c62f0343277019e04c764010a09a76834f2 -
Trigger Event:
release
-
Statement type: