Tkinter canvas updating speed reduces during the course of a program - python

The following python program creates a Tkinter Canvas object and draws random matrix on it.
It also measures the time it takes to make 10 subsequent updates. As you may see from the
output below, this time grows continiously and substantially during the course of
the program. What is the reason for this behavior and how can I fix it?
from Tkinter import Tk, Canvas
import time
import numpy as np
window = Tk()
nRows = 30
nCols = 30
CELL_SIZE = 10
canvas = Canvas(window, width=CELL_SIZE*nRows,
height=CELL_SIZE*nCols)
canvas.pack()
def drawbox(m):
for y in range(nRows):
for x in range(nCols):
if m[y][x]:
color = '#00FF00'
else:
color = '#000000'
canvas.create_rectangle(CELL_SIZE*x,
CELL_SIZE*y,
CELL_SIZE*x+CELL_SIZE,
CELL_SIZE*y+CELL_SIZE,
fill=color,
outline="#000000", width=1)
count = 0
timeStart = time.time()
while(True):
board = np.random.rand(nRows, nCols) > 0.5
if count % 10 == 0:
print '%.1f seconds'%(time.time() - timeStart)
timeStart = time.time()
count = 0
count += 1
drawbox(board)
canvas.after(5)
canvas.update()
Here is the output
0.0 seconds
1.7 seconds
4.1 seconds
6.3 seconds
8.7 seconds

You create new items at each updates. The canvas display all the rectangles you have previously added and thus go slower and slower (each update create 900 rectangles, after 30 you have 27,000 objects in your scene...)
To avoid this, you may create your rectangles once, and then only update their colors.
You could have at toplevel:
rectangles = [ [ canvas.create_rectangle (CELL_SIZE*x, CELL_SIZE*y,
CELL_SIZE*x+CELL_SIZE, CELL_SIZE*y+CELL_SIZE,
fill="#000000",outline="#000000", width=1)
for x in range(nCols)] for y in range(nRows)]
and in drawbox:
canvas.itemconfig(rectangles[y][x], fill=color)

Every time drawbox is called in your program, you're creating a new set of rectangles and then drawing them on top of the old rectangles. As time goes on, you're drawing more and more rectangles (even though it doesn't look like it since the new rectangles are being drawn above the old ones). Also note that with the way your program is written, you're bleeding memory.
The way to fix this is to create the rectangles on the first go-around and then update them on the subsequent passes using canvas.itemconfig(rectangle_id,fill=color). I've posted an (ugly) modification to your drawbox below which accomplishes this.
def drawbox(m,_rectangles={}):
if(_rectangles):
myrectangles=_rectangles
else:
myrectangles={}
for y in range(nRows):
for x in range(nCols):
if m[y][x]:
color = '#00FF00'
else:
color = '#000000'
if(not _rectangles):
cid=canvas.create_rectangle(CELL_SIZE*x,
CELL_SIZE*y,
CELL_SIZE*x+CELL_SIZE,
CELL_SIZE*y+CELL_SIZE,
fill=color,
outline="#000000", width=1)
myrectangles[(y,x)]=cid
else:
canvas.itemconfig(_rectangles[(y,x)],fill=color)
if(not _rectangles):
_rectangles.update(myrectangles)

The canvas is known to be slow the more items you add (though it can typcially handle 1000's or 10's of 1000's without too much problem). You have a couple of problems. One, as other answers have pointed out, is that you keep creating more and more objects. By reusing the existing objects and updating their coordinates and colors you should see a dramatic improvement in speed.
The second problem is your infinite loop and your sleep (canvas.after(5)). There's a much better way to achieve the effect without the annoying side effect of your GUI freezing for 5 ms at a time.
All you need to do is create a function that draws or updates the objects, then puts an event on the queue to call itself again after some period of time. It will then automatically update without you having to explicitly create a loop.
For example:
def redraw():
board = np.random.rand(nRows, nCols) > 0.5
drawbox(board)
canvas.after(100, redraw)

Related

Moving the cursor in relation to a certain fix-point in Python

is there any possibility to move the cursor in Python with % starting from a certain coordinate like (1,1)?
I am using pyautogui atm to automate and I thought it would be quite convenient if this is independent from the monitor size making it universal.
Thanks for your help in advance!
It's possible indirectly. As detailed at the top of the Mouse Control Functions page of the documentation, you can get the screen size using the size() function (which returns a tuple of (X, Y)). You can then do the math to figure out how many screen pixels equal the percentage you're looking for, then call moveTo() to go there.
# script to move mouse 50% to the right and down
import pyautogui as pag
percentage = 0.5
cur_X, cur_Y = pag.position() # current X and Y coordinates
size_X, size_Y = pag.size() # screen size
goto_X = (size_X - cur_X) * percentage + cur_X # current location plus half
goto_Y = (size_Y - cur_Y) * percentage + cur_Y # the distance to the edge
pag.moveTo(goto_X, goto_Y, 1) # move to new position, taking 1 second

Pyglet. How to change picture(animate) for vertices dynamically. OpenGL

Environment:
Python: 3.6.6
pyglet version: 1.3.2
Code base:
abstract_model.py
import pyglet
def get_texture_group(file, order_group_index):
texture = pyglet.image.load(file).texture
order_group = pyglet.graphics.OrderedGroup(order_group_index)
return pyglet.graphics.TextureGroup(texture, order_group)
class AbstractModel(object):
def _create_as_vertex(self):
v_x = self.cell_data.get("x") * 32
v_y = self.cell_data.get("y") * -1 * 32
texture_group = self.map_type_iamge.get(self.cell_data.get("t"))
x_offset = self.x_offset * self.scale
x, y, z = v_x + x_offset, v_y, self.z
x_ = (texture_group.texture.width * self.scale + x_offset + v_x)
y_ = (texture_group.texture.height * self.scale + v_y)
tex_coords = ('t2f', (0, 0, 1, 0, 1, 1, 0, 1))
self.vertices = self.batch.add(
4, pyglet.gl.GL_QUADS,
texture_group,
('v3f', (x, y, z,
x_, y, z,
x_, y_, z,
x, y_, z)),
tex_coords)
def _animate(self, dt):
# lets assume that I have list of pyglet.graphics.TextureGroup
# and they should somehow be drawn one after other
print("I need change image. dt=", dt, self)
pyglet.clock.schedule_once(self._animate, 1)
ground3d.py
import os
import pyglet
import settings
from models import abstract_model
GROUND_DIR = os.path.join(settings.STATIC_DIR, "ground")
order_group_index = 0
map_type_iamge = {
1: abstract_model.get_texture_group(os.path.join(GROUND_DIR, "w1.png"), order_group_index),
2: abstract_model.get_texture_group(os.path.join(GROUND_DIR, "t1.png"), order_group_index),
1001: abstract_model.get_texture_group(os.path.join(GROUND_DIR, "t1_direction.png"), order_group_index),
}
class Ground3D(abstract_model.AbstractModel):
def __init__(self, cell_data, batch):
self.batch = batch
self.cell_data = cell_data
self.map_type_iamge = map_type_iamge
self.scale = 1
self.x_offset = 0
self.z = 0
self.entity = None
self._create_as_vertex()
pyglet.clock.schedule_once(self._animate, 1)
Explanation:
I have models(just flat rect for an example) which should be placed on 3 dimensions. And these models should be animated, like picture_1, after second picture_2, ... etc.
As I understood from my previous question using pyglet.sprite.Sprite() in 3D batch is not a good idea.
Question:
How I can change pictures(using TextureGroup or any other approaches) on self.vertices?
Or which arroach/classes I use use to implement it. I can't find any examples for such (as for my simple vision) usual case as animation for some flat models in 3 dimensions.
There are many example about rotating/moving/resizing of vertices, but how to build a correct question(is animation aspect) for getting answer in google - I don't know.
PS: If you, reader, have any usefull links on this subject(for pyglet or just for OpenGL) I would be very appreciated you share this link(s) in comment.
Texture coordinates.
You should have a single texture atlas for all frames of all different things that are animated ever.
Preferably, everything should have same animation speed and same amount of frames all the time.
Let's say there's two sprites that have 2 frames for entire animation, and they are stored in 64x64 texture atlas. (EDIT: sorry for ambiguity, 64x64 PIXELS, just because it could imply that we have 64x64 tile atlas, same everywhere else where I mention this)
Now, you need to have a global timer with global value which indicates current animation frame, not game frame. It should be independent of framerate.
Said value should be updated every once in a while at your desired speed like this:
current_frame = (current_frame + 1) % animation_length
Since we have 2 frames in this example, it will turn out like this:
# init
animation_length = 2
current_frame = 0
# updates:
current_frame = (0 + 1) % 2 # 1 % 2 -> 1
current_frame = (1 + 1) % 2 # 2 % 2 -> 0
...
Now, you need to update UV's of all your sprites only when the frame changes.
UV's start from left right and go from 0 to 1 (as far as I remember, for the sake of this example, they do, shhh).
Since we have 2 frames each, we can calculate "tiles" in the UV coordinates like this:
tile_width = 1.0 / frames # 2 frames each, width will be 0.5
tile_height = 1.0 / sprites # 2 sprites each, height will be 0.5 too, perfect
Now, on first frame, you generate your UV's like normal, you just take vertical ID or something, and use tile_height * sprite_id to get current V coordinate, and your U is calculated like tile_width * current_frame.
This assumes that you already have sprite batching so what you do is go over every sprite on update, and basically just recalculate new UV's with new frame, meaning all sprites change their frame to the next one, yay!
If you want to have systems that are independent of eachother, say, very slow animations for some, and faster for others, you'll need different sprite batches or proper calculation on from where to where you need to update UV's in vertex buffer array. Everything else is exactly the same, except now current_frame won't be global but rather contained, preferebly in some list or separate object that manages timers for animations.
You don't need to change anything in your shaders, they just need right UV's for the frames and you're set.
By the way, this is very basic, you could apply some logic yourself so you could instead have 16x16 grid of 32x32 pixels in your texture, each line of sprites having 4 different animations, these could be either sprite's states (attack, run, etc), but how you do it is entirely on you, most importantly, get it to work. Goodluck.
But if you do it the way I said, then state will be another integer, and UV for state, assuming all states have exactly the same width, it would be like this:
state_width = 1 / states
tile_width = 1 / (states * frames_per_state)
U = state_width * current_state + tile_width * current_frame
Now, one issue arises, player could start his animation at the last attack frame.
It's normal, entities with actions should all have individual timers, what I described above, is for tons of sprites that are just background, like grass. Now when you divided it up, you could have a proper way to reset current frame to 0 when new state is assigned.
If your entities are objects, you could write proper methods that recalculate UV's every time you rebuild the sprite batch using those sprites, and then timers itselves could be contained in objects.
We need to draw something? Check animation state, has it changed, no? Send UV's that were calculated before, otherwise, wait a bit, we need to recalculate, only then add those to VBO, and well, render your thing, in the end, it will appear as if you have animations, even though really, it's just a simple, but great UV manipulation.
Goodluck.

Plotting real time data with PyQt

So I'm trying to plot real time data with PyQt. I have it working in a sense, but matplotlib seems to be slowing it down a lot. If I reduce the number of plots I can get the sample rate I want. I have two timer events, one that gathers data and another that plots, with a ratio of 10 to 1.
Searching for a fix I found about Blitting with Matplotlib from SO, and was led to tutorials like this. The problem I'm seeing is that this is only dealing with the plotting part. Every attempt I have made at sampling and plotting a portion of the data I've gathered ends in a crash.
So an outline of what I'd like to do would be this
class graph(ParentMplCanvas):
def __init__(self, *args, **kwargs):
self.axes = fig.add_subplot(111)
self.x = range(1000)
self.data = np.zeros(1000)
#set timer for data to be sampled once every 10ms.
self.updateData()
self.line, = self.ax.plot(self.x,self.data, animated=True)
# Update the plot every second
self.gTimer = fig.canvas.new_timer(interval=1000)
self.gTimer.add_callback(self.update_figure)
self.gtimer.start()
def updateData(self):
self.i += 1
#append with 0's if self.i > 1000
self.data[self.i] = self.funcToGrabCurrentValFromDevice()
self.updateTimer()
def updateTimer(self):
self.dTimer = Timer(0.01,updateData)
self.dTimer.start()
class ApplicationWindow(gui.QMainWindow):
some stuff to call docked windows and the above graph in a docked window see [how I did it here][2]
Maybe I am just not understanding the blitting, but everything I'm seeing there they already have all the data. Any time I've tried to just access a portion of the data it seems to crash the program. I'm trying to just plot a 100 sample region at a time and have it continuously update.
Where I am lost:
How do I properly write update_figure so that I can plot the last 100 (or n) data points that were sampled?

How to undraw plot with Zelle graphics?

This is a code problem for Python 3.5.2 using John Zelle's graphics.py:
I have spent a good amount of time looking for the answer here, but just can not figure it out. The function undraw() exists just like getMouse(). But it seems like it do not work for the plot() command, only the draw() command. What am I doing wrong? And how can I keep the window open, but erase the previous the plot before the next one is drawn?
pdf documentation for the functions of graphics:
http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf
win = GraphWin("Plot",500,500) # Creates a window
for m in range(0,j): # Loop for each function
# Randomizes a color for each function
color = random.choice( ['red','black','green','yellow','pink','blue'] )
for h in range(0,t): # Loop for each pair of values "x,y"
# Find points and plot each point in win
win.plot(axis[h],points[m][h],color)
win.getMouse() # Pause before clicking
win.undraw() # AttributeError: 'GraphWin' object has no attribute 'undraw'
The first issue is that undraw() is a method of GraphicsObject, not GraphWin, so win.undraw() is simply incorrect.
The second issue is that plot() is a low level pixel setting method that does not keep track of what it did at the Zelle Graphics level, unlike objects that are drawn.
However, the underpinning is Tkinter which does keep track of objects that it draws, and GraphWin is a subclass of Canvas, so you can do:
win = GraphWin("Plot", 500, 500) # Creates a window
for m in range(j): # Loop for each function
color = random.choice(['red', 'black', 'green', 'yellow', 'pink', 'blue']) # Randomizes a color for each function
for h in range(t): # Loop for each pair of values "x, y"
win.plot(axis[h], points[m][h], color) # Find points and plot each point in win
win.getMouse() # Pause before clicking
win.delete("all") # Clear out old plot

How do I make a variable for a nested loop?

I have a bit of code that draws an array of lines (32X32 grid). The actual code that draws it starts from #Nested loop to draw anti-aliased lines in a 32X32 grid. Because I have a patch within it that has lines of different orientation, the codes is a few lines long.
At the moment, I have a .draw() at the end of the nested loop that draws my array.
It doesn't seem like a good way to do this.
Is there a way to create a variable for this entire nested loop so i can call it as and when I want? For instance myStim.draw()
# Import what is needed
import numpy as np
from psychopy import visual, event, core, logging
from math import sin, cos
import random, math
# Create space variables and a window
lineSpaceX = 0.55
lineSpaceY = 0.55
patch_orientation = 45 # zero is vertical, going anti-clockwise
surround_orientation = 90
#Jitter values
g_posJitter = 0.05 #gaussian positional jitter
r_posJitter = 0.05 #random positional jitter
g_oriJitter = 5 #gaussian orientation jitter
r_oriJitter = 5 #random orientation jitter
#Region where the rectangular patch would appear
x_rand=random.randint(6,13) #random.randint(Return random integers from low (inclusive) to high (inclusive).
y_rand=random.randint(6,16)
#rectangular patch dimensions
width=15
height=12
message = visual.TextStim(win,pos=(0.0,-12.0),text='...Press SPACE to continue...')
# Initialize clock to record response time
rt_clock = core.Clock()
#Nested loop to draw anti-aliased lines in a 32X32 grid
for x in xrange(1,33): #32x32 grid.
for y in xrange(1,33):
##Define x & y value (Gaussian distribution-positional jitter)
x_pos = (x-32/2-1/2 )*lineSpaceX + random.gauss(0,g_posJitter) #random.gauss(mean,s.d); -1/2 is to center even-numbered stimuli; 32x32 grid
y_pos = (y-32/2-1/2 )*lineSpaceY + random.gauss(0,g_posJitter)
if (x >= x_rand and x < x_rand+width) and (y >= y_rand and y < y_rand+height): # note only "=" on one side
Line_Orientation = random.gauss(patch_orientation,g_oriJitter) #random.gauss(mean,s.d) - Gaussian func.
else:
Line_Orientation = random.gauss(surround_orientation,g_oriJitter) #random.gauss(mean,s.d) - Gaussian func.
#stimOri = random.uniform(xOri - r_oriJitter, xOri + r_oriJitter) #random.uniform(A,B) - Uniform func.
visual.Line(win, units = "deg", start=(0,0), end=(0.0,0.35), pos=(x_pos,y_pos), ori=Line_Orientation, autoLog=False).draw() #Gaussian func.
frameN = 0
for frameN in range(80): #for exactly 80 frames; 1 frame = 16.67ms on the 1920 x 1080 monitor
if frameN == 0:
rt_clock.reset() # set reaction time clock to 0
message.draw()
win.flip()# display stimulus
frameN = frameN + 1
keys = event.waitKeys(keyList=['space', 'escape','q']) #create key list response
# handle key responses
if len(keys)>0:
rt = rt_clock.getTime()
if keys == ['space']:
event.clearEvents()
break
else:
print 'Stopped Early'
win.close()
core.quit()
print x_rand, y_rand
print keys, rt #display response and reaction time on screen output window
It's not a variable you want, but a function.
The way you are currently doing things (via visual.Line(...).draw()) is very inefficient. You're creating a new line on very iteration, just to draw it once, and not storing a reference to it. A much more time-efficient scheme is to create just a single line object instance, referenced with a variable name, and then on every iteration, simply update its attributes (orientation etc), before drawing it.
An alternative would be to create multiple line object instances once, but store each in a list. Then drawing them again as required is a simple matter of:
for line_instance in line_list:
line_instance.draw()

Categories