Kivy drawn items 'disappearing' (or possible render order problem) - python

(this is a repost from https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/kivy-users/0B5GYWkTIwE/-OQYp_ykCQAJ)
Very occasionally our kivy-based UI glitches in a very odd way: the system remains responsive and the displays of buttons and text work but it seems that all "drawn" items disappear, i.e. items from kivy.graphics.
Here is the main control screen in it's proper state:
and when it goes wrong it changes to:
All the items rendered with the kivy.graphics objects have gone!
This is using 1.9.1-1build3 (in Ubuntu) and 1.9.1-dev (from kivypie on an RPi)
Does anyone have a clue as to what might be causing this or how to track it down? It seems to happen on touch events, but it is so rare it's been impossible so far to find any commonality and there is no error report output to stdout/stderr or the .kivy/log/
Here's a section of code showing how elements are being drawn:
class LineWidget(GUIWidget):
"""A line widget. A simple sequence of points"""
def __init__(self, points, width, **kwargs):
GUIWidget.__init__(self, **kwargs)
self.points = points
with self.canvas:
self.line_color = theme.system.line.Color()
self.line = Line(width=width)
self.bind(size=self.update_rect, pos=self.update_rect)
def update_rect(self, instance, value):
"""Update the point positions."""
self.pos = instance.pos
if self.points:
self.line.points = self.get_points(self.points)
def set_color(self, color):
"""Change the color"""
self.line_color.rgb = color
and the blue/grey background is rendered:
with self.canvas.before:
if background_color is None:
background_color = theme.primary.black
self._background_color = Color(*background_color)
self._background_rectangle = Rectangle(size=self.size, pos=self.pos)
In my post to the kivy group, it has been suggested that maybe some race condition has happened resulting in the native kivy objects being rendered on top of mine and to try using canvas.after, but unless I can find a way to provoke the problem I'll never know if this has worked.
Is it possible that the render order (Z-index) has changed?
Is it possible there is some race condition?
The problem happens so rarely it has been impossible so far to
provoke the problem (100s of random clicks on widgets is the only
way) - is there some programmatic method to try and force the issue?
what debug could I put in to try and find out what might be
happening?
Thanks,

Related

Circular keyboard python (tkinter?)

So I'm trying to make some different virtual keyboard designs with the goal of using them in combination with head tracking. I wanted to explore if maybe a circular keyboard would be easier to use with head tracking compared to a standard layout.
The keyboard would look like the one in the picture, but I'm not sure how to make this kind of a layout using tkinter (this is what I used for the other keyboard but I'm not limited to this lib in any way). Any tips on how to make non square/rectangular UI?
Thanks in advance.
You can use the Canvas widget to draw the menu as a collection of arc items. One of the features of the canvas is that you can give items on the canvas a tag, and you can bind that tag to an event.
This isn't a complete solution, but it illustrates how it's possible to draw a circular menu that responds to clicks, as well as enter and leave events. In the following code we draw a circle as a sequence of eight arcs. Each arc is given a generic tag of "item" and a specific tag of "item-". These tags can then be used to bind events, and to get information about the item that was clicked on.
class PieMenu(tk.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tag_bind("item", "<ButtonRelease-1>", self._click)
self.tag_bind("item", "<Enter>", self._enter)
self.tag_bind("item", "<Leave>", self._leave)
for index, start in enumerate(range(0, 360, 45)):
self.create_arc(
(2, 2, 200, 200),
outline="black", fill="white",
start=start, extent=45, tags=(f"item-{index}", "item")
)
def _click(self, event):
item = event.widget.find_withtag("current")[0]
tags = event.widget.itemcget("current", "tags")
print(f"item: {item} tags: {tags}")
def _enter(self,event):
event.widget.itemconfigure("current", fill="lightgray")
def _leave(self,event):
event.widget.itemconfigure("current", fill="white")
You would need to add code to include the text of each item, and code to know what function to call when a particular part of the menu is clicked. The point of the example is to show the technique for drawing one simple menu that can respond to mouse events.

trying to draw over sprite or change picture pyglet

I am trying to learn pyglet and practice some python coding with a questionnaire thing, but I am unable to find a way to make the background picture be removed or drawn on top of or something for 10 seconds. I am new and am lacking in a lot of the knowledge I would need, thank you for helping!
import pyglet
from pyglet.window import Window
from pyglet.window import key
from pyglet import image
import time
card1 = False
cat_image = pyglet.image.load("cat.png")
dog_image = pyglet.image.load("dog.png")
image = pyglet.image.load("backg.png")
background_sprite = pyglet.sprite.Sprite(image)
cat = pyglet.sprite.Sprite(cat_image)
dog = pyglet.sprite.Sprite(dog_image)
window = pyglet.window.Window(638, 404, "Life")
mouse_pos_x = 0
mouse_pos_y = 0
catmeme = pyglet.image.load("catmeme.png")
sprite_catmeme = pyglet.sprite.Sprite(catmeme)
#window.event
def on_draw():
window.clear()
background_sprite.draw()
card_draw1(63, 192, 385, 192)
def card1():
while time.time() < (time.time() + 10):
window.clear()
sprite_catmeme.draw()
#window.event
def card_draw1(x1, y1, x2, y2):
cat.set_position(x1, y1)
dog.set_position(x2, y2)
cat.draw()
dog.draw()
def card_draw2():
pass
#window.event
def on_mouse_press(x, y, button, modifiers):
if x > cat.x and x < (cat.x + cat.width):
if y > cat.y and y < (cat.y + cat.height):
card1()
game = True
while game:
on_draw()
pyglet.app.run()
There's a few flaws in the order and in which you do things.
I will try my best to describe them and give you a piece of code that might work better for what your need is.
I also think your description of the problem is a bit of an XY Problem which is quite common when asking for help on complex matters where you think you're close to a solution, so you're asking for help on the solution you've come up with and not the problem.
I'm assuming you want to show a "Splash screen" for 10 seconds, which happens to be your background? And then present the cat.png and dog.png ontop of it, correct?
If that's the case, here's where you probably need to change things in order for it to work:
The draw() function
It doesn't really update the screen much, it simply adds things to the graphical memory. What updates the screen is you or something telling the graphics library that you're done adding things to the screen and it's time to update everything you've .draw()'n. So the last thing you need in the loop would be window.flip() in order for the things you've drawn to actually show.
Your things might show if you try to wiggle the window around, it should trigger a re-draw of the scene because of how the internal mechanics of pyglet work..
If you don't call .flip() - odds are probable that the redraw() call will never occur - which again, is a internal mechanism of Pyglet/GL that tells the graphics card that something has been updated, we're done updating and it's time to redraw the scene.
a scene
This is the word most commonly used for what the user is seeing.
I'll probably throw this around a lot in my text, so it's good to know that this is what the user is seeing, not what you've .draw()'n or what's been deleted, it's the last current rendering of the graphics card to the monitor.
But because of how graphical buffers work we've might have removed or added content to the memory without actually drawing it yet. Keep this in mind.
The pyglet.app.run() call
This is a never ending loop in itself, so having that in a while game: loop doesn't really make sense because .run() will "hang" your entire application, any code you want to execute needs to be in def on_draw or an event that is generated from within the graphical code itself.
To better understand this, have a look at my code, i've pasted it around a couple of times here on SO over the years and it's a basic model of two custom classes that inherits the behavior of Pyglet but lets you design your own classes to behave slightly differently.
And most of the functionality is under on_??? functions, which is almost always a function used to catch Events. Pyglet has a lot of these built in, and we're going to override them with our own (but the names must be the same)
import pyglet
from pyglet.gl import *
key = pyglet.window.key
class CustomSprite(pyglet.sprite.Sprite):
def __init__(self, texture_file, x=0, y=0):
## Must load the texture as a image resource before initializing class:Sprite()
self.texture = pyglet.image.load(texture_file)
super(CustomSprite, self).__init__(self.texture)
self.x = x
self.y = y
def _draw(self):
self.draw()
class MainScreen(pyglet.window.Window):
def __init__ (self):
super(MainScreen, self).__init__(800, 600, fullscreen = False)
self.x, self.y = 0, 0
self.bg = CustomSprite('bg.jpg')
self.sprites = {}
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
elif symbol == key.C:
print('Rendering cat')
self.sprites['cat'] = CustomSprite('cat.png', x=10, y=10)
elif symbol == key.D:
self.sprites['dog'] = CustomSprite('dog.png', x=100, y=100)
def render(self):
self.clear()
self.bg.draw()
for sprite_name, sprite_obj in self.sprites.items():
sprite_obj._draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = MainScreen()
x.run()
Now, this code is kept simple on purpose, the full code I usually paste on SO can be found at Torxed/PygletGui, the gui.py is where most of this comes from and it's the main loop.
What I do here is simply replace the Decorators by using "actual" functions inside a class. The class itself inherits the functions from a traditional pyglet.window.Window, and as soon as you name the functions the same as the inherited onces, you replace the core functionality of Window() with whatever you decide.. In this case, i mimic the same functions but add a few of my own.
on_key_press
One such example is on_key_press(), which normally just contain a pass call and does nothing, here, we check if key.C is pressed, and if so - we add a item to self.sprites.. self.sprites just so happen to be in our render() loop, anything in there will be rendered ontop of a background.
Here's the pictures I used:
(named bg.jpg, cat.png, dog.png - note the different file endings)
class:CustomSprite
CustomSprite is a very simple class designed to make your life easier at this point, nothing else. It's very limited in functionality but the little it do is awesome.
It's soul purpose is to take a file name, load it as an image and you can treat the object like a traditional pyglet.sprite.Sprite, meaning you can move it around and manipulate it in many ways.
It saves a few lines of code having to load all the images you need and as you can see in gui_classes_generic.py you can add a heap of functions that's "invisible" and normally not readily availbale to a normal sprite class.
I use this a bunch! But the code gets complicated real fast so I kept this post simple on purpose.
the flip function
Even in my class, I still need to use flip() in order to update the contents of the screen. This is because .clear() clears the window as you would expect, that also triggers a redraw of the scene.
bg.draw() might in some cases trigger a redraw if the data is big enough or if something else happens, for instance you move the window.
but calling .flip() will tell the GL backend to force a redraw.
Further optimizations
There's a thing called batched rendering, basically the graphic card is designed to take enormous ammounts of data and render it in one go, so calling .draw() on several items will only clog the CPU before the GPU even gets a chance to shine. Read more about Batched rendering and graphics! It will save you a lot of frame rates.
Another thing is to keep as little functionality as possible in the render() loop and use the event triggers as your main source of coding style.
Pyglet does a good job of being fast, especially if you only do things on event driven tasks.
Try to avoid timers, but if you really do need to use time for things, such as removing cat.png after a certain ammount of time, use the clock/time event to call a function that removes the cat. Do not try to use your own t = time() style of code unless you know where you're putting it and why. There's a good timer, I rarely use it.. But you should if you're starting off.
This has been one hell of a wall of text, I hope it educated you some what in the life of graphics and stuff. Keep going, it's a hurdle to get into this kind of stuff but it's quite rewarding once you've mastered it (I still haven't) :)

Issue with "dynamic" padding in matplotlib

I've been trying to set a fixed size padding (in pixels) on my matplotlib figure. I've never been able to find a solution that suits my needs on the internet so I had to make a little workaround for this, which is done with the following code :
def resizeEvent(self,e):
windowWidth=self.geometry().width()
windowHeight=self.geometry().height()
#print(windowWidth,windowHeight)
figure.subplots_adjust(left=100.0/windowWidth,right=1-100.0/windowWidth, top=1-100.0/windowHeight,bottom=100.0/windowHeight)
It works fine when manually resizing the window (we have a padding of 100px on every side).
Unfortunatly, when clicking Maximize, the padding (in 0 to 1) seems to be equal to it's previous value, even if the print returns the correct window size (1920px).
A second click to Restore Down will then set the padding to the value we should have when we maximized it.
I don't really get what's happening here, I must be missing something...
Tell me if you need more information such as more code.
Thank you for your kind help :)
I've had similar issues in the past (the figure not resizing at start up) and moved the recalculation of the figure subplots to the draw method. Something like this (I use tight_layout but you get the idea):
class PlotWidget(FigureCanvas):
def __init__(self):
self.__resizing_needed = True
def draw(self):
if self.__resizing_needed:
self.__resizing_needed = False
try:
self.figure.tight_layout()
except ValueError:
# Set the minimumSize of this widget to prevent this
print('Plot too small.')
super(PlotWidget, self).draw()
def resizeEvent(self, event):
self.__resizing_needed = True
super(PlotWidget, self).resizeEvent(event)

TextCtrl scrollbar unusable until window is resized

Solved:
Thanks to Aya's answer below I now know that the issue was caused by self.panel = wx.Panel(self, -1) on line 18. I created a panel and didn't attach anything to it. The original issue description is still below for reference.
My Google-fu has failed me. I'm building the text editor that you can find here, written in Python with wxPython:
https://github.com/joshsaintjacque/py-ed/blob/master/pyed.py
The issue that I'm running into is this: when I open a text file (the only functionality built in at this point) that's larger than the viewable area in the TextCtrl the scroll bar remains disabled until the window is re-sized, then it works fine.
I know that the act of re-sizing the window is running some command that I'm neglecting to include in my OpenFile function (or perhaps in init), but I can't figure out what.
Any thoughts anyone has that could lead me in the right direction would be greatly appreciated.
Thanks!
+1 for including a link to the full source code - makes it so much easier to test.
I couldn't reproduce the fault you describe on wxPython 2.8.12 on Win32, but upon running your code, I found a seemingly extraneous wx.Panel object being created on pyed.py line 18...
self.panel = wx.Panel(self, -1)
...which seems to be interfering with the correct operation of the program. After commenting out that line, it seems to work fine.
A couple of other things I noticed: line 56...
self.SetTitle("PyEd - Editing ... " + filename)
...should probably be put in the preceding if-block, otherwise you'll get an error if the user clicks "Cancel" on the wx.FileDialog, and on line 16...
wx.Frame.__init__(self, parent, id, 'PyEd', (-1, -1), wx.Size(640, 480))
...if you use keyword args rather than positional args...
wx.Frame.__init__(self, parent=parent, id=id, title='PyEd', size=wx.Size(640, 480))
...you needn't bother re-specifying the default value for the window position, which is also slightly safer, in case the wxPython developers decide to change the defaults in a future version.
You can also factor out constant values, and the optional creation of the wx.Size object to reduce that line to...
wx.Frame.__init__(self, parent=None, title='PyEd', size=(640, 480))
Finally, with regards to IDs: in most cases you'll probably find they're of little use. Where they come in handy is where you want many similar controls, and it makes more sense to have them handled by a single event handler function.
Consider this example...
def create_buttons(parent):
parent.button1 = wx.Button(label='Button 1')
parent.button2 = wx.Button(label='Button 2')
parent.button3 = wx.Button(label='Button 3')
parent.button1.Bind(wx.EVT_BUTTON, on_button_1)
parent.button2.Bind(wx.EVT_BUTTON, on_button_2)
parent.button3.Bind(wx.EVT_BUTTON, on_button_3)
def on_button_1(event):
print 'You clicked button 1'
def on_button_2(event):
print 'You clicked button 2'
def on_button_3(event):
print 'You clicked button 3'
...which is fine, but if you need, say, 100 buttons, you may prefer to implement it like this...
def create_buttons(parent):
parent.buttons = [wx.Button(id=i, label='Button %d' % i) for i in range(100)]
parent.Bind(wx.EVT_BUTTON, on_button)
def on_button(event):
button_id = event.GetId()
print 'You clicked button %d' % button_id
Oh, and be careful using id as a variable name, because it's also a Python built-in function name.
It looks as if you're not setting the min or max size hints for the window, nor are you calling Self.Fit() to fit the box sizer to the window size (or is it the other way round? I'm rusty on my wxPython...)
Right where you call self.SetSizer(sizer), you should be able to fix this by adding:
self.Fit()
self.SetSizeHintSz(minSize=wx.Size(640, 480))
You may be able to get around the separate call to self.Fit() by using self.SetSizerAndFit()
(edited for spelling.)

What is the most efficient way to display multiple pixmaps into a scroll area?

I am trying to make an application that displays a PDF file, using PyQt4 and python-poppler-qt4.
So far I have managed to display the entire document by loading pixmaps generated with Poppler, set on a QLabel and appended to a QFrame. The QFrame is displayed in a QScrollArea.
It looks pretty good, until implementing zooming, which is done by regenerating the pixmaps all over again, with an incremented resolution. This process requires the entire document to be rendered into pixmaps, which obviously takes time and results into an unwanted lag.
Logic wants that I should display images of the pages I am seeing only (it sounds like quantum physics). I have two options in mind:
create blank pages with QLabels and load the image onto them when they become visible in the scroll area;
create only one page and add or remove precedent or subsequent pages right before it should be displayed.
I am not sure I am on the right track or whether there is an alternative.
The first option seems more feasible, because the visibility of a blank page determines when the pixmap has to be uploaded (although I have no idea how to delete that pixmap when the page is hidden). Yet I am not sure that zooming will be faster this way, since a document of, say, 600 pages, will have to be regenerated, albeit with blank pages.
The second option should definitely improve zooming since 1 to 4 pages at a time would have to be regenerated when zooming. In that second case however, I am not sure how to trigger the construction of pages.
What would you suggest?
wouldn't it be simple to forget the QLabels and directly draw the image:
from PyQt4.QtGui import *
import sys
app = QApplication(sys.argv)
class Test(QWidget):
def __init__(self):
super(Test, self).__init__()
self.painter = QPainter()
# placeholder for the real stuff to draw
self.image = QImage("/tmp/test.jpg")
def paintEvent(self, evt):
rect = evt.rect()
evt.accept()
print rect
self.painter.begin(self)
zoomedImage = self.image # ... calculate this for your images
sourceRect = rect # ... caluclate this ...
# draw it directly
self.painter.drawImage(rect, self.image, sourceRect)
self.painter.end()
t = Test()
t.setGeometry(0,0,600,800)
s = QScrollArea()
s.setWidget(t)
s.setGeometry(0,0,300,400)
s.show()
app.exec_()
I've worked out an answer, using option 1 in the question:
def moveEvent(self, event):
self.checkVisibility()
event.ignore()
def resizeEvent(self, event):
self.checkVisibility()
event.ignore()
def checkVisibility(self):
print "Checking visibility"
for page in self.getPages():
if not page.visibleRegion().isEmpty():
if page.was_visible:
pass
else:
print page.page_number, "became visible"
page.was_visible = True
self.applyImageToPage(page)
else:
if page.was_visible:
print page.page_number, "became invisible"
page.was_visible = False
else:
pass
def applyImageToPage(self, page):
print "applying image to page", page.page_number
source = self.getSourcePage(self.getPageNumber(page))
scale = self.display.scale
# this is where the error occurs
image = source.renderToImage(72 * scale, 72 * scale)
pixmap = QtGui.QPixmap.fromImage(image)
page.setPixmap(pixmap)

Categories