I am writing a GUI using a camera in kivy, and am unsure why my code is not working. I have a camera feed, and two methods of capturing a picture from it: one triggered by a gpiozero
when_pressed
callback, and one triggered by a kivy.uix.button
on_press
callback.
The kivy.uix.button
callback succeeds in capturing an image, but the gpiozero
callback says Exception: Shader didnt link, check info log.
, fails to save an image, and then makes the camera feed go black (although images can later still be captured with the successful option). Why does one callback work but not the other?
Here is the related code, and the corresponding terminal outputs. I've annotated the terminal output with # ALL CAPS COMMENTS
. (My code is inspired by the kivy docs camera example, which also captures successfully).
import kivy
#kivy.require('1.11.1')
# Uncomment these lines to see all the messages
#from kivy.logger import Logger
#import logging
#Logger.setLevel(logging.TRACE)
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.camera import Camera
from kivy.core.window import Window
from gpiozero import Button as gpiozeroButton # renamed to avoid conflict w/ kivy.uix.button
import time
Window.fullscreen = 'auto' # uses display's current resolution
capture_btn = gpiozeroButton(pin=13, pull_up=False) # set up GPIO
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
capture_btn.when_pressed = self.capture # initialize callback for GPIO button
def capture(self):
print('Capture step 1')
camera = self.ids['camera']
print('Capture step 2')
timestr = time.strftime("%Y%m%d_%H%M%S")
print('Capture step 3')
camera.export_to_png("IMG_{}.png".format(timestr))
print("Captured")
class LifterApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
LifterApp().run()
# #:kivy 1.11.1
<RootWidget>:
Camera:
id: camera
resolution: (640, 480)
play: True
Button:
text: "capture"
pos_hint: {'x':0.0, 'y':0.0}
size_hint: (0.2, 0.2)
on_press: root.capture()
[INFO ] [Logger ] Record log in /home/pi/.kivy/logs/kivy_20-12-30_27.txt
[INFO ] [Kivy ] v1.11.1
[INFO ] [Kivy ] Installed at "/usr/local/lib/python3.7/dist-packages/kivy/__init__.py"
[INFO ] [Python ] v3.7.3 (default, Dec 20 2019, 18:57:59)
[GCC 8.3.0]
[INFO ] [Python ] Interpreter at "/usr/bin/python3"
[INFO ] [Factory ] 184 symbols loaded
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO ] [Text ] Provider: sdl2(['text_pango'] ignored)
[INFO ] [Camera ] Provider: picamera
[INFO ] [Window ] Provider: sdl2(['window_egl_rpi'] ignored)
[INFO ] [GL ] Using the "OpenGL" graphics system
[INFO ] [GL ] Backend used <sdl2>
[INFO ] [GL ] OpenGL version <b'3.1 Mesa 19.3.2'>
[INFO ] [GL ] OpenGL vendor <b'VMware, Inc.'>
[INFO ] [GL ] OpenGL renderer <b'llvmpipe (LLVM 9.0.1, 128 bits)'>
[INFO ] [GL ] OpenGL parsed version: 3, 1
[INFO ] [GL ] Shading version <b'1.40'>
[INFO ] [GL ] Texture max size <8192>
[INFO ] [GL ] Texture max units <32>
[INFO ] [Window ] auto add sdl2 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
[INFO ] [ProbeSysfs ] device match: /dev/input/event0
[INFO ] [MTD ] Read event from </dev/input/event0>
[INFO ] [ProbeSysfs ] device match: /dev/input/event0
[INFO ] [HIDInput ] Read event from </dev/input/event0>
[INFO ] [Base ] Start application main loop
[INFO ] [MTD ] </dev/input/event0> range position X is 0 - 800
[INFO ] [MTD ] </dev/input/event0> range position Y is 0 - 480
[INFO ] [HIDMotionEvent] using <WaveShare WS170120>
[INFO ] [MTD ] </dev/input/event0> range touch major is 0 - 0
[INFO ] [HIDMotionEvent] <WaveShare WS170120> range ABS X position is 0 - 800
[INFO ] [MTD ] </dev/input/event0> range touch minor is 0 - 0
[INFO ] [HIDMotionEvent] <WaveShare WS170120> range ABS Y position is 0 - 480
[INFO ] [MTD ] </dev/input/event0> range pressure is 0 - 255
[INFO ] [HIDMotionEvent] <WaveShare WS170120> range ABS pressure is 0 - 255
[INFO ] [MTD ] </dev/input/event0> axes invertion: X is 0, Y is 0
[INFO ] [HIDMotionEvent] <WaveShare WS170120> range position X is 0 - 800
[INFO ] [MTD ] </dev/input/event0> rotation set to 0
[INFO ] [HIDMotionEvent] <WaveShare WS170120> range position Y is 0 - 480
[INFO ] [HIDMotionEvent] <WaveShare WS170120> range pressure is 0 - 255
[INFO ] [GL ] NPOT texture support is available
Capture step 1 # TRIGGERED BY KIVY.UIX.BUTTON ON_PRESS CALLBACK
Capture step 2
Capture step 3
Captured
Capture step 1 # TRIGGERED BY GPIOZERO WHEN_PRESSED CALLBACK
Capture step 2
Capture step 3
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/gpiozero/pins/rpigpio.py", line 244, in _call_when_changed
super(RPiGPIOPin, self)._call_when_changed()
File "/usr/lib/python3/dist-packages/gpiozero/pins/local.py", line 143, in _call_when_changed
self.state if state is None else state)
File "/usr/lib/python3/dist-packages/gpiozero/pins/pi.py", line 293, in _call_when_changed
method(ticks, state)
File "/usr/lib/python3/dist-packages/gpiozero/input_devices.py", line 197, in _pin_changed
self._fire_events(ticks, bool(self._state_to_value(state)))
File "/usr/lib/python3/dist-packages/gpiozero/mixins.py", line 368, in _fire_events
self._fire_activated()
File "/usr/lib/python3/dist-packages/gpiozero/mixins.py", line 397, in _fire_activated
super(HoldMixin, self)._fire_activated()
File "/usr/lib/python3/dist-packages/gpiozero/mixins.py", line 344, in _fire_activated
self.when_activated()
File "main.py", line 35, in capture
camera.export_to_png("IMG_{}.png".format(timestr))
File "/usr/local/lib/python3.7/dist-packages/kivy/uix/widget.py", line 727, in export_to_png
self.export_as_image().save(filename, flipped=False)
File "/usr/local/lib/python3.7/dist-packages/kivy/uix/widget.py", line 744, in export_as_image
with_stencilbuffer=True)
File "kivy/graphics/fbo.pyx", line 152, in kivy.graphics.fbo.Fbo.__init__
File "kivy/graphics/instructions.pyx", line 777, in kivy.graphics.instructions.RenderContext.__init__
File "kivy/graphics/shader.pyx", line 184, in kivy.graphics.shader.Shader.__init__
File "kivy/graphics/shader.pyx", line 701, in kivy.graphics.shader.Shader.vs.__set__
File "kivy/graphics/shader.pyx", line 557, in kivy.graphics.shader.Shader.build_vertex
File "kivy/graphics/shader.pyx", line 587, in kivy.graphics.shader.Shader.link_program
Exception: Shader didnt link, check info log.
^C[INFO ] [Base ] Leaving application in progress...
Traceback (most recent call last):
File "main.py", line 46, in <module>
LifterApp().run()
File "/usr/local/lib/python3.7/dist-packages/kivy/app.py", line 855, in run
runTouchApp()
File "/usr/local/lib/python3.7/dist-packages/kivy/base.py", line 504, in runTouchApp
EventLoop.window.mainloop()
File "/usr/local/lib/python3.7/dist-packages/kivy/core/window/window_sdl2.py", line 747, in mainloop
self._mainloop()
File "/usr/local/lib/python3.7/dist-packages/kivy/core/window/window_sdl2.py", line 479, in _mainloop
EventLoop.idle()
File "/usr/local/lib/python3.7/dist-packages/kivy/base.py", line 339, in idle
Clock.tick()
File "/usr/local/lib/python3.7/dist-packages/kivy/clock.py", line 591, in tick
self._process_events()
File "kivy/_clock.pyx", line 384, in kivy._clock.CyClockBase._process_events
File "kivy/_clock.pyx", line 414, in kivy._clock.CyClockBase._process_events
File "kivy/_clock.pyx", line 412, in kivy._clock.CyClockBase._process_events
File "kivy/_clock.pyx", line 167, in kivy._clock.ClockEvent.tick
File "/usr/local/lib/python3.7/dist-packages/kivy/core/camera/camera_picamera.py", line 71, in _update
self._camera.capture(output, self._format, use_video_port=True)
File "/usr/lib/python3/dist-packages/picamera/camera.py", line 1421, in capture
if not encoder.wait(self.CAPTURE_TIMEOUT):
File "/usr/lib/python3/dist-packages/picamera/encoders.py", line 393, in wait
result = self.event.wait(timeout)
File "/usr/lib/python3.7/threading.py", line 552, in wait
signaled = self._cond.wait(timeout)
File "/usr/lib/python3.7/threading.py", line 300, in wait
gotit = waiter.acquire(True, timeout)
KeyboardInterrupt
you can use the kivy.clock.mainthread
decorator to replace your __wrapper
function.
from kivy.clock import mainthread
...
class RootWidget(FloatLayout):
...
@mainthread
def capture(self):
...
also this gets ride of the dt argument that you don't need, as it preserves the signature of the function.
The original issue is certainly that the code managing when_pressed
runs in a thread, and hence plays badly with any opengl operation (you get a "shader didn't link", but you might as well get a crash, or nothing, on another computer), as opengl is not supposed to be used from multiple threads, the mainthread decorator, by using the Clock to delegate to the main thread, avoids the issue.