Processing and identifying noises in EEG signal

48 visualizaciones (últimos 30 días)
David
David el 25 de Jun. de 2024
Respondida: Diego Caro el 26 de Jun. de 2024
I'm working on a final project of processing the EEG signal from a 24-day-old infant. The data file is in .edf format, and I've extracted and plotted it as a time series. You can find the data file here: https://file.io/3fWPetZYclsH.
The project's requirements are:
  • Analyze the data to identify signal and noises.
  • Design filters to eliminate the DC component, Electricity noise, and others (if contaminated).
My first approach was to use the power spectral density (PSD), but this idea was rejected as my professor wanted us to use more fundamental algorithms like DFT of Hibert-Huang Transform (HHT). Another problem with my prior work was that my filtered data lost all of the data after a a specific frequency, which my professor attributed to using a 'second-order' filter. Below is my attempt on creating the filters. The two tasks i need help is:
  • Use DFT and/or HHT to identify signal and noises. It is strictly required to identify where the noises are.
  • Designing filters that effectively eliminate noise without losing essential data points.
I'd appreciate any guidance from anyone. Thank you in advance for your assistance!
def highpass_filter(data, sfreq, cutoff=0.5):
nyquist = 0.5 * sfreq
normal_cutoff = cutoff / nyquist
b, a = butter(1, normal_cutoff, btype='high', analog=False)
return filtfilt(b, a, data)
def notch_filter(data, sfreq, freq=50.0, quality=30.0):
nyquist = 0.5 * sfreq
notch_freq = freq / nyquist
b, a = iirnotch(notch_freq, quality)
return filtfilt(b, a, data)
def bandpass_filter(data, sfreq, lowcut=0.5, highcut=8.0):
nyquist = 0.5 * sfreq
low = lowcut / nyquist
high = highcut / nyquist
b, a = butter(1, [low, high], btype='band')
return filtfilt(b, a, data)

Respuesta aceptada

Star Strider
Star Strider el 25 de Jun. de 2024
The data file doesn’t exist. At least it’s not available to me. It’s bettter to attach it here, anyway. If it has an excluded exttension, use the zip function to enclose it in a .zip file that you can upload here (providing it’s within the size limitts).
You can make things easier for yourself (since you obviously have the Signal Processing Toolbox) by using the highpass, bandstop, and bandpass functions. Use the 'ImpulseResponse','iir' name-value pair in each function call to force them to design a very efficientt elliptic filter. There are other name-value pair arguments to those functions that can allow you to modify the filter design, however I usually go with the default values. (An order 1 Butterworth filter isn’t going to do much actual filtering. Use the freqz function to view their Bode plots.)
Implementing the fft function is straightforward, and there are several examples in the documentation and in Answers. (I wrote several of them here, using essentially the same code, and most recently a function I created.)
Your approach is correct. The Fourier transform will demonstrate the frequency components of the signal. Frequency-selective filters will be useful for band-limited noise, including mains/powerline noise, however broadband noise will be difficult to deal with. There is no best solution for that, however wavelet denoising and the Savitzky-Golay filter are commonly used to reduce or eliminate broadband noise. The tradeoff is in reducing the noise without compromising the signal morphology. Badly designed filters that eliminate most of the noise can render the resulting filtered signal clinically useless.
  5 comentarios
David
David el 25 de Jun. de 2024
I really like your idea of separating the data into each EEG band. I’ve asked my professor for the work after the filtering, that are: * Plot the original and filtered data (in both time and frequency domain) to visualize the change after filtering. * Calculate the SNR to demonstrate that the designed filter is effective. I assume that the filter should focus on dealing with low frequency noise (DC component, shown in the left half of your fft plot) and also high frequency noise (electrical line noise, somewhat in between 50Hz and 60Hz). I think it is most important to identify those noises, show how we have dealing with them, and provide evidence of the filter’s efficiency (SNR, visualization).
Star Strider
Star Strider el 25 de Jun. de 2024
Editada: Star Strider el 25 de Jun. de 2024
Thank you!
My apologies for the delay. Away for a bit.
There is not much high-frequency noise, and I didn’t see any mains/powerline noise at all. This indicates that a good reference electtrode was employed during the recording. There is a snr function I would recommend to calculate that. You can eliminate the D-C offset with a highpass filter. Experiment with the cutoff frrequency,m probably beginning at 3E-3 Hz, then re-calculate the fft and see the result. Another option, witthout using any filter, is simply to subtract the mean of each channel from that channel to eliminate the D-C offset, although my ‘FFT1’ function does that automatically, so there is actually no constant basellilne offset in the Fourier transformed signals, although there could be some baseline variation due to a ‘wandering’ non-constant baselline.
That would go something like this —
zf = dir('*.zip');
Uz = unzip(zf.name);
Lv1 = contains(Uz,'.edf');
EDF = Uz{Lv1};
Data = edfread(EDF)
Data = 82x19 timetable
Record Time Fp1 Fp2 F3 F4 C3 C4 P3 P4 O1 O2 F7 F8 T3 T4 T5 T6 FZ CZ PZ ___________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ _______________ 0 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 8 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 16 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 24 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 32 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 40 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 48 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 56 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 64 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 72 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 80 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 88 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 96 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 104 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 112 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} 120 sec {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double} {1600x1 double}
VN = Data.Properties.VariableNames;
Fs = 1600/8;
tv = linspace(0, 1599, 1600).'/Fs;
Ofst = seconds(Data.('Record Time'));
tm = repmat(tv,1,size(Data,1)) + Ofst.';
t = tm(:);
% Data.Fp1{1}
Datac = table2array(Data);
DataMtx = cell2mat(cat(1,Datac));
DataMtxHPF = highpass(DataMtx, 3E-3, Fs, 'ImpulseResponse','iir');
figure
plot(t, DataMtx)
grid
xlabel('Time (s)')
ylabel('Amplitude')
[FT_EEG,Fv] = FFT1(DataMtxHPF,t);
figure
plot(Fv, abs(FT_EEG)*2)
grid
Ax = gca;
Ax.XScale = 'log';
xlabel('Frequency (Hz)')
ylabel('Magnitude')
title('Fourier Transforms of EEG Data')
legend(VN, 'Location','best', 'NumColumns',2)
xlim([0 50])
figure
tiledlayout(5,4)
for k = 1:size(FT_EEG,2)
nexttile
semilogx(Fv, abs(FT_EEG(:,k))*2)
grid
title(VN{k})
ylim([0 30])
end
sgtitle('EEG Lead Fourier Transform Ensemble')
[Alpha,fdbp1] = bandpass(DataMtx, [8 13], Fs, 'ImpulseResponse','iir'); % Filter Leads Using Common EEG Band Definitions
[Beta,fdbp2] = bandpass(DataMtx, [13 30], Fs, 'ImpulseResponse','iir');
[Delta,fdbp3] = bandpass(DataMtx, [0.5 4], Fs, 'ImpulseResponse','iir');
[Theta,fdbp4] = bandpass(DataMtx, [4 7], Fs, 'ImpulseResponse','iir');
for k = 1:size(DataMtx,2) % Calculate Band Power In Each LEad
AlphaPwr(:,k) = trapz(t,Alpha(:,k).^2);
BetaPwr(:,k) = trapz(t,Beta(:,k).^2);
DeltaPwr(:,k) = trapz(t,Delta(:,k).^2);
ThetaPwr(:,k) = trapz(t,Theta(:,k).^2);
end
RowNames = {'Alpha Power','Beta Power','Delta Power','Theta Power'};
Band_Power_Mtx = cat(1,AlphaPwr,BetaPwr,DeltaPwr,ThetaPwr);
Band_PowerTable = array2table(Band_Power_Mtx, 'RowNames',RowNames, 'VariableNAmes',VN)
Band_PowerTable = 4x19 table
Fp1 Fp2 F3 F4 C3 C4 P3 P4 O1 O2 F7 F8 T3 T4 T5 T6 FZ CZ PZ _________ __________ __________ __________ _________ __________ __________ __________ __________ __________ __________ __________ __________ __________ __________ __________ __________ __________ __________ Alpha Power 20103 19077 20472 20520 20674 20447 20592 21416 22063 21567 24707 18285 20979 26023 19988 21327 19630 22244 21372 Beta Power 12984 12602 12891 12922 12754 12557 12867 13358 14070 13439 16171 13465 14006 18102 13447 13576 11973 13225 12944 Delta Power 1.966e+06 2.0312e+06 2.0404e+06 2.0374e+06 2.013e+06 1.9977e+06 2.0816e+06 2.0268e+06 2.0968e+06 2.0766e+06 2.2473e+06 2.0902e+06 1.9028e+06 2.1955e+06 1.9531e+06 2.1549e+06 2.0564e+06 2.0654e+06 2.1565e+06 Theta Power 69088 66680 67770 69080 70569 68215 67268 74189 74489 75103 79596 64526 68741 78694 66479 65991 66762 80357 69637
function [FTs1,Fv] = FFT1(s,t)
t = t(:);
L = numel(t);
if size(s,2) == L
s = s.';
end
Fs = 1/mean(diff(t));
Fn = Fs/2;
NFFT = 2^nextpow2(L);
FTs = fft((s - mean(s)) .* hann(L).*ones(1,size(s,2)), NFFT)/sum(hann(L));
Fv = Fs*(0:(NFFT/2))/NFFT;
% Fv = linspace(0, 1, NFFT/2+1)*Fn;
Iv = 1:numel(Fv);
FTs1 = FTs(Iv,:);
end
Make appropriate changes (including to the highpass stopband) to get the result you want.
EDIT — (25 Jun 2024 at 23:55)
Minor tweak, and typographcal error correction.
.

Iniciar sesión para comentar.

Más respuestas (1)

Diego Caro
Diego Caro el 26 de Jun. de 2024
If allowed by your instructors, you could use the EEGLAB toolbox, which is an environment bulit to analyze EEG signals. The GUI is very intuitive and easy to follow. To identify artifacts on your signal, you could run the ICA algorithm, which effectively classifies the orignal signal components into brain, muscle, eye, heart, line noise or other.
After performing the ICA algorith on an 8 electrode EEG signal, this is what comes out. In simple terms, it tells you the probability of each component to come from certain source.
As you can see, the second component has a 64.7% chance of being Line Noise. To identify wheter this is true or not, you could take a look at the extended analysis of this component.
From the power spectrum, it is obvious that this component is corrupted with Line Noise (because of the notorious spike at 60 Hz). After that, you could use the EEGLAB built-in functions, or desing your own to eliminate unwanted artifacts.
Regards,
Diego.

Categorías

Más información sobre EEG/MEG/ECoG en Help Center y File Exchange.

Productos


Versión

R2023b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by