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

Merge branch 'MarvinTeichmann-unittest'

parents 8b00be1e 617a0162
......@@ -5,3 +5,54 @@ build
pydensecrf/eigen.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.
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
......@@ -101,10 +103,14 @@ d.addPairwiseGaussian(sxy=3, compat=3)
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.
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.
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
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
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.
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
---------
......@@ -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).
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":
cdef class DenseCRF:
cdef c_DenseCRF *_this
cdef int _nlabel
cdef int _nvar
cdef class DenseCRF2D(DenseCRF):
......
......@@ -59,6 +59,9 @@ cdef class DenseCRF:
else:
self._this = NULL
self._nvar = nvar
self._nlabel = nlabels
def __dealloc__(self):
# Because destructors are virtual, this is enough to delete any object
# of child classes too.
......@@ -66,12 +69,20 @@ cdef class DenseCRF:
del self._this
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)
def setUnary(self, Unary u):
self._this.setUnaryEnergy(u.move())
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:
self._this.setUnaryEnergy(eigen.c_matrixXf(u))
else:
......@@ -102,6 +113,10 @@ cdef class DenseCRF2D(DenseCRF):
self._w = w
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):
if isinstance(sxy, Number):
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
from distutils.core import setup
from Cython.Build import cythonize
from setuptools import setup
# TODO:
# - Wrap learning.
# - 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(
name="pydensecrf",
version="0.1",
description="A python interface to Philipp Krähenbühl's fully-connected CRF code.",
version="1.0rc2",
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_email="lucasb.eyer.be@gmail.com",
url="http://github.com/lucasb-eyer/pydensecrf",
ext_modules=cythonize(['pydensecrf/eigen.pyx', 'pydensecrf/densecrf.pyx']),
packages=["pydensecrf"]
ext_modules=ext_modules,
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