Repository: scikit-image/scikit-image

Issue:
Lucy Richardson return NaN value
## Description
Hi all,
I'm using LR deconvolution for many times. unfortunately, in some cases LR return NaN value. 
I'm understand that this issue was already fixed according: #2551 #2552, but even with this change LR still give NaN value. 
I attached here an image and  psf  (pickle file), and I would like to get your help find the problem.

[image.zip](https://github.com/scikit-image/scikit-image/files/4007828/image.zip)
[psf.zip](https://github.com/scikit-image/scikit-image/files/4007830/psf.zip)

## Way to reproduce
```python
# Place the full code we need to recreate your issue here
# upload all necessary images to github too!
```


## Version information
```python
# Paste the output of the following python commands
from __future__ import print_function
import sys; print(sys.version)
import platform; print(platform.platform())
import skimage; print("scikit-image version: {}".format(skimage.__version__))
import numpy; print("numpy version: {}".format(numpy.__version__))
```

```python
# your output here

```


richardson_lucy returns NaN
Hi guys,

I think there is a bug in your Richardson-Lucy implementation:

https://github.com/scikit-image/scikit-image/blob/master/skimage/restoration/deconvolution.py
line 389
`relative_blur = image / convolve_method(im_deconv, psf, 'same')`

as soon as  [image] contains 0, more and more NaN will be created and propagated due to convolution.
on my PC (win7, 64bit, anaconda py3.5) the given test case doe not work because of this.

##FIX
add  behind line 389
`relative_blur[np.isnan(relative_blur)] = 0`

Cheers,
Karl


Discussion:
@yairif thanks for the report and the test data! As you can probably imagine, now is a busy time for most of our developers, but we will pick this up in the new year, as it seems like a significant bug.

Thanks again!
Thank you for the update.

On Mon, Dec 30, 2019, 15:25 Juan Nunez-Iglesias <notifications@github.com>
wrote:

> @yairif <https://github.com/yairif> thanks for the report and the test
> data! As you can probably imagine, now is a busy time for most of our
> developers, but we will pick this up in the new year, as it seems like a
> significant bug.
>
> Thanks again!
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/scikit-image/scikit-image/issues/4378?email_source=notifications&email_token=ANMFJNTX7XPU4SBZKGJ6AZTQ3HZFZA5CNFSM4KAX3RZ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEH2JSGQ#issuecomment-569678106>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ANMFJNUIUMAYMECUYKA4VOLQ3HZFZANCNFSM4KAX3RZQ>
> .
>

@yairif thanks for opening this. I personally may not have the time to look into this issue, but it would be helpful for whoever does, if you provide them with exactly the code you used to go from:

1. A directory with the two pickle files
2. Loading and assembling the data
3. Operating on the data
4. Noticing that some entries are NaN

Finally, the output of:
```python
# Paste the output of the following python commands
from __future__ import print_function
import sys; print(sys.version)
import platform; print(platform.platform())
import skimage; print("scikit-image version: {}".format(skimage.__version__))
import numpy; print("numpy version: {}".format(numpy.__version__))
```
is very helpful for us to understand things.


Ok,  thanks for your comment. I will do it soon as possible.

On Mon, Dec 30, 2019, 19:16 Mark Harfouche <notifications@github.com> wrote:

> @yairif <https://github.com/yairif> thanks for opening this. I personally
> may not have the time to look into this issue, but it would be helpful for
> whoever does, if you provide them with exactly the code you used to go from:
>
>    1. A directory with the two pickle files
>    2. Loading and assembling the data
>    3. Operating on the data
>    4. Noticing that some entries are NaN
>
> Finally, the output of:
>
> # Paste the output of the following python commandsfrom __future__ import print_functionimport sys; print(sys.version)import platform; print(platform.platform())import skimage; print("scikit-image version: {}".format(skimage.__version__))import numpy; print("numpy version: {}".format(numpy.__version__))
>
> is very helpful for us to understand things.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/scikit-image/scikit-image/issues/4378?email_source=notifications&email_token=ANMFJNRRICYG5WLVXIADPC3Q3IUF3A5CNFSM4KAX3RZ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEH2YGUQ#issuecomment-569738066>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ANMFJNQBYEJHQAVRQNXARGLQ3IUF3ANCNFSM4KAX3RZQ>
> .
>

Here I attached: 

- data -Image&PSF (zip file), 

- very simple code which loading the data, use RL deconvolution and plot the results.

- modules version

[image.zip](https://github.com/scikit-image/scikit-image/files/4011938/image.zip)
[psf.zip](https://github.com/scikit-image/scikit-image/files/4011939/psf.zip)

```
from zipfile import ZipFile
import numpy as np
from skimage import color, data, restoration
import matplotlib.pyplot as plt

Image = ZipFile('path_to_file/Image.zip', 'r')
Image.extractall('path_to_extract_folder')
Image.close()
Image = np.load('path_to_Image_file')

PSF = ZipFile('path_to_file/PSF.zip', 'r')
PSF.extractall('path_to_extract_folder')
PSF.close()
PSF = np.load('path_to_psf_file')

deconvolved_RL = restoration.richardson_lucy(Image, PSF, iterations=20)

fig, (ax0, ax1, ax2) = plt.subplots(1, 3)
c = ax0.pcolor(Image)
ax0.set_title('Interpulation')
c = ax1.pcolor(PSF)
ax1.set_title('psf')
c = ax2.pcolor(deconvolved_RL)
ax2.set_title('RL-deconv')
fig.tight_layout()
plt.show()
```
![RL_deconv](https://user-images.githubusercontent.com/56120502/71610964-1c6db380-2b9e-11ea-9d32-de5def06282b.png)

Matlab plot for the same input data (Image, PSF, 20 iteration)
![deconvolution](https://user-images.githubusercontent.com/56120502/71610911-c862cf00-2b9d-11ea-82af-c154efb57cd6.png)

############ version ######################
3.7.3 (default, Mar 27 2019, 22:11:17) 
[GCC 7.3.0]
Linux-4.15.0-72-generic-x86_64-with-debian-buster-sid
scikit-image version: 0.15.0
numpy version: 1.15.2

I can confirm that this also occurs with modern versions of scikit-image and numpy:
```
import numpy as np
from skimage import color, data, restoration
import matplotlib.pyplot as plt

Image = np.load('path_to_file/image', allow_pickle=True)  
PSF = np.load('path_to_file/psf', allow_pickle=True)  
 
deconvolved_RL = restoration.richardson_lucy(Image, PSF, iterations=20) 
 
fig, (ax0, ax1, ax2) = plt.subplots(1, 3) 
c = ax0.pcolor(Image) 
ax0.set_title('Interpulation') 
c = ax1.pcolor(PSF) 
ax1.set_title('psf') 
c = ax2.pcolor(deconvolved_RL) 
ax2.set_title('RL-deconv') 
fig.tight_layout() 
plt.show()
```

```
3.7.6 | packaged by conda-forge | (default, Dec 27 2019, 00:09:34) 
[GCC 7.3.0]
Linux-5.3.0-24-generic-x86_64-with-debian-buster-sid
scikit-image version: 0.16.2
numpy version: 1.17.3
The problem stems with exactly how the algorithm is implemented.

In the RL implementation as of `0.16.2`, these two lines are executed in a tight loop
```
        relative_blur = image / convolve(im_deconv, psf, mode='same')
        im_deconv *= convolve(relative_blur, psf_mirror, mode='same')
```
Therefore, if `image` is ever a long string of zeros,  then things do not look good since in the next iteration, you will be dividing by zero.

To fix this today, for your work, I would add a small offset to the image:

```
Image += Image.max() * 1E-5 * np.random.random(Image.shape)
Image += Image.max() * 1E-5
```
one of these two should do.


To fix this in the function itself, you could just add the same lines before the computaion starts. 
That said, I'm not a big fan of calling `np.max` or `np.min` inside scikit-image to `sanitize data`. It is a rather large cost to bear for people that want a bit more speed. but I understand that getting NaNs is annoying.




![image](https://user-images.githubusercontent.com/90008/71637797-5761fc80-2c1b-11ea-8f91-03e0ace4b746.png)


@radjkarl I'm trying to solve this issue. Could you please give the test case where it fails?
Hi @meriki,
As it looks like,  the issue is already fixed: #2551 #2552
Can you please try the test case provided in the docstring?
Alternatively any input image with values=0 should work.

@radjkarl #2552 is closed. Look at #2553. The reviewers(@soupault @jni ) were asking for a test case where the old code fails. With all 0s as input, no NaNs are being generated, all the relative blurs become non-zero in the for-loop . Following is the image output. 
Do you get any other cases in mind?
![image](https://cloud.githubusercontent.com/assets/17230557/23664117/6eea9484-0333-11e7-8ff0-acca67283a7a.png)

@meriki:
I tried the test case again. it workes - my fault. sorry.

BUT:
please add 'clip=False' to the docstring example.
since the input image is 0-255
and clip=True by default

the output image would be elsewise ==1

```
import numpy as np
from skimage import color, data, restoration
from scipy.signal import convolve2d
import pylab as plt


camera = color.rgb2gray(data.camera())
psf = np.ones((5, 5)) / 25
camera = convolve2d(camera, psf, 'same')
camera += 0.1 * camera.std() * np.random.standard_normal(camera.shape)
#####added clip=False
deconvolved = restoration.richardson_lucy(camera, psf, 5, clip=False)

plt.imshow(deconvolved)
plt.show()
```



I encountered this bug as well when trying to use this function to calculate maximum intensity values using a saturated psf. If you would like I can try and reproduce the bug using different input but below is the proper solution to just fix the code so this can never happen.

The solution is to use machine epsilon:

`eps = np.finfo(image.dtype).eps`
`x = convolve_method(im_deconv, psf, 'same')`
`np.place(x, x==0, eps)`
`relative_blur = image / x + eps`
@aswalters Your solution looks like a very typical approach taken when applying inverse filters.  Can you please provide a failing test and make a PR?  We'd appreciate that very much, thank you.

Resolving patch:
diff --git a/skimage/restoration/deconvolution.py b/skimage/restoration/deconvolution.py
--- a/skimage/restoration/deconvolution.py
+++ b/skimage/restoration/deconvolution.py
@@ -426,12 +426,13 @@ def richardson_lucy(image, psf, num_iter=50, clip=True, filter_epsilon=None):
     im_deconv = np.full(image.shape, 0.5, dtype=float_type)
     psf_mirror = np.flip(psf)
 
+    # Small regularization parameter used to avoid 0 divisions
+    eps = 1e-12
+
     for _ in range(num_iter):
-        conv = convolve(im_deconv, psf, mode='same')
+        conv = convolve(im_deconv, psf, mode='same') + eps
         if filter_epsilon:
-            with np.errstate(invalid='ignore'):
-                relative_blur = np.where(conv < filter_epsilon, 0,
-                                         image / conv)
+            relative_blur = np.where(conv < filter_epsilon, 0, image / conv)
         else:
             relative_blur = image / conv
         im_deconv *= convolve(relative_blur, psf_mirror, mode='same')


Test patch: