8000 WIP: Turn off relabelling in label2rgb by jni · Pull Request #3015 · scikit-image/scikit-image · GitHub
[go: up one dir, main page]

Skip to content

WIP: Turn off relabelling in label2rgb #3015

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8000
1 change: 1 addition & 0 deletions TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Version 0.16
* In ``skimage.measure.moments_central``, remove ``cc`` and ``**kwargs``
arguments.
* In ``skimage.morphology.remove_small_holes``, remove ``min_size`` argument.
* Change default values in ``skimage.color.label2rgb``.

Other
-----
Expand Down
14 changes: 14 additions & 0 deletions skimage/_shared/_default_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class DefaultValue(object):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Ellipsis instead of None?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stefanv @soupault to clarify, you mean, get rid of this object, and just use Ellipsis directly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I see it, use Ellipsis instead of 'None' in this class. Your option would work equally well, though

"""Class to provide a dummy default value other than None.

This is useful for cases where it makes sense to pass 'None' as an
actual value.
"""
def __repr__(self):
return 'None'

def __str__(self):
return 'None'


DEFAULT = DefaultValue()
174 changes: 127 additions & 47 deletions skimage/color/colorlabel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import numpy as np

from .._shared.utils import warn
from .._shared._default_value import DEFAULT
from ..util import img_as_float
from . import rgb_colors
from .colorconv import rgb2gray, gray2rgb
Expand Down Expand Up @@ -39,7 +40,7 @@ def _rgb_vector(color):
return np.array(color[:3])


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

Colors are cycled for normal labels, but the background color should only
Expand All @@ -51,7 +52,10 @@ def _match_label_with_color(label, colors, bg_label, bg_color):
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)
if unique:
_, mapped_labels = np.unique(label, return_inverse=True)
else:
mapped_labels = label.ravel()

# get rank of bg_label
bg_label_rank_list = mapped_labels[label.flat == bg_label]
Expand All @@ -75,7 +79,9 @@ def _match_label_with_color(label, colors, bg_label, bg_color):


def label2rgb(label, image=None, colors=None, alpha=0.3,
bg_label=-1, bg_color=(0, 0, 0), image_alpha=1, kind='overlay'):
background=DEFAULT, background_color=None, image_alpha=1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understood correctly, this DEFAULT thing is introduced only to help in handling such complex deprecation scenarios. I think using it for other purposes would reduce user experience and should not be recommended.
Pehaps, changing the name to UPDATED could better show its temporary nature.

kind='overlay', relabel=DEFAULT,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
kind='overlay', relabel=DEFAULT,
kind='overlay', *, relabel=DEFAULT,

bg_label=DEFAULT, bg_color=DEFAULT):
"""Return an RGB image where color-coded labels are painted over the image.

Parameters
Expand All @@ -90,59 +96,124 @@ def label2rgb(label, image=None, colors=None, alpha=0.3,
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.
bg_color : str or array, optional
Background color. Must be a name in `color_dict` or RGB float values
between [0, 1].
background : int, optional
Label to be treated as background. Currently defaults to -1, but
will default to 0 starting in version 0.16. Use None to indicate that
the image contains no background.
background_color : str, tuple, array, or None, optional
The color of the background. If None, background pixels will have
no overlay at all and will show the image directly.
image_alpha : float [0, 1], optional
Opacity of the image.
kind : string, one of {'overlay', 'avg'}
kind : string, one of {'overlay', 'avg', 'average'}
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.
and overlays the colored labels over the original image. 'avg' or
'average' replaces each labeled segment with its average color, for a
stained-glass or pastel painting appearance.
relabel : bool, optional
Whether to remap input labels to be sequential. Defaults to True
currently, but will change to False in 0.16.

Other parameters
----------------
bg_label : int, optional **DEPRECATED**
Label that's treated as the background. This parameter has been
deprecated in favor of 'background' and will be removed in version
0.16.
bg_color : str or array, optional **DEPRECATED**
Background color. Must be a name in `color_dict` or RGB float values
between [0, 1]. This parameter has been deprecated in favor of
'background_color' and will be removed in version 0.16.

Returns
-------
result : array of float, shape (M, N, 3)
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 None and kind in ['avg' or 'average']:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if image is None and kind in ['avg' or 'average']:
if image is None and kind in ('avg', 'average'):

mesg = ('"image" *must* be provided when average color mode is used '
'in label2rgb. See\n\n'
'* http://scikit-image.org/docs/dev/auto_examples/'
'segmentation/plot_boundary_merge.html\n\n'
'for example usage.')
raise ValueError(mesg)
if background is DEFAULT:
if bg_label is DEFAULT:
if np.any(label == -1) or np.any(label == 0):
mesg = ('Starting in version 0.16, label2rgb will consider '
'label 0 to be background. Set background=None to '
'suppress this message without defining a background, '
'background=-1 to set -1 to be the background, or '
'background=0 to suppress this message and use 0 '
'as the background label.')
warn(mesg)
background = -1
else:
mesg = ('The parameter "bg_label" was renamed "background" in '
'version 0.14 and will be removed in version 0.16.')
warn(mesg)
background = bg_label
if background is None:
background = int(np.max(labels)) + 1
if relabel is DEFAULT:
mesg = ('The behavior of label2rgb for nonconsecutive labels will '
'change in version 0.16. Currently, nonconsecutive labels '
'are remapped to be consecutive before being mapped to '
'colors. Starting in 0.16, no remapping will occur, so that '
'coloring is reproducible between different images. To get '
'this behavior today, pass relabel=False to label2rgb. See '
'the discussion at '
'https://github.com/scikit-image/scikit-image/issues/2674 '
'for details.')
relabel = True
if kind == 'overlay':
return _label2rgb_overlay(label, image, colors, alpha, bg_label,
bg_color, image_alpha)
return _label2rgb_overlay(label, image, colors, alpha, background,
background_color, image_alpha, relabel)
else:
return _label2rgb_avg(label, image, bg_label, bg_color)
return _label2rgb_avg(label, image, background, background_color)


def _label2rgb_overlay(label, image=None, colors=None, alpha=0.3,
bg_label=-1, bg_color=None, image_alpha=1):
background=None, background_color=None, image_alpha=1,
relabel=True, bg_label=None, bg_color=None):
"""Return an RGB image where color-coded labels are painted over the image.

Parameters
----------
label : array, shape (M, N)
Integer array of labels with the same shape as `image`.
image : array, shape (M, N, 3), optional
label : array, shape (M, N,[[ P,] ...])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
label : array, shape (M, N,[[ P,] ...])
label : array, shape (M, N,[ P[, ...]])

Integer array of labels with the same shape as `image` (not including
channels).
image : array, shape (M, N,[[[ P,] ...,] 3]), optional
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
image : array, shape (M, N,[[[ P,] ...,] 3]), optional
image : array, shape (M, N,[ P,[ ...,][ 3]), optional

Image used as underlay for labels. If the input is an RGB image, it's
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.
bg_color : str or array, optional
Background color. Must be a name in `color_dict` or RGB float values
between [0, 1].
background : int, optional
Label to be treated as background. Starting in version 0.16, this
default will change to 0.
background_color : str, tuple, array, or None, optional
The color of the background. If None, background pixels will have
no overlay at all and will show the image directly.
image_alpha : float [0, 1], optional
Opacity of the image.

Other parameters
----------------
bg_label : int, optional **DEPRECATED**
Label that's treated as the background. This parameter has been
deprecated in favor of 'background'.
bg_color : str or array, optional **DEPRECATED**
Background color. Must be a name in `color_dict` or RGB float values
between [0, 1]. This parameter has been deprecated in favor of
'background_color'.

Returns
-------
result : array of float, shape (M, N, 3)
result : array of float, shape (M, N,[[ P,] ...,] 3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result : array of float, shape (M, N,[[ P,] ...,] 3)
result : array of float, shape (M, N,[ P, [...,]] 3)

The result of blending a cycling colormap (`colors`) for each distinct
value in `label` with the image, at a certain alpha value.
"""
Expand All @@ -152,32 +223,40 @@ def _label2rgb_overlay(label, image=None, colors=None, alpha=0.3,

if image is None:
image = np.zeros(label.shape + (3,), dtype=np.float64)
# Opacity doesn't make sense if no image exists.
# Opacity doesn't make sense if no image is provided
alpha = 1
else:
if not image.shape[:2] == label.shape:
raise ValueError("`image` and `label` must be the same shape")

if image.min() < 0:
warn("Negative intensities in `image` are not supported")

image = img_as_float(rgb2gray(image))
image = gray2rgb(image) * image_alpha + (1 - image_alpha)
if not image.shape[:label.ndim] == label.shape:
msg = ('The (non-color) shape of `image` and `label` passed to\n'
'skimage.color.label2rgb must exactly match. See the '
'function documentation at\n\n'
'* http://scikit-image.org/docs/dev/api/'
'skimage.color.html#skimage.color.label2rgb\n\n'
'and example usage at\n\n'
'* http://scikit-image.org/docs/dev/auto_examples/'
'xx_applications/plot_coins_segmentation.html')
raise ValueError(msg)

# convert image to an gray-only RGB image to enable blending with
# label colors. Note that rgb2gray is a no-op if the original image
# is grayscale already.
image = (image_alpha * gray2rgb(rgb2gray(image))
+ (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)
offset = min(label.min(), background)
if offset != 0:
label = label - offset # Make sure you don't modify the input array.
bg_label -= offset
background -= offset

new_type = np.min_scalar_type(int(label.max()))
if new_type == np.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)
mapped_labels_flat, color_cycle = _match_labels_with_colors(label, colors,
background, background_color, relabel)

if len(mapped_labels_flat) == 0:
return image
Expand All @@ -191,14 +270,15 @@ def _label2rgb_overlay(label, image=None, colors=None, alpha=0.3,
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
remove_background = 0 in mapped_labels_flat and background_color is None
if remove_background:
result[label == bg_label] = image[label == bg_label]
result[label == background] = image[label == background]

return result


def _label2rgb_avg(label_field, image, bg_label=0, bg_color=(0, 0, 0)):
def _label2rgb_avg(label_field, image, background=0,
background_color=(0, 0, 0)):
"""Visualise each segment in `label_field` with its mean color in `image`.

Parameters
Expand All @@ -207,9 +287,9 @@ def _label2rgb_avg(label_field, image, bg_label=0, bg_color=(0, 0, 0)):
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
background : int, optional
A value in `label_field` to be treated as background.
bg_color : 3-tuple of int, optional
background_color : 3-tuple of int, optional
The color for the background label

Returns
Expand All @@ -219,10 +299,10 @@ def _label2rgb_avg(label_field, image, bg_label=0, bg_color=(0, 0, 0)):
"""
out = np.zeros_like(image)
labels = np.unique(label_field)
bg = (labels == bg_label)
if bg.any():
labels = labels[labels != bg_label]
out[bg] = bg_color
is_background = (labels == background)
if is_background.any():
labels = labels[labels != background]
out[is_background] = background_color
for label in labels:
mask = (label_field == label).nonzero()
color = image[mask].mean(axis=0)
Expand Down
6 changes: 0 additions & 6 deletions skimage/color/tests/test_colorlabel.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,3 @@ def test_avg():
# test default background color
out_bg = label2rgb(label_field, image, bg_label=2, kind='avg')
assert_array_equal(out_bg, expected_out_bg)


def test_negative_intensity():
labels = np.arange(100).reshape(10, 10)
image = -1 * np.ones((10, 10))
assert_warns(UserWarning, label2rgb, labels, image)
0