Background:
I am learning an interesting Fast CWT algorithm(PPCWT) by reading this paper published in 2019. The algorithm is summarized as below. The continuous wavelet transform of a signal can be calculated efficiently according to the last row of Eq.(1.4). To test this algorithm, I write a Cython module ppCWT_cymod.pyx
, which is shown below the image.
#ppCWT_cymod.pyx
import sys
import math
import numpy as np
import cython
cimport numpy as np
from libc.math cimport ceil, floor, sqrt, log, round, abs, exp
# Second derivative of Gaussian-derived wavelet
def d3GDWdx3( scale, pos ):
x = scale*pos
s3 = scale**3
p1 = s3*np.exp(-(x/2.0)**2)*x*(60.0-20.0*x**2+x**4)
p2 = 32.0*np.sqrt(np.pi)
return p1/p2
# Compute the Bj
def BjCoeff( freqs, Ls, N, wavelet_name="gdw", half_support=13.0, Nums=46 ):
Dx = Ls/float(N)
# Choose a special wavelet function
GDW_set = {"gdw", "GDW", "Gaussian-derived wavelet", "Gaussian derived wavelet"}
if wavelet_name in GDW_set:
scales = Dx*freqs*2.0/np.sqrt(5.0)
d3psi = d3GDWdx3
alpha = half_support
J = Nums
else:
sys.exit("There is no wavelet function called " + wavelet_name)
gamma = np.linspace(-alpha,alpha,num=J+1,endpoint=True, dtype=np.float64)
pos = (gamma[1:]+gamma[:-1])/2.0
ss, pp = np.meshgrid(scales,pos,indexing="ij")
Bcoeffs= np.zeros( (len(scales),len(gamma)), dtype=np.float64)
Bcoeffs[:,0] = d3psi( scales, pos[0]/scales )
Bcoeffs[:,-1] = -d3psi( scales, pos[-1]/scales )
Bcoeffs[:,1:-1] = d3psi( ss[:,1:], pp[:,1:]/ss[:,1:] )-d3psi( ss[:,1:], pp[:,0:-1]/ss[:,1:] )
return gamma, Bcoeffs
# 4th-order B-spline function
cdef bspline4th( double var ):
cdef double y
cdef double x = abs(var)
if (x<=0.5):
y = 115.0/192.0-5.0*x**2/8.0+x**4/4.0
elif ((x>0.5)&(x<=1.5)):
y = (55.0+20.0*x-120.0*x**2+80.0*x**3-16.0*x**4)/96.0
elif ((x>1.5)&(x<=2.5)):
y = (5.0-2.0*x)**4/384.0
elif (x>2.5):
y = 0.0
return y
#5th-order B-spline function
cdef bspline5th( double var ):
cdef double y
cdef double x = abs(var)
if (x<=1.0):
y = (33.0-30.0*x**2+15.0*x**4-5.0*x**5)/60.0
elif ((x>1.0)&(x<=2.0)):
y = (51.0+75.0*x-210.0*x**2+150.0*x**3-45.0*x**4+5.0*x**5)/120.0
elif ((x>2.0)&(x<=3.0)):
y = -(x-3.0)**5/120.0
elif (x>3.0):
y = 0.0
return y
#6th-order B-spline function
cdef bspline6th( double var ):
cdef double y
cdef double x = abs(var)
if (x<=0.5):
y = (5887.0-4620.0*x**2+1680.0*x**4-320.0*x**6)/11520.0
elif ((x>0.5)&(x<=1.5)):
y = (23583.0-420.0*x-16380.0*x**2-5600.0*x**3+15120.0*x**4-\
6720.0*x**5+960.0*x**6)/46080.0
elif ((x>1.5)&(x<=2.5)):
y = (4137.0+30408.0*x-59220.0*x**2+42560.0*x**3-\
15120.0*x**4+2688.0*x**5-192.0*x**6)/23040.0
elif ((x>2.5)&(x<=3.5)):
y = (7.0-2.0*x)**6/46080.0
elif (x>3.5):
y = 0.0
return y
#7th-order B-spline function
cdef bspline7th( double var ):
cdef double y
cdef double x = abs(var)
if (x<=1.0):
y = 151.0/315.0 - x**2/3.0+x**4/9.0-x**6/36.0+x**7/144.0
elif ((x>1.0)&(x<=2.0)):
y = (2472.0-392.0*x-504.0*x**2-1960.0*x**3+2520.0*x**4-\
1176.0*x**5+252.0*x**6-21*x**7)/5040.0
elif ((x>2.0)&(x<=3.0)):
y = (-1112.0+12152.0*x-19320.0*x**2+13720.0*x**3-5320.0*x**4+\
1176.0*x**5-140.0*x**6+7.0*x**7)/5040.0
elif ((x>3.0)&(x<=4.0)):
y = -(x-4.0)**7/5040.0
elif (x>4.0):
y = 0.0
return y
# Interpolation coefficients of the signal based on 3rd-order B-spline
# This signal satisfy the zero boundary condition
@cython.wraparound(False)
@cython.boundscheck(False)
@cython.cdivision(True)
cdef InterpCoeffs( np.ndarray[double,ndim=1] signal ):
cc_plus = np.zeros_like(signal, dtype=np.double)
cc_minus = np.zeros_like(signal, dtype=np.double)
cdef int k
cdef int N = len(signal)
cdef double z1 = -2.0 + sqrt(3.0)
cdef double z1_2 = z1**2
cdef int N_z1 = -<int>( round(60*log(10) / log(abs(z1))) )
cdef double[:] y_input = signal
cdef double[:] c_plus = cc_plus
cdef double[:] c_minus = cc_minus
# Compute c_plus
c_plus[0] = y_input[0]
for k in range(1,N):
c_plus[k] = y_input[k] + z1*c_plus[k-1]
# Compute c_minus
z1_powers = z1_2**( np.arange(1, N_z1+1) )
c_minus[N-1] = -6.0*( y_input[N-1]/z1 + c_plus[N-2] )*np.sum( z1_powers )
for k in range(N-2,-1,-1):
c_minus[k] = z1*( c_minus[k+1] - 6.0*c_plus[k] )
c_minus_left = c_minus[0]*( z1**np.arange(6,0,-1) )
c_minus_right = -6.0*c_plus[N-1]*np.sum( z1_powers )*np.array([1.0, z1])
ck = np.concatenate((c_minus_left, c_minus, c_minus_right), axis=0)
gk1 = np.cumsum(ck)
gk2 = np.cumsum(gk1)
gk3 = np.cumsum(gk2)
gk4 = np.cumsum(gk3)
return gk1, gk2, gk3, gk4
# 1st-th antiderivative of signal
@cython.wraparound(False)
@cython.boundscheck(False)
@cython.cdivision(True)
cdef AntiDerivative1st( int N, np.ndarray[double,ndim=1] InterpolationCoeffs, double new_indx ):
cdef int k, Ak, Bk
cdef double BSval
cdef double out = 0.0
cdef double[:] gk = InterpolationCoeffs[3:]
if ( (new_indx>=0)&(new_indx<=N-1) ):
Ak = <int>(ceil(new_indx-3))
Bk = <int>(floor(new_indx+2))
for k in range(Ak, Bk+1, 1):
BSval = bspline4th(new_indx-<double>(k)-0.5)
out = out+gk[k+3]*BSval
return out
else:
sys.exit( "Check the value of new_indx." )
# 2nd-th antiderivative of signal
@cython.wraparound(False)
@cython.boundscheck(False)
@cython.cdivision(True)
cdef AntiDerivative2nd( int N, np.ndarray[double,ndim=1] InterpolationCoeffs, double new_indx ):
cdef int k, Ak, Bk
cdef double BSval
cdef double out = 0.0
cdef double[:] gk = InterpolationCoeffs[2:]
if ( (new_indx>=0)&(new_indx<=N-1) ):
Ak = <int>(ceil(new_indx-4))
Bk = <int>(floor(new_indx+2))
for k in range(Ak, Bk+1, 1):
BSval = bspline5th(new_indx-<double>(k)-1.0)
out = out+gk[k+4]*BSval
return out
else:
sys.exit( "Check the value of new_indx." )
# 3rd-th antiderivative of signal
@cython.wraparound(False)
@cython.boundscheck(False)
@cython.cdivision(True)
cdef AntiDerivative3rd( int N, np.ndarray[double,ndim=1] InterpolationCoeffs, double new_indx ):
cdef int k, Ak, Bk
cdef double BSval
cdef double out = 0.0
cdef double[:] gk = InterpolationCoeffs[1:]
if ( (new_indx>=0)&(new_indx<=N-1) ):
Ak = <int>(ceil(new_indx-5))
Bk = <int>(floor(new_indx+2))
for k in range(Ak, Bk+1, 1):
BSval = bspline6th(new_indx-<double>(k)-1.5)
out = out+gk[k+5]*BSval
return out
else:
sys.exit( "Check the value of new_indx." )
# 4-th antiderivative of signal
@cython.wraparound(False)
@cython.boundscheck(False)
@cython.cdivision(True)
cdef AntiDerivative4th( int N, np.ndarray[double,ndim=1] InterpolationCoeffs, double new_indx ):
cdef int k, Ak, Bk
cdef double BSval
cdef double out = 0.0
cdef double[:] gk = InterpolationCoeffs[:]
if ( (new_indx>=0)&(new_indx<=N-1) ):
Ak = <int>(ceil(new_indx-6))
Bk = <int>(floor(new_indx+2))
for k in range(Ak, Bk+1, 1):
BSval = bspline7th(new_indx-<double>(k)-2.0)
out = out+gk[k+6]*BSval
return out
else:
sys.exit( "Check the value of new_indx." )
# 4th Antiderivative of the signal with zero boundaries in the whole space
@cython.wraparound(False)
@cython.boundscheck(False)
@cython.cdivision(True)
cdef OverallAntiDerivative4th( int N, np.ndarray[double,ndim=1] InterpolationCoeffs,
double C1, double C2, double C3, double C4, double new_indx ):
cdef double x = new_indx
cdef double x0 = <double>(N)
cdef double y
if (x<0.0):
y = 0.0
elif ((x>=0.0)&(x<x0-1.0)):
y = AntiDerivative4th(N, InterpolationCoeffs, x)
elif (x>=x0-1.0):
y = C4+C3*(x-x0+1.0)+0.5*C2*(x-x0+1.0)**2+C1*((x-x0+1.0)**3)/6.0
return y
# CWT of the one-dimensional signal
@cython.wraparound(False)
@cython.boundscheck(False)
@cython.cdivision(True)
def ppCWT_cy( np.ndarray[double,ndim=1] freqs, np.ndarray[double,ndim=1] signal,
double Ls, np.ndarray[double,ndim=1] gamma, np.ndarray[double,ndim=2] Bj ):
cwt_out = np.zeros( (len(freqs),len(signal)), dtype=np.double)
cdef int s, b, j
cdef int Ns = len(freqs)
cdef int N = len(signal)
cdef int Nj = len(gamma)
cdef double C1, C2, C3, C4, F4, indx
cdef double Dx = Ls/<double>(N)
cdef double[:] scales = 2.0*freqs/sqrt(5.0)
cdef double[:] sscales = Dx*2.0*freqs/sqrt(5.0)
cdef double[:,:] cwt = cwt_out
if ( (Bj.shape[1]!=Nj)|(Bj.shape[0]!=Ns) ):
sys.exit( "The length of Bj should be equal to that of gamma" )
# Antiderivatives of signal
gk1, gk2, gk3, gk4 = InterpCoeffs( signal )
C1 = AntiDerivative1st( N, gk1, <double>(N)-1.0 )
C2 = AntiDerivative2nd( N, gk2, <double>(N)-1.0 )
C3 = AntiDerivative3rd( N, gk3, <double>(N)-1.0 )
C4 = AntiDerivative4th( N, gk4, <double>(N)-1.0 )
# Perform CWT
for s in range(Ns): # per scale
for b in range(N): # per sampling point
for j in range(Nj):
indx = <double>(b) - gamma[j]/sscales[s]
F4 = OverallAntiDerivative4th( N, gk4, C1, C2, C3, C4, indx )
cwt[s,b] = cwt[s,b] + Bj[s,j]*F4
cwt[s,b] = Dx*sqrt(scales[s])*cwt[s,b]
return cwt_out
Question: To check the precision of the result of my code, I choose y=exp(-x^2) as the test signal since the CWT of it has an analytic formula. The code snippet is
import numpy as np
from ppCWT_cymod import ppCWT_cy,BjCoeff
# signal
N=2048
L=10.0
Dx = L/N
freqs = np.geomspace(2*np.pi/L, N*np.pi/L, num=256)
x = Dx*np.linspace(-N/2.0,N/2.0-1,N)
y = np.exp(-x**2)
# perform CWT
gamma, Bj = BjCoeff( freqs, L, N, wavelet_name="gdw", half_support=13, Nums=34 )
cwt = ppCWT_cy( freqs, y, L, gamma, Bj )
Apparently, as the scale gets smaller and smaller (freqs
larger), we see that wired noise arises on the right-hand side of the origin. After my examination, such a phenomenon has nothing to do with half_support
, Nums
. At fixed length L
, the noise is more severe as sampling number N
becomes larger. I really can't figure out the source of this noise. Can you help me?