Add spatialdata-plot delegation backend (gated by feature flag)#1173
Draft
timtreis wants to merge 2 commits into
Draft
Add spatialdata-plot delegation backend (gated by feature flag)#1173timtreis wants to merge 2 commits into
timtreis wants to merge 2 commits into
Conversation
Introduces a parallel rendering pipeline for sq.pl.spatial_scatter and sq.pl.spatial_segment that routes through spatialdata-plot under SQUIDPY_USE_SDATAPLOT=1. Off by default. Closes the spatial-plotting half of scverse#912 in shim form so the legacy and new paths can run side-by-side during the migration window. Pipeline (capture-intent -> adapter -> spatialdata-plot): - _capture: parses squidpy kwargs into a structured Intent (DataIntent / RenderIntent / LayoutIntent / PostRenderIntent / PanelIntent). Panel expansion at capture, per-library values resolved to PanelIntent scalars. Folds vmin/vmax/vcenter into a Normalize, routes Colormap and list palettes through cmap, infers groups from dict palettes. - _adapter: builds a transient SpatialData from Visium-style AnnData (one coordinate system per library; shapes/points/labels element + optional image + shared table). pairwise=True on concat to preserve obsp for render_graph. - _render: per-panel render_images -> render_graph -> render_shapes / render_labels / render_points -> show, with post-render hooks for title/frameon/crop_coord. Surface covered: shape (circle/hex/square/visium_hex/None for points), color (single or multi-feature with library_first ordering), groups, palette (dict/list/Colormap), cmap, norm + vmin/vmax/vcenter, alpha, na_color, outline (triple-render), connectivity_key + edges_*, size (scalar or per-library), crop_coord, scalebar_dx/units (when spatialdata-plot exposes them), title, axis_label, frameon, fig/ax, img_alpha/img_cmap/img_channel, layer/use_raw/alt_var. legend_loc='on data' emits a DeprecationWarning and falls back to the default. Bump: spatialdata-plot>=0.3.4 for render_graph and scalebar in show(). Tests: 38 new tests in test_spatial_scatter_sdataplot.py covering the three identified user happy paths (Visium+H&E categorical, Visium+H&E continuous N-gene grid, segmentation-mask cell-type coloring) plus stress-test parity. Public API unchanged. Legacy implementation untouched. Plan in plans/delegate-plots-to-sdata-plot.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Maintainer-review pass on the delegation backend:
M1 Image materialization: replace np.asarray+transpose with np.moveaxis so
dask-backed images stay lazy until render. Critical for Visium HD scale.
M2/S3 Table model: build one TableModel per library instead of a single
concat(pairwise=True). Avoids materializing a cross-library obsp at
O(N_total^2). render_* calls pass table_name=f'{lib}_table'.
S1 Outline: use sdata-plot v0.3.4 tuple outline_color/outline_width support
to draw both rings in one render_shapes call (was three).
S2 Thread spatial_key through DataIntent.coordinate_system to the adapter
so a non-default obsm key is honored.
S4 Skip legacy reference-image tests under SQUIDPY_USE_SDATAPLOT=1
(tests/plotting/conftest.py). Avoids confusing pixel-diff failures
for users kicking the tires.
S5 Function-scope MIBI-TOF fixture with explicit copy so adapter-side
obs mutations don't leak across TestPath3Segmentation tests.
S6 Single mpl-recognized color string passed as palette routes to the
panel.color slot rather than failing sdata-plot's palette+groups
validation.
Quality: collapse needs_shapes/needs_labels/needs_points booleans into a
single DataIntent.element_kind Literal. Extract _apply_color_override.
Drop unused Intent fields (shapes_layer/labels_layer/image_layer/points_layer,
scalebar_kwargs, scale_factor). Drop dead _SHOW_SUPPORTS_SCALEBAR runtime
guard. Drop the capture_scatter_intent_path1 alias.
Adapter now uses Key.uns.spot_diameter instead of raw scalefactor lookups.
All 38 self-tests still passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1173 +/- ##
==========================================
+ Coverage 73.56% 73.69% +0.13%
==========================================
Files 44 48 +4
Lines 6929 7357 +428
Branches 1174 1239 +65
==========================================
+ Hits 5097 5422 +325
- Misses 1347 1419 +72
- Partials 485 516 +31
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces a parallel rendering pipeline for
sq.pl.spatial_scatterandsq.pl.spatial_segmentthat delegates tospatialdata-plotunderSQUIDPY_USE_SDATAPLOT=1. Off by default — the legacy implementation is untouched. Lays the foundation for closing the spatial-plotting half of #912.Architecture
capture-intent -> adapter -> spatialdata-plotinsrc/squidpy/pl/_sdata_delegation/:_intent.pyIntent,DataIntent,RenderIntent,LayoutIntent,PostRenderIntent,PanelIntent)_capture.py_adapter.pySpatialDatafrom a Visium-styleAnnData(one coordinate system per library; shapes/points/labels + optional image + shared table).pairwise=Truepreserves obsp forrender_graph_render.pyrender_images -> render_graph -> render_shapes/labels/points -> show, with post-render hooks fortitle,frameon,crop_coordThe shim is short-lived by design —
_make_tmp_sdataonly handles theAnnData -> SpatialDatatranslation and disappears whenAnnDatainput is removed at v2.0 (seeplans/delegate-plots-to-sdata-plot.md).Kwarg coverage
Captured and forwarded:
shape(circle/hex/square/visium_hex/Nonefor points)color(single feature or N-feature panel grid withlibrary_firstordering)groups,palette(dict / list /Colormap),cmap,norm+vmin/vmax/vcenterfolded intoNormalizealpha,na_color,outline(triple-render passes)connectivity_key+edges_*viarender_graphsize(scalar or per-library sequence ->render_shapes(scale=))crop_coord,scalebar_dx/scalebar_units(version-gated),title,axis_label,frameon,fig/aximg_alpha,img_cmap,img_channel,layer/use_raw/alt_varlegend_loc='on data'emits aDeprecationWarningand falls back to the default (per maintainer call: known buggy in spatial coords, slated for removal).Dependency bump
spatialdata-plot>=0.3.4forrender_graph(PR scverse/spatialdata-plot#592) andscalebar_dx/scalebar_unitsonshow()(PR scverse/spatialdata-plot#648).Test plan
test_spatial_scatter_sdataplot.pycovering the three identified user happy paths (Visium+H&E categorical, Visium+H&E continuous N-gene grid, segmentation-mask cell-type coloring) plus stress-test parity (groups,crop_coord,outline,sizeper-library, palette variants, fig/ax passthrough, edges, scalebar)SQUIDPY_USE_SDATAPLOT=1against existingtests/plotting/test_spatial_static.py: 16 pass cleanly, 16 fail (one is fixture pollution in the session-scoped fixture; the rest are expected pixel-diff against legacy baselines because the renderer is nowspatialdata-plot)SQUIDPY_USE_SDATAPLOT=1job (follow-up — not in this PR)mainkeep matching the legacy renderer)Public API
Unchanged. Existing
sq.pl.spatial_scatter/sq.pl.spatial_segmentsignatures and behavior are identical when the flag is off. The internal entrypoints are underscore-prefixed (_spatial_scatter_via_sdata_plot,_spatial_segment_via_sdata_plot).