LoginSignup
38
56

More than 3 years have passed since last update.

PySimpleGUIでデータViewerを作る

Last updated at Posted at 2021-01-26

はじめに

実験データや測定データが入ったファイルがたくさんあって,そのデータを理解するためには何らかの後処理や可視化が必要,という状況は割とよくあると思います.

例えば材料の性質を知るために行われる圧縮試験という試験において,その結果として荷重と変形量が記録されたCSVファイルが得られる場合を考えます.生のデータを理解するのは難しく,応力やひずみの計算や応力ひずみ曲線の作成が必要になります.しかし実験の結果をちょっと確認するためだけに毎回Excelを開いて計算するのも面倒です.

そこでもしWindowsのFile Explorerのプレビュー表示のようにワンクリックでデータを確認できたら便利なのではないでしょうか.そしてPySimpleGUIというPythonライブラリを使うとそれが比較的簡単に実現できます.

この記事では画像のようなシンプルなデータViewerを作ります.
image.png

データが入っているフォルダを選ぶと,その中身が左のツリーに表示されます.そしてファイル名をクリックすると中身のデータのプロットを見ることができます.
ツリーは入れ子になっているフォルダにも対応しており,また複数のファイルを選択することで複数のデータを重ねてプロットすることも可能です.

環境・ライブラリ等

  • Windows10
  • Python 3.8.2
  • PySimpleGUI
  • Matplotlib
  • Numpy

PySimpleGUIについて

PySimpleGUIはPythonでGUIアプリケーションを作成できるライブラリです.PythonのGUIライブラリには様々なものがあり,私もいくつか利用したことがあります.それらのライブラリと比較したときのPySimpleGUIの特徴は,素早く簡単にGUIアプリケーションを作れるというところだと思います.またPySimpleGUIはドキュメントやサンプルコードなどのリソースが豊富で,しばらくドキュメントを読めばすぐにプログラムを書き始められるようになると思います.

PySimpleGUIの基本的な部分については公式サイトにわかりやすい説明がたくさんあるので省きます.
https://pysimplegui.readthedocs.io/en/latest/

GitHubのREADMEもわかりやすくておすすめです.
https://github.com/PySimpleGUI/PySimpleGUI/blob/master/readme.md

READMEの日本語版もあるみたいです.
https://github.com/PySimpleGUI/PySimpleGUI/blob/master/readme.ja.md

大量(200個くらいあるらしい)のデモプログラムも必見です.OpenCVやMatplotlibなどの他ライブラリと連携しているプログラムもあります.
https://github.com/PySimpleGUI/PySimpleGUI/tree/master/DemoPrograms

プログラム

プログラム全体です.

import os
import numpy as np
import PySimpleGUI as sg
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

folder_icon = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABnUlEQVQ4y8WSv2rUQRSFv7vZgJFFsQg2EkWb4AvEJ8hqKVilSmFn3iNvIAp21oIW9haihBRKiqwElMVsIJjNrprsOr/5dyzml3UhEQIWHhjmcpn7zblw4B9lJ8Xag9mlmQb3AJzX3tOX8Tngzg349q7t5xcfzpKGhOFHnjx+9qLTzW8wsmFTL2Gzk7Y2O/k9kCbtwUZbV+Zvo8Md3PALrjoiqsKSR9ljpAJpwOsNtlfXfRvoNU8Arr/NsVo0ry5z4dZN5hoGqEzYDChBOoKwS/vSq0XW3y5NAI/uN1cvLqzQur4MCpBGEEd1PQDfQ74HYR+LfeQOAOYAmgAmbly+dgfid5CHPIKqC74L8RDyGPIYy7+QQjFWa7ICsQ8SpB/IfcJSDVMAJUwJkYDMNOEPIBxA/gnuMyYPijXAI3lMse7FGnIKsIuqrxgRSeXOoYZUCI8pIKW/OHA7kD2YYcpAKgM5ABXk4qSsdJaDOMCsgTIYAlL5TQFTyUIZDmev0N/bnwqnylEBQS45UKnHx/lUlFvA3fo+jwR8ALb47/oNma38cuqiJ9AAAAAASUVORK5CYII='
file_icon = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABU0lEQVQ4y52TzStEURiHn/ecc6XG54JSdlMkNhYWsiILS0lsJaUsLW2Mv8CfIDtr2VtbY4GUEvmIZnKbZsY977Uwt2HcyW1+dTZvt6fn9557BGB+aaNQKBR2ifkbgWR+cX13ubO1svz++niVTA1ArDHDg91UahHFsMxbKWycYsjze4muTsP64vT43v7hSf/A0FgdjQPQWAmco68nB+T+SFSqNUQgcIbN1bn8Z3RwvL22MAvcu8TACFgrpMVZ4aUYcn77BMDkxGgemAGOHIBXxRjBWZMKoCPA2h6qEUSRR2MF6GxUUMUaIUgBCNTnAcm3H2G5YQfgvccYIXAtDH7FoKq/AaqKlbrBj2trFVXfBPAea4SOIIsBeN9kkCwxsNkAqRWy7+B7Z00G3xVc2wZeMSI4S7sVYkSk5Z/4PyBWROqvox3A28PN2cjUwinQC9QyckKALxj4kv2auK0xAAAAAElFTkSuQmCC'

# GUIがぼやける現象を防ぐための関数
def make_dpi_aware():
  import ctypes
  import platform
  if int(platform.release()) >= 8:
    ctypes.windll.shcore.SetProcessDpiAwareness(True)


# 描画用の関数
def draw_figure(canvas, figure):
    figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
    figure_canvas_agg.draw()
    figure_canvas_agg.get_tk_widget().pack(side="top", fill="both", expand=1)
    return figure_canvas_agg

# ツリーデータ作成用関数
def get_tree_data(parent, dirname):
    treedata = sg.TreeData()

    # https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Tree_Element.py#L26
    def add_files_in_folder(parent, dirname):

        files = os.listdir(dirname)
        for f in files:
            fullname = os.path.join(dirname, f)
            if os.path.isdir(fullname):
                treedata.Insert(parent, fullname, f, values=[], icon=folder_icon)
                add_files_in_folder(fullname, fullname)
            else:

                treedata.Insert(parent, fullname, f, values=[
                                os.stat(fullname).st_size], icon=file_icon)

    add_files_in_folder(parent, dirname)

    return treedata

if __name__ == "__main__":
    make_dpi_aware()

    # ツリーのデータ作成
    treedata = get_tree_data("", os.getcwd())

    # メニュー
    menu_def = [["File", ["Open Folder"]]]

    # レイアウト作成
    layout = [[sg.Menu(menu_def)],
            [sg.Tree(data=treedata,
                headings=[],
                auto_size_columns=True,
                num_rows=24,
                col0_width=20,
                key="-TREE-",
                show_expanded=False,
                enable_events=True), sg.Canvas(key="-CANVAS-")]]

    window = sg.Window("CSV Viewer", layout, finalize=True, element_justification="center", font="Monospace 8", resizable=False)

    # 埋め込むfigを作成
    fig = plt.figure(figsize=(7, 5))
    ax = fig.add_subplot(111)

    # fig_agg作成
    fig_agg = draw_figure(window["-CANVAS-"].TKCanvas, fig)

    # イベントループ
    while True:
        event, values = window.read()
        # print(event, values)

        if event is None:
            break

        elif event == "-TREE-":
            # 前回描いたグラフを消す
            ax.cla()
            fig_agg.draw()

            # 選択されているファイルをそれぞれ開きプロットする
            try:
                for fname in values["-TREE-"]:
                    if not os.path.isdir(fname):
                        data = np.genfromtxt(fname, delimiter=",")
                        ax.plot(data)
            except Exception as e:
                sg.Print(e)

            # キャンバス更新
            fig_agg.draw()

        elif event == "Open Folder":
            # 表示するフォルダを変更する
            starting_path = sg.popup_get_folder("Folder to display")
            treedata = get_tree_data("", starting_path)
            window["-TREE-"].update(values=treedata)

    # ウィンドウを閉じる.
    window.close()

PySimpleGUI内にMatplotlibのプロットを埋め込む方法については,私が以前書いた記事で説明しています.
PySimpleGUIにMatplotlibを埋め込みたい

ファイルツリーの作り方については,PySimpleGUIのGitHubにあるデモプログラムを参考にしています.
https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Tree_Element.py

ファイルツリーはTree Elementというエレメントを利用しています.上のプログラムのget_tree_data関数はファイルツリーのデータを持ったTree Dataを返します.その後このTree DataをTree Elementに渡します.

get_tree_data関数の内側のall_files_in_folder関数は少し複雑かもしれません.指定されたフォルダ以下にある全てのフォルダ・ファイルを再帰的にTree Dataに登録する関数です.
表示するフォルダdirnameの内側にあるファイルとフォルダを順番に見ていき,もしファイルだったらファイルのアイコンを付けてツリーに挿入,もしフォルダであればフォルダのアイコンを付けてツリーに挿入し,さらにそのフォルダに対してall_files_in_folderを呼び出して再帰しています.

    def add_files_in_folder(parent, dirname):

        files = os.listdir(dirname)
        for f in files:
            fullname = os.path.join(dirname, f)
            if os.path.isdir(fullname):
                treedata.Insert(parent, fullname, f, values=[], icon=folder_icon)
                add_files_in_folder(fullname, fullname)
            else:

                treedata.Insert(parent, fullname, f, values=[
                                os.stat(fullname).st_size], icon=file_icon)


プログラムの最初にあるfolder_iconfile_iconは,ファイルとフォルダのアイコンに使う画像をBase64にエンコードしたものです.Base64については次の記事を参考にしてください.iconにはファイル名も指定できます.
base64ってなんぞ??理解のために実装してみた

Tree Elementを作る際にenable_event=Trueとすることで,ツリー内のデータ(つまりファイル名orフォルダ名)をクリックしたときにイベント("-TREE-")が発生するようになっています.

       elif event == "-TREE-":
            # 前回描いたグラフを消す
            ax.cla()
            fig_agg.draw()

            # 選択されているファイルをそれぞれ開きプロットする
            try:
                for fname in values["-TREE-"]:
                    if not os.path.isdir(fname):
                        data = np.genfromtxt(fname, delimiter=",")
                        ax.plot(data)
            except Exception as e:
                sg.Print(e)

            # キャンバス更新
            fig_agg.draw()

おわりに

このプログラム自体は大して役に立ちませんが,用途に応じてカスタマイズすることで便利なツールになると思います.例えばデータを読み込んだ後に何かしらの処理を加えてみたり,メニューからデータを表示する方法を変えられるようにしてみたり.さらにファイルツリーは必ずしも「ファイル」ツリーである必要はなく,例えばExcelファイル内のシートをツリーに表示させるみたいなこともできます.

PySimpleGUIはとてもシンプルなのに強力で,しかもプログラムを書いてて楽しいという最高のライブラリなのでぜひたくさんの人に使ってもらいたいです.

38
56
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
56