Skip to content

Add render_graph for spatial connectivity visualization#592

Open
timtreis wants to merge 6 commits intomainfrom
feature/render-graph
Open

Add render_graph for spatial connectivity visualization#592
timtreis wants to merge 6 commits intomainfrom
feature/render-graph

Conversation

@timtreis
Copy link
Copy Markdown
Member

@timtreis timtreis commented Apr 16, 2026

Summary

  • Adds sdata.pl.render_graph() to render spatial graph edges from adjacency matrices stored in table.obsp, using element centroids for coordinates via spatialdata.get_centroids()
  • Supports shapes, points, and labels elements
  • Includes groups + group_key filtering (both-endpoints semantics) and auto-discovery of element/table when unambiguous

API

# Basic: edges over shapes
sdata.pl.render_graph("cells", connectivity_key="spatial").pl.render_shapes("cells").pl.show()

# Full composition: image + graph + labels
sdata.pl.render_images("he").pl.render_graph("seg", table_name="table").pl.render_labels("seg").pl.show()

# Filtered: only tumor-to-tumor edges
sdata.pl.render_graph("cells", group_key="cell_type", groups=["tumor"]).pl.render_shapes("cells", color="cell_type").pl.show()

Implementation details

  • connectivity_key accepts full obsp key or prefix (auto-resolves "spatial""spatial_connectivities")
  • No networkx dependency — direct sparse matrix upper triangle → LineCollection
  • Rasterized by default for performance

timtreis and others added 6 commits April 16, 2026 19:32
Three visual regression tests covering the highest-value scenarios:
- Basic graph overlay on shapes (KNN connectivity edges)
- Graph overlay on labels with background image (most common real-world case)
- Group-filtered graph (only edges between specified cell types)

Tests will fail until render_graph is implemented.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds `sdata.pl.render_graph()` to render spatial graph edges from
adjacency matrices stored in table.obsp, using element centroids
for coordinates. Supports shapes, points, and labels elements via
spatialdata.get_centroids().

Key features:
- connectivity_key accepts full obsp key or prefix (auto-resolves)
- element and table_name auto-discovered when unambiguous
- groups + group_key filtering (both-endpoints semantics)
- Rasterized LineCollection for performance
- No networkx dependency (direct sparse matrix to line segments)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Drop **kwargs from render_graph() and _render_graph() — the deferred
  execution pattern can't forward arbitrary kwargs, matching how other
  render functions use kwargs.get() for named options only
- Add adjacency matrix shape validation against table.n_obs to catch
  subset graphs stored in multi-region tables
- Fix CS dispatch to use get_transformation() for element-level CS
  membership instead of type-level has_shapes/has_labels flags
- Vectorize edge filtering: replace Python-loop set lookups with
  numpy boolean arrays (has_coord + np.isin for groups)
- Remove dead line (dict overwritten immediately)
- Use triu(k=1) to skip self-loops at the sparse level

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-discovery: render_graph() with no args finds the single table/element
- Empty graph: zero-edge adjacency matrix renders without error
- Error paths: missing obsp key, missing element, groups without group_key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AnnData rejects mismatched obsp matrices at assignment time,
making this check dead code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Initial baselines were generated on macOS/arm64. Matplotlib anti-aliasing
produces subtly different output on Linux, causing CI failures with RMS
values 27-37 (tolerance 15). CI-generated images are byte-identical across
Python 3.11, 3.14-stable, and 3.14-pre.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.60000% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.40%. Comparing base (5cfedc7) to head (c190858).

Files with missing lines Patch % Lines
src/spatialdata_plot/pl/utils.py 65.95% 7 Missing and 9 partials ⚠️
src/spatialdata_plot/pl/render.py 81.48% 5 Missing and 5 partials ⚠️
src/spatialdata_plot/pl/basic.py 84.61% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #592      +/-   ##
==========================================
+ Coverage   76.30%   76.40%   +0.09%     
==========================================
  Files          11       11              
  Lines        3229     3352     +123     
  Branches      757      784      +27     
==========================================
+ Hits         2464     2561      +97     
- Misses        465      477      +12     
- Partials      300      314      +14     
Files with missing lines Coverage Δ
src/spatialdata_plot/pl/render_params.py 87.32% <100.00%> (+0.69%) ⬆️
src/spatialdata_plot/pl/basic.py 85.75% <84.61%> (+0.26%) ⬆️
src/spatialdata_plot/pl/render.py 85.83% <81.48%> (-0.36%) ⬇️
src/spatialdata_plot/pl/utils.py 66.46% <65.95%> (+<0.01%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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