Im trying to update the texture of an image on a second screen on my application. When I try to do this it throws an Index out of range error.
I think this might be because im not updating this on the main thread as im trying to do it via the second window, but I dont know how I could structure the program any differently, without splitting up the two screens.
Code and Logs and screenshots
WindowManager:
LoginWindow:
MainWindow:
<LoginWindow>:
name: "Login"
MDTextField:
id: ipAddress
hint_text: "drone ip"
size_hint_x: None
size_hint_y: None
height: 30
width: 200
pos_hint: {"center_x": .5, "center_y": .55}
on_text_validate: app.ip_validate(ipAddress.text)
MDSpinner:
id: spinnerIP
size_hint: None, None
size: dp(20), dp(20)
pos_hint: {'center_x': .5, 'center_y': .45}
active: False
MDLabel:
id: timeLbl
text: "I am here"
theme_text_color: "Hint"
font_size: ipAddress.font_size
pos_hint: {'center_x': 0.52, 'center_y': 0.95}
<MainWindow>
name: "Main"
MDLabel:
id: timeLbl
text: "I am here"
theme_text_color: "Hint"
pos_hint: {'center_x': 0.52, 'center_y': 0.95}
Image:
id: imageFrame
source: "images/noSignal.jpg"
#!/usr/bin/env python3.9
# adding sub-modules
import sys
sys.path.append('../')
# auxillary libraries
import cv2
import socket
import threading
import subprocess
import numpy as np
from enum import Enum
from datetime import datetime
# kivy libraries
from kivymd.app import MDApp
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.image import Image
from kivy.graphics.texture import Texture
from kivy.uix.screenmanager import ScreenManager, Screen
# module libraries
#from Networking.GCSPublisher import GCSPublisher
# Window Enum class
class windows(Enum):
loginWindow = 0
mainWindow = 1
# login window class
class LoginWindow(Screen):
pass
# main window class
class MainWindow(Screen):
pass
# window manager
class WindowManager(ScreenManager):
pass
# class - UAVApp
class UAVApp(MDApp):
# current frame
currentFrame = windows.loginWindow.name
# builds the application
def build(self):
# layout options
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "BlueGray"
# creating capture for imagery
self.capture = cv2.VideoCapture(0)
# scheduling image clock
Clock.schedule_interval(self.frame_capture, 1/30.0)
# scheduling time clock
Clock.schedule_interval(self.time_function, 1)
return Builder.load_file('UAVApp.kv')
# a function to capture frames from the receiver
def frame_capture(self, dt):
if(self.currentFrame == windows.mainWindow.name):
# get frame
ret, frame = self.capture.read()
if(ret):
bufferFrame = cv2.flip(frame, 0)
bufferFrameStr = bufferFrame.tostring()
imageTexture = Texture.create(size = (frame.shape[1], frame.shape[0]), colorfmt = 'bgr')
imageTexture.blit_buffer(bufferFrameStr, colorfmt = 'bgr', bufferfmt = 'ubyte')
self.root.screens[windows.mainWindow.value].ids['imageFrame'].texture = imageTexture # update texture
# a function to update the time label
def time_function(self, dt):
if(self.currentFrame == windows.loginWindow.name):
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
#self.root.ids.timeLbl.text = current_time
self.root.screens[windows.loginWindow.value].ids.timeLbl.text = current_time # todo: make this an enum
# ADC threading
def controlThread(self, name):
# opening connection to drone
gcsPublisher = GCSPublisher(self.root.screens[windows.loginWindow.value].ids.ipAddress.text)
# on ip text field validation
def ip_validate(self, text):
self.root.screens[windows.loginWindow.value].ids.spinnerIP.active = True
validIP = self.checkIP(self.root.screens[windows.loginWindow.value].ids.ipAddress.text)
if(not validIP):
self.root.screens[windows.loginWindow.value].ids.spinnerIP.active = False
self.root.screens[windows.loginWindow.value].ids.ipAddress.text = ""
return
self.root.screens[windows.loginWindow.value].ids.spinnerIP.active = False
# Create thread for networking and control
#try:
# t1 = threading.Thread(target=self.controlThread, args=("",))
# t1.start()
#except:
# print ("Error: unable to start thread")
self.root.switch_to(self.root.screens[windows.mainWindow.value])
self.currentFrame = windows.mainWindow.name
# checking the ip address
def checkIP(self, text):
st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
st.connect((text, 1))
IP = st.getsockname()[0]
return True
except:
return False
# entry point
if __name__ == '__main__':
UAVApp().run()
CodePudding user response:
The problem is your use of the switch_to() method of ScreenManager. Not well documented, but this method removes the old Screen when it switches to the new Screen. So the number of Screens in the screens list gets reduced causing the index error.
A fix is to use the current property of ScreenManager rather than switch_to(). Try replacing:
self.root.switch_to(self.root.screens[windows.mainWindow.value])
with:
self.root.current = "Main"
And your texture update is happening on the main thread because you are calling the frame_capture() method by using Clock.schedule_interval(), which puts it on the main thread.
