0
0

python GUI tcl/tk グラフ・画像表示 マウス操作

Last updated at Posted at 2024-04-29

実行サンプル

tcl/tkのCanvasで グラフ・画像表示する
また、マウスドラッグで画像から範囲指定する(四角形を表示)

image.png

実行環境

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)

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