PySide2 / PyQt5

PyQt5+PyQtGraph:3D Surface 秀圖 GUI 視窗介面

前言

一段時間沒寫文章了,沒有持續新增文章果然會惰性大發阿,小弟我也還在學習階段,總是要有學到新東西也才有新的筆記可以寫麻XD。

如題,今天的內容算是分享吧,自己也還在研究中,這個項目就是如何使用 Python 做出 3D 繪圖視窗。

如果有用 MATLAB 去畫 mesh 或是 surface view 的圖形資料,肯定對這樣的介面不陌生,而在 Python 中也能透過 Matplotlib 這個套件去畫出 3D 圖出來,進行三維的圖像分析,但相信用過就知道,那個效能完全是無法實際運用的..

因此今天要來分享的是使用我最常用的 PyQt5 做出基本 UI 介面,再加上 PyQtGraph 這個 GUI 函式庫來做出一個秀圖視窗。

基本介紹

PyQt5

基於 Qt 的 Python GUI Toolkit,簡單講就是可以用 Python 程式建構出完整功能介面的 GUI 工具,基本上我都是用這套工具去設計自己的 Python 軟體出來,以後有時間再慢慢分享 (主要是我發現我 CNN 的東西沒人看,反而 GUI 一堆人看,明明 PyQt 的部分還沒寫很多文章阿~ 太神奇了>.<)

如果還不知道怎麼配置環境的人,可以去看我之前的文章
PyQt5:使用 VS Code 來開發 PyQt 的 GUI 程式
PyQt5:完成一個 WebCam GUI 程式

PyQtGraph

這是基於 PyQt/PySide 的 GUI 套件,基本上就是 PyQt 的強化版,而且文檔也非常完整,再加上他有自己的範例程式可以執行,理論上可以降低製作 GUI 的門檻,我也是透過這個工具庫,才能完成稍微像樣一點的功能出來。

套件安裝

基本上我都是在 Windows 撰寫程式,搭配 Anaconda/Miniconda 來簡化 Python 環境安裝,如果真的安裝失敗可以留言跟我說,我再補上我的環境的套件版本號。(依照經驗都是版本錯誤才會踩坑ㄎㄎ)

pip install PyQt5
pip install PyQtGraph

PyQtGraph Examples

關於剛剛說的範例程式,如下圖
file

如果想要叫出此範例只要執行兩行程式碼即可

from pyqtgraph import examples
examples.run()

其實這次做的東西也是從這邊的範例弄出來的,當然要好好介紹一下囉~

首先,左邊是 PyQtGraph 範例程式的清單,右邊你選中範例的程式碼,如果要看執行結果就是點 Run Example,也可以直接點擊左邊清單的項目就會執行了…..恩??,沒有錯啦,講完了呵呵。
執行範例就是這麼簡單,不然哪能叫範例,對不對www。
(Note:如果不能執行多半是環境沒裝好><。)

老毛病又犯了,每次都講了一堆東西才進入正題ㄏㄏ

3D 秀圖

如果對 Python 及 PyQt 程式有一定瞭解,可以直接去看我的 Github,裡面除了這裡的範例外,也有測試其他秀圖的範例程式。

PyQt 嵌入 Matplotlib

給你各位看看我用 PyQt 嵌入 Matplotlib 的結果

file

實際在 PyQt 中嵌入 Matplotlib 的效能非常差,但是我資料量並不大 (大約40x40px),用滑鼠拖移會感受到明顯延遲,導致使用體驗也很差。基於效能考量,決定另尋其他方案,最後就開始嘗試使用 PyQtGraph 來製作 3D 秀圖。

對此 PyQt 嵌入 Matplotlib 有興趣也可以去我的 Github 查看,不過這隻程式 Google 就找的到了ㄏㄏ。

PyQtGraph 3D Graphics

關於 PyQtGraph 裡面的 3D 圖形範例,可分為 Volumetric、Isosurface、Surface Plot、Scatter Plot、Shaders、Line Plot、Mesh、Image、Text,這些在 PyQtGraph Examples 都可以找到。

今天會講的主要是 "GLSurfacePlot Example" 這個範例程式,先來看範例程式的執行結果
真心覺得這結果有夠炫泡,真夠酷的!!

file

上圖的範例程式就請自己去看 PyQtGraph Examples 的範例程式囉~~~

接下來,會以我自己從 "GLSurfacePlot Example" 範例改出來的程式來說明。

GLSurfacePlot

我的主要目標是利用 Python 建構一套可以 Real-time 的 3D 秀圖,此秀圖需要使用 jet colormap,由於小羔羊我能力不足QAQ,實在很難手刻出這樣的功能…,好在 Python 總能讓我發現強大套件,我認為 PyQtGraph 這套 GUI 工具絕對是最適合快速建構出 3D 秀圖的方案。

不囉嗦,直接先上程式。

Note: 因為我直接改範例,因此有些變數名稱沒有修改。

程式碼 (Github)

from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
import numpy as np
import time
import matplotlib.pyplot as plt

## Create a GL View widget to display data
app = pg.mkQApp("GLSurfacePlot Example")
w = gl.GLViewWidget()
w.show()
w.setWindowTitle('pyqtgraph example: GLSurfacePlot')
w.setCameraPosition(distance=100)

x = np.linspace(-50, 50, 500)
y = np.linspace(-50, 50, 500)
d = (x.reshape(500,1)**2 + y.reshape(1,500)**2) * 0.1
d2 = d ** 0.5 + 0.5
## precompute height values for all frames
phi = np.arange(0, np.pi*2, np.pi/20.)
z = np.sin(d[np.newaxis,...] + phi.reshape(phi.shape[0], 1, 1)) / d2[np.newaxis,...]
print(x.shape)
print(y.shape)

cmap = plt.get_cmap('jet')

t1 = time.time()
minZ=np.min(z)
maxZ=np.max(z)
print(minZ, maxZ, z.shape)
rgba_img = cmap((z[0]*5-minZ)/(maxZ -minZ))
print(rgba_img.shape)
print(z.shape)

p4 = gl.GLSurfacePlotItem(x=x, y=y, colors=rgba_img, computeNormals=False)
w.addItem(p4)
t2 = time.time()
print(t2-t1)

index = 0
def update():
    global p4, z, index
    p4.setData(z=z[index%z.shape[0]]*5)
    index += 1

timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(10)

if __name__ == '__main__':
    pg.exec()

程式解說

PyQtGraph 的 3D 繪圖是基於 pyopengl 來繪製,根據官方文件未來會棄用,對我而言能用就行了XD。

建構 3D Surface 秀圖視窗
要完成 3D Surface 秀圖,有三個關鍵指令:

  • w = gl.GLViewWidget(): 建立 OpenGL 的視窗元件 ‘w’。
  • p4 = gl.GLSurfacePlotItem(): 建立 3D Surface Plot 的物件 ‘p4’。
  • w.addItem(p4): 在 ‘w’ 中加入 GLSurfacePlotItem 物件 ‘p4’。

填入資料來顯示 3D Surface Plot

3D Surface Plot 主要使用 GLSurfacePlotItem 類別來建立秀圖,相關說明可以參考這裡,在此範例中需要先設定一些參數。

GLSurfacePlotItem 參數說明:

  • xy: 設定 X、Y 網格位置。[都是 1D-Array]
  • z: 每個網格頂點的高度/強度值。[1D-Array]
  • colors: 設定頂點顏色 (vertex colors)。[3D-Array = (width, height, 4)]
  • computeNormals: 計算法向量渲染。可以設 computeNormals=False 來節省網格更新的時間。

GLSurfacePlotItem 方法:
GLSurfacePlotItem(x=None, y=None, z=None, colors=None, **kwds)

  • __init__(x=None, y=None, z=None, colors=None, **kwds)
    此初始化將會傳遞參數給 setData() 來繪圖。
  • setData(x=None, y=None, z=None, colors=None)
    用來更新資料。

init() 是當呼叫類別時,就會自動執行的 python 類別方法。(白話文就是,他會處理你的參數)
例如:

class human:
    def __init__(self, name):
        print("name:", name)  # 初始化的名子
    def rename(self, name):
        print("rename", name) # 呼叫方法來命名
man = human("John")
man.rename("Frank")

將會印出

John
Frank

GLSurfacePlotItem 即時更新部分程式碼

# 定義資料
x = np.linspace(-50, 50, 500)
y = np.linspace(-50, 50, 500)
d = (x.reshape(500,1)**2 + y.reshape(1,500)**2) * 0.1
d2 = d ** 0.5 + 0.5
phi = np.arange(0, np.pi*2, np.pi/20.)
z = np.sin(d[np.newaxis,...] + phi.reshape(phi.shape[0], 1, 1)) / d2[np.newaxis,...]

# 設定頂點顏色
cmap = plt.get_cmap('jet')
minZ = np.min(z)
maxZ = np.max(z)
rgba_img = cmap( (z[0]*5-minZ)/(maxZ -minZ) )

# 建立 GLSurfacePlotItem 物件
p4 = gl.GLSurfacePlotItem(x=x, y=y, colors=rgba_img, computeNormals=False)

# 資料更新函式
index = 0
def update():
    global p4, z, index
    p4.setData(z=z[index%z.shape[0]]*5) # setData 是 GLSurfacePlotItem 用來更新資料的方法
    index += 1

# 即時更新資料
timer = QtCore.QTimer()  # 計時器
timer.timeout.connect(update)
timer.start(10)

程式執行結果

file

Refer

留下一個回覆

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *