Tkinter: Exception RuntimeError: 'maximum recursion depth exceeded'


O

Olaf Dietrich

I have the following (now extremely minimalistic) Tkinter
application:

--------------- START --------------------------------------------

#! /usr/bin/python2.6

import sys
import Tkinter
import numpy
from PIL import Image, ImageTk

class Viewer(object):

def __init__(self, tk_root):
'''Initialize viewer object for NumPy arrays.'''
self.root = tk_root
self.data = numpy.zeros((512,512))
self.canvas = Tkinter.Canvas(self.root, width=512, height=512)
self.canvas.pack()
self.canvas.bind('<Button1-Motion>', self.__leftbutton_motion)
self.viewdata()

def viewdata(self):
'''Update the currently viewed image data.'''
# convert to Tkinter photo object and insert into canvas
data = (self.data*255).astype('uint8')
img = Image.fromstring('L', (512,512), data.tostring())
self.photo = ImageTk.PhotoImage(img)
self.canvas.create_image(1, 1, image=self.photo, anchor=Tkinter.NW)
self.root.update()

def __leftbutton_motion(self, e):
'''Handle Ctrl + left mouse button (button 1) motion.'''
self.data[e.y, e.x] = 1
self.viewdata()

# sys.setrecursionlimit(400)
tk = Tkinter.Tk()
Viewer(tk)
tk.mainloop()

--------------- END ----------------------------------------------

With this class, I can "draw" with the mouse onto the
canvas (originally, the points were connected with
lines ...).

After some somewhat heavy mouse action inside the
canvas (with the left button pressed), the application throws:


| Exception RuntimeError: 'maximum recursion depth exceeded' in <bound method PhotoImage.__del__ of <Tkinter.PhotoImage instance at 0x19c0998>> ignored
| Exception in Tkinter callback
| Traceback (most recent call last):
| File "/usr/local/apps/python/lib/python2.6/lib-tk/Tkinter.py", line 1410, in __call__
| return self.func(*args)
| File "./test_recursion.py", line 31, in __leftbutton_motion
| self.viewdata()
| File "./test_recursion.py", line 24, in viewdata
| self.photo = ImageTk.PhotoImage(img)
| File "/usr/local/apps/python/lib/python2.6/site-packages/PIL/ImageTk.py", line 113, in __init__
| self.__photo = apply(Tkinter.PhotoImage, (), kw)

(and similiar ones)


This error can be provoked faster by setting the recursion limit
to lower values (e.g. 400 as in the comment above).


Is there anything I can do (apart from increasing the recursion
limit) to avoid this exception? Can I improve anything in the
script above to make the whole thing more robust?


Thanks in advance for any help or suggestions

Olaf
 
Ad

Advertisements

J

Jeff Hobbs

I have the following (now extremely minimalistic) Tkinter
application:

--------------- START --------------------------------------------

#! /usr/bin/python2.6

import sys
import Tkinter
import numpy
from PIL import Image, ImageTk

class Viewer(object):

    def __init__(self, tk_root):
        '''Initialize viewer object for NumPy arrays.'''
        self.root = tk_root
        self.data = numpy.zeros((512,512))
        self.canvas = Tkinter.Canvas(self.root, width=512, height=512)
        self.canvas.pack()
        self.canvas.bind('<Button1-Motion>', self.__leftbutton_motion)
        self.viewdata()

    def viewdata(self):
        '''Update the currently viewed image data.'''
        # convert to Tkinter photo object and insert into canvas
        data = (self.data*255).astype('uint8')
        img = Image.fromstring('L', (512,512), data.tostring())
        self.photo = ImageTk.PhotoImage(img)
        self.canvas.create_image(1, 1, image=self.photo, anchor=Tkinter.NW)
        self.root.update()

    def __leftbutton_motion(self, e):
        '''Handle Ctrl + left mouse button (button 1) motion.'''
        self.data[e.y, e.x] = 1
        self.viewdata()

# sys.setrecursionlimit(400)
tk = Tkinter.Tk()
Viewer(tk)
tk.mainloop()

--------------- END ----------------------------------------------

With this class, I can "draw" with the mouse onto the
canvas (originally, the points were connected with
lines ...).

After some somewhat heavy mouse action inside the
canvas (with the left button pressed), the application throws:

| Exception RuntimeError: 'maximum recursion depth exceeded' in <bound method PhotoImage.__del__ of <Tkinter.PhotoImage instance at 0x19c0998>> ignored
| Exception in Tkinter callback
| Traceback (most recent call last):
|   File "/usr/local/apps/python/lib/python2.6/lib-tk/Tkinter.py", line 1410, in __call__
|     return self.func(*args)
|   File "./test_recursion.py", line 31, in __leftbutton_motion
|     self.viewdata()
|   File "./test_recursion.py", line 24, in viewdata
|     self.photo = ImageTk.PhotoImage(img)
|   File "/usr/local/apps/python/lib/python2.6/site-packages/PIL/ImageTk.py", line 113, in __init__
|     self.__photo = apply(Tkinter.PhotoImage, (), kw)

(and similiar ones)

This error can be provoked faster by setting the recursion limit
to lower values (e.g. 400 as in the comment above).

Is there anything I can do (apart from increasing the recursion
limit) to avoid this exception? Can I improve anything in the
script above to make the whole thing more robust?

Thanks in advance for any help or suggestions

It seems very heavy-handed to create 1-pixel images for drawing onto
the canvas. Any reason not to use something lighter weight?

I suspect the "self.root.update()" is the problem. Try
update_idletasks() instead, or to even avoid it if possible. You
don't want to call update in the event loop, because you are likely
reprocessing from the same call, causing the recursion.

Jeff
 
O

Olaf Dietrich

Jeff Hobbs said:
After some somewhat heavy mouse action inside the
canvas (with the left button pressed), the application throws:

| Exception RuntimeError: 'maximum recursion depth exceeded' in <bound method PhotoImage.__del__ of <Tkinter.PhotoImage instance at 0x19c0998>> ignored
| Exception in Tkinter callback [...]
(and similiar ones)

This error can be provoked faster by setting the recursion limit
to lower values (e.g. 400 as in the comment above).

Is there anything I can do (apart from increasing the recursion
limit) to avoid this exception? Can I improve anything in the
script above to make the whole thing more robust?

It seems very heavy-handed to create 1-pixel images for drawing onto
the canvas. Any reason not to use something lighter weight?

The example was _heavily_ simplified; originally, there was
a background (e.g. gray-scale) image and I was drawing lines or
other shapes onto that background that should appear in color
and semi-transparent on top of the background. Additionally,
both background and foreground can be zoomed and scrolled, and
there should be a pixel-by-pixel correspondence between the
(zoomed) background pixels and the semi-transparent shapes
in the foreground (as opposed to overlayed vector graphics).
I could not find a more light-weight solution for these
requirements.

I suspect the "self.root.update()" is the problem. Try
update_idletasks() instead, or to even avoid it if possible. You
don't want to call update in the event loop, because you are likely
reprocessing from the same call, causing the recursion.

Indeed, Tk/Toplevel/Canvas.update() seems to be at the
center of the problem.

If I replace update() by update_idletasks(), the problem
disappears, but unfortunately, considerably fewer events
are recorded on the canvas (when connecting the pixels with
lines, the lines become much longer with update_idletasks()
than with update()). If I remove both update() and
update_idletasks(), things work similarly as with
update_idletasks() (the display is only marginally slower
than with update_idletasks()).

I wonder if there is any compromise between update()
and update_idletasks():

* update(): smooth lines but recursion exception
* update_idletasks(): non-smooth lines without exceptions


Thanks a lot for your help
Olaf
 
L

Lawrence D'Oliveiro

If I replace update() by update_idletasks(), the problem
disappears, but unfortunately, considerably fewer events
are recorded on the canvas (when connecting the pixels with
lines, the lines become much longer with update_idletasks()
than with update()). If I remove both update() and
update_idletasks(), things work similarly as with
update_idletasks() (the display is only marginally slower
than with update_idletasks()).

How about simply avoiding recursive updates.

Add this line to your __init__ method:

self.reentered = False

In your viewdata method, put this at the start:

save_reentered = self.reentered
self.reentered = True

and this at the end:

self.reentered = save_reentered

while the update call becomes conditional on not being reentered:

if not save_reentered:
self.root.update()

Of course, this is all a total guess, I really know nothing about TKinter.
:)
 
O

Olaf Dietrich

Lawrence D'Oliveiro said:
If I replace update() by update_idletasks(), the problem
disappears, but unfortunately, considerably fewer events
are recorded on the canvas (when connecting the pixels with
lines, the lines become much longer with update_idletasks()
than with update()).
[...]
How about simply avoiding recursive updates.

Add this line to your __init__ method:

self.reentered = False [...]
if not save_reentered:
self.root.update()

I tried this, but it makes the GUI even less responsive
than the unconditional call of update_idletasks().

Thanks,
Olaf
 
Ad

Advertisements

J

Jeff Hobbs

Jeff  Hobbs <[email protected]>:




On Oct 12, 9:43 am, (e-mail address removed) (Olaf Dietrich) wrote:
After some somewhat heavy mouse action inside the
canvas (with the left button pressed), the application throws:
| Exception RuntimeError: 'maximum recursion depth exceeded' in <bound method PhotoImage.__del__ of <Tkinter.PhotoImage instance at 0x19c0998>> ignored
| Exception in Tkinter callback [...]
(and similiar ones)
This error can be provoked faster by setting the recursion limit
to lower values (e.g. 400 as in the comment above).
Is there anything I can do (apart from increasing the recursion
limit) to avoid this exception? Can I improve anything in the
script above to make the whole thing more robust?
It seems very heavy-handed to create 1-pixel images for drawing onto
the canvas.  Any reason not to use something lighter weight?

The example was _heavily_ simplified; originally, there was
a background (e.g. gray-scale) image and I was drawing lines or
other shapes onto that background that should appear in color
and semi-transparent on top of the background. Additionally,
both background and foreground can be zoomed and scrolled, and
there should be a pixel-by-pixel correspondence between the
(zoomed) background pixels and the semi-transparent shapes
in the foreground (as opposed to overlayed vector graphics).
I could not find a more light-weight solution for these
requirements.

Interesting. The canvas would support many of your primitives,
although the semi-transparent is only directly supported by images in
the core. I'm not sure I entirely understand your app needs, but this
sounds like something the tkpath Tk extension solves (sort of canvas+
+), though that doesn't have a Tkinter wrapper that I'm aware of.
Indeed, Tk/Toplevel/Canvas.update() seems to be at the
center of the problem.

If I replace update() by update_idletasks(), the problem
disappears, but unfortunately, considerably fewer events
are recorded on the canvas (when connecting the pixels with
lines, the lines become much longer with update_idletasks()
than with update()). If I remove both update() and
update_idletasks(), things work similarly as with
update_idletasks() (the display is only marginally slower
than with update_idletasks()).

I wonder if there is any compromise between update()
and update_idletasks():

* update(): smooth lines but recursion exception
* update_idletasks(): non-smooth lines without exceptions

In general it isn't necessary to use update to keep up in pure Tcl/
Tk. You can see an example drawing app at http://wiki.tcl.tk/15386
that has no trouble keeping up with the fastest mouse, without
update. Without digging deeper into the Tkinter wrapper, maybe just
extending the recursionlimit is the easiest work-around?

Jeff
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top