実行サンプル
tcl/tkのCanvasで グラフ・画像表示する
また、マウスドラッグで画像から範囲指定する(四角形を表示)
実行環境
windows10
python3.9.5
pythonモジュール tcl/tk
実行手順
ソースコードを作成する
windowsコマンドプロンプトでプログラム作成したフォルダに移動し、下記実行する
python プログラム名.py
参考 URL
なし
ソースコード
このページの例でのソースコードは以下
https://github.com/sakurataiko1/python_sample/tree/main/sample_tcltk/sample_canvas_matplotlib_mouseDrag
参考まで canvasを2つにした例
https://github.com/sakurataiko1/python_sample/tree/main/sample_tcltk/sample_canvas_matplotlib_mouseDrag_plot2
MainWindow.py
import tkinter as tk
from tkinter import ttk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import pyplot as plt
import cv2
from matplotlib.patches import Rectangle # 矩形描画
from PlotWidget import PlotWidget
class GraphApp:
def __init__(self, root):
self.root = root
self.root.title("Matplotlib Graph with Rectangle Drawing")
# 部品定義: GUI全体レイアウトのためのフレーム 縦にならべる(row)
self.frame00 = tk.Frame(self.root)
self.frame01 = tk.Frame(self.root)
# 部品配置
self.frame00.grid(row=0, column=0, padx=5, pady=5, sticky='nsew')
self.frame01.grid(row=1, column=0, padx=5, pady=5, sticky='nsew')
# ユーザーによる画面サイズ変更に追従して、GUI部品も伸縮するようにする
for i in range(2):
self.root.columnconfigure(i, weight=1) # 横方向へのサイズ伸縮
self.root.rowconfigure(1, weight=1) # 縦方向へのサイズ伸縮 は canvas=plotwidget配置する段のみ
# 部品定義 1段目
self.button_clear = ttk.Button(self.frame00, text="画面クリア", command=self.func_on_button_clear_clicked)
self.button_plot1 = ttk.Button(self.frame00, text="Plot1", command=self.func_on_button_plot1_clicked)
self.button_image1 = ttk.Button(self.frame00, text="image1", command=self.func_on_button_image1_clicked)
self.button_graphAndImage1 = ttk.Button(self.frame00, text="plot and Image", command=self.func_on_button_graphAndImage1_clicked)
# 部品配置 1段目
self.button_clear.pack(side='left', padx=2)
self.button_plot1.pack(side='left', padx=2)
self.button_image1.pack(side='left', padx=2)
self.button_graphAndImage1.pack(side='left', padx=2)
# レイアウト2段目:Matplotlibのグラフを表示するCanvas
# 部品定義 2段目
self.plot_canvas1 = PlotWidget(self.frame01)
# 部品配置 2段目
self.plot_canvas1.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
def func_on_button_clear_clicked(self):
# Canvasのクリア
self.plot_canvas1.ax.clear()
def func_on_button_plot1_clicked(self):
# Canvasのクリア
self.plot_canvas1.ax.clear()
# 描画
self.func_plot_graph1()
def func_on_button_image1_clicked(self):
# Canvasのクリア
self.plot_canvas1.ax.clear()
# 描画
self.func_draw_image1()
def func_on_button_graphAndImage1_clicked(self):
# Canvasのクリア
self.plot_canvas1.ax.clear()
# グラフ描画
self.func_plot_graph1()
# 画像描画
self.func_draw_image1()
def func_plot_graph1(self):
# グラフを描画するダミーのデータ
x = [1, 2, 3, 4, 5]
y = [2, 3, 5, 7, 11]
# Canvasのクリア
#self.plot_canvas1.ax.clear()
# グラフ描画
self.plot_canvas1.ax.plot(x, y)
# Canvasに反映
self.plot_canvas1.canvas.draw()
def func_draw_image1(self):
# Canvasのクリア
#self.plot_canvas1.ax.clear()
# 画像を描画する グラフに重ねる
workfilepath = "test_image01.png"
xlim = self.plot_canvas1.ax.get_xlim()
ylim = self.plot_canvas1.ax.get_ylim()
self.plot_canvas1.func_addImage_cv2(workfilepath, xlim, ylim)
self.plot_canvas1.canvas.draw() # 描画反映
if __name__ == "__main__":
root = tk.Tk()
app = GraphApp(root)
root.mainloop()
PlotWidget.py
import tkinter as tk
from tkinter import ttk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import pyplot as plt
import cv2
from matplotlib.patches import Rectangle # 矩形描画
class PlotWidget(tk.Canvas):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
#org-ok# self.fig = Figure(figsize=(5, 4), dpi=100)
#org-ok# self.ax = self.fig.add_subplot(111)
self.fig, self.ax = plt.subplots()
#self.canvas = FigureCanvas(self.fig)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas_widget = self.canvas.get_tk_widget()
self.canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.canvas_widget.bind("<Button-1>", self.func_on_canvas_click)
self.canvas_widget.bind("<Button1-Motion>", self.func_mouse_onMotion) # 画面上でマウスドラッグした場合のイベント
self.canvas_widget.bind("<ButtonRelease-1>", self.func_mouse_onRelease) # 画面上でマウスボタンを離した場合のイベント
self.canvas.draw()
# 矩形描画のための変数
self.rect_start = None
self.rect_end = None
self.rect_id = None
# マウスドラッグで矩形描画のための変数
self.pressed = False # Lock to stop the motion event from behaving badly when the mouse isn't pressed
self.rect = Rectangle((0, 0), 1, 1, facecolor='None', edgecolor='green')
self.x1 = None
self.y1 = None
self.x2 = None
self.y2 = None
self.ax.add_patch(self.rect) # matplotlib への紐づけ
def func_on_canvas_click(self, event):
# Canvas上でマウスクリック時の処理
# マウス 2点クリックで矩形描画の場合
if event.x is not None and event.y is not None:
# Upon initial press of the mouse record the origin and record the mouse as pressed
self.pressed = True
self.rect.set_linestyle('dashed')
self.x1 = event.x
self.y1 = event.y
# 前回描いたものを消す
if self.rect_id is not None:
self.canvas_widget.delete(self.rect_id)
def func_draw_rectangle_Drag(self):
# 矩形を描画
x1, y1 = self.rect_start
x2, y2 = self.rect_end
self.rect_id = self.canvas_widget.create_rectangle(x1, y1, x2, y2, outline="red", width=2)
def func_mouse_onMotion(self, event):
# マウスドラッグ時の処理
'''Callback to handle the motion event created by the mouse moving over the canvas'''
# 輪郭取得 通常モード 矩形で範囲選択 mouseMode == RectSelect
# If the mouse has been pressed draw an updated rectangle when the mouse is moved so
# the user can see what the current selection is
if self.pressed:
# Check the mouse was released on the canvas, and if it wasn't then just leave the width and
# height as the last values set by the motion event
if event.x is not None and event.y is not None:
self.x2 = event.x
self.y2 = event.y
# Set the width and height and draw the rectangle
self.rect.set_width(self.x2 - self.x1)
self.rect.set_height(self.y2 - self.y1)
self.rect.set_xy((self.x1, self.y1))
# 描画反映
# 前回描いたものを消す
if self.rect_id is not None:
self.canvas_widget.delete(self.rect_id)
# 今回クリックされた座標に描く
self.rect_id = self.canvas_widget.create_rectangle(self.x1, self.y1, self.x2, self.y2, outline="green", width=2)
# -end- if self.pressed:
def func_mouse_onRelease(self, event):
# マウスボタンが離された時の処理
# 輪郭取得 通常モード 矩形で範囲選択 mouseMode == RectSelect
'''Callback to handle the mouse being released over the canvas'''
if self.pressed:
# Upon release draw the rectangle as a solid rectangle
#
# Check that the mouse was actually pressed on the canvas to begin with and this isn't a rouge mouse
# release event that started somewhere else
self.pressed = False
self.rect.set_linestyle('solid')
# Check the mouse was released on the canvas, and if it wasn't then just leave the width and
# height as the last values set by the motion event
if event.x is not None and event.y is not None:
self.x2 = event.x
self.y2 = event.y
# Set the width and height and origin of the bounding rectangle
self.boundingRectWidth = self.x2 - self.x1
self.boundingRectHeight = self.y2 - self.y1
self.bouningRectOrigin = (self.x1, self.y1)
# Draw the bounding rectangle
self.rect.set_width(self.boundingRectWidth)
self.rect.set_height(self.boundingRectHeight)
self.rect.set_xy((self.x1, self.y1))
# 描画反映
#self.canvas_widget.draw()
# 前回描いたものを消す
if self.rect_id is not None:
self.canvas_widget.delete(self.rect_id)
# 今回クリックされた座標に描く
self.rect_id = self.canvas_widget.create_rectangle(self.x1, self.y1, self.x2, self.y2, outline="green", width=2)
def func_plot_graph1(self):
# グラフを描画する
x = [1, 2, 3, 4, 5]
y = [2, 3, 5, 7, 11]
#self.ax.clear() # 画像と重ねるため、グラフクリアはしない
self.ax.plot(x, y)
#self.canvas.draw() # 画像と重ねるため, ここでの反映はしない
def func_plot_graphOnly(self):
# グラフを描画する
x = [1, 2, 3, 4, 5]
y = [2, 3, 5, 7, 11]
self.ax.clear()
self.ax.plot(x, y)
self.canvas.draw() # 描画反映
def func_setImage_cv2(self, workfilepath):
# 画像表示 画像のみ、グラフ重ねて表示の場合は別処理
# ※in_xlim, in_ylim で画像を、グラフ位置に調整して重ねる この記述がないとずれてしまって重なって見えない場合がある
image = cv2.imread(workfilepath)
self.ax.imshow(image, aspect='auto', alpha=0.6)
def func_addImage_cv2(self, workfilepath, in_xlim, in_ylim):
# グラフ描画後、画像を重ねて表示する
# ※in_xlim, in_ylim で画像を、グラフ位置に調整して重ねる この記述がないとずれてしまって重なって見えない場合がある
image = cv2.imread(workfilepath)
self.ax.imshow(image, extent=[*in_xlim, *in_ylim], aspect='auto', alpha=0.6)