-1

I created a tkinter window with a scrollable figure using the method from this post. Basically, I load in a relatively large image which i want to be resizeable using a tk.Scale, but I want the window which contains the figure to stay a constant size. The problem is that once my figure reaches ~16x16 inches, the program gets unbearably slow. Any ideas would be greatly appreciated.

The particularly slowing line is figure.set_size_inches([factor * s for s in oldSize])

Here's the whole code for the window:

import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as tk
from tkinter import ttk

class MacroWindow(tk.Tk):
    def __init__(self, controller, *args, **kwargs):      
        tk.Tk.__init__(self, *args, **kwargs) #initialize regular Tk stuff

        #set properties for main window
        tk.Tk.wm_title(self, "Macro View")
        tk.Tk.geometry(self, newGeometry = '600x700+200+200')
        #define container for what's in the window
        self.controller = controller
        self.figSize_inches = [8,8]
        self.addScrollingFigure()
        frame_buttons = ttk.Frame(self)
        frame_buttons.grid(row = 1, column = 0, sticky = 'nsew')
        button_loadMacroImage = ttk.Button(frame_buttons,text = "Load Test Macro Image", command = 
                            lambda: self.loadMacroImage())
        button_loadMacroImage.grid(row = 0, column = 0, padx = 10, pady = 10, sticky = 'nw')
        self.scale_zoom = tk.Scale(self, orient = tk.VERTICAL)
        self.scale_zoom.grid(row = 2, column = 0, sticky = 'ew')
        self.scale_zoom.config(command = self.changeSize, from_=.1, to=5, resolution = .1)

    def addScrollingFigure(self):
        self.frame_canvas = ttk.Frame(self)
        self.frame_canvas.grid(row = 0, column = 0, sticky = 'nsew')
        # set up canvas with scrollbars
        canvas = tk.Canvas(self.frame_canvas)
        canvas.grid(row = 0, column = 0, sticky = 'nsew')
        xScrollbar = tk.Scrollbar(self.frame_canvas, orient = tk.HORIZONTAL)
        yScrollbar = tk.Scrollbar(self.frame_canvas, orient = tk.VERTICAL)
        xScrollbar.grid(row = 1, column = 0, sticky = 'ew')
        yScrollbar.grid(row = 0, column = 1, sticky = 'ns')
        canvas.config(xscrollcommand = xScrollbar.set)
        xScrollbar.config(command = canvas.xview)
        canvas.config(yscrollcommand = yScrollbar.set)
        yScrollbar.config(command = canvas.yview)

        #create figure and axis
        f_wholeCellFig = Figure(figsize = self.figSize_inches, dpi = fig_dpi)
        a=f_wholeCellFig.add_subplot(1,1,1)
        f_wholeCellFig.subplots_adjust(left = 0, right = 1,  bottom = 0, top = 1, wspace = 0.02, hspace = 0)

        self.wholeCellFig = f_wholeCellFig
        self.wholeCellAx = a

        #plug in the figure
        figAgg = FigureCanvasTkAgg(f_wholeCellFig,canvas)
        mplCanvas = figAgg.get_tk_widget()
        self.mplCanvas = mplCanvas
        self.canvas = canvas
        # and connect figure with scrolling region
        self.cwid = canvas.create_window(0, 0, window=mplCanvas, anchor='nw')
        self.changeSize(1.0)


    def changeSize(self,factor):
        if not isinstance(factor,float):
            factor = self.scale_zoom.get()
        figure = self.wholeCellFig
        oldSize = self.figSize_inches
        figure.set_size_inches([factor * s for s in oldSize])
        wi,hi = [i*figure.dpi for i in figure.get_size_inches()]
        self.mplCanvas.config(width = wi, height = hi)
        self.canvas.itemconfigure(self.cwid, width = wi, height = hi)
        self.canvas.config(scrollregion = self.canvas.bbox('all'), width = 500, height = 500)
        figure.subplots_adjust(left = 0, bottom = 0, top = 1, right = 1)
        figure.canvas.draw()

    def loadMacroImage(self):
        if simulation:
            image = io.imread('../testing/macroImage.tif')
        a = self.wholeCellAx
        a.clear()
        a.axis('equal')
        a.axis('off')
        self.volume = image
        self.multi_slice_viewer()

    def multi_slice_viewer(self):
        ax = self.wholeCellAx
        self.scale_z.config(command = self.scaleCallback, from_=0, to=self.volume.shape[0]-1)
        ax.index = self.volume.shape[0] // 2
        self.scale_z.set(ax.index)
        ax.imshow(self.volume[ax.index])
        self.wholeCellFig.canvas.draw()

root = tk.Tk()
window = MacroWindow(root)
root.mainloop()
Misha Smirnov
  • 126
  • 1
  • 3

2 Answers2

0

So I've not used matplotlib specifically but reading through your code it looks like every time you call your draw command you're scaling your image, meaning the image gets re-scaled every time it moves. I could be wrong about that but if that's the case that's also your problem.

Re-scaling is very process intensive and can slow your program to a crawl. So save the scaled image as an object of its own so that it scales once and then refer to the scaled image any time you want to call your draw.

But as I said, I've not worked with matplotlib so I might be misunderstanding what your code is doing so feel free to ignore me :)

MCBama
  • 1,432
  • 10
  • 18
0

So from browsing around, it seems that Matplotlib is just not good at doing things quickly, especially with large images. To have a large, scrollable image in Tkinter, it's better to use PIL and TkImage. I worked off an example described here and my results improved a lot.

Here's Scott Harden's code sample (from the link) to create a window with a scrollable image:

from Tkinter import *
import Image, ImageTk

class ScrolledCanvas(Frame):
     def __init__(self, parent=None):
          Frame.__init__(self, parent)
          self.master.title("Spectrogram Viewer")
          self.pack(expand=YES, fill=BOTH)
          canv = Canvas(self, relief=SUNKEN)
          canv.config(width=400, height=200)
          #canv.config(scrollregion=(0,0,1000, 1000))
          #canv.configure(scrollregion=canv.bbox('all'))
          canv.config(highlightthickness=0)

          sbarV = Scrollbar(self, orient=VERTICAL)
          sbarH = Scrollbar(self, orient=HORIZONTAL)

          sbarV.config(command=canv.yview)
          sbarH.config(command=canv.xview)

          canv.config(yscrollcommand=sbarV.set)
          canv.config(xscrollcommand=sbarH.set)

          sbarV.pack(side=RIGHT, fill=Y)
          sbarH.pack(side=BOTTOM, fill=X)

          canv.pack(side=LEFT, expand=YES, fill=BOTH)
          self.im=Image.open("./1hr_original.jpg")
          width,height=self.im.size
          canv.config(scrollregion=(0,0,width,height))
          self.im2=ImageTk.PhotoImage(self.im)
          self.imgtag=canv.create_image(0,0,anchor="nw",image=self.im2)

ScrolledCanvas().mainloop()
Misha Smirnov
  • 126
  • 1
  • 3