Understanding performance limitations of the Tkinter Canvas - python

I've created a simple application to display a scatterplot of data using Tkinter's Canvas widget (see the simple example below). After plotting 10,000 data points, the application becomes very laggy, which can be seen by trying to change the size of the window.
I realize that each item added to the Canvas is an object, so there may be some performance issues at some point, however, I expected that level to be much higher than 10,000 simple oval objects. Further, I could accept some delays when drawing the points or interacting with them, but after they are drawn, why would just resizing the window be so slow?
After reading effbot's performance issues with the Canvas widget it seems there may be some unneeded continuous idle tasks during resizing that need to be ignored:
The Canvas widget implements a straight-forward damage/repair display
model. Changes to the canvas, and external events such as Expose, are
all treated as “damage” to the screen. The widget maintains a dirty
rectangle to keep track of the damaged area.
When the first damage event arrives, the canvas registers an idle task
(using after_idle) which is used to “repair” the canvas when the
program gets back to the Tkinter main loop. You can force updates by
calling the update_idletasks method.
So, the question is whether there is any way to use update_idletasks to make the application more responsive once the data has been plotted? If so, how?
Below is the simplest working example. Try resizing the window after it loads to see how laggy the application becomes.
Update
I originally observed this problem in Mac OS X (Mavericks), where I get a substantial spike in CPU usage when just resizing the window. Prompted by Ramchandra's comments I've tested this in Ubuntu and this doesn't seem to occur. Perhaps this is a Mac Python/Tk problem? Wouldn't be the first I've run into, see my other question:
PNG display in PIL broken on OS X Mavericks?
Could someone also try in Windows (I don't have access to a Windows box)?
I may try running on the Mac with my own compiled version of Python and see if the problem persists.
Minimal working example:
import Tkinter
import random
LABEL_FONT = ('Arial', 16)
class Application(Tkinter.Frame):
def __init__(self, master, width, height):
Tkinter.Frame.__init__(self, master)
self.master.minsize(width=width, height=height)
self.master.config()
self.pack(
anchor=Tkinter.NW,
fill=Tkinter.NONE,
expand=Tkinter.FALSE
)
self.main_frame = Tkinter.Frame(self.master)
self.main_frame.pack(
anchor=Tkinter.NW,
fill=Tkinter.NONE,
expand=Tkinter.FALSE
)
self.plot = Tkinter.Canvas(
self.main_frame,
relief=Tkinter.RAISED,
width=512,
height=512,
borderwidth=1
)
self.plot.pack(
anchor=Tkinter.NW,
fill=Tkinter.NONE,
expand=Tkinter.FALSE
)
self.radius = 2
self._draw_plot()
def _draw_plot(self):
# Axes lines
self.plot.create_line(75, 425, 425, 425, width=2)
self.plot.create_line(75, 425, 75, 75, width=2)
# Axes labels
for i in range(11):
x = 75 + i*35
y = x
self.plot.create_line(x, 425, x, 430, width=2)
self.plot.create_line(75, y, 70, y, width=2)
self.plot.create_text(
x, 430,
text='{}'.format((10*i)),
anchor=Tkinter.N,
font=LABEL_FONT
)
self.plot.create_text(
65, y,
text='{}'.format((10*(10-i))),
anchor=Tkinter.E,
font=LABEL_FONT
)
# Plot lots of points
for i in range(0, 10000):
x = round(random.random()*100.0, 1)
y = round(random.random()*100.0, 1)
# use floats to prevent flooring
px = 75 + (x * (350.0/100.0))
py = 425 - (y * (350.0/100.0))
self.plot.create_oval(
px - self.radius,
py - self.radius,
px + self.radius,
py + self.radius,
width=1,
outline='DarkSlateBlue',
fill='SteelBlue'
)
root = Tkinter.Tk()
root.title('Simple Plot')
w = 512 + 12
h = 512 + 12
app = Application(root, width=w, height=h)
app.mainloop()

There is actually a problem with some distributions of TKinter and OS Mavericks. Apparently you need to install ActiveTcl 8.5.15.1. There is a bug with TKinter and OS Mavericks. If it still isn't fast eneough, there are some more tricks below.
You could still save the multiple dots into one image. If you don't change it very often, it should still be faster. If you are changing them more often, here are some other ways to speed up a python program. This other stack overflow thread talks about using cython to make a faster class. Because most of the slowing down is probably due to the graphics this probably won't make it a lot faster but it could help.
Suggestions on how to speed up a distance calculation
you could also speed up the for loop by defining an iterator ( ex: iterator = (s.upper() for s in list_to_iterate_through) ) beforehand, but this is called to draw the window, not constantly as the window is maintained, so this shouldn't matter very much. Also, a another way to speed things up, taken from python docs, is to lower the frequency of python's background checks:
"The Python interpreter performs some periodic checks. In particular, it decides whether or not to let another thread run and whether or not to run a pending call (typically a call established by a signal handler). Most of the time there's nothing to do, so performing these checks each pass around the interpreter loop can slow things down. There is a function in the sys module, setcheckinterval, which you can call to tell the interpreter how often to perform these periodic checks. Prior to the release of Python 2.3 it defaulted to 10. In 2.3 this was raised to 100. If you aren't running with threads and you don't expect to be catching many signals, setting this to a larger value can improve the interpreter's performance, sometimes substantially."
Another thing I found online is that for some reason setting the time by changing os.environ['TZ'] will speed up the program a small amount.
If this still doesn't work, than it is likely that TKinter is not the best program to do this in. Pygame could be faster, or a program that uses the graphics card like open GL (I don't think that is available for python, however)

Tk must be getting bogged down looping over all of those ovals. I'm not
sure that the canvas was ever intended to hold so many items at once.
One solution is to draw your plot into an image object, then place the image
into your canvas.

Related

Vispy: Using timers for visualizations wrapped in functions/classes

I am currently working on using Vispy to add visualization capabilities to a Python simulation library. I have managed to get some basic visualizations running with the data from the simulations, and am now looking at wrapping it in functions/classes so users of the libraries easily visualize the simulation (by passing the data in a specific format or something) without having to code it themselves.
However, I am having trouble figuring out the right way/best practice to get the timers working properly to update the objects as they change in time.
For example, when running the visualization as script, an example of how I have implemented the timer is using global variables and iterators similar to how it is done in the Vispy Scene "Changing Line Colors" demo on the Vispy website :
def on_timer(event):
global colormaps, line, text, pos
color = next(colormaps)
line.set_data(pos=pos, color=color)
text.text = color
timer = app.Timer(.5, connect=on_timer, start=True)
But when I wrap the entire visualization script in a function/class, I am having trouble getting the timer to work correctly given the difference in scopes of the variables now. If anyone could give some idea of the best way to achieve this that would be great.
EDIT:
I have made an extremely simplified visualization that might be similar to some of the expected use cases. The code when run as a stand alone python script is:
import numpy as np
from vispy import app, scene
# Reproducible data (oscillating wave)
time = np.linspace(0, 5, 300)
rod_positions = []
for t in time:
wave_position = []
for i in range(100):
wave_position.append([0, i, 20 * (np.cos(t + i / 10))])
rod_positions.append(wave_position)
rod_positions = np.array(rod_positions)
# Prepare canvas
canvas = scene.SceneCanvas(keys="interactive", size=(800, 600), bgcolor="black")
canvas.measure_fps()
# Set up a view box to display the image with interactive pan/zoom
view = canvas.central_widget.add_view()
view.camera = scene.TurntableCamera()
rod = scene.visuals.Tube(
points=rod_positions[0], radius=5, closed=False, tube_points=16, color="green",
)
view.add(rod)
view.camera.set_range()
text = scene.Text(
f"Time: {time[0]:.4f}",
bold=True,
font_size=14,
color="w",
pos=(80, 30),
parent=canvas.central_widget,
)
update_counter = 0
max_updates = len(time) - 1
def on_timer_update(event):
global update_counter
update_counter += 1
if update_counter >= max_updates:
timer.stop()
canvas.close()
# Update new rod position and radius
rod_new_meshdata = scene.visuals.Tube(
points=rod_positions[update_counter],
radius=5,
closed=False,
tube_points=16,
color="green",
)._meshdata
rod.set_data(meshdata=rod_new_meshdata)
text.text = f"Time: {time[update_counter]:.4f}"
# Connect timer to app
timer = app.Timer("auto", connect=on_timer_update, start=True)
if __name__ == "__main__":
canvas.show()
app.run()
My attempt at wrapping this in a class is the following:
import numpy as np
from vispy import app, scene
class Visualizer:
def __init__(self, rod_position, time) -> None:
self.rod_positions = rod_position
self.time = time
self.app = app.application.Application()
# Prepare canvas
self.canvas = scene.SceneCanvas(keys="interactive", size=(800, 600), bgcolor="black")
self.canvas.measure_fps()
# Set up a view box to display the image with interactive pan/zoom
self.view = self.canvas.central_widget.add_view()
self.view.camera = scene.TurntableCamera()
self.update_counter = 0
self.max_updates = len(time) - 1
def _initialize_objects(self):
self.rod = scene.visuals.Tube(
points=self.rod_positions[0], radius=5, closed=False, tube_points=16, color="green",
)
self.view.add(self.rod)
self.view.camera.set_range()
self.text = scene.Text(
f"Time: {self.time[0]:.4f}",
bold=True,
font_size=14,
color="w",
pos=(80, 30),
parent=self.canvas.central_widget,
)
def update_timer(self, event):
self.update_counter += 1
if self.update_counter >= self.max_updates:
self.timer.stop()
self.canvas.close()
# Update new rod position and radius
rod_new_meshdata = scene.visuals.Tube(
points=self.rod_positions[self.update_counter],
radius=5,
closed=False,
tube_points=16,
color="green",
)._meshdata
self.rod.set_data(meshdata=rod_new_meshdata)
self.text.text = f"Time: {self.time[self.update_counter]:.4f}"
def run(self):
self._initialize_objects()
# Connect timer to app
self.timer = app.Timer("auto", connect=self.update_timer, start=True, app=self.app)
self.canvas.show()
self.app.run()
if __name__ == "__main__":
# Reproducible data (oscillating wave)
time = np.linspace(0, 5, 150)
rod_positions = []
for t in time:
wave_position = []
for i in range(100):
wave_position.append([0, i, 20 * (np.cos(2 * t + i / 10))])
rod_positions.append(wave_position)
rod_positions = np.array(rod_positions)
Visualizer = Visualizer(rod_positions, time)
Visualizer.run()
It seems to be working now which is good. However this is a minimal reproduction of my problem, so I would just like to make sure that this is the optimal/intended way. As a side note, I also feel as if my way of updating the rod by generating the meshdata and updating the rod in the view with this meshdata is not optimal and slowing the visualization down (it runs at around 10fps). Is the update loop itself optimal?
Thanks
Thanks for updating your question with your example. It makes it much easier to answer and continue our conversation. I started a pull request for VisPy about this topic where I add some examples that use Qt timers and Qt background threads. I hope to finish the PR in the next month or two so if you have any feedback please comment on it:
https://github.com/vispy/vispy/pull/2339
Timers
In general timers are a good way to update a visualization in a GUI event loop friendly way. However, in most GUI frameworks the timer is executed in the current GUI thread. This has the benefit that you can call GUI/drawing functions directly (they are in the same thread), but has the downside that any time you spend in your timer callback is time you're taking away from the GUI framework in order to respond to user interaction and redraw the application. You can see this in my timer example in the above PR if you uncomment the extra sleep line. The GUI basically stops responding while it is running the timer function.
I have a little message on GUI threads and event loops in the vispy FAQ here.
Threads
Threads are a good way around the limitations of timers, but come with a lot of complexities. Threads are also usually best done in a GUI-framework specific manner. I can't tell from your post if you are doing Qt or some other backend, but you will probably get the most bang for your buck by using the GUI framework specific thread functionality rather than generic python threads. If an application that works across GUI frameworks is what you're looking for then this likely isn't an option.
The benefit of using something like a QThread versus a generic python thread is that you can use things like Qt's signals and slots for sending data/information between the threads.
Note that as I described in the FAQ linked above that if you use threads you won't be able to call the set_data methods directly in your background thread(s). You'll need to send the data to the main GUI thread and then have it call the set_data method.
Suggestions
You are correct that your creation of a new Tube visual that you only use for extracting mesh data is a big slow down. The creation of a Visual is doing a lot of low-level GL stuff to get ready to draw the Visual, but then you don't use it so that work is essentially wasted time. If you can find a way to create the MeshData object yourself without the Visual you should see a speed up, but if you have a lot of data switching to threads is likely still needed.
I'm not sure how much this example's design resembles what you're really doing, but I would consider keeping the Application, Timer, and Visualizer classes separate. I'm not sure I can give you a good reason why, but something about creating these things in a singe class worries me. In general I think the programming concept of dependency injection applies here: don't create your dependencies inside your code, create them outside and pass them as arguments to your code. Or in the case of the Application and Timer, connect them to the proper methods on your class. If you have a much larger application then it may make sense to have a class that wraps all of the Vispy canvas and data updating logic, another one just for the outer GUI window, and then another for a data generation class. Then in the bottom if main block of code create your application, your timer, your canvas wrapper, your main window (passing the canvas wrapper if needed), and connect them all together.
Other thoughts
This is a problem I want to make easier for vispy users and scientific python programmers in general so let me know what I'm missing here and we'll see what kind of ideas we can come up with. Feel free to comment and provide feedback on my pull request. Do you have other ideas for what the examples should be doing? I have plans for additional fancier examples in that PR, but if you have thoughts about what you'd like to see let me know.

What range does mainloop() have over code?

I would like to make a python tkinter window with custom-moving widgets on a canvas to simulate motion. For now, I have one canvas, and one not-moving oval widget. I am having problems at the base level; mainloop(). I understand that it runs in wait for the user to do something, but I am having a hard time seeing:
How to control/see exactly what code mainloop() is reiterating (where, and only tkinter?);
How to properly interrupt it and return to it from another function, if it doesn't do it itself;
What code should be reiterated? All tkinter objects, or only updating changing ones? Use some kind of update operation instead? Finally;
What is the functionality difference between tkinter.mainloop() and window.mainloop()? Perhaps the previous questions will answer.
I have minor experience with Swift, and started learning the very similar Python yesterday evening. I've tried probably hundred of mutations to my code, which currently is in the test stage. I have moved everything in and out of the apparent range of the mainloop, and even got several hundred tiny Python windows all over the screen. Everything does one of two things: it does nothing, or gives me an error. Since I don't know what is even running, or if it is running, I can't diagnose anything. My goal is simply to move a circle one hundred pixels repeatedly. I've scanned around for sources, but—it may be me—a clear one is scarce. I have my code here all marked up. This page is closest to what I am looking for: Move a ball inside Tkinter Canvas Widget (simple Arkanoid game). Everything appears to be under mainloop. So, everything is redrawn every pass? Here, unfortunately, is my whole script; I can't only show pieces. It, for some reason, only brings up a small window, not a full-screen one. (Edit: I seem to have lost the screen size code)
import tkinter
import time
# Initial values for circle's corners and start idicator ('b'):
x1 = 10
y1 = 10
x2 = 210
y2 = 210
b = 0
# Window ('window')
window = tkinter.Tk()
# Canvas ('area')
area = tkinter.Canvas(window, width=1368, height=650)
area.place(x=0, y=0)
# Ovals to be placed on 'area'
oval1 = area.create_oval(x1,y1,x2,y2,fill='#42befe')
oval2 = area.create_oval(100,10,300,210,fill='#d00000')
# Turns b to 1 to start shifting when 'butt' is pressed:
def startFunc():
b = 1
print('b = 1')
# My button to activate 'startFunc'
butt = tkinter.Button(window, text='Start movement', command=startFunc)
butt.pack()
# Adjusts the x and y coordinates when they are fed in:
def Shift(A, B, C, D):
print('Shift activated.')
window.after(1000)
print('Edit and return:')
A += 100
B += 100
C += 100
D += 100
return(A, B, C, D)
# Problems start about here: my Mainloop section;
# I have little idea how this is supposed to be.
while True:
if b == 1:
# Takes adjusted tuple
n = Shift(x1, y1, x2, y2)
print('Returned edited tuple.')
# Changes coordinates
x1 = n[0]
y1 = n[1]
x2 = n[2]
y2 = n[3]
print(f'{x1}, {y1}, {x2}, and {y2}')
# Reiterate moving oval
oval1 = area.create_oval(x1,y1,x2,y2,fill='#42befe')
#Does this re-run 'window' relations outside here, or only within the 'while'?
window.mainloop()
It ought to show a 1368 by 650 window, not a tiny one. The button does nothing but print, which means the final 'while' is not running, despite the mainloop. It want it to loop inside the 'while' line, which should adjust coordinates and move my blue circle. The iteration may NOT touch the initial values, or else it would reset them.
In effect, calling mainloop is the same as if you added this to your code instead of calling mainloop():
while the_program_is_running():
event = wait_for_event()
process_the_event(event)
As a rule of thumb, mainloop() should be called exactly once after the UI has initialized and you are ready for the user to start interacting with your program. When it exits, you typically won't have any code after it, and your program will exit.
How to control/see exactly what code mainloop() is reiterating (where, and only tkinter?);
I don't know what you mean by "reiterating". It doesn't run any code except it's own internal code. It simply waits for events, and then dispatches them to handlers.
How to properly interrupt it and return to it from another function, if it doesn't do it itself;
It's exceedingly rare to do this in a running program. Typically, calling mainloop is the last thing your program does before the user starts interacting with it, and as soon as it exits your program quits.
However, to answer the specific answer of how to interrupt it, you can call the quit method of the root window. That will cause the most recent call to mainloop() to return.
What code should be reiterated? All tkinter objects, or only updating changing ones? Use some kind of update operation instead?
That question is hard to answer because it doens't make much sense. When you call mainloop(), it will watch for all events on all tkinter objects.
What is the functionality difference between tkinter.mainloop() and window.mainloop()
They have exactly the same effect and behavior. Tkinter oddly chose to make mainloop available from any widget. The most common way to call it is from either the tkinter module itself, or from the root window.
My goal is simply to move a circle one hundred pixels repeatedly.
The normal way to do that is to create a function that moves it one hundred pixels. Then, that function (-- or a function that calls it -- can put itself on an event queue to be run in the future.
For example, the following code will move a canvas object 100 pixels every second until the program exits:
def move_object():
the_canvas.move(item_id, 100, 0)
the_canvas.after(1000, move_object)
When it is called, it will move the item 100 pixels to the right. Then, it will place a new call to itself on the event queue to be picked up and handled in approximately 1000 milliseconds.
There are many working examples of using after on this site, including the question you linked to in your question.
Everything appears to be under mainloop. So, everything is redrawn every pass?
No, not exactly. The only objects that are redrawn are things that need to be redrawn. Moving objects on a canvas, resizing a window, dragging another window over your window, etc, all place an event on the event queue that tells tkinter "this object needs to be redrawn". The processing of that event happens automatically by mainloop. If nothing is happening in your application, nothing gets redrawn by mainloop.
It ought to show a 1368 by 650 window, not a tiny one
That is because you haven't given the main window a size. You've given the canvas a size, but you're using place which won't cause the containing window to grow or shrink to fit. As a beginner, you should completely avoid place and instead use pack or grid, because pack and grid will both automatically size your window to fit everything inside.
While it's tempting to use place for its perceived simplicity, in reality it usually requires you to do a lot more work than if you used one of the other geometry managers, and it results in a GUI that isn't particularly responsive to change.
while True:
You should almost never do this in tkinter. Tkinter -- and almost all event based programs -- rely on a steady flow of events. When you have an infinite loop, it cannot process those events. You can put an explicit call to update the screen inside your loop, but that is inefficient and should be avoided. If you need to do something periodically, create a function that encapsulates the body of your loop, then use after to get mainloop to run it while it is processing events.
window.after(1000)
You should almost never use after this way without a second argument. This usage is functionally no different than calling time.sleep(1) in that it prevents mainloop from processing events. You should structure your code to allow for a steady stream of events to be processed by mainloop.
while True: ... window.mainloop()
You definitely need to avoid calling mainloop inside a loop. A well behaved tkinter program should call mainloop() exactly once.

Tkinter Canvas Freezes Program

For my class, I am creating a "Mandelbrot Explorer" program. There is one main issue: I lose control of the GUI (all written in Tkinter/Ttk, in Python 2.7) when actually drawing to the Canvas.
Here is my code:
# There is some code above and below, but only this is relevant
for real, imag in graph.PlaneIteration(self.graph.xMin, self.graph.xMax, resolution, self.graph.yMin, self.graph.yMax, resolution, master = self.graph, buffer_action = self.graph.flush):
# the above line iterates on the complex plane, updating the Canvas for every x value
c = complex(real, imag)
function, draw, z, current_iter = lambda z: z**2 + c, True, 0, 1
while current_iter <= iterations:
z = function(z)
if abs(z) > limit:
draw = False
break
current_iter += 1
self.progressbar.setValue(100 * (real + self.graph.xMax) / total)
color = self.scheme(c, current_iter, iterations, draw)
# returns a hex color value
self.graph.plot(c, color)
# self.graph is an instance of my custom class (ComplexGraph) which is a wrapper
# around the Canvas widget
# self.graph.plot just creates a line on the Canvas:
# self.create_line(xs,ys,xs+1,ys+1, fill=color)
My issue is that when run, the graphing takes a while - about 30 seconds. In this time, I cannot use the GUI. If I try to, the window freezes and only unfreezes once the drawing is done.
I tried using threading (I enclosed the entirety of the upper code in a function, thread_process):
thread.start_new_thread(thread_process, ())
However, the problem remains.
Is there a way to fix this? Thanks!
You can execute your loop "threaded" with Tkinter by implicitly returning to Tkinter's main loop execution after every point your draw. Do this by using widget.after to register the next function call:
plane = graph.PlaneIteration(...)
def plotNextPoint():
try:
real, imag = plane.next()
except StopIteration:
return
c = complex(real, imag)
...
self.graph.plot(c, color)
self.graph.after(0, plotNextPoint)
plotNextPoint()
This way, after each point you draw, the Tkinter mainloop will run again and update the display before calling your plotNextPoint function again. If this is too slow, try wrapping the body of plotNextPoint in a for _ in xrange(n) loop to draw n points between redraws.
You're right about the cause of the problem—the GUI event loop is not running while you're busy running this code.
And you're right about threading being a good solution. (The other major solution is to break the job up into smaller subtasks and have each one schedule the next. For a more detailed overview of the options and all of the wrinkles, see Why your GUI app freezes.)
But it's not quite as simple as putting the whole thing on a thread.
Unfortunately, Tkinter (like many GUI frameworks) is not free-threaded. You cannot call methods on any GUI objects from a background thread. If you do, different things happen on different platforms and versions, ranging from blocking the main thread to crashing the program to raising exceptions.
Also, remember that, even without Tkinter, you can't safely share mutable objects between threads without some kind of synchronization. And you're doing exactly that with the Tkinter objects, right?
The Tkinter wiki explains one way to get around both of these problems at once in Tkinter and Threads: Create a Queue, have the background thread put messages on it, and have the main thread check it every so often (e.g., by using after to schedule a nonblocking get every 100ms until the background thread is done).
If you don't want to come up with a "protocol" for passing data from the background thread to the main thread, remember that in Python, a bound method, or a tuple of a bound method and some arguments, it perfectly good, passable data. So, instead of calling self.graph.plot(c, color), you can just self.q.put((self.graph.plot, c, color)).
The library mtTkinter wraps this all up for you, making it look like Tkinter is free-threaded by using a Queue in the background. It isn't highly tested or frequently maintained, but even if it doesn't work in the future it still makes great sample code.

plotting / wxPython - Why is this call to MemoryDC.SelectObject() slow?

I'm attempting to do real-time plotting of data in matplotlib, in a "production" application that uses wxPython. I had been using Chaco for this purpose, but I'm trying to avoid Chaco in the future for many reasons, one of which is that since it's not well-documented I often must spend a long time reading the Chaco source code when I want to add even the smallest feature to one of my plots. One aspect where Chaco wins out over matplotlib is in speed, so I'm exploring ways to get acceptable performance from matplotlib.
One technique I've seen widely used for fast plots in matplotlib is to set animated to True for elements of the plot which you wish to update often, then draw the background (axes, tick marks, etc.) only once, and use the canvas.copy_from_bbox() method to save the background. Then, when drawing a new foreground (the plot trace, etc.), you use canvas.restore_region() to simply copy the pre-rendered background to the screen, then draw the new foreground with axis.draw_artist() and canvas.blit() it to the screen.
I wrote up a fairly simple example that embeds a FigureCanvasWxAgg in a wxPython Frame and tries to display a single trace of random data at 45 FPS. When the program is running with the Frame at the default size (hard-coded in my source), it achieves ~13-14 frames per second on my machine. When I maximize the window, the refresh drops to around 5.5 FPS. I don't think this will be fast enough for my application, especially once I start adding additional elements to be rendered in real-time.
My code is posted here: basic_fastplot.py
I wondered if this could be made faster, so I profiled the code and found that by far the largest consumers of processing time are the calls to canvas.blit() at lines 99 and 109. I dug a little further, instrumenting the matplotlib code itself to find that most of this time is spent in a specific call to MemoryDC.SelectObject(). There are several calls to SelectObject in surrounding code, but only the one marked below takes any appreciable amount of time.
From the matplotlib source, backend_wxagg.py:
class FigureCanvasWxAgg(FigureCanvasAgg, FigureCanvasWx):
# ...
def blit(self, bbox=None):
"""
Transfer the region of the agg buffer defined by bbox to the display.
If bbox is None, the entire buffer is transferred.
"""
if bbox is None:
self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
self.gui_repaint()
return
l, b, w, h = bbox.bounds
r = l + w
t = b + h
x = int(l)
y = int(self.bitmap.GetHeight() - t)
srcBmp = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
srcDC = wx.MemoryDC()
srcDC.SelectObject(srcBmp) # <<<< Most time is spent here, 30milliseconds or more!
destDC = wx.MemoryDC()
destDC.SelectObject(self.bitmap)
destDC.BeginDrawing()
destDC.Blit(x, y, int(w), int(h), srcDC, x, y)
destDC.EndDrawing()
destDC.SelectObject(wx.NullBitmap)
srcDC.SelectObject(wx.NullBitmap)
self.gui_repaint()
My questions:
What could SelectObject() be doing that is taking so long? I had sort of assumed it would just be setting up pointers, etc., not doing much copying or computation.
Is there any way I might be able to speed this up (to get maybe 10 FPS at full-screen)?

Window too small

Here is the code:
from Tkinter import *
def main():
w1 = Tk()
w1["height"] = 400;
w1["width"] = 500;
w1.title("Gui")
f1 = Frame(w1)
f1.grid_propagate()
f1["height"] = w1["height"];
f1["width"] = w1["width"];
f1.pack()
p1 = Button(f1)
p1["borderwidth"] = 6
p1["text"] = "esci"
p1["background"] = "red"
p1["command"] = f1.quit
p1.pack()
w1.mainloop()
main()
I have given to w1 and f1 (window and frame) a 500x400 size, but a too small window appears: it's too small, I would say 200x100, but I don't know ... why does this happen?
It is this small because tkinter windows by default "shrink to fit". This is called geometry propagation, and it seems like you might already be familiar with it because you call grid_propagate -- though, without having it actually do anything and without using what it returns.
There are actually two problems in your code. The first is that you call f1.grid_propagate(), but you are using pack to arrange the widgets in f1 so you need to call f1.pack_propagate instead. Second, you need to pass a flag of False to actually turn off propagation. Doing that will prevent f1 from shrinking to fit its contents.
Second, you aren't turning propagation off on w1, so it will shrink to fit its children. You either need to call w1.grid_propagate(False), OR you can call w1.wm_geometry("500x400") to request that the window manager set the size to that exact dimension.
If you are just learning Tkinter, I suggest you resist the urge to turn propagation off. In my couple dozen years of coding in tk I've used that feature maybe once every couple of years. Tkinter's geometry managers are remarkable tools and you should learn to take advantage of them. Instead of having containers control their size, let the children be the size you want them to be and the rest of the GUI will work just fine.
You need to use
f1.pack_propagate(0)
so that it doesn't shrink

Categories

Resources