From 3e5d3eea5df712183bfc1d484673d9e739888195 Mon Sep 17 00:00:00 2001 From: anon Date: Wed, 10 Jun 2026 17:53:06 +0200 Subject: [PATCH] perf(shapes): drop the per-render table join when coloring by a table column _render_shapes joined the element to its annotating table via _join_table_for_element, whose table[indices, :].copy() does an out-of-order sparse CSR row-gather copy of the whole AnnData (~150 ms on Visium-width tables, more on Xenium/Visium-HD). After #709 color is resolved per shape by _extract_color_column (region-masks + reindexes the table itself) and after #711 the join is a left join, so the joined element/table are no longer needed: use the original element + table. An audit confirms no downstream consumer needs the joined table - the matplotlib/datashader draws never touch it, _decorate_axs's adata is a dead parameter, and .uns custom colors are read from sdata[table_name] inside _set_color_source_vec. Output is pixel-identical to the join path (verified on partial-annotation/na_color, fully-annotated, real datashader (curio 69k) and real Visium), so the visual baselines are unchanged. Measured: visium_hne render_shapes(color=gene) 451 ms -> 297 ms (1.52x). The outline path keeps its own (rarer) join; converting it is a separate follow-up. --- src/spatialdata_plot/pl/render.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 6ab13a6f..17236af7 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -600,8 +600,8 @@ def _render_shapes( _check_obs_var_shadow(sdata, element, col_for_color, render_params.table_name) - # filter_tables=False: join_spatialelement_table below overwrites the table, - # so the cs-level sparse copy is wasted work. + # filter_tables=False: the annotating table is read directly below (color is resolved per shape + # from it, region-masked and reindexed), so the cs-level sparse table copy would be wasted work. sdata_filt = sdata.filter_by_coordinate_system( coordinate_system=coordinate_system, filter_tables=False, @@ -610,12 +610,11 @@ def _render_shapes( table_name = render_params.table_name if table_name is None: table = None - shapes = sdata_filt[element] else: + # No join/copy: _set_color_source_vec resolves each shape's color from the table (region-masked + # and reindexed to the element), so unannotated shapes keep their place and render with na_color. _check_instance_ids_overlap(sdata_filt, table_name, element, sdata_filt[element].index) - joined_element, joined_table = _join_table_for_element(sdata, element, table_name) - sdata_filt[element] = shapes = joined_element - sdata_filt[table_name] = table = joined_table + table = sdata_filt[table_name] shapes = sdata_filt[element]