Skip to content

Commit 2c70efa

Browse files
committed
Merge branch 'MarvinTeichmann-unittest'
2 parents 8b00be1 + 617a016 commit 2c70efa

File tree

15 files changed

+423
-67
lines changed

15 files changed

+423
-67
lines changed

.gitignore

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,54 @@ build
55

66
pydensecrf/eigen.cpp
77
pydensecrf/densecrf.cpp
8+
9+
10+
# Byte-compiled / optimized / DLL files
11+
__pycache__/
12+
*.py[cod]
13+
*$py.class
14+
15+
# C extensions
16+
*.so
17+
18+
# Distribution / packaging
19+
.Python
20+
env/
21+
build/
22+
develop-eggs/
23+
dist/
24+
downloads/
25+
eggs/
26+
.eggs/
27+
lib/
28+
lib64/
29+
parts/
30+
sdist/
31+
var/
32+
wheels/
33+
*.egg-info/
34+
.installed.cfg
35+
*.egg
36+
37+
38+
# PyInstaller
39+
# Usually these files are written by a python script from a template
40+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
41+
*.manifest
42+
*.spec
43+
44+
# Installer logs
45+
pip-log.txt
46+
pip-delete-this-directory.txt
47+
48+
# Unit test / coverage reports
49+
htmlcov/
50+
.tox/
51+
.coverage
52+
.coverage.*
53+
.cache
54+
nosetests.xml
55+
coverage.xml
56+
*.cover
57+
.hypothesis/
58+

MANIFEST.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
include pydensecrf/eigen.pxd
2+
include pydensecrf/eigen.pyx
3+
include pydensecrf/densecrf.pxd
4+
include pydensecrf/densecrf.pyx
5+
recursive-include pydensecrf/densecrf *

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ and provide a link to this repository as a footnote or a citation.
1616
Installation
1717
============
1818

19-
You can install this using `pip` by executing:
19+
The package is on PyPI, so simply run `pip install pydensecrf` to install it.
20+
21+
If you want the newest and freshest version, you can install it by executing:
2022

2123
```
2224
pip install git+https://github.com/lucasb-eyer/pydensecrf.git
@@ -101,10 +103,14 @@ d.addPairwiseGaussian(sxy=3, compat=3)
101103
d.addPairwiseBilateral(sxy=80, srgb=13, rgbim=im, compat=10)
102104
```
103105

106+
### Non-RGB bilateral
107+
104108
An important caveat is that `addPairwiseBilateral` only works for RGB images, i.e. three channels.
105109
If your data is of different type than this simple but common case, you'll need to compute your
106110
own pairwise energy using `utils.create_pairwise_bilateral`; see the [generic non-2D case](https://github.com/lucasb-eyer/pydensecrf#generic-non-2d) for details.
107111

112+
A good [example of working with Non-RGB data](https://github.com/lucasb-eyer/pydensecrf/blob/master/examples/Non%20RGB%20Example.ipynb) is provided as a notebook in the examples folder.
113+
108114
### Compatibilities
109115

110116
The `compat` argument can be any of the following:
@@ -145,6 +151,7 @@ According to the paper, `w(2)` was set to 1 and `w(1)` was cross-validated, but
145151
Looking through Philip's code (included in [pydensecrf/densecrf](https://github.com/lucasb-eyer/pydensecrf/tree/master/pydensecrf/densecrf)),
146152
I couldn't find such explicit weights, and my guess is they are thus hard-coded to 1.
147153
If anyone knows otherwise, please open an issue or, better yet, a pull-request.
154+
Update: user @waldol1 has an idea in [this issue](https://github.com/lucasb-eyer/pydensecrf/issues/37). Feel free to try it out!
148155

149156
Inference
150157
---------
@@ -230,3 +237,24 @@ Common Problems
230237
---------------------------------
231238

232239
If while importing pydensecrf you get an error about some undefined symbols (for example `.../pydensecrf/densecrf.so: undefined symbol: _ZTINSt8ios_base7failureB5cxx11E`), you most likely are inadvertently mixing different compilers or toolchains. Try to see what's going on using tools like `ldd`. If you're using Anaconda, [running `conda install libgcc` might be a solution](https://github.com/lucasb-eyer/pydensecrf/issues/28).
240+
241+
Maintaining
242+
===========
243+
244+
These are instructions for maintainers about how to release new versions. (Mainly instructions for myself.)
245+
246+
```
247+
# Go increment the version in setup.py
248+
> python setup.py build_ext
249+
> python setup.py sdist
250+
> twine upload dist/pydensecrf-VERSION_NUM.tar.gz
251+
```
252+
253+
And that's it. At some point, it would be cool to automate this on [TravisCI](https://docs.travis-ci.com/user/deployment/pypi/), but not worth it yet.
254+
At that point, looking into [creating "manylinux" wheels](https://github.com/pypa/python-manylinux-demo) might be nice, too.
255+
256+
Testing
257+
=======
258+
259+
Thanks to @MarvinTeichmann we now have proper tests, install the package and run `py.test`.
260+
Maybe there's a better way to run them, but both of us don't know :smile:

pydensecrf/densecrf.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ cdef extern from "densecrf/include/densecrf.h":
8686

8787
cdef class DenseCRF:
8888
cdef c_DenseCRF *_this
89+
cdef int _nlabel
90+
cdef int _nvar
8991

9092

9193
cdef class DenseCRF2D(DenseCRF):

pydensecrf/densecrf.pyx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,30 @@ cdef class DenseCRF:
5959
else:
6060
self._this = NULL
6161

62+
self._nvar = nvar
63+
self._nlabel = nlabels
64+
6265
def __dealloc__(self):
6366
# Because destructors are virtual, this is enough to delete any object
6467
# of child classes too.
6568
if self._this:
6669
del self._this
6770

6871
def addPairwiseEnergy(self, float[:,::1] features not None, compat, KernelType kernel=DIAG_KERNEL, NormalizationType normalization=NORMALIZE_SYMMETRIC):
72+
if features.shape[1] != self._nvar:
73+
raise ValueError("Bad shape for pairwise energy (Need (?, {}), got {})".format(self._nvar, (features.shape[0], features.shape[1])))
74+
6975
self._this.addPairwiseEnergy(eigen.c_matrixXf(features), _labelcomp(compat), kernel, normalization)
7076

7177
def setUnary(self, Unary u):
7278
self._this.setUnaryEnergy(u.move())
7379

7480
def setUnaryEnergy(self, float[:,::1] u not None, float[:,::1] f = None):
81+
if u.shape[0] != self._nlabel or u.shape[1] != self._nvar:
82+
raise ValueError("Bad shape for unary energy (Need {}, got {})".format((self._nlabel, self._nvar), (u.shape[0], u.shape[1])))
83+
# TODO: I don't remember the exact shape `f` should have, so I'm not putting an assertion here.
84+
# If you get hit by a wrong shape of `f`, please open an issue with the necessary info!
85+
7586
if f is None:
7687
self._this.setUnaryEnergy(eigen.c_matrixXf(u))
7788
else:
@@ -102,6 +113,10 @@ cdef class DenseCRF2D(DenseCRF):
102113
self._w = w
103114
self._h = h
104115

116+
# Also set these for the superclass
117+
self._nvar = w*h
118+
self._nlabel = nlabels
119+
105120
def addPairwiseGaussian(self, sxy, compat, KernelType kernel=DIAG_KERNEL, NormalizationType normalization=NORMALIZE_SYMMETRIC):
106121
if isinstance(sxy, Number):
107122
sxy = (sxy, sxy)

pydensecrf/test.py

Lines changed: 0 additions & 48 deletions
This file was deleted.

pydensecrf/test_eigen.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

pydensecrf/tests/test_dcrf.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import numpy as np
2+
import pydensecrf.densecrf as dcrf
3+
import pydensecrf.utils as utils
4+
5+
import pytest
6+
7+
8+
def _get_simple_unary():
9+
unary1 = np.zeros((10, 10), dtype=np.float32)
10+
unary1[:, [0, -1]] = unary1[[0, -1], :] = 1
11+
12+
unary2 = np.zeros((10, 10), dtype=np.float32)
13+
unary2[4:7, 4:7] = 1
14+
15+
unary = np.vstack([unary1.flat, unary2.flat])
16+
unary = (unary + 1) / (np.sum(unary, axis=0) + 2)
17+
18+
return unary
19+
20+
21+
def _get_simple_img():
22+
23+
img = np.zeros((10, 10, 3), dtype=np.uint8)
24+
img[2:8, 2:8, :] = 255
25+
26+
return img
27+
28+
29+
def test_call_dcrf2d():
30+
31+
d = dcrf.DenseCRF2D(10, 10, 2)
32+
33+
unary = _get_simple_unary()
34+
img = _get_simple_img()
35+
36+
d.setUnaryEnergy(-np.log(unary))
37+
# d.setUnaryEnergy(PyConstUnary(-np.log(Up)))
38+
39+
d.addPairwiseBilateral(sxy=2, srgb=2, rgbim=img, compat=3)
40+
# d.addPairwiseBilateral(2, 2, img, 3)
41+
np.argmax(d.inference(10), axis=0).reshape(10, 10)
42+
43+
44+
def test_call_dcrf():
45+
46+
d = dcrf.DenseCRF(100, 2)
47+
48+
unary = _get_simple_unary()
49+
img = _get_simple_img()
50+
51+
d.setUnaryEnergy(-np.log(unary))
52+
# d.setUnaryEnergy(PyConstUnary(-np.log(Up)))
53+
54+
feats = utils.create_pairwise_bilateral(sdims=(2, 2), schan=2,
55+
img=img, chdim=2)
56+
57+
d.addPairwiseEnergy(feats, compat=3)
58+
# d.addPairwiseBilateral(2, 2, img, 3)
59+
np.argmax(d.inference(10), axis=0).reshape(10, 10)
60+
61+
62+
def test_call_dcrf_eq_dcrf2d():
63+
64+
d = dcrf.DenseCRF(100, 2)
65+
d2 = dcrf.DenseCRF2D(10, 10, 2)
66+
67+
unary = _get_simple_unary()
68+
img = _get_simple_img()
69+
70+
d.setUnaryEnergy(-np.log(unary))
71+
d2.setUnaryEnergy(-np.log(unary))
72+
73+
feats = utils.create_pairwise_bilateral(sdims=(2, 2), schan=2,
74+
img=img, chdim=2)
75+
76+
d.addPairwiseEnergy(feats, compat=3)
77+
78+
d2.addPairwiseBilateral(sxy=2, srgb=2, rgbim=img, compat=3)
79+
# d.addPairwiseBilateral(2, 2, img, 3)
80+
res1 = np.argmax(d.inference(10), axis=0).reshape(10, 10)
81+
res2 = np.argmax(d2.inference(10), axis=0).reshape(10, 10)
82+
83+
assert(np.all(res1 == res2))
84+
85+
86+
def test_compact_wrong():
87+
88+
# Tests whether expection is indeed raised
89+
##########################################
90+
91+
# Via e-mail: crash when non-float32 compat
92+
d = dcrf.DenseCRF2D(10, 10, 2)
93+
d.setUnaryEnergy(np.ones((2, 10 * 10), dtype=np.float32))
94+
compat = np.array([1.0, 2.0])
95+
96+
with pytest.raises(ValueError):
97+
d.addPairwiseBilateral(sxy=(3, 3), srgb=(3, 3, 3), rgbim=np.zeros(
98+
(10, 10, 3), np.uint8), compat=compat)
99+
d.inference(2)

pydensecrf/tests/test_eigen.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import numpy as np
2+
import pydensecrf.eigen as e
3+
4+
import pytest
5+
6+
7+
def test_vector_conversion():
8+
np_vector = np.random.randn(3).astype(np.float32)
9+
c_vector = e.vectorXf(np_vector)
10+
assert np.all(np.array(c_vector) == np_vector)
11+
12+
13+
def test_matrix_conversion():
14+
np_matrix = np.random.randn(3, 3).astype(np.float32)
15+
assert(np_matrix.ndim == 2)
16+
c_matrix = e.matrixXf(np_matrix)
17+
assert np.all(np.array(c_matrix) == np_matrix)
18+
19+
20+
def test_wrong_dims():
21+
np_matrix = np.random.randn(3, 3, 3).astype(np.float32)
22+
assert(np_matrix.ndim == 3)
23+
# c_matrix only supports ndim == 2
24+
with pytest.raises(ValueError):
25+
# Check whether a Value Error is raised
26+
e.matrixXf(np_matrix)
27+
28+
29+
def test_wrong_type():
30+
np_matrix = np.random.randn(3, 3).astype(np.float64)
31+
# c_matrix requies type np.float32
32+
with pytest.raises(ValueError):
33+
# Check whether a Value Error is raised
34+
e.matrixXf(np_matrix)
35+
36+
37+
def test_none_type():
38+
np_matrix = None
39+
with pytest.raises(TypeError):
40+
# Check whether a Value Error is raised
41+
e.matrixXf(np_matrix)

0 commit comments

Comments
 (0)