বায়োমেডিক্যাল সিগন্যাল প্রসেসিং-এ DSP — ECG/EEG পরিচিতি
এই পাঠে যা শিখবেন
- ECG ও EEG সাধারণভাবে কী পরিমাপ করে, এবং কেন তারা DSP-এর দৃষ্টিতে সাধারণ সিগন্যালের মতোই
- একটি সিন্থেটিক, পর্যায়ক্রমিক "স্পাইক"-সদৃশ সিগন্যাল গসিয়ান পালস দিয়ে তৈরি করা
- ৫০ Hz পাওয়ার-লাইন ইন্টারফেয়ারেন্সের মতো একটি সাধারণ দূষণ সিগন্যালে যোগ করে DFT দিয়ে শনাক্ত করা
- একটি লো-পাস ফিল্টার দিয়ে ইন্টারফেয়ারেন্স কমিয়ে, সত্যিকারের before/after স্পেকট্রাম দিয়ে যাচাই করা
১ · ECG ও EEG সংক্ষেপে — এবং একটি গুরুত্বপূর্ণ সতর্কতা
ইলেকট্রোকার্ডিওগ্রাম (ECG/EKG) ত্বকে বসানো ইলেকট্রোড দিয়ে হৃদপিণ্ডের বৈদ্যুতিক কার্যকলাপ সময়ের সাথে রেকর্ড করে, আর ইলেকট্রোএনসেফালোগ্রাম (EEG) একইভাবে মাথার ত্বকে বসানো ইলেকট্রোড দিয়ে মস্তিষ্কের বৈদ্যুতিক কার্যকলাপ রেকর্ড করে। এই দুই ধরনের বায়োমেডিক্যাল সিগন্যালই — একটি ADC (M2-এ বিস্তারিত) দিয়ে ডিজিটাইজ করার পর — DSP-এর দৃষ্টিতে ঠিক এই কোর্সের বাকি সব সিগন্যালের মতোই: সংখ্যার একটি সিরিজ, যার ওপর ফিল্টারিং, স্পেকট্রাল অ্যানালাইসিস ইত্যাদি প্রয়োগ করা যায়।
হৃদপিণ্ডের বৈদ্যুতিক কার্যকলাপ, ইলেকট্রোড দিয়ে রেকর্ড করা সিগন্যাল।
মস্তিষ্কের বৈদ্যুতিক কার্যকলাপ, একইভাবে ইলেকট্রোড দিয়ে ডিজিটাইজ করা সিগন্যাল।
এই পাঠের সিগন্যাল কোডে জেনারেট করা একটি কাল্পনিক উদাহরণ, কোনো বাস্তব রোগীর ডেটা নয়।
২ · একটি সিন্থেটিক "হার্টবিট-সদৃশ" সিগন্যাল তৈরি
নিচের কোডে দুটি সরু গসিয়ান পালস ($\text{amp} \cdot e^{-(t-t_0)^2/(2\sigma^2)}$ আকারের) দিয়ে একটি পর্যায়ক্রমিক "স্পাইক"-সদৃশ সিগন্যাল তৈরি করা হচ্ছে — এই আকৃতিটি শুধু ধারণাগতভাবে হৃদস্পন্দনের একটি তীক্ষ্ণ, ক্ষণস্থায়ী স্পাইকের মতো দেখতে বেছে নেওয়া হয়েছে, বাস্তব ECG-এর জটিল P-QRS-T তরঙ্গ আকৃতির সঠিক প্রতিরূপ নয়। এতে একটি $50$ Hz সাইন-তরঙ্গ যোগ করা হচ্ছে যা বাস্তব-বিশ্বে বৈদ্যুতিক তারের চারপাশে সাধারণ একটি দূষণ — পাওয়ার-লাইন ইন্টারফেয়ারেন্স — প্রতিনিধিত্ব করে (অনেক দেশে এই ফ্রিকোয়েন্সি ৫০ Hz বা ৬০ Hz, মেইন বিদ্যুৎ সরবরাহের ফ্রিকোয়েন্সির সমান, একটি সুপরিচিত ব্যবহারিক তথ্য)।
import math, cmath
fs = 250.0 # স্যাম্পল রেট (Hz)
N = 300 # স্যাম্পল সংখ্যা (সময়কাল ১.২ সেকেন্ড)
dt = 1.0 / fs
def gauss_pulse(t, t0, sigma, amp=1.0):
return amp * math.exp(-((t - t0) ** 2) / (2 * sigma ** 2))
t0a, t0b = 0.15, 0.75 # দুটি সিন্থেটিক "স্পাইক"-এর কেন্দ্র (সেকেন্ড)
sigma = 0.02 # স্পাইকের প্রস্থ (সেকেন্ড)
# *** সম্পূর্ণ সিন্থেটিক, কাল্পনিক সিগন্যাল -- বাস্তব রোগীর ECG ডেটা নয় ***
clean = [gauss_pulse(n * dt, t0a, sigma) + gauss_pulse(n * dt, t0b, sigma) for n in range(N)]
f_line = 50.0 # পাওয়ার-লাইন ইন্টারফেয়ারেন্স ফ্রিকোয়েন্সি (Hz) -- সিন্থেটিক দূষণ
amp_line = 0.3
noisy = [clean[n] + amp_line * math.sin(2 * math.pi * f_line * n / fs) for n in range(N)]
def dft_magnitude(x):
n_samples = len(x)
mags = []
for k in range(n_samples):
s = 0 + 0j
for n in range(n_samples):
s += x[n] * cmath.exp(-2j * math.pi * k * n / n_samples)
mags.append(abs(s))
return mags
mags_clean = dft_magnitude(clean)
mags_noisy = dft_magnitude(noisy)
k50 = round(f_line / (fs / N))
print(f"fs = {fs} Hz, N = {N}, রেজোলিউশন = {fs/N:.4f} Hz/bin, ৫০ Hz-এর বিন k = {k50}")
print()
print(f"{'বিন k':>6} | {'ফ্রিকোয়েন্সি':>12} | {'clean |X[k]|':>13} | {'noisy |X[k]|':>13}")
for k in [2, 4, 6, 8, 10, k50]:
print(f"{k:>6} | {k*fs/N:>10.2f} Hz | {mags_clean[k]:>13.3f} | {mags_noisy[k]:>13.3f}")
clean সিগন্যালের (কোনো ইন্টারফেয়ারেন্স ছাড়া) ৫০ Hz বিনে ম্যাগনিটিউড
ঠিক ০.০, কিন্তু noisy সিগন্যালে (৫০ Hz দূষণ যোগ করার পরে) সেই একই বিনে
ম্যাগনিটিউড লাফিয়ে ৪৫.০০০-এ উঠেছে — একটি বিশাল, স্পষ্ট স্পাইক, যেখানে বাকি নিম্ন-ফ্রিকোয়েন্সি
বিনগুলো (২, ৪, ৬, ৮, ১০) দুই সংস্করণেই অভিন্ন। এটি নিশ্চিত করে যে ৫০ Hz স্পাইকটি সম্পূর্ণভাবে আমাদের ইচ্ছাকৃতভাবে
যোগ করা দূষণ থেকে এসেছে, মূল "স্পাইক"-সদৃশ সিগন্যাল থেকে নয়।
৩ · পাওয়ার-লাইন ইন্টারফেয়ারেন্স ফিল্টার করে সরানো
যেহেতু আমাদের সিন্থেটিক সিগন্যালের মূল বিষয়বস্তু নিম্ন ফ্রিকোয়েন্সিতে কেন্দ্রীভূত (উপরের টেবিলে দেখা বিন ২-১০, অর্থাৎ ~১.৭-৮.৩ Hz-এ), আর ইন্টারফেয়ারেন্স তার চেয়ে অনেক ওপরে (৫০ Hz), একটি M7-স্টাইল লো-পাস ফিল্টার (কাট-অফ ৪০ Hz, ইন্টারফেয়ারেন্সের নিচে কিন্তু সিগন্যালের বেশিরভাগ শক্তির ওপরে) দিয়ে ইন্টারফেয়ারেন্স দমন করা সম্ভব হওয়া উচিত। নিচে সেই windowed-sinc ফিল্টার সত্যিই প্রয়োগ করে before/after তুলনা করা হচ্ছে।
fc = 40.0 # লো-পাস কাট-অফ (Hz) -- ৫০ Hz ইন্টারফেয়ারেন্সের নিচে
M = 20
taps = 2 * M + 1
fc_norm = fc / fs
h = []
for i in range(-M, M + 1):
if i == 0:
hv = 2 * fc_norm
else:
hv = math.sin(2 * math.pi * fc_norm * i) / (math.pi * i)
w = 0.54 - 0.46 * math.cos(2 * math.pi * (i + M) / (taps - 1)) # Hamming window
h.append(hv * w)
dc_gain = sum(h)
h = [c / dc_gain for c in h]
def convolve(x, h):
y = [0.0] * (len(x) + len(h) - 1)
for n in range(len(x)):
for k in range(len(h)):
y[n + k] += x[n] * h[k]
return y
filtered_full = convolve(noisy, h)
filtered = filtered_full[M:M + N] # ফিল্টারের M-স্যাম্পল বিলম্ব বাদ দিয়ে align
mags_filtered = dft_magnitude(filtered)
print(f"ফিল্টার: {taps}-ট্যাপ windowed-sinc lowpass, কাট-অফ = {fc} Hz")
print()
print(f"{'বিন k':>6} | {'ফ্রিকোয়েন্সি':>12} | {'ফিল্টারের আগে':>14} | {'ফিল্টারের পরে':>14}")
for k in [2, 4, 6, 8, 10, k50]:
print(f"{k:>6} | {k*fs/N:>10.2f} Hz | {mags_noisy[k]:>14.3f} | {mags_filtered[k]:>14.3f}")
reduction = mags_filtered[k50] / mags_noisy[k50]
print()
print(f"৫০ Hz-এ ম্যাগনিটিউড: {mags_noisy[k50]:.3f} -> {mags_filtered[k50]:.3f} "
f"(অনুপাত {reduction:.4f}, অর্থাৎ {(1-reduction)*100:.1f}% হ্রাস)")
n_peak = round(t0a * fs)
print(f"প্রথম স্পাইকের কাছাকাছি (n={n_peak}) মান -- clean: {clean[n_peak]:.4f}, "
f"noisy: {noisy[n_peak]:.4f}, filtered: {filtered[n_peak]:.4f}")
বাস্তব ECG/EEG অধিগ্রহণ সিস্টেমেও একই সমস্যা সাধারণ — পাওয়ার-লাইন ইন্টারফেয়ারেন্স (৫০/৬০ Hz, স্থানীয় মেইন ফ্রিকোয়েন্সি অনুযায়ী) মোকাবেলায় প্রায়ই একটি সংকীর্ণ নচ ফিল্টার (ঠিক সেই একটি ফ্রিকোয়েন্সি দমন করে, বাকি সব রাখে) ব্যবহার হয়, এই পাঠের বিস্তৃত লো-পাস ফিল্টারের চেয়ে বেশি নির্দিষ্ট। ECG-তে "R-পিক" (হৃদস্পন্দনের প্রতিটি চক্রের সবচেয়ে তীক্ষ্ণ অংশ) শনাক্তকরণ ধারণাগতভাবে M11/L50-এর ম্যাচড-ফিল্টার-স্টাইল পিক-ডিটেকশনের সাথে সম্পর্কিত। এগুলো সুপরিচিত, সাধারণ বায়োমেডিক্যাল-ইঞ্জিনিয়ারিং ধারণা — কিন্তু এই পাঠে দেখানো নির্দিষ্ট সংখ্যাগুলো (৪৫.০, ৯৮.৮% ইত্যাদি) শুধুমাত্র আমাদের সিন্থেটিক উদাহরণের নিজস্ব ফলাফল, কোনো বাস্তব ক্লিনিক্যাল সিস্টেমের পারফরম্যান্স প্রতিনিধিত্ব করে না।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
clean সিগন্যালে ৫০ Hz বিনে ম্যাগনিটিউড ঠিক ০.০ — এটি কি প্রত্যাশিত, নাকি কাকতালীয়?
এটি সম্পূর্ণ প্রত্যাশিত। clean সিগন্যাল শুধু দুটি গসিয়ান পালসের যোগফল — এতে কোনো ৫০ Hz
সাইন-তরঙ্গ উপাদান নেই। একটি গসিয়ান পালসের DFT বিস্তৃত (broadband, অনেক ফ্রিকোয়েন্সি জুড়ে ছড়ানো), কিন্তু
এখানে বিশেষভাবে ঠিক ৫০ Hz বিনে ম্যাগনিটিউড ০ হওয়ার নির্দিষ্ট কারণ নেই এমন ভাবার দরকার নেই — এটি এই
নির্দিষ্ট পালস-বিন্যাস ও সময়কালের জন্য কোডের সত্যিকারের গণনার ফলাফল, প্রতিটি রান-এ পুনরুৎপাদনযোগ্য।
প্র ০২ ফিল্টার করার পরেও ৫০ Hz বিনে ম্যাগনিটিউড পুরোপুরি ০ হয়নি (০.৫২৫ রয়ে গেছে) — এর মানে কি ফিল্টারটি অসম্পূর্ণ বা ভুল?
না, এটি প্রত্যাশিত এবং M7-এর মৌলিক ধারণার সাথে সামঞ্জস্যপূর্ণ। একটি finite-length লো-পাস ফিল্টার কাট-অফের ওপরে ফ্রিকোয়েন্সি সম্পূর্ণ শূন্যে নামায় না — বরং তীব্রভাবে দমন করে (এখানে ৯৮.৮%)। আরও ট্যাপ ব্যবহার করলে (M7/L34-এর ট্রেড-অফ আলোচনার মতো) দমন আরও তীক্ষ্ণ করা সম্ভব, কিন্তু একটি সংকীর্ণ নচ ফিল্টার এক্ষেত্রে সাধারণত বেশি কার্যকর, কারণ এটি বিশেষভাবে শুধু ৫০ Hz-কে লক্ষ্য করে, আশেপাশের নিম্ন-ফ্রিকোয়েন্সি বিষয়বস্তুতে হাত না দিয়ে।
প্র ০৩ এই পাঠে বারবার বলা হয়েছে সিগন্যালটি সিন্থেটিক। বাস্তব ক্লিনিক্যাল ECG/EEG ডেটার সাথে কাজ করার সময় একজন DSP ইঞ্জিনিয়ারের কী অতিরিক্ত সতর্কতা নেওয়া উচিত বলে মনে হয়?
বাস্তব বায়োমেডিক্যাল সিগন্যালে অনেক বেশি জটিল, অপ্রত্যাশিত গঠন থাকে (রোগী-ভেদে পরিবর্তনশীল তরঙ্গ আকৃতি, একাধিক ধরনের নয়েজ, মুভমেন্ট আর্টিফ্যাক্ট), এবং কোনো ফিল্টারিং সিদ্ধান্ত ভুল হলে গুরুত্বপূর্ণ ক্লিনিক্যাল তথ্য হারিয়ে যেতে পারে বা মিথ্যা প্যাটার্ন তৈরি হতে পারে। তাই বাস্তব সিস্টেমে ফিল্টার ডিজাইন, যাচাইকরণ, এবং ফলাফলের ব্যাখ্যা — এই পুরো প্রক্রিয়াটি চিকিৎসা-বিশেষজ্ঞ ও নিয়ন্ত্রক মান (regulatory standards) মেনে করা হয়, যা এই পাঠের সরল, স্ব-শিক্ষামূলক ডেমোর সুযোগের বাইরে।
অনুশীলন
-
চিন্তা করুন: যদি ইন্টারফেয়ারেন্স ফ্রিকোয়েন্সি $f_{line} = 50$ Hz-এর বদলে $60$ Hz
(অনেক দেশের মেইন ফ্রিকোয়েন্সি) হতো, রেজোলিউশন $fs/N = 0.8333$ Hz/bin অপরিবর্তিত রেখে, ইন্টারফেয়ারেন্স
কোন বিনে ($k$) দেখা যেত বলে মনে হয়?
$k = 60 / 0.8333 \approx 72$ (নিকটতম পূর্ণসংখ্যা বিন)। যেহেতু আমাদের ফিল্টারের কাট-অফ ৪০ Hz-এই থাকত, ৬০ Hz ইন্টারফেয়ারেন্সও একইভাবে দমিত হতো — বরং কাট-অফ থেকে আরও দূরে থাকায় সম্ভবত আরও বেশি দমিত হতো, কারণ লো-পাস ফিল্টারের দমন সাধারণত কাট-অফ থেকে ফ্রিকোয়েন্সি যত দূরে যায় তত বাড়ে।
-
পরীক্ষা করুন: উপরের প্রথম কোড সেলে
f_line = 50.0-কেf_line = 60.0-এ বদলে (দুটো কোড সেলই আবার Run করে) আপনার হিসেব যাচাই করুন — নতুনk50(এখন প্রকৃতপক্ষে ৬০ Hz-এর বিন) মান এবং ফিল্টারের আগে/পরের ম্যাগনিটিউড লক্ষ করুন।রান করলে
k50 = round(60.0/0.8333) = 72দেখাবে, আর সেই বিনে ফিল্টারের আগে বড় স্পাইক (প্রায় ৪৫.০-এর কাছাকাছি, ইন্টারফেয়ারেন্স প্রশস্ততা অপরিবর্তিত থাকায়) ও ফিল্টারের পরে সেটি আরও বেশি দমিত হবে (৪০ Hz কাট-অফ থেকে ৫০ Hz-এর চেয়ে ৬০ Hz বেশি দূরে বলে) — নিশ্চিত করে যে লো-পাস ফিল্টারের দমন-ক্ষমতা কাট-অফ থেকে ফ্রিকোয়েন্সির দূরত্বের সাথে বাড়ে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ স্যাম্পলিং, কনভোলিউশন, Z-ট্রান্সফর্ম, DTFT/DFT, FFT, FIR/IIR ফিল্টার ডিজাইন, মাল্টিরেট প্রসেসিং, স্পেকট্রাল এস্টিমেশন, র্যান্ডম সিগন্যাল প্রসেসিং, বাস্তব প্রয়োগ ও ক্যাপস্টোন।
- Numerical Methods কোর্স সহোদর কোর্স সাধারণ সংখ্যাগত অ্যালগরিদম, রুট-ফাইন্ডিং, ইন্টিগ্রেশন ও ODE সলভিং-এর গভীর কভারেজ — এই কোর্স সেই একই "হাতে-লেখা" দর্শনের উপর সিগন্যাল-প্রসেসিং-নির্দিষ্ট দিকটি যোগ করে।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, Machine Learning, Deep Learning, System Design, Cybersecurity, Cloud Computing & DevOps, এবং আরও অনেক কোর্স — সব এক জায়গায়।