web-dev-qa-db-fra.com

Comprendre la déconvolution scipy

J'essaie de comprendre scipy.signal.deconvolve .

Du point de vue mathématique, une convolution n'est que la multiplication dans l'espace de Fourier, donc je m'attends à ce que pour deux fonctions f et g:
Deconvolve(Convolve(f,g) , g) == f

En numpy/scipy, ce n'est pas le cas ou il me manque un point important. Bien qu'il y ait des questions liées à la déconvolution sur SO déjà (comme ici et ici )), elles n'abordent pas ce point, d'autres restent floues ( ceci ) ou sans réponse ( ici ). Il y a aussi deux questions sur SignalProcessing SE ( this et this ) les réponses ne sont pas utiles pour comprendre le fonctionnement de la fonction de déconvolution de scipy.

La question serait:

  • Comment reconstruisez-vous le signal d'origine f à partir d'un signal alambiqué, en supposant que vous connaissez la fonction de convolution g.?
  • Ou en d'autres termes: comment ce pseudocode Deconvolve(Convolve(f,g) , g) == f se traduit-il en numpy/scipy?

Modifier : Notez que cette question ne vise pas à empêcher les inexactitudes numériques (bien qu'il s'agisse également d'une question ouverte ) mais à comprendre comment convolve/deconvolve fonctionnent ensemble dans scipy.

Le code suivant essaie de le faire avec une fonction Heaviside et un filtre gaussien. Comme on peut le voir sur l'image, le résultat de la déconvolution de la convolution n'est pas du tout la fonction Heaviside d'origine. Je serais heureux si quelqu'un pouvait faire la lumière sur ce problème.

import numpy as np
import scipy.signal
import matplotlib.pyplot as plt

# Define heaviside function
H = lambda x: 0.5 * (np.sign(x) + 1.)
#define gaussian
gauss = lambda x, sig: np.exp(-( x/float(sig))**2 )

X = np.linspace(-5, 30, num=3501)
X2 = np.linspace(-5,5, num=1001)

# convolute a heaviside with a gaussian
H_c = np.convolve( H(X),  gauss(X2, 1),  mode="same"  )
# deconvolute a the result
H_dc, er = scipy.signal.deconvolve(H_c, gauss(X2, 1) )


#### Plot #### 
fig , ax = plt.subplots(nrows=4, figsize=(6,7))
ax[0].plot( H(X),          color="#907700", label="Heaviside",    lw=3 ) 
ax[1].plot( gauss(X2, 1),  color="#907700", label="Gauss filter", lw=3 )
ax[2].plot( H_c/H_c.max(), color="#325cab", label="convoluted" ,  lw=3 ) 
ax[3].plot( H_dc,          color="#ab4232", label="deconvoluted", lw=3 ) 
for i in range(len(ax)):
    ax[i].set_xlim([0, len(X)])
    ax[i].set_ylim([-0.07, 1.2])
    ax[i].legend(loc=4)
plt.show()

enter image description here

Edit : Notez qu'il y a un exemple matlab , montrant comment convoluer/déconvolver un signal rectangulaire en utilisant

yc=conv(y,c,'full')./sum(c); 
ydc=deconv(yc,c).*sum(c); 

Dans l'esprit de cette question, il serait également utile que quelqu'un puisse traduire cet exemple en python.

20

Après quelques essais et erreurs, j'ai découvert comment interpréter les résultats de scipy.signal.deconvolve() et je poste mes résultats en tant que réponse.

Commençons par un exemple de code de travail

import numpy as np
import scipy.signal
import matplotlib.pyplot as plt

# let the signal be box-like
signal = np.repeat([0., 1., 0.], 100)
# and use a gaussian filter
# the filter should be shorter than the signal
# the filter should be such that it's much bigger then zero everywhere
gauss = np.exp(-( (np.linspace(0,50)-25.)/float(12))**2 )
print gauss.min()  # = 0.013 >> 0

# calculate the convolution (np.convolve and scipy.signal.convolve identical)
# the keywordargument mode="same" ensures that the convolution spans the same
#   shape as the input array.
#filtered = scipy.signal.convolve(signal, gauss, mode='same') 
filtered = np.convolve(signal, gauss, mode='same') 

deconv,  _ = scipy.signal.deconvolve( filtered, gauss )
#the deconvolution has n = len(signal) - len(gauss) + 1 points
n = len(signal)-len(gauss)+1
# so we need to expand it by 
s = (len(signal)-n)/2
#on both sides.
deconv_res = np.zeros(len(signal))
deconv_res[s:len(signal)-s-1] = deconv
deconv = deconv_res
# now deconv contains the deconvolution 
# expanded to the original shape (filled with zeros) 


#### Plot #### 
fig , ax = plt.subplots(nrows=4, figsize=(6,7))

ax[0].plot(signal,            color="#907700", label="original",     lw=3 ) 
ax[1].plot(gauss,          color="#68934e", label="gauss filter", lw=3 )
# we need to divide by the sum of the filter window to get the convolution normalized to 1
ax[2].plot(filtered/np.sum(gauss), color="#325cab", label="convoluted" ,  lw=3 )
ax[3].plot(deconv,         color="#ab4232", label="deconvoluted", lw=3 ) 

for i in range(len(ax)):
    ax[i].set_xlim([0, len(signal)])
    ax[i].set_ylim([-0.07, 1.2])
    ax[i].legend(loc=1, fontsize=11)
    if i != len(ax)-1 :
        ax[i].set_xticklabels([])

plt.savefig(__file__ + ".png")
plt.show()    

Ce code produit l'image suivante, montrant exactement ce que nous voulons (Deconvolve(Convolve(signal,gauss) , gauss) == signal)

enter image description here

Voici quelques conclusions importantes:

  • Le filtre doit être plus court que le signal
  • Le filtre devrait être beaucoup plus grand que zéro partout (ici> 0,013 est assez bon)
  • L'utilisation de l'argument de mot clé mode = 'same' Pour la convolution garantit qu'elle vit sur la même forme de tableau que le signal.
  • La déconvolution a n = len(signal) - len(gauss) + 1 points. Donc, afin de le laisser également résider sur la même forme de tableau d'origine, nous devons l'étendre de s = (len(signal)-n)/2 des deux côtés.

Bien entendu, d'autres conclusions, commentaires et suggestions concernant cette question sont toujours les bienvenus.

11

Comme écrit dans les commentaires, je ne peux pas m'empêcher de l'exemple que vous avez publié à l'origine. Comme l'a souligné @Stelios, la déconvolution pourrait ne pas fonctionner en raison de problèmes numériques.

Je peux cependant reproduire l'exemple que vous avez publié dans votre édition:

enter image description here

C'est le code qui est une traduction directe du code source matlab:

import numpy as np
import scipy.signal
import matplotlib.pyplot as plt

x = np.arange(0., 20.01, 0.01)
y = np.zeros(len(x))
y[900:1100] = 1.
y += 0.01 * np.random.randn(len(y))
c = np.exp(-(np.arange(len(y))) / 30.)

yc = scipy.signal.convolve(y, c, mode='full') / c.sum()
ydc, remainder = scipy.signal.deconvolve(yc, c)
ydc *= c.sum()

fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(4, 4))
ax[0][0].plot(x, y, label="original y", lw=3)
ax[0][1].plot(x, c, label="c", lw=3)
ax[1][0].plot(x[0:2000], yc[0:2000], label="yc", lw=3)
ax[1][1].plot(x, ydc, label="recovered y", lw=3)

plt.show()
5
Cleb