Add spatialdata-plot delegation backend (gated by feature flag) - #1173
Draft
timtreis wants to merge 9 commits into
Draft
Add spatialdata-plot delegation backend (gated by feature flag)#1173timtreis wants to merge 9 commits into
timtreis wants to merge 9 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1173 +/- ##
==========================================
- Coverage 78.62% 78.62% -0.01%
==========================================
Files 63 68 +5
Lines 9330 9891 +561
Branches 1557 1654 +97
==========================================
+ Hits 7336 7777 +441
- Misses 1422 1494 +72
- Partials 572 620 +48
🚀 New features to boost your workflow:
|
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>
Legend/colorbar/save/axis_label/scalebar params and image styling were captured into the Intent but never threaded or forwarded, so they were silently ignored. Thread them through capture and forward into the per-panel show()/render_images/render_graph calls: - _show_kwargs() forwards legend_loc/fontsize/fontweight/fontoutline, na_in_legend, colorbar, scalebar_dx/units/params into show(). - render_images(alpha=, cmap=, channel=) for img_alpha/img_cmap/img_channel. - save -> save_fig(fig, path=save) once at the end (not per-panel). - edges_kwargs -> render_graph (edge_alpha/linestyle/weight_key); unknown keys raise instead of being dropped. - axis_label -> post-render set_xlabel/ylabel (no native show() kwarg yet, scverse/spatialdata-plot#763). - wspace/hspace added to both capture signatures (previously rejected) and applied via subplots_adjust when the grid is backend-owned. TestWiredKwargs asserts observable effects (colorbar/legend present-absent, axis labels, save writes a file, edges reject-list) rather than internals.
timtreis
force-pushed
the
delegate-plots-to-sdata-plot
branch
from
August 12, 2026 13:56
c792632 to
a1aa420
Compare
Render a user's SpatialData directly instead of only AnnData via the shim. - _Source abstraction (_source.py): capture's only input coupling is library resolution + element naming, so _AnnDataSource (shim names) and _SpatialDataSource (names resolved from coordinate systems + tables) share one capture path. - PanelIntent carries resolved element/image/table/graph names; _render no longer derives names from library_id, so it is input-agnostic. - New public kwargs shapes_layer/points_layer/image_layer/table (scatter) and labels_layer/image_layer/table (segment) disambiguate when a coordinate system holds multiple candidate elements; ambiguity raises listing them. - Entrypoints branch on SpatialData (skip the transient-sdata shim). AnnData input now emits a DeprecationWarning (removal target v2.0). use_raw and library_key raise on SpatialData input (AnnData-only concepts). TestSpatialDataNativeInput covers categorical/continuous render, use_raw and library_key rejection, element ambiguity + shapes_layer disambiguation, and the AnnData deprecation warning.
…kend - scale_factor: previously rejected via **unsupported, now an explicit image scale-factor override for the AnnData shim (threaded intent -> _make_tmp_sdata; V1 confirmed the 1/scalef Scale direction keeps spots aligned). Ignored on SpatialData input, which already carries its own transforms. - Clarify the shim's uns[spatial] KeyError to point at SpatialData input for non-Visium layouts (no silent data drop). - Tests: scale_factor accepted+stored; render parametrized over [anndata, spatialdata]; public-API path (SQUIDPY_USE_SDATAPLOT=1 through sq.pl.spatial_scatter) returns a Figure and warns on AnnData input. - CI: a required test-sdataplot-backend job runs the delegation suite with the flag on (legacy reference suite is skipped under the flag by conftest). - Docs: release note (experimental opt-in backend + AnnData deprecation); corrected the stale capture docstrings (connectivity_key/spatial_key are supported, legend_loc='on data' warns) and documented the *_layer/table kwargs.
An informal legacy-vs-delegation render comparison surfaced a silent value-source difference: for a continuous gene the legacy colorbar ran 0-3.0 (raw counts) but the delegation backend ran 0-1.6 (.X). Legacy/scanpy resolve use_raw=None to True when no layer is set and adata.raw exists; _resolve_use_raw treated None as "use .X", so flipping the flag silently changed plotted values. Resolve use_raw=None the same way (layer is None and adata.raw is not None) and thread the layer through. Regression test asserts the default color mapping uses raw (larger vmax) while use_raw=False uses .X.
…names) Quality cleanup from a reuse/simplify/efficiency/altitude review; no behavior change. - __init__: fold the two near-identical entrypoints into one _delegate(input_obj, capture_fn, **kwargs); the input-dispatch policy now lives in one place. - _capture: collapse a dead _resolve_palette branch (both arms returned the same value); extract the duplicated legend_loc='on data' deprecation into _downgrade_on_data_legend(). - _assign_names: resolve element/image/table names once per unique library instead of once per (library x color) panel - avoids re-running filter_by_coordinate_system for every color on SpatialData input. - _intent/_render: drop PanelIntent.graph_element_name (it always equalled element_name); the graph render path reads element_name directly. - _source: drop the _ELEMENT_CONTAINER identity map; ElementKind values already are the container attribute names.
Sphinx warns on the duplicate release/notes-dev.{md,rst} pair and treats
warnings as errors, failing the Read the Docs build.
The v0.8.0 template sync (scverse#1257) deleted docs/extensions/typed_returns.py, which renders numpy-style Returns type annotations as clean cross-references. Without it, public functions annotated '-> NDArray' emit an unresolvable numpy._typing._array_like.NDArray reference, and the docs build (run with -W) fails. This is why main's Read the Docs build is currently red. Restore the extension and re-register it in conf.py. Verified green with a full 'sphinx-build -M html docs docs/_build -W'.
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).