8000 Deconvolution with Gauss Prior by alexjarosch · Pull Request #5026 · scikit-image/scikit-image · GitHub
[go: up one dir, main page]

Skip to content

Deconvolution with Gauss Prior #5026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
14a98c6
new deconvolution method based on Gauss prior in frequency domain added
alexjarosch Oct 20, 2020
ee3a500
name to contributors.txt added
alexjarosch Oct 20, 2020
e8b7184
example added
alexjarosch Oct 20, 2020
1d314c1
float type correted in decon routine
alexjarosch Oct 20, 2020
5f1221a
benchmark added
alexjarosch Oct 20, 2020
c0e2821
some PEP8 style complaints fixed
alexjarosch Oct 20, 2020
42d516c
fix __init__
alexjarosch Oct 20, 2020
6344b61
fixed documentation example
alexjarosch Oct 20, 2020
c99ac4b
unit test added
alexjarosch Oct 20, 2020
bc45268
unit test modified
alexjarosch Oct 20, 2020
cec6120
unit test file registered, now working
alexjarosch Oct 20, 2020
1b5d908
Pep 8 github fix
alexjarosch Oct 20, 2020
77623a0
test_download_all_with_pooch deactivated
alexjarosch Oct 20, 2020
10ec48d
Pep 8 github fix
alexjarosch Oct 20, 2020
eba402d
test for DNP_Gauss_freq deactivated.
alexjarosch Oct 20, 2020
3242621
documentation fix
alexjarosch Oct 20, 2020
4cc04ab
new test for DNS_Gauss_freq() on astronaut, rev. change test_data.py
alexjarosch Oct 21, 2020
db1759e
first round comments done
alexjarosch Oct 22, 2020
b2d6e51
DOC: fix typo in docstring
grlee77 Oct 28, 2020
ab33d1a
remove flip of PSF
grlee77 Oct 28, 2020
0acddad
use real-valued FFTs for efficiency
grlee77 Oct 28, 2020
d60074a
ENH: add n-dimensional support
grlee77 Oct 28, 2020
ba2b9f0
add ndim check
grlee77 Oct 28, 2020
ea20451
ENH: allow user to specify pad_width to reduce boundary artifacts
grlee77 Oct 28, 2020
9255247
Merge pull request #1 from grlee77/deconvolution-gauss-prior-nd
alexjarosch Oct 29, 2020
849516a
function name changed, test data registered, test working
alexjarosch Oct 29, 2020
faf46a9
PEP 8 fix
alexjarosch Oct 29, 2020
f233c16
another PEP 8 fix
alexjarosch Oct 29, 2020
073099d
added structural_similarity test
alexjarosch Oct 29, 2020
afcc35b
switch to fetch from image_fetcher.fetch
alexjarosch Oct 30, 2020
5be3ff9
Merge branch 'master' into deconvolution-gauss-prior
alexjarosch Oct 30, 2020
1b2a9f2
Merge remote-tracking branch 'upstream/master' into deconvolution-gau…
alexjarosch Oct 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ each and every contributor, regardless of their role.
- Taylor D. Scott
Simplified _upsampled_dft and extended register_translation to nD images.

- Alexander H. Jarosch
Deconvolution algorithm based on natural priors.

- David J. Mellert
Polar and log-polar warping, nD windows

Expand Down
12 changes: 12 additions & 0 deletions benchmarks/benchmark_restoration.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ def peakmem_richardson_lucy_f32(self):
restoration.richardson_lucy(self.volume_f32, self.psf_f32,
iterations=1)

def time_gaussian_natural_prior_f64(self):
restoration.gaussian_natural_prior(self.volume_f64, self.psf_f64)

def time_gaussian_natural_prior_f32(self):
restoration.gaussian_natural_prior(self.volume_f32, self.psf_f32)

def peakmem_gaussian_natural_prior_f64(self):
restoration.gaussian_natural_prior(self.volume_f64, self.psf_f64)

def peakmem_gaussian_natural_prior_f32(self):
restoration.gaussian_natural_prior(self.volume_f32, self.psf_f32)


class RollingBall(object):
"""Benchmark Rolling Ball algorithm."""
Expand Down
59 changes: 59 additions & 0 deletions doc/examples/filters/plot_deconvolution_natural_prior.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
==================================
Image Deconvolution Natural Priors
==================================
In this example, we deconvolve an image using deconvolution with natural
priors, e.g Gauss, solved in the frequency domain with a new
deconvolution algorithm ([1]_, [2]_, [3]_).

The algorithm is based on a PSF (Point Spread Function),
where PSF is described as the impulse response of the
optical system. The blurred image is sharpened with a smoothness weight,
which defines the weight of the Gauss prior during deconvolution .

.. [1] http://groups.csail.mit.edu/graphics/CodedAperture/
.. [2] Levin, A., Fergus, R., Durand, F., & Freeman, W. T. (2007).
Deconvolution using natural image priors.
Massachusetts Institute of Technology,
Computer Science and Artificial Intelligence Laboratory, 3.
.. [3] Levin, A., Fergus, R., Durand, F., & Freeman, W. T. (2007).
Image and depth from a conventional camera with a coded aperture.
ACM transactions on graphics (TOG), 26(3), 70-es.
"""
import numpy as np
import matplotlib.pyplot as plt

< 8000 /span>
from scipy.signal import convolve2d as conv2

from skimage import color, data, restoration

astro = color.rgb2gray(data.astronaut())

psf = np.ones((5, 5)) / 25
astro = conv2(astro, psf, 'same')
# Add Noise to Image
astro_noisy = astro.copy()
astro_noisy += (np.random.poisson(lam=25, size=astro.shape) - 10) / 255.

# Restore Image using Richardson-Lucy algorithm
deconvolved_DNP = restoration.gaussian_natural_prior(astro_noisy, psf)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(8, 5))
plt.gray()

for a in (ax[0], ax[1], ax[2]):
a.axis('off')

ax[0].imshow(astro)
ax[0].set_title('Original Data')

ax[1].imshow(astro_noisy)
ax[1].set_title('Noisy data')

ax[2].imshow(deconvolved_DNP, vmin=astro_noisy.min(), vmax=astro_noisy.max())
ax[2].set_title('Restoration using\nnatural Gauss prior')


fig.subplots_adjust(wspace=0.02, hspace=0.2,
top=0.9, bottom=0.05, left=0, right=1)
plt.show()
4 changes: 4 additions & 0 deletions skimage/data/_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@
"restoration/tests/astronaut_rl.npy":
"3f8373e2c6182a89366e51cef6624e3625deac75fdda1079cbdad2a33322152c",
"restoration/tests/camera_rl.npy": "fd4f59af84dd471fbbe79ee70c1b7e68a69864c461f0db5ac587e7975363f78f",
"restoration/tests/astronaut_dnp.npy":
"5a9d319ab7749c369f5a670d684b9d56d619b8c8e6a326b14879baa4bba072f7",
"restoration/tests/camera_unsup.npy": "401e512f7becec46c3379f34dbd7956a02a4f2e68f802a8d5bd605d355588880",
"restoration/tests/camera_unsup2.npy": "cc4b31ea2a6ebe07bc7529174677b3e94eae4b7679fd822d3e721a9c8d4854d1",
"restoration/tests/camera_wiener.npy": "4505ea8b0d63d03250c6d756560d615751b76dd6ffc4a95972fa260c0c84633e",
Expand Down Expand Up @@ -152,6 +154,8 @@
"data/pivchallenge-B-B001_1.tif": "https://gitlab.com/scikit-image/data/-/raw/master/pivchallenge/B/B001_1.tif",
"data/pivchallenge-B-B001_2.tif": "https://gitlab.com/scikit-image/data/-/raw/master/pivchallenge/B/B001_2.tif",
"restoration/tests/astronaut_rl.npy": "https://gitlab.com/scikit-image/data/-/raw/master/astronaut_rl.npy",
"restoration/tests/astronaut_dnp.npy":
"https://gitlab.com/scikit-image/data/-/raw/master/astronaut_dnp.npy",
}

legacy_registry = {
Expand Down
4 changes: 3 additions & 1 deletion skimage/restoration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

"""

from .deconvolution import wiener, unsupervised_wiener, richardson_lucy
from .deconvolution import (wiener, unsupervised_wiener, richardson_lucy,
gaussian_natural_prior)
from .unwrap import unwrap_phase
from ._denoise import (denoise_tv_chambolle, denoise_tv_bregman,
denoise_bilateral, denoise_wavelet, estimate_sigma)
Expand All @@ -16,6 +17,7 @@
__all__ = ['wiener',
'unsupervised_wiener',
'richardson_lucy',
'gaussian_natural_prior',
'unwrap_phase',
'denoise_tv_bregman',
'denoise_tv_chambolle',
Expand Down
104 changes: 104 additions & 0 deletions skimage/restoration/deconvolution.py
Original file line number Diff line number Diff line change
< 9E88 a href="#diff-87be2b5ca31afe4ca1d683cb77680447282dc3a265e6620d4661669a9b546b4a" id="expand-up-link-0-diff-87be2b5ca31afe4ca1d683cb77680447282dc3a265e6620d4661669a9b546b4a" class="js-expand directional-expander single-expander" aria-label="Expand Up" data-url="/scikit-image/scikit-image/blob_excerpt/43f5931dfdd759a23e30fb718a86a277dfce0170?context=pull_request&diff=unified&direction=up&in_wiki_context&last_left&last_right&left=6&left_hunk_size=6&mode=100644&path=skimage%2Frestoration%2Fdeconvolution.py&pull_request_id=506730535&right=6&right_hunk_size=8" data-left-range="1-5" data-right-range="1-5"> Expand Up @@ -6,6 +6,8 @@
from scipy.signal import convolve

from . import uft
from .._shared.fft import fftmodule as fft
from ..util import crop

__keywords__ = "restoration, image, deconvolution"

Expand Down Expand Up @@ -383,3 +385,105 @@ def richardson_lucy(image, psf, iterations=50, clip=True, filter_epsilon=None):
im_deconv[im_deconv < -1] = -1

return im_deconv


def gaussian_natural_prior(image, psf, smoothness_weight=0.01, clip=True,
pad_width=0):
""" Deconvolution using Gaussian natural image priors.

Parameters
----------
image : ndarray
Input degraded image.
psf : ndarray
The point spread function.
smoothness_weight : float, optional
The smoothness weight defines how much weight the Gaussian prior is
given in the process. No prior is used when smoothness_weight=0.
clip : boolean, optional
True by default. If true, pixel value of the result above 1 or
under 0 are thresholded for skimage pipeline compatibility.
pad_width : int or tuple of int, optional
If pad_width > 0, the image will be extended by `pad_width` along each
boundary by use of `numpy.pad` with `mode='reflect'`. This is done to
reduce artifacts that can arise near the edges of the deconvolved image
due to the periodic nature of the discrete Fourier transform.

Returns
-------
im_deconv : ndarray
The deconvolved image.

Examples
--------
>>> from skimage import img_as_float, data, restoration
>>> from scipy.ndimage import convolve
>>> camera = img_as_float(data.camera())
>>> psf = np.ones((5, 5)) / 25
>>> camera = convolve(camera, psf)
>>> camera += 0.1 * camera.std() * np.random.standard_normal(camera.shape)
>>> deconvolved = restoration.gaussian_natural_prior(camera, psf)

Notes
-----
This algorithm solves the deconvolution in the frequency domain. Artefacts
like 'ringing' on sharp transitions are strongly reduced in comparison to
other deconvolution strategies.
The smoothness weight needs to be estimated by trial and error. See [1-3]
for guidance.

References
----------
.. [1] http://groups.csail.mit.edu/graphics/CodedAperture/
.. [2] Levin, A., Fergus, R., Durand, F., & Freeman, W. T. (2007).
Deconvolution using natural image priors.
Massachusetts Institute of Technology,
Computer Science and Artificial Intelligence Laboratory, 3.
.. [3] Levin, A., Fergus, R., Durand, F., & Freeman, W. T. (2007).
Image and depth from a conventional camera with a coded aperture.
ACM transactions on graphics (TOG), 26(3), 70-es.
"""
float_type = np.promote_types(image.dtype, np.float32)
image = image.astype(float_type, copy=False)
psf = psf.astype(float_type, copy=False)
if image.ndim != psf.ndim:
raise ValueError(
"psf and image must have an equal number of dimensions"
)

# pad to reduce boundary artifacts
image = np.pad(image, pad_width, mode='reflect')
shape = image.shape

# sum of squared first order differences along each axis
ndim = psf.ndim
G = 0
d_shape = [1, ] * ndim
for n in range(ndim):
if n == ndim - 1:
D = fft.rfft([-1, 1], n=shape[n])
else:
D = fft.fft([-1, 1], n=shape[n])
d_shape[n] = D.size
D = D.reshape(d_shape) # reshape 1D array for broadcasting
G = G + np.conj(D) * D
d_shape[n] = 1

F = fft.rfftn(psf, s=shape)
F_conj = np.conj(F)
A = F_conj * F + smoothness_weight * G
b = F_conj * fft.rfft2(image)

X = np.divide(b, A)
x = np.real(fft.irfft2(X))

# recenter the deconvolved image
shift = tuple([(s - 1) // 2 for s in psf.shape])
im_deconv = np.roll(x, shift=shift, axis=range(psf.ndim))
# remove any padding that was previously added
im_deconv = crop(im_deconv, pad_width)
if clip:
im_deconv[im_deconv > 1] = 1
im_deconv[im_deconv < 0] = 0

return im_deconv
23 changes: 22 additions & 1 deletion skimage/restoration/tests/test_restoration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from skimage.data import astronaut, camera
from skimage import restoration, util
from skimage.restoration import uft
from skimage.metrics import structural_similarity

test_img = util.img_as_float(camera())

Expand Down Expand Up @@ -62,7 +63,7 @@ def test_image_shape():
point[2, 2] = 1.
psf = ndi.gaussian_filter(point, sigma=1.)
# image shape: (45, 45), as reported in #1172
image = util.img_as_float(camera()[65:165, 215:315]) # just the face
image = util.img_as_float(camera()[65:165, 215:315]) # just the face
image_conv = ndi.convolve(image, psf)
deconv_sup = restoration.wiener(image_conv, psf, 1)
deconv_un = restoration.unsupervised_wiener(image_conv, psf)[0]
Expand All @@ -87,6 +88,26 @@ def test_richardson_lucy():
np.testing.assert_allclose(deconvolved, np.load(path), rtol=1e-3)


def test_gaussian_natural_prior():
ideal = rgb2gray(astronaut())
psf = np.ones((5, 5)) / 25
data = convolve2d(ideal, psf, 'same')
# add some noise to the data
np.random.seed(0)
data_n = data + 0.1 * data.std() * np.random.standard_normal(data.shape)
deconvolved_noise = restoration.gaussian_natural_prior(data_n, psf)
# also deconvolve with no noise data
deconvolved = restoration.gaussian_natural_prior(data, psf)

# test for improvement of the image on noise free data
ssim_blurred = structural_similarity(ideal, data)
ssim_deconv = structural_similarity(ideal, deconvolved)
assert ssim_deconv > ssim_blurred

path = fetch('restoration/tests/astronaut_dnp.npy')
np.testing.assert_allclose(deconvolved_noise, np.load(path), rtol=1e-5)


@pytest.mark.parametrize('dtype_image', [np.float32, np.float64])
@pytest.mark.parametrize('dtype_psf', [np.float32, np.float64])
def test_richardson_lucy_filtered(dtype_image, dtype_psf):
Expand Down
0