pygletを用いて、ウィンドウ形式で情報を可視化するコード。行列や棒グラフをリアルタイムで表示させる。
内容
関数に登場するAppenderには[任意の登録用変数名].appendを入れる。(参考)
batchにはpygletの一括描画用バッチを入れる。
具体例はサンプルを参照。
可視化関数
行列表示関数
shapeを使って、隙間付きで表示する
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として表示する
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)
棒グラフ
単純な棒グラフ
一次元の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)
積み上げ棒グラフ
二次元の配列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)
凡例用(凡例名をリスト形式で入力)
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))
文字列表示
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=True
とwidth=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() #ループを抜けたら終了