
Pythonによる信号処理のGUIアプリ作成挑戦記第3弾は「周波数波形編」です。読み込んだ時間波形に対し平均化フーリエ変換を施す処理をGUIで行います。今回は設定の読み込み、変更処理や例外処理も含めて実装する方法を紹介します。
こんにちは。wat(@watlablog)です。引き続き信号処理のGUIアプリを作っていきます!今回はフーリエ変換部分を実装します!
バックナンバー
この記事は以下の記事の内容を前提とした続きものです。
是非こちらの記事も読んでみてください。
①アプリ構想とフレーム構築編
「フレーム構築編:wxPythonで信号処理のGUIアプリをつくろう①」の記事で信号処理のGUIアプリを作り始めました。
この記事はどんなコンセプトでアプリを作るのか、各GUIをどうやって作っていくのか、開発環境や実際に作ったGUIコードを紹介しています。
②波形読み込み編
「波形読み込み編:wxPythonで信号処理のGUIアプリをつくろう②」の記事ではファイル読み込み部分を実装しました。ボタンによるファイル選択、拡張子制限、matplotlibへのデータ渡しといったコードを紹介しています。
この記事でやること
平均化フーリエ変換をGUIコードへ実装する
本記事はフーリエ変換部分を実装します。
フーリエ変換は「PythonでFFT実装!SciPyのフーリエ変換まとめ」で紹介した平均化フーリエ変換を使います。
平均化フーリエ変換は時間波形全範囲を周波数変換するのではなく、時間波形をあるフレーム区間でオーバーラップしながら切り出して周波数領域で平均化する処理です。
オーバーラップ処理は「PythonでFFTをする前にオーバーラップ処理をしよう!」で図解した下図を参考にしてください。

また、平均化フーリエ変換の処理フローを以下に示します。今回はこの流れを念頭に置きつつGUI化しましょう。

GUI上のイメージはこちら。

GUI特有の例外処理を実装する
CUIプログラムとして自分だけで使っている時は、その都度出力ウィンドウに表示されるエラーメッセージを参考にデバッグが可能です。
しかしGUIアプリはユーザが自由に設定を変更し計算を行い、かつ出力ウィンドウは基本見ません。
そのため、プログラマ側で想定される例外処理を行う必要があります。
今回は計算部分を実装しますが、計算アルゴリズム自体は当ブログで既に扱った既知の内容です。今回はこの例外処理を実装する部分が新しい内容です。
編集するファイル
前回と同様に、今回も編集するファイルはイベントを記述するwlFrontPanel.pyと計算部分を記述するdspToolkit.pyです。
ファイル構成のイメージ図を下図に再掲します。

それでは実際にコードを書いてから詳細を見ていきましょう!
フーリエ変換をGUIで行うPythonコード
dspToolkit.py
dspToolkit.pyには「PythonでFFT実装!SciPyのフーリエ変換まとめ」で紹介した以下のメソッドを追記します。
def calc_overlap(self):
オーバーラップ処理をするメソッドです。詳細は「PythonでFFTをする前にオーバーラップ処理をしよう!」をご覧ください。
def hanning(self, time_array):
ハニング窓(もしくはハン窓とも呼ぶ)という窓関数をかけるメソッドです。窓関数には色々な種類がありますが、今回はハニング窓一択のシンプルなアプリとします。
窓関数については「PythonでFFT!SciPyで窓関数をかける」の記事をご覧ください。
また、窓関数を使う場合は補正係数も重要です。振幅補正係数については「窓関数使用時の補正!FFTの時に忘れがちな計算とは?」の記事を参照ください。
def fft(self, time_array):
フーリエ変換は自由なフレームサイズをユーザが指定して行います。
def db(self, x, dBref):
デシベル(dB)変換をするメソッドです。振動データはリニアスケールだと振幅視認性の悪い場合が多く、対数スケールであるdB値に変換することが一般的です。dB変換については「Pythonで音圧のデシベル(dB)変換式と逆変換式!」に詳細を記載していますので、是非参考にしてください。
全コード
コピペできるように、dspToolkit.pyの全文を以下に示します。
wlFrontPanel.py
wlFrontPanel.pyのイベント挙動で先ほど書いたメソッドを使います。
今回コンストラクタの編集はせず、以下のメソッドを追記しただけです。
def clear_freq_plot(self):
オーバーラップ処理ができない時や数値制御器(textCtrl)が数値でないといった場合は時間波形は読み込むことができますがフーリエ変換はできません。
そんな時、ただエラー回避しただけだと前の周波数波形が残っている状態になりユーザが誤解するため、周波数波形をクリアにするメソッドを用意しました。
def freq_data_plot(self):
フーリエ変換のメインメソッドです。
最初の「try:, except:」はフレームサイズ、オーバーラップ率、dBrefが数値でない場合に発生するValueErrorを捉える例外処理です。
ValueErrorが発生した場合は即returnでこのメソッドを終わらせています。
念のためprintでメッセージを残していますが、これは開発者の動作チェック用です。もしエラーを使って分岐処理をする場合は、return時にタプルで作成したcodeを返すことで拡張可能と思います。
フレームサイズが全時間長よりも長い場合はオーバーラップができず、エラーになる場合が考えられます。
オーバーラップのメソッドoverlapの戻り値にtime_arrayを設定しており、周波数平均化回数が1未満(つまりintで0)の場合は空リスト「[ ]」が来るようにしています。
このtime_arrayが空であれば先ほどの例外処理と同じようにreturnでメソッドを終了するようにしてみました。
正常に計算ができる場合は最後まで行い、周波数波形をプロットして終了します。
もし開発中にテストデータを使ってその他のエラーが発生した場合は、上記のような例外処理を随時入れていくことでロバスト性の高いアプリになることでしょう。
def Button_open_wavOnButtonClick(self, event):
wavファイルを読み込むこのメソッドは時間波形読み込み時に中身を書いたものですが、最後に「self.freq_data_plot()」を実行することでフーリエ変換を同時に行うようにしました。
def Button_open_csvOnButtonClick(self, event):
csvファイルを読み込むこちらのメソッドもwavの時と同様に最後にフーリエ変換メソッドを追記しました。
def textCtrl_****OnText(self, event):
textCtrl(FrameSize, Overlap, dBref)のOnTextには数値が変更された場合、変数に値をセットする処理を書いています。
例外処理にはValueErrorを検出するようにしていますが、ここでは出力ウィンドウにエラー文を発生させないことだけのために書いており、エラー処理自体は上記freq_data_plotに任せています。
def Button_re_calcOnButtonClick(self, event):
アプリのフロントパネルに配置している「Re:Calculation」のイベントメソッドです。ユーザがフーリエ変換に関する設定変更をした時に再度計算をする場合に使います。
例外処理は上に記載したメソッドで既に十分であるため、ここでは計算のメソッドを一回呼びだすだけにしました。
全コード
周波数波形プロット動作のデモ
YouTube動画による動作確認結果
例によってYouTube動画による動的な動作を確認します。
以下の動画は今回のコードを実行し、ファイルの読み込みやフレームサイズ、オーバーラップの変更、dB変換に切り替え…を行っています。
イメージ通り!…と思います。
まとめ
今回は時間波形を読み込んだ後に自動実行されるフーリエ変換部分を実装しました。
計算コード自体は数年前から当ブログで扱っていた内容でしたが、いざGUIアプリに実装する場合は色々な例外を考えなければいけないところに難しさがあると思いました。
print()を使って確認していた文はそのまま残してしまいましたが、正式にはLogging系のライブラリを使うのが良いのかも知れません。
ただ個人的には好きな文章を残せるprint()に便利さを感じてしまいます。
テキストボックス(textCtrl)のイベントは今回、エンターキーをクリックしなくてもイベント発生と認識するOnTextを使いました。ただこれだと1文字消したり追記したりする度にイベントが回るので、もしかしたら別のイベントトリガの方が適しているかも知れません。
後はスペクトログラムとフィルタ、軸設定のカスタマイズ機能が残りました。
非ITエンジニアがGUIアプリを作っていく過程をもう少しお楽しみください。
(筆者が途中で挫折しなければ…)
前回記事で書き方のスタイルを覚えたので、今回は楽勝でした!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!