skimage.color.colorlabel 源代码

import itertools

import numpy as np

from .._shared.utils import _supported_float_type, warn
from ..util import img_as_float
from . import rgb_colors
from .colorconv import gray2rgb, rgb2hsv, hsv2rgb


__all__ = ['color_dict', 'label2rgb', 'DEFAULT_COLORS']


DEFAULT_COLORS = (
    'red',
    'blue',
    'yellow',
    'magenta',
    'green',
    'indigo',
    'darkorange',
    'cyan',
    'pink',
    'yellowgreen',
)


color_dict = {k: v for k, v in rgb_colors.__dict__.items() if isinstance(v, tuple)}


def _rgb_vector(color):
    """Return RGB color as (1, 3) array.

    This RGB array gets multiplied by masked regions of an RGB image, which are
    partially flattened by masking (i.e. dimensions 2D + RGB -> 1D + RGB).

    Parameters
    ----------
    color : str or array
        Color name in ``skimage.color.color_dict`` or RGB float values between [0, 1].
    """
    if isinstance(color, str):
        color = color_dict[color]
    # Slice to handle RGBA colors.
    return np.array(color[:3])


def _match_label_with_color(label, colors, bg_label, bg_color):
    """Return `unique_labels` and `color_cycle` for label array and color list.

    Colors are cycled for normal labels, but the background color should only
    be used for the background.
    """
    # Temporarily set background color; it will be removed later.
    if bg_color is None:
        bg_color = (0, 0, 0)
    bg_color = _rgb_vector(bg_color)

    # map labels to their ranks among all labels from small to large
    unique_labels, mapped_labels = np.unique(label, return_inverse=True)
    # unique_inverse is no longer flat in NumPy 2.0
    mapped_labels = mapped_labels.reshape(-1)

    # get rank of bg_label
    bg_label_rank_list = mapped_labels[label.flat == bg_label]

    # The rank of each label is the index of the color it is matched to in
    # color cycle. bg_label should always be mapped to the first color, so
    # its rank must be 0. Other labels should be ranked from small to large
    # from 1.
    if len(bg_label_rank_list) > 0:
        bg_label_rank = bg_label_rank_list[0]
        mapped_labels[mapped_labels < bg_label_rank] += 1
        mapped_labels[label.flat == bg_label] = 0
    else:
        mapped_labels += 1

    # Modify labels and color cycle so background color is used only once.
    color_cycle = itertools.cycle(colors)
    color_cycle = itertools.chain([bg_color], color_cycle)

    return mapped_labels, color_cycle


[文档] def label2rgb( label, image=None, colors=None, alpha=0.3, bg_label=0, bg_color=(0, 0, 0), image_alpha=1, kind='overlay', *, saturation=0, channel_axis=-1, ): """Return an RGB image where color-coded labels are painted over the image. Parameters ---------- label : ndarray Integer array of labels with the same shape as `image`. image : ndarray, optional Image used as underlay for labels. It should have the same shape as `labels`, optionally with an additional RGB (channels) axis. If `image` is an RGB image, it is converted to grayscale before coloring. colors : list, optional List of colors. If the number of labels exceeds the number of colors, then the colors are cycled. alpha : float [0, 1], optional Opacity of colorized labels. Ignored if image is `None`. bg_label : int, optional Label that's treated as the background. If `bg_label` is specified, `bg_color` is `None`, and `kind` is `overlay`, background is not painted by any colors. bg_color : str or array, optional Background color. Must be a name in ``skimage.color.color_dict`` or RGB float values between [0, 1]. image_alpha : float [0, 1], optional Opacity of the image. kind : string, one of {'overlay', 'avg'} The kind of color image desired. 'overlay' cycles over defined colors and overlays the colored labels over the original image. 'avg' replaces each labeled segment with its average color, for a stained-class or pastel painting appearance. saturation : float [0, 1], optional Parameter to control the saturation applied to the original image between fully saturated (original RGB, `saturation=1`) and fully unsaturated (grayscale, `saturation=0`). Only applies when `kind='overlay'`. channel_axis : int, optional This parameter indicates which axis of the output array will correspond to channels. If `image` is provided, this must also match the axis of `image` that corresponds to channels. .. versionadded:: 0.19 ``channel_axis`` was added in 0.19. Returns ------- result : ndarray of float, same shape as `image` The result of blending a cycling colormap (`colors`) for each distinct value in `label` with the image, at a certain alpha value. """ if image is not None: image = np.moveaxis(image, source=channel_axis, destination=-1) if kind == 'overlay': rgb = _label2rgb_overlay( label, image, colors, alpha, bg_label, bg_color, image_alpha, saturation ) elif kind == 'avg': rgb = _label2rgb_avg(label, image, bg_label, bg_color) else: raise ValueError("`kind` must be either 'overlay' or 'avg'.") return np.moveaxis(rgb, source=-1, destination=channel_axis)
def _label2rgb_overlay( label, image=None, colors=None, alpha=0.3, bg_label=-1, bg_color=None, image_alpha=1, saturation=0, ): """Return an RGB image where color-coded labels are painted over the image. Parameters ---------- label : ndarray Integer array of labels with the same shape as `image`. image : ndarray, optional Image used as underlay for labels. It should have the same shape as `labels`, optionally with an additional RGB (channels) axis. If `image` is an RGB image, it is converted to grayscale before coloring. colors : list, optional List of colors. If the number of labels exceeds the number of colors, then the colors are cycled. alpha : float [0, 1], optional Opacity of colorized labels. Ignored if image is `None`. bg_label : int, optional Label that's treated as the background. If `bg_label` is specified and `bg_color` is `None`, background is not painted by any colors. bg_color : str or array, optional Background color. Must be a name in ``skimage.color.color_dict`` or RGB float values between [0, 1]. image_alpha : float [0, 1], optional Opacity of the image. saturation : float [0, 1], optional Parameter to control the saturation applied to the original image between fully saturated (original RGB, `saturation=1`) and fully unsaturated (grayscale, `saturation=0`). Returns ------- result : ndarray of float, same shape as `image` The result of blending a cycling colormap (`colors`) for each distinct value in `label` with the image, at a certain alpha value. """ if not 0 <= saturation <= 1: warn(f'saturation must be in range [0, 1], got {saturation}') if colors is None: colors = DEFAULT_COLORS colors = [_rgb_vector(c) for c in colors] if image is None: image = np.zeros(label.shape + (3,), dtype=np.float64) # Opacity doesn't make sense if no image exists. alpha = 1 else: if image.shape[: label.ndim] != label.shape or image.ndim > label.ndim + 1: raise ValueError("`image` and `label` must be the same shape") if image.ndim == label.ndim + 1 and image.shape[-1] != 3: raise ValueError("`image` must be RGB (image.shape[-1] must be 3).") if image.min() < 0: warn("Negative intensities in `image` are not supported") float_dtype = _supported_float_type(image.dtype) image = img_as_float(image).astype(float_dtype, copy=False) if image.ndim > label.ndim: hsv = rgb2hsv(image) hsv[..., 1] *= saturation image = hsv2rgb(hsv) elif image.ndim == label.ndim: image = gray2rgb(image) image = image * image_alpha + (1 - image_alpha) # Ensure that all labels are non-negative so we can index into # `label_to_color` correctly. offset = min(label.min(), bg_label) if offset != 0: label = label - offset # Make sure you don't modify the input array. bg_label -= offset new_type = np.min_scalar_type(int(label.max())) if new_type == bool: new_type = np.uint8 label = label.astype(new_type) mapped_labels_flat, color_cycle = _match_label_with_color( label, colors, bg_label, bg_color ) if len(mapped_labels_flat) == 0: return image dense_labels = range(np.max(mapped_labels_flat) + 1) label_to_color = np.stack([c for i, c in zip(dense_labels, color_cycle)]) mapped_labels = label mapped_labels.flat = mapped_labels_flat result = label_to_color[mapped_labels] * alpha + image * (1 - alpha) # Remove background label if its color was not specified. remove_background = 0 in mapped_labels_flat and bg_color is None if remove_background: result[label == bg_label] = image[label == bg_label] return result def _label2rgb_avg(label_field, image, bg_label=0, bg_color=(0, 0, 0)): """Visualise each segment in `label_field` with its mean color in `image`. Parameters ---------- label_field : ndarray of int A segmentation of an image. image : array, shape ``label_field.shape + (3,)`` A color image of the same spatial shape as `label_field`. bg_label : int, optional A value in `label_field` to be treated as background. bg_color : 3-tuple of int, optional The color for the background label Returns ------- out : ndarray, same shape and type as `image` The output visualization. """ out = np.zeros(label_field.shape + (3,), dtype=image.dtype) labels = np.unique(label_field) bg = labels == bg_label if bg.any(): labels = labels[labels != bg_label] mask = (label_field == bg_label).nonzero() out[mask] = bg_color for label in labels: mask = (label_field == label).nonzero() color = image[mask].mean(axis=0) out[mask] = color return out