Commit 2c70efa9 authored by lucasb-eyer's avatar lucasb-eyer
Browse files

Merge branch 'MarvinTeichmann-unittest'

parents 8b00be1e 617a0162
...@@ -5,3 +5,54 @@ build ...@@ -5,3 +5,54 @@ build
pydensecrf/eigen.cpp pydensecrf/eigen.cpp
pydensecrf/densecrf.cpp pydensecrf/densecrf.cpp
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
include pydensecrf/eigen.pxd
include pydensecrf/eigen.pyx
include pydensecrf/densecrf.pxd
include pydensecrf/densecrf.pyx
recursive-include pydensecrf/densecrf *
...@@ -16,7 +16,9 @@ and provide a link to this repository as a footnote or a citation. ...@@ -16,7 +16,9 @@ and provide a link to this repository as a footnote or a citation.
Installation Installation
============ ============
You can install this using `pip` by executing: The package is on PyPI, so simply run `pip install pydensecrf` to install it.
If you want the newest and freshest version, you can install it by executing:
``` ```
pip install git+https://github.com/lucasb-eyer/pydensecrf.git pip install git+https://github.com/lucasb-eyer/pydensecrf.git
...@@ -101,10 +103,14 @@ d.addPairwiseGaussian(sxy=3, compat=3) ...@@ -101,10 +103,14 @@ d.addPairwiseGaussian(sxy=3, compat=3)
d.addPairwiseBilateral(sxy=80, srgb=13, rgbim=im, compat=10) d.addPairwiseBilateral(sxy=80, srgb=13, rgbim=im, compat=10)
``` ```
### Non-RGB bilateral
An important caveat is that `addPairwiseBilateral` only works for RGB images, i.e. three channels. An important caveat is that `addPairwiseBilateral` only works for RGB images, i.e. three channels.
If your data is of different type than this simple but common case, you'll need to compute your If your data is of different type than this simple but common case, you'll need to compute your
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. 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.
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.
### Compatibilities ### Compatibilities
The `compat` argument can be any of the following: 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 ...@@ -145,6 +151,7 @@ According to the paper, `w(2)` was set to 1 and `w(1)` was cross-validated, but
Looking through Philip's code (included in [pydensecrf/densecrf](https://github.com/lucasb-eyer/pydensecrf/tree/master/pydensecrf/densecrf)), Looking through Philip's code (included in [pydensecrf/densecrf](https://github.com/lucasb-eyer/pydensecrf/tree/master/pydensecrf/densecrf)),
I couldn't find such explicit weights, and my guess is they are thus hard-coded to 1. I couldn't find such explicit weights, and my guess is they are thus hard-coded to 1.
If anyone knows otherwise, please open an issue or, better yet, a pull-request. If anyone knows otherwise, please open an issue or, better yet, a pull-request.
Update: user @waldol1 has an idea in [this issue](https://github.com/lucasb-eyer/pydensecrf/issues/37). Feel free to try it out!
Inference Inference
--------- ---------
...@@ -230,3 +237,24 @@ Common Problems ...@@ -230,3 +237,24 @@ Common Problems
--------------------------------- ---------------------------------
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). 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).
Maintaining
===========
These are instructions for maintainers about how to release new versions. (Mainly instructions for myself.)
```
# Go increment the version in setup.py
> python setup.py build_ext
> python setup.py sdist
> twine upload dist/pydensecrf-VERSION_NUM.tar.gz
```
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.
At that point, looking into [creating "manylinux" wheels](https://github.com/pypa/python-manylinux-demo) might be nice, too.
Testing
=======
Thanks to @MarvinTeichmann we now have proper tests, install the package and run `py.test`.
Maybe there's a better way to run them, but both of us don't know :smile:
...@@ -86,6 +86,8 @@ cdef extern from "densecrf/include/densecrf.h": ...@@ -86,6 +86,8 @@ cdef extern from "densecrf/include/densecrf.h":
cdef class DenseCRF: cdef class DenseCRF:
cdef c_DenseCRF *_this cdef c_DenseCRF *_this
cdef int _nlabel
cdef int _nvar
cdef class DenseCRF2D(DenseCRF): cdef class DenseCRF2D(DenseCRF):
......
...@@ -59,6 +59,9 @@ cdef class DenseCRF: ...@@ -59,6 +59,9 @@ cdef class DenseCRF:
else: else:
self._this = NULL self._this = NULL
self._nvar = nvar
self._nlabel = nlabels
def __dealloc__(self): def __dealloc__(self):
# Because destructors are virtual, this is enough to delete any object # Because destructors are virtual, this is enough to delete any object
# of child classes too. # of child classes too.
...@@ -66,12 +69,20 @@ cdef class DenseCRF: ...@@ -66,12 +69,20 @@ cdef class DenseCRF:
del self._this del self._this
def addPairwiseEnergy(self, float[:,::1] features not None, compat, KernelType kernel=DIAG_KERNEL, NormalizationType normalization=NORMALIZE_SYMMETRIC): def addPairwiseEnergy(self, float[:,::1] features not None, compat, KernelType kernel=DIAG_KERNEL, NormalizationType normalization=NORMALIZE_SYMMETRIC):
if features.shape[1] != self._nvar:
raise ValueError("Bad shape for pairwise energy (Need (?, {}), got {})".format(self._nvar, (features.shape[0], features.shape[1])))
self._this.addPairwiseEnergy(eigen.c_matrixXf(features), _labelcomp(compat), kernel, normalization) self._this.addPairwiseEnergy(eigen.c_matrixXf(features), _labelcomp(compat), kernel, normalization)
def setUnary(self, Unary u): def setUnary(self, Unary u):
self._this.setUnaryEnergy(u.move()) self._this.setUnaryEnergy(u.move())
def setUnaryEnergy(self, float[:,::1] u not None, float[:,::1] f = None): def setUnaryEnergy(self, float[:,::1] u not None, float[:,::1] f = None):
if u.shape[0] != self._nlabel or u.shape[1] != self._nvar:
raise ValueError("Bad shape for unary energy (Need {}, got {})".format((self._nlabel, self._nvar), (u.shape[0], u.shape[1])))
# TODO: I don't remember the exact shape `f` should have, so I'm not putting an assertion here.
# If you get hit by a wrong shape of `f`, please open an issue with the necessary info!
if f is None: if f is None:
self._this.setUnaryEnergy(eigen.c_matrixXf(u)) self._this.setUnaryEnergy(eigen.c_matrixXf(u))
else: else:
...@@ -102,6 +113,10 @@ cdef class DenseCRF2D(DenseCRF): ...@@ -102,6 +113,10 @@ cdef class DenseCRF2D(DenseCRF):
self._w = w self._w = w
self._h = h self._h = h
# Also set these for the superclass
self._nvar = w*h
self._nlabel = nlabels
def addPairwiseGaussian(self, sxy, compat, KernelType kernel=DIAG_KERNEL, NormalizationType normalization=NORMALIZE_SYMMETRIC): def addPairwiseGaussian(self, sxy, compat, KernelType kernel=DIAG_KERNEL, NormalizationType normalization=NORMALIZE_SYMMETRIC):
if isinstance(sxy, Number): if isinstance(sxy, Number):
sxy = (sxy, sxy) sxy = (sxy, sxy)
......
import numpy as np
import pydensecrf.densecrf as dcrf
# TODO: Make this real unit-tests some time in the future...
# Tests for specific issues
###########################
# Via e-mail: crash when non-float32 compat
d = dcrf.DenseCRF2D(10,10,2)
d.setUnaryEnergy(np.ones((2,10*10), dtype=np.float32))
compat = np.array([1.0, 2.0])
try:
d.addPairwiseBilateral(sxy=(3,3), srgb=(3,3,3), rgbim=np.zeros((10,10,3), np.uint8), compat=compat)
d.inference(2)
raise TypeError("Didn't raise an exception, but should because compat dtypes don't match!!")
except ValueError:
pass # That's what we want!
# The following is not a really good unittest, but was the first tests.
###########################
# d = densecrf.PyDenseCRF2D(3, 2, 3)
# U = np.full((3,6), 0.1, dtype=np.float32)
# U[0,0] = U[1,1] = U[2,2] = U[0,3] = U[1,4] = U[2,5] = 0.8
d = dcrf.DenseCRF2D(10, 10, 2)
U1 = np.zeros((10, 10), dtype=np.float32)
U1[:,[0,-1]] = U1[[0,-1],:] = 1
U2 = np.zeros((10, 10), dtype=np.float32)
U2[4:7,4:7] = 1
U = np.vstack([U1.flat, U2.flat])
Up = (U + 1) / (np.sum(U, axis=0) + 2)
img = np.zeros((10,10,3), dtype=np.uint8)
img[2:8,2:8,:] = 255
d.setUnaryEnergy(-np.log(Up))
#d.setUnaryEnergy(PyConstUnary(-np.log(Up)))
d.addPairwiseBilateral(2, 2, img, 3)
# d.addPairwiseBilateral(2, 2, img, 3)
np.argmax(d.inference(10), axis=0).reshape(10,10)
import numpy as np
import eigen as e
V = np.random.randn(3).astype(np.float32)
M = np.random.randn(3,3).astype(np.float32)
foo = e.vectorXf(V)
assert np.all(np.array(foo) == V)
foo = e.matrixXf(M)
assert np.all(np.array(foo) == M)
import numpy as np
import pydensecrf.densecrf as dcrf
import pydensecrf.utils as utils
import pytest
def _get_simple_unary():
unary1 = np.zeros((10, 10), dtype=np.float32)
unary1[:, [0, -1]] = unary1[[0, -1], :] = 1
unary2 = np.zeros((10, 10), dtype=np.float32)
unary2[4:7, 4:7] = 1
unary = np.vstack([unary1.flat, unary2.flat])
unary = (unary + 1) / (np.sum(unary, axis=0) + 2)
return unary
def _get_simple_img():
img = np.zeros((10, 10, 3), dtype=np.uint8)
img[2:8, 2:8, :] = 255
return img
def test_call_dcrf2d():
d = dcrf.DenseCRF2D(10, 10, 2)
unary = _get_simple_unary()
img = _get_simple_img()
d.setUnaryEnergy(-np.log(unary))
# d.setUnaryEnergy(PyConstUnary(-np.log(Up)))
d.addPairwiseBilateral(sxy=2, srgb=2, rgbim=img, compat=3)
# d.addPairwiseBilateral(2, 2, img, 3)
np.argmax(d.inference(10), axis=0).reshape(10, 10)
def test_call_dcrf():
d = dcrf.DenseCRF(100, 2)
unary = _get_simple_unary()
img = _get_simple_img()
d.setUnaryEnergy(-np.log(unary))
# d.setUnaryEnergy(PyConstUnary(-np.log(Up)))
feats = utils.create_pairwise_bilateral(sdims=(2, 2), schan=2,
img=img, chdim=2)
d.addPairwiseEnergy(feats, compat=3)
# d.addPairwiseBilateral(2, 2, img, 3)
np.argmax(d.inference(10), axis=0).reshape(10, 10)
def test_call_dcrf_eq_dcrf2d():
d = dcrf.DenseCRF(100, 2)
d2 = dcrf.DenseCRF2D(10, 10, 2)
unary = _get_simple_unary()
img = _get_simple_img()
d.setUnaryEnergy(-np.log(unary))
d2.setUnaryEnergy(-np.log(unary))
feats = utils.create_pairwise_bilateral(sdims=(2, 2), schan=2,
img=img, chdim=2)
d.addPairwiseEnergy(feats, compat=3)
d2.addPairwiseBilateral(sxy=2, srgb=2, rgbim=img, compat=3)
# d.addPairwiseBilateral(2, 2, img, 3)
res1 = np.argmax(d.inference(10), axis=0).reshape(10, 10)
res2 = np.argmax(d2.inference(10), axis=0).reshape(10, 10)
assert(np.all(res1 == res2))
def test_compact_wrong():
# Tests whether expection is indeed raised
##########################################
# Via e-mail: crash when non-float32 compat
d = dcrf.DenseCRF2D(10, 10, 2)
d.setUnaryEnergy(np.ones((2, 10 * 10), dtype=np.float32))
compat = np.array([1.0, 2.0])
with pytest.raises(ValueError):
d.addPairwiseBilateral(sxy=(3, 3), srgb=(3, 3, 3), rgbim=np.zeros(
(10, 10, 3), np.uint8), compat=compat)
d.inference(2)
import numpy as np
import pydensecrf.eigen as e
import pytest
def test_vector_conversion():
np_vector = np.random.randn(3).astype(np.float32)
c_vector = e.vectorXf(np_vector)
assert np.all(np.array(c_vector) == np_vector)
def test_matrix_conversion():
np_matrix = np.random.randn(3, 3).astype(np.float32)
assert(np_matrix.ndim == 2)
c_matrix = e.matrixXf(np_matrix)
assert np.all(np.array(c_matrix) == np_matrix)
def test_wrong_dims():
np_matrix = np.random.randn(3, 3, 3).astype(np.float32)
assert(np_matrix.ndim == 3)
# c_matrix only supports ndim == 2
with pytest.raises(ValueError):
# Check whether a Value Error is raised
e.matrixXf(np_matrix)
def test_wrong_type():
np_matrix = np.random.randn(3, 3).astype(np.float64)
# c_matrix requies type np.float32
with pytest.raises(ValueError):
# Check whether a Value Error is raised
e.matrixXf(np_matrix)
def test_none_type():
np_matrix = None
with pytest.raises(TypeError):
# Check whether a Value Error is raised
e.matrixXf(np_matrix)
# coding: UTF-8 # coding: UTF-8
from distutils.core import setup from setuptools import setup
from Cython.Build import cythonize
# TODO: # TODO:
# - Wrap learning. # - Wrap learning.
# - Make LabelCompatibility, UnaryEnergy, PairwisePotential extensible? (Maybe overkill?) # - Make LabelCompatibility, UnaryEnergy, PairwisePotential extensible? (Maybe overkill?)
# If Cython is available, build using Cython.
# Otherwise, use the pre-built (by someone who has Cython, i.e. me) wrapper `.cpp` files.
try:
from Cython.Build import cythonize
ext_modules = cythonize(['pydensecrf/eigen.pyx', 'pydensecrf/densecrf.pyx'])
except ImportError:
from setuptools.extension import Extension
ext_modules = [
Extension("pydensecrf/eigen", ["pydensecrf/eigen.cpp", "pydensecrf/eigen_impl.cpp"], language="c++", include_dirs=["pydensecrf/densecrf/include"]),
Extension("pydensecrf/densecrf", ["pydensecrf/densecrf.cpp", "pydensecrf/densecrf/src/densecrf.cpp", "pydensecrf/densecrf/src/unary.cpp", "pydensecrf/densecrf/src/pairwise.cpp", "pydensecrf/densecrf/src/permutohedral.cpp", "pydensecrf/densecrf/src/optimization.cpp", "pydensecrf/densecrf/src/objective.cpp", "pydensecrf/densecrf/src/labelcompatibility.cpp", "pydensecrf/densecrf/src/util.cpp", "pydensecrf/densecrf/external/liblbfgs/lib/lbfgs.c"], language="c++", include_dirs=["pydensecrf/densecrf/include", "pydensecrf/densecrf/external/liblbfgs/include"]),
]
setup( setup(
name="pydensecrf", name="pydensecrf",
version="0.1", version="1.0rc2",
description="A python interface to Philipp Krähenbühl's fully-connected CRF code.", description="A python interface to Philipp Krähenbühl's fully-connected (dense) CRF code.",
long_description="See the README.md at http://github.com/lucasb-eyer/pydensecrf",
author="Lucas Beyer", author="Lucas Beyer",
author_email="lucasb.eyer.be@gmail.com", author_email="lucasb.eyer.be@gmail.com",
url="http://github.com/lucasb-eyer/pydensecrf", url="http://github.com/lucasb-eyer/pydensecrf",
ext_modules=cythonize(['pydensecrf/eigen.pyx', 'pydensecrf/densecrf.pyx']), ext_modules=ext_modules,
packages=["pydensecrf"] packages=["pydensecrf"],
setup_requires=['cython>=0.22'],
classifiers=[
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Development Status :: 5 - Production/Stable",
"Programming Language :: C++",
"Programming Language :: Python",
"Operating System :: POSIX :: Linux",
"Topic :: Software Development :: Libraries",
"Topic :: Scientific/Engineering :: Image Recognition",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
],
) )
# coding: utf-8
# In[1]:
# import sys
# sys.path.insert(0,'/home/dlr16/Applications/anaconda2/envs/PyDenseCRF/lib/python2.7/site-packages')
# In[2]:
import numpy as np
import matplotlib.pyplot as plt
# get_ipython().magic(u'matplotlib inline')
plt.rcParams['figure.figsize'] = (20, 20)
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
import pydensecrf.densecrf as dcrf
from pydensecrf.utils import unary_from_softmax, create_pairwise_bilateral, create_pairwise_gaussian
# ## Start from scratch
# In[3]:
from scipy.stats import multivariate_normal
x, y = np.mgrid[0:512, 0:512]
pos = np.empty(x.shape + (2,))
pos[:, :, 0] = x; pos[:, :, 1] = y
rv = multivariate_normal([256, 256], 128*128)
# In[4]:
probs = rv.pdf(pos)
probs = (probs-probs.min()) / (probs.max()-probs.min())
probs = 0.2 * (probs-0.5) + 0.5
probs = np.tile(probs[:,:,np.newaxis],(1,1,2))
probs[:,:,1] = 1 - probs[:,:,0]
# plt.plot(probs[256,:,0])
# transpose for graph
probs = np.transpose(probs,(2,0,1))
# In[17]:
# XX:IF NCHANNELS != 3, I GET ERRONEOUS OUTPUT
nchannels=4
U = unary_from_softmax(probs) # note: num classes is first dim
d = dcrf.DenseCRF2D(probs.shape[1],probs.shape[2],probs.shape[0])
d.setUnaryEnergy(U)
Q_Unary = d.inference(10)
map_soln_Unary = np.argmax(Q_Unary, axis=0).reshape((probs.shape[1],probs.shape[2]))
tmp_img = np.zeros((probs.shape[1],probs.shape[2],nchannels)).astype(np.uint8)
tmp_img[150:362,150:362,:] = 1
energy = create_pairwise_bilateral(sdims=(10,10), schan=0.01, img=tmp_img, chdim=2)
d.addPairwiseEnergy(energy, compat=10)
# This is wrong and will now raise a ValueError:
#d.addPairwiseBilateral(sxy=(10,10),
# srgb=0.01,
# rgbim=tmp_img,
# compat=10)
Q = d.inference(100)
map_soln = np.argmax(Q, axis=0).reshape((probs.shape[1],probs.shape[2]))
plt.subplot(2,2,1)
plt.imshow(probs[0,:,:])
plt.colorbar()
plt.subplot(2,2,2)
plt.imshow(map_soln_Unary)
plt.colorbar()
plt.subplot(2,2,3)
plt.imshow(tmp_img[:,:,0])
plt.colorbar()
plt.subplot(2,2,4)
plt.imshow(map_soln)
plt.colorbar()
plt.show()
# probs of shape 3d image per class: Nb_classes x Height x Width x Depth
# assume the image has shape (69, 51, 72)
import numpy as np
import pydensecrf.densecrf as dcrf
from pydensecrf.utils import unary_from_softmax, create_pairwise_gaussian
###
#shape = (69, 51, 72)
#probs = np.random.randn(5, 69, 51).astype(np.float32)
#probs /= probs.sum(axis=0, keepdims=True)
#
#d = dcrf.DenseCRF(np.prod(shape), probs.shape[0])
#U = unary_from_softmax(probs)
#print(U.shape)
#d.setUnaryEnergy(U)
#feats = create_pairwise_gaussian(sdims=(1.0, 1.0, 1.0), shape=shape)
#d.addPairwiseEnergy(feats, compat=3, kernel=dcrf.FULL_KERNEL, normalization=dcrf.NORMALIZE_SYMMETRIC)
#Q = d.inference(5)
#new_image = np.argmax(Q, axis=0).reshape((shape[0], shape[1],shape[2]))
###
SHAPE, NLABELS = (69, 51, 72), 5
probs = np.random.randn(NLABELS, 68, 50).astype(np.float32) # WRONG shape here
probs /= probs.sum(axis=0, keepdims=True)
d = dcrf.DenseCRF(np.prod(SHAPE), NLABELS)
d.setUnaryEnergy(unary_from_softmax(probs)) # THIS SHOULD THROW and not crash later
feats = create_pairwise_gaussian(sdims=(1.0, 1.0, 1.0), shape=SHAPE)
d.addPairwiseEnergy(feats, compat=3, kernel=dcrf.FULL_KERNEL, normalization=dcrf.NORMALIZE_SYMMETRIC)
Q = d.inference(5)
new_image = np.argmax(Q, axis=0).reshape(SHAPE)
import unittest
import numpy as np
import pydensecrf.utils as utils
class TestUnary(unittest.TestCase):
def test_unary(self):
M = 3
U, P, N = 1./M, 0.8, 0.2/(M-1) # Uniform, Positive, Negative
labels = np.array([
[0, 1, 2, 3],
[3, 2, 1, 0],
])
unary = -np.log(np.array([
[U, P, N, N, N, N, P, U],
[U, N, P, N, N, P, N, U],
[U, N, N, P, P, N, N, U],
]))
np.testing.assert_almost_equal(utils.compute_unary(labels, M, GT_PROB=P), unary)
if __name__ == "__main__":
unittest.main()
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