Commit 9d014a46 authored by lucasb-eyer's avatar lucasb-eyer
Browse files

Clearer and more unified unary utilities.

parent 15d17821
...@@ -72,10 +72,10 @@ don't know how to without introducing an explicit dependency on numpy. ...@@ -72,10 +72,10 @@ don't know how to without introducing an explicit dependency on numpy.
There's two common ways of getting unary potentials: There's two common ways of getting unary potentials:
1. From a hard labeling generated by a human or some other processing. 1. From a hard labeling generated by a human or some other processing.
This case is covered by `from pydensecrf.utils import compute_unary`. This case is covered by `from pydensecrf.utils import unary_from_labels`.
2. From a probability distribution computed by, e.g. the softmax output of a 2. From a probability distribution computed by, e.g. the softmax output of a
deep network. For this, see `from pydensecrf.utils import softmax_to_unary`. deep network. For this, see `from pydensecrf.utils import unary_from_softmax`.
For usage of both of these, please refer to their docstrings or have a look at [the example](examples/inference.py). For usage of both of these, please refer to their docstrings or have a look at [the example](examples/inference.py).
......
...@@ -16,7 +16,7 @@ except ImportError: ...@@ -16,7 +16,7 @@ except ImportError:
imwrite = imsave imwrite = imsave
# TODO: Use scipy instead. # TODO: Use scipy instead.
from pydensecrf.utils import compute_unary, create_pairwise_bilateral, create_pairwise_gaussian from pydensecrf.utils import unary_from_labels, create_pairwise_bilateral, create_pairwise_gaussian
if len(sys.argv) != 4: if len(sys.argv) != 4:
print("Usage: python {} IMAGE ANNO OUTPUT".format(sys.argv[0])) print("Usage: python {} IMAGE ANNO OUTPUT".format(sys.argv[0]))
...@@ -52,8 +52,8 @@ colorize[:,2] = (colors & 0xFF0000) >> 16 ...@@ -52,8 +52,8 @@ colorize[:,2] = (colors & 0xFF0000) >> 16
# Compute the number of classes in the label image. # Compute the number of classes in the label image.
# We subtract one because the number shouldn't include the value 0 which stands # We subtract one because the number shouldn't include the value 0 which stands
# for "unknown" or "unsure". # for "unknown" or "unsure".
M = len(set(labels.flat)) - 1 n_labels = len(set(labels.flat)) - 1
print(M, " labels and \"unknown\" 0: ", set(labels.flat)) print(n_labels, " labels and \"unknown\" 0: ", set(labels.flat))
########################### ###########################
### Setup the CRF model ### ### Setup the CRF model ###
...@@ -64,10 +64,10 @@ if use_2d: ...@@ -64,10 +64,10 @@ if use_2d:
print("Using 2D specialized functions") print("Using 2D specialized functions")
# Example using the DenseCRF2D code # Example using the DenseCRF2D code
d = dcrf.DenseCRF2D(img.shape[1], img.shape[0], M) d = dcrf.DenseCRF2D(img.shape[1], img.shape[0], n_labels)
# get unary potentials (neg log probability) # get unary potentials (neg log probability)
U = compute_unary(labels, M, GT_PROB=0.7) U = unary_from_labels(labels, n_labels, gt_prob=0.7, zero_unsure=True)
d.setUnaryEnergy(U) d.setUnaryEnergy(U)
# This adds the color-independent term, features are the locations only. # This adds the color-independent term, features are the locations only.
...@@ -83,10 +83,10 @@ else: ...@@ -83,10 +83,10 @@ else:
print("Using generic 2D functions") print("Using generic 2D functions")
# Example using the DenseCRF class and the util functions # Example using the DenseCRF class and the util functions
d = dcrf.DenseCRF(img.shape[1] * img.shape[0], M) d = dcrf.DenseCRF(img.shape[1] * img.shape[0], n_labels)
# get unary potentials (neg log probability) # get unary potentials (neg log probability)
U = compute_unary(labels, M, GT_PROB=0.7) U = unary_from_labels(labels, n_labels, gt_prob=0.7, zero_unsure=True)
d.setUnaryEnergy(U) d.setUnaryEnergy(U)
# This creates the color-independent features and then add them to the CRF # This creates the color-independent features and then add them to the CRF
......
import numpy as np import numpy as np
from logging import warning
def compute_unary(labels, M, GT_PROB=0.5): def unary_from_labels(labels, n_labels, gt_prob, zero_unsure=True):
""" """
Simple classifier that is 50% certain that the annotation is correct. Simple classifier that is 50% certain that the annotation is correct.
(same as in the inference example). (same as in the inference example).
...@@ -9,53 +10,81 @@ def compute_unary(labels, M, GT_PROB=0.5): ...@@ -9,53 +10,81 @@ def compute_unary(labels, M, GT_PROB=0.5):
Parameters Parameters
---------- ----------
labels: nummpy.array labels: numpy.array
The label-map. The label value `0` is not a label, but the special The label-map, i.e. an array of your data's shape where each unique
value indicating that the location has no label/information and thus value corresponds to a label.
every label is equally likely. n_labels: int
M: int The total number of labels there are.
The number of labels there are, not including the special `0` value. If `zero_unsure` is True (the default), this number should not include
GT_PROB: float `0` in counting the labels, since `0` is not a label!
gt_prob: float
The certainty of the ground-truth (must be within (0,1)). The certainty of the ground-truth (must be within (0,1)).
zero_unsure: bool
If `True`, treat the label value `0` as meaning "could be anything",
i.e. entries with this value will get uniform unary probability.
If `False`, do not treat the value `0` specially, but just as any
other class.
""" """
assert 0 < GT_PROB < 1, "`GT_PROB must be in (0,1)." assert 0 < gt_prob < 1, "`gt_prob must be in (0,1)."
labels = labels.flatten() labels = labels.flatten()
u_energy = -np.log(1.0 / M) n_energy = -np.log((1.0 - gt_prob) / (n_labels - 1))
n_energy = -np.log((1.0 - GT_PROB) / (M - 1)) p_energy = -np.log(gt_prob)
p_energy = -np.log(GT_PROB)
# Note that the order of the following operations is important. # Note that the order of the following operations is important.
# That's because the later ones overwrite part of the former ones, and only # That's because the later ones overwrite part of the former ones, and only
# after all of them is `U` correct! # after all of them is `U` correct!
U = np.full((M, len(labels)), n_energy, dtype='float32') U = np.full((n_labels, len(labels)), n_energy, dtype='float32')
U[labels - 1, np.arange(U.shape[1])] = p_energy U[labels - 1 if zero_unsure else labels, np.arange(U.shape[1])] = p_energy
U[:, labels == 0] = u_energy
# Overwrite 0-labels using uniform probability, i.e. "unsure".
if zero_unsure:
U[:, labels == 0] = -np.log(1.0 / n_labels)
return U return U
def softmax_to_unary(sm, GT_PROB=1): def compute_unary(labels, M, GT_PROB=0.5):
""" """Deprecated, use `unary_from_labels` instead."""
Util function that converts softmax scores (classwise probabilities) to warning("pydensecrf.compute_unary is deprecated, use unary_from_labels instead.")
unary potentials (the negative log likelihood per node). return unary_from_labels(labels, M, GT_PROB)
def unary_from_softmax(sm, scale=None, clip=1e-5):
"""Converts softmax class-probabilities to unary potentials (NLL per node).
Parameters Parameters
---------- ----------
sm: nummpy.array sm: numpy.array
Softmax input. The first dimension is expected to be the classes, Output of a softmax where the first dimension is the classes,
all others will be flattend. all others will be flattend. This means `sm.shape[0] == n_classes`.
GT_PROB: float scale: float
The certainty of the softmax output (default is 1). The certainty of the softmax output (default is None).
If not None, the softmax outputs are scaled to range from uniform
probability for 0 outputs to `scale` probability for 1 outputs.
clip: float
Minimum value to which probability should be clipped.
This is because the unary is the negative log of the probability, and
log(0) = inf, so we need to clip 0 probabilities to a positive value.
""" """
num_cls = sm.shape[0] num_cls = sm.shape[0]
if GT_PROB < 1: if scale is not None:
assert 0 < scale <= 1, "`scale` needs to be in (0,1]"
uniform = np.ones(sm.shape) / num_cls uniform = np.ones(sm.shape) / num_cls
sm = GT_PROB * sm + (1 - GT_PROB) * uniform sm = scale * sm + (1 - scale) * uniform
if clip is not None:
sm = np.clip(sm, clip, 1.0)
return -np.log(sm).reshape([num_cls, -1]).astype(np.float32) return -np.log(sm).reshape([num_cls, -1]).astype(np.float32)
def softmax_to_unary(sm, GT_PROB=1):
"""Deprecated, use `unary_from_softmax` instead."""
warning("pydensecrf.softmax_to_unary is deprecated, use unary_from_softmax instead.")
scale = None if GT_PROB == 1 else GT_PROB
return unary_from_softmax(sm, scale, clip=None)
def create_pairwise_gaussian(sdims, shape): def create_pairwise_gaussian(sdims, shape):
""" """
Util function that create pairwise gaussian potentials. This works for all Util function that create pairwise gaussian potentials. This works for all
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment