Skip to content

Commit 68c13fb

Browse files
dcherianclaude
andauthored
rusterize>=0.7 (#85)
* `rusterize>=0.7` for `all_touched` support * Pass all_touched through rusterize engine and test it Remove rusterize-specific snapshots since rusterize>=0.7 now matches rasterio output exactly. Add all_touched parametrization to tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * tweak --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a3ee155 commit 68c13fb

10 files changed

Lines changed: 44 additions & 65 deletions

File tree

docs/examples/line-zonal-stats.ipynb

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@
140140
" ds,\n",
141141
" geoms,\n",
142142
" all_touched=True,\n",
143-
" engine=\"rasterio\",\n",
143+
" engine=\"rusterize\",\n",
144144
" )\n",
145145
" .compute()\n",
146146
" .rename(\"segment\")\n",
@@ -195,14 +195,6 @@
195195
"id": "13",
196196
"metadata": {},
197197
"outputs": [],
198-
"source": []
199-
},
200-
{
201-
"cell_type": "code",
202-
"execution_count": null,
203-
"id": "14",
204-
"metadata": {},
205-
"outputs": [],
206198
"source": [
207199
"import xvec # noqa: F401\n",
208200
"\n",
@@ -213,7 +205,7 @@
213205
},
214206
{
215207
"cell_type": "markdown",
216-
"id": "15",
208+
"id": "14",
217209
"metadata": {},
218210
"source": [
219211
"## Plot"
@@ -222,7 +214,7 @@
222214
{
223215
"cell_type": "code",
224216
"execution_count": null,
225-
"id": "16",
217+
"id": "15",
226218
"metadata": {},
227219
"outputs": [],
228220
"source": [

pixi.lock

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pixi.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pytest-xdist = "*"
4747
rasterio = "*"
4848

4949
[feature.with-rusterize.pypi-dependencies]
50-
rusterize = "<0.7"
50+
rusterize = ">=0.7.1"
5151

5252
[feature.with-xarray-dev.pypi-dependencies]
5353
xarray = { git = "https://github.com/pydata/xarray.git", rev = "26ccc7f8f014f29c551fd566a04d6e9f878c0b0b" }

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ dynamic=["version"]
3838
[project.optional-dependencies]
3939
dask = ["dask-geopandas"]
4040
rasterize = ["rasterio"]
41-
rusterize = ["rusterize"]
41+
rusterize = ["rusterize>=0.7.1"]
4242
exactextract = ["exactextract", "sparse"]
4343
docs = [
4444
"geodatasets",
4545
"pooch",
4646
"dask-geopandas",
4747
"rasterio",
48-
"rusterize",
48+
"rusterize>=0.7.1",
4949
"rioxarray",
5050
"exactextract",
5151
"sparse",

src/rasterix/rasterize/core.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ def rasterize(
211211
Requires GDAL.
212212
213213
- **rusterize**: A Rust-based rasterization engine. Does not require GDAL.
214-
Does not support ``all_touched=True``.
215214
216215
- **exactextract**: Uses the exactextract library for precise sub-pixel
217216
coverage computation. Any pixel with non-zero coverage is burned, which

src/rasterix/rasterize/exact.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ def rasterize_geometries(
429429
if all_touched:
430430
raise NotImplementedError(
431431
"all_touched=True is not supported by the exactextract engine. "
432-
"Use engine='rasterio' if you need all_touched support."
432+
"Use engine='rusterize' or engine='rasterio' if you need all_touched support."
433433
)
434434

435435
if len(geometries) == 0:
@@ -540,7 +540,7 @@ def np_geometry_mask(
540540
if all_touched:
541541
raise NotImplementedError(
542542
"all_touched=True is not supported by the exactextract engine. "
543-
"Use engine='rasterio' if you need all_touched support."
543+
"Use engine='rasterio' or engine='rusterize' if you need all_touched support."
544544
)
545545

546546
if len(geometries) == 0:

src/rasterix/rasterize/rusterize.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ def rasterize_geometries(
6565
Starting value for geometry indices.
6666
all_touched : bool
6767
If True, all pixels touched by geometries will be burned in.
68-
Note: rusterize may not support this parameter directly.
6968
merge_alg : str
7069
Merge algorithm. Supported values: "last", "sum", "first", "min", "max", "count", "any".
7170
fill : Any
@@ -80,12 +79,6 @@ def rasterize_geometries(
8079
"""
8180
from rusterize import rusterize
8281

83-
if all_touched:
84-
raise NotImplementedError(
85-
"all_touched=True is not supported by the rusterize engine. "
86-
"Use engine='rasterio' if you need all_touched support."
87-
)
88-
8982
# Create GeoDataFrame with index values
9083
# Dummy CRS required by rusterize but not used by the algorithm
9184
# https://github.com/ttrotto/rusterize/issues/10
@@ -102,6 +95,7 @@ def rasterize_geometries(
10295
field="value",
10396
fun=merge_alg,
10497
background=fill,
98+
all_touched=all_touched,
10599
encoding="numpy",
106100
dtype=str(dtype),
107101
)
@@ -167,7 +161,6 @@ def np_geometry_mask(
167161
Affine transform for the output grid.
168162
all_touched : bool
169163
If True, all pixels touched by geometries will be included.
170-
Note: rusterize may not support this parameter directly.
171164
invert : bool
172165
If True, pixels inside geometries are True (unmasked).
173166
If False (default), pixels inside geometries are False (masked).
@@ -181,12 +174,6 @@ def np_geometry_mask(
181174
"""
182175
from rusterize import rusterize
183176

184-
if all_touched:
185-
raise NotImplementedError(
186-
"all_touched=True is not supported by the rusterize engine. "
187-
"Use engine='rasterio' if you need all_touched support."
188-
)
189-
190177
# Create GeoDataFrame with burn value
191178
# Dummy CRS required by rusterize but not used by the algorithm
192179
# https://github.com/ttrotto/rusterize/issues/10
@@ -202,6 +189,7 @@ def np_geometry_mask(
202189
burn=1,
203190
fun="any",
204191
background=0,
192+
all_touched=all_touched,
205193
encoding="numpy",
206194
dtype="uint8",
207195
)
-129 KB
Binary file not shown.
-129 KB
Binary file not shown.

tests/test_rasterize.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,15 @@ def dataset():
2121
return ds
2222

2323

24+
@pytest.mark.parametrize("all_touched", [False, True])
2425
@pytest.mark.parametrize("clip", [False, True])
25-
def test_rasterize(clip, engine, dataset):
26-
# Use engine-specific snapshots due to pixel boundary differences:
27-
# - rasterio: default (center-point) rasterization
28-
# - rusterize: has its own boundary handling
29-
# - exactextract: equivalent to all_touched=True (any coverage counts)
30-
if engine == "rusterize":
31-
suffix = "_rusterize"
32-
elif engine == "exactextract":
33-
suffix = "_all_touched" # exactextract matches rasterio all_touched=True
26+
def test_rasterize(clip, all_touched, engine, dataset):
27+
if engine == "exactextract" and all_touched:
28+
pytest.skip("exactextract always behaves as all_touched=True; does not accept the parameter")
29+
30+
# exactextract inherently matches the all_touched snapshot
31+
if all_touched or engine == "exactextract":
32+
suffix = "_all_touched"
3433
else:
3534
suffix = ""
3635
fname = f"rasterize_snapshot{suffix}.nc"
@@ -43,7 +42,7 @@ def test_rasterize(clip, engine, dataset):
4342
snapshot = snapshot.sel(latitude=slice(83.25, None))
4443

4544
world = gpd.read_file(geodatasets.get_path("naturalearth land"))
46-
kwargs = dict(xdim="longitude", ydim="latitude", clip=clip, engine=engine)
45+
kwargs = dict(xdim="longitude", ydim="latitude", clip=clip, all_touched=all_touched, engine=engine)
4746
rasterized = rasterize(dataset, world[["geometry"]], **kwargs)
4847
xr.testing.assert_identical(rasterized, snapshot)
4948
assert rasterized.dtype == "uint8"
@@ -68,17 +67,16 @@ def test_rasterize(clip, engine, dataset):
6867
xr.testing.assert_identical(drasterized, snapshot)
6968

7069

70+
@pytest.mark.parametrize("all_touched", [False, True])
7171
@pytest.mark.parametrize("invert", [False, True])
7272
@pytest.mark.parametrize("clip", [False, True])
73-
def test_geometry_mask(clip, invert, engine, dataset):
74-
# Use engine-specific snapshots due to pixel boundary differences:
75-
# - rasterio: default (center-point) rasterization
76-
# - rusterize: has its own boundary handling
77-
# - exactextract: equivalent to all_touched=True (any coverage counts)
78-
if engine == "rusterize":
79-
suffix = "_rusterize"
80-
elif engine == "exactextract":
81-
suffix = "_all_touched" # exactextract matches rasterio all_touched=True
73+
def test_geometry_mask(clip, invert, all_touched, engine, dataset):
74+
if engine == "exactextract" and all_touched:
75+
pytest.skip("exactextract always behaves as all_touched=True; does not accept the parameter")
76+
77+
# exactextract inherently matches the all_touched snapshot
78+
if all_touched or engine == "exactextract":
79+
suffix = "_all_touched"
8280
else:
8381
suffix = ""
8482
fname = f"geometry_mask_snapshot{suffix}.nc"
@@ -93,7 +91,9 @@ def test_geometry_mask(clip, invert, engine, dataset):
9391

9492
world = gpd.read_file(geodatasets.get_path("naturalearth land"))
9593

96-
kwargs = dict(xdim="longitude", ydim="latitude", clip=clip, invert=invert, engine=engine)
94+
kwargs = dict(
95+
xdim="longitude", ydim="latitude", clip=clip, invert=invert, all_touched=all_touched, engine=engine
96+
)
9797
rasterized = geometry_mask(dataset, world[["geometry"]], **kwargs)
9898
xr.testing.assert_identical(rasterized, snapshot)
9999

0 commit comments

Comments
 (0)