FFT of a given diagram (data) - matlab

I have collected some data of pressure distribution of an acoustic field along an axis, i.e. Pressure_vs_distance, at a specific source frequency (Let's say 0.5 MHz). Now, I want to obtain the frequency components of this data (which will be n*0.5 MHz) but every code I try, I can't extract those frequencies and the FFT diagram is like a vertical line at zero. The data and figure are provided below. Could you please help me to see what's wrong?
P.S: My own guess is the number of axial data. But, even when I increased it, the result didn't change (shape of the fft diagram).
Pressure (MPa) _vs_axial distance (cm)
data (Size~ 2 Mb): https://ufile.io/wjhlo
L=length(y);
dx=9.43479300064157e-05;
fs=1/dx;
out=fft(y,L)/L;
figure
plot(fs/2*linspace(0,1,(length(out)/2)+1),abs(out(1:(length(out)/2)+1)))
title('One sided Spectrum')
xlabel('Normalized frequency')
ylabel('Magnitude')

From this question:
Confusion in figuring out the relation between actual frequency values and FFT plot indexes in MATLAB
[Ycomp, fHz] = getFFT(y(1:1000),1/(x(2)-x(1)))
Your sampling frequency seems to be 10.6KHz. So according to the Nyquist Shannon sampling theorem you will not detect frequencies above 5.3 KHz. Not sure where your 0.5 MHz is coming from.

First of all prepare your data: try pass your data through the pre-emphasis filter:
y1 = filter([1 -1], 1, y);
Next, observe your data and remove obvious data errors:
y1(1:3) = 0;
The Fourier analysis suites only for quasi-periodic signals. So, you should select an interval of quasi-stationarity and perform analysis on such (possible overlapping) intervals. Let's say the signal is quasi-stationary on 5000 points. And we want to perform analysis with 100 points shift:
sampleRate = 1/(x(2)-x(1));
frameSize = 5000;
frameShift = 100;
[signalSpectrogram, freqObs, timeObs] = spectrogram(y1, frameSize, frameSize-frameShift, pow2(nextpow2(frameSize)), sampleRate);
Let's display all the results:
figure('NumberTitle','off', 'Name','The Spectral Analysis', 'Units','normalized', 'Position',[0 0 1 1]);
subplot(3,1,1);
plot(x,y);
grid('on');
xLim = xlim();
title('Original Signal');
subplot(3,1,2);
plot(x,y1);
grid('on');
title('Prepared Signal');
subplot(3,1,3);
imagesc(timeObs, freqObs, 10*log10(signalSpectrogram.*conj(signalSpectrogram)));
axis('xy');
axis([xLim 0 200]);
title('Prepared Signal Spectrogram');
At the end you must get something similar to:

Related

Highpass Lowpass filter signal, remove edge artifacts Matlab

I need to filter some signals eliminating frequencies below 0.7 HZ and above 4 HZ (as suggested in the reference literature.
I'm trying this on one of the signals (second subplot):
I am adding a link with the example signal used here exampleSignal
My signal is "ydata". To highpass the signal I tried:
vidInfo.frameRate = 29.9293;
highPassedSig = highpass(ydata,4,vidInfo.frameRate) % vidInfo.frameRate
It seems to me that there is some serious edge artifact.
If I eliminate the first 50 and the last 50 samples, this is the central part of the signal:
To get rid of the edge artifact, I tried to make the signal longer at the beginning and the end. I tried two approaches: 1) zero padding, 2) doubling the first and last 50 samples of the signal. The zero padding didn't work. I found some code on mathworks for the second approach:
R=0.1; % 10% of signal
Nr=50;
N=size(ydata,1);
NR=min(round(N*R),Nr); % At most 50 points
for i=1:size(ydata,2)
ydata1(:,i)=2*ydata(1,i)-flipud(ydata(2:NR+1,i)); % maintain continuity in level and slope
ydata2(:,i)=2*ydata(end,i)-flipud(ydata(end-NR:end-1,i));
end
sigToHighPass=[ydata1;ydata;ydata2];
% Do filtering
highPassedSig = highpass(sigToHighPass,4,vidInfo.frameRate);
highPassedSig = highPassedSig(NR+1:end-NR,:)
This is the result:
It looks like the edge artifact is removed. At this point, I tried to apply the low-pass filter to the highpass filtered data.
N=size(highPassedSig,1);
NR=min(round(N*R),Nr); % At most 50 points
for i=1:size(highPassedSig,2)
highPassedSig1(:,i)=2*highPassedSig(1,i)-flipud(highPassedSig(2:NR+1,i)); % maintain continuity in level and slope
highPassedSig2(:,i)=2*highPassedSig(end,i)-flipud(highPassedSig(end-NR:end-1,i));
end
sigToLowPass=[highPassedSig1;highPassedSig;highPassedSig2];
% Do filtering
lowPassedSig = lowpass(sigToLowPass,0.7,vidInfo.frameRate);
lowPassedSig=lowPassedSig(NR+1:end-NR,:)
This is the result
Once again, it looks like there is some serious edge artifact.
I also tried other approaches. For instance:
d = designfilt('lowpassfir', 'FilterOrder', 5, 'CutoffFrequency', 0.7, 'SampleRate', vidInfo.frameRate);
lowPassedSig = filter(d, ydata);
This is the result (subplot3):
Does anyone have any idea how can I deal with this?
Thank you,
Gianluca
the fundamental problem I see in your approach is that you are miss-interpreting your cut-off frequencies. If you want to remove frequencies below 0.7 Hz and above 4 Hz, you need :
A lowpass filter with a cut-off frequency at 4 Hz.
A highpass filter with a cut-off frequency at 0.7 Hz.
Not the other way around !
This is easily done using the highpass and lowpass functions as you suggested. They are dealing with the padding automatically. If you want you can design your own digital filter BUT with the cut-off frequencies mentionned above :)
A matlab code that does what you want is :
ydata = load("signaldata.mat","-mat");
ydata = ydata.ydata;
f = 29.9293;
lp = 0.7;
hp = 4;
[y_hp,d_hp] = highpass(ydata,lp,f);
[y_lphp,d_lp] = lowpass(y_hp,hp,f);
%% plot result
x = (1:length(ydata))/f;
figure;
subplot(3,1,1);
plot(x,ydata);
legend("Original");
subplot(3,1,2);
plot(x,y_hp);
legend("Highpass");
subplot(3,1,3);
plot(x,y_lphp);
xlabel('Time [s]');
legend("Lowpass");
%% plot filters
figure;
[h,w] = freqz(d_hp);
subplot(2,2,1);
title('Highpass');
loglog(w/pi*f/2,abs(h));
subplot(2,2,3);
semilogx(w/pi*f/2,angle(h)/pi*180);
[h,w] = freqz(d_lp);
subplot(2,2,2);
title('Lowpass');
loglog(w/pi*f/2,abs(h));
subplot(2,2,4);
semilogx(w/pi*f/2,angle(h)/pi*180);
This gives the following plots :
The input and output data
The frequency responses (magnitude and phase) of the filters.
I hope this explanation will help you !

Matlab FFT - Scaling y-axis

As a data basis, I have measured data the volts were recorded.
Matlab will now be used to perform an FFT.
I have the following questions:
- What unit do I have on the ordinate axis after the FFT? Also volts?
- How is scaled correctly? By hiding the negative frequencies (Nyquist), I would actually have to double the amplitude, right?
- Do I have to multiply all values of the FFT again with 20 * log10 (FFT) to represent the ordinate in db?
Thank you so much for your support!
Frank
Matlab example:
load('TimeDomain.mat')%loading of the time domain signal
L=2500; %length of the signal
Fs=500000;%sampling frequency
N=2^nextpow2(L);%scale factor
t=(0:L-1)*10^-3;%time domain array
f=linspace(0,Fs/2,length(t));%frequency domain array
FFT=abs(fft(Timedomain,N));
figure(1)
plot(f,FFT(1:2500))
Yes, after the FFT, the unit of the ordinate axis will still be volts.
You can scale it by dividing by the number of samples of your signal, then you can indeed multiplying by two (except the first and last elements that represents respectively the frequency 0 and Fs/2) if you want to plot all the spectrum in the positive side.
Then if you want to plot in dB, you can use the function mag2db which applies the formula that you said.
I found some weirdnesses in your code, so I suggest some fixes. My Timedomain signal is a 100 kHz sine at 1 V.
Fs = 500000; % sampling frequency
L = 2500; % length of the signal
t = (0:L-1)/Fs; % time domain array
f = linspace(0, Fs/2, L/2+1); % frequency domain array
Timedomain = cos(2*pi*100000*t); % Input signal
FFT = abs(fft(Timedomain)/L);
FFT(2:L/2) = 2*FFT(2:L/2);
%% Plots
subplot(2,1,1);
plot(f, FFT(1:L/2+1)); xlabel('Frequency (Hz)'); ylabel('Tension (V)');
subplot(2,1,2);
plot(f, mag2db(FFT(1:L/2+1))); xlabel('Frequency (Hz)'); ylabel('Tension (dBV)');
This returns:

Dividing FFT into several bins containing dominant peaks and finding energy in these bins

I've generated FFTs for a set of signals in time domain at different deflections. I want to find in which frequency range the signal is more sensitive. Can someone help me do the following since I'm new to programming and signal processing??
So I want to select a frequency range in which all the dominant peaks
are coming .
( In this case FFT plot of signals, by looking at the figure, the frequency
range to be considered would be somewhere between 35000Hz-125000Hz
(Is there a way to automatically find this ?) Also I don't know where
to fix the threshold.)
Divide it into a number of bins (not more than 10) in such a way that
each peak should fall completely within one of these bins. (bins need
not be of equal length and should be same for all signals)
Then, calculate the energy for each bin in each signal. Then plot curves
for each bin showing the variation of energy in each bin with respect
to deflection. (ie. deflection on x-axis and area under curve on y
axis.)
Following is the code I'm using for getting FFT (also attached the set of time domain signals and deflection data):
[m, n]= size(amp); % amp- amplitude data
t=linspace(0,5000,n); % time
Fs=2000000;
for i=1:m
L = n;
NFFT = 2^nextpow2(L);
UT = fft(amp(i,:),NFFT)/L;
f = Fs/2*linspace(0,1,NFFT/2);
ft(i,:) = zeros(1,NFFT/2);
ft(i,:) = UT(1:NFFT/2);
mag(i,:) = 2*abs(ft(i,:));
end;
figure
for i=1:m
plot(f,mag(i,:))
hold on;
end;
xlabel('Frequency (Hz)','fontsize',12)
ylabel('Magnitude','fontsize',12)
title('Frequency Spectrum of recieved signals','fontsize',14)
axis([0,150000,0,4])
legend(arrayfun(#(deflection) ['Deflection = ',num2str(deflection),'\mum'],deflection,'Uni',0));
set(gcf, 'Color', 'w');

How to find the frequency of a periodic sound signal?

I'm working on sound signals of a walking pattern, which has obvious regular patterns:
Then I thought I can get the frequency of walking (approximately 1.7Hz from the image) using FFT function:
x = walk_5; % Walking sound with a size of 711680x2 double
Fs = 48000; % sound frquency
L=length(x);
t=(1:L)/Fs; %time base
plot(t,x);
figure;
NFFT=2^nextpow2(L);
X=fft(x,NFFT);
Px=X.*conj(X)/(NFFT*L); %Power of each freq components
fVals=Fs*(0:NFFT/2-1)/NFFT;
plot(fVals,Px(1:NFFT/2),'b','LineSmoothing','on','LineWidth',1);
title('One Sided Power Spectral Density');
xlabel('Frequency (Hz)')
ylabel('PSD');
But then it doesn't give me what I expected:
FFT result:
zoom image has lots of noises:
and there is no information near 1.7Hz
Here is the graph from log domain using
semilogy(fVals,Px(1:NFFT));
It's pretty symmetric though:
I couldn't find anything wrong with my code. Do you have any solutions to easily extract the 1.7Hz from the walking pattern?
here is the link for the audio file in mat
https://www.dropbox.com/s/craof8qkz9n5dr1/walk_sound.mat?dl=0
Thank you very much!
Kai
I suggest you to forget about DFT approach since your signal is not appropriate for this type of analysis due to many reasons. Even by looking on the spectrum in range of frequencies that you are interested in, there is no easy way to estimate the peak:
Of course you could try with PSD/STFT and other funky methods, but this is an overkill. I can think of two, rather simple methods, for this task.
First one is based simply on the Auto Correlation Function.
Calculate the ACF
Define the minimum distance between them. Since you know that expected frequency is around 1.7Hz, then it corresponds to 0.58s. Let's make it 0.5s as the minimum distance.
Calculate the average distance between peaks found.
This gave me an approximate frequency of 1.72 Hz .
Second approach is based on the observation to your signal already has some peaks which are periodic. Therefore we can simply search for them using findpeaks function.
Define the minimum peak distance in a same way as before.
Define the minimum peak height. For example 10% of maximum peak.
Get the average difference.
This gave me an average frequency of 1.7 Hz.
Easy and fast method. There are obviously some things that can be improved, such as:
Refining thresholds
Finding both positive and negative peaks
Taking care of some missing peaks, i.e. due to low amplitude
Anyway that should get you started, instead of being stuck with crappy FFT and lazy semilogx.
Code snippet:
load walk_sound
fs = 48000;
dt = 1/fs;
x = walk_5(:,1);
x = x - mean(x);
N = length(x);
t = 0:dt:(N-1)*dt;
% FFT based
win = hamming(N);
X = abs(fft(x.*win));
X = 2*X(1:N/2+1)/sum(win);
X = 20*log10(X/max(abs(X)));
f = 0:fs/N:fs/2;
subplot(2,1,1)
plot(t, x)
grid on
xlabel('t [s]')
ylabel('A')
title('Time domain signal')
subplot(2,1,2)
plot(f, X)
grid on
xlabel('f [Hz]')
ylabel('A [dB]')
title('Signal Spectrum')
% Autocorrelation
[ac, lag] = xcorr(x);
min_dist = ceil(0.5*fs);
[pks, loc] = findpeaks(ac, 'MinPeakDistance', min_dist);
% Average distance/frequency
avg_dt = mean(gradient(loc))*dt;
avg_f = 1/avg_dt;
figure
plot(lag*dt, ac);
hold on
grid on
plot(lag(loc)*dt, pks, 'xr')
title(sprintf('ACF - Average frequency: %.2f Hz', avg_f))
% Simple peak finding in time domain
[pkst, loct] = findpeaks(x, 'MinPeakDistance', min_dist, ...
'MinPeakHeight', 0.1*max(x));
avg_dt2 = mean(gradient(loct))*dt;
avg_f2 = 1/avg_dt2;
figure
plot(t, x)
grid on
hold on
plot(loct*dt, pkst, 'xr')
xlabel('t [s]')
ylabel('A')
title(sprintf('Peak search in time domain - Average frequency: %.2f Hz', avg_f2))
Here's a nifty solution:
Take the absolute value of your raw data before taking the FFT. The data has a ton of high frequency noise that is drowning out whatever low frequency periodicity is present in the signal. The amplitude of the high frequency noise gets bigger every 1.7 seconds, and the increase in amplitude is visible to the eye, and periodic, but when you multiply the signal by a low frequency sine wave and sum everything you still end up with something close to zero. Taking the absolute value changes this, making those amplitude modulations periodic at low frequencies.
Try the following code comparing the FFT of the regular data with the FFT of abs(data). Note that I took a few liberties with your code, such as combining what I assume were the two stereo channels into a single mono channel.
x = (walk_5(:,1)+walk_5(:,2))/2; % Convert from sterio to mono
Fs = 48000; % sampling frquency
L=length(x); % length of sample
fVals=(0:L-1)*(Fs/L); % frequency range for FFT
walk5abs=abs(x); % Take the absolute value of the raw data
Xold=abs(fft(x)); % FFT of the data (abs in Matlab takes complex magnitude)
Xnew=abs(fft(walk5abs-mean(walk5abs))); % FFT of the absolute value of the data, with average value subtracted
figure;
plot(fVals,Xold/max(Xold),'r',fVals,Xnew/max(Xnew),'b')
axis([0 10 0 1])
legend('old method','new method')
[~,maxInd]=max(Xnew); % Index of maximum value of FFT
walkingFrequency=fVals(maxInd) % print max value
And plotting the FFT for both the old method and the new, from 0 to 10 Hz gives:
As you can see it detects a peak at about 1.686 Hz, and for this data, that's the highest peak in the FFT spectrum.

Band pass implementation matlab

I've 2 raw signals X and Y measuring vibrations of a rotating shaft at const. speed 633.33 Hz. My goal is to extract only specific frequency component (say 1X or .35X)and plot orbits (X signal plotted against Y signal) for them. I took the raw signal and I applied the low pass filter using Butterworth filter. it gave me smooth signal in the time domain. Now when I'm trying to apply the butterworth band pass filter between frequencies (630 Hz to 640 Hz) its not working properly. I don't know if I'm doing it right.
The following picture is after the application of low pass filter ( butterworth).
This is another after I applied butterworth low pass and band pass filters. There is complete change in the original signal.
I'm expecting the filter to do something like this a cleaner orbit for 1X frequency component.
My MATLAB code is as follows.
L = length(X); % length of signal
fs= 2e6; % sampling frequency
df = fs/L; % Frequency window
dt = 1/df; % time window
%calculate time axis
T = (0:dt:(L-1)*dt)';
subplot(3,2,1);
plot(T,X);
title('before filtering X signal')
subplot (3,2,2);
plot(T,Y);
title('before filtering Y signal')
subplot(3,2,5);
plot(X,Y);
title('Orbits before filtering')
X = detrend(X,0); % Removing DC Offset
Y = detrend(Y,0); % Removing DC Offset
% Butterworth low pass filter to remove high frequency components
[b2,a2] = butter(6,5*633/(fs/2),'low');
dataInX = X;
X = filter(b2,a2,dataInX); %filter command filters
dataInY = Y;
Y = filter(b2,a2,dataInY);
% butter worth band pass to only plot for 1X frequency component
[b1,a1] = butter(1,[633/(fs/2) 640/(fs/2)],'bandpass');
dataInX = X;
X = filter(b1,a1,dataInX); %filter command filters
dataInY = Y;
Y = filter(b1,a1,dataInY);
subplot(3, 2 ,3);
plot(T,X);
axis tight
title('X signal after filtering')
subplot(3,2,4);
plot(T,Y);
axis tight
title('Y signal after filtering')
subplot(3,2,6);
plot(X,Y);
title('Orbit after filtering')
axis tight
I'm also attaching my data file for reference.
I'm new into the world of filters and DSP. Could someone help to fix this one with suggestions or hints or ideas.
After lowpassing the signal ( i.e. [b2,a2] = butter(6,5*633/(fs/2),'low');) you can down sample from 2 MHz to ~ 4 kHZ and wont see much change in the result. Downsampling in this case will not change any resolution that the filter not already has reduced.
You could e. use resample(x,1,500) to downsample your 2 MHz signal to 4 kHz AFTER applying the lowpass. Then you should have no trouble with your narrow bandpass. Make sure to pass the new samplingrate onto the filter.
Also, if you have the full signal available, use filtfilt instead of filt to avoid phase distortion. In this case, you can lower the filter order a bit, but 4th order for the lowpass and the bandpass (after resampling) should just work fine.