slightlynybbled
slightlynybbled

Reputation: 2645

Tkinter canvas create_image and create_oval optimization

Background

I am trying - and succeeding - in creating a simple plot using using the Canvas object within tkinter. I am trying to use as many tools that are installed with Python3 as possible. Matplotlib and others are great, but they are pretty large installs for something that I'm trying to keep a bit smaller.

The plots are updated every 0.5s based on input from a hardware device. The previous 128 points are deleted and the current 128 points are drawn. See my most recent blog post for a couple of screenshots. I have successfully created the plots using canvas.create_oval(), but as I was running it, I heard my PC fans ramp up a bit (I have them on an aggressive thermal profile) and realized that I was using 15% of the CPU, which seemed odd.

The Problem

After running cProfile, I found that the canvas.create_oval() was taking more cumulative time than I would have expected.

After reading a bit about optimization in the tkinter canvas (there isn't much out there except 'use something else'), I came across a post that suggested that one might use an image of a dot and use canvas.create_images() instead of a canvas.create_oval(). I tried that and the time in create_image() was a bit less, but still quite significant.

For completeness, I will include the code fragment. Note that this method is part of a class called Plot4Q which is a subclass of tk.Canvas:

def plot_point(self, point, point_format=None, fill='green', tag='data_point'):
    x, y = point

    x /= self.x_per_pixel
    y /= self.y_per_pixel

    x_screen, y_screen = self.to_screen_coords(x, y)

    if fill == 'blue':
        self.plot.create_image((x_screen, y_screen), image=self.blue_dot, tag=tag)
    else:
        self.plot.create_image((x_screen, y_screen), image=self.green_dot, tag=tag)

The Profile

I am a profiling newb, so it would be prudent to include some portion of the output of that profiler. I have sorted by 'cumtime' and highlighted the relevant methods.

profile output

Note that scatter consumes 11.6% of the total run time.

The Question

Is there a more efficient method of creating points (and deleting them, though that doesn't take very long in tkinter) on a canvas?

If not, is there a more efficient way of creating the plot and embedding it into the tkinter interface?

I am somewhat open to using a different library, but I would like to keep it small and fast. I had thought that the tk canvas would be small and fast since it was functioning competently on machines with 1/10th of the power that a modern PC has.

More Info

After running a helpful answer below (Brian Oakley), I have updated results.

To explain the updated code a bit, I am using ovals again (I like the color control). I check to see if the tag exists. If it does not exist, then the new oval is created at the point specified. If the tag does exist, then the new coordinate is calculated and the move function is called.

def plot_point(self, point, fill='green', tag='data_point'):
    if not fill:
        fill = self.DEFAULT_LINE_COLOR

    point_width = 2

    # find the location of the point on the canvas
    x, y = point

    x /= self.x_per_pixel
    y /= self.y_per_pixel

    x_screen, y_screen = self.to_screen_coords(x, y)

    x0 = x_screen - point_width
    y0 = y_screen - point_width
    x1 = x_screen + point_width
    y1 = y_screen + point_width

    # if the tag exists, then move the point, else create the point
    point_ids = self.plot.find_withtag(tag)

    if point_ids != ():
        point_id = point_ids[0]

        location = self.plot.coords(point_id)
        current_x = location[0]
        current_y = location[1]

        move_x = x_screen - current_x
        move_y = y_screen - current_y

        self.plot.move(point_id, move_x, move_y)

    else:
        point = self.plot.create_oval(x0,
                                      y0,
                                      x1,
                                      y1,
                                      outline=fill,
                                      fill=fill,
                                      tag=tag)

enter image description here

The improvement is only slight, 10.4% vs. 11.6%.

Upvotes: 3

Views: 2168

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 386372

The canvas has performance problems when many items are created (more specifically, when new object ids are created). Deleting objects doesn't help, the problem is in the ever increasing object ids which are never reused. This problem usually doesn't appear until you have 10's of thousands of items. If you're creating 256/second, you'll start to bump into that problem in just a minute or two.

You can completely eliminate this overhead if you create 128 objects off screen once, and then simply move them around rather than destroying and recreating them.

Upvotes: 3

Related Questions