0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pygletでのデータ可視化関数備忘録

Last updated at Posted at 2024-12-19

ezgif-5-4a1c732d66.gif

 pygletを用いて、ウィンドウ形式で情報を可視化するコード。行列や棒グラフをリアルタイムで表示させる。

内容


 関数に登場するAppenderには[任意の登録用変数名].appendを入れる。(参考
 batchにはpygletの一括描画用バッチを入れる。
 具体例はサンプルを参照。

可視化関数

行列表示関数

shapeを使って、隙間付きで表示する

screenshot.76.jpg

def matrixDisplay(window :pyglet.window.Window, Appender, batch, matrix :np.array, x, y, width = 10, fill_rate = 0.8, high_color = (50,0,150), low_color = (240,240,240), Trans = False):
    lower_val = matrix.min()
    max_val = matrix.max() - lower_val
    if matrix.ndim == 1:  # 1次元配列の場合
        matrix = matrix.reshape(-1, 1)  # 2次元配列に変換
    else:
        matrix = np.flipud(matrix)
    if Trans:
        matrix = matrix.T

    for i in range(matrix.shape[0]):
        for j in range(matrix.shape[1]):
            level = (matrix[i,j]-lower_val)/(max_val+1e-16)
            interpolated_color = tuple([int(low_color[k]*(1 - level) + high_color[k]*level) for k in range(3)])
            Appender(pyglet.shapes.Rectangle(x+j*width, y+i*width, width*fill_rate, width*fill_rate, color=interpolated_color, batch=batch))
    # キャプション用の座標を返す
    name_x = max(0, min(window.width, x + matrix.shape[1] / 2.0 * width))
    name_y = max(0, min(window.height, y - 15))
    return (name_x, name_y)

画像に変換してSpriteとして表示する

screenshot.75.jpg

np.arrayで画像データ(大きさ二次元+RGB一次元)の三次元行列を作成、pygletのspriteとして表示する。

def matrixImageDisplay(window :pyglet.window.Window, Appender, batch, matrix :np.array, x, y, scale :float = 1.0, high_color = (50,0,150), low_color = (240,240,240), Trans = False):
    lower_val = matrix.min()
    max_val = matrix.max() - lower_val
    if matrix.ndim == 1:  # 1次元配列の場合
        matrix = matrix.reshape(-1, 1)  # 2次元配列に変換
    else:
        matrix = np.flipud(matrix)
    if Trans:
        matrix = matrix.T

    data = np.zeros((matrix.shape + (3,)), dtype=np.uint8)
    for i in range(matrix.shape[0]):
        for j in range(matrix.shape[1]):
            level = (matrix[i,j]-lower_val)/(max_val+1e-16)
            data[i,j] = [int(low_color[k]*(1 - level) + high_color[k]*level) for k in range(3)]
    # 行列を画像に変換する
    raw_image = pyglet.image.ImageData(data.shape[1], data.shape[0], 'RGB', data.tobytes())
    tempSprite = pyglet.sprite.Sprite(raw_image, x, y, batch=batch)
    # 拡大時に画像がぼやけるのを防止するコード
    pyglet.gl.glTexParameteri(pyglet.gl.GL_TEXTURE_2D, pyglet.gl.GL_TEXTURE_MAG_FILTER, pyglet.gl.GL_NEAREST)
    pyglet.gl.glTexParameteri(pyglet.gl.GL_TEXTURE_2D, pyglet.gl.GL_TEXTURE_MIN_FILTER, pyglet.gl.GL_NEAREST)
    # 画像の拡大
    tempSprite.scale = scale
    Appender(tempSprite)
    # キャプション用の座標を返す
    name_x = max(0, min(window.width, x + matrix.shape[1] / 2.0 * scale))
    name_y = max(0, min(window.height, y - 15))
    return (name_x, name_y)

棒グラフ

単純な棒グラフ

screenshot.79.jpg

一次元のnp.array配列を入れれば、それが棒グラフになる。

def barplotDisplay(window :pyglet.window.Window, Appender, batch, array, x, y, width = 30, height = 50, fill_rate = 0.8, bar_color = (50,50,255), line_color = (0,0,0), line_width = 1):
    array = array/array.sum()
    for i in range(len(array)):
       Appender(pyglet.shapes.Rectangle(x+i*width+int(width*(1-fill_rate)), y, width*fill_rate, int(array[i]*height), color=bar_color, batch=batch))
    Appender(pyglet.shapes.Line(x, y, x + len(array)*width, y, line_width, color = line_color, batch=batch))
    Appender(pyglet.shapes.Line(x, y, x, y + height, line_width, color = line_color, batch=batch))
    name_x = max(0, min(window.width, x + int(len(array)*width/2)))
    name_y = max(0, min(window.height, y + height + 20))
    return (name_x, name_y)

積み上げ棒グラフ

screenshot.77.jpg

二次元の配列np.arrayを代入。列が各棒に対応し、行が積み上げる値。
負の値は下方向に延びる。

def stackedBarplotDisplay(window :pyglet.window.Window, Appender, batch, matrix :np.array, x, y, width = 30, height = 50, fill_rate = 0.8, bar_color_pallete :list = [(220,220,50), (240,30,30), (50,200,50), (120,170,200), (200,100,220)], line_color = (0,0,0), line_width = 1):
    assert matrix.shape[1] <= len(bar_color_pallete), f"Y elements ({matrix.shape[1]}) is larger than Color pallete ({len(bar_color_pallete)}) Please make color pallete larger than Name List."
    posmax = np.where(matrix>0,matrix,0.).sum(axis=1).max()
    negmax = np.where(matrix<0,matrix,0.).sum(axis=1).min()
    scale :float = height / (posmax - negmax) if posmax != negmax else 0.
    baseline :int = int(y - negmax * scale)
    margin :int = int(width*(1-fill_rate))
    for i in range(matrix.shape[0]):
        posStack :float = 0.
        negStack :float = 0.
        for h in range(matrix.shape[1]): # 十分な数のcolor palleteを用意する必要がある
            if matrix[i,h] < 0:
                negStack += matrix[i,h] * scale
                Appender(pyglet.shapes.Rectangle(x+i*width+margin, baseline + negStack, width*fill_rate, -matrix[i,h]*scale, color=bar_color_pallete[h], batch=batch))
            elif matrix[i,h] > 0:
                Appender(pyglet.shapes.Rectangle(x+i*width+margin, baseline + posStack, width*fill_rate, matrix[i,h]*scale, color=bar_color_pallete[h], batch=batch))
                posStack += matrix[i,h] * scale
    Appender(pyglet.shapes.Line(x, baseline, x + matrix.shape[0]*width, baseline, line_width, color = line_color, batch=batch))
    Appender(pyglet.shapes.Line(x, y, x, y + height, line_width, color = line_color, batch=batch))
    name_x = max(0, min(window.width, x + int(matrix.shape[0]*width/2)))
    name_y = max(0, min(window.height, y + height + 20))
    return (name_x, name_y)

凡例用(凡例名をリスト形式で入力)

screenshot.80.jpg

def colorLegend(Appender, batch, x :int, y: int, name_list :list, font_name = 'Times New Roman', font_size = 9, rate = 1.5, color_pallete :list = [(220,220,50), (240,30,30), (50,200,50), (120,170,200), (200,100,220)], font_color = (0, 0, 0)):
    margin = int(font_size*rate)
    assert len(name_list) <= len(color_pallete), f"Name List length({len(name_list)}) is larger than Color pallete ({len(color_pallete)}) Please make color pallete larger than Name List."
    for i in range(len(name_list)):
        Appender(pyglet.shapes.Rectangle(x, y-i*margin-int(font_size*.5), font_size, font_size, color=color_pallete[i], batch=batch))
        Appender(pyglet.text.Label(name_list[i], font_name=font_name, font_size=font_size,
                                    x=x+margin, y=y-i*margin, color = font_color + (255,), #color and alpha
                                    anchor_x='left', anchor_y='center', batch=batch))

文字列表示

screenshot.78.jpg

black = (0, 0, 0)
eleAppender(pyglet.text.Label(f"Hello\nWorld\nT={T/3:.2f}",
    font_name='Times New Roman', font_size=11,
    x=620, y=window.height - 70, width = 240,
    color = black + (255,), anchor_x='left', anchor_y='center',
    multiline = True, batch=PygletBatch))

/nを使えば、改行が可能。このとき、multiline=Truewidth=240(所謂パワポのテキストボックスの横幅的なもの)を設定する必要がある。
また、black = (0, 0, 0)で色を変数化しているが、文字を描画する際は透明度Alphaが指定できるため、black + (255,) = (0, 0, 0, 255)でRGBAに変更している。

実行可能なサンプル

UI_Sample.py
import pyglet
import numpy as np
import random
import time

#========関数========

def matrixDisplay(window :pyglet.window.Window, Appender, batch, matrix :np.array, x, y, width = 10, fill_rate = 0.8, high_color = (50,0,150), low_color = (240,240,240), Trans = False):
    lower_val = matrix.min()
    max_val = matrix.max() - lower_val
    if matrix.ndim == 1:  # 1次元配列の場合
        matrix = matrix.reshape(-1, 1)  # 2次元配列に変換
    else:
        matrix = np.flipud(matrix)
    if Trans:
        matrix = matrix.T

    for i in range(matrix.shape[0]):
        for j in range(matrix.shape[1]):
            level = (matrix[i,j]-lower_val)/(max_val+1e-16)
            interpolated_color = tuple([int(low_color[k]*(1 - level) + high_color[k]*level) for k in range(3)])
            Appender(pyglet.shapes.Rectangle(x+j*width, y+i*width, width*fill_rate, width*fill_rate, color=interpolated_color, batch=batch))
    # キャプション用の座標を返す
    name_x = max(0, min(window.width, x + matrix.shape[1] / 2.0 * width))
    name_y = max(0, min(window.height, y - 15))
    return (name_x, name_y)

def matrixImageDisplay(window :pyglet.window.Window, Appender, batch, matrix :np.array, x, y, scale :float = 1.0, high_color = (50,0,150), low_color = (240,240,240), Trans = False):
    lower_val = matrix.min()
    max_val = matrix.max() - lower_val
    if matrix.ndim == 1:  # 1次元配列の場合
        matrix = matrix.reshape(-1, 1)  # 2次元配列に変換
    else:
        matrix = np.flipud(matrix)
    if Trans:
        matrix = matrix.T

    data = np.zeros((matrix.shape + (3,)), dtype=np.uint8)
    for i in range(matrix.shape[0]):
        for j in range(matrix.shape[1]):
            level = (matrix[i,j]-lower_val)/(max_val+1e-16)
            data[i,j] = [int(low_color[k]*(1 - level) + high_color[k]*level) for k in range(3)]
    # 行列を画像に変換する
    raw_image = pyglet.image.ImageData(data.shape[1], data.shape[0], 'RGB', data.tobytes())
    tempSprite = pyglet.sprite.Sprite(raw_image, x, y, batch=batch)
    # 拡大時に画像がぼやけるのを防止するコード
    pyglet.gl.glTexParameteri(pyglet.gl.GL_TEXTURE_2D, pyglet.gl.GL_TEXTURE_MAG_FILTER, pyglet.gl.GL_NEAREST)
    pyglet.gl.glTexParameteri(pyglet.gl.GL_TEXTURE_2D, pyglet.gl.GL_TEXTURE_MIN_FILTER, pyglet.gl.GL_NEAREST)
    # 画像の拡大
    tempSprite.scale = scale
    Appender(tempSprite)
    # キャプション用の座標を返す
    name_x = max(0, min(window.width, x + matrix.shape[1] / 2.0 * scale))
    name_y = max(0, min(window.height, y - 15))
    return (name_x, name_y)

def barplotDisplay(window :pyglet.window.Window, Appender, batch, array, x, y, width = 30, height = 50, fill_rate = 0.8, bar_color = (50,50,255), line_color = (0,0,0), line_width = 1):
    array = array/array.sum()
    for i in range(len(array)):
       Appender(pyglet.shapes.Rectangle(x+i*width+int(width*(1-fill_rate)), y, width*fill_rate, int(array[i]*height), color=bar_color, batch=batch))
    Appender(pyglet.shapes.Line(x, y, x + len(array)*width, y, line_width, color = line_color, batch=batch))
    Appender(pyglet.shapes.Line(x, y, x, y + height, line_width, color = line_color, batch=batch))
    name_x = max(0, min(window.width, x + int(len(array)*width/2)))
    name_y = max(0, min(window.height, y + height + 20))
    return (name_x, name_y)

def stackedBarplotDisplay(window :pyglet.window.Window, Appender, batch, matrix :np.array, x, y, width = 30, height = 50, fill_rate = 0.8, bar_color_pallete :list = [(220,220,50), (240,30,30), (50,200,50), (120,170,200), (200,100,220)], line_color = (0,0,0), line_width = 1):
    assert matrix.shape[1] <= len(bar_color_pallete), f"Y elements ({matrix.shape[1]}) is larger than Color pallete ({len(bar_color_pallete)}) Please make color pallete larger than Name List."
    posmax = np.where(matrix>0,matrix,0.).sum(axis=1).max()
    negmax = np.where(matrix<0,matrix,0.).sum(axis=1).min()
    scale :float = height / (posmax - negmax) if posmax != negmax else 0.
    baseline :int = int(y - negmax * scale)
    margin :int = int(width*(1-fill_rate))
    for i in range(matrix.shape[0]):
        posStack :float = 0.
        negStack :float = 0.
        for h in range(matrix.shape[1]): #need enough color pallete for h
            if matrix[i,h] < 0:
                negStack += matrix[i,h] * scale
                Appender(pyglet.shapes.Rectangle(x+i*width+margin, baseline + negStack, width*fill_rate, -matrix[i,h]*scale, color=bar_color_pallete[h], batch=batch))
            elif matrix[i,h] > 0:
                Appender(pyglet.shapes.Rectangle(x+i*width+margin, baseline + posStack, width*fill_rate, matrix[i,h]*scale, color=bar_color_pallete[h], batch=batch))
                posStack += matrix[i,h] * scale
    Appender(pyglet.shapes.Line(x, baseline, x + matrix.shape[0]*width, baseline, line_width, color = line_color, batch=batch))
    Appender(pyglet.shapes.Line(x, y, x, y + height, line_width, color = line_color, batch=batch))
    name_x = max(0, min(window.width, x + int(matrix.shape[0]*width/2)))
    name_y = max(0, min(window.height, y + height + 20))
    return (name_x, name_y)

def colorLegend(Appender, batch, x :int, y: int, name_list :list, font_name = 'Times New Roman', font_size = 9, rate = 1.5, color_pallete :list = [(220,220,50), (240,30,30), (50,200,50), (120,170,200), (200,100,220)], font_color = (0, 0, 0)):
    margin = int(font_size*rate)
    assert len(name_list) <= len(color_pallete), f"Name List length({len(name_list)}) is larger than Color pallete ({len(color_pallete)}) Please make color pallete larger than Name List."
    for i in range(len(name_list)):
        Appender(pyglet.shapes.Rectangle(x, y-i*margin-int(font_size*.5), font_size, font_size, color=color_pallete[i], batch=batch))
        Appender(pyglet.text.Label(name_list[i], font_name=font_name, font_size=font_size,
                                    x=x+margin, y=y-i*margin, color = font_color + (255,), #color and alpha
                                    anchor_x='left', anchor_y='center', batch=batch))

#================

config = pyglet.gl.Config(double_buffer=True)
#visible=Falseで作成してすぐにはウィンドウを表示しない
window = pyglet.window.Window(640, 480, visible=False, config=config, resizable=False)
window.set_caption('UI Test') #ウィンドウのタイトル
pyglet.gl.glClearColor(1,1,1,1) #ウィンドウの背景色 Red,Green,Blue,Alpha 0.0~1.0で指定
window.set_location(20,50) #ウィンドウの位置を100, 200の位置に取る。自身の画面に応じて変えること

PygletBatch = pyglet.graphics.Batch() #バッチの定義

black = (0, 0, 0) #黒色

dt = 0 #ループ毎のΔt
LoopFlag = True #メインループの終了判定用フラグ
PauseFlag = False #一時停止判定用フラグ

@window.event
def on_close(): #ウィンドウの×ボタンがクリックされたときのイベント
    global LoopFlag #外の変数にアクセスするために必要
    LoopFlag = False #ループフラグを折ってループから抜ける。

@window.event
def on_draw(): #windowのイベントとして描画処理を書く
    window.clear()
    PygletBatch.draw() #描写

@window.event
def on_key_press(symbol, modifiers): #押されたときのみイベントが発生する
    global LoopFlag
    if symbol == pyglet.window.key.ESCAPE:
        LoopFlag = False #ESCキーが押されたらループフラグを折ってループから抜ける。

@window.event
def on_mouse_press(x, y, button, modifiers): #ウィンドウをクリックするとイベントが発生する
    global PauseFlag
    if button == pyglet.window.mouse.LEFT:
        PauseFlag = not PauseFlag #左クリックされたら一時停止

#変数
T :int = 0

window.set_visible() #ここでウィンドウを表示
while LoopFlag: #メインループ
    dt = pyglet.clock.tick() #前回tick()を呼び出してから経った時間(秒)を返す
    clockTime = time.time() #現時刻(秒)を取得
    elementList = [] #図形登録用リスト
    Appender = elementList.append #登録用の関数

    #文字
    Appender(pyglet.text.Label(f'T={T}\ndt={dt:.4f}', #\nで改行
                    font_name='Times New Roman', font_size=24, x=20, y=window.height - 20, width=240, color = black + (255,), #RGB+Alpha
                    anchor_x='left', anchor_y='top', multiline = True, batch=PygletBatch))
    if not PauseFlag: T += 1

    #行列
    A_matrix = np.array([[random.random()*5, random.random()*5, random.random()*5, random.random()*5],[0,5,0,0],[0,2,5,2]]) if not PauseFlag else A_matrix
    # 画像として表示
    matrixImageDisplay(window, Appender, PygletBatch, A_matrix, 120, window.height - 150, 15.0)
    # shapesを使って表示(要素間に余白アリ)
    matrixNamePos = matrixDisplay(window, Appender, PygletBatch, A_matrix, 220, window.height - 150, 15.0, fill_rate=0.8)
    #行列の下にキャプションをつける
    Appender(pyglet.text.Label('matrix', font_name='Times New Roman', font_size=9, x=matrixNamePos[0], y=matrixNamePos[1], color = black + (255,), anchor_x='center', anchor_y='center', batch=PygletBatch))

    #棒グラフ
    barplotDisplay(window, Appender, PygletBatch, np.array([50, 50+T%50, 30, 20]), 20, window.height - 150, line_width=2, width = 10)
    #積み上げグラフ
    stackedBarplotDisplay(window, Appender, PygletBatch, A_matrix, 330, window.height - 150, line_width=2, height = 100, width = 10)
    # 凡例
    colorLegend(Appender, PygletBatch, 300, window.height - 60, ["A","B","C","D"])

    #25ms程度経過するように待つ
    time.sleep(max(0.0, 0.025 - (clockTime - time.time())))

    # pygletのイベント実行
    windowlist = list(pyglet.app.windows)
    for w in windowlist: #現在存在するwindow全てについて更新
        w.switch_to()
        w.dispatch_events()
        w.dispatch_event('on_draw')
        w.flip()

window.close() #ループを抜けたら終了
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?