|
83 | 83 | ColorLike = Union[tuple[float, ...], str] |
84 | 84 |
|
85 | 85 |
|
| 86 | +def _is_color_like(color: Any) -> bool: |
| 87 | + """Check if a value is a valid color, returns False for pseudo-bools. |
| 88 | +
|
| 89 | + For discussion, see: https://github.com/scverse/spatialdata-plot/issues/327. |
| 90 | + matplotlib accepts strings in [0, 1] as grey-scale values - therefore, |
| 91 | + "0" and "1" are considered valid colors. However, we won't do that |
| 92 | + so we're filtering these out. |
| 93 | + """ |
| 94 | + if isinstance(color, bool): |
| 95 | + return False |
| 96 | + if isinstance(color, str): |
| 97 | + try: |
| 98 | + num_value = float(color) |
| 99 | + if 0 <= num_value <= 1: |
| 100 | + return False |
| 101 | + except ValueError: |
| 102 | + # we're not dealing with what matplotlib considers greyscale |
| 103 | + pass |
| 104 | + |
| 105 | + return bool(colors.is_color_like(color)) |
| 106 | + |
| 107 | + |
86 | 108 | def _prepare_params_plot( |
87 | 109 | # this param is inferred when `pl.show`` is called |
88 | 110 | num_panels: int, |
@@ -532,7 +554,15 @@ def _normalize( |
532 | 554 |
|
533 | 555 | perc = np.percentile(img, [pmin, pmax]) |
534 | 556 |
|
535 | | - norm = (img - perc[0]) / (perc[1] - perc[0] + eps) |
| 557 | + # Ensure perc is an array of two elements |
| 558 | + if np.isscalar(perc): |
| 559 | + logger.warning( |
| 560 | + "Percentile range is too small, using the same percentile for both min " |
| 561 | + "and max. Consider using a larger percentile range." |
| 562 | + ) |
| 563 | + perc = np.array([perc, perc]) |
| 564 | + |
| 565 | + norm = (img - perc[0]) / (perc[1] - perc[0] + eps) # type: ignore |
536 | 566 |
|
537 | 567 | if clip: |
538 | 568 | norm = np.clip(norm, 0, 1) |
@@ -727,7 +757,7 @@ def _map_color_seg( |
727 | 757 | val_im = np.squeeze(val_im, axis=0) |
728 | 758 | if "#" in str(color_vector[0]): |
729 | 759 | # we have hex colors |
730 | | - assert all(colors.is_color_like(c) for c in color_vector), "Not all values are color-like." |
| 760 | + assert all(_is_color_like(c) for c in color_vector), "Not all values are color-like." |
731 | 761 | cols = colors.to_rgba_array(color_vector) |
732 | 762 | else: |
733 | 763 | cols = cmap_params.cmap(cmap_params.norm(color_vector)) |
@@ -1557,15 +1587,11 @@ def _type_check_params(param_dict: dict[str, Any], element_type: str) -> dict[st |
1557 | 1587 | if (contour_px := param_dict.get("contour_px")) and not isinstance(contour_px, int): |
1558 | 1588 | raise TypeError("Parameter 'contour_px' must be an integer.") |
1559 | 1589 |
|
1560 | | - if (color := param_dict.get("color")) and element_type in { |
1561 | | - "shapes", |
1562 | | - "points", |
1563 | | - "labels", |
1564 | | - }: |
| 1590 | + if (color := param_dict.get("color")) and element_type in {"shapes", "points", "labels"}: |
1565 | 1591 | if not isinstance(color, str): |
1566 | 1592 | raise TypeError("Parameter 'color' must be a string.") |
1567 | 1593 | if element_type in {"shapes", "points"}: |
1568 | | - if colors.is_color_like(color): |
| 1594 | + if _is_color_like(color): |
1569 | 1595 | logger.info("Value for parameter 'color' appears to be a color, using it as such.") |
1570 | 1596 | param_dict["col_for_color"] = None |
1571 | 1597 | else: |
@@ -1645,7 +1671,7 @@ def _type_check_params(param_dict: dict[str, Any], element_type: str) -> dict[st |
1645 | 1671 | raise TypeError("Parameter 'cmap' must be a string, a Colormap, or a list of these types.") |
1646 | 1672 |
|
1647 | 1673 | if (na_color := param_dict.get("na_color")) != "default" and ( |
1648 | | - na_color is not None and not colors.is_color_like(na_color) |
| 1674 | + na_color is not None and not _is_color_like(na_color) |
1649 | 1675 | ): |
1650 | 1676 | raise ValueError("Parameter 'na_color' must be color-like.") |
1651 | 1677 |
|
|
0 commit comments