-
Notifications
You must be signed in to change notification settings - Fork 41
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
"Fourier" inverse Abel transform #198
Comments
Looks cool! Thanks for looking into this. How does this compare with the "Fourier Hankel" method? And how does fitting cosine functions to the data differ from just taking the FFT? |
Q1:
The selection of this algorithm was from the reference given in issue #61, a method that appeared to be favoured by the user. I am not familiar with the Fourier Hankel (FH) method. It looks to be a next step to this method,
Q2:
This is a good question, that is not straight forward to answer. The Pretzler basis function |
Yeah, in my mind, I always thought of the FT as a magical way to simultaneously "fit" a bunch of cosine (and sine) functions to your data. So, it seems like we should be able to switch out the fitting for a FFT for a big speed gain. But, perhaps I am missing something here. So, it sounds like this method is somewhat distinct from the FH, which is great. I will try to have a closer look through your code soon. |
Isn't getting the amplitudes of the cosine functions as simple as taking the real part of the FFT? |
You are right, on a frequency scale the coefficients are |
Update: I have not made much progress on reconciling the cosine series coefficients from The end aim is to use the fast The following test case is the sum of two cosine functions Running the However, I cannot correlate the coefficients returned from [Edit:] python3 fft5.py Fourier transform------------ Coefficients An from amplitude of fft at frequency n: Test code, requires import numpy as np
from scipy.fftpack import rfft, fftfreq
import matplotlib.pyplot as plt
import abel
def cosine(r):
return 10*np.cos(2*np.pi*2*r/r[-1]) +\
5*np.cos(2*np.pi*8*r/r[-1])
# signal
r = np.linspace(0, 1, 201)
signal = cosine(r)
n = len(r)
n2 = n//2
# Fourier transform --------------------
fourier_signal = rfft(signal)
# fourier_signal structure [y(0),Re(y(1)),Im(y(1)),...,Re(y(n/2))] n even
# restructure array to extract real (cosine) coefficients, this appears clumsy(?)
fft_signal = fourier_signal[0]
fft_signal = np.append(fft_signal, fourier_signal[1::2])/n2
# frequencies
freq_signal = fftfreq(signal.size, r[1]-r[0])[:n2]
# Least-squares -------------------
# forward transform using hansenlaw
forward_signal = abel.hansenlaw.hansenlaw_transform(signal, direction='forward')
# inverse Abel transform via fitting Fourier series
# fitting Fourier series to forward transformed profile
abel_inverse_of_forward_signal, An = abel.fourier.fourier_transform(
forward_signal, Nu=21)
# Results ----------------------
print("Least-squares ---------------")
print("Coefficients An from lsq fit:")
print(An[:11], end="\n\n")
print("Fourier transform------------")
print("frequencies:")
print(freq_signal[:11], end="\n\n")
print("Coefficients An from amplitude of fft at frequency n:")
print(fft_signal[:11])
# Plots ---------------
fig, ((axsignal, axforward), (axfft, axinverse)) = plt.subplots(2, 2)
# input signal 2x cosines with frequencies 2 and 8
axsignal.plot(r, signal)
axsignal.set_title(r"$10\cos(2\pi\, 2r)+5\cos(2\pi\, 8r)$")
axsignal.set_ylabel("signal")
axsignal.set_xlabel(r"radius ($r$)")
# the fast-Fourier transform
axfft.plot(freq_signal, fft_signal[:-1])
axfft.set_title("fft signal")
axfft.set_xticks([2, 8, 50])
axfft.set_yticks([5, 10])
axfft.set_xlabel(r"frequency ($n$)")
axfft.set_ylabel(r"Cosine coefficients $A_n$")
axfft.axis(xmin=0, xmax=50)
# forward Abel transform
axforward.plot(r, forward_signal)
axforward.set_title("forward Abel transform")
axforward.set_xlabel("radius ($r$)")
# inverse Abel transform via abel/fourier.py
# "should" look like the signal input
axinverse.plot(r, signal, '--', label='signal')
axinverse.plot(r, abel_inverse_of_forward_signal.T, label='fourier')
axinverse.set_title("inverse Abel transform")
axinverse.set_xlabel("radius ($r$)")
axinverse.legend(fontsize='smaller')
plt.subplots_adjust(hspace=0.6, wspace=0.3)
plt.savefig("fft5.png", dpi=75)
plt.show() |
Oh, I now see. The |
So, you're able to replace the fitting procedure with an FFT? This should be much faster. |
Unfortunately, no. I see no common pattern between the coefficients of the two methods. I think this may be due to the phase factors/complex components of At the moment I am packaging The method is slow, a factor of Perhaps, this method belongs in a separate subsection of |
Finally! I have decode the Straight comparison of the fit of a cosine series to a 2-frequency cosine function (2 and 8), using (Edit - fixed poor code, deleted mouse triggered duplicated code) import numpy as np
from scipy.fftpack import rfft
from scipy.optimize import least_squares
import matplotlib.pyplot as plt
import abel
# fit fft, An to the same function
def cosine(r):
return 10*np.cos(2*np.pi*2*r/r[-1]) +\
5*np.cos(2*np.pi*8*r/r[-1])
#return 5+np.cos(2*np.pi*7*r/r[-1])
def series(An, r):
sum = np.zeros_like(r)
for n, an in enumerate(An):
sum += an*np.cos(2*np.pi*n*r/r[-1])
return sum
def residual(An, r, signal):
return signal - series(An, r)
# signal
r = np.linspace(0, 1, 101)
signal = cosine(r)
n = r.size
n2 = n//2
# Fourier transform --------------------
fourier_signal = rfft(signal)/n2
# coefficients thanks to
# http://stackoverflow.com/questions/4258106/how-to-calculate-a-fourier-series-in-numpy
a0, a, b = fourier_signal[0], fourier_signal[1:-1].real,\
-fourier_signal[1:-1].imag
# Least-squares -------------------
# fitting Fourier series
An = np.arange(21)
res = least_squares(residual, An, args=(r, signal))
An = res.x
# re-align fft coefficients
fA = np.zeros_like(An)
fA[0] = a0/2
fA[1:] = a[:fA.size*2-2:2]
for n in np.arange(21):
print("{:2d} {:5.2f} {:5.2f}".format(n, An[n], fA[n]))
# Plots ---------------
plt.plot(r, signal, label="signal")
plt.plot(r, series(An, r), label="series")
plt.plot(r, series(fA, r), label="fft")
plt.legend()
plt.savefig("fftvslsq.png", dpi=75)
plt.show() |
Awesome! The explanation for why the indices of the coefficients from the FFT and from your fitting algorithm don't align is that the FFT is calculating the coefficients for different frequencies than what you are using in your fitting algorithm. If you look at the frequencies in both cases, then the Intensity-as-a-function-of-frequency plots line up perfectly. Just re-interpolate from the FFT to get the intensities at the exact frequencies you desire. Alternatively, if we make sure to use the same frequencies for the least-squares and the FFT, then things match up. I hacked up your code a bit to show how this works: import numpy as np
# from scipy.fftpack import rfft
# FFTs are already in numpy, so no extra scipy import needed
from scipy.optimize import least_squares
from scipy.interpolate import InterpolatedUnivariateSpline
import matplotlib.pyplot as plt
import abel
def cosine(r):
return 10*np.cos(2*np.pi*2*r/r[-1]) +\
5*np.cos(2*np.pi*8*r/r[-1])
#return 5+np.cos(2*np.pi*7*r/r[-1])
def series(An, r):
sum = np.zeros_like(r)
for n, an in enumerate(An):
sum += an*np.cos(2*np.pi*n*r/r[-1])
return sum
def residual(An, r, signal):
return signal - series(An, r)
n = 100
r = np.linspace(0, 2*np.pi, n)
leastsq_freqs = np.fft.rfftfreq(n, r[1]-r[0])
signal = cosine(r)
# Fourier transform --------------------
fourier_signal = np.fft.rfft(signal)/(0.5*r.size)
fourier_freqs = np.fft.rfftfreq(signal.size, r[1]-r[0])
fft_DC_term, fft_cos_terms, fft_sin_terms = fourier_signal[0], fourier_signal[1:-1].real, -fourier_signal[1:-1].imag
interpolation = False
if interpolation:
# re-interpolate FFT to the frequencies of the least-squares
# only needed if the fourier_freqs don't match the leastsq_freqs
fourier_interp = InterpolatedUnivariateSpline(fourier_freqs, fourier_signal.real)
fA = fourier_interp(leastsq_freqs)
else:
fA = fourier_signal.real
# Least-squares:
An = np.zeros_like(leastsq_freqs)
res = least_squares(residual, An, args=(r, signal))
An = res.x
for n in np.arange(21):
print("{:2d} {:5.2f} {:5.2f}".format(n, An[n], fA[n]))
# Plots ---------------
fig, axs = plt.subplots(2,1,figsize=(6,8))
axs[0].plot(fourier_freqs, fourier_signal.real, marker='o', color='r')
axs[0].plot(leastsq_freqs, An, marker='o', color='g')
axs[0].set_xlabel('Fourier frequency (1/rad)')
axs[0].set_ylabel('Fourier coefficient')
axs[1].plot(r, signal, label="signal")
axs[1].plot(r, series(An, r), label="series")
axs[1].plot(r, series(fA, r), label="fft")
axs[1].legend()
axs[1].set_xlabel('Angle (rad)')
axs[1].set_ylabel('Intensity')
plt.savefig("fft.png", dpi=200)
plt.show() |
Thanks @DanHickstein! The problem is that
|
Do we just normalize the length of our image to 2pi, so Edit, no: Edit2: Yes, Edit3: but, then the maximum frequency is pi! |
Well, I think that the spatial frequencies that we work with are arbitrary, but they are basically defined in this case by the sum += an*np.cos(2*np.pi*n*r/r[-1]) I think that this means that we want to have The maximum frequency is given by the highest frequency that is Nyquist sampled, so it depends on how many points we have in |
Fail I now realise that basis row intensity function is not a cosine series(!) and hence the fast Fourier transform ( i.e not a cosine series.
I will look into this next. |
I take back the fail (above), Fourier cosine coefficients may be obtained using a fast-fourier-transform The secret is to duplicate (repeat) the function, to ensure that it is symmetric. I think this is a well known fact, that I had missed. :-( This test code gist compares the two methods, cosine amplitude coefficients
The figure shows a sum of 3 cosines (frequencies 2, 8, and 11), with the same computed from the
|
hello, We also used Tikhonov regularization based on PyAbel package. Basically def Tik(A,b,k,para_lambda,auto,para_start,para_end,N):
# Tik regularization
# A_para_L*x = b_0
# k is ratio<1 of L
# if auto == True, find lambda automatically,N is number of lambda to try,
s = A.shape[0]
L=add_L(k,s)
m = int(k*s)
b_0 = np.vstack((b.reshape((s,1)),np.zeros((m,1))))
if auto:
rho = np.zeros(N)
eta = np.zeros(N)
para = np.linspace(para_start,para_end,N)
for i in range(N):
A_para_L = np.vstack((A,para[i]*L))
x=np.linalg.lstsq(A_para_L,flatten(b_0),rcond=None)[0]
rho[i] = np.linalg.norm(np.subtract(np.matmul(A,x),b.reshape((s,1))))
eta[i] = np.linalg.norm(np.matmul(L,x))
# plt.plot(eta[i],rho[i],'.')
logrho = np.log(rho)
logeta = np.log(eta)
# now curverture of L-curve
# note that 2nd direv is less
kappa = 2*(np.diff(logrho)[:-1]*np.diff(logeta,2)-np.diff(logrho,2)*np.diff(logeta)[:-1])/(np.diff(logrho)[:-1]**2+np.diff(logeta)[:-1]**2)**1.5
para_lambda = para[np.argmax(abs(kappa))]
print 'lambda = ', para_lambda
A_para_L = np.vstack((A,para_lambda*L))
x=np.linalg.lstsq(A_para_L,flatten(b_0),rcond = None)[0]
return x DH edited 2018-08-28 to format code. |
Dear Professor Liu, Apologies for the delayed reply. Thank you very much for presenting this method and for bringing your paper to our attention! We are delighted that PyAbel has been useful for your research, and that you have made a comparison of so many Abel transform methods. We are very interested in your results, and would like to discuss them with you. But first I must confess that we are disappointed to see no mention of PyAbel in your manuscript. Instead, all of the methods are presented as if the authors of the manuscript are entirely responsible for their implementation. Since your manuscript deals with the technical details of the various Abel transform methods, it seems inappropriate to omit the fact that the methods were part of PyAbel. Anyway, moving on to a more interesting discussion. You show that the performance of many methods that are not currently incorporated into PyAbel greatly exceeds the methods that are currently implemented. The logical action is that we should attempt to incorporate these methods into PyAbel! I hope that you can help us understand these methods better and perhaps incorporate them into PyAbel. I have several questions for you:
Thanks again for your nice comparison and for sending us the manuscript. We look forward to working with you to make PyAbel better and more useful for everyone! With best regards, Dan |
@liuxunchen, did you get chance to take a look at these questions? |
Dear Danhickstein,
I just see the email. You are absolutely correct. I will open github and take
a look. Sorry for the delay. Sometimes the Github is difficult to load.
xunchen
…On Friday, September 14, 2018 3:27:17 AM CST Danhickstein wrote:
@liuxunchen, did you get chance to take a look at these questions?
|
A preliminary Python version of Pretzler's Fourier cosine series transform is in my
PyAbel
fork fourier branch. The fileabel/fourier.py
is self-contained, and will run with your ownPyAbel
distribution:python3 fourier.py
.I like the method because the concept is simple, and it illustrates how the other basex methods (may) work.
The code would benefit from @DanHickstein's vectorization magic for
hbasis
. I have yet to tidy up the code, link to the correct basis storage functions intools/basis.py
, documentation, unit test.Open for discussion, before I prepare it for
PyAbel
.The text was updated successfully, but these errors were encountered: