Manual Annotation with Tkinter

963 Views Asked by At

I am using Tkinter to import images with Openslide. I would like to integrate a manual annotation module into my program like this:

class ResizableCanvas(Canvas):

    def __init__(self, parent, **kwargs):
        Canvas.__init__(self, parent, **kwargs)
        self.bind("<Configure>", self.on_resize)
        self.height = self.winfo_reqheight()
        self.width = self.winfo_reqwidth()

    def on_resize(self, event):
        wscale = float(event.width) / self.width
        hscale = float(event.height) / self.height
        self.width = event.width
        self.height = event.height
        self.config(width=self.width, height=self.height)


    class ViewerTab:

    def __init__(self, master, model, dim=800):

        self.sideFrame = ttk.Frame(self.master, width=100)


        self.coords = {"x":0,"y":0,"x2":0,"y2":0}

        self.lines = []
    
    def click(self):

        self.coords["x"] = self.x
        self.coords["y"] = self.y
    
        self.lines.append(self.canvas.create_line(self.coords["x"],self.coords["y"],self.coords["x"],self.coords["y"]))
    
    def drag(self):
        # update the coordinates from the event
        self.coords["x2"] = self.x
        self.coords["y2"] = self.y
    

        self.canvas.coords(self.lines[-1], self.coords["x"],self.coords["y"],self.coords["x2"],self.coords["y2"])
    
        #self.canvas.bind("<Button-1>", self.dirbutton)
        #self.canvas.bind("<B1-Motion>", self.move)
        self.canvas.bind("<ButtonRelease-1>", self.nomove)
        self.canvas.bind("<Button-2>", self.get_position)
        self.canvas.bind("<ButtonPress-1>", self.click)
        self.canvas.bind("<B1-Motion>", self.drag) 
1

There are 1 best solutions below

9
j_4321 On BEST ANSWER

So if I got it right from the comments, the issue is to be able to both pan the slide and draw on it using binding to mouse clicks and motion. There are several way to do that, for instance:

  1. Use radiobuttons so that the user selects the "mode": either pan or annotate. Here is a small example based on https://stackoverflow.com/a/50129744/6415268 for the drawing part. The click() and drag() functions do different actions depending on the selected mode (stored a the StringVar).

     import tkinter as tk
    
     coords = {"x": 0, "y": 0, "x2": 0, "y2": 0}
     # keep a reference to all lines by keeping them in a list
     lines = []
    
     def click(event):
         if mode.get() == "pan":
             canvas.scan_mark(event.x, event.y)
         else:
             # define start point for line
             coords["x"] = canvas.canvasx(event.x)
             coords["y"] = canvas.canvasy(event.y)
    
             # create a line on this point and store it in the list
             lines.append(canvas.create_line(coords["x"], coords["y"], coords["x"], coords["y"]))
    
     def drag(event):
         if mode.get() == "pan":
             canvas.scan_dragto(event.x, event.y, gain=1)
         else:
             # update the coordinates from the event
             coords["x2"] = canvas.canvasx(event.x)
             coords["y2"] = canvas.canvasy(event.y)
    
             # Change the coordinates of the last created line to the new coordinates
             canvas.coords(lines[-1], coords["x"], coords["y"], coords["x2"], coords["y2"])
    
    
     root = tk.Tk()
     mode = tk.StringVar(root, "pan")
     toolbar = tk.Frame(root)
     toolbar.pack(fill='x')
    
     tk.Radiobutton(toolbar, text="Pan",
                    variable=mode, value="pan").pack(side='left')
     tk.Radiobutton(toolbar, text="Annotate",
                    variable=mode, value="annotate").pack(side='left')
    
     canvas = tk.Canvas(root, bg="white")
     canvas.create_rectangle(0, 0, 50, 50, fill='red')
     canvas.create_rectangle(400, 400, 450, 450, fill='blue')
     canvas.pack(fill='both')
    
     canvas.bind("<ButtonPress-1>", click)
     canvas.bind("<B1-Motion>", drag)
    
     root.mainloop()
    
  2. Another possibility is to use different bindings for the two kinds of actions using event modifiers (e.g. pressing the Ctrl, Shift or Alt key). For instance, the panning can be bound to Ctrl + mouse events while the drawing happens on simple mouse clicks and motion.

     import tkinter as tk
    
     coords = {"x": 0, "y": 0, "x2": 0, "y2": 0}
     # keep a reference to all lines by keeping them in a list
     lines = []
    
     def draw_click(event):
         # define start point for line
         coords["x"] = canvas.canvasx(event.x)
         coords["y"] = canvas.canvasy(event.y)
    
         # create a line on this point and store it in the list
         lines.append(canvas.create_line(coords["x"], coords["y"], coords["x"], coords["y"]))
    
     def draw_drag(event):
         # update the coordinates from the event
         coords["x2"] = canvas.canvasx(event.x)
         coords["y2"] = canvas.canvasy(event.y)
    
         # Change the coordinates of the last created line to the new coordinates
         canvas.coords(lines[-1], coords["x"], coords["y"], coords["x2"], coords["y2"])
    
    
     root = tk.Tk()
     toolbar = tk.Frame(root)
     toolbar.pack(fill='x')
    
     canvas = tk.Canvas(root, bg="white")
     canvas.create_rectangle(0, 0, 50, 50, fill='red')
     canvas.create_rectangle(400, 400, 450, 450, fill='blue')
     canvas.pack(fill='both')
    
     canvas.bind("<ButtonPress-1>", draw_click)
     canvas.bind("<B1-Motion>", draw_drag)
     canvas.bind('<Control-ButtonPress-1>', lambda event: canvas.scan_mark(event.x, event.y))
     canvas.bind("<Control-B1-Motion>", lambda event: canvas.scan_dragto(event.x, event.y, gain=1))
    
     root.mainloop()