Kivy: How to update an image on a screen? - python

Hello, I am currently creating a GUI using kivy, on Windows. I am using it to ssh into a Raspberry Pi, take an image, then scp it back to my windows machine. I have successfully done this using the GUI. I have two screens. The first is the login. Once I login a second screen appears with buttons and an image. The login screen and buttons serve their functions properly. However, the image file does not update like I want it to. Either I want it to update in a certain interval, or update after I take a picture.
Here is the
Second Screen Interface. I want the picture on the top right to update itself automatically. The "take picture" button takes the picture then sends it to my computer where I'd like to refresh the image in the GUI and display it
My main python file, which I called "ScreenChange2.py" is shown below
import kivy
import os
kivy.require('1.10.0')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.image import Image
from kivy.properties import StringProperty
from kivy.clock import Clock
from kivy.uix.widget import Widget
from sshtest import ssh #import the ssh class from the ssh python file
class ScreenOne(Screen):
def login(self): #define login using ssh as a function
#Make a Class Variable called connection. This allows for other
#classes to call on it without passing it as an argument
ScreenOne.connection = ssh("192.168.1.3", "pi", "seniordesign")
#ScreenOne.connection.sendCommand("ls")
#ScreenOne.connection.sendCommand("mkdir thisistest")
print("Logging in") #For error checking
def gpioSet(self): #allows for gpio pins to trigger image capture
ScreenOne.connection.sendCommand("echo '18' > /sys/class/gpio/export")
ScreenOne.connection.sendCommand("echo 'out' > /sys/class/gpio/gpio18/direction")
class ScreenTwo(Screen): #This is where all the functions are
def command(self, input): #create a function that sends command through ssh
ScreenOne.connection.sendCommand(input) #Call on connection made before to send command
class MyScreenManager(ScreenManager):
pass
#Create the Application that will change screens. Add App(App) to end of wanted classname
class ScreenChangeApp(App):
#Create a function that builds the app. Keyword self make sures to reference
#current instantiation
def build(self):
screen_manager = ScreenManager()
screen_manager.add_widget(ScreenOne(name = "screen_one"))
screen_manager.add_widget(ScreenTwo(name = "screen_two"))
return screen_manager #after building the screens, return them
#MyScreenManager.login()
sample_app = ScreenChangeApp()
sample_app.run()
The KV file is as shown
#: import os os
<CustomButton#Button>:
font_size: 12
size_hint: .2,.1
<Picture#Image>:
id: image
<ScreenOne>: #define The First Screen
BoxLayout: #Use box layout
Button: #create button
text: "Connect to the Raspberry Pi"
on_press:
root.login()
root.gpioSet()
root.manager.transition.direction= "left"
root.manager.transition.duration = 1
root.manager.current = "screen_two"
<ScreenTwo>:
BoxLayout:
spacing: 10
padding: 10
orientation: "vertical"
CustomButton:
text: "Send an ls command"
on_press:
root.command("ls")
CustomButton:
text: "Take picture"
on_press:
root.command("python cameradaemon.py &") #'&' runs script in background
root.command("sleep .1")
root.command("echo '1' > /sys/class/gpio/gpio18/value") #set pin high to take pic
root.command("echo '0' > /sys/class/gpio/gpio18/value") #take it off to prevent another photo
root.command("scp TEST.jpg Jason#192.168.1.10:C:/Users/Jason/Desktop/RaspberryPiTransferred")
root.command("kill %1")
CustomButton:
text: "Create Histogram"
on_press:
os.system("cd C:/Users/Jason/Desktop/KivyFiles/Histogram & python histogram.py")
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
padding: 10
BoxLayout
size_hint: .3, .3
Image:
source: 'C:/Users/Jason/Desktop/RaspberryPiTransferred/TEST.jpg'
As you can probably tell, at the end of the KV file, I insert the image file in a box layout as static. I understand this is why my image won't update, but what I need to know is how I can update it automatically.
I was thinking I could maybe make a custom picture rule, which I started at the top as
<Picture#Image>:
I also understand there is a Reload() function for image files, but I do not understand how to implement that in a KV file.
I've tried creating a class in my main file that runs the reload function every second, but the image doesn't display as it isn't linked to any image in the KV file.
In other words, how do I make it so that the image being displayed in this Second Screen to update automatically given the two scripts I've given. Thank you

In your ScreenTwo class create a StringProperty at the class level:
class ScreenTwo(Screen): #This is where all the functions are
img_src = StringProperty('C:/Users/Jason/Desktop/RaspberryPiTransferred/TEST.jpg')
def command(self, input):
And in your kv file reference it by:
Image:
source: root.img_src
Then change the image just by doing:
img_src = 'some/new/image.jpg'
The image should update without any further actions needed.

Related

Button doing on_release action twice in kivy python

I'm trying to make an app that can automate an Instagram page, and I have the script for the program ready, but just need to implement it into a GUI. For this, I'm using Kivy. However, I only need to send one request to the API for this. When I click on a button, it triggers the command attached to on_release of the button twice. How can I fix this?
Simplified Python Script:
from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang import Builder
class StartScreen(Screen):
pass
class WindowManager(ScreenManager):
pass
class BruhApp(MDApp):
def build(self):
self.root = Builder.load_file("bruh.kv")
if __name__ == '__main__':
BruhApp().run()
.kv File:
WindowManager:
StartScreen:
<StartScreen>:
name: "start"
FloatLayout:
MDLabel:
text: "Welcome to the Instagram Page Automator!"
font: "RobotoThin"
font_style: "H5"
halign: "center"
size_hint:0.9,0.7
color: 0,0,0,0.5
pos_hint:{"center_x":0.5,"y":0.5}
MDFillRoundFlatButton:
md_bg_color: 0, 1, 0.6, 1
text:"Continue"
font: "RobotoThin"
size_hint:0.4,0.07
pos_hint:{"center_x":0.5,"y":0.1}
on_release:
print("yes")
This script prints "Yes" twice for me everytime I press the button.
The kivy system automatically loads a kv file for an App if the file is correctly named (See the documentation). Since your kv file is correctly named, it will be loaded automatically. But since you also load it explicitly using the line:
self.root = Builder.load_file("bruh.kv")
the kv file actually gets loaded twice, which causes some things to be instantiated twice. Try replacing the build() method with a simple pass.

Kivy (Python) Problem with AttributeError after add_widget with kv-file

I'am new in Kivy and have follow problem (Environment is Python 3.7 with Kivy-1.11.1.):
I need a navigation area and a view area (=ViewScreen). With the navigation area i change the view area (change of kv-files - look later at 'def next_screen'). My problem is, that i can't interact with widgets (e.g. 'lblTest') in the view area.
I use follow files:
testGUI.py (= GUI Application)
testGUIRoot.kv (= RootWidget as kv-file)
testGUIBtn1.kv (= view area 1 as kv-file)
testGUIBtn2.kv (= view area 2 as kv-file)
The GUI Application is simple and starts the GUI and change the view area.
testGUI.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
class RootWidget(BoxLayout):
# runs select application
def startApplication (self, instance):
print(self.lblTest)
class mainGUI(App):
def build(self):
# loading the content of root.kv
self.root = Builder.load_file('testGUIRoot.kv')
def next_screen(self, screen):
#Clear container and load the given screen object from file in kv folder.
filename = screen + '.kv'
# unload the content of the .kv file
# reason: it could have data from previous calls
Builder.unload_file(filename)
# clear the container
self.root.container.clear_widgets()
# load the content of the .kv file
screen = Builder.load_file(filename)
# add the content of the .kv file to the container
self.root.container.add_widget(screen)
if __name__ == '__main__':
'''Start the application'''
mainGUI().run()
I use follow kv-files:
testGUIRoot.kv:
#:kivy 1.11.1
RootWidget:
container: container
orientation: 'horizontal'
# Navigation
BoxLayout:
orientation: 'vertical'
size_hint: (0.35, 1)
Button:
text: 'testButton1'
on_release: root.startApplication(self,)
on_press: app.next_screen('testGUIBtn1')
Button:
text: 'testButton2'
on_release: root.startApplication(self,)
on_press: app.next_screen('testGUIBtn2')
# ViewScreen
BoxLayout:
size_hint: (0.65, 1)
id: container
orientation: 'vertical'
padding: 0
spacing: 3
Label:
text: 'no data'
color: (0.667,0.667,0.667,1)
font_size: 14
bold: True
testGUIBtn1.kv:
#:kivy 1.11.1
Label:
id: lblTest
text: 'Button 1'
color: (0.667,0.667,0.667,1)
font_size: 14
bold: True
testGUIBtn2.kv:
#:kivy 1.11.1
Label:
id: lblTest
text: 'Button 2'
color: (0.667,0.667,0.667,1)
font_size: 14
bold: True
Follow error appears:
AttributeError: 'RootWidget' object has no attribute 'lblTest'
Have you a solution to interact with the Object 'lblTest'? For example like self.lblTest.text = 'Test-Text'.
Thank you in advance
I have been working with Kivy just this week, also as a new user.
The thing I have learned is to define properties on your RootWidget, just as the labels you already defined in the .kv files. This 'links' the layout and the Python code to eachother.
First of all you need to import the Kivy ObjectProperty by adding:
from kivy.properties import ObjectProperty
Next up is to declare the property on your RootWidget class.
You can add lblTest = ObjectProperty(None) right after the class-declaration.
The top of your file testGUI.py should look like this then:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
class RootWidget(BoxLayout):
# Link the layout objects
lblTest = ObjectProperty(None)
# runs select application
def startApplication (self, instance):
print(self.lblTest)
The page that really helped me with this is https://techwithtim.net/tutorials/kivy-tutorial/object-properties/
A little sidenote is it would be best to keep your id attributes fully unique.
I found the solution
Content of 'def next_screen' of mainGUI(App) to 'def startApplication'. Now i can change widgets in object oScreen or using the object in other python libs.
def startApplication (self, instance, sScreen):
filename = sScreen + '.kv'
# unload the content of the .kv file
# reason: it could have data from previous calls
Builder.unload_file(filename)
# clear the container
self.container.clear_widgets()
# load the content of the .kv file
oScreen = Builder.load_file(filename)
# add the content of the .kv file to the container
self.container.add_widget(oScreen)
print(oScreen.ids.lblTest.text)
The follow should be added in kv-files testGUIBtn1.kv, testGUIBtn2.kv:
RootWidget:
In kv-file testGUIRoot.kv you have change on_release to
on_release: root.startApplication(self,'testGUIBtn1')

Python & Kivy: Camera and Coming Texts in Different Screens

I want to build an app with Kivy in Python but I got some errors that I tried to solve many times but I can't.
I'm opening a camera screen firstly. In screen, we see our webcam screen and there is 2 buttons at the bottom (Play and Capture). While I pressing Play, webcam is on and if I press Capture button, I'm taking snapshot. After taking snapshot, we are going to the screen which is located on the left. Special thanks to Erik , he built this working code below.
But when I capture the photo and we are in the left screen, I want to show some text (coming from ANOTHER py file for example basic hello1, hello2... whatever) in left screen.
I know that how can I print basically a sentence in just Console with only print function but I want to write many print function's results in my application on the left screen synchronously from ANOTHER py file.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
import time
class CheckScreen(Screen):
pass
class CameraClickScreen(Screen):
def capture(self):
camera = self.ids['camera']
timestr = time.strftime("%Y%m%d_%H%M%S")
camera.export_to_png("IMG_{}.png".format(timestr))
print("Captured")
GUI = Builder.load_string("""
GridLayout:
cols: 1
ScreenManager:
id: screen_manager
CameraClickScreen:
name: "camera_click_screen"
id: camera_click_screen
CheckScreen:
name: "check_screen"
id: check_screen
<CameraClickScreen>:
orientation: 'vertical'
GridLayout:
cols: 1
Camera:
id: camera
resolution: (640, 480)
play: False
ToggleButton:
text: 'Play'
on_press: camera.play = not camera.play
size_hint_y: None
height: '48dp'
Button:
text: 'Capture'
size_hint_y: None
height: '48dp'
on_press:
root.capture()
# root refers to <CameraClickScreen>
# app refers to TestCamera, app.root refers to the GridLayout: at the top
app.root.ids['screen_manager'].transition.direction = 'left'
app.root.ids['screen_manager'].current = 'check_screen'
<CheckScreen>:
Button:
text: "Next Screen"
font_size: 50
""")
class TestCamera(App):
def build(self):
return GUI
TestCamera().run()
Normally with this code, on the left screen, we see "Next Screen" sentence with a button. I want to see variable texts coming from py file. For example Hello 1, and clear the screen, Hello 2, and clear the screen, ..., Hello 5 and stop.
How can I add this basic print functions and integrate with app to show us on the left screen?

Kivy popup running in separate thread

I am populating a treeview in Kivy that takes some time depending on how large it is.
In the case the tree is large and takes awhile, I would like to display a popup while it is populating so the user is aware the program has not frozen, and close this popup when the logic for populating the tree finishes.
Here is what I have come up with through some research on the topic, but the popup still seems to only come once the tree is finished populating:
def show(self, *args):
self.error_popup.open()
def populate_tree(self, model):
#Clock.schedule_once(self.error_popup.open())
popup_thread = threading.Thread(target=self.show())
popup_thread.start()
# order the dictionary for better user experience
ordered_data = collections.OrderedDict(sorted(model.items()))
# logic to populate tree
for county, value in ordered_data.items():
if county != "model_name":
# set initial county dropdowns in tree
county_label = self.treeview.add_node(TreeViewButton(text=str(county), on_press=self.edit_node))
i = 0 # keep count of rules
# add rules children to county
for rule_obj, rule_list in value.items():
for rule in rule_list:
i += 1
# set rule number in tree
rule_label = self.treeview.add_node(TreeViewButton(text='Rule ' + str(i), on_press=self.edit_node), county_label)
# add conditions children to rule
for condition in rule:
self.treeview.add_node(TreeViewButton(text=condition, on_press=self.edit_node), rule_label)
#Clock.schedule_once(self.error_popup.dismiss())
#somehow close popup_thread
I included a kivy Clock attempt in case that is more on the right track of what I am looking for, however currently it will just open the popup and never populate the tree. I am new to GUI programming and event callbacks, so any help is greatly appreciated.
I tried keeping the code short, if more is needed please let me know.
I built an app which does something similar to what you're doing (different computation, but as you said the point was it was time-consuming and you want to thread a popup that shows the app isn't crashed - it's just crankin' the numbers). What ended up working for me was to set up a button to execute a dummy function which toggles both the popup and the calculation. Run the popup first and then thread the calculation through the 'from threading import Thread' module to execute the computation on a separate thread.
Here's a working example. It's just sleeping for 5 seconds but you can stick your computation into that function and it should work just fine. What it does is opens the popup before the computation and closes the popup when the calculation is done. Also, you can stick a 'Loading.gif' file into the folder and it'll import that as your loading gif if you want to use something other than what kivy pulls up (which is essentially a loading gif for loading your Loading.gif which isn't loading because it's not there... haha). Also added an 'ABORT' button if your user gets tired of waiting.
Finally just as a side note, I've had difficulties getting the .kv file to build into the pyinstaller application bundeler, so just as a heads up, using the builder.load_string(KV) function is a good alternative for that.
from threading import Thread
from sys import exit
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.lang import Builder
KV = '''
<Pop>:
id:pop
title: ''
auto_dismiss: False
padding: 10
spacing: 10
BoxLayout:
BoxLayout:
padding: 10
spacing: 10
orientation: 'vertical'
Label:
font_size: 22
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
text: "Process is currently running."
Label:
id: error_msg
size_hint_x: 0.3
text: ''
BoxLayout:
orientation: 'vertical'
Button:
background_color: (1,0,0,1)
text: "ABORT"
on_press: root.sysex()
AsyncImage:
source: 'Loading.gif'
<MetaLevel>:
rows: 1
cols: 1
Button:
text: 'RUN'
on_release: root.dummy()
'''
Builder.load_string(KV)
class MetaLevel(GridLayout):
def dummy(self, *args):
App.get_running_app().pop.open()
Thread(target=self.calculate, args=(args,), daemon=True).start()
def calculate(self, *args):
import time
time.sleep(5)
App.get_running_app().pop.dismiss()
class Pop(Popup):
def sysex(self):
exit()
class Cruncher(App):
def build(self):
self.pop = Pop()
return MetaLevel()
if __name__ == "__main__":
Cruncher().run()
Were you able to get this sorted?
I think it works if you use the thread for populating the tree rather than using it for showing the popup. After populating the tree, in the same thread you can close the pop up using Popup.dismiss()
main.py file
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
import time, threading
class popupTestApp(App):
def waitSec(self):
time.sleep(5)
self.p.dismiss()
def popUpFunc(self):
self.p = Popup(title='Test Popup', content=Label(text='This is a test'), size_hint=(None,None), size=(400,400))
self.p.open()
popUpThread = threading.Thread(target=self.waitSec)
popUpThread.start()
if __name__ == '__main__':
popupTestApp().run()
popuptest.kv file
BoxLayout:
BoxLayout:
id:LeftPane
Button:
id:MyButton
text:'Pop it up!'
on_release:app.popUpFunc()
BoxLayout:
id:RightPane
Label:
text: 'Another Pane'
Take a look at the below link where this is explained well.
Building a simple progress bar or loading animation in Kivy

struggling with python and Kivy

I'm learning python and Kivy and I'm really struggling to understand how to call functions and continue functions from a Kivy GUI.
Here is my .py:
import csv
import os
import easygui
import kivy
kivy.require('1.0.7')
from kivy.app import App
from kivy.animation import Animation
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
def csvImport(filename):
with open(filename, 'rb') as f:
reader = csv.reader(f)
your_list = list(reader)
return your_list
class LoadFile(App):
def FileLoadScreen(self):
self.add_widget(Button(size_hint_y=(None), height=('48dp'), text='Select File',
on_press=self.ImportFile))
def ImportFile(self, instance):
filepath = easygui.fileopenbox()
if filepath!='.':
a=csvImport(filepath)
instance.text='File Loaded'
instance.disabled=True
class loginBAKApp(App):
def logAuth(username,password):
if username!='' and password!='':
print('ok')
kv_directory = 'GUI'
if __name__ == '__main__':
loginBAKApp().run()
And this is my loginBAK.kv:
#:kivy 1.9.0
GridLayout:
row_force_default: True
row_default_height: 40
rows: 3
cols: 2
padding: 10
spacing: 10
Label:
id: userLabel
text: 'Username:'
TextInput:
id: username
Label:
id: passwordLabel
text: 'Password:'
TextInput:
id: password
password: True
Button:
id:btn_login
text: 'Login'
on_press: print('OK')
This code seems to work without issues (when I click the login button, it does print 'OK'. I tried to swap it out with
on_press: logAuth(username,password)
and I get an error that logAuth is not defined.
Ultimately, what I'm trying to model here (as my first learning experience) is to hit the login button and as long as the fields are not blank, display a login success message for 5 seconds and then delete the fields and call the LoadFile app (add a button that can be clicked to select and import a file).
What exactly am I doing wrong here? I've sifted through about 60 scripts online and have been looking at the Kivy examples for hours and I cannot figure out how I'm doing this wrong. Can someone point me in the right direction and/or make suggestions as to creating/deleting the gui to do what I described? I'm new to Kivy (and can code basic python scripts) so this is all a little confusing to when I read some of the other questions on stackoverflow.
on_press: logAuth(username,password)
logAuth is a method of your app class, not a function defined in the kv namespace. You can instead use app.logAuth(...), app is a keyword that references the current App instance.

Categories