Skip to content

perf(shapes): vectorize datashader polygon scaling#738

Merged
timtreis merged 1 commit into
mainfrom
perf/datashader-vectorize-polygon-scale
Jun 22, 2026
Merged

perf(shapes): vectorize datashader polygon scaling#738
timtreis merged 1 commit into
mainfrom
perf/datashader-vectorize-polygon-scale

Conversation

@timtreis

Copy link
Copy Markdown
Member

Problem

On the datashader shapes path, polygon/multipolygon scaling used a per-geometry Python loop:

_geometry[is_polygon].apply(lambda g: affinity.scale(g, xfact=scale, yfact=scale))

affinity.scale builds a new geometry per shape — a pure-Python O(n) loop that dominates large polygon renders: measured ~60% of a 100k-polygon datashader render (and ~33 s at 1M is almost entirely this loop).

Fix

_scale_geometries: scale every coordinate about each geometry's bounding-box centre (affinity.scale's default origin) in one vectorized pass via shapely.get_coordinates/set_coordinates.

Byte-identical

Verified main-vs-branch on a 2000-polygon datashader render at scale=0.6 and scale=2.0diff_px=0, max|d|=0. Unit test test_scale_geometries_matches_affinity_scale locks the helper against shapely.affinity.scale for asymmetric polygons (bbox-centre ≠ centroid), multipolygons, and holes (tolerance 1e-9).

Impact

  • ~12× polygons / ~6.5× multipolygons on the scale step → ~2×+ end-to-end at 100k, growing with n.
  • Only fires for scale != 1.0 (default skips it; circles use the fast-path, not this branch).
  • Peak memory rises (materializes the coord block); negligible at ≤100k — chunking is a possible follow-up at 1M+.
  • The affine transform loop is left as-is: vectorizing it is ~0.9× (shapely coordinate (de)serialization dominates, not the Python callback).

The datashader path scaled polygons with a per-geometry affinity.scale loop
(_geometry[is_polygon].apply(lambda g: affinity.scale(...))) — a pure-Python
loop that dominates large polygon renders (measured ~60% of a 100k-polygon
render; the prototype's 33s at 1M is almost entirely this loop).

Replace with _scale_geometries: scale every coordinate about each geometry's
bounding-box centre (affinity.scale's default origin) in one vectorized pass
via shapely.get_coordinates/set_coordinates. Byte-identical to affinity.scale
including asymmetric shapes, multipolygons and holes (verified main-vs-branch,
diff_px=0); ~12x polygons / ~6.5x multipolygons -> ~2x+ end-to-end at 100k,
growing with n. Only fires for scale != 1.0.

The affine transform loop is left as-is (vectorizing it is ~0.9x: shapely
coordinate (de)serialization dominates, not the Python callback).
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.32%. Comparing base (55f4970) to head (2e4f0b3).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #738      +/-   ##
==========================================
+ Coverage   79.26%   79.32%   +0.05%     
==========================================
  Files          17       17              
  Lines        4596     4604       +8     
  Branches     1028     1030       +2     
==========================================
+ Hits         3643     3652       +9     
+ Misses        603      602       -1     
  Partials      350      350              
Files with missing lines Coverage Δ
src/spatialdata_plot/pl/render.py 89.69% <100.00%> (+0.13%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreis timtreis merged commit 440da99 into main Jun 22, 2026
7 of 8 checks passed
@timtreis timtreis deleted the perf/datashader-vectorize-polygon-scale branch June 22, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants