フィルタ処理編:wxPythonで信号処理のGUIアプリをつくろう⑤

  • このエントリーをはてなブックマークに追加

Pythonによる信号処理のGUIアプリ作成挑戦記第5弾は「フィルタ処理編」です。ローパス/ハイパス/バンドパス/バンドストップをGUIで一度に実装するアイデア例とその例外処理方法を紹介します。既に実装しているフーリエ変換機能で効果を確認します。

こんにちは。wat(@watlablog)です。今回は当ブログでも人気のあるフィルタ処理をGUIアプリケーションで書く方法を紹介します

バックナンバー

この記事は以下の記事の内容を前提とした続きものです。
是非こちらの記事も読んでみてください。

①アプリ構想とフレーム構築編

フレーム構築編:wxPythonで信号処理のGUIアプリをつくろう①」の記事で信号処理のGUIアプリを作り始めました。

この記事はどんなコンセプトでアプリを作るのか、各GUIをどうやって作っていくのか、開発環境や実際に作ったGUIコードを紹介しています。

②波形読み込み編

波形読み込み編:wxPythonで信号処理のGUIアプリをつくろう②」の記事ではファイル読み込み部分を実装しました。ボタンによるファイル選択、拡張子制限、matplotlibへのデータ渡しといったコードを紹介しています。

③周波数波形編

周波数波形編:wxPythonで信号処理のGUIアプリをつくろう③」の記事は平均化フーリエ変換を扱いました。平均化フーリエ変換はフレームサイズの条件次第で計算がかからない(オーバーラップ処理ができない)ため、関連の例外処理を施す必要がありました。

④スペクトログラム編

スペクトログラム編:wxPythonで信号処理のGUIアプリをつくろう④」ではフーリエ変換を拡張させ、信号の周波数変化や時間変化を可視化できるようにしました。

ここまで来ると読み込んだ波形の特徴が一目瞭然となり、これだけでもいっぱしのアプリケーションと呼べるものになったと思います。

この記事でやること

フィルタ処理

この記事ではフィルタ処理を実装しますが、最初に実装しなかったことに理由があります。

フィルタ処理は周波数領域に変化を及ぼすため、フーリエ変換やスペクトログラムを実装する前に作ると検証が大変です。

デバッグや例外処理を行うためにも、まずは周波数分析ができる状態までアプリケーションを作ってからフィルタ処理を実装するのが効率の良いコーディングと考えました。

ローパスフィルタ

まずは低周波信号を通過させるローパスフィルタLowpass filter)です。
百聞は一見にしかず、ということで以下のYouTube動画に今回実装するフィルタ処理のローパスフィルタをかける操作を紹介します。

サンプル信号は「②波形読み込み編」で作成したチャープ信号のwavファイルを使っています。この信号は時間経過によって周波数が増加していくため、フィルタ処理の検証に持ってこいです。

ローパスフィルタのPythonコード解説は「PythonのSciPyでローパスフィルタをかける!」の記事に詳細を書きましたので、是非こちらをご覧ください。

ハイパスフィルタ

ハイパスフィルタHighpass filter)は高周波信号を通過させます。
こちらもYouTube動画で実装イメージを固めておきましょう。

ハイパスフィルタのボタンをクリックすることで、先ほどのローパスフィルタとは逆に低周波帯域に大きな減衰がかかることを確認しました。

ローパスフィルタとほぼ同じですが、ハイパスフィルタは「PythonのSciPyでハイパスフィルタをかける!」という記事に解説があります。

バンドパスフィルタ

バンドパスフィルタBandpass filter)はある周波数範囲のみ信号を通過させます。特定の周波数帯域を抽出する際に便利です。

以下のYouTube動画では500[Hz]から1000[Hz]までの信号を通過させたことで、チャープ信号の最初と最後が急激に減衰する形となることを確認しました。

バンドパスフィルタは「PythonのSciPyでバンドパスフィルタをかける!」で紹介しています。ローパスやハイパスと比べ、Pythonコードには周波数情報を配列で渡すという違いがあるのでご注意ください。

バンドストップフィルタ

バンドストップフィルタBandstop filter)はバンドパスフィルタとは逆に特定の周波数帯域のみを減衰させます。

以下のYouTube動画は500[Hz]から1000[Hz]にバンドストップフィルタを適用させることで、チャープ信号の中域に減衰がかかることを確認しています。

当ブログではバンドストップフィルタを「PythonのSciPyでバンドストップフィルタをかける!」で紹介していますので、こちらもご参考に。

フィルタ処理をGUIで行うPythonコード

dspToolkit.py

いつものように、まずは信号処理用のメソッドをクラスとしてまとめたdspToolkit.pyファイルの変化点を解説します。

def filter(self, filter_type):

全コードはこの後載せますが、フィルタ処理用に追加したメソッドはこのdef filter()です。

このメソッド1つでフィルタ種類filter_type:low, high, band, bandstop)の情報を受け取り、時間波形に対し指定した操作をするようにしてみました。

先ほど紹介した記事とは、tryでフィルタ処理を実行しexceptを使ってValueErrorを検知することでエラー処理をしているところが大きな違いとなります。

GUIで入力する周波数範囲の設定が信号自体のサンプリング条件を満たしていたとしても、通過域や阻止域減衰量も含めて計算されるフィルタ伝達関数で不都合が起こると処理ができません

もしエラーが頻発する場合は、阻止域減衰量を高めに設定する、フィルタ周波数を余裕をもって設定するといったことが必要です。

今回のプログラムは当ブログで紹介したwavファイルでエラーが発生しない初期値を設定しています。

全コード

コピペ用の全コードはこちら。
フィルタ周波数等の初期値はコンストラクタ部分に追加しています。

wlFrontPanel.py

GUIのイベントに関する処理はこちらのwlFrontPanel.pyファイルにまとめます。

Button_〜OnButtonClick(self, event):

ボタン処理はローパスフィルタButton_lpOnButtonClick(self, event):)、ハイパスフィルタButton_hpOnButtonClick(self, event):)、バンドパスフィルタButton_bpOnButtonClick(self, event):)、バンドストップフィルタButton_bsOnButtonClick(self, event):)でほぼ同じような書き方をしているためまとめて解説します。

これらのボタンをクリックすることで、指定したフィルタ処理のイベントが実行されます。

最初に設定情報が入ったtextCtrlがおかしな値となっていないかのチェックをtry: except:文で実行し、self.dsp.filter()でフィルタ処理を実行します。

フィルタ処理でエラーが発生するかどうかをretを使って判定し、エラー発生(ret==-1)であればメッセージボックスwx.MessageBoxでユーザーに計算がされなかったことを知らせるようにしました。

エラーが発生しない場合(ret==0)は時間波形と周波数波形(+スペクトログラム)のプロットを行います。

textCtrl_〜OnText(self, event):

周波数設定や減衰量の制御器(textCtrl)はこれまで同様、数値変更時のValueErrorの回避のみ入れています。

Button_original_dataOnButtonClick(self, event):

Filter setupパネルの「Restore to original」ボタンをクリックすることで、フィルタ後の波形をファイル読み込み時の状態に復帰できる機能を付けてみました。

フィルタ処理は設定を少しずつ変更して実行を繰り返すことが多いので、いちいちファイル読み込みからやり直さなくても良いようにという程度の機能です。

仕組みは単純で、ファイル読み込み時にself.dsp.time_y_originalに最初の波形を保持させておくというだけ。メモリは多く必要になりますが…。

全コード

コピペ用の全コードはこちら。
前回からファイル読み込み部分も少々変わっていますが、このコード全体をコピペすれば問題ないと思います。main.pyは変更なしです。

まとめ

コードの動作デモは既に記事の前半で紹介したため割愛します。今回はフィルタ処理部分を実装してみました。

既にフーリエ変換やSTFT計算によるスペクトログラム表示を実装していたので、フィルタ適用の効果を即座に確認できるアプリケーションになりました。

エラー処理部分はもしかしたらまだ不足があると思います。

本来はテストファイルを網羅的に作ってソフト設計時に色々考慮するのが正しい開発だと思いますが、趣味プログラマとしてはもぐら叩きのように出てきたら潰すスタンスでも良いのかなと思います。

もう挫折することはないと思いますが…次回はファイル保存やプロット軸設定といった細かい内容をまとめ、アプリ完成編を書く予定です。

アプリとしてはほぼできてきました!あとは仕上げあるのみ!Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!

  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。

コメント

  1. niimi より:

    お世話になっております。大変、参考にさせていただいております。
    初めてコメントをさせていただきますので、コメント内容等失礼がありましたら
    すみません。質問などは受け付けていますでしょうか?もし受け付けているので
    ありましたら、ご教授よろしくお願いいたします。

    <質問内容>

    “Pythonでテキストファイル内を検索して値を抽出する方法”の
    “文字列を一部含むか/行内位置不定で検索(in)”の項目のなかで最終的に
    linesというリストを取得することができましたが、何行目を[6, 15, 23]ではなく
    linesを使って出力するにはどうしたら良いでしょうか?
    print(text_line[6, 15, 23])ではなく、linesなどを使って指示をしたいです。
    手動で6, 15, 23を入力するのが、困難だからです。

    また、次にその行の何番目の文字を取得するという操作をしたいです。
    print(text_line[6][:4])ではなく、linesなどを使って指示をしたいです。

    以上、ご指導よろしくお願いいたします。

    1. wat より:

      ご訪問ありがとうございます。
      質問は歓迎ですよ。ただどうやらこの記事の質問ではないようです。
      「Pythonでテキストファイル内を検索して値を抽出する方法」の記事でコメントしていただけると、他の方からの返信も期待できます。

      以下回答です。
      ○行目、という情報は既にlinesに入っていますので、手動で数値を入力しなくても、lines[i]のようにインデックスを使って抽出することが可能です。
      ↓例:このコードをサンプルの一番下に書いて実行
      for i in range(len(lines)):
      print(text_line[lines[i]])
      print(text_line[lines[i]][:4])

      forループ内1行目がlinesを使って行を抽出する例、
      2行目がさらに一定文字を取得する例です。

      以上、ちょっと考えてみましたが、もし質問の意図と回答がずれている場合はお知らせください。
      よろしくお願い致します。

niimi へ返信する コメントをキャンセル

*