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ファイルでエラーが発生しない初期値を設定しています。
全コード
コピペ用の全コードはこちら。
フィルタ周波数等の初期値はコンストラクタ部分に追加しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
import soundfile as sf import numpy as np import pandas as pd from scipy import fftpack from scipy import signal class DspToolkit(): def __init__(self): '''コンストラクタ''' # 時間波形読み込みで使う変数 self.time_y = np.array(0) self.time_y_original = np.array(0) self.time_x = np.array(0) self.sampling = 0 self.dt = 0 self.length = 0 # フーリエ変換で使う変数 self.frame_size = 1024 self.overlap = 0 self.dbref = 2e-5 self.averaging = 0 self.fft_axis = np.array(0) self.fft_mean = np.array(0) self.fft_array = np.array(0) self.acf = 0 # フィルタ処理で使う変数 self.lp_freq = 1000 self.hp_freq = 1000 self.bp_freq_low = 500 self.bp_freq_high = 1000 self.bs_freq_low = 500 self.bs_freq_high = 1000 self.attenuation_pass = 3 self.attenuation_stop = 40 def open_wav(self, path): '''パスを受け取ってwavファイルを読み込む''' self.time_y, self.sampling = sf.read(path) self.time_y_original = self.time_y.copy() self.get_time_information() def open_csv(self, path): '''パスを受け取ってcsvファイルを読み込む''' df = pd.read_csv(path, encoding='SHIFT-JIS') self.time_y = df.T.iloc[1] self.time_y_original = self.time_y.copy() self.sampling = 1 / df.T.iloc[0, 1] self.get_time_information() def get_time_information(self): '''Time plotの表示器に表示させる情報の計算と時間軸作成を行う''' self.dt = 1 / self.sampling self.time_x = np.arange(0, len(self.time_y), 1) * self.dt self.length = len(self.time_x) * self.dt print('Time waveform information was obtained.') def calc_overlap(self): '''時間波形をオーバーラップ率で切り出してリスト化する''' frame_cycle = self.frame_size / self.sampling x_ol = self.frame_size * (1 - (self.overlap / 100)) self.averaging = int((self.length - (frame_cycle * (self.overlap / 100))) / (frame_cycle * (1 - (self.overlap / 100)))) time_array = [] final_time = 0 if self.averaging != 0: for i in range(self.averaging): ps = int(x_ol * i) time_array.append(self.time_y[ps:ps+self.frame_size:1]) final_time = (ps + self.frame_size) / self.sampling print('Frame size=', self.frame_size) print('Frame cycle=', frame_cycle) print('averaging=', self.averaging) return time_array, final_time return time_array, final_time def hanning(self, time_array): '''ハニング窓をかけ振幅補正係数ACFを計算する''' han = signal.hann(self.frame_size) self.acf = 1 / (sum(han) / self.frame_size) # オーバーラップされた複数時間波形全てに窓関数をかける for i in range(self.averaging): time_array[i] = time_array[i] * han return time_array def fft(self, time_array): '''平均化フーリエ変換をする''' fft_array = [] for i in range(self.averaging): # FFTをして配列に追加、窓関数補正値をかけ、(Fs/2)の正規化を実施。 fft_array.append(self.acf * np.abs(fftpack.fft(np.array(time_array[i])) / (self.frame_size / 2))) # 全てのFFT波形のパワー平均を計算してから振幅値とする。 self.fft_axis = np.linspace(0, self.sampling, self.frame_size) self.fft_array = np.array(fft_array) self.fft_mean = np.sqrt(np.mean(self.fft_array ** 2, axis=0)) def db(self, x, dBref): '''dB変換をする''' y = 20 * np.log10(x / dBref) return y def filter(self, filter_type): '''フィルタをかける''' # ナイキスト周波数fnを設定して通過域端周波数wpと阻止域端周波数wsを正規化 # 阻止域周波数は通過域周波数の2倍にしている仕様(ここは目的に応じて変えても良い) fn = self.sampling / 2 # Lowpassフィルタ if filter_type == 'low': wp = self.lp_freq / fn ws = (self.lp_freq * 2) / fn try: # フィルタ次数とバタワース正規化周波数を計算 N, Wn = signal.buttord(wp, ws, self.attenuation_pass, self.attenuation_stop) # フィルタ伝達関数の分子と分母を計算 b, a = signal.butter(N, Wn, filter_type) self.time_y = signal.filtfilt(b, a, self.time_y) except ValueError: return -1 # Highpassフィルタ if filter_type == 'high': wp = self.hp_freq / fn ws = (self.hp_freq * 2) / fn try: # フィルタ次数とバタワース正規化周波数を計算 N, Wn = signal.buttord(wp, ws, self.attenuation_pass, self.attenuation_stop) # フィルタ伝達関数の分子と分母を計算 b, a = signal.butter(N, Wn, filter_type) self.time_y = signal.filtfilt(b, a, self.time_y) except ValueError: return -1 # bandpassフィルタ if filter_type == 'band': fp = np.array([float(self.bp_freq_low), float(self.bp_freq_high)]) fs = np.array([float(self.bp_freq_low)/2, float(self.bp_freq_high)*2]) wp = fp / fn ws = fs / fn try: # フィルタ次数とバタワース正規化周波数を計算 N, Wn = signal.buttord(wp, ws, self.attenuation_pass, self.attenuation_stop) # フィルタ伝達関数の分子と分母を計算 b, a = signal.butter(N, Wn, filter_type) self.time_y = signal.filtfilt(b, a, self.time_y) except ValueError: return -1 # bandstopフィルタ if filter_type == 'bandstop': fp = np.array([float(self.bs_freq_low), float(self.bs_freq_high)]) fs = np.array([float(self.bs_freq_low) / 2, float(self.bs_freq_high) * 2]) wp = fp / fn ws = fs / fn try: # フィルタ次数とバタワース正規化周波数を計算 N, Wn = signal.buttord(wp, ws, self.attenuation_pass, self.attenuation_stop) # フィルタ伝達関数の分子と分母を計算 b, a = signal.butter(N, Wn, filter_type) self.time_y = signal.filtfilt(b, a, self.time_y) except ValueError: return -1 return 0 |
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は変更なしです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 |
"""Subclass of FrontPanel, which is generated by wxFormBuilder.""" import wx import numpy as np import MainFrame from dspToolkit import DspToolkit # Implementing FrontPanel class WlFrontPanel(MainFrame.FrontPanel): def __init__(self, parent): MainFrame.FrontPanel.__init__(self, parent) # 初期化 # ファイル self.input_file = '' # 信号処理クラスのインスタンス化 self.dsp = DspToolkit() # ファイルダイアログの戻り値定数(条件分岐用) self.FLAG_OPEN = 5100 self.FLAG_CANCEL = 5101 self.FLAG_YES = 5103 self.FLAG_NO = 5104 # GUI表示器に初期値を格納 # フーリエ変換設定 self.textCtrl_FrameSize.SetValue(str(self.dsp.frame_size)) self.textCtrl_Overlap.SetValue(str(self.dsp.overlap)) self.textCtrl_dBref.SetValue(str(self.dsp.dbref)) # フィルタ設定 self.textCtrl_lp_freq.SetValue(str(self.dsp.lp_freq)) self.textCtrl_hp_freq.SetValue(str(self.dsp.hp_freq)) self.textCtrl_bp_freq_low.SetValue(str(self.dsp.bp_freq_low)) self.textCtrl_bp_freq_high.SetValue(str(self.dsp.bp_freq_high)) self.textCtrl_bs_freq_low.SetValue(str(self.dsp.bs_freq_low)) self.textCtrl_bs_freq_high.SetValue(str(self.dsp.bs_freq_high)) self.textCtrl_attenuation_pass.SetValue(str(self.dsp.attenuation_pass)) self.textCtrl_attenuation_stop.SetValue(str(self.dsp.attenuation_stop)) def file_selector(self, message, extension): '''指定した拡張子のファイルパスをダイアログで取得する''' # ダイアログでファイルオープン open_dialog = wx.FileDialog(None, message, wildcard=extension) # FLAG_OPEN=5100, FLAG_CANCEL=5101 file_ans = open_dialog.ShowModal() if file_ans == self.FLAG_OPEN: print('User selected file open operation.') self.input_file = open_dialog.GetPath() print('Selected path is;', self.input_file) return self.FLAG_OPEN else: print('User canceled file open operation.') return self.FLAG_CANCEL def time_data_plot(self, time_x, time_y, length): '''時間波形をプロットしてタブをTime plot(0)に切り替える''' time_data = self.ax1_time.plot(time_x, time_y, color='red') # オートスケールで軸設定する場合 self.ax1_time.set_xlim(0, length) if np.isnan(np.max(time_y)): message = 'Time waveform has NaN. It may be caused by wrong filter frequency. Time plot will be restored to original.' wx.MessageBox(message) print(message) self.dsp.time_y = self.dsp.time_y_original time_y = self.dsp.time_y_original time_data = self.ax1_time.plot(time_x, time_y, color='red') self.ax1_time.set_ylim(np.min(time_y), np.max(time_y)) self.canvas_timeplot.draw() line_time = time_data.pop(0) line_time.remove() # タブ(wx.Notebook)の切り替え self.Tab.SetSelection(0) def clear_freq_plot(self): '''周波数波形プロットと平均化回数情報をクリアにする''' fft_data = self.ax1_fft.plot(0, 0) self.canvas_fftplot.draw() line_fft = fft_data.pop(0) line_fft.remove() self.textCtrl_Average.SetValue('0') def clear_spec_plot(self): '''スペクトログラム表示をクリアにする''' self.cbar.remove() self.im.remove() self.ax1_spec.set_ylim(0, 10) self.im = self.ax1_spec.imshow(np.zeros((10, 10)), vmin=-1, vmax=1, aspect='auto', cmap='jet') self.cbar = self.fig_spec.colorbar(self.im) self.canvas_specplot.draw() def freq_data_plot(self): '''読み込んだ時間波形に対しフーリエ変換をかけて周波数プロットする''' try: int(self.textCtrl_FrameSize.Value) int(self.textCtrl_Overlap.Value) float(self.textCtrl_dBref.Value) except ValueError: # 設定値が計算に使えない値の時はエラーメッセージを表示し、周波数プロット/スペクトログラムクリアと表示器の初期化をする。 message = 'Frequency calculation was ignored. Unusable value(s) were entered into frequency calculation setting.' code = (1, message) wx.MessageBox(message) print(code) self.clear_freq_plot() self.clear_spec_plot() return # 時間波形をオーバーラップ処理する。 time_array, final_time = self.dsp.calc_overlap() # オーバーラップ処理した波形リストに窓間数をかける。 time_array = self.dsp.hanning(time_array) # オーバーラップができない場合は処理を終わらせる。 if time_array == []: message = 'Overlap calculation was ignored. Frame size was larger than data length.' code = 1, message wx.MessageBox(message) print(code) self.clear_freq_plot() self.clear_spec_plot() return # 平均化フーリエ変化をする。 self.dsp.fft(time_array) # 周波数波形にプロットする。 if self.checkBox_dBswitch.Value == True: # dBrefチェックボックスがONの時は振幅をdB変換する。 self.dsp.fft_mean = self.dsp.db(self.dsp.fft_mean, float(self.textCtrl_dBref.Value)) fft_data = self.ax1_fft.plot(self.dsp.fft_axis, self.dsp.fft_mean, color='red') # オートスケールで軸設定する場合 self.ax1_fft.set_xlim(0, self.dsp.fft_axis[int((len(self.dsp.fft_axis)-1)/2)]) # ナイキスト周波数までの表示 if np.max(self.dsp.fft_mean) == float('inf'): message = 'Frequency calculation was ignored because of overflow. It may be caused by wrong filter frequency. Time plot will be restored to original.' wx.MessageBox(message) print(message) self.time_data_plot(self.dsp.time_x, self.dsp.time_y_original, self.dsp.length) self.dsp.time_y = self.dsp.time_y_original self.clear_freq_plot() self.clear_spec_plot() return else: self.ax1_fft.set_ylim(np.min(self.dsp.fft_mean), np.max(self.dsp.fft_mean)*1.1) self.canvas_fftplot.draw() line_fft = fft_data.pop(0) line_fft.remove() # 平均化回数を表示器に入力する。 self.textCtrl_Average.SetValue(str(self.dsp.averaging)) message = 'Frequency calculation was done successfully.' code = 0, message print(code) # スペクトログラム表示 self.spectrogram_plot(self.dsp.fft_array, final_time) return def spectrogram_plot(self, fft_array, final_time): '''スペクトログラム表示をする''' # オートスケールで軸設定する場合(x軸はself.imで行う仕様) self.ax1_spec.set_ylim(0, self.dsp.fft_axis[int((len(self.dsp.fft_axis) - 1) / 2)]) # ナイキスト周波数までの表示 if self.checkBox_dBswitch.Value == True: fft_array = self.dsp.db(fft_array, self.dsp.dbref) spec_min = np.min(fft_array) spec_max = np.max(fft_array) self.cbar.remove() self.im.remove() self.im = self.ax1_spec.imshow(fft_array.T, vmin=spec_min, vmax=spec_max, extent=[0, final_time, 0, self.dsp.sampling], aspect='auto', cmap='jet') self.cbar = self.fig_spec.colorbar(self.im) self.cbar.set_label('Amp.') self.canvas_specplot.draw() # Handlers for FrontPanel events. def FrontPanelOnClose(self, event): '''ダイアログボックス選択式のウィンドウクローズ''' # Yes/Noダイアログを使ってユーザに選択させる。 message_dialog = wx.MessageDialog(None, 'Do you really want to close window?', 'Window close dialog', wx.YES_NO) ans = message_dialog.ShowModal() # FLAG_YES=5103, FLAG_NO=5104 if ans == self.FLAG_YES: print('User selected YES button to close window.') self.Destroy() def Button_open_wavOnButtonClick(self, event): '''wavファイルを開き時間波形を表示させる''' print('Open.wav was clicked.') # ファイルダイアログを開きwavファイルのパスを取得する。 ans = self.file_selector(message='Select a wav file.', extension='*.wav') # Open操作の時のみ操作を実行する。 if ans == self.FLAG_OPEN: self.dsp.open_wav(self.input_file) # 時間波形情報をTextCtrlに表示する。 self.textCtrl_dt.SetValue(str('{:.4e}'.format(self.dsp.dt))) self.textCtrl_Samplingrate.SetValue(str(self.dsp.sampling)) self.textCtrl_length.SetValue(str(np.round(self.dsp.length, 2))) # 時間波形をプロットする。 self.time_data_plot(self.dsp.time_x, self.dsp.time_y, self.dsp.length) # フーリエ変換をして周波数波形をプロットする。 self.freq_data_plot() def Button_open_csvOnButtonClick(self, event): '''csvファイルを開き時間波形を表示させる''' print('Open.csv was clicked.') # ファイルダイアログを開きwavファイルのパスを取得する。 ans = self.file_selector(message='Select a csv file.', extension='*.csv') # Open操作の時のみ操作を実行する。 if ans == self.FLAG_OPEN: self.dsp.open_csv(self.input_file) # 時間波形情報をTextCtrlに表示する。 self.textCtrl_dt.SetValue(str('{:.4e}'.format(self.dsp.dt))) self.textCtrl_Samplingrate.SetValue(str(self.dsp.sampling)) self.textCtrl_length.SetValue(str(np.round(self.dsp.length, 2))) # 時間波形をプロットする。 self.time_data_plot(self.dsp.time_x, self.dsp.time_y, self.dsp.length) # フーリエ変換をして周波数波形をプロットする。 self.freq_data_plot() def Button_save_wavOnButtonClick(self, event): # TODO: Implement Button_save_wavOnButtonClick pass def Button_save_csvOnButtonClick(self, event): # TODO: Implement Button_save_csvOnButtonClick pass def Button_Save_fft_csvOnButtonClick(self, event): # TODO: Implement Button_Save_fft_csvOnButtonClick pass def textCtrl_FrameSizeOnText(self, event): print('Set frame size.') try: self.dsp.frame_size = int(self.textCtrl_FrameSize.Value) except ValueError: pass def textCtrl_OverlapOnText(self, event): print('Set overlap ratio.') try: self.dsp.overlap = int(self.textCtrl_Overlap.Value) except ValueError: pass def textCtrl_dBrefOnText(self, event): print('Set dB-reference.') try: self.dsp.dbref = float(self.textCtrl_dBref.Value) except ValueError: pass def Button_re_calcOnButtonClick(self, event): print('User clicked re-calculation button.') self.freq_data_plot() def Button_lpOnButtonClick(self, event): print('User clicked lowpass button.') try: float(self.textCtrl_lp_freq.Value) except ValueError: pass return ret = self.dsp.filter('low') if ret == -1: wx.MessageBox('Error! Ignored filter calculation. Frequency range is out of range.') else: self.time_data_plot(self.dsp.time_x, self.dsp.time_y, self.dsp.length) self.freq_data_plot() def textCtrl_lp_freqOnText(self, event): print('Set frequency for lowpass.') try: self.dsp.lp_freq = float(self.textCtrl_lp_freq.Value) except ValueError: pass def Button_hpOnButtonClick(self, event): print('User clicked highpass button.') try: float(self.textCtrl_lp_freq.Value) except ValueError: pass return ret = self.dsp.filter('high') if ret == -1: wx.MessageBox('Error! Ignored filter calculation. Frequency range is out of range.') else: self.time_data_plot(self.dsp.time_x, self.dsp.time_y, self.dsp.length) self.freq_data_plot() def textCtrl_hp_freqOnText(self, event): print('Set frequency for highpass.') try: self.dsp.hp_freq = float(self.textCtrl_hp_freq.Value) except ValueError: pass def textCtrl_attenuation_passOnText(self, event): print('Set attenuation_pass.') try: self.dsp.attenuation_pass = float(self.textCtrl_attenuation_pass.Value) except ValueError: pass def textCtrl_attenuation_stopOnText(self, event): print('Set attenuation_stop.') try: self.dsp.attenuation_stop = float(self.textCtrl_attenuation_stop.Value) except ValueError: pass def Button_bpOnButtonClick(self, event): print('User clicked bandpass button.') try: float(self.textCtrl_bp_freq_low.Value) float(self.textCtrl_bp_freq_high.Value) except ValueError: pass return ret = self.dsp.filter('band') if ret == -1: wx.MessageBox('Error! Ignored filter calculation. Frequency range is out of range.') else: self.time_data_plot(self.dsp.time_x, self.dsp.time_y, self.dsp.length) self.freq_data_plot() def textCtrl_bp_freq_lowOnText(self, event): print('Set lower frequency for bandpass.') try: self.dsp.bp_freq_low = float(self.textCtrl_bp_freq_low.Value) except ValueError: pass def textCtrl_bp_freq_highOnText(self, event): print('Set upper frequency for bandpass.') try: self.dsp.bp_freq_high = float(self.textCtrl_bp_freq_high.Value) except ValueError: pass def Button_bsOnButtonClick(self, event): print('User clicked bandstop button.') try: float(self.textCtrl_bs_freq_low.Value) float(self.textCtrl_bs_freq_high.Value) except ValueError: pass return ret = self.dsp.filter('bandstop') if ret == -1: wx.MessageBox('Error! Ignored filter calculation. Frequency range is out of range.') else: self.time_data_plot(self.dsp.time_x, self.dsp.time_y, self.dsp.length) self.freq_data_plot() def textCtrl_bs_freq_lowOnText(self, event): print('Set lower frequency for bandstop.') try: self.dsp.bs_freq_low = float(self.textCtrl_bs_freq_low.Value) except ValueError: pass def textCtrl_bs_freq_highOnText(self, event): print('Set upper frequency for bandpass.') try: self.dsp.bs_freq_high = float(self.textCtrl_bs_freq_high.Value) except ValueError: pass def Button_original_dataOnButtonClick(self, event): print('User clicked restore button.') self.dsp.time_y = self.dsp.time_y_original self.time_data_plot(self.dsp.time_x, self.dsp.time_y, self.dsp.length) self.freq_data_plot() def checkbox_time_fixOnCheckBox(self, event): # TODO: Implement checkbox_time_fixOnCheckBox pass def textCtrl_time_xminOnText(self, event): # TODO: Implement textCtrl_time_xminOnText pass def textCtrl_time_xmaxOnText(self, event): # TODO: Implement textCtrl_time_xmaxOnText pass def textCtrl_time_yminOnText(self, event): # TODO: Implement textCtrl_time_yminOnText pass def textCtrl_ymaxOnText(self, event): # TODO: Implement textCtrl_ymaxOnText pass def checkbox_freq_fixOnCheckBox(self, event): # TODO: Implement checkbox_freq_fixOnCheckBox pass def textCtrl_freq_xminOnText(self, event): # TODO: Implement textCtrl_freq_xminOnText pass def textCtrl_freq_xmaxOnText(self, event): # TODO: Implement textCtrl_freq_xmaxOnText pass def textCtrl_freq_yminOnText(self, event): # TODO: Implement textCtrl_freq_yminOnText pass def textCtrl_freq_ymaxOnText(self, event): # TODO: Implement textCtrl_freq_ymaxOnText pass |
まとめ
コードの動作デモは既に記事の前半で紹介したため割愛します。今回はフィルタ処理部分を実装してみました。
既にフーリエ変換やSTFT計算によるスペクトログラム表示を実装していたので、フィルタ適用の効果を即座に確認できるアプリケーションになりました。
エラー処理部分はもしかしたらまだ不足があると思います。
本来はテストファイルを網羅的に作ってソフト設計時に色々考慮するのが正しい開発だと思いますが、趣味プログラマとしてはもぐら叩きのように出てきたら潰すスタンスでも良いのかなと思います。
もう挫折することはないと思いますが…次回はファイル保存やプロット軸設定といった細かい内容をまとめ、アプリ完成編を書く予定です。
アプリとしてはほぼできてきました!あとは仕上げあるのみ!Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!
お世話になっております。大変、参考にさせていただいております。
初めてコメントをさせていただきますので、コメント内容等失礼がありましたら
すみません。質問などは受け付けていますでしょうか?もし受け付けているので
ありましたら、ご教授よろしくお願いいたします。
<質問内容>
“Pythonでテキストファイル内を検索して値を抽出する方法”の
“文字列を一部含むか/行内位置不定で検索(in)”の項目のなかで最終的に
linesというリストを取得することができましたが、何行目を[6, 15, 23]ではなく
linesを使って出力するにはどうしたら良いでしょうか?
print(text_line[6, 15, 23])ではなく、linesなどを使って指示をしたいです。
手動で6, 15, 23を入力するのが、困難だからです。
また、次にその行の何番目の文字を取得するという操作をしたいです。
print(text_line[6][:4])ではなく、linesなどを使って指示をしたいです。
以上、ご指導よろしくお願いいたします。
ご訪問ありがとうございます。
質問は歓迎ですよ。ただどうやらこの記事の質問ではないようです。
「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行目がさらに一定文字を取得する例です。
以上、ちょっと考えてみましたが、もし質問の意図と回答がずれている場合はお知らせください。
よろしくお願い致します。