前回までの記事で、STWIN.boxとFP-SNS-DATALOG2のGUIツールを使って音声データを取り込み、MEMS-Studioを使ってセンサデータの読み込み&解析をするところまでは確認できました。
関連記事: IoT向け小型センサ・ノード開発キット「STWIN.box 」を使ってセンサ・データ収集④ 超音波FFT
関連記事: IoT向け小型センサ・ノード開発キット「STWIN.box 」を使ってセンサ・データ収集⑤ 超音波FFT MEMS-StudioでFFT解析
記事④と記事⑤で紹介したとおり、FP-SNS-DATALGO2のUltrasoundプロジェクト を使うと、(STM32マイコンによる)FFTの計算結果をリアルタイムで描画 & データとして記録・保存できます。しかし、「過去のあのデータ波形をもう一度チェックしたい」という場合、MEMS-Studioを使えば時間軸データについてはファイルをLOADして解析できますが、FFTデータファイルの方はExcelなどで開く以外確認することができません。そこで今回は、一般的なPython環境でグラフ再表示させるところまで試してみます。
1. セットアップ
1-1. ハードウェアとソフトウェアの準備
ハードウェア(STWIN.box)とソフトウェア(FP-SNS-DATALOG2)の準備内容は記事④と同じになります。記事⑤と同じく、音声テスト用として、20KHz以上の音声帯域対応のオーディオ機器を用意します。
- STWIN.box
- FP-SNS-DATALOG2(Ultrasound Project)
- PC: Windows PC
- Python環境: Anaconda V1.12.3 (Python V3.12.7)
- 信号発生ソフトウェア: WaveGenerator
- 60~100KHzあたりまでの音声帯域出力対応のスピーカおよびアンプ
1-2. データの用意
サンプルデータとして、30KHzのsin波をスピーカから出力再生させた状態で、FP-SNS-DATALOG2のUltrasound用GUIでログを取りました。サンプリング周期が速く(192KHz)データサイズがすぐに大きくなってしまうので、ログ時間は5秒程度にしました。
マイクデータとFFTデータは、hsdatalog_data_export.pyを使ってcsvファイルに変換しておきます。ファイル名はmp23absu_mic_30k.csv、fft_dpu_30k.csvにしました。
今回はFFTデータ(fft_dpu_30k.csv)だけ使います。1行256個のデータが2400行以上並んでいます。
2. コードの生成と実行
表示方法としては、FFT結果1行1行を連続で表示させるアニメーションのような機能にしたいと考えました。
ウェブで調べたところ、matplotlibのFuncAnimation機能を使えば実現できそうです。出力表示のさせ方について3パターンほどトライしましたので、環境に応じて参考にしてください。
- ベーシック版: 基本的なアニメーション描画を確認できます
- ベーシック版 + MPEGファイル: ビデオファイルとして保存できるようにしました
- HTML版: jupyter notebookで使用したい場合こちら
2-1. FFTのシンプルアニメーション
コード生成
PythonコードはAIで自動生成しました。
AIへの生成指示内容(プロンプト):
「1行に256個のデータが入っていてそれが100行以上あるcsvファイルデータを読んで、Pythonのmatplotlibを使ってグラフ化し、1行ずつアニメーション表示させるPythonプログラムを作ってほしい。csvファイルは、pythonコマンド実行時に引数としてファイル指定して入力するようにします。グラフはマーカ無しの折れ線グラフにしてください。X軸のインデックスとして、新たに0から始まり95625までの、375ずつ増える256個の数値を作成してください。X軸レジェンドはログスケール表記にしてください。」
瞬時に以下のようなコードを生成してくれました。動作を試しながら内容確認しつつ、結果的に2か所ほど修正してます(オリジナルのコード文はコメントアウトしてます)。
fft_graph_anim.py
import sys
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
def main():
# コマンドライン引数でCSVファイルを指定
if len(sys.argv) != 2:
print("Usage: python script.py <csv_file>")
sys.exit(1)
csv_file = sys.argv[1]
# CSVファイルを読み込む
try:
data = pd.read_csv(csv_file, header=0)
# data = pd.read_csv(csv_file, header=None) #Origin
except Exception as e:
print(f"Error reading CSV file: {e}")
sys.exit(1)
# X軸のインデックスを作成 (0から95625まで375ずつ増加)
x_values = np.arange(0, 95625 + 1, 375)
if data.shape[1] != len(x_values):
print(f"Error: CSV file must have exactly {len(x_values)} columns.")
sys.exit(1)
# グラフの初期設定
fig, ax = plt.subplots(figsize=(10, 6))
line, = ax.plot([], [], lw=2) # 折れ線グラフ (マーカーなし)
ax.set_xscale('log') # X軸をログスケールに設定
ax.set_xlim(100, 100000) # X軸の範囲 (ログスケールなので1以上)
# ax.set_xlim(1, 100000) # X軸の範囲 (ログスケールなので1以上) #Origin
ax.set_ylim(data.min().min(), data.max().max()) # Y軸の範囲をデータに基づいて設定
ax.set_title("CSV Data Animation")
ax.set_xlabel("X-axis (Log Scale)")
ax.set_ylabel("Value")
ax.grid(True)
# アニメーションの更新関数
def update(frame):
line.set_data(x_values, data.iloc[frame])
return line,
# アニメーションの作成
ani = FuncAnimation(fig, update, frames=len(data), interval=200, blit=True)
# グラフを表示
plt.show()
if __name__ == "__main__":
main()
修正箇所
- 修正1
read_csvのヘッダ指定について、“header=None”となっていましたが、fft_dpu.csvファイルの1行目は“FFT_DPU_x”といったカラム名の列になっているので、
0行目 = カラムヘッダ(header=0)
と認識してもらうようにします。
# CSVファイルを読み込む
try:
data = pd.read_csv(csv_file, header=0)
# data = pd.read_csv(csv_file, header=None) #Origin
- 修正2
X軸の範囲については、1~100000(1Hz ~ 100KHz)がセットされていましたが、$10^0$ ~ $10^2$ (1 ~ 100)の範囲の値はデータとして存在しません。X軸の最小値をとりあえず “100($10^2$)” に変更しておきます。
ax.set_xlim(100, 100000) # X軸の範囲 (ログスケールなので1以上)
# ax.set_xlim(1, 100000) # X軸の範囲 (ログスケールなので1以上) #Origin
生成されたPythonスクリプトを、他のスクリプト(hsdatalog_data_export.pyなど)と同じフォルダ内にセーブします。スクリプト名は「fft_graph_anim.py」としました。
実行
以下コマンドで実行できます。Aanacondaプロンプトで動作確認しました。ちなみにjupyter notebook上でも試しましたが、こちらではうまく動きませんでした。
コマンド: python fft_graph_anim.py データファイル名(csv)
実行例:
対象データファイル:[20250604_15_27_53_Exported]フォルダ内のfft_dpu_30k.csv
(base) D:\...\Utilities\python fft_graph_anim.py ./20250604_15_27_53_Exported/fft_dpu_30k.csv
表示されたグラフがこちらです(1ショット)。2441行のFFTデータを順番に表示していく様子が観察できます。
アニメーションのFPS
データ表示周期の設定は200ms(interval=200。指定ない場合、デフォルト:200)となってましたので、表示スピードは5フレーム/秒です。リピートをオフ('repeat=False' パラメータ追加)にして再生時間を確認したところ、全て表示するのに約8分20秒かかりました。単純計算では約8分(2441/5/60 = 8.13 =8分8秒)と予想していましたが、若干遅延が発生するものと思われます。
# アニメーションの作成
ani = FuncAnimation(fig, update, frames=len(data), interval=200, blit=True, repeat=False)
#ani = FuncAnimation(fig, update, frames=len(data), interval=200, blit=True) #Origin
2-2. mpegファイルとして保存
最初に作成したスクリプトでは、一度アニメーション表示が始まると途中停止、早送り・早戻しなどの操作ができません(停止方法は、アニメーション・ウインドウ右上の×ボタンクリックによる強制終了)。そこで、一般的なプレーヤで再生できるように、動画ファイルとして保存するようにしたいと思います。
コード生成
同じくAIに指示しましたところ、以下コードを追記してくれました。
追加コード
# アニメーションをファイルに保存
try:
ani.save(output_file, writer='ffmpeg', fps=2)
print(f"Animation saved as {output_file}")
except Exception as e:
print(f"Error saving animation: {e}")
sys.exit(1)
生成されたPythonスクリプトは、「fft_graph_anim_mpeg.py」として保存しました。
そのままでも動作には問題ありませんでしたが、コマンド実行時に第2引数として出力用のファイル名を指定する仕様になっていて面倒でしたので、この部分だけ変更してます(オリジナルのコード文はコメントアウトしてます)。出力動画ファイル名は「fft_dpu.mp4」で固定としました。
コード全体はこちら:
fft_graph_anim_mpeg.py
import sys
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
def main():
# コマンドライン引数でCSVファイルを指定
# if len(sys.argv) != 3: #Origin
if len(sys.argv) != 2:
# print("Usage: python script.py <csv_file> <output_file>") #Origin
print("Usage: python script.py <csv_file>")
sys.exit(1)
csv_file = sys.argv[1]
# output_file = sys.argv[2] #Origin
output_file = "fft_dpu.mp4"
# CSVファイルを読み込む
try:
data = pd.read_csv(csv_file, header=0)
except Exception as e:
print(f"Error reading CSV file: {e}")
sys.exit(1)
# X軸のインデックスを作成 (0から95625まで375ずつ増加)
x_values = np.arange(0, 95625 + 1, 375)
if data.shape[1] != len(x_values):
print(f"Error: CSV file must have exactly {len(x_values)} columns.")
sys.exit(1)
# グラフの初期設定
fig, ax = plt.subplots(figsize=(10, 6))
line, = ax.plot([], [], lw=2) # 折れ線グラフ (マーカーなし)
ax.set_xscale('log') # X軸をログスケールに設定
ax.set_xlim(100, 100000) # X軸の範囲 (ログスケールなので1以上)
ax.set_ylim(data.min().min(), data.max().max()) # Y軸の範囲をデータに基づいて設定
ax.set_title("CSV Data Animation")
ax.set_xlabel("X-axis (Log Scale)")
ax.set_ylabel("Value")
ax.grid(True)
# アニメーションの更新関数
def update(frame):
line.set_data(x_values, data.iloc[frame])
return line,
# アニメーションの作成
ani = FuncAnimation(fig, update, frames=len(data), interval=200, blit=True)
# アニメーションをファイルに保存
try:
ani.save(output_file, writer='ffmpeg', fps=2)
print(f"Animation saved as {output_file}")
except Exception as e:
print(f"Error saving animation: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
ffmpegのインストールが必要
実行前に、ffmpegをPCにインストールしてください。
コード生成の際にAIに、
「アニメーションを動画ファイルとして保存するために、ffmpegまたはimagemagickが必要です」
と言われましたので、ffmpegをPCにインストールしました。これが入ってないとエラーになります。最初、“Pythonのモジュールとして必要なのかな?”と思いましたが、PC(筆者の環境はWindows PC)にインストールする必要があるそうです。
インストールといってもサイトからファイルをダウンロードして解凍し、WindowsのPATHが通る場所に置く、という少々面倒な手作業になります。ウェブで調べてみたのですが、現時点ではこのような方法しか見つけられませんでした。WindowsのPATHは、“Windows環境変数”のところで編集できます。(コントロールパネル → システムのプロパティ → 詳細設定 → 環境変数)
ffmpegインストールファイルのダウンロードはこちら: ffmpeg
筆者のPC環境では、解凍後のffmpegフォルダを適当な場所に保存しました。次にffmpeg.exeが入っているフォルダを確認し、そのPATH(***\ffmpeg\bin)をWindowsの環境変数編集ダイアログ内で新規追加しました。
実行
以下コマンドで実行できます。
コマンド: python fft_graph_anim_mpeg.py データファイル名(csv)
実行例:
[20250604_15_27_53_Exported]フォルダ内のfft_dpu_30k.csv
(base) D:\...\Utilities\python fft_graph_anim_mpeg.py ./20250604_15_27_53_Exported/fft_dpu_30k.csv
しばらく待ったのち、mp4ファイルが生成されました(fft_dpu.mp4)。PCのメディアプレーヤなどで再生できます。
元々は約5秒間のログデータでしたが、生成されたファイルは約20分の動画になっていました。
コードを確認するとfps=2設定でしたので、2フレーム/秒になります。約2400行のFFTデータの場合、2400/2 = 1200秒ということで確かに20分です。こちらの設定をfps=20フレーム/秒に変更したところ、約2分の動画ができました。
# アニメーションをファイルに保存
try:
ani.save(output_file, writer='ffmpeg', fps=20) #Origin:fps=2
print(f"Animation saved as {output_file}")
2-3. HTML表示
今度は、jupyter notebook上で実行できるようにします。最初のコードについてはjupyter notebookでも試したのですが、うまく実行できていませんでした(FFTの静止グラフがひとつ表示されて終了)。webで調べたところ「IPythonのHTMLを使えばよい」という情報を見つけたので(筆者は意味よくわかっていません)、そのままAIに指示して作ってもらいます。
なお当たり前の話なのかもしれませんが、今回生成したスクリプトはAnacondaなどの通常プロンプトからは実行できません。
コード生成
生成されたコードはこちらですが、1か所編集 & 1か所修正してます。
fft_graph_anim_html.py
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import numpy as np
import matplotlib
matplotlib.rcParams['animation.embed_limit'] = 200
# CSVファイルを読み込む関数
def load_csv(file_path):
try:
data = pd.read_csv(file_path, header=0)
return data
except Exception as e:
print(f"Error reading CSV file: {e}")
return None
# アニメーションを作成する関数
def create_animation(data):
# X軸のインデックスを作成 (0から95625まで375ずつ増加)
x_values = np.arange(0, 95625 + 1, 375)
if data.shape[1] != len(x_values):
raise ValueError(f"CSV file must have exactly {len(x_values)} columns.")
# グラフの初期設定
fig, ax = plt.subplots(figsize=(10, 6))
line, = ax.plot([], [], lw=2) # 折れ線グラフ (マーカーなし)
ax.set_xscale('log') # X軸をログスケールに設定
ax.set_xlim(100, 100000) # X軸の範囲 (ログスケールなので1以上)
ax.set_ylim(data.min().min(), data.max().max()) # Y軸の範囲をデータに基づいて設定
ax.set_title("CSV Data Animation")
ax.set_xlabel("X-axis (Log Scale)")
ax.set_ylabel("Value")
ax.grid(True)
# アニメーションの更新関数
def update(frame):
line.set_data(x_values, data.iloc[frame])
return line,
# アニメーションの作成
ani = FuncAnimation(fig, update, frames=len(data), interval=200, blit=True)
plt.close()
return ani
# メイン処理
def main():
# CSVファイルのパスを指定
csv_file = "./20250604_15_27_53_Exported/fft_dpu_30k.csv" # 必要に応じてファイルパスを変更してください
# CSVファイルを読み込む
data = load_csv(csv_file)
if data is None:
return
# アニメーションを作成
ani = create_animation(data)
# アニメーションをHTMLとして表示
return HTML(ani.to_jshtml())
# 実行
main()
ファイル指定文の編集
読み込むログファイルについては、コード内にてPATH付きで指定するようにします。
# CSVファイルのパスを指定
csv_file = "./20250604_15_27_53_Exported/fft_dpu_30k.csv" # 必要に応じてファイルパスを変更してください
修正箇所
コード生成後、jupyter noteboookのセルに直接コードを張り付けて実行してみたところ、「アニメーション容量サイズがリミットをオーバーしている」といった内容のエラーが発生して途中で停止しました。リミット容量のデフォルトは20(20MByte)だそうです。
エラーメッセージ:
Animation size has reached 20998621 bytes, exceeding the limit of 20971520.0. If you're sure you want a larger animation embedded, set the animation.embed_limit rc parameter to a larger value (in MB). This and further frames will be dropped.
20MByte以上も必要な動画とは思えなかったので、どうしてこんなに容量が必要なのかAIに聞いてみたところ、
「to_jshtml()はアニメーションの各フレームを画像(通常はBase64エンコードされたPNG)として埋め込み、それらをJavaScriptで切り替える形でブラウザ上にアニメーションを表示します。」
だそうです。
画像サイズにも依りますが、参考見積もりとしては「100フレームの動画で数M ~数十MByte」なので、今回2400フレーム以上あるので確かに20MByteは軽く超える感じです。
ちなみに前の章で作成したMP4ファイルは約20分の動画で約2.6MByte程度のサイズでした。
この設定を変更する方法を調べましたところ、以下コマンドの追加で変更できるようです。とりあえず10倍の'200'にセットしてみたら無事動作しました。
追加文
import matplotlib
matplotlib.rcParams['animation.embed_limit'] = 200
実行
修正後、再度実行してみます。しばらく待ったのち、以下のようなグラフが表示されました。
特に設定した覚えはないですが、デフォルトで再生・停止ボタンなどが使えるようになっています。個人的には上の2つの方法よりこちらの方が便利に感じました。
以上、Python環境でFP-SNS-DATALOG2のFFTデータを表示させる例をいくつか紹介しました。