0

I'm having some issues when running my code inside Spyder3 IPython console. Now I did some digging on this and come to the conclusion that either I have graphical issues regarding drivers or X11 forwarding or whatsoever.

Or I simply have an issue of not properly cleaning up my code which is why the second execution always becomes troublesome, requiring a kernel restart which as well might cause errors due to BadWindows etc.

I do hope cleaning up properly will suffice, because dealing with any of these XCB errors or GTK warnings is too complicated

My question is: How do I properly cleanup tkinter window objects, pygame surfaces and multithreads? Expectation: Delete everything after the window is closed because it certainly is not required anymore at that point or will simply cause errors if it persists.

I've read about the python Deconstructor del but before I go into this I would prefer to know what kind of things even require deletion.

Here's an example of error message I get on second execution of my program (first execution works properly)

An error ocurred while starting the kernel
[xcb] Unknown sequence number while processing queue
[xcb] Most likely this is a multi‑threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
python3: ../../src/xcb_io.c:259: poll_for_event: Assertion `!xcb_xlib_threads_sequence_lost' failed.
X Error of failed request: BadWindow (invalid Window parameter)
Major opcode of failed request: 2 (X_ChangeWindowAttributes)
Resource id in failed request: 0xe00008
Serial number of failed request: 75
Current serial number in output stream: 76
X Error of failed request: BadWindow (invalid Window parameter)
Major opcode of failed request: 2 (X_ChangeWindowAttributes)
Resource id in failed request: 0xe00008
Serial number of failed request: 75
Current serial number in output stream: 76
X Error of failed request: BadWindow (invalid Window parameter)
Major opcode of failed request: 2 (X_ChangeWindowAttributes)
Resource id in failed request: 0xe00008
Serial number of failed request: 75
Current serial number in output stream: 76

And here is the Code that I'm testing:

import pygame as pg
import pygame.camera
import os
import threading as th

class Capture():
    def __init__(self, parent):
        os.environ['SDL_WINDOWID'] = parent
        pg.display.init()
        pg.camera.init()

        self.size = (640,480)
        self.display = pg.display.set_mode(self.size)
        self.display.fill(pg.Color(255,255,255))

        pg.display.update()

        self.clist = pg.camera.list_cameras()

        if not self.clist:
            raise ValueError('Sorry, no cameras detected.')
        print('Cameras: ', self.clist)    

        self.snapshot = pg.surface.Surface(self.size, 0, self.display)
    def feed(self, number):
        try:
            self.cam = pg.camera.Camera(self.clist[number], self.size)
        except IndexError:
            print('Provided Camera not available.')
        self.cam.start()
        self.thread = True
        self.t = th.Thread(name='Livefeed', target=self.live)
        self.t.start()
    def live(self):
        while self.thread:
            if self.cam.query_image():
                self.cam.get_image(self.snapshot)
            self.display.blit(self.snapshot, self.snapshot.get_rect())
            pg.display.update()
    def stop(self):
        self.thread = False
        self.t.join()
        self.cam.stop()

#for Camera DEBUG
if __name__ == '__main__':
    import tkinter as tk
    root = tk.Tk()
    embed = tk.LabelFrame(root, width=650, height=490)
    embed.grid(row=0, column=0)
    root.update()
    setup = Capture(str(embed.winfo_id()))
    buttons = tk.LabelFrame(root, width=100)
    buttons.grid(row=0, column=1)
    cam1 = tk.Button(buttons, text='Cam 1',
                     command=lambda: setup.feed(0), width=25)
    cam2 = tk.Button(buttons, text='Cam 2',
                     command=lambda: setup.feed(1), width=25)
    cam3 = tk.Button(buttons, text='Cam 3',
                     command=lambda: setup.feed(2), width=25)
    cam4 = tk.Button(buttons, text='Cam 4',
                     command=lambda: setup.feed(3), width=25)
    cam1.grid(row=0, columnspan=2)
    cam2.grid(row=1, columnspan=2)
    cam3.grid(row=2, columnspan=2)
    cam4.grid(row=3, columnspan=2)
    camStop = tk.Button(buttons, width=50, text='Feed Off',
                    command=lambda: setup.stop())
    camStop.grid(row=4, columnspan=2)
    root.mainloop()
HackXIt
  • 422
  • 5
  • 17
  • 2
    always put error as text, not screenshot - nobody can copy text from screenshot to use it. – furas Nov 30 '17 at 09:28
  • I don't know why do you run it in Spyder3 IPython. Natural method is to run in system teminal/console as `python scriptname.py` – furas Nov 30 '17 at 09:30
  • problem seams be `thread` - `tkinter` and `pygame` is not thread-safe so don't use it in two threads at the same time. I see you start `pygame` in main thread but later you use it new thread. Maybe run all in new thread - starting from `pg.display.init()` – furas Nov 30 '17 at 09:39
  • @furas Edited. I run it in Spyder3 IPython because it's a nice IDE to develop in, so why shouldn't I use the inbuilt Ipython console? I don't consider it as "natural method" to run in system terminal during development, which is dependant on preferred development Area. I will try to put pygame in a seperate thread. However why does it work then on first execution? Shouldn't first execution cause errors as well if that's the case? – HackXIt Nov 30 '17 at 09:42
  • BTW: code run correctly on Linux but I would add code to closing button because now you don't stop thread at the end. [How do I handle the window close event in Tkinter?](https://stackoverflow.com/questions/111155/how-do-i-handle-the-window-close-event-in-tkinter) – furas Nov 30 '17 at 09:44
  • as for Spyder - try code in system console to see if it is problem with code or with Spyder. – furas Nov 30 '17 at 09:45
  • Okay, yes, execution runs properly in terminal, aside from already known errors which I'm trying to fix. It still interests me how I can make sure that when the execution in IPython ends that it's more or less a fresh IPython to rerun the code. – HackXIt Nov 30 '17 at 10:01

1 Answers1

1

I have this problem if I don't stop thread before closing program.

If I add code to closing button X then I have no problem

def on_closing():
    print("stoping")
    setup.stop()
    print('destroy')
    root.destroy()

root.protocol("WM_DELETE_WINDOW", on_closing)  

root.mainloop()

I have to add some code to check if thread is running before I try to stop it.

def stop(self):
    if self.thread:
        self.thread = False
        self.t.join()
        self.cam.stop()

It needs in __init__

self.thread = False

Full code

import pygame as pg
import pygame.camera
import os
import threading as th

class Capture():

    def __init__(self, parent):
        os.environ['SDL_WINDOWID'] = parent
        pg.display.init()
        pg.camera.init()

        self.size = (640,480)
        self.display = pg.display.set_mode(self.size)

        self.display.fill(pg.Color(255,255,255))
        pg.display.update()

        self.clist = pg.camera.list_cameras()

        if not self.clist:
            raise ValueError('Sorry, no cameras detected.')
        print('Cameras: ', self.clist)    

        self.snapshot = pg.surface.Surface(self.size, 0, self.display)

        self.thread = False

    def feed(self, number):
        try:
            self.cam = pg.camera.Camera(self.clist[number], self.size)
        except IndexError:
            print('Provided Camera not available.')

        self.cam.start()
        self.thread = True
        self.t = th.Thread(name='Livefeed', target=self.live)
        self.t.start()

    def live(self):
        while self.thread:
            if self.cam.query_image():
                self.cam.get_image(self.snapshot)
            self.display.blit(self.snapshot, self.snapshot.get_rect())
            pg.display.update()

    def stop(self):
        if self.thread:
            self.thread = False
            self.t.join()
            self.cam.stop()

#for Camera DEBUG
if __name__ == '__main__':
    import tkinter as tk

    root = tk.Tk()

    embed = tk.LabelFrame(root, width=650, height=490)
    embed.grid(row=0, column=0)
    root.update()
    setup = Capture(str(embed.winfo_id()))

    buttons = tk.LabelFrame(root, width=100)
    buttons.grid(row=0, column=1)

    cam1 = tk.Button(buttons, text='Cam 1',
                     command=lambda: setup.feed(0), width=25)
    cam2 = tk.Button(buttons, text='Cam 2',
                     command=lambda: setup.feed(1), width=25)
    cam3 = tk.Button(buttons, text='Cam 3',
                     command=lambda: setup.feed(2), width=25)
    cam4 = tk.Button(buttons, text='Cam 4',
                     command=lambda: setup.feed(3), width=25)
    cam1.grid(row=0, columnspan=2)
    cam2.grid(row=1, columnspan=2)
    cam3.grid(row=2, columnspan=2)
    cam4.grid(row=3, columnspan=2)
    camStop = tk.Button(buttons, width=50, text='Feed Off',
                    command=setup.stop)
    camStop.grid(row=4, columnspan=2)

    def on_closing():
        print('stoping')
        setup.stop()
        print('destroy')
        root.destroy()

    root.protocol("WM_DELETE_WINDOW", on_closing)  

    root.mainloop()

BTW: instead of command=lambda:self.stop() you can do command=self.stop

furas
  • 134,197
  • 12
  • 106
  • 148
  • Ah. Missed that lambda, will fix. I'll look into the full code. – HackXIt Nov 30 '17 at 10:05
  • I added/adjusted if statements for start, feed and stop. So now it checks if thread is already running on start, feed and stop, which should minimize errors when repeatedly pressing buttons. I still need to test this inside spyder3 but for the while thanks for your help. – HackXIt Nov 30 '17 at 10:31
  • 1
    Problem still persists in spyder3. `X Error of failed request: BadWindow (invalid Window parameter) Major opcode of failed request: 2 (X_ChangeWindowAttributes) Resource id in failed request: 0xe00008 Serial number of failed request: 75 Current serial number in output stream: 76` – HackXIt Nov 30 '17 at 12:23