Python – 用matplotlib畫出互動式圖像 [1]

這只是一般的圖片。

在進行資料的視覺化時,我們很常會使用散佈圖來呈現,但是當我們想要知道圖片上特定的點其背後所代表的資料、圖像時,我們除了從座標去回推之外,可以透過hover annotation的方式來更直觀的讓使用者了解到該資料中所蘊含之資訊。

簡單來說,該功能就是使用matplotlib.pyplot畫圖,當滑鼠游標經過該資料點時,於其周圍顯示出該資料點之資訊,就像是下列影片中所展示的功能:

這個討論串中有大大提供了一個簡單的範例程式,有興趣的讀者可以直接根據本程式碼來進行修改使用。

底下為分部詳解,有能力的讀者可以跳過:

import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import numpy as np; np.random.seed(42)
# Generate data x, y for scatter and an array of images.
x = np.arange(20)
y = np.random.rand(len(x))
arr = np.empty((len(x),10,10))
for i in range(len(x)):
f = np.random.rand(5,5)
arr[i, 0:5,0:5] = f
arr[i, 5:,0:5] =np.flipud(f)
arr[i, 5:,5:] =np.fliplr(np.flipud(f))
arr[i, 0:5:,5:] = np.fliplr(f)
# create figure and plot scatter
fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot(x,y, ls=“”, marker=“o”)
# create the annotations box
im = OffsetImage(arr[0,:,:], zoom=5)
xybox=(50, 50)
ab = AnnotationBbox(im, (x[0],y[0]), xybox=xybox, xycoords=‘data’,
boxcoords=“offset points”, pad=0.3, arrowprops=dict(arrowstyle=“->”))
# add it to the axes and make it invisible
ax.add_artist(ab)
ab.set_visible(False)
def hover(event):
# if the mouse is over the scatter points
if line.contains(event)[0]:
# find out the index within the array from the event
ind, = line.contains(event)[1][“ind”]
# get the figure size
w,h = fig.get_size_inches()*fig.dpi
ws = (event.x > w/2.)*1 + (event.x <= w/2.)
hs = (event.y > h/2.)*1 + (event.y <= h/2.)
# if event occurs in the top or right quadrant of the figure,
# change the annotation box position relative to mouse.
ab.xybox = (xybox[0]*ws, xybox[1]*hs)
# make annotation box visible
ab.set_visible(True)
# place it at the position of the hovered scatter point
ab.xy =(x[ind], y[ind])
# set the image corresponding to that point
im.set_data(arr[ind,:,:])
else:
#if the mouse is not over a scatter point
ab.set_visible(False)
fig.canvas.draw_idle()
# add callback for mouse moves
fig.canvas.mpl_connect(‘button_press_event’, hover)
view raw hover_demo.py hosted with ❤ by GitHub

直接執行的話,會得到本文最一開始的圖片,將滑鼠移到點上時,就會在旁邊顯示出該點所連結到的圖片檔。

當然,matplotlib本身也有提供範例

接下來進行本範例程式碼的說明:

import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import numpy as np; np.random.seed(42)
# Generate data x, y for scatter and an array of images.
x = np.arange(20)
y = np.random.rand(len(x))
arr = np.empty((len(x),10,10))
for i in range(len(x)):
f = np.random.rand(5,5)
arr[i, 0:5,0:5] = f
arr[i, 5:,0:5] =np.flipud(f)
arr[i, 5:,5:] =np.fliplr(np.flipud(f))
arr[i, 0:5:,5:] = np.fliplr(f)
view raw hover_part_1.py hosted with ❤ by GitHub

首先我們一樣是將Library給匯入,除了常見的plt跟np之外呢,是這次功能的主要腳色:OffsetImage、AnnotationBbox。

我在讀這功能時,一直想到我怎樣都學不會的Bbox,不好意思扯遠了。

接下來,我們要畫散佈圖,那就必須要有資料,於是我們先將x令為一從0-19,共含20個數字的array,而y則是一含有20個隨機數字的array,len(x)只是為了方便,如果想要看更多點的話只要改變x = np.arange(20)中的數字即可。

接下來將20張10×10的隨機影像儲存在arr這個變數裡。製作方式,是先產生一個5×5的隨機矩陣,之後上下、左右翻轉填入先前製作好的空矩陣裡。

# create figure and plot scatter
fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot(x,y, ls=“”, marker=“o”)
view raw hover_part_2.py hosted with ❤ by GitHub

這一段就很直白了,先建立一個畫框叫做fig,在fig裡面加上一個繪圖區叫做ax,最後在ax裡面以x,y資料畫圖。

這邊值得一提的是,到底111代表什麼? 其實就是 1×1 張圖中的第1張。你也可以改成234這樣就是2×3張圖中的第四張。

在plot函數裡ls代表linestyle,這邊填上「””」表示不用畫線,因為plot這個函數是用來畫折線圖的,我們偷吃步假裝它是散佈圖。而marker就是代表點的圖示,有以下選擇:

上述三行執行完之後會得到這個靜態圖片:

靜態的,還無法動喔!
# create the annotations box
im = OffsetImage(arr[0,:,:], zoom=5)
xybox=(50., 50.)
ab = AnnotationBbox(im, (50,50), xybox=xybox, xycoords=‘data’,
boxcoords=“offset points”, pad=0.3, arrowprops=dict(arrowstyle=“->”))
# add it to the axes and make it visible
ax.add_artist(ab)
ab.set_visible(True)
view raw hover_part_3.py hosted with ❤ by GitHub

這一段,則是製作要顯示在點旁邊的小圖示。首先我們將先前製作的隨機圖片中的第一個 (也就是arr[ 0 , : , : ])轉換成OffsetImage這個Class底下的一個物件,並稱之為im。

ab,是AnnotationBbox這個Class底下的一個物件,裡面包含了上面製作的im。 (50,50)表示該AnnotationBbox的位置,xybox則是距離該標示點的距離,不過第一個參數在底下的程式裡都會不斷的改變,所以在這邊只是先給一個初始值而已,並不太重要。而xycoords在官方文件中的說明如下:

簡單來說呢,用預設值data就好了。boxcoords後面的參數代表了使用與畫圖時相同的座標軸。pad則是邊示圖片的白色邊框大小。arrowprops則是指向該標註點的箭頭相關設定,想要修改的朋友可以往這邊走: 點我

最後兩行程式碼,則是在ax中加入我們剛剛製作好的小箱子,並使之顯示。

到這邊為止的範例程式碼如下:

import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import numpy as np; np.random.seed(42)
# Generate data x, y for scatter and an array of images.
x = np.arange(20)
y = np.random.rand(len(x))
arr = np.empty((len(x),10,10))
for i in range(len(x)):
f = np.random.rand(5,5)
arr[i, 0:5,0:5] = f
arr[i, 5:,0:5] =np.flipud(f)
arr[i, 5:,5:] =np.fliplr(np.flipud(f))
arr[i, 0:5:,5:] = np.fliplr(f)
# create figure and plot scatter
fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot(x,y, ls=“”, marker=“o”)
# create the annotations box
im = OffsetImage(arr[0,:,:], zoom=5)
xybox=(50, 50)
ab = AnnotationBbox(im, (x[0],y[0]), xybox=xybox, xycoords=‘data’,
boxcoords=“offset points”, pad=0.3, arrowprops=dict(arrowstyle=“->”))
# add it to the axes and make it invisible
ax.add_artist(ab)
ab.set_visible(True)
view raw hover_part_4.py hosted with ❤ by GitHub

執行完畢之後,會得到下圖:

一張有標註,但是無法隨著游標改變的靜態圖。

接下來就是我們的重頭戲了,該如何將互動性的功能給加上去

def hover(event):
# if the mouse is over the scatter points
if line.contains(event)[0]:
# find out the index within the array from the event
ind, = line.contains(event)[1][“ind”]
# get the figure size
w,h = fig.get_size_inches()*fig.dpi
ws = (event.x > w/2.)*1 + (event.x <= w/2.)
hs = (event.y > h/2.)*1 + (event.y <= h/2.)
# if event occurs in the top or right quadrant of the figure,
# change the annotation box position relative to mouse.
ab.xybox = (xybox[0]*ws, xybox[1]*hs)
# make annotation box visible
ab.set_visible(True)
# place it at the position of the hovered scatter point
ab.xy =(x[ind], y[ind])
# set the image corresponding to that point
im.set_data(arr[ind,:,:])
else:
#if the mouse is not over a scatter point
ab.set_visible(False)
fig.canvas.draw_idle()
view raw hover_part_5.py hosted with ❤ by GitHub

我們自定義一個function 名叫hover,這個名字隨便我們取,你喜歡讓程式碼裡充滿屎尿的話也可以叫做shit。

其實每一行都有完整的註解了,那我這邊就不再多贅述。

最精華的,在這一行:

fig.canvas.mpl_connect(‘motion_notify_event’, hover)
view raw hover_part_6.py hosted with ❤ by GitHub

將我們在上頭所製作的fig給連結到我們方才定義的function,那當什麼時候要執行該hover功能呢?就是 “motion_notify_event”,它代表了當滑鼠有動作時,就呼叫後面的hover功能,當然也可以改,以下是可以選擇的列表:

如果今天我們替換成’button_press_event’,那就變成點擊之後才會顯示圖片,可以試試看。

好,以上就是如何製作互動式圖片的方式,那實際應用在Deep Learning中的話,就是在畫完t-SNE之後,可以透過滑鼠移動,來觀察特定群聚之間的圖片特性。

有任何問題,歡迎寫信給我。

發佈留言

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

error: 我鎖起來惹 ˊ_>ˋ