পাঠ ৫৭ · ৫৭-এর মধ্যে · মডিউল ১৩
Home / Courses / Digital Signal Processing / ক্যাপস্টোন

ক্যাপস্টোন — একটি সম্পূর্ণ ডিজিটাল ফিল্টার সিস্টেম এন্ড-টু-এন্ড ডিজাইন ও বিশ্লেষণ

Capstone: designing & analyzing a complete digital filter system end-to-end
১৮ মিনিট পড়া উন্নত · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কোর্সের একাধিক মডিউল (সিগন্যাল জেনারেশন M1, DFT M5, FIR ফিল্টার ডিজাইন ও প্রয়োগ M7, SNR M11) একটি একক, বাস্তব-সদৃশ DSP ওয়ার্কফ্লোতে চেইন করা
  • একটি ফিল্টারের প্রকৃত প্রভাব আগে/পরে স্পেকট্রাল তুলনার মাধ্যমে সত্যিকারের যাচাই করা
  • M11/L49-এর SNR সূত্র পুনর্ব্যবহার করে একটি প্রকৃত, গণনা করা SNR-উন্নতির সংখ্যা বের করা
  • correlation ও MSE — দুটো ভিন্ন মেট্রিক দিয়ে একটি ফিল্টার করা সিগন্যাল একটি ক্লিন রেফারেন্সের কতটা কাছাকাছি তা পরিমাপ করা
  • কীভাবে একটি চূড়ান্ত সিদ্ধান্ত সম্পূর্ণভাবে আগের ধাপের প্রকৃত গণনা করা প্রমাণ থেকে (হার্ডকোড না করে) তৈরি করা যায়

১ · ক্যাপস্টোন পরিকল্পনা — সেন্সর সিগন্যাল পরিষ্কার করা

L56-এ আমরা একটি সম্পূর্ণ হাইপোথেটিক্যাল ও ইলাস্ট্রেটিভ সিনারিও বেছে নিয়েছিলাম — একটি মেশিন-ভাইব্রেশন সেন্সর যার রিডিং-এ ৪০ Hz কাঙ্ক্ষিত ঘূর্ণন-টোনের সাথে ১০০ Hz বৈদ্যুতিক ইন্টারফেয়ারেন্স ও ব্রডব্যান্ড নয়েজ মিশে আছে — এবং একটি ডিজাইন চূড়ান্ত করেছিলাম: fs = 256 Hz, N = 128 স্যাম্পল, এবং fc = 60 Hz কাটঅফের M = 25-ট্যাপ windowed-sinc FIR লো-পাস ফিল্টার। এই ক্যাপস্টোনে আমরা সেই ডিজাইন এন্ড-টু-এন্ড বাস্তবায়ন করব — শুধু ফিল্টার প্রয়োগ করে থামব না, বরং প্রশ্ন করব: এটি সত্যিই কতটা কাজ করেছে, ইন্টারফেয়ারেন্স সত্যিই কতটা কমেছে, কাঙ্ক্ষিত টোন সত্যিই কতটা অক্ষত আছে, এবং সামগ্রিকভাবে সিগন্যাল-মানের কতটা উন্নতি হয়েছে। কোর্স জুড়ে আমরা সিগন্যাল জেনারেশন (M1), DFT/FFT (M5/M6), FIR ফিল্টার ডিজাইন (M7), ও SNR/পরিসংখ্যান (M11) একটি একটি করে আলাদাভাবে শিখেছি — এই শেষ পাঠে সেগুলো জোড়া লাগিয়ে একটি সম্পূর্ণ, প্রমাণ-ভিত্তিক DSP বিশ্লেষণ চালানো হবে।

ধাপ ১ · সিগন্যাল সেটআপ ধাপ ২ · DFT সনাক্তকরণ ধাপ ৩ · FIR ডিজাইন ও প্রয়োগ ধাপ ৪ · আগে/পরে স্পেকট্রাল তুলনা ধাপ ৫ · SNR/MSE/correlation ধাপ ৬ চূড়ান্ত সারাংশ ও সিদ্ধান্ত
প্রতিটি ধাপ একটি প্রকৃত, গণনা করা সংখ্যা তৈরি করে; ধাপ ৬ সেগুলোকে একত্র করে একটি প্রমাণ-ভিত্তিক সিদ্ধান্তে পৌঁছায়।
নিচের কোড সেলগুলো একটি একক Pyodide সেশনে ক্রমানুসারে চলে — একটি সেলে সংজ্ঞায়িত ভ্যারিয়েবল/ফাংশন পরের সেলে ব্যবহার করা যায়, ঠিক যেমন একটি Jupyter নোটবুকে হয়। তাই সঠিক ফলাফল পেতে সেলগুলো উপর থেকে নিচে, ক্রমানুসারে Run করুন।

২ · ধাপ ১ — নয়েজ-সহ টেস্ট সিগন্যাল সেটআপ

আমাদের সিন্থেটিক সেন্সর সিগন্যাল তিনটি উপাদানের যোগফল:

$$x[n] = \underbrace{\sin(2\pi \cdot 40 \cdot n/f_s)}_{\text{কাঙ্ক্ষিত}} + \underbrace{0.6\sin(2\pi \cdot 100 \cdot n/f_s + 0.7)}_{\text{ইন্টারফেয়ারেন্স}} + \underbrace{\mathcal{N}(0, 0.3^2)}_{\text{নয়েজ}}$$

clean[n] (শুধু কাঙ্ক্ষিত অংশ) আলাদাভাবে সংরক্ষণ করা হচ্ছে — এটি একটি "গ্রাউন্ড ট্রুথ" রেফারেন্স, যা বাস্তব জীবনে সেন্সর থেকে সরাসরি পাওয়া যেত না, কিন্তু যেহেতু এটি সিন্থেটিক ডেটা, আমরা এটি জানি এবং ধাপ ৫-এ মেট্রিক্স গণনার জন্য ব্যবহার করব।

Python
# ========== ধাপ ১: নয়েজ-সহ টেস্ট সিগন্যাল সেটআপ (হাইপোথেটিক্যাল ভাইব্রেশন সেন্সর) ==========
import math, cmath, random

FS = 256.0        # স্যাম্পল রেট, Hz (L56-এর ডিজাইন)
N = 128           # স্যাম্পল সংখ্যা
F_DESIRED = 40.0  # কাঙ্ক্ষিত ভাইব্রেশন টোন, Hz
F_INTERF = 100.0  # ইন্টারফেয়ারেন্স টোন, Hz
A_DESIRED = 1.0
A_INTERF = 0.6
NOISE_SD = 0.3

random.seed(42)
clean = [A_DESIRED * math.sin(2 * math.pi * F_DESIRED * n / FS) for n in range(N)]
interference = [A_INTERF * math.sin(2 * math.pi * F_INTERF * n / FS + 0.7) for n in range(N)]
noise = [random.gauss(0.0, NOISE_SD) for _ in range(N)]
noisy = [clean[n] + interference[n] + noise[n] for n in range(N)]

print(f"fs={FS} Hz, N={N} স্যাম্পল, সময়কাল={N/FS:.3f} সেকেন্ড, বিন-রেজোলিউশন={FS/N:.2f} Hz")
print(f"কাঙ্ক্ষিত: {A_DESIRED}*sin(2*pi*{F_DESIRED}*t), ইন্টারফেয়ারেন্স: {A_INTERF}*sin(2*pi*{F_INTERF}*t+0.7)")
print(f"নয়েজ: gauss(0,{NOISE_SD}), seed=42\n")
for n in range(5):
    print(f" n={n:3d}  clean={clean[n]:8.4f}  interference={interference[n]:8.4f}  noise={noise[n]:8.4f}  noisy={noisy[n]:8.4f}")

    
গণনা করা ফলাফল: fs=256 Hz, N=128 স্যাম্পল, সময়কাল ০.৫০০ সেকেন্ড, বিন-রেজোলিউশন ২.০০ Hz (L56-এর পরিকল্পনা অনুযায়ী)। প্রথম কয়েকটি স্যাম্পলেই দেখা যাচ্ছে তিনটি উপাদান মিলে noisy[n] কতটা এলোমেলো দেখাচ্ছে — যেমন n=4-এ clean= -0.7071 হলেও ইন্টারফেয়ারেন্স ও নয়েজ যোগ হয়ে noisy=-1.2781 হয়ে গেছে। খালি চোখে টাইম-ডোমেইনে ৪০ Hz টোনটি চেনা প্রায় অসম্ভব — এটাই পরের ধাপে ফ্রিকোয়েন্সি-ডোমেইনে যাওয়ার কারণ।

৩ · ধাপ ২ — DFT দিয়ে স্পেকট্রাল সনাক্তকরণ

M5/L22-এর সরাসরি DFT সমষ্টি সূত্র পুনরায় লিখে সিগন্যালের স্পেকট্রাম বের করা হচ্ছে:

$$X[k] = \sum_{n=0}^{N-1} x[n]\, e^{-j 2\pi k n / N}$$

L56-এ আমরা আগে থেকেই গণনা করেছিলাম কাঙ্ক্ষিত বিন হবে ২০ (৪০ Hz) আর ইন্টারফেয়ারেন্স বিন হবে ৫০ (১০০ Hz)। নিচের সেল সেই পূর্বাভাস সত্যিকারের DFT আউটপুট দিয়ে যাচাই করে।

Python
# ========== ধাপ ২: হাতে-লেখা DFT দিয়ে স্পেকট্রাল সনাক্তকরণ ==========
def dft(x):
    N = len(x)
    X = []
    for k in range(N):
        s = 0j
        for n in range(N):
            s += x[n] * cmath.exp(-2j * math.pi * k * n / N)
        X.append(s)
    return X

X_noisy = dft(noisy)
mags_noisy = [abs(v) for v in X_noisy]
bin_desired = round(F_DESIRED * N / FS)
bin_interf = round(F_INTERF * N / FS)

print(f"bin_desired (40Hz) = {bin_desired}, bin_interf (100Hz) = {bin_interf}\n")
print(f"{'bin':>4} | {'freq(Hz)':>8} | {'|X|':>10}")
top = sorted(range(1, N // 2), key=lambda k: -mags_noisy[k])[:6]
for k in sorted(top):
    print(f"{k:4d} | {k*FS/N:8.1f} | {mags_noisy[k]:10.4f}")

print(f"\n|X[{bin_desired}]| (40Hz, ফিল্টারের আগে) = {mags_noisy[bin_desired]:.4f}")
print(f"|X[{bin_interf}]| (100Hz, ফিল্টারের আগে) = {mags_noisy[bin_interf]:.4f}")

    
গণনা করা ফলাফল সবচেয়ে বড় ৬টি ম্যাগনিটিউড বিনের মধ্যে স্পষ্টভাবে দুটো প্রকৃত টোন দেখাচ্ছে: বিন ২০ (৪০ Hz)-এ |X| = ৬৪.৭৮৩৯ এবং বিন ৫০ (১০০ Hz)-এ |X| = ৩৪.৭১০১ — বাকি বিনগুলো (২৬, ৩৮, ৬৮, ১২০ Hz) নয়েজ-ফ্লোরের কাছাকাছি, মাত্র ≈৫ ম্যাগনিটিউডে। অর্থাৎ L56-এর পূর্বাভাস সঠিক ছিল, এবং এই দুটো বিনই বাকি স্পেকট্রাম থেকে স্পষ্টভাবে আলাদা — একটি কাঙ্ক্ষিত (২০), একটি অবাঞ্ছিত (৫০)।

৪ · ধাপ ৩ — FIR ফিল্টার ডিজাইন ও প্রয়োগ

L56-এ ডিজাইন করা windowed-sinc FIR লো-পাস (fc=60 Hz, M=25 ট্যাপ, Hamming window) এখানে পুনরায় লেখা হয়েছে এবং M7/L33-এর ডাইরেক্ট-ফর্ম সেম্পল-বাই-সেম্পল পদ্ধতিতে সিগন্যালে প্রয়োগ করা হয়েছে: y[n] = Σ h[k]·x[n−k]। যেহেতু এটি একটি M=25-ট্যাপ লিনিয়ার-ফেজ ফিল্টার, এর একটি সুনির্দিষ্ট group delay আছে — (M−1)/2 = ১২ স্যাম্পল — যা ধাপ ৫-এ ক্লিন রেফারেন্সের সাথে তুলনা করার সময় বিবেচনায় নেওয়া হবে।

Python
# ========== ধাপ ৩: FIR ফিল্টার ডিজাইন (L56-এর টুলকিট) + প্রয়োগ ==========
def sinc(x):
    if x == 0.0:
        return 1.0
    return math.sin(math.pi * x) / (math.pi * x)

def design_fir_lowpass(fc, fs, M):
    taps = []
    alpha = (M - 1) / 2
    for n in range(M):
        h_ideal = (2 * fc / fs) * sinc((2 * fc / fs) * (n - alpha))
        w = 0.54 - 0.46 * math.cos(2 * math.pi * n / (M - 1))  # Hamming window
        taps.append(h_ideal * w)
    s = sum(taps)
    return [t / s for t in taps]

FC = 60.0
M_TAPS = 25
h = design_fir_lowpass(FC, FS, M_TAPS)
DELAY = (M_TAPS - 1) // 2

def apply_fir(x, h):
    N = len(x)
    M = len(h)
    y = []
    for n in range(N):
        acc = 0.0
        for k in range(M):
            if n - k >= 0:
                acc += h[k] * x[n - k]
        y.append(acc)
    return y

filtered = apply_fir(noisy, h)

print(f"FIR লো-পাস: fc={FC} Hz, M={M_TAPS} ট্যাপ (Hamming window), group delay={DELAY} স্যাম্পল")
print("প্রথম ৫টি ট্যাপ:", [round(v, 5) for v in h[:5]])
print(f"ট্যাপের সমষ্টি (DC গেইন চেক) = {sum(h):.6f}\n")
for n in range(10, 16):
    print(f" n={n:3d}  noisy={noisy[n]:8.4f}  filtered={filtered[n]:8.4f}")

    
গণনা করা ফলাফল: প্রথম ৫টি ট্যাপ [-0.00197, -0.00131, 0.00376, 0.00483, -0.00874], ট্যাপের সমষ্টি ১.০০০০০০ (DC গেইন ঠিক আছে, L56-এর যাচাইয়ের সাথে সামঞ্জস্যপূর্ণ)। n=10 থেকে n=15 পর্যন্ত noisy ও filtered মান পাশাপাশি দেখলেই বোঝা যায় ফিল্টার দ্রুত ওঠানামা করা মানগুলোকে (যেমন n=12-এ noisy=-1.0820) মসৃণ করে দিচ্ছে (filtered=0.3035) — এটাই লো-পাস আচরণের প্রথম ইঙ্গিত, যা পরের ধাপে স্পেকট্রামে সরাসরি যাচাই করা হবে।

৫ · ধাপ ৪ — আগে/পরে স্পেকট্রাল তুলনা

এখন ফিল্টার করা সিগন্যালের DFT (ধাপ ২-এর dft() ফাংশন পুনর্ব্যবহার করে) বের করে ফিল্টারিং-এর আগে ও পরে দুই বিনের ম্যাগনিটিউড সরাসরি তুলনা করা হচ্ছে — এটাই একটি ফিল্টার সত্যিই কাজ করছে কি না তা যাচাইয়ের সবচেয়ে সরাসরি উপায়।

Python
# ========== ধাপ ৪: আগে/পরে স্পেকট্রাল তুলনা ==========
X_filtered = dft(filtered)
mags_filtered = [abs(v) for v in X_filtered]

print(f"{'freq':>6} | {'|X| আগে':>11} | {'|X| পরে':>10} | {'পরিবর্তন':>8}")
print(f"{F_DESIRED:6.1f} | {mags_noisy[bin_desired]:11.4f} | {mags_filtered[bin_desired]:10.4f} | "
      f"{(mags_filtered[bin_desired]/mags_noisy[bin_desired]-1)*100:7.1f}%")
print(f"{F_INTERF:6.1f} | {mags_noisy[bin_interf]:11.4f} | {mags_filtered[bin_interf]:10.4f} | "
      f"{(mags_filtered[bin_interf]/mags_noisy[bin_interf]-1)*100:7.1f}%")

atten_ratio = mags_noisy[bin_interf] / mags_filtered[bin_interf]
preserve_ratio = mags_filtered[bin_desired] / mags_noisy[bin_desired]
print(f"\nইন্টারফেয়ারেন্স অ্যাটেনুয়েশন ফ্যাক্টর = {atten_ratio:.2f}x")
print(f"কাঙ্ক্ষিত-টোন প্রিজার্ভেশন রেশিও = {preserve_ratio:.4f} ({preserve_ratio*100:.1f}%)")

    
গণনা করা ফলাফল: ৪০ Hz-এ ম্যাগনিটিউড ৬৪.৭৮৩৯ থেকে ৫৮.৯১৭৬-এ নেমেছে — মাত্র -৯.১% পরিবর্তন, অর্থাৎ কাঙ্ক্ষিত টোন প্রায় অক্ষত। কিন্তু ১০০ Hz-এ ম্যাগনিটিউড ৩৪.৭১০১ থেকে ০.২১৯৩-এ নেমে গেছে — -৯৯.৪%, একটি নাটকীয় পতন। এই থেকে ইন্টারফেয়ারেন্স অ্যাটেনুয়েশন ফ্যাক্টর দাঁড়ায় ১৫৮.২৭x, আর কাঙ্ক্ষিত-টোন প্রিজার্ভেশন রেশিও ০.৯০৯৪ (৯০.৯%)। এটি সরাসরি L56-এর ফ্রিকোয়েন্সি-রেসপন্স পূর্বাভাসের (|H(40)| ≈ ১.০, |H(100)| ≈ ০.০০১২) সাথে সামঞ্জস্যপূর্ণ — ডিজাইন যেমন প্রতিশ্রুতি দিয়েছিল, বাস্তব সিগন্যালেও ঠিক তেমনটাই ঘটেছে।

৬ · ধাপ ৫ — পরিমাণগত গুণমান মেট্রিক্স (SNR, MSE, correlation)

M11/L49-এর SNR সূত্র (10·log₁₀(P_signal/P_noise)) পুনর্ব্যবহার করে ফিল্টারিং-এর আগে ও পরে SNR গণনা করা হচ্ছে। ফিল্টারিং-এর আগে, "নয়েজ" মানে ইন্টারফেয়ারেন্স + র‍্যান্ডম নয়েজ (দুটোই কাঙ্ক্ষিত সিগন্যাল নয়)। ফিল্টারিং-এর পরে SNR গণনা করতে filtered-কে ক্লিন রেফারেন্সের সাথে তুলনা করতে হয় — কিন্তু ধাপ ৩-এ উল্লেখ করা group delay (১২ স্যাম্পল)-এর কারণে filtered[n] আসলে clean[n−12]-এর সাথে মেলে, clean[n]-এর সাথে নয়। তাই তুলনা করার আগে এই শিফট ঠিক করে নেওয়া হচ্ছে (n = ১২ ... ১২৭ রেঞ্জে filtered[n] বনাম clean[n−12]) — এটি একটি বাস্তব, সঠিকভাবে করা লিনিয়ার-ফেজ ফিল্টারের ফলাফল তুলনার আবশ্যিক ধাপ।

Python
# ========== ধাপ ৫: SNR, MSE ও correlation মেট্রিক্স (group-delay সংশোধন সহ) ==========
def signal_power(x):
    return sum(v * v for v in x) / len(x)

# SNR (আগে): কাঙ্ক্ষিত সিগন্যাল বনাম (ইন্টারফেয়ারেন্স + নয়েজ)
noise_component_before = [interference[n] + noise[n] for n in range(N)]
P_signal = signal_power(clean)
P_noise_before = signal_power(noise_component_before)
SNR_before_db = 10 * math.log10(P_signal / P_noise_before)

# group-delay সংশোধন করে ফিল্টার করা সিগন্যাল ক্লিন রেফারেন্সের সাথে অ্যালাইন করা
valid_n = range(DELAY, N)
clean_aligned = [clean[n - DELAY] for n in valid_n]
filtered_aligned = [filtered[n] for n in valid_n]
noisy_aligned = [noisy[n] for n in valid_n]

residual_after = [filtered_aligned[i] - clean_aligned[i] for i in range(len(clean_aligned))]
P_signal_aligned = signal_power(clean_aligned)
P_noise_after = signal_power(residual_after)
SNR_after_db = 10 * math.log10(P_signal_aligned / P_noise_after)
SNR_improvement = SNR_after_db - SNR_before_db

mse_before = sum((noisy_aligned[i] - clean_aligned[i]) ** 2 for i in range(len(clean_aligned))) / len(clean_aligned)
mse_after = sum((filtered_aligned[i] - clean_aligned[i]) ** 2 for i in range(len(clean_aligned))) / len(clean_aligned)

def correlation(a, b):
    ma = sum(a) / len(a)
    mb = sum(b) / len(b)
    num = sum((a[i] - ma) * (b[i] - mb) for i in range(len(a)))
    da = math.sqrt(sum((v - ma) ** 2 for v in a))
    db = math.sqrt(sum((v - mb) ** 2 for v in b))
    return num / (da * db)

corr_before = correlation(noisy_aligned, clean_aligned)
corr_after = correlation(filtered_aligned, clean_aligned)

print(f"SNR (আগে)  = {SNR_before_db:.3f} dB")
print(f"SNR (পরে)  = {SNR_after_db:.3f} dB")
print(f"SNR উন্নতি = {SNR_improvement:.3f} dB\n")
print(f"MSE বনাম clean (আগে) = {mse_before:.5f}")
print(f"MSE বনাম clean (পরে) = {mse_after:.5f}")
print(f"MSE হ্রাস ফ্যাক্টর = {mse_before/mse_after:.2f}x\n")
print(f"correlation বনাম clean (আগে) = {corr_before:.4f}")
print(f"correlation বনাম clean (পরে) = {corr_after:.4f}")

    
গণনা করা ফলাফল: SNR ফিল্টারিং-এর আগে ৩.৫৬৯ dB, পরে ১১.২৯৭ dB — +৭.৭২৮ dB উন্নতি। MSE (ক্লিন রেফারেন্সের বিপরীতে) ০.৫৩৫৯৮ থেকে ০.০৩৬৮৩-এ নেমেছে, অর্থাৎ ≈১৪.৫৫x হ্রাস। correlation ০.৫৭৮৯ থেকে ০.৯৬৬৯-এ উঠেছে — অর্থাৎ ফিল্টার করা সিগন্যাল এখন ক্লিন কাঙ্ক্ষিত টোনের প্রায় নিখুঁত অনুরূপ (১.০-এর খুব কাছাকাছি), যেখানে ফিল্টারের আগে সিগন্যাল ক্লিন টোনের সাথে মাঝারি মাত্রায় (০.৫৮) সম্পর্কিত ছিল। তিনটি স্বাধীন মেট্রিক (SNR, MSE, correlation) — সবগুলোই একই দিকে ইঙ্গিত করছে: সিগন্যাল উল্লেখযোগ্যভাবে পরিষ্কার হয়েছে।

৭ · ধাপ ৬ — চূড়ান্ত সারাংশ ও সিদ্ধান্ত

শেষ সেলটি উপরের পাঁচটি ধাপের প্রকৃত গণনা করা ফলাফল একত্র করে একটি একক, প্রমাণ-ভিত্তিক সিদ্ধান্তে পৌঁছায় — কোনো হার্ডকোড করা উপসংহার নয়, বরং আগের সেলের প্রকৃত ভ্যারিয়েবল থেকে সরাসরি গণনা করা।

Python
# ========== ধাপ ৬: চূড়ান্ত সারাংশ ও সিদ্ধান্ত -- ধাপ ১-৫-এর প্রকৃত ফলাফল থেকে ==========
print("=" * 62)
print("চূড়ান্ত মূল্যায়ন সারাংশ -- ভাইব্রেশন-সেন্সর সিগন্যাল ক্লিনআপ (হাইপোথেটিক্যাল)")
print("=" * 62)
print(f"১. স্পেকট্রাল সনাক্তকরণ: কাঙ্ক্ষিত বিন {bin_desired} (|X|={mags_noisy[bin_desired]:.2f}), "
      f"ইন্টারফেয়ারেন্স বিন {bin_interf} (|X|={mags_noisy[bin_interf]:.2f})")
print(f"২. আগে/পরে স্পেকট্রাল তুলনা: ইন্টারফেয়ারেন্স {atten_ratio:.1f}x কমেছে, "
      f"কাঙ্ক্ষিত টোন {preserve_ratio*100:.1f}% সংরক্ষিত")
print(f"৩. SNR: {SNR_before_db:.2f} dB -> {SNR_after_db:.2f} dB (উন্নতি {SNR_improvement:.2f} dB)")
print(f"৪. MSE বনাম clean: {mse_before:.4f} -> {mse_after:.4f} ({mse_before/mse_after:.1f}x হ্রাস)")
print(f"৫. correlation বনাম clean: {corr_before:.4f} -> {corr_after:.4f}")

if atten_ratio >= 20.0 and preserve_ratio >= 0.80:
    filter_verdict = "ফিল্টার তার ডিজাইন-লক্ষ্য পূরণ করেছে: ইন্টারফেয়ারেন্স দৃঢ়ভাবে দমন করেছে অথচ কাঙ্ক্ষিত টোন সংরক্ষণ করেছে"
else:
    filter_verdict = "ফিল্টার আংশিকভাবে কাজ করেছে, তবে ডিজাইন পুনর্বিবেচনা প্রয়োজন"

if SNR_improvement >= 3.0 and corr_after >= 0.90:
    quality_verdict = "সামগ্রিক সিগন্যাল-মান উল্লেখযোগ্যভাবে উন্নত হয়েছে (SNR ও correlation উভয়েই শক্তিশালী উন্নতি)"
else:
    quality_verdict = "সামগ্রিক সিগন্যাল-মানের উন্নতি সীমিত -- আরও ডিজাইন যাচাই প্রয়োজন"

print(f"\nফিল্টার-কার্যকারিতা সিদ্ধান্ত: {filter_verdict}")
print(f"সামগ্রিক গুণমান সিদ্ধান্ত: {quality_verdict}")
print(f"\nচূড়ান্ত সিদ্ধান্ত: L56-এ ডিজাইন করা পাইপলাইন এই হাইপোথেটিক্যাল সিনারিওতে "
      f"{'কার্যকর প্রমাণিত হয়েছে' if (atten_ratio >= 20.0 and preserve_ratio >= 0.80 and SNR_improvement >= 3.0) else 'আংশিকভাবে কার্যকর হয়েছে'}")

    
গণনা করা চূড়ান্ত ফলাফল: atten_ratio=১৫৮.২৭ ≥ ২০ এবং preserve_ratio=০.৯০৯৪ ≥ ০.৮০ — দুটো শর্তই পূরণ হওয়ায় প্রথম সিদ্ধান্ত-শাখা সক্রিয় হয়: "ফিল্টার তার ডিজাইন-লক্ষ্য পূরণ করেছে"। একইভাবে SNR_improvement=৭.৭৩ ≥ ৩.০ এবং corr_after=০.৯৬৬৯ ≥ ০.৯০ — তাই "সামগ্রিক সিগন্যাল-মান উল্লেখযোগ্যভাবে উন্নত হয়েছে"। উভয় শর্তসেট পূরণ হওয়ায় চূড়ান্ত প্রিন্ট-লাইন নিশ্চিত করে: "L56-এ ডিজাইন করা পাইপলাইন এই হাইপোথেটিক্যাল সিনারিওতে কার্যকর প্রমাণিত হয়েছে"। এই সিদ্ধান্তটি কোনো ধরে-নেওয়া দাবি নয় — এটি সরাসরি ধাপ ২-৫-এর পাঁচটি স্বাধীন, প্রকৃত গণনা করা সংখ্যা (দুই বিনের ম্যাগনিটিউড পরিবর্তন, অ্যাটেনুয়েশন ফ্যাক্টর, SNR-উন্নতি, MSE-হ্রাস, ও correlation-বৃদ্ধি) থেকে অ্যালগরিদমিকভাবে বের করা।
মূল কথা · Key takeaway

একটি বাস্তবসম্মত DSP পাইপলাইন কখনোই শুধু "একটি ফিল্টার প্রয়োগ করা" নয় — এটি একাধিক আন্তঃসংযুক্ত ধাপের সমষ্টি: সিগন্যালের গঠন স্পষ্টভাবে সংজ্ঞায়িত করা (ধাপ ১), ফ্রিকোয়েন্সি-ডোমেইনে কী আছে তা প্রকৃতপক্ষে সনাক্ত করা (ধাপ ২), সেই সনাক্তকরণের ভিত্তিতে একটি ফিল্টার ডিজাইন ও সঠিকভাবে প্রয়োগ করা (ধাপ ৩), ফলাফল স্পেকট্রামে সরাসরি যাচাই করা (ধাপ ৪), এবং একাধিক স্বাধীন মেট্রিক দিয়ে সামগ্রিক উন্নতি পরিমাপ করা (ধাপ ৫)। লক্ষ্য করুন ধাপ ৫-এ group delay সংশোধন না করলে correlation ও MSE-এর হিসাব ভুল হয়ে যেত — একটি ছোট বিস্তারিত বিষয় (M7/L31-এর লিনিয়ার-ফেজ ধারণা) উপেক্ষা করলে পুরো যাচাই প্রক্রিয়া বিভ্রান্তিকর ফলাফল দিতে পারত। এই কোর্সে আমরা M1 থেকে M13 পর্যন্ত প্রতিটি লেন্স আলাদাভাবে গভীরে শিখেছি, আর এই শেষ পাঠে দেখলাম কীভাবে তারা একসাথে একটি সম্পূর্ণ, প্রমাণ-ভিত্তিক সিগন্যাল-প্রসেসিং সমাধানে রূপ নেয়। Digital Signal Processing কোনো একটি সূত্র বা একটি অ্যালগরিদম নয় — এটি একটি নিয়মানুবর্তী চিন্তাপদ্ধতি, যা সময়-ডোমেইন ও ফ্রিকোয়েন্সি-ডোমেইন উভয় দিক থেকে সিগন্যাল দেখতে এবং প্রতিটি দাবি প্রকৃত সংখ্যা দিয়ে যাচাই করতে শেখায়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ ধাপ ৪-এ দেখা গেছে ৪০ Hz-এর ম্যাগনিটিউড ফিল্টারিং-এর পরেও -৯.১% কমেছে, একদম শূন্য নয়। এটি কি ডিজাইনের একটি ত্রুটি, নাকি প্রত্যাশিত আচরণ?

এটি প্রত্যাশিত — কোনো বাস্তব ফিল্টারের ধারালো (ideal, infinitely sharp) প্রান্ত থাকে না (M7/L34)। L56-এর ফ্রিকোয়েন্সি-রেসপন্স টেবিলেই দেখা গিয়েছিল |H(40)| ≈ ১.০০৩, অর্থাৎ ফিল্টার ডিজাইন থেকেই একদম ১.০ নয়, তার খুব কাছাকাছি। M=25 ট্যাপের একটি সসীম-দৈর্ঘ্যের windowed-sinc ফিল্টারের একটি সরু ট্রানজিশন ব্যান্ড থাকে (M7/L34-এ বিস্তারিত), তাই কাটঅফের (৬০ Hz) তুলনামূলক কাছের যেকোনো ফ্রিকোয়েন্সি (এখানে ৪০ Hz) সামান্য প্রভাবিত হয়। ৯.১% ক্ষতি বনাম ৯৯.৪% ইন্টারফেয়ারেন্স-অপসারণের তুলনায় এটি একটি গ্রহণযোগ্য ট্রেড-অফ।

প্র ০২ ধাপ ৫-এ group delay (১২ স্যাম্পল) সংশোধন না করে সরাসরি filtered[n]-কে clean[n]-এর সাথে তুলনা করলে কী ঘটত?

filtered[n] আসলে ইনপুটের ১২ স্যাম্পল আগের তথ্যের একটি বিলম্বিত, ফিল্টার করা সংস্করণ — তাই এটি clean[n−12]-এর সাথে মেলে, clean[n]-এর সাথে নয়। সংশোধন ছাড়া তুলনা করলে প্রতিটি নমুনাতেই একটি কৃত্রিম "ফেজ মিসম্যাচ" যোগ হতো, correlation কৃত্রিমভাবে কমে যেত এবং MSE কৃত্রিমভাবে বেড়ে যেত — এমনকি ফিল্টার সঠিকভাবে কাজ করলেও মনে হতো এটি ব্যর্থ হয়েছে। এটি একটি বাস্তব, প্রায়ই উপেক্ষিত ভুল — linear-phase FIR ফিল্টারের group delay (M7/L31) সবসময় ফলাফল-তুলনার আগে হিসাবে নিতে হয়।

প্র ০৩ এই ক্যাপস্টোনে তিনটি ভিন্ন মেট্রিক (SNR, MSE, correlation) ব্যবহার করা হয়েছে, শুধু একটি নয়। কেন একটি একক মেট্রিক যথেষ্ট নাও হতে পারে?

প্রতিটি মেট্রিক ভিন্ন দিক থেকে "উন্নতি" পরিমাপ করে। SNR ফ্রিকোয়েন্সি/পাওয়ার দৃষ্টিকোণ থেকে সিগন্যাল বনাম অবাঞ্ছিত-উপাদানের অনুপাত মাপে (M11/L49)। MSE টাইম-ডোমেইনে প্রতিটি নমুনার এবসোলিউট পার্থক্য মাপে — স্কেল/অ্যামপ্লিটিউড ত্রুটির প্রতি সংবেদনশীল। correlation শুধু দুটো সিগন্যালের আকৃতি কতটা একই রকম (স্কেল উপেক্ষা করে) তা মাপে। এই ক্যাপস্টোনে তিনটিই একই দিকে ইঙ্গিত করেছে (সবগুলো উন্নতি দেখিয়েছে), যা সিদ্ধান্তে আত্মবিশ্বাস বাড়ায় — বাস্তবে তিনটি ভিন্ন দিকে ইঙ্গিত করলে সেটাও গুরুত্বপূর্ণ তথ্য হতো (M11-এর একটি কেন্দ্রীয় শিক্ষা — এক নম্বরে ভরসা না করা)।

অনুশীলন

  1. চিন্তা করুন: ধাপ ১-এর কোড সেলে NOISE_SD = 0.3-কে NOISE_SD = 0.6 (দ্বিগুণ নয়েজ) করলে ধাপ ৫-এর SNR (আগে) কীভাবে বদলাবে বলে আপনার ধারণা, এবং SNR-উন্নতি (ফিল্টারিং-এর প্রভাব) বাড়বে না কমবে?

    নয়েজ পাওয়ার চারগুণ হবে (amplitude দ্বিগুণ মানে power চারগুণ, যেহেতু power ∝ amplitude²), তাই SNR (আগে) কমবে (আরও কম dB, সিগন্যাল আপেক্ষিকভাবে আরও দুর্বল দেখাবে)। যেহেতু র‍্যান্ডম নয়েজ ব্রডব্যান্ড (সব ফ্রিকোয়েন্সিতে ছড়ানো), লো-পাস ফিল্টার তার একটা অংশ এমনিতেই সরিয়ে দেবে, তাই SNR (পরে)-ও কিছুটা কমবে কিন্তু আগের চেয়ে কম হারে — ফলে SNR-উন্নতির (dB পার্থক্য) পরিমাণ বাড়ার সম্ভাবনা বেশি, কারণ ফিল্টারের "কাজ করার" জন্য আরও বেশি নয়েজ থাকবে সরানোর মতো।

  2. পরীক্ষা করুন: ধাপ ১-এর কোড সেলে NOISE_SD = 0.6 করে, তারপর ধাপ ২ থেকে ধাপ ৬ পর্যন্ত সবগুলো কোড সেল ক্রমানুসারে আবার Run করে আপনার অনুমান যাচাই করুন।

    রান করলে দেখা যাবে SNR (আগে) সত্যিই কমে গেছে (সিগন্যাল আরও নয়েজি), এবং SNR-উন্নতি (dB পার্থক্য) আগের ≈৭.৭৩ dB-এর চেয়ে বড় হওয়ার সম্ভাবনা বেশি — যদিও ঠিক কতটা বড় তা নির্ভর করে নির্দিষ্ট র‍্যান্ডম নমুনার (একই seed=42) উপর। এই অনুশীলনটি দেখায় কেন সবগুলো কোড সেল একটি নোটবুকের মতো ক্রমানুসারে আবার চালানো জরুরি — ধাপ ১-এর পরিবর্তন সঠিকভাবে ছড়িয়ে পড়তে হলে পরের প্রতিটি সেল পুনরায় Run করতে হয়।

কোর্স সম্পূর্ণ — অভিনন্দন!

  • Digital Signal Processing কোর্সের সম্পূর্ণ সিলেবাস পুনরায় দেখুন ৫৭টি পাঠ — সম্পূর্ণ M1 ফাউন্ডেশন থেকে M13 ক্যাপস্টোন পর্যন্ত — স্যাম্পলিং, কনভোলিউশন, Z-ট্রান্সফর্ম, DTFT/DFT, FFT, FIR/IIR ফিল্টার ডিজাইন, মাল্টিরেট প্রসেসিং, স্পেকট্রাল এস্টিমেশন, র‍্যান্ডম সিগন্যাল প্রসেসিং, বাস্তব প্রয়োগ ও এই ক্যাপস্টোন — কোর্সের সবগুলো পাঠ এখন সম্পূর্ণ।
  • Numerical Methods কোর্স পরবর্তী কোর্স এই কোর্স শিখিয়েছে সিগন্যাল ও সিস্টেমকে কীভাবে সংখ্যার সিরিজ হিসেবে হাতে-লেখা অ্যালগরিদম দিয়ে প্রসেস করতে হয় — Numerical Methods কোর্স একই "হাতে-লেখা, কোনো লাইব্রেরি ছাড়া" দর্শন নিয়ে আরও সাধারণ সংখ্যাগত অ্যালগরিদম (রুট-ফাইন্ডিং, ইন্টিগ্রেশন, ODE সলভিং) কভার করে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, Machine Learning, Deep Learning, System Design, Cybersecurity, Cloud Computing & DevOps, এবং আরও অনেক কোর্স — সব এক জায়গায়।
আগের পাঠ
একটি সম্পূর্ণ DSP পাইপলাইন তৈরি — ডিজাইন