API Reference¶
All functions are importable from the top-level contourrs package.
shapes¶
def shapes(
source: NDArray,
mask: NDArray[np.bool_] | None = None,
connectivity: int = 4,
transform: Affine | tuple[float, ...] | None = None,
nodata: int | float | None = None,
) -> list[tuple[dict, float]]
Extract polygon shapes from a raster array.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
source |
NDArray |
required | 2D numpy array (uint8/16/32, int16/32, float32/64) |
mask |
NDArray[np.bool_] |
None |
2D boolean array. True = include pixel |
connectivity |
int |
4 |
Pixel neighborhood: 4 or 8 |
transform |
Affine \| tuple[float, ...] |
None |
affine.Affine or (a, b, c, d, e, f) |
nodata |
int \| float |
None |
Exclude pixels equal to this value; np.nan excludes NaNs |
Returns: list[tuple[dict, float]] — GeoJSON geometry dicts paired with pixel values. Returns an eager list; rasterio.features.shapes returns an iterator.
Connectivity applies to every unmasked raster value, including zero. With
connectivity=8, diagonal neighbors share a region label, but parts that touch
only at a pixel corner are emitted as separate Polygon geometries. The number
of output polygons can therefore exceed the number of connected regions and
differ from rasterio's eight-connected output.
When a boundary revisits a pixel corner, it is split into simple rings before
the affine transform is applied. An enclosed ring becomes a hole, including
when that hole touches its shell at a single corner. This preserves pixel
coverage without a geometry-repair step. shapes_arrow() uses the same tracing
and polygon assembly.
Example:
import numpy as np
from contourrs import shapes
raster = np.array([[1, 1, 2], [1, 2, 2], [3, 3, 3]], dtype=np.uint8)
results = shapes(raster, connectivity=4)
for geojson, value in results:
print(f"value={value}, type={geojson['type']}")
shapes_arrow¶
def shapes_arrow(
source: NDArray,
mask: NDArray[np.bool_] | None = None,
connectivity: int = 4,
transform: Affine | tuple[float, ...] | None = None,
nodata: int | float | None = None,
) -> pyarrow.Table
Extract polygon shapes as a PyArrow Table with WKB geometry.
Zero-copy from Rust via Arrow C Data Interface. The table includes GeoParquet metadata for direct Parquet export and avoids constructing Python geometry dictionaries.
The historical benchmarks include 5-6x speedups over shapes() for some raster sizes; see performance for all sizes and conditions.
Parameters: Same as shapes().
Returns: pyarrow.Table with columns:
| Column | Type | Description |
|---|---|---|
geometry |
Binary | WKB-encoded polygons |
value |
Float64 | Pixel value |
Example:
from contourrs import shapes_arrow
import pyarrow.parquet as pq
table = shapes_arrow(raster, connectivity=4)
pq.write_table(table, "polygons.parquet")
contours¶
def contours(
source: NDArray,
thresholds: list[float],
mask: NDArray[np.bool_] | None = None,
transform: Affine | tuple[float, ...] | None = None,
nodata: int | float | None = None,
) -> list[tuple[dict, float]]
Generate filled contour (isoband) polygons from a continuous raster.
Uses marching squares to produce polygons between consecutive threshold pairs. Returns the same format as shapes() for compatibility.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
source |
NDArray |
required | 2D numpy array (uint8/16/32, int16/32, float32/64) |
thresholds |
list[float] |
required | Break values; bands formed from consecutive pairs (min 2) |
mask |
NDArray[np.bool_] |
None |
2D boolean array. True = include pixel |
transform |
Affine \| tuple[float, ...] |
None |
affine.Affine or (a, b, c, d, e, f) |
nodata |
int \| float |
None |
Exclude pixels equal to this value; np.nan excludes NaNs |
Returns: list[tuple[dict, float]] — GeoJSON geometry dicts paired with the lower threshold of each band.
Example:
import numpy as np
from contourrs import contours
dem = np.random.default_rng(42).random((256, 256)).astype(np.float32)
for geojson, value in contours(dem, thresholds=[0.25, 0.5, 0.75]):
print(f"band={value}, rings={len(geojson['coordinates'])}")
contours_arrow¶
def contours_arrow(
source: NDArray,
thresholds: list[float],
mask: NDArray[np.bool_] | None = None,
transform: Affine | tuple[float, ...] | None = None,
nodata: int | float | None = None,
) -> pyarrow.Table
Generate filled contour polygons as a PyArrow Table with WKB geometry.
Same as contours() but returns an Arrow Table via zero-copy FFI. The table includes GeoParquet-compatible metadata.
Parameters: Same as contours().
Returns: pyarrow.Table with columns:
| Column | Type | Description |
|---|---|---|
geometry |
Binary | WKB-encoded polygons |
value |
Float64 | Band lower threshold |
Example:
from contourrs import contours_arrow
import pyarrow.parquet as pq
dem = np.random.default_rng(42).random((256, 256)).astype(np.float32)
table = contours_arrow(dem, thresholds=[0.25, 0.5, 0.75])
pq.write_table(table, "contours.parquet")
Supported dtypes¶
All functions require two-dimensional, C-contiguous arrays.
Use np.ascontiguousarray(source) after slicing or transposing an array if needed; masks must also be C-contiguous.
All functions accept the following input types, and output coordinates use f64.
| Input dtype | Python type | Precision |
|---|---|---|
uint8 |
np.uint8 |
Lossless -> f64 |
uint16 |
np.uint16 |
Lossless -> f64 |
uint32 |
np.uint32 |
Lossless -> f64 |
int16 |
np.int16 |
Lossless -> f64 |
int32 |
np.int32 |
Lossless -> f64 |
float32 |
np.float32 |
Promoted to f64 |
float64 |
np.float64 |
Native (zero-copy in contours) |
Contour coordinates¶
Contours interpolate between samples at integer (col, row) coordinates.
Without an affine transform, the contour domain spans [0, width - 1] by [0, height - 1].
Categorical polygonization instead follows pixel footprints spanning [0, width] by [0, height].
Masked and nonfinite samples are excluded from contour regions.
Mask and nodata semantics¶
maskuses rasterio-style semantics:Trueincludes a pixel,Falseexcludes itnodata=is optional sugar for building that exclusion mask from the source array- If both
maskandnodataare passed, the effective mask ismask & (source != nodata) nodata=np.nanexcludes NaN pixels
Coordinate reference systems¶
An affine transform changes coordinates but does not assign a coordinate reference system (CRS).
The Arrow output declares an unknown CRS (null); it does not infer a CRS from the transform.
Assign the known CRS before exporting georeferenced data, for example:
import geopandas as gpd
gdf = gpd.GeoDataFrame.from_arrow(table).set_crs("EPSG:32615")
gdf.to_parquet("polygons.parquet")
Choose the EPSG code that describes the input raster, not the example code above.
Affine transform¶
The transform parameter accepts either an affine.Affine instance or a 6-element tuple (a, b, c, d, e, f) representing the affine transformation:
This matches the convention used by rasterio and GDAL. When None, pixel coordinates are used directly (identity transform).