I have been trying to create a kivy camera scanner from a number of sources (I would use the zbarcam if I could, but the garden.xcamera module will not import, therefore I am trying to create something similar).
Problem
The problem is the camera does not read or update the texture continuously nor is there a way that I can find to capture frame-for-frame from the camera. This means I only get the texture on initialization.
Tried
Firstly, I have tried scheduling an event that will update the texture instance every 0.5 seconds. I could not get the texture instance of the camera because there is some delay in the camera to load, which caused an error.
Secondly, I created an on_texture event in my kv string, but it only reads the texture on initialization.
Thirdly, I tried binding the on_texture event a bit later in the python script, by creating a binding function and calling it as a scheduled event. It did not even get the instance.
Fourthly, I created_triggers and ask_update(callbacks) to the _on_texture() event, but again the script loads to fast before the camera can instantiate crashing the script.
Fifthly, I noticed there is a kivy.core.video module that contains a on_frame attribute. Did re-write my script to use it in conjunction with the kivy.uix.video module, but noticed that the video cannot run without first loading a video file.
Code
import kivy
import gi
kivy.require('1.11.1')
gi.require_version('Gst', '1.0')
from collections import namedtuple
from PIL import Image
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.camera import Camera
import time
from gi.repository import Gst
import pyzbar.pyzbar
from kivy.uix.modalview import ModalView
Builder.load_string('''
#: import Window kivy.core.window.Window
<ScanPreview>:
auto_dismiss: False
size_hint_x: 0.6
size_hint_y: None
height: Window.height / 9
pos_hint: {'top':0.7, 'x': 0.1}
background_normal: ''
background_color: (1, 1, 1, 0)
background: 'white.png'
Label:
id: sc_data
text: 'See me...'
<ScanCamera>:
orientation: 'vertical'
The_Camera:
id: camera
resolution: root.resolution
on_texture: root._on_texture(camera)
ToggleButton:
text: 'Stop'
on_press: camera.play = not camera.play
size_hint_y: None
height: '48dp'
''')
class ScanPreview(ModalView):
pass
class The_Camera(Camera):
pass
class ScanCamera(BoxLayout):
resolution = ListProperty([640, 480])
symbols = ListProperty([])
code_types = ListProperty(set(pyzbar.pyzbar.ZBarSymbol))
cam_cam = ObjectProperty(The_Camera())
the_preview = ObjectProperty(ScanPreview())
Symb = namedtuple('Symb', ['type','data'])
def __init__(self, **kwargs):
super(ScanCamera, self).__init__(**kwargs)
self.cam_cam.play = True
def _on_texture(self, instance):
#source: https://github.com/kivy-garden/garden.zbarcam/blob/develop
#/zbarcam/zbarcam.py
print(instance)
if not instance.texture == None:
print(instance.texture)
self.symbols = self._detect_qrcode_frame(
texture=instance.texture, code_types=self.code_types)
def _detect_qrcode_frame(cls, texture, code_types):
image_data = texture.pixels
size = texture.size
#source: https://github.com/kivy-garden/garden.zbarcam/blob/develop
#/zbarcam/zbarcam.py
# Fix for mode mismatch between texture.colorfmt and data returned
#by
# texture.pixels. texture.pixels always returns RGBA, so that
#should
# be passed to PIL no matter what texture.colorfmt returns. refs:
# https://github.com/AndreMiras/garden.zbarcam/issues/41
pil_image = Image.frombytes(mode='RGBA', size=size,
data=image_data)
symbols = []
print(pil_image)
print(size)
print(texture.tex_coords)
print(texture.target)
codes = pyzbar.pyzbar.decode(pil_image, symbols=code_types)
for code in codes:
symbol = CameraClick.Symb(type=code.type, data=code.data)
symbols.append(symbol)
print(symbols)
return symbols
class TestCamera(App):
title = 'Scan Camera'
def build(self):
return ScanCamera()
def on_stop(self):
cc = The_Camera()
print('Stop')
cc.play = False
def on_pause(self):
return True
def on_resume(self):
pass
TestCamera().run()
Desired result
The camera's texture must continuously update, which will allow the pyzbar and PIL module to decode the texture?
I don't know if this is how its done, but as I answered my own question I am posting the answer and marking it as such.
Answer
So I managed to solve my question by using the example code here: https://kivy-fork.readthedocs.io/en/latest/_modules/kivy/uix/camera.html and mixing in some functions of zbarcam.
By using self.canvas.ask_update() in the on_texture call it updates the texture. I have added to the code and it now updates the texture continuously and prints the barcode to a togglebutton. At the moment I have just tested it on Ubuntu Bionic Beaver. Will test it on android this weekend.
Answer code
import kivy
import gi
kivy.require('1.11.1')
gi.require_version('Gst', '1.0')
from collections import namedtuple
from PIL import Image as Img
from kivy.app import App
from gi.repository import Gst
import pyzbar.pyzbar
from kivy.uix.image import Image
from kivy.core.camera import Camera as CoreCamera
from kivy.properties import NumericProperty, ListProperty, \
BooleanProperty, ObjectProperty
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.core.window import Window
Builder.load_string('''
#: import Window kivy.core.window.Window
<ScanCamera>:
<BarcWin>:
ActionBar:
pos_hint: {'top': 1, 'right': 1}
color: (1,1,1,1)
canvas.before:
Color:
rgba: (0,0,0,1)
Rectangle:
pos: self.pos
size: self.size
ActionView:
use_separator: True
ActionPrevious:
title: ''
with_previous: False
app_icon: ''
ActionButton:
color: (0,0,0,1)
background_normal: ''
Image:
source: 'gear_2.png'
center_y: self.parent.center_y
center_x: self.parent.center_x
size: self.parent.width /1.7, self.parent.height/ 1.7
allow_stretch: True
ActionButton:
color: (0,0,0,1)
size_hint_x: 0.09
background_normal: ''
Image:
source: 'dustbin_backgrnd_792521.png'
center_y: self.parent.center_y
center_x: self.parent.center_x
size: self.parent.width /1.7, self.parent.height/ 1.7
allow_stretch: True
ScanCamera:
pos_hint: {'top': 0.9, 'right': 1}
size_hint: [1, 0.8]
canvas.before:
PushMatrix
Rotate:
angle: 0
origin: self.center
canvas.after:
PopMatrix
Line:
width: 2.
rectangle: (self.x + 40, self.y + 40, self.width/1.1, self.height/1.12)
ToggleButton:
id: show_bcode
pos_hint: {'bottom': 1, 'right': 1}
size_hint: [1, 0.1]
color: (1,1,1,1)
background_color: (0,0,0,0)
background_normal: ''
canvas.before:
Color:
rgba: (.18,.36,.61,1) if self.state=='down' else (0,0,0,1)
Rectangle:
pos: self.pos
size: self.size
text: 'Hier kom die barcode...'
''')
class BarcWin(FloatLayout):
cam_cam = ObjectProperty(None)
def __init__(self, **kwargs):
super(BarcWin, self).__init__(**kwargs)
self.cam_cam = ScanCamera()
def accept_in(self):
print('In')
def accept_out(self):
print('Out')
class ScanCamera(Image):
play = BooleanProperty(True)
index = NumericProperty(-1)
resolution = ListProperty([Window.width, Window.height])
symbols = ListProperty([])
code_types = ListProperty(set(pyzbar.pyzbar.ZBarSymbol))
Symb = namedtuple('Symb', ['type','data'])
app_ini_ = ObjectProperty(None)
got_bcode = BooleanProperty(False)
def __init__(self, **kwargs):
self._camera = None
super(ScanCamera, self).__init__(**kwargs)
if self.index == -1:
self.index = 0
on_index = self._on_index
fbind = self.fbind
fbind('index', on_index)
fbind('resolution', on_index)
on_index()
self.app_ini_ = App.get_running_app()
def on_tex(self, *l):
self.canvas.ask_update()
if not self.texture == None:
self.symbols = self._detect_qrcode_frame(texture=self.texture, code_types=self.code_types)
if not self.symbols == []:
for s in self.symbols:
if s.data:
if s.data.decode('utf-8') != "":
self.app_ini_.root.ids.show_bcode.text = s.data.decode('utf-8')
def _on_index(self, *largs):
self._camera = None
if self.index < 0:
return
if self.resolution[0] < 0 or self.resolution[1] < 0:
return
#first init of corecamera object
self._camera = CoreCamera(index=self.index,
resolution=self.resolution, stopped=True)
#when camera loads call _camera_loaded method to bind corecamera method with uix.image texture
self._camera.bind(on_load=self._camera_loaded)
if self.play:
self._camera.start()
self._camera.bind(on_texture=self.on_tex)
def _camera_loaded(self, *largs):
#bind camera texture with uix.image texture that is still equal to None
self.texture = self._camera.texture
def on_play(self, instance, value):
if not self._camera:
return
if value:
self._camera.start()
else:
self._camera.stop()
def _detect_qrcode_frame(self, texture, code_types):
if not self.got_bcode:
image_data = texture.pixels
size = texture.size
# Fix for mode mismatch between texture.colorfmt and data returned by
# texture.pixels. texture.pixels always returns RGBA, so that should
# be passed to PIL no matter what texture.colorfmt returns. refs:
# https://github.com/AndreMiras/garden.zbarcam/issues/41
pil_image = Img.frombytes(mode='RGBA', size=size,
data=image_data)
bcode = []
codes = pyzbar.pyzbar.decode(pil_image, symbols=code_types)
#print(pil_image, type(pil_image), dir(pil_image))
if codes != []:
for code in codes:
symbol = self.Symb(type=code.type, data=code.data)
bcode.append(symbol)
return bcode
else:
self.got_bcode = False
return []
class TestCamera(App):
title = 'Scan Camera'
def build(self):
return BarcWin()
def on_stop(self):
cc = ScanCamera()
print('Stop')
cc._camera.stop()
def on_pause(self):
return True
def on_resume(self):
pass
TestCamera().run()
Related
Below is a "Hello World" code snippet that I reworked from KivMob examples on GitHub. The examples weren't really helpful because they lacked proper comments, so I didn't know what was going on.
I can't see any ads when I port this app to Android. I'm also failing to see the logic of how and where the ads are supposed to appear. Can someone please help me to get KivMob to show up on Android?
from kivmob import KivMob
import kivy.utils
from kivy.app import App
from kivy.lang import Builder
from kivy.config import Config
from kivy.properties import ListProperty
from kivy.utils import platform
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.label import Label
if platform not in ('android', 'ios'):
# Approximate dimensions of mobile phone.
Config.set('graphics', 'resizable', '0')
__version__ = "1.0"
Builder.load_string("""
#:import kivy kivy
<KivMobDemoUI>:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
FloatLayout:
BoxLayout:
orientation: "vertical"
AnchorLayout:
CustomButton:
text: "Toggle Banner"
size_hint: 0.8, 0.2
on_release: app.toggle_banner()
<CustomButton>:
canvas.before:
Color:
rgba: self.box_color
RoundedRectangle:
pos: self.pos
size: self.size
color: (1, 1, 1, 1)
bold: True
text_size: self.width, None
height: self.texture_size[1]+size_hint_y: None
text_size: self.width, None
height: self.texture_size[1] + sp(20)
halign: 'center'
valign: 'middle'
""")
class KivMobDemoUI(FloatLayout):
pass
class CustomButton(ButtonBehavior, Label):
box_color = ListProperty(kivy.utils.get_color_from_hex("56b669"))
prv_color = ListProperty(kivy.utils.get_color_from_hex("56b669"))
def __init__(self, **kwargs):
super(CustomButton, self).__init__(**kwargs)
self.always_release = True
def on_press(self):
self.prv_color = self.box_color
self.box_color = (0,0,0,1)
def on_release(self):
self.box_color = self.prv_color
class KivMobDemo(App):
def build(self):
APP_ID = "ca-app-pub-ENTER#"
BANNER_ID = "ca-app-pub-ENTER#"
TEST_DEVICE_ID = "ca-app-pub-ENTER#"
self.ads = KivMob(APP_ID)
self.ads.add_test_device(TEST_DEVICE_ID)
self.ads.new_banner({"unitID": BANNER_ID})
self.ads.show_banner()
self.ads.request_banner()
self.toggled = False
return KivMobDemoUI()
def on_pause(self):
return True
def on_resume(self):
return True
def toggle_banner(self):
if not self.toggled:
self.ads.show_banner()
else:
self.ads.hide_banner()
self.toggled = not self.toggled
if __name__ == "__main__":
KivMobDemo().run()
You have to add some notes in your buildozer.spec file:
requirements = kivy, android, jnius, kivmob
...
android.permissions = INTERNET, ACCESS_NETWORK_STATE
android.api = 27
android.minapi = 21
android.sdk = 24
android.ndk = 17b
android.gradle_dependencies = 'com.google.firebase:firebase-ads:10.2.0'
p4a.branch = master
android.meta_data = com.google.android.gms.ads.APPLICATION_ID=ca-app-pub-3940256099942544~3347511713
I am using kivy and python to build an application.
I am trying to build an application in which I can select several images, add them to an array, and then pass this array of images through another method which stitches the images (using the stitcher class). The output image will display on one of the three screens (also I want to remove the middle screen).
So essentially what I would like help with is how to be able to select multiple files with filechooser in kivy and then add these files to array that I can later pass through a different method.
With the help of #ikolim in this post, I have been able to create the application.
main.py
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from PIL import Image
class RootWidget(TabbedPanel):
manager = ObjectProperty(None)
img = ObjectProperty(None)
img3 = ObjectProperty(None)
img4 = ObjectProperty(None)
lab = ObjectProperty(None)
def on_touch_up(self, touch):
if not self.img3.collide_point(*touch.pos):
return True
else:
self.lab.text = 'Pos: (%d,%d)' % (touch.x, touch.y)
return True
def switch_to(self, header):
# set the Screen manager to load the appropriate screen
# linked to the tab head instead of loading content
self.manager.current = header.screen
# we have to replace the functionality of the original switch_to
self.current_tab.state = "normal"
header.state = 'down'
self._current_tab = header
def select_to(self, *args):
try:
print(args[1][0])
iw = Image.open(args[1][0])
iw.save('./phase.jpg')
gray = iw.convert('1')
gray.save('./gray_im.jpg')
self.img3.source = './gray_im.jpg'
self.img4.source = './gray_im.jpg'
self.img.source = './phase.jpg'
self.img.reload()
self.img3.reload()
self.img4.reload()
except:
pass
def update_touch_label(self, label, touch):
label.text = 'Pos:(%d, %d)' % (touch.x, touch.y)
label.texture_update()
label.pos = touch.pos
label.size = label.texture_size[0] + 20, label.texture_size[1] + 20
class TestApp(App):
title = 'Screen Widget'
def build(self):
return RootWidget()
def on_pause(self):
return True
if __name__ == '__main__':
TestApp().run()
Test.kv
#:kivy 1.10.1
<RootWidget>:
manager: manager
img: img
img3: img3
img4: img4
lab: lab
do_default_tab: False
ScreenManager:
id: manager
Screen:
id: sc1
name:'Load img'
FileChooserIconView:
canvas.before:
Color:
rgb: 0.5, 0.4, 0.5
Rectangle:
pos: self.pos
size: self.size
on_selection:
root.select_to(*args)
Screen:
id: sc2
name: 'Image'
FloatLayout:
Button:
id: lab
pos_hint: {"right": 0.55, 'top': 1}
size_hint: .15,0.1
RelativeLayout:
Image:
id: img
on_touch_down:
str('Relative:{}'.format(args[1].pos))
pos_hint: {"left": 1, 'bottom': 1}
size_hint: 0.5, 1
allow_stretch: True
RelativeLayout:
Image:
id: img3
pos_hint: {"right": 1, 'bottom': 1}
size_hint: 0.5, 1
allow_stretch: True
Screen:
id: sc3
name: 'Image_'
FloatLayout:
Image:
id: img4
keep_data: True
post: self.pos
size: self.size
TabbedPanelHeader:
text: sc1.name
background_color: 1, 0, 0, 1
screen: sc1.name
TabbedPanelHeader:
text: sc2.name
background_color: 1, 1, 0, 1
screen: sc2.name
TabbedPanelHeader:
text: sc3.name
background_color: 1, 0, 1, 1
screen: sc3.name
In your Test.kv file, after FileChooserIconView: add multiselect: True
FileChooserIconView:
multiselect: True
This will allow the selection of multiple files.
If you use FileChooserListView instead of FileChooserIconView, the file chooser window will not discriminate selected/not selected files visually. You can still select multiple files, but you need to remember the selected files. The first click selects and the second click deselects. I hope the developers of this widget will correct this issue soon.
Here is an example that does what I think you want:
import os
import kivy
from kivy import platform
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
import kivy.garden.filebrowser
class FileBrowserApp(App):
def build(self):
self.root = FloatLayout()
button = Button(text='Select Files', pos_hint={'x':0, 'y': 0}, size_hint=(0.2, 0.1))
button.bind(on_press=self.do_select)
self.root.add_widget(button)
return self.root
def do_select(self, *args):
homeDir = None
if platform == 'win':
homeDir = os.environ["HOMEPATH"]
elif platform == 'android':
homeDir = os.path.dirname(os.path.abspath(__file__))
elif platform == 'linux':
homeDir = os.environ["HOME"]
self.fbrowser = kivy.garden.filebrowser.FileBrowser(select_string='Select',
multiselect=True, filters=['*.png'], path=homeDir)
self.root.add_widget(self.fbrowser)
self.fbrowser.bind(
on_success=self._fbrowser_success,
on_canceled=self._fbrowser_canceled,
on_submit=self._fbrowser_success)
def _fbrowser_success(self, fbInstance):
if len(fbInstance.selection) == 0:
return
selected = []
for file in fbInstance.selection:
selected.append(os.path.join(fbInstance.path, file))
print('selected: ' + str(selected))
self.root.remove_widget(self.fbrowser)
self.fbrowser = None
def _fbrowser_canceled(self, instance):
self.root.remove_widget(self.fbrowser)
self.fbrowser = None
if __name__=="__main__":
app = FileBrowserApp()
app.run()
Concerning the following code, which is a subset of my calculation game app, I have two questions:
1) Why is dummy_series not plotted, although the axes (with ticks and labels are drawn)
2) How do I get the quit button to properly exit the app (does it have to remove all widgets from the root widget? Is there an oposite to AppObject.run(), that stops the app? - SOLVED: App.get_running_app().stop()
For the first question, the relevant part of the code is found in the StatisticScreen and the PlotScreen classes. The first one creates the dummy_series, initializes the creation of the graph widget and changes the screen to the PlotScreen. In the PlotScreen class, there is showPlot methods, which is basically copied from the github README.
So far I tried to change the overall background color to white. Both by "canvas before" a white rectangle, and by really changing the background color of the window. Both had no effect (exept the axes and labels were hidden, because they are also white). Then I tried to color the graph differently each time I create it (taken from the same github repo, there is a TestApp in if __name__ == '__main__':). But there is still no graph.
For the second question please consider the changeScreen-method of CalculationRoot. If it is called with quit as the argument, currently it just empties the screen_list and returns False. The idea was to call the callback of the "Back"-Button (key=27,1000). Since closing the App with the "Back"-Button actually works, given the screen_list is empty, I thought I could use this existing process. Also scheduling the keyHandler-method of the app object CalculationApp does not have the desired effect of closing the app.
# Python build-in Modules
import os
import operator # better handling of +, #, *, etc.
import webbrowser # access homepages via the about section
import random # create random math questions
import datetime # for the timer
import itertools # eg for cycling colors
from functools import partial # schedule callback functions that take arguments different from 'dt'
# Kivy
from kivy.lang.builder import Builder
from kivy.app import App
from kivy.core.window import Window
from kivy.utils import platform
from kivy.uix.screenmanager import Screen
from kivy.properties import ObjectProperty, NumericProperty, StringProperty
from kivy.storage.dictstore import DictStore
from kivy.utils import get_color_from_hex as rgb
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.logger import Logger
from kivy.garden.graph import Graph, MeshLinePlot, SmoothLinePlot
# Non-standard python
import numpy as np
###############################################################################
# Constants
###############################################################################
BACKGROUND_COLOR = [0,0,0,0]
TEXT_COLOR = [1,1,1,1]
kv_string = """
#:import Factory kivy.factory.Factory
#:set color_button (0.784, 0.4, 0.216, 1) # brown
#:set color_button_pressed (0.659, 0.3, 0.431, 1) # darker brown
#:set color_background_down '(0.4, 0.4, 0.4, 1)' # purple
<WrappedLabel#Label>:
size_hint_y: None
height: self.texture_size[1] + self.texture_size[1]/2
markup: True
<GridLabel#Label>:
font_size: min(root.width, root.height) * .3
<StatisticsSpinner#Spinner>:
background_color: color_button if self.state == 'normal' else color_button_pressed
background_down: color_background_down
option_cls: Factory.get("SpinnerLabel")
<SpinnerLabel#SpinnerOption>:
background_color: color_button if self.state == 'down' else color_button_pressed
background_down: color_background_down
<CalculationRoot>:
orientation: 'vertical'
cg_screen_manager: cg_screen_manager
statistic_screen: statistic_screen
plot_screen: plot_screen
ScreenManager:
id: cg_screen_manager
StartScreen:
name: 'StartScreen'
StatisticScreen:
id: statistic_screen
name: 'StatisticScreen'
PlotScreen:
id: plot_screen
name: 'PlotScreen'
<StartScreen#Screen>:
BoxLayout:
orientation: 'vertical'
padding: root.width * .02, root.height * .02
spacing: min(root.width, root.height) * .02
WrappedLabel:
text: '[b] Calculation Game [/b]'
font_size: min(root.width, root.height) * .1
Button:
text: 'Statistic'
on_release: app.root.changeScreen(self.text.lower())
Button:
text: 'Quit'
<StatisticScreen#Screen>:
stats_operation_spinner: stats_operation_spinner
stats_difficulty_spinner: stats_difficulty_spinner
stats_num_questions_button: stats_num_questions_button
BoxLayout:
orientation: 'vertical'
padding: root.width * .02, root.height * .02
spacing: min(root.width, root.height) * .02
WrappedLabel:
text: '[b] Statistics [/b]'
halign: 'center'
font_size: min(root.width, root.height) * .1
GridLayout:
size_hint: .9,.4
cols: 2
pos_hint: {'center_x': .5}
GridLabel:
text: 'Operation Type'
StatisticsSpinner:
id: stats_operation_spinner
text: '+'
values: ['+', '-', '*', ':', '%']
GridLabel:
text: 'Difficulty'
StatisticsSpinner:
id: stats_difficulty_spinner
text: '1'
values: ['1','2','3','4']
GridLabel:
text: 'Number of Questions'
Button:
id: stats_num_questions_button
text: '8'
on_release: app.root.statistic_screen.switchNumQuestions(self, self.text)
# on_release: self.text = '16' if self.text == '8' else self.text = '8'
background_color: color_button if self.state == 'normal' else color_button_pressed
Button:
size_hint: 1, .2
text: 'Plot'
on_release: app.root.statistic_screen.showPlot()
font_size: min(root.width, root.height) * .1
<PlotScreen#Screen>:
"""
###############################################################################
# Widgets
###############################################################################
class StatisticScreen(Screen):
""" Selection screen, where you can fix the parameters for
a pot of your statistics.
"""
def __init__(self, *args, **kwargs):
super(StatisticScreen, self).__init__(*args, **kwargs)
def showPlot(self):
""" 'onPlotButtonPress'
callback for the 'plot'-Button on the bottom of StatisticScreen
"""
dummy_series = np.random.randint(1, 10, (12,))
App.get_running_app().root.ids.plot_screen.createKivyPlot(dummy_series)
App.get_running_app().root.changeScreen('plot')
def switchNumQuestions(self, instance, text):
""" 'onNumQuestionsButtonPress'
callback for the 'choose number of questions'-button.
"""
if int(text) == 16:
instance.text = '8'
else:
instance.text = '16'
class PlotScreen(Screen):
def __init__(self, *args, **kwargs):
super(PlotScreen, self).__init__(*args, **kwargs)
self.series = None
self.graph_figure = None # error if uncommented... why? (referred to former name self.canvas)
self.colors = itertools.cycle([rgb('7dac9f'), rgb('dc7062'), rgb('66a8d4'), rgb('e5b060')])
def createKivyPlot(self, series=np.array(range(12))):
graph_theme = {
'label_options': {
'color': rgb('444444'), # color of tick labels and titles
'bold': True},
'background_color': rgb('f8f8f2'), # back ground color of canvas
'tick_color': rgb('808080'), # ticks and grid
'border_color': rgb('808080')} # border drawn around each graph
self.graph_figure = Graph(xlabel='Last 12 games', ylabel='Average Response Time', x_ticks_minor=1,
x_ticks_major=5, y_ticks_major=1,
y_grid_label=True, x_grid_label=True, padding=10,
x_grid=True, y_grid=True, xmin=0, xmax=len(series), ymin=0, ymax=int(1.5*max(series)), **graph_theme)
plot = SmoothLinePlot(mode='line_strip', color=next(self.colors))
plot.points = [(x, series[x]) for x in range(0, len(series))]
self.graph_figure.add_plot(plot)
self.add_widget(self.graph_figure)
def destroy(self):
self.remove_widget(self.graph_figure)
self.graph_figure = None
###############################################################################
# Root Widget
###############################################################################
class CalculationRoot(BoxLayout):
""" Root of all widgets
"""
calculation_screen = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(CalculationRoot, self).__init__(*args, **kwargs)
self.screen_list = [] # previously visited screens
def changeScreen(self, next_screen):
if self.screen_list == [] or self.ids.cg_screen_manager.current != self.screen_list[-1]:
self.screen_list.append(self.ids.cg_screen_manager.current)
if next_screen == 'start':
self.ids.cg_screen_manager.current = 'StartScreen'
elif next_screen == 'statistic':
self.ids.cg_screen_manager.current = 'StatisticScreen'
elif next_screen == 'plot':
self.ids.cg_screen_manager.current = 'PlotScreen'
elif next_screen == 'quit':
self.screen_list == []
#self.onBackBtnPress() # not working
#Clock.schedule_once(partial(App.get_running_app().keyHandler(27)), 0) # not working
return False
def onBackBtnPress(self):
if self.screen_list:
self.ids.cg_screen_manager.current = self.screen_list.pop()
return True
return False
###############################################################################
# App Object
###############################################################################
class CalculationApp(App):
""" App object
"""
def __init__(self, *args, **kwargs):
super(CalculationApp, self).__init__(*args, **kwargs)
self.use_kivy_settings = False
def keyHandler(self, *args):
key = args[1]
print(key)
if key in (1000, 27):
return self.root.onBackBtnPress()
def post_build_init(self, ev):
if platform == 'android':
pass
win = self._app_window
win.bind(on_keyboard = self.keyHandler)
def build(self):
Builder.load_string(kv_string)
self.bind(on_start=self.post_build_init)
return CalculationRoot()
if __name__ in ('__main__', '__android__'):
CalculationApp().run()
Apparently, there has been a problem with using kivy.garden.graph inside a ScreenManager for some time. According to this issue report, it has been fixed in kivy version v1.10.1.dev0. However, I think you can get around it by adding _with_stencilbuffer=False to your call to Graph().
And to stop the app, you can modify your kv_string in the StartScreen section to include :
Button:
text: 'Quit'
on_release: app.stop()
I am using Kivy with a webcam. I have followed this example by #Arnav of using opencv to form and display the camera as a widget. I have "extended" the layout within python it to add two buttons as a test, in preparation for a more complicated layout.
class CamApp(App):
def build(self):
self.capture = cv2.VideoCapture(0)
self.my_camera = KivyCamera(capture=self.capture, fps=30,resolution=(1920,1080))
root = BoxLayout(orientation = 'vertical')
root.add_widget(self.my_camera,1)
box2 = BoxLayout(orientation = 'vertical')
btn1 = Button(text='Hello world 1')
btn2 = Button(text='Hello world 2')
box2.add_widget(btn1)
box2.add_widget(btn2)
root.add_widget(box2, 0)
return root
#return Builder.load_string(kv)
While this works I would prefer to move the UI components out of python and into a kv language file.
The problem is knowing how to "describe" the self.my_camera in the kv file?
I am not sure whether to inherit the KivyCamera class as a widget within the kv file i.e.
kv = '''
<Cam1#KivyCamera>:
texture: self.my_camera
resolution: (1920, 1080)
pos: self.pos
size: self.size
Or whether to use the canvas widget
<MyWidget>:
canvas:
Rectangle:
source: self.my_camera
pos: self.pos
size: self.size
I have tried other "hacked" implementations, but in all cases the problem is linking through the self.my_camera into the kv file.
Any suggestions?
Perhaps this example may help you.
# Import 'kivy.core.text' must be called in entry point script
# before import of cv2 to initialize Kivy's text provider.
# This fixes crash on app exit.
import kivy.core.text
import cv2
from kivy.app import App
from kivy.base import EventLoop
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.graphics.texture import Texture
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
class KivyCamera(Image):
def __init__(self, **kwargs):
super(KivyCamera, self).__init__(**kwargs)
self.capture = None
def start(self, capture, fps=30):
self.capture = capture
Clock.schedule_interval(self.update, 1.0 / fps)
def stop(self):
Clock.unschedule_interval(self.update)
self.capture = None
def update(self, dt):
return_value, frame = self.capture.read()
if return_value:
texture = self.texture
w, h = frame.shape[1], frame.shape[0]
if not texture or texture.width != w or texture.height != h:
self.texture = texture = Texture.create(size=(w, h))
texture.flip_vertical()
texture.blit_buffer(frame.tobytes(), colorfmt='bgr')
self.canvas.ask_update()
capture = None
class QrtestHome(BoxLayout):
def init_qrtest(self):
pass
def dostart(self, *largs):
global capture
capture = cv2.VideoCapture(0)
self.ids.qrcam.start(capture)
def doexit(self):
global capture
if capture != None:
capture.release()
capture = None
EventLoop.close()
class qrtestApp(App):
def build(self):
Window.clearcolor = (.4,.4,.4,1)
Window.size = (400, 300)
homeWin = QrtestHome()
homeWin.init_qrtest()
return homeWin
def on_stop(self):
global capture
if capture:
capture.release()
capture = None
qrtestApp().run()
and the kv file:
<QrtestHome>:
BoxLayout:
orientation: "vertical"
Label:
height: 20
size_hint_y: None
text: 'Testing the camera'
KivyCamera:
id: qrcam
BoxLayout:
orientation: "horizontal"
height: 20
size_hint_y: None
Button:
id: butt_start
size_hint: 0.5,1
text: "start"
on_press: root.dostart()
Button:
id: butt_exit
text: "quit"
size_hint: 0.5,1
on_press: root.doexit()
I am trying to get the label’s text in “EventWindow” to update when the variable “current_text” in the Event class in “scripts/events.py”. I realize that the answer may lie in binding “current_text” to “ct”, but that just results in “AttributeError: class Event has no attribute ‘bind’”. If I am barking up the wrong tree with this solution, I would be very receptive of another method.
Below are the relevant code snippets, but the full project is available at: https://github.com/DinkWerks
main.py
from kivy.properties import ObjectProperty, StringProperty
from kivy.app import App
# Utility Imports
import yaml
# Game Imports
from scripts.events import Event
# File Loads
loc_file = file('data/location.yml')
loc_dat = yaml.load(loc_file)
# Bind Classes
event = Event()
class EventWindow(BoxLayout):
ct = StringProperty('')
def __init__(self, **kwargs):
super(EventWindow, self).__init__(**kwargs)
self.ct = event.current_text
# Error occurs below. Comment out too see semi-functional app.
Event.bind(current_text=self.setter('ct'))
scripts/events.py
from kivy.properties import StringProperty
...
from player import Player
from enemy import Enemy
class Event(Player):
open_events = file('data/event.yml', 'r')
event_file = yaml.load(open_events)
current_text = StringProperty('1234')
def __init__(self):
Player.__init__(self)
self.events = Event.event_file
self.selection = ''
self.current_text = '1234'
def event_name(self):
...
def event_selector(self, eid):
...
def parse(self):
driver = 1
variables = ('Name', 'Is_Person', 'Level', 'Gold')
poss_commands = ("[Next Slide]", "[Query]", "[Terminate]", "[Combat]")
while driver >= 0:
text = self.events[self.selection][driver]
lexer = shlex(text)
lexer.quotes = '/'
output = ''
command = ''
for token in lexer:
if token in variables:
output += str(eval('Player.' + token))
elif token.replace('/', '') in poss_commands:
command += token.replace('/', '')
else:
output += token.replace('/', '')
self.current_text = output
driver += self.controller(command)
self.modifier()
def modifier(self):
...
def controller(self, cmd):
...
text.kv
<EventWindow>:
BoxLayout:
pos: 100,100
size_hint: .4,.7
orientation: 'vertical'
Image:
source: 'maps/map.jpg'
pos: self.pos
size: self.size
ScrollView:
canvas.before:
Color:
rgba: [.2,.2,.2,.8]
Rectangle:
size: self.size
pos: self.pos
Label:
id: text_area
text: root.ct
padding: 15,10
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
<Foo>:
id: bl
popup: popup.__self__
header: header
BoxLayout:
orientation: 'vertical'
BoxLayout:
...
FloatLayout:
id: mapspace
canvas:
...
EventWindow:
id: event
Popup:
...
Thanks!
In your code, Event is derived from Player, which itself is an old style class. Importantly, neither implement methods necessary for event management (e.g. fbind; for a stub class defining an interface see Observable). Most likely it is enough to make Event or Player a subclass of EventDispatcher to be able to create and bind to events kivy-style.