Skip to content

Commit cdaa0c2

Browse files
committed
perf(render_points): drop the per-point AnnData "hack"
render_points built a full AnnData over every point (X=xy, obs=coords) just to reuse the legacy color machinery — incurring AnnData's O(n) index-uniqueness check + dtype cast on every call, regardless of backend or color. The modern ColorSpec/resolve_color pipeline already carries coords (points df), color (get_values merge + color_spec), and the legend (color_spec), so the AnnData is vestigial. Remove it: feed matplotlib coords from points["x"/"y"], let the existing get_values merge supply table obs/var colors, and keep the original table in sdata_filt so resolve_color still reads user uns palettes. Also drop the now-dead `adata` parameter threaded through _add_legend_and_colorbar / _decorate_axs / _render_centroids_as_points (none read it). 10M-transcript render: ~3x faster on both backends (no-color 11.2s->3.5s mpl, 8.6s->3.2s ds; continuous 9.2s->2.6s mpl, 8.2s->2.6s ds).
1 parent 078afb1 commit cdaa0c2

2 files changed

Lines changed: 7 additions & 57 deletions

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 7 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,14 @@
1515
import matplotlib.ticker
1616
import numpy as np
1717
import pandas as pd
18-
import scanpy as sc
1918
import spatialdata as sd
2019
import xarray as xr
21-
from anndata import AnnData
2220
from matplotlib import patheffects
2321
from matplotlib.cm import ScalarMappable
2422
from matplotlib.colors import Colormap, ListedColormap, Normalize
2523
from scanpy._settings import settings as sc_settings
2624
from scanpy.plotting._tools.scatterplots import _add_categorical_legend
2725
from spatialdata import get_extent, get_values
28-
from spatialdata._core.query.relational_query import match_table_to_element
2926
from spatialdata.models import PointsModel, ShapesModel, get_table_keys
3027
from spatialdata.transformations import set_transformation
3128
from spatialdata.transformations.transformations import Identity
@@ -38,7 +35,6 @@
3835
_get_colors_for_categorical_obs,
3936
_get_linear_colormap,
4037
_map_color_seg,
41-
_maybe_set_colors,
4238
_prepare_cmap_norm,
4339
_resolve_continuous_norm,
4440
resolve_color,
@@ -333,7 +329,6 @@ def _add_legend_and_colorbar(
333329
ax: matplotlib.axes.SubplotBase,
334330
cax: ScalarMappable | None,
335331
fig_params: FigParams,
336-
adata: AnnData | None,
337332
col_for_color: str | None,
338333
color_spec: ColorSpec,
339334
palette: ListedColormap | list[str] | None,
@@ -387,7 +382,6 @@ def _add_legend_and_colorbar(
387382
ax=ax,
388383
cax=cax,
389384
fig_params=fig_params,
390-
adata=adata,
391385
value_to_plot=col_for_color,
392386
color_source_vector=color_source_vector,
393387
color_vector=color_vector,
@@ -755,7 +749,6 @@ def _draw_centroids(xy: np.ndarray, radius: float | None = None) -> None:
755749
color_spec=color_spec,
756750
norm=norm,
757751
na_color=render_params.cmap_params.na_color,
758-
adata=table,
759752
col_for_color=col_for_color,
760753
palette=palette,
761754
fig_params=fig_params,
@@ -1025,7 +1018,6 @@ def _draw_centroids(xy: np.ndarray, radius: float | None = None) -> None:
10251018
ax=ax,
10261019
cax=cax,
10271020
fig_params=fig_params,
1028-
adata=table,
10291021
col_for_color=col_for_color,
10301022
color_spec=color_spec,
10311023
palette=palette,
@@ -1110,7 +1102,6 @@ def _render_centroids_as_points(
11101102
color_spec: ColorSpec,
11111103
norm: Normalize | None,
11121104
na_color: Any,
1113-
adata: AnnData | None,
11141105
col_for_color: str | None,
11151106
palette: Any,
11161107
fig_params: FigParams,
@@ -1170,7 +1161,6 @@ def _render_centroids_as_points(
11701161
ax=ax,
11711162
cax=cax,
11721163
fig_params=fig_params,
1173-
adata=adata,
11741164
col_for_color=col_for_color,
11751165
color_spec=color_spec,
11761166
palette=palette,
@@ -1382,50 +1372,15 @@ def _render_points(
13821372
else points_pd_with_color
13831373
)
13841374

1385-
# we construct an anndata to hack the plotting functions
1386-
if table_name is None:
1387-
adata = AnnData(
1388-
X=points[["x", "y"]].values,
1389-
obs=points[coords],
1390-
dtype=points[["x", "y"]].values.dtype,
1391-
)
1392-
else:
1393-
matched_table = match_table_to_element(sdata=sdata, element_name=element, table_name=table_name)
1394-
adata_obs = matched_table.obs.copy()
1395-
# if the points are colored by values in X (or a different layer), add the values to obs
1396-
if col_for_color in matched_table.var_names:
1397-
if table_layer is None:
1398-
adata_obs[col_for_color] = matched_table[:, col_for_color].X.flatten()
1399-
else:
1400-
adata_obs[col_for_color] = matched_table[:, col_for_color].layers[table_layer].flatten()
1401-
adata = AnnData(
1402-
X=points[["x", "y"]].values,
1403-
obs=adata_obs,
1404-
dtype=points[["x", "y"]].values.dtype,
1405-
uns=matched_table.uns,
1406-
)
1407-
sdata_filt[table_name] = adata
1408-
1409-
# we can modify the sdata because of dealing with a copy
1375+
# Color (from a points column, table obs, or table var/X) is already materialized on `points` via
1376+
# the get_values merge above; coordinates and the legend come straight from `points`/`color_spec`,
1377+
# so no AnnData round-trip is needed. resolve_color reads the original table (kept in sdata_filt)
1378+
# for any user-defined uns palette.
14101379

14111380
# Convert back to dask dataframe to modify sdata
14121381
transformation_in_cs = sdata_filt.points[element].attrs["transform"][coordinate_system]
14131382
_reparse_points(sdata_filt, element, points_for_model, transformation_in_cs, coordinate_system, col_for_color)
14141383

1415-
if col_for_color is not None:
1416-
assert isinstance(col_for_color, str)
1417-
cols = sc.get.obs_df(adata, [col_for_color])
1418-
# maybe set color based on type
1419-
if isinstance(cols[col_for_color].dtype, pd.CategoricalDtype):
1420-
uns_color_key = f"{col_for_color}_colors"
1421-
if uns_color_key in adata.uns:
1422-
_maybe_set_colors(
1423-
source=adata,
1424-
target=adata,
1425-
key=col_for_color,
1426-
palette=palette,
1427-
)
1428-
14291384
# when user specified a single color, we emulate the form of `na_color` and use it
14301385
default_color = (
14311386
render_params.color if col_for_color is None and color is not None else render_params.cmap_params.na_color
@@ -1478,9 +1433,8 @@ def _render_points(
14781433
color_spec = color_spec.filter(keep)
14791434
if int(keep.sum()) == 0:
14801435
return
1481-
# filter the materialized points, adata, and re-register in sdata_filt
1436+
# filter the materialized points and re-register in sdata_filt
14821437
points = points[keep].reset_index(drop=True)
1483-
adata = adata[keep]
14841438
_reparse_points(sdata_filt, element, points, transformation_in_cs, coordinate_system, col_for_color)
14851439

14861440
color_spec = color_spec.apply_transfunc(render_params.transfunc)
@@ -1550,8 +1504,8 @@ def _render_points(
15501504
update_parameters = not _mpl_ax_contains_elements(ax)
15511505
cax = _scatter_points(
15521506
ax,
1553-
adata[:, 0].X.flatten(),
1554-
adata[:, 1].X.flatten(),
1507+
points["x"].to_numpy(),
1508+
points["y"].to_numpy(),
15551509
color_spec.color_vector,
15561510
size=render_params.size,
15571511
cmap=render_params.cmap_params.cmap,
@@ -1570,7 +1524,6 @@ def _render_points(
15701524
ax=ax,
15711525
cax=cax,
15721526
fig_params=fig_params,
1573-
adata=adata,
15741527
col_for_color=col_for_color,
15751528
color_spec=color_spec,
15761529
palette=None,
@@ -2409,7 +2362,6 @@ def _render_labels(
24092362
),
24102363
norm=render_params.cmap_params.fresh_norm(), # ax.scatter autoscales in place; don't mutate the shared norm
24112364
na_color=na_color,
2412-
adata=table if table_name is not None else None,
24132365
col_for_color=col_for_color,
24142366
palette=palette,
24152367
fig_params=fig_params,
@@ -2510,7 +2462,6 @@ def _draw_labels(
25102462
ax=ax,
25112463
cax=cax,
25122464
fig_params=fig_params,
2513-
adata=table,
25142465
col_for_color=col_for_color,
25152466
color_spec=color_spec,
25162467
palette=palette,

src/spatialdata_plot/pl/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,6 @@ def _decorate_axs(
454454
value_to_plot: str | None,
455455
color_source_vector: pd.Series[CategoricalDtype] | Categorical,
456456
color_vector: pd.Series[CategoricalDtype] | Categorical,
457-
adata: AnnData | None = None,
458457
palette: ListedColormap | str | list[str] | None = None,
459458
alpha: float = 1.0,
460459
na_color: Color = Color("default"),

0 commit comments

Comments
 (0)