@@ -618,13 +618,10 @@ def _render_shapes(
618618 )
619619
620620 table_name = render_params .table_name
621- if table_name is None :
622- table = None
623- else :
621+ if table_name is not None :
624622 # No join/copy: _set_color_source_vec resolves each shape's color from the table (region-masked
625623 # and reindexed to the element), so unannotated shapes keep their place and render with na_color.
626624 _check_instance_ids_overlap (sdata_filt , table_name , element , sdata_filt [element ].index )
627- table = sdata_filt [table_name ]
628625
629626 shapes = sdata_filt [element ]
630627
@@ -1709,6 +1706,22 @@ def _draw_channel_legend(
17091706 )
17101707
17111708
1709+ def _composite_channels (
1710+ channel_cmaps : list [Colormap ],
1711+ layers : dict [Any , np .ndarray ],
1712+ channels : list [Any ],
1713+ ) -> np .ndarray :
1714+ """Sum per-channel RGB into one ``(H, W, 3)`` buffer.
1715+
1716+ Holds O(1) full-resolution buffers instead of the full ``(n_channels, H, W, 4)`` cube;
1717+ byte-identical to stacking then summing because the reduction is sequential.
1718+ """
1719+ acc = channel_cmaps [0 ](layers [channels [0 ]])[:, :, :3 ].copy ()
1720+ for cmap , ch in zip (channel_cmaps [1 :], channels [1 :], strict = True ):
1721+ acc += cmap (layers [ch ])[:, :, :3 ]
1722+ return acc
1723+
1724+
17121725def _render_images (
17131726 sdata : sd .SpatialData ,
17141727 render_params : ImageRenderParams ,
@@ -2008,14 +2021,7 @@ def _render_images(
20082021 legend_colors = ["red" , "green" , "blue" ]
20092022 else : # -> use given cmap for each channel
20102023 channel_cmaps = [render_params .cmap_params .cmap ] * n_channels
2011- stacked = (
2012- np .stack (
2013- [channel_cmaps [ind ](layers [ch ]) for ind , ch in enumerate (channels )],
2014- 0 ,
2015- ).sum (0 )
2016- / n_channels
2017- )
2018- stacked = stacked [:, :, :3 ]
2024+ stacked = _composite_channels (channel_cmaps , layers , channels ) / n_channels
20192025 logger .warning (
20202026 "One cmap was given for multiple channels and is now used for each channel. "
20212027 + _MULTI_CMAP_BLENDING_WARNING
@@ -2036,19 +2042,11 @@ def _render_images(
20362042 if n_channels == 2 :
20372043 seed_colors = ["#ff0000ff" , "#00ff00ff" ]
20382044 channel_cmaps = [_get_linear_colormap ([c ], "k" )[0 ] for c in seed_colors ]
2039- colored = np .stack (
2040- [channel_cmaps [ch_ind ](layers [ch ]) for ch_ind , ch in enumerate (channels )],
2041- 0 ,
2042- ).sum (0 )
2043- colored = np .clip (colored [:, :, :3 ], 0 , 1 )
2045+ colored = np .clip (_composite_channels (channel_cmaps , layers , channels ), 0 , 1 )
20442046 elif n_channels == 3 :
20452047 seed_colors = _get_colors_for_categorical_obs (list (range (n_channels )))
20462048 channel_cmaps = [_get_linear_colormap ([c ], "k" )[0 ] for c in seed_colors ]
2047- colored = np .stack (
2048- [channel_cmaps [ind ](layers [ch ]) for ind , ch in enumerate (channels )],
2049- 0 ,
2050- ).sum (0 )
2051- colored = np .clip (colored [:, :, :3 ], 0 , 1 )
2049+ colored = np .clip (_composite_channels (channel_cmaps , layers , channels ), 0 , 1 )
20522050 else :
20532051 if isinstance (render_params .cmap_params , list ):
20542052 cmap_is_default = render_params .cmap_params [0 ].cmap_is_default
@@ -2102,8 +2100,7 @@ def _render_images(
21022100 raise ValueError ("If 'palette' is provided, its length must match the number of channels." )
21032101
21042102 channel_cmaps = [_get_linear_colormap ([c ], "k" )[0 ] for c in palette if isinstance (c , str )]
2105- colored = np .stack ([channel_cmaps [i ](layers [c ]) for i , c in enumerate (channels )], 0 ).sum (0 )
2106- colored = np .clip (colored [:, :, :3 ], 0 , 1 )
2103+ colored = np .clip (_composite_channels (channel_cmaps , layers , channels ), 0 , 1 )
21072104
21082105 legend_colors = list (palette )
21092106
@@ -2118,14 +2115,7 @@ def _render_images(
21182115
21192116 elif palette is None and got_multiple_cmaps :
21202117 channel_cmaps = [cp .cmap for cp in render_params .cmap_params ] # type: ignore[union-attr]
2121- colored = (
2122- np .stack (
2123- [channel_cmaps [ind ](layers [ch ]) for ind , ch in enumerate (channels )],
2124- 0 ,
2125- ).sum (0 )
2126- / n_channels
2127- )
2128- colored = colored [:, :, :3 ]
2118+ colored = _composite_channels (channel_cmaps , layers , channels ) / n_channels
21292119
21302120 legend_colors = [matplotlib .colors .to_hex (cm (0.75 )) for cm in channel_cmaps ]
21312121
0 commit comments