Skip to content

pl.render_shapes do not support normal hex color #391

Closed

Description

@wangjiawen2013

Hi,
An normal hex color is a string include seven chars, such as "#DD4958", but pl.render_shapes uses nine chars when using datashader, which is not compatible with palette in stored in sdata["table"].uns["leiden_colors"]:
Image

This is my palette:
['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b']
So ian error occurred when using customized palette:
Image

Activity

  1. wangjiawen2013 commented on Dec 2, 2024

    @wangjiawen2013
    Author

    Besides, I must run sdata.pl.render_shapes without groups and palette first , then run sdata.pl.render_shapes with groups and palette for the second time. If I run sdata.pl.render_shapes with groups and palette directly, this error will occur:

    Image

  2. wangjiawen2013 commented on Dec 2, 2024

    @wangjiawen2013
    Author

    Another question, we can only set groups as one "string" or all the categories. Such as if we have "0", "1", "2", "3", "4", "5" categories, we can only set groups="3" or groups=["0", "1","2","3","4","5"], we cannot set groups=["1","2"] and so on.

  3. LucaMarconato commented on Jan 5, 2025

    @LucaMarconato
    Member

    Thanks for reporting. It seems to be related to #375. @timtreis any take?

  4. timtreis commented on Jan 7, 2025

    @timtreis
    Member

    hm, odd. Will investigate

  5. christophechu commented on Feb 14, 2025

    @christophechu

    The same issue persists. Besides that, why is it mandatory to specify groups when providing a palette? This restriction prevents me from customizing cell group colors freely, which is very inconvenient.

    palette
    ['#F4A582', '#0571B0', '#CA0020', '#92C5DE', '#7BAFDE', '#BBD0DE']
    sdata.pl.render_images("he_image").pl.render_shapes(
        "cell_boundaries",
        color="coarse_celltype_level1",
        method="datashader",
        groups=sdata["table"].obs['coarse_celltype_level1'].cat.categories.tolist(),
        palette=palette
    ).pl.show(title="Lineage expression over H&E image", coordinate_systems="global", figsize=(10, 5))
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/spatialdata_plot/pl/basic.py:937, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
        932     wanted_elements, wanted_shapes_on_this_cs, wants_shapes = _get_wanted_render_elements(
        933         sdata, wanted_elements, params_copy, cs, "shapes"
        934     )
        936     if wanted_shapes_on_this_cs:
    --> 937         _render_shapes(
        938             sdata=sdata,
        939             render_params=params_copy,
        940             coordinate_system=cs,
        941             ax=ax,
        942             fig_params=fig_params,
        943             scalebar_params=scalebar_params,
        944             legend_params=legend_params,
        945         )
        947 elif cmd == "render_points" and has_points:
        948     wanted_elements, wanted_points_on_this_cs, wants_points = _get_wanted_render_elements(
        949         sdata, wanted_elements, params_copy, cs, "points"
        950     )
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/spatialdata_plot/pl/render.py:259, in _render_shapes(sdata, render_params, coordinate_system, ax, fig_params, scalebar_params, legend_params)
        256         if isinstance(ds_cmap, str) and ds_cmap[0] == "#":
        257             ds_cmap = ds_cmap[:-2]
    --> 259     ds_result = ds.tf.shade(
        260         agg,
        261         cmap=ds_cmap,
        262         color_key=color_key,
        263         min_alpha=np.min([254, render_params.fill_alpha * 255]),
        264         how="linear",
        265     )
        266 elif aggregate_with_reduction is not None:  # to shut up mypy
        267     ds_cmap = render_params.cmap_params.cmap
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/transfer_functions/__init__.py:720, in shade(agg, cmap, color_key, how, alpha, min_alpha, span, name, color_baseline, rescale_discrete_levels)
        717         return _interpolate(agg, cmap, how, alpha, span, min_alpha, name,
        718                             rescale_discrete_levels)
        719 elif agg.ndim == 3:
    --> 720     return _colorize(agg, color_key, how, alpha, span, min_alpha, name, color_baseline,
        721                      rescale_discrete_levels)
        722 else:
        723     raise ValueError("agg must use 2D or 3D coordinates")
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/transfer_functions/__init__.py:378, in _colorize(agg, color_key, how, alpha, span, min_alpha, name, color_baseline, rescale_discrete_levels)
        374 if len(color_key) < len(cats):
        375     raise ValueError(f"Insufficient colors provided ({len(color_key)}) for the categorical "
        376                      f"fields available ({len(cats)})")
    --> 378 colors = [rgb(color_key[c]) for c in cats]
        379 rs, gs, bs = map(array, zip(*colors))
        381 # Reorient array (transposing the category dimension first)
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/transfer_functions/__init__.py:378, in <listcomp>(.0)
        374 if len(color_key) < len(cats):
        375     raise ValueError(f"Insufficient colors provided ({len(color_key)}) for the categorical "
        376                      f"fields available ({len(cats)})")
    --> 378 colors = [rgb(color_key[c]) for c in cats]
        379 rs, gs, bs = map(array, zip(*colors))
        381 # Reorient array (transposing the category dimension first)
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/colors.py:116, in rgb(x)
        114 if isinstance(x, str):
        115     if x.startswith('#'):
    --> 116         return hex_to_rgb(x)
        117     elif x in color_lookup:
        118         return hex_to_rgb(color_lookup[x])
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/colors.py:91, in hex_to_rgb(x)
         83 """Convert a color hexcode to an rgb tuple.
         84 
         85 Example
       (...)
         88 (255, 255, 255)
         89 """
         90 if not (x.startswith('#') and len(x) == 7):
    ---> 91     raise ValueError("Invalid hex color")
         92 x = x.strip('#')
         93 try:
    
    ValueError: Invalid hex color
    

    @LucaMarconato @timtreis

  6. LucaMarconato commented on Dec 26, 2025

    @LucaMarconato
    Member

    @timtreis is this still open?

  7. mamyAndrianteranagna commented on Feb 6, 2026

    @mamyAndrianteranagna

    Hello,
    Is this issue solved ?
    I'm facing the same problem.
    Thanks !

  8. brainfo commented on Feb 15, 2026

    @brainfo

    The same issue persists. Besides that, why is it mandatory to specify groups when providing a palette? This restriction prevents me from customizing cell group colors freely, which is very inconvenient.

    palette
    ['#F4A582', '#0571B0', '#CA0020', '#92C5DE', '#7BAFDE', '#BBD0DE']
    sdata.pl.render_images("he_image").pl.render_shapes(
        "cell_boundaries",
        color="coarse_celltype_level1",
        method="datashader",
        groups=sdata["table"].obs['coarse_celltype_level1'].cat.categories.tolist(),
        palette=palette
    ).pl.show(title="Lineage expression over H&E image", coordinate_systems="global", figsize=(10, 5))
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/spatialdata_plot/pl/basic.py:937, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
        932     wanted_elements, wanted_shapes_on_this_cs, wants_shapes = _get_wanted_render_elements(
        933         sdata, wanted_elements, params_copy, cs, "shapes"
        934     )
        936     if wanted_shapes_on_this_cs:
    --> 937         _render_shapes(
        938             sdata=sdata,
        939             render_params=params_copy,
        940             coordinate_system=cs,
        941             ax=ax,
        942             fig_params=fig_params,
        943             scalebar_params=scalebar_params,
        944             legend_params=legend_params,
        945         )
        947 elif cmd == "render_points" and has_points:
        948     wanted_elements, wanted_points_on_this_cs, wants_points = _get_wanted_render_elements(
        949         sdata, wanted_elements, params_copy, cs, "points"
        950     )
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/spatialdata_plot/pl/render.py:259, in _render_shapes(sdata, render_params, coordinate_system, ax, fig_params, scalebar_params, legend_params)
        256         if isinstance(ds_cmap, str) and ds_cmap[0] == "#":
        257             ds_cmap = ds_cmap[:-2]
    --> 259     ds_result = ds.tf.shade(
        260         agg,
        261         cmap=ds_cmap,
        262         color_key=color_key,
        263         min_alpha=np.min([254, render_params.fill_alpha * 255]),
        264         how="linear",
        265     )
        266 elif aggregate_with_reduction is not None:  # to shut up mypy
        267     ds_cmap = render_params.cmap_params.cmap
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/transfer_functions/__init__.py:720, in shade(agg, cmap, color_key, how, alpha, min_alpha, span, name, color_baseline, rescale_discrete_levels)
        717         return _interpolate(agg, cmap, how, alpha, span, min_alpha, name,
        718                             rescale_discrete_levels)
        719 elif agg.ndim == 3:
    --> 720     return _colorize(agg, color_key, how, alpha, span, min_alpha, name, color_baseline,
        721                      rescale_discrete_levels)
        722 else:
        723     raise ValueError("agg must use 2D or 3D coordinates")
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/transfer_functions/__init__.py:378, in _colorize(agg, color_key, how, alpha, span, min_alpha, name, color_baseline, rescale_discrete_levels)
        374 if len(color_key) < len(cats):
        375     raise ValueError(f"Insufficient colors provided ({len(color_key)}) for the categorical "
        376                      f"fields available ({len(cats)})")
    --> 378 colors = [rgb(color_key[c]) for c in cats]
        379 rs, gs, bs = map(array, zip(*colors))
        381 # Reorient array (transposing the category dimension first)
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/transfer_functions/__init__.py:378, in <listcomp>(.0)
        374 if len(color_key) < len(cats):
        375     raise ValueError(f"Insufficient colors provided ({len(color_key)}) for the categorical "
        376                      f"fields available ({len(cats)})")
    --> 378 colors = [rgb(color_key[c]) for c in cats]
        379 rs, gs, bs = map(array, zip(*colors))
        381 # Reorient array (transposing the category dimension first)
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/colors.py:116, in rgb(x)
        114 if isinstance(x, str):
        115     if x.startswith('#'):
    --> 116         return hex_to_rgb(x)
        117     elif x in color_lookup:
        118         return hex_to_rgb(color_lookup[x])
    
    File /rsrch8/scratch/genomic_med/tchu1/conda/envs/st-python/lib/python3.10/site-packages/datashader/colors.py:91, in hex_to_rgb(x)
         83 """Convert a color hexcode to an rgb tuple.
         84 
         85 Example
       (...)
         88 (255, 255, 255)
         89 """
         90 if not (x.startswith('#') and len(x) == 7):
    ---> 91     raise ValueError("Invalid hex color")
         92 x = x.strip('#')
         93 try:
    
    ValueError: Invalid hex color
    

    @LucaMarconato @timtreis

    Hi,
    I'm on version 0.2.14.dev13+gc796afbb7.d20260117 and I don't see this issue. the pallet I input was the same type as yours, a list ["#1474B2", "#7BCCC4", "#DF65B0", "#AA0649", "#FEDA7E"], from

    NICO_CT_COLORS = {
        "CTB": "#1474B2",
        "STB": "#7BCCC4",
        "VEC": "#DF65B0",
        "Fibroblast": "#AA0649",
        "Hofbauer cells": "#FEDA7E"
    }
    present_groups = table.obs[CELLTYPE_KEY].dropna().unique().tolist()
    
    groups = [g for g in present_groups if g in NICO_CT_COLORS]
    palette = [NICO_CT_COLORS[g] for g in groups] 
    

    In my case I have color uns in the table so there's a info:

    INFO     Successfully extracted 6 colors from 'nico_ct_leiden_0.1_colors' in
             table 'cells_nico_table_leiden_0.1_feature_selection'.
    

    while it is override by the input palette in render_shapes, not the uns (different colors in the list)

    the traceback you suggested there's character drop somewhere so that the len of the color code is no long 7. -- Not sure how.

    I'm not sure if I get your customize palette correctly? you can specify group and palette to be the same, is that fully customizable?

    Image
  9. timtreis commented on Mar 25, 2026

    @timtreis
    Member

    Works with 0.3.0 now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions