Skip to content

replace separate 2D and 3D Chambolle TV denoising functions with a single nD function#1839

Merged
jni merged 11 commits intoscikit-image:masterfrom
grlee77:nd_denoise_tv
Jan 25, 2016
Merged

replace separate 2D and 3D Chambolle TV denoising functions with a single nD function#1839
jni merged 11 commits intoscikit-image:masterfrom
grlee77:nd_denoise_tv

Conversation

@grlee77
Copy link
Copy Markdown
Contributor

@grlee77 grlee77 commented Dec 22, 2015

This is a refactor of the existing Chambolle TV denoising algorithm, replacing the separate 2D and 3D implementations with a single nD equivalent. The output should be identical to the previous implementation for the 2D and 3D cases.

I debated leaving the 2D code in place because it is a bit simpler to read, but ended up removing it because it was redundant.

A 1D example that would not have run previously:

import numpy as np
from skimage import restoration
from matplotlib import pyplot as plt
x = 0.9 * np.ones(400)
x[100:200] = 0
x[300:] = 0
x += 0.1 * np.random.rand(x.size)
x = np.clip(x, 0, 1)
res = restoration.denoise_tv_chambolle(x, weight=0.5)

# plot it
t = np.arange(x.size)
plt.figure(); plt.plot(t, x, 'r', t, res, 'k')

tvexample1d

@grlee77
Copy link
Copy Markdown
Contributor Author

grlee77 commented Dec 22, 2015

Also, the default weight of 50 in the denoise_TV_chambolle function is huge. It seemed that a weight < 1 was needed to get reasonable results, but I didn't change the default here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use helper function _add_shifted_p_to_d for more clarity? The code is a bit hard to read here...

@emmanuelle
Copy link
Copy Markdown
Member

Regarding the default value of weight, I think this default value was set before img_as_float was used, i.e. it's a reasonable value for images in the range [0, 255]. Changing the default to 1 or 0.5 might be better.

@grlee77
Copy link
Copy Markdown
Contributor Author

grlee77 commented Dec 23, 2015

I will take a look at the performance.

Regarding the weight, I am in favor of changing the default. I think we should certainly at least adjust it in the tests. It is still set to very large values there (aside from the couple of tests I just added).

@emmanuelle
Copy link
Copy Markdown
Member

Go for it for weight, better now than later. Or you could also file a separate PR for the weight to be sure that we merge it for 0.12.

@grlee77
Copy link
Copy Markdown
Contributor Author

grlee77 commented Dec 23, 2015

@emmanuelle
Good catch on the C vs. Fortran order. The implementation here was about 2 times slower!

Just using order='F' for g and p restored performance (I measured 2-3% slower than the previous 2D only implementation, which seems more acceptable).

I also verified that resulting denoised image is indeed identical to the existing implementation.

I will make a small separate PR for the weight issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here you're adding a C-order and an F-order arrays, which is not optimal.

@emmanuelle
Copy link
Copy Markdown
Member

@grlee77 thanks for checking the performance issue. Creating F-order instead of C-order arrays was a quick and dirty trick that worked, but in fact it would be better if you could have the first dimension corresponding to the dimension of length ndim, and keep C-order arrays. Do you think it would be possible? Then you would also recover the 2-3% that correspond I think to the moment where you add im + d (adding a C-order to a F-order to output an F-order array).

@emmanuelle
Copy link
Copy Markdown
Member

Also, you'll need to rebase on master since your PR on weight has been merged.

@grlee77
Copy link
Copy Markdown
Contributor Author

grlee77 commented Dec 23, 2015

okay sure. I will take a look in a few days

@JDWarner
Copy link
Copy Markdown
Contributor

Agreed with @emmanuelle about weight - if the default is way off, it should be fixed.

@emmanuelle
Copy link
Copy Markdown
Member

@grlee77 any update on your PR? We shouldn't let this momentum fizzle out :-) !

@grlee77
Copy link
Copy Markdown
Contributor Author

grlee77 commented Jan 19, 2016

Yes, I did make some additional commits, but I think they are still on my laptop. I may be able to push them later today.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use p.sum(axis=-1)? It is more readable to people unfamiliar with numpy. I would even suggest np.sum(p, axis=-1).

@jni
Copy link
Copy Markdown
Member

jni commented Jan 21, 2016

@grlee77 I'm late to this party but thank you for an awesome contribution! The code looks cleaner in nd than specialised for 2D, as is often the case, and we get the added benefit of nd compatibility!

grlee77 and others added 8 commits January 23, 2016 10:59
_denoise_tv_chambolle_2d and _denoise_tv_chambolle_3d are replaced by _denoise_tv_chambolle_nd

The restriction to 2D and 3D in denoies_tv_chambolle was removed.
…gardless of the number of dimensions.

As in the original reference, the stepsize should be in both the numerator and denominator of p.
…2*ndim)

use the default weight in all the tests as well.
@grlee77
Copy link
Copy Markdown
Contributor Author

grlee77 commented Jan 23, 2016

@emmanuelle
I have switched back to C-ordering as requested. I believe it was a tiny bit faster back when I tested it, but don't have exact benchmark numbers on hand.

@grlee77
Copy link
Copy Markdown
Contributor Author

grlee77 commented Jan 23, 2016

Sorry to fiddle with the weights again, but the changes in #dd5a708 are necessary to give a consistent level of denoising regardless of the number of image dimensions.

To demonstrate that the present implementation is correct, see the following example, which I repeated for the _denoise_tv_chambolle_nd code prior to and after #dd5a708

import numpy as np
from skimage import restoration, data, color, img_as_float
from matplotlib import pyplot as plt

np.random.seed(1234)
astro = img_as_float(data.astronaut())  # [:128, :128])
astro_gray = color.rgb2gray(astro)
img = astro_gray.copy()
# add noise to astronaut
sigma = 0.15
img += sigma * np.random.standard_normal(img.shape)
# clip noise so that it does not exceed allowed range for float images.
img = np.clip(img, 0, 1)


# generate 3D, 4D and 8D images by tiling
img3 = np.tile(img[..., None], (1, 1, 16));
img4 = np.tile(img[..., None, None], (1, 1, 4, 4));
img8 = np.tile(img[..., None, None, None, None, None, None], (1, 1, 2, 2, 2, 2));

# denoise all nD images and view a single 2D slice
w = 0.2
denoised_astro2 = _denoise_tv_chambolle_nd(img, weight=w, multichannel=False)
denoised_astro3 = _denoise_tv_chambolle_nd(img3, weight=w, multichannel=False)
denoised_astro4 = _denoise_tv_chambolle_nd(img4, weight=w, multichannel=False)
denoised_astro8 = _denoise_tv_chambolle_nd(img8, weight=w, multichannel=False)


fig, axes = plt.subplots(2, 2)
axes[0, 0].imshow(denoised_astro2, cmap=plt.cm.gray, interpolation='nearest')
axes[0, 0].set_axis_off()
axes[0, 0].set_title('2D')
axes[0, 1].imshow(denoised_astro3[..., 0], cmap=plt.cm.gray, interpolation='nearest')
axes[0, 1].set_axis_off()
axes[0, 1].set_title('3D')
axes[1, 0].imshow(denoised_astro4[..., 0, 0], cmap=plt.cm.gray, interpolation='nearest')
axes[1, 0].set_axis_off()
axes[1, 0].set_title('4D')
axes[1, 1].imshow(denoised_astro8[..., 0, 0, 0, 0, 0, 0], cmap=plt.cm.gray, interpolation='nearest')
axes[1, 1].set_axis_off()
axes[1, 1].set_title('8D')
volshow([denoised_astro2, denoised_astro3[..., 0],  denoised_astro4[..., 0, 0],
         denoised_astro8[..., 0, 0, 0, 0, 0, 0]])

With the old code, the level of denoising decreases as the number of images increases:
tv_old_nd_weightings

The new code, gives much more consistent results:
tv_new_nd_weightings

@grlee77
Copy link
Copy Markdown
Contributor Author

grlee77 commented Jan 24, 2016

Is there not a way to set a slightly less strict criterion for Coveralls? -0.007%

@blink1073
Copy link
Copy Markdown
Contributor

I opened an issue about that on Coveralls several months ago, but no response...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shape[-1], surely? Might want to add a test for this after fixing!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! can't believe I missed that one... I added 3D + channels to one of the tests

Copy link
Copy Markdown
Member

@stefanv stefanv Jan 24, 2016 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jni
Copy link
Copy Markdown
Member

jni commented Jan 25, 2016

@grlee77 Sweet, thank you! =D

jni added a commit that referenced this pull request Jan 25, 2016
replace separate 2D and 3D Chambolle TV denoising functions with a single nD function
@jni jni merged commit d199ab2 into scikit-image:master Jan 25, 2016
@stefanv
Copy link
Copy Markdown
Member

stefanv commented Jan 25, 2016

Good work!

@jni
Copy link
Copy Markdown
Member

jni commented Jan 25, 2016

@stefanv the Guardian of the Dimensions is happy. =P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants