【问题标题】:Computing FFT of a spectrum using python使用python计算频谱的FFT
【发布时间】:2020-10-14 13:25:54
【问题描述】:

频谱显示波纹,我们可以直观地量化为 ~50 MHz 波纹。我正在寻找一种方法来计算这些波纹的频率,而不是通过目视检查数千个光谱。由于该函数在频域中,因此采用 FFT 会将其返回到时域(如果我是正确的,则会进行时间反转)。我们如何获得这些涟漪的频率?

【问题讨论】:

  • @SiHa -- 图片已上传。
  • 我会使用自相关分析而不是 FFT。
  • @manu190466 -- 假设您只有一个频谱,您将如何计算这些波纹的频率?
  • @LPGaming 我明白了。我正在考虑,我会回复你的。我知道困惑在哪里,但我正在撰写一个体面的答案,对我来说如此赤裸裸。
  • 感谢您的提醒,我在您的代码中看到了用于平滑信号的低通滤波器,我也在最终解决方案中使用了它。

标签: python fft


【解决方案1】:

要获得适合蓝色图的光谱,您需要做两件事:

  1. 正确计算频谱图的频率(红色)
  2. 消除数据中的偏差,使光谱较少受到低浓度的污染 频率。那是因为你对涟漪感兴趣,而不是缓慢的波动。

注意,当您计算FFT时,您可以获得复杂的值,该值包含每个频率的振动幅度和阶段的信息。在您的情况下,红色图应该是幅度谱(与相位谱相比)。为了得到它,我们占据了绝对的价值 FFT系数。

此外,您使用 fft 获得的频谱是两侧对称的(因为信号是真实的)。你真的只需要一边来获得纹波峰值频率的想法。我在代码中实现了这一点。

使用您的数据后,这是我所拥有的:

import pandas as pd
import numpy as np
import pylab as plt
import plotly.graph_objects as go
from scipy import signal as sig

df = pd.read_csv("ripple.csv")
f = df.Frequency.to_numpy()
data = df.Data
data = sig.medfilt(data)  # median filter to remove the spikes

fig = go.Figure()
fig.add_trace(go.Scatter(x=f, y=(data - data.mean())))
fig.update_layout(
    xaxis_title="Frequency in GHz", yaxis_title="dB"
)  # the blue plot with ripples
fig.show()

# Remove bias to get rid of low frequency peak
data_fft = np.fft.fft(data - data.mean())

L = len(data)  # number of samples

# Compute two-sided spectrum
tssp = abs(data_fft / L)

# Compute one-sided spectrum
ossp = tssp[0 : int(L / 2)]
ossp[1:-1] = 2 * ossp[1:-1]

delta_freq = f[1] - f[0]  # without this freqs computation is incorrect
freqs = np.fft.fftfreq(f.shape[-1], delta_freq)

# Use first half of freqs since spectrum is one-sided
plt.plot(freqs[: int(L / 2)], ossp, "r-")  # the red plot
plt.xlim([0, 50])
plt.xticks(np.arange(0, 50, 1))
plt.grid()
plt.xlabel("Oscillations per frequency")
plt.show()

所以你可以看到有两个峰值:低频。振荡在1到2 Hz之间 每个GHz的大约17个振荡约为你的涟漪。

【讨论】:

  • 优秀的答案。唯一缺少的是如何从 17 Hz 到他正在寻找的 50MHz 纹波周期。看看my take的问题。
  • 谢谢DMitrii,指出低频峰值去除。 @jpnadas 在他的回答中解释了进一步的步骤。感谢您共享知识。 span>
  • @ lpgaming,您只能在一次接受的标记1答案。由于我进一步进入你的问题,也许你应该考虑标记我的。 span>
  • @jpnadas,我很困惑,你是如何获得这个 ~50 MHz 频率的,但从你的回答中我明白了,这是什么意思。不过,我并没有真正理解它的物理意义,因为这 ~50 MHz(或我们现在知道的 57)并不是纹波的真正频率。相反,正如你所指出的那样,它是原始(蓝色)频谱中的纹波周期。由于在时域中,我们似乎在时域中的频率似乎是频率,而不是在时间域中表征窄带噪声,尽管它是等效的。 span>
  • 哎呀!我的糟糕,我第一次点击你的答案,然后点击了dmitrii。错误已修复... @jpnadas答案标记为已接受的答案,因为他提供了完整的答案。 span>
【解决方案2】:

问题源于您混淆了您正在测量的术语“频率”和数据的频率。

你想要的是纹波频率,实际上就是你数据的周期。

解决了这个问题,让我们看看如何修复您的 fft。

正如Dmitrii's answer 所指出的,您必须确定数据的采样频率,并去除 FFT 结果中的低频分量。

要确定采样频率,您可以通过将每个样本减去其前一个样本并计算平均值来确定采样周期。平均采样频率正好是它的倒数。

fs = 1 / np.mean(freq[1:] - freq[:-1])

对于高通滤波器,您可以使用巴特沃斯滤波器,this 是一个很好的实现。

# Defining a high pass filter
def butter_highpass(cutoff, fs, order=5):
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
    return b, a

def butter_highpass_filter(data, cutoff, fs, order=5):
    b, a = butter_highpass(cutoff, fs, order=order)
    y = signal.filtfilt(b, a, data)
    return y

接下来,绘制fft时,需要取它的绝对值,这就是你所追求的。此外,因为它给了你积极和消极的部分,你可以只使用积极的部分。就 x 轴而言,它将是采样频率的 0 到一半。这将在this answer

上进一步探讨
fft_amp = np.abs(np.fft.fft(amp, amp.size))
fft_amp = fft_amp[0:fft_amp.size // 2]
fft_freq = np.linspace(0, fs / 2, fft_amp.size)

现在,要确定纹波频率,只需获取 FFT 的峰值即可。您正在寻找的值(大约 50MHz)将是纹波峰值的周期(以 GHz 为单位),因为您的原始数据以 GHz 为单位。对于这个例子,它实际上是 57MHz 左右。

peak = fft_freq[np.argmax(fft_amp)]

ripple_period = 1 / peak * 1000

print(f'The ripple period is {ripple_period} MHz')

这是完整的代码,它还绘制了数据。

import numpy as np
import pylab as plt
from scipy import signal as signal


# Defining a high pass filter
def butter_highpass(cutoff, fs, order=5):
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
    return b, a

def butter_highpass_filter(data, cutoff, fs, order=5):
    b, a = butter_highpass(cutoff, fs, order=order)
    y = signal.filtfilt(b, a, data)
    return y


with open('ripple.csv', 'r') as fil:
    data = np.genfromtxt(fil, delimiter=',', skip_header=True)

amp = data[:, 0]
freq = data[:, 1]


# Determine the sampling frequency of the data (it is around 500 Hz)
fs = 1 / np.mean(freq[1:] - freq[:-1])

# Apply a median filter to remove the noise
amp = signal.medfilt(amp)

# Apply a highpass filter to remove the low frequency components 5 Hz was chosen
# as the cutoff fequency by visual inspection. Depending on the problem, you
# might want to choose a different value

cutoff_freq = 5
amp = butter_highpass_filter(amp, cutoff_freq, fs)

_, ax = plt.subplots(ncols=2, nrows=1)
ax[0].plot(freq, amp)
ax[0].set_xlabel('Frequency GHz')
ax[0].set_ylabel('Intensity dB')
ax[0].set_title('Filtered signal')

# The FFT part is as follows

fft_amp = np.abs(np.fft.fft(amp, amp.size))
fft_amp = fft_amp[0:fft_amp.size // 2]
fft_freq = np.linspace(0, fs / 2, fft_amp.size)

ax[1].plot(fft_freq, 2 / fft_amp.size * fft_amp, 'r-')  # the red plot
ax[1].set_xlabel('FFT frequency')
ax[1].set_ylabel('Intensity dB')

plt.show()

peak = fft_freq[np.argmax(fft_amp)]

ripple_period = 1 / peak * 1000

print(f'The ripple period is {ripple_period} MHz')

剧情如下:

【讨论】:

  • 感谢您的详尽回答。我将详细阅读您的话,理解数学和代码等的每一点,然后还原。同时,我尝试运行代码,它在行 - ax[1].plot(fft_freq, 2 / fft_amp.size * fft_amp, 'r-') # 红色图处引发了 x & y 数组不匹配的错误。请让我知道你是否得到同样的结果。谢谢。
  • 原来的答案有错别字。我已经编辑过了。可以再试一次吗?
  • 太棒了!它有效...感谢您的快速回复。如果需要更多帮助,我将详细介绍并恢复。干杯。
  • 当然,如果它按预期工作,您可能希望将答案标记为已接受,以便人们在找到此线程时知道它解决了问题。
  • 我已用绿色勾号将其标记为已接受的答案。我是这个网站的新手,所以如果我错过了什么,请告诉我。再次感谢!
猜你喜欢
  • 1970-01-01
  • 2011-02-05
  • 1970-01-01
  • 2010-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多