波形読み込み編:wxPythonで信号処理のGUIアプリをつくろう②

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

Pythonによる信号処理のGUIアプリ作成挑戦記第2弾は「時間波形読み込み」です。wavやcsvをファイルダイアログで開く方法や信号処理クラスとのやり取りを通して、具体的にGUIプログラミングをする方法を模索します。

こんにちは。wat(@watlablog)です。信号処理GUIアプリ作りPART2は時間波形読み込み部分の実装をします

はじめに

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

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

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

このページは続きから説明をするため、まだ記事を読んでいない方は是非上記リンクからご覧ください。

この記事で行うこと

この記事では時間波形の読み込み部分としてGUIオブジェクトへのイベント付けファイル処理プロットをコードで記述します。

前回はmain.pyMainFrame.pywlFrontPanel.pyの3ファイルを作成しましたが、今回は新たにdspToolkit.pyを新規作成し具体的なコードを書いていきます。

この記事で編集するファイル

開発環境

開発に使うライブラリは前回に比べcsvファイルを扱うPandaswavファイルを扱うPySoundFileが追加されます。

Numpyや標準ライブラリでもcsvファイルの取り扱いは可能ですが、Pandasはデータ分析の分野で非常に多用されているため、他のプログラムでも互換性を持たせるために使おうという方針です。

Python Python 3.9.6
PyCharm (IDE) PyCharm CE 2020.1
Numpy 1.21.1
matplotlib 3.4.3
wxPython 4.1.1
Pandas 1.0.5
PySoundFile 0.9.0.post1

時間波形をGUIで読み込みプロットまで行うPythonコード

早速前回のコードを編集し、時間波形を読み込んでmatplotlibのプロットを行うプログラミングをしていきましょう。

dspToolkit.pyの新規作成

全コード

dspToolkit.pyGUI以外の信号処理関連コードをまとめます。
信号処理関連コードとは、波形の入ったファイルを読み込みデータに変換する機能も含みます。

.pyファイルを新規作成した後に、以下のコードを記述します。
このファイルはDspToolkit()というclassを記述しており、この中に変数の初期化であるコンストラクタや各種メソッドを追加しました。

Pythonプログラミング初心者のうちはclass表記のコードを見た瞬間ページを閉じたくなるかも知れません。

その場合、まずは「Pythonのクラスの使い方とオブジェクト指向の考え方を理解する」の記事を一読していただけるとこの書き方に戸惑わなくなると思います。

def __init__(self):

この部分はコンストラクタです。コンストラクタにはclass内で使う変数群であるプロパティをひとまとめにして初期化を行います。

とりあえず今回の時間波形読み込み機能に関しては、波形のxyとデータの情報である刻みdt、サンプリングレートsampling、時間長lengthを設定しています。

def open_wav(self, path):

このメソッドはwavファイルを読み込むだけのものです。PySoundFileによる読み込みはサンプリングレートの情報も一緒に得ることができます。

データ情報の計算はcsvファイル読み込みと同じ処理なので、後述するget_time_informationにまとめる書き方にしてみました。

def open_csv(self, path):

このメソッドはcsvファイルを読み込むだけのものです。
サンプルのcsvファイルは下の方にダウンロード可能としますが、0から始まる時間軸データの2行目(df.T.iloc[1]:時間刻みdt)を抽出します。

wavとまとめて条件分岐しても良いと思いましたが、どっちが良いかはまだ迷っています。

def get_time_information(self):

このメソッドはwavとcsvのどちらでも、開かれたデータに対する時間波形情報を計算します。

Time plotタブにある表示器にデータを渡すところはGUIのイベントをまとめるwlFrontPanel.pyの役割です。

wlFrontPanel.pyの編集

全コード

前回の記事で作成したwlFrontPanel.pyを編集します。

まずは編集後の全コードを以下に示します。前回との差異を確認してみてください。

ファイルの先頭にdspToolkit.pyをimportする文を入れて先ほど新規作成したファイルを参照しています。

また、class WlFrontPanelは既に継承していたMainFrame.FrontPanelに続き、DspToolkitを多重継承しています。

正直ここは多重継承で書いて良かったのか、それとも単純にdspToolkitのインスタンスを作って別々で使った方が良かったのか…ソフトウェア開発経験0の筆者には判断がつきませんでした…(強い人、是非アドバイスを…)。

多重継承をやめてコンストラクタでDspToolkitのインスタンス化をしてみました。self.dsp.…と毎回書きますが、この方が個人的にわかりやすいと思いました。

実際どういう書き方が良いのか模索中…。

def __init__( self, parent ):

このクラスのコンストラクタは入力ファイルパスと共に、ファイルダイアログを使った時の戻り値を定数として登録しています。

何度も条件分岐に使う場合はこのような入れ物に入れておいた方が、修正が必要な時にプロパティ値だけ変更すれば良いと思ったので設定しました。定数なので全て大文字で記載しています。

def file_selector(self, message, extension):

このメソッドはファイルダイアログを使ってパスを取得するためのものですが、wavファイルやcsvファイルを開く時に共通して使えるように書きました。

引数は任意のメッセージmessage拡張子extensionを与えることで、使う時に好きな文章をユーザに見せることができ、さらに指定した拡張子以外はファイルを選択できなくなります

ファイルを開いた時とダイアログをキャンセルした場合で条件分岐を設け、return文で結果がどうだったかわかるようにしました

def time_data_plot(self):

wavファイル読み込みボタンとcsvファイル読み込みボタンで、それぞれ時間波形をプロットする操作は同じです。そのためこのメソッドにまとめてみました。

この部分は「wxPythonでGUIレイアウトを作り込む時に参照するページ」で書いたものと同じですが、どんな時間波形が来ても基本は横軸と縦軸のレンジを合わせるようにする処理を入れています。

もちろん後で軸の設定をカスタムする機能は実装する予定です。

タブの初期位置がどこであっても、時間波形が読み込まれた段階でTime plotタブに戻すことができるようself.Tab.SetSelection(0)を記載しています。

def Button_open_wavOnButtonClick( self, event ):

このイベントはOpen .wavボタンをクリックした時に呼び出されます。
これまでに紹介したメソッドを順番に実行します。

def Button_open_csvOnButtonClick( self, event ):

Open .csvボタンをクリックした時も同様にこれまでのメソッドを順番に実行するだけです。

時間波形読み込み動作のデモ

サンプルファイル

プログラムの動作確認にはwavファイルとcsvファイルが必要です。すぐに試せるようにこちらにサンプルファイルを用意しました。

是非ダウンロードして使ってみてください。

wavファイル

dsp-test01:チャープ信号
Pythonでチャープ信号!周波数スイープ正弦波の作り方

dsp-test02:のこぎり波信号
Pythonでのこぎり波を生成!次数の高調波成分を見てみた

dsp-test03:録音音声(口笛)
現場でPC1つ!簡単に録音・FFT・wav保存するPythonコード

csvファイル

csvはこちらの記事の多チャンネルcsvを1チャンネルずつに分解しただけ。
ただPythonでcsvから離散フーリエ変換をするだけのコード

YouTube動画による動作確認結果

上記ファイルの作成や編集を行い、前回記事で紹介したmain.pyとMainFrame.pyと合わせて実行することで、時間波形読み込み機能の実装を確認できます。

例によって静止画だと伝わらないと思い、以下のYouTube動画で雰囲気を掴んでいただければと思います。

想定した動作を確認することができました!

まとめ

前回の記事に続きGUIで作る信号処理アプリは時間波形をwavファイルとcsvファイルから読み込めるようになりました。

ここまでで大枠の型はできた状態になったと思いますので、後はひたすら実装していくだけです。

今回はコンストラクタでクラスのインスタンス化をしたり、ダイアログの選択肢結果をif文で判定したりしていますが、筆者は特にIT系の人ではないことにご注意ください(あくまで趣味や勉強の範囲)。

今回のファイル構成やクラス内処理は独自の構成で、かつ我流感が否めないため、筆者自身も引き続き他の人のコードを参考に勉強をしていきます。

さらに開発現場だったらもっとDocstringを丁寧に書いたりTypeHintを書いたりすると思いますが、ここでは省略しています。

ゆくゆくはもっと効率の良いコードを書いてみたいですが…修行あるのみ!

…GUIコードの良いお手本とかどなたかお持ちでしたら情報お願いします💦
 (コメント欄、Twitter等へ!)

イベントドリブンなプログラミングが少しずつわかってきました!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!

SNSでもご購読できます。

コメント

  1. 村上 より:

    コメント失礼いたします。
    本記事にあるプログラムと全く同じ文を用いて動作させようとしているのですが、
    self.textCtrl_dt.SetValue(str(‘{:.4e}’.format(self.dsp.dt)))
    AttributeError: ‘wlFrontPanel’ object has no attribute ‘textCtrl_dt’というエラーが出てプロットできない状態です。エラーを出している部分を削除して実行すると正常に動作します。
    どういった原因が考えられるでしょうか。

    1. wat より:

      ご訪問ありがとうございます。
      AttributeErrorを読むと、textCtrl_dtというAttributeがないと書いてあります。
      このtextCtrl_dtというのはdt値をテキストボックスで確認するGUIですが、このGUIウィジェットはMainFrame.pyで宣言しています。
      MainFrame.pyに以下のコードは書いてありますか?

      ・MainFrame.pyでウィジェットを登録
      self.textCtrl_dt = wx.TextCtrl(…)
       〜
       bSizer_time_information.Add(…)

      この記事は前の記事からの続きとなっているため、その部分のコードはこちらにあります。
      https://watlab-blog.com/2022/05/19/gui-dsp01-frame/

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

*