lundi 1 mars 2021

Change content of tk.canvas from another thread in the Tkinter (Python)

My high-level task - change the image on the display from the tests. I'm creating an automatic test for the device with cameras. These cameras directed to the my display. The simplest test looks like this:

  1. Show aruco-marker or picture on the monitor;
  2. Capture image from the cameras;
  3. Detect aruco-marker or expected picture on the images;

Tkinter-related part of code:

import tkinter as tk
from PIL.ImageTk import PhotoImage
from time import sleep
import threading


class StandDisplayManager(tk.Frame):
    def __init__(self):
        super().__init__()
        self.pack()
        self.bind("<<Close>>", quit)

        self.canv = tk.Canvas(self)
        self.canv.config(width=self.winfo_screenwidth(), height=self.winfo_screenheight())
        self.canv.pack()

        self.markers = list()

        global display_manager_is_ready
        display_manager_is_ready = True

    def show_aruco_marker(self, id="0001", x=300, y=300):
        img_name = env_config.TEST_DATA_PREFIX + "/aruco_id_%s.png" % (id)
        imgobj = PhotoImage(file=img_name)

        self.canv.config(bg='white')
        marker_id = self.canv.create_image(x, y, image=imgobj, anchor=tk.NW)
        self.canv.tkraise(marker_id)
        self.markers.append(marker_id)
        self.canv.pack()

        # I need this sleep to demonstrate my problem
        # I can display first marker without any problems
        # But second marker (and other markers) gone after end of this method
        # So, I'm using this sleep just to see the marker
        sleep(1)

    def hide_aruco_markers(self):
        for marker in self.markers:
            self.canv.delete(marker)
        self.markers.clear()

    def close(self):
        self.event_generate("<<Close>>")


def _run_stand_display_manager():
    global display_manager
    display_manager = StandDisplayManager()
    display_manager.mainloop()


def run_stand_display_manager_in_separate_thread():
    display_manager_thread = threading.Thread(
        target=_run_stand_display_manager)
    display_manager_thread.start()
    global display_manager_is_ready
    while not display_manager_is_ready:
        sleep(0.1)

My test:


def test_with_display_manager_example():
    global display_manager
    run_stand_display_manager_in_separate_thread()

    display_manager.show_aruco_marker("0011")
    # display shows marker 0011 and I can capture images and detect it
    sleep(3)
    display_manager.hide_aruco_markers()
    display_manager.show_aruco_marker("0012")
    # Display shows marker 0012 1 second only and hide it after end of method show_aruco_marker
    # So, I can't capture image and detect marker without "sleep" in the method show_aruco_marker
    # Test failed here
    sleep(3)
    display_manager.hide_aruco_markers()
    display_manager.show_aruco_marker("0010")
    display_manager.close()

I can't solve this issue in a pretty way. I found this solution (Update data in a Tkinter-GUI with data from a second Thread) with a queue and AsyncioThread. But I don't like this solution because I need update image on the display immediately (And I can do it immediately, in fact, with ugly "sleep" :) ).

Help me please solve this problem.

Or suggest please more appropriate tool. It will be cool also!

Aucun commentaire:

Enregistrer un commentaire