ノイズ除去ガイド

弱い信号の S/N 比を、1引数で改善する

pyMEA — デノイズプリセット・個別メソッド・手法の検証

pyMEA ノイズ除去ガイド

このスライドのゴール

MEA計測データのノイズを除去し、ピーク検出・ISI・FPD・伝導速度の算出精度を上げるための機能を扱います。

  • プリセット1引数でデノイズを適用できる
  • 細胞種・信号強度に応じた手法の選び方がわかる
  • 自分のデータで手法を客観的に検証できる

前提:read_MEA() でデータを読み込めていること。基本操作は「pyMEA コードガイド」を参照してください。

pyMEA ノイズ除去ガイド

なぜデノイズが必要か

細胞の信号が弱いと S/N 比が下がり、ピーク検出の取りこぼし・誤検出が増えて解析精度が落ちます。

既存のフィルターは目的が限定的でした。

既存手法 対象 限界
FILTER_MEA 電源ハムの基本波 ドリフト・広帯域ノイズに無効
CARDIO_AVE_WAVE 平均波形の算出 時系列を1拍に潰す
iirnotch_filter 50Hz 電源基本波のみ

さらに、64ch の空間冗長性がまったく活用されていませんでした。

pyMEA ノイズ除去ガイド

3つの設計原則

デノイズ機能は、以下を必ず守ります。

  • 全時系列を保持 — shape (65, N) のまま。ISI・拍動間変動・不整脈が残る
  • ゼロ位相 — sosfiltfilt で順逆2回がけ。ピークタイミングがずれない
  • イミュータブル — 元データを変更せず、新しい MEA を返す

ポイント:ゼロ位相なので、デノイズしても ISI や伝導速度が壊れません。実測でタイミングズレ 0msを確認しています。

pyMEA ノイズ除去ガイド

1. クイックスタート

まずは1引数で使ってみる

pyMEA ノイズ除去ガイド

クイックスタート

read_MEA() に filter_type を渡すだけ。返り値は通常どおり PyMEA です。

from pyMEA import *

hed_path = "/User/you/your_record_data.hed"
start, end = 0, 5
electrode_distance = 450

# 心筋データのデノイズ
mea = read_MEA(hed_path, start, end, electrode_distance, FilterType.CARDIO_DENOISE)

# 以降は通常どおり
peak_index = detect_peak_neg(mea.data)
isi = mea.calculator.isi(peak_index, ch=6)
mea.fig.showAll()

ポイント:プリセットを使えば mea.fig と mea.calculator もデノイズ後のデータに束縛されます。迷ったらプリセットが安全です。

pyMEA ノイズ除去ガイド

2. 手法の選び方

万能の手法は存在しない

pyMEA ノイズ除去ガイド

手法の選び方

最適な手法は細胞種と信号強度で変わります。 まず下図で当たりをつけ、S/N 比で確認します。

手法選択のフローチャート
pyMEA ノイズ除去ガイド

プリセット一覧

FilterType の3つがデノイズ用。read_MEA() の第5引数に渡します。

プリセット 適用される処理 対象
CARDIO_DENOISE highpass(1) → CMR 心筋(強〜中信号)
CARDIO_DENOISE_WEAK bandpass(1, 1000) → CMR 微弱な心筋
NEURO_DENOISE bandpass(100, 3000) 神経
mea = read_MEA(hed_path, start, end, electrode_distance, FilterType.CARDIO_DENOISE)
mea = read_MEA(hed_path, start, end, electrode_distance, FilterType.CARDIO_DENOISE_WEAK)
mea = read_MEA(hed_path, start, end, electrode_distance, FilterType.NEURO_DENOISE)
pyMEA ノイズ除去ガイド

プリセットの中身

filter_type に応じて、デノイズ段が順に合成されます。各段は新しい MEA を返します。

プリセットごとのデノイズ段

補足:CMR = 共通中央値リファレンス(common_median_reference())。神経に CMR が入らないのは、共通モード成分が少なく効果が薄いためです。

pyMEA ノイズ除去ガイド

実測の効果

実データ・シミュレーションでの検証結果です。

データ 手法 S/N改善 振幅保持 検出
心筋(強信号) CARDIO_DENOISE 1.23x 102% 100% / ズレ 0ms
心筋(微弱 S/N≈6.6) CARDIO_DENOISE_WEAK 1.7x — 真検出 95%→100%
誤検出 23%→8%
神経(弱信号) NEURO_DENOISE 1.16x 92% —

ポイント:微弱心筋では bandpass が必須です。支配ノイズが帯域外の広帯域ノイズになるため、highpass + CMR では S/N が上がりません。

pyMEA ノイズ除去ガイド

3. 個別メソッド

プリセットを構成する4つの部品

pyMEA ノイズ除去ガイド

highpass / bandpass

いずれもゼロ位相 Butterworth。mea(PyMEA)と mea.data(MEA)の両方から呼べます。

denoised = mea.highpass(cutoff=1)            # ドリフト除去(PyMEA を返す)
denoised = mea.bandpass(low=100, high=3000)  # 神経のスパイク帯
denoised = mea.bandpass(low=1, high=1000)    # 心筋(鋭いスパイクを保持)
  • 帯域の目安:心筋 1-1000Hz / 神経 100-3000Hz
  • highpass はドリフト(低周波)、bandpass は帯域外の広帯域ノイズも落とす

⚠ 心筋で bandpass(1, 300) は禁物。鋭い脱分極スパイクを潰し、振幅が 49% まで落ちます(実測)。

pyMEA ノイズ除去ガイド

common_median_reference

各時刻で全64電極の中央値を求め、各電極から減算します(CMR)。

denoised = mea.data.common_median_reference()
  • 対象:電源ハム・参照電極ドリフト・機械振動など、電極間で相関するノイズ
  • 長所:64ch の空間冗長性を活用。スパイク形状を変えない(無歪み)。パラメータ不要
  • 注意:無相関な熱雑音には無効。神経では効果が薄い(実測 1.02x)

なぜ平均(CAR)でなく中央値か:心筋は全面が同期して拍動するため、平均だと信号成分まで引いてしまいます。中央値ならその影響が限定的です。

pyMEA ノイズ除去ガイド

wavelet_denoise

離散ウェーブレット変換 + ソフト閾値。信号帯域内に重なる広帯域ノイズを除去します。

denoised = mea.data.wavelet_denoise(wavelet="db4")
  • 線形フィルタと違い、鋭いスパイク形状を保ったまま帯域内ノイズを落とす
  • 強信号の心筋では単独で最も S/N が上がる
  • 閾値はノイズ標準偏差から自動算出(universal threshold)

⚠ 微弱信号では危険。弱いスパイクごと削り、微弱心筋で真検出率が 84% に低下した実測があります。プリセットには含めていません。 使うなら必ず検出数を確認してください。

pyMEA ノイズ除去ガイド

4. 独自パイプライン

プリセット以外を組むときの注意

pyMEA ノイズ除去ガイド

内部では何が起きているか

read_MEA() はプリセットに応じてデノイズ段を合成し、最後に PyMEA を組み立て直します。

read_MEA の処理フロー
pyMEA ノイズ除去ガイド

mea のメソッドで組む

PyMEA(mea)には highpass / bandpass / common_median_reference / wavelet_denoise があります。デノイズ後のデータで再構築した PyMEA を返すので、fig / calculator もそのまま使えます。

mea = read_MEA(hed_path, start, end, electrode_distance)

# PyMEA を返す → fig・calculator が自動で追従する
denoised = mea.bandpass(1, 1000).common_median_reference()

peak_index = detect_peak_neg(denoised.data)
snr = denoised.calculator.snr(peak_index, ch=6)
denoised.fig.showAll()

ポイント:mea.xxx() ならチェーンでそのまま描画・計算まで繋がります。組み直しは不要です。

pyMEA ノイズ除去ガイド

⚠ 落とし穴:mea.data を直接触ると

mea.data.xxx() は MEA を返します(PyMEA ではない)。この場合 mea.fig / mea.calculator は読み込み時のデータに束縛されたままで、デノイズ結果を見ません。

denoised = mea.data.wavelet_denoise()   # ← MEA を返す
peak_index = detect_peak_neg(denoised)

# ❌ 間違い:mea.calculator は生データを持つので、生データの S/N が返る
snr = mea.calculator.snr(peak_index, ch=6)

サンプルデータでの実測値:

書き方 S/N
生データ 141.6
❌ mea.data...() + mea.calculator 140.9(効いていない)
✅ mea....() の calculator(PyMEA) 194.1

基本は mea.data でなく mea のメソッドを使うこと。MEA を直接扱う場合だけ Calculator / FigMEA を手で組み直します。

pyMEA ノイズ除去ガイド

5. 手法の検証

自分のデータで選ぶ

pyMEA ノイズ除去ガイド

S/N比で検証する

Calculator.snr() は スパイク振幅 / 静穏区間のノイズRMS を返します。

def snr(peak_index: Peaks64, ch: int, noise_window: float = 0.015) -> float

生データと各プリセットを並べて、自分のデータで最良の手法を選びます。

for name, filter_type in [
    ("raw", FilterType.NONE),
    ("cardio_denoise", FilterType.CARDIO_DENOISE),
    ("cardio_denoise_weak", FilterType.CARDIO_DENOISE_WEAK),
]:
    mea = read_MEA(hed_path, start, end, electrode_distance, filter_type)
    peak_index = detect_peak_neg(mea.data)
    snr = mea.calculator.snr(peak_index, ch=6)
    print(f"{name:20s} S/N={snr:6.2f}  検出数={len(peak_index[6])}")
pyMEA ノイズ除去ガイド

S/N比だけで判断しない

上のコードの出力例(サンプルデータ・ch 6):

raw                  S/N=141.58  検出数=5
cardio_denoise       S/N=174.53  検出数=5
cardio_denoise_weak  S/N=325.85  検出数=5

必ず検出数も見る。wavelet_denoise のようにノイズと一緒に弱いスパイクを削る手法は、S/N が上がっても検出を取りこぼしていることがあります。数値はデータ依存なので、振幅保持と併せて判断してください。

神経データは正解ラベルが無く相対閾値検出が交絡するため、最終的には波形の目視確認で帯域・閾値を調整することを推奨します。

pyMEA ノイズ除去ガイド

既存フィルターとの使い分け

デノイズ機能は既存フィルターの置き換えではなく併存です。目的が異なります。

機能 時系列 目的
CARDIO_DENOISE / _WEAK / NEURO_DENOISE 保持 S/N比改善
CARDIO_AVE_WAVE 1拍に潰れる 平均波形の算出
FILTER_MEA 保持 電源同期減算
iirnotch_filter(50) 保持 50Hz ノッチ

CARDIO_AVE_WAVE は時系列を1拍に潰すため、ISI・拍動間変動・不整脈が失われます。これらを解析したいならデノイズ機能を使ってください。

pyMEA ノイズ除去ガイド

注意点・制約

ナイキスト周波数

カットオフはサンプリングレートの半分未満でなければなりません。

mea.data.bandpass(100, 3000)   # OK(10000Hz 記録)
mea.data.bandpass(100, 5000)   # ValueError

down_sampling() を掛けるとサンプリングレートが下がるため、デノイズはダウンサンプリングの前に適用します。

処理時間の目安(64ch × 3秒 @10kHz)

メソッド 時間
highpass(1) / bandpass(1, 1000) 約 0.08 / 0.10 s
common_median_reference() / wavelet_denoise() 約 0.04 / 0.05 s
pyMEA ノイズ除去ガイド

まとめ

  • 迷ったらプリセット。read_MEA(..., FilterType.CARDIO_DENOISE) の1引数で適用できる
  • 心筋は強弱で使い分け。微弱なら CARDIO_DENOISE_WEAK、神経は NEURO_DENOISE
  • 独自パイプラインは mea.xxx()(PyMEA)で組む。mea.data.xxx() を直接触ると fig・calculator が追従しない
  • S/N と検出数を必ず両方見る。wavelet_denoise は微弱信号でスパイクを削る

詳細ドキュメント:docs/api/denoising.md に全メソッドのリファレンスと注意点、docs/prd/denoising.md に設計判断と実測データがあります。

pyMEA ノイズ除去ガイド