Skip to content

Commit 9d014a4

Browse files
committed
Clearer and more unified unary utilities.
1 parent 15d1782 commit 9d014a4

File tree

3 files changed

+65
-36
lines changed

3 files changed

+65
-36
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ don't know how to without introducing an explicit dependency on numpy.
7272
There's two common ways of getting unary potentials:
7373

7474
1. From a hard labeling generated by a human or some other processing.
75-
This case is covered by `from pydensecrf.utils import compute_unary`.
75+
This case is covered by `from pydensecrf.utils import unary_from_labels`.
7676

7777
2. From a probability distribution computed by, e.g. the softmax output of a
78-
deep network. For this, see `from pydensecrf.utils import softmax_to_unary`.
78+
deep network. For this, see `from pydensecrf.utils import unary_from_softmax`.
7979

8080
For usage of both of these, please refer to their docstrings or have a look at [the example](examples/inference.py).
8181

examples/inference.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
imwrite = imsave
1717
# TODO: Use scipy instead.
1818

19-
from pydensecrf.utils import compute_unary, create_pairwise_bilateral, create_pairwise_gaussian
19+
from pydensecrf.utils import unary_from_labels, create_pairwise_bilateral, create_pairwise_gaussian
2020

2121
if len(sys.argv) != 4:
2222
print("Usage: python {} IMAGE ANNO OUTPUT".format(sys.argv[0]))
@@ -52,8 +52,8 @@
5252
# Compute the number of classes in the label image.
5353
# We subtract one because the number shouldn't include the value 0 which stands
5454
# for "unknown" or "unsure".
55-
M = len(set(labels.flat)) - 1
56-
print(M, " labels and \"unknown\" 0: ", set(labels.flat))
55+
n_labels = len(set(labels.flat)) - 1
56+
print(n_labels, " labels and \"unknown\" 0: ", set(labels.flat))
5757

5858
###########################
5959
### Setup the CRF model ###
@@ -64,10 +64,10 @@
6464
print("Using 2D specialized functions")
6565

6666
# Example using the DenseCRF2D code
67-
d = dcrf.DenseCRF2D(img.shape[1], img.shape[0], M)
67+
d = dcrf.DenseCRF2D(img.shape[1], img.shape[0], n_labels)
6868

6969
# get unary potentials (neg log probability)
70-
U = compute_unary(labels, M, GT_PROB=0.7)
70+
U = unary_from_labels(labels, n_labels, gt_prob=0.7, zero_unsure=True)
7171
d.setUnaryEnergy(U)
7272

7373
# This adds the color-independent term, features are the locations only.
@@ -83,10 +83,10 @@
8383
print("Using generic 2D functions")
8484

8585
# Example using the DenseCRF class and the util functions
86-
d = dcrf.DenseCRF(img.shape[1] * img.shape[0], M)
86+
d = dcrf.DenseCRF(img.shape[1] * img.shape[0], n_labels)
8787

8888
# get unary potentials (neg log probability)
89-
U = compute_unary(labels, M, GT_PROB=0.7)
89+
U = unary_from_labels(labels, n_labels, gt_prob=0.7, zero_unsure=True)
9090
d.setUnaryEnergy(U)
9191

9292
# This creates the color-independent features and then add them to the CRF

pydensecrf/utils.py

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,90 @@
11
import numpy as np
2+
from logging import warning
23

34

4-
def compute_unary(labels, M, GT_PROB=0.5):
5+
def unary_from_labels(labels, n_labels, gt_prob, zero_unsure=True):
56
"""
67
Simple classifier that is 50% certain that the annotation is correct.
78
(same as in the inference example).
89
910
1011
Parameters
1112
----------
12-
labels: nummpy.array
13-
The label-map. The label value `0` is not a label, but the special
14-
value indicating that the location has no label/information and thus
15-
every label is equally likely.
16-
M: int
17-
The number of labels there are, not including the special `0` value.
18-
GT_PROB: float
13+
labels: numpy.array
14+
The label-map, i.e. an array of your data's shape where each unique
15+
value corresponds to a label.
16+
n_labels: int
17+
The total number of labels there are.
18+
If `zero_unsure` is True (the default), this number should not include
19+
`0` in counting the labels, since `0` is not a label!
20+
gt_prob: float
1921
The certainty of the ground-truth (must be within (0,1)).
22+
zero_unsure: bool
23+
If `True`, treat the label value `0` as meaning "could be anything",
24+
i.e. entries with this value will get uniform unary probability.
25+
If `False`, do not treat the value `0` specially, but just as any
26+
other class.
2027
"""
21-
assert 0 < GT_PROB < 1, "`GT_PROB must be in (0,1)."
28+
assert 0 < gt_prob < 1, "`gt_prob must be in (0,1)."
2229

2330
labels = labels.flatten()
2431

25-
u_energy = -np.log(1.0 / M)
26-
n_energy = -np.log((1.0 - GT_PROB) / (M - 1))
27-
p_energy = -np.log(GT_PROB)
32+
n_energy = -np.log((1.0 - gt_prob) / (n_labels - 1))
33+
p_energy = -np.log(gt_prob)
2834

2935
# Note that the order of the following operations is important.
3036
# That's because the later ones overwrite part of the former ones, and only
3137
# after all of them is `U` correct!
32-
U = np.full((M, len(labels)), n_energy, dtype='float32')
33-
U[labels - 1, np.arange(U.shape[1])] = p_energy
34-
U[:, labels == 0] = u_energy
38+
U = np.full((n_labels, len(labels)), n_energy, dtype='float32')
39+
U[labels - 1 if zero_unsure else labels, np.arange(U.shape[1])] = p_energy
40+
41+
# Overwrite 0-labels using uniform probability, i.e. "unsure".
42+
if zero_unsure:
43+
U[:, labels == 0] = -np.log(1.0 / n_labels)
44+
3545
return U
3646

3747

38-
def softmax_to_unary(sm, GT_PROB=1):
39-
"""
40-
Util function that converts softmax scores (classwise probabilities) to
41-
unary potentials (the negative log likelihood per node).
48+
def compute_unary(labels, M, GT_PROB=0.5):
49+
"""Deprecated, use `unary_from_labels` instead."""
50+
warning("pydensecrf.compute_unary is deprecated, use unary_from_labels instead.")
51+
return unary_from_labels(labels, M, GT_PROB)
52+
53+
54+
def unary_from_softmax(sm, scale=None, clip=1e-5):
55+
"""Converts softmax class-probabilities to unary potentials (NLL per node).
4256
4357
Parameters
4458
----------
45-
sm: nummpy.array
46-
Softmax input. The first dimension is expected to be the classes,
47-
all others will be flattend.
48-
GT_PROB: float
49-
The certainty of the softmax output (default is 1).
50-
59+
sm: numpy.array
60+
Output of a softmax where the first dimension is the classes,
61+
all others will be flattend. This means `sm.shape[0] == n_classes`.
62+
scale: float
63+
The certainty of the softmax output (default is None).
64+
If not None, the softmax outputs are scaled to range from uniform
65+
probability for 0 outputs to `scale` probability for 1 outputs.
66+
clip: float
67+
Minimum value to which probability should be clipped.
68+
This is because the unary is the negative log of the probability, and
69+
log(0) = inf, so we need to clip 0 probabilities to a positive value.
5170
"""
5271
num_cls = sm.shape[0]
53-
if GT_PROB < 1:
72+
if scale is not None:
73+
assert 0 < scale <= 1, "`scale` needs to be in (0,1]"
5474
uniform = np.ones(sm.shape) / num_cls
55-
sm = GT_PROB * sm + (1 - GT_PROB) * uniform
75+
sm = scale * sm + (1 - scale) * uniform
76+
if clip is not None:
77+
sm = np.clip(sm, clip, 1.0)
5678
return -np.log(sm).reshape([num_cls, -1]).astype(np.float32)
5779

5880

81+
def softmax_to_unary(sm, GT_PROB=1):
82+
"""Deprecated, use `unary_from_softmax` instead."""
83+
warning("pydensecrf.softmax_to_unary is deprecated, use unary_from_softmax instead.")
84+
scale = None if GT_PROB == 1 else GT_PROB
85+
return unary_from_softmax(sm, scale, clip=None)
86+
87+
5988
def create_pairwise_gaussian(sdims, shape):
6089
"""
6190
Util function that create pairwise gaussian potentials. This works for all

0 commit comments

Comments
 (0)