Psychopy: draw all images from a list at once - python

I have a list with 12 images. All of them have different locations (that is, they do not overlap at all). I want to draw them all at once. In other words, I want to see all 12 pictures at the same time on the screen. So far, I only got this:
lines = [line1,line2,line3,line4,line5,line6
line7,line8,line9,line10,line11,line12]
for i in range(12):
lines[i].draw()
But, of course, this code only draws one pic at a time, after I press any key. Is there then a way to draw the 12 pics at the same time?
Thanks in advance!

Your original code only drew one image at a time because of how the loop was set up -- it was (more or less) saying "For each element in circles, draw several things and flip the front and back buffers". Each time the buffers flip, unless you tell it otherwise via win.flip(clearBuffer = False), the previous things on the screen are removed. To draw the images at the same time, you could just loop through the image list and call the draw() method on each element, e.g.:
for i in imglist:
i.draw()
win.flip()
If you are willing to cede control over properties of individual images, one way would be to use BufferImageStim. This takes longer to initialize, but may be faster than drawing individual images (I haven't timed it properly). Both methods are demonstrated below.
from psychopy import visual, event, core
import urllib
import random
win = visual.Window([400, 400], fullscr = False)
# picture of a cat, save to file
urllib.urlretrieve('https://s-media-cache-ak0.pinimg.com/736x/' +
'07/c3/45/07c345d0eca11d0bc97c894751ba1b46.jpg', 'tmp.jpg')
# create five images with (probably) unique positions
imglist = [visual.ImageStim(win = win, image = 'tmp.jpg',
size = (.2, .2),
pos = ((random.random() - 0.5) * 2,
(random.random() - 0.5) * 2))
for i in xrange(5)]
# draw individual images
for i in imglist:
i.draw()
win.flip()
# wait for key press, then clear window
event.waitKeys()
win.flip()
core.wait(0.5)
# create aggregate stimulus (should look identical)
buffs = visual.BufferImageStim(win, stim = imglist)
buffs.draw()
win.flip()
event.waitKeys()
core.quit()

Related

Blitting subsurface from tilesheet is doing weird things

I am coding a prototype platformer in pygame. I'm using a .png as a tilesheet, I load it and then get a list tileset.tiles of all the different tile textures in it. I then use three layers of .csv tilemaps to associate every tile in the grid with its own corresponding texture. I bake all of the tile layers onto a map surface once, and then blit this surface at every frame.
The problem is that the outcome is not as expected, apparently not all of the tiles are properly blit onto the surface. The problem seems to arise when the same subsurface has to be blit a second time by the load method in the Room class. It's not clear to me what exactly causes this. I have tried playing around with the .csv files, and it seems that different arrangements of tiles, even across layers, have an influence on what is actually rendered on screen. I've added screenshots to illustrate this better. For reference, the id number 8 corresponds to a blue square texture, which should be the sky. The other numbers correspond to several different textures.
bottom layer csv
middle layer csv
top layer csv
outcome
By changing the first tile of the middle layer no difference is shown (tile 0 corresponds to the flower texture):
alternate middle layer
outcome
Or, if I try and change the first few tiles of the bottom layer:
alternate bottom layer
outcome
Generally, if I change some tile number in a csv file, weird things happen, and other seemingly random tiles get blit. Also, I am able to manually place tiles at any position on the screen without any problems (bypassing the load method and directly blitting subsurfaces from the tileset class) so I think that the tileset class is working properly.
Here's the full code:
import pygame as pg
class Tileset:
def __init__(self,img:pg.Surface):
self.tiles = []
self.img = img
self.loadtiles()
def loadtiles(self):
for i in range(16):
for n in range(16):
currentimg = self.img.subsurface(32*n,32*i,32,32)
self.tiles.append(currentimg.copy())
class Tile(pg.sprite.Sprite):
def __init__(self,image:pg.Surface,position:tuple):
self.img = image
self.pos = position
def draw (self,surface:pg.Surface):
surface.blit(self.img,self.pos)
class Room:
def __init__(self,id,size:tuple):
self.id = id
self.layers = [[],[],[]]
self.size = (size[0]*32,size[1]*32)
# Call when a new room must be loaded: reads room csv, stores tile info, overwrites drawn map with new map
def load(self,map):
map = pg.Surface(self.size)
for layer in self.layers:
with open ('levels/final/room'+str(self.id)+'_'+str(self.layers.index(layer))+'.csv') as file:
data = file.readlines()
for rrrow in data: # unprocessed row
rrow = rrrow.strip('\n').split(',') # semi processed row
row = [] # processed row
for rtile in rrow: # unprocessed tile in row
if rtile != -1:
tileimg = tileset.tiles[int(rtile)]
tile = Tile(tileimg,(rrow.index(rtile)*32, data.index(rrrow)*32)) # process tile
tile.draw(map) # draw tile on current tilemap
row.append(tile) # store tile in row
layer.append(row) # store row in layer (to use later for collisions)
return map
### pygame loop setup (incomplete, shouldn't matter ###
res = (32*30,32*20)
scr = pg.display.set_mode(res)
tileset = Tileset(pg.image.load('graphics\stock.png'))
room0 = Room(0,(30,20))
map = pg.Surface((0,0))
map = room0.load(map)
running = True
while running:
scr.fill((0,0,0))
scr.blit(map,(0,0))
pg.display.flip()
I have just solved it. The problem was in the use of the '.index' method to determine what the position of that tile would be. .index would just return the first occurrence of that tile in the list, and not the actual one, so any time a tile would have to appear for a second time in the same row, or every time two rows looked the same, that tile/row would just be positioned on top of its previous copy. Solved by using i and n as counters for the tile and row positions instead of the .index method.

Weird things happening when making image get bigger on mouse hover in Pygame

I am making a scene where there is a thumbs-up image that is supposed to get bigger on mouse hover, and shrink back to normal size when the mouse is no longer hovering.
This is how I make the thumbs-up image:
thumbs_up_image = pygame.image.load("./plz_like.png")
thumbs_up_rect = thumbs_up_image.get_rect(topleft=(screen.get_width() // 2 - thumbs_up_image.get_width() + 75,
screen.get_height() // 2 + thumbs_up_image.get_height() - 225))
And this is how I make it get bigger:
if thumbs_up_rect.collidepoint(pygame.mouse.get_pos()):
thumbs_up_image = pygame.transform.scale(thumbs_up_image,
[n + 50 for n in thumbs_up_image.get_size()])
thumbs_up_rect = thumbs_up_image.get_rect()
This is how the image is blited:
screen.blit(thumbs_up_image, thumbs_up_rect)
The problem is that when I hover on the thumbs-up image, it first goes to the top-left corner of the screen. Then, when I hover on it again, it gets super big and pixelated.
What am I doing wrong?
I managed to figure it out by myself.
This is how I do it:
First, I prepared a bigger version of the image and it's rect: (as shown below)
big_thumbs_image = pygame.transform.scale(thumbs_up_image, [i + 50 for i in thumbs_up_image.get_size()])
big_thumbs_image_rect = thumbs_up_image.get_rect(
topleft=(screen.get_width() // 2 - thumbs_up_image.get_width() + 55,
screen.get_height() // 2 + thumbs_up_image.get_height() - 250))
Then, when the small image's rect collides with the mouse, blit the bigger image:
if thumbs_up_rect.collidepoint(pygame.mouse.get_pos()):
screen.blit(big_thumbs_image, big_thumbs_image_rect)
You are not showing the code that actually renders the image to the screen.; But basically: you are not saving the original size - at each hover event it will grow and grow (and it will grow once per frame, if that code is run in the mainloop).
You need a variable to hold the original image, one to tell your code the image has already been resized, and an else clause on this if to restore the original image: pygame won't do that for you.
Also, when you use the get_rect for the image, its top-left position will always be "0, 0" - you have to translate this top-left corner to a suitable coordinate- getting the rectangle center of the original sprite (wherever the data of its location on the screen is kept), and setting the same center on the new rect should work.
And finally, prefer "rotozoom" than "scale" - Pygame documentation is clear that the second method uses better algorithms for scaling.
Try using this pygame function:
pygame.transform.rotozoom(Surface, angle, scale)
I also had some issues with pixilation in a game but it seemed to work with this.

I need to make a "mosaic" - but very simple

The best code for mosaic I've found you can see at this page:
https://github.com/codebox/mosaic
However, the code doesn't work well on my Windows computer, and also I think the code is too advanced for what it should do. Here are my requirements I've posted on reddit:
1) The main photo already has reduced number of colors (8)
2) I have already every image associated with colour needed to be replaced (e.g. number 1 is supposed to replace black pixels, number 2 replaces green pixels...)
3) I need to enlarge the photo by the small photo's size (9 x 9 small photos will produce 81 times bigger image), which should push the pixels "2n" points away from each other, but instead of producing a n x n same-coloured area around every single one of them (this is how I believe enlarging works in general, correct me if I'm wrong), it will just colour the white spaces with unrecognized colour, which is not associated with any small photo (let's call that colour C)
4) Now all it needs is to run through all non-C coloured pixels and put an image centered on that pixel, which would create the mosaic.
Since I'm pretty new to Python (esp. graphics) and need it just for one use, could someone help me with creating that code? I think that code I got inspired with is too complicated. Two things I don't need:
1) "approximation" - if the enlargement is lesser than needed for 100% quality (e.g. the pictures are 9x9, but every side of the original photo can be only 3 times larger, then the program needs to merge some pixels of different colours together, leading to quality loss)
2) association colour - picture: my palette of pictures is small and of colours as well, I can do it manually
For the ones who didn't get what I mean, here is my idea: https://ibb.co/9GNhqBx
I had a quick go using pyvips:
#!/usr/bin/python3
import sys
import os
import pyvips
if len(sys.argv) != 4:
print("usage: tile-directory input-image output-image")
sys.exit(1)
# the size of each tile ... 16x16 for us
tile_size = 16
# load all the tile images, forcing them to the tile size
print(f"loading tiles from {sys.argv[1]} ...")
for root, dirs, files in os.walk(sys.argv[1]):
tiles = [pyvips.Image.thumbnail(os.path.join(root, name), tile_size,
height=tile_size, size="force")
for name in files]
# drop any alpha
tiles = [image.flatten() if image.hasalpha() else image
for image in tiles]
# copy the tiles to memory, since we'll be using them many times
tiles = [image.copy_memory() for image in tiles]
# calculate the average rgb for an image, eg. image -> [12, 13, 128]
def avg_rgb(image):
m = image.stats()
return [m(4,i)[0] for i in range(1,4)]
# find the avg rgb for each tile
tile_colours = [avg_rgb(image) for image in tiles]
# load the main image ... we can do this in streaming mode, since we only
# make a single pass over the image
main = pyvips.Image.new_from_file(sys.argv[2], access="sequential")
# find the abs of an image, treating each pixel as a vector
def pyth(image):
return sum([band ** 2 for band in image.bandsplit()]) ** 0.5
# calculate a distance map from the main image to each tile colour
distance = [pyth(main - colour) for colour in tile_colours]
# make a distance index -- hide the tile index in the bottom 16 bits of the
# distance measure
index = [(distance[i] << 16) + i for i in range(len(distance))]
# find the minimum distance for each pixel and mask out the bottom 16 bits to
# get the tile index for each pixel
index = index[0].bandrank(index[1:], index=0) & 0xffff
# replicate each tile image to make a set of layers, and zoom the index to
# make an index matching the output size
layers = [tile.replicate(main.width, main.height) for tile in tiles]
index = index.zoom(tile_size, tile_size)
# now for each layer, select pixels matching the index
final = pyvips.Image.black(main.width * tile_size, main.height * tile_size)
for i in range(len(layers)):
final = (index == i).ifthenelse(layers[i], final)
print(f"writing {sys.argv[3]} ...")
final.write_to_file(sys.argv[3])
I hope it's easy to read. I can run it like this:
$ ./mosaic3.py smallpic/ mainpic/Use\ this.jpg x.png
loading tiles from smallpic/ ...
writing x.png ...
$
It takes about 5s on this 2015 laptop and makes this image:
I had to shrink it for upload, but here's a detail (bottom left of the first H):
Here's a google drive link to the mosaic, perhaps it'll work: https://drive.google.com/file/d/1J3ofrLUhkuvALKN1xamWqfW4sUksIKQl/view?usp=sharing
And here's this code on github: https://github.com/jcupitt/mosaic

Having multiple sprites in one Pyglet Window

I have the below code which currently outputs an image on a blank Pyglet window, however there is only one image outputted. I really need there to be a new image added every two second, with the previous images remaining intact also. For example, one image is added, two seconds later another image is added and two seconds after this another image is added. I have added the random library so an image at random can be added.
The code I have for this is below, it only displays the one image - I feel that this is getting stuck somewhere around the draw section.
import pyglet
import time
import random
window = pyglet.window.Window()
while True:
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
choice = images[random.randint(0,2)]
rawImage = pyglet.resource.image(choice)
sprite = pyglet.sprite.Sprite(rawImage)
#window.event
def on_draw():
window.clear()
sprite.draw()
time.sleep(2)
pyglet.app.run()
Any help or advice you can offer with this would be really appreciated.
A few issues/suggestions with your code.
First of all, the following code is redundant:
while True:
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
....
pyglet.app.run()
Because pyglet.app.run() is a blocking call, meaning, the loop will never loop - because pyglet.app.run() is in itself, a loop (more on this later).
Except if your application crashes, but you don't handle those exceptions so not even in that case will the code be re-run/looped.
Secondly, you should never define arrays/lists or anything really inside a loop. Loops are usually meant for logical operations, not creating things. Most contently some times it's useful to create things inside a loop, but those times they are more often than not accompanied by a if statement.
Resources cost a lot for the computer, both to setup and for the memory/hard drives etc. So trying to create your lists as early as possible and outside of any loops are suggested. for instance:
window = pyglet.window.Window()
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
while True:
choice = images[random.randint(0,2)]
Would have been a better option, if - again - the loop actually looped. In this case it's just a matter of tidying things up.
Also, this block of code:
#window.event
def on_draw():
window.clear()
sprite.draw()
Should not be created in a loop either, it's meant to replace your window variables on_draw function. So that should be moved out and put as early as possible in your logic IMO. At least kept separated by all other logic so it's not in between a "adding random image" and inside a "loop".
Now, the main reason your code fails, is that you thought this would loop, it doesn't. Again, pyglet.app.run() will lock your code execution on that row, because it's, well a never ending loop inside that function call.
You could expand your code and copy paste the code from pyglet.py's source code and it would look something like this (just to give you an idea of what's happening):
window = pyglet.window.Window()
while True:
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
choice = images[random.randint(0,2)]
rawImage = pyglet.resource.image(choice)
sprite = pyglet.sprite.Sprite(rawImage)
#window.event
def on_draw():
window.clear()
sprite.draw()
time.sleep(2)
def run(self):
while True:
event = self.dispatch_events()
if event:
self.on_draw()
note how pyglet.app.run() expands into another while True loop, that never really breaks. It's a bit oversimplified, but that's essentially what happens.
So your sprite = pyglet.sprite.Sprite(rawImage) will never be re-triggered.
Then, to your second biggest issue why this code would never work:
You're doing:
def on_draw():
sprite.draw()
But each loop you would have replaced the old sprite object, with a new one by doing sprite = pyglet.sprite.Sprite(rawImage). So what you would want to do, is keep a list/dictionary outside of the loop with all your visible image, and add to it and only render the images added.
Much like this:
import pyglet
import time
import random
width, height = 800, 600
window = pyglet.window.Window(width, height)
## Image options are the options we have,
## while `images` are the visible images, this is where we add images
## so that they can be rendered later
image_options = ["Image 1.png", "Image 2.png", "Image 3.png"]
images = {}
## Keep a timer of when we last added a image
last_add = time.time()
## Just a helper-function to generate a random image and return it
## as a sprite object (good idea to use sprites, more on that later)
def get_random_image():
choice = image_options[random.randint(0, len(image_options)-1)]
return pyglet.sprite.Sprite(pyglet.image.load(choice))
## Here, we define the `on_draw` replacement for `window.on_draw`,
## and it's here we'll check if we should add a nother image or not
## depending on how much time has passed.
#window.event
def on_draw():
window.clear()
## If two seconds have passed, and the ammount of images added are less/equal
## to how many images we have in our "database", aka `image_options`, then we'll
## add another image somewhere randomly in the window.
if time.time() - last_add > 2 and len(images) < len(image_options):
last_add = time.time()
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image
## Then here, is where we loop over all our added images,
## and render them one by one.
for _id_ in images:
images[_id_].draw()
## Ok, lets start off by adding one image.
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image
## And then enter the never ending render loop.
pyglet.app.run()
Now, this only works when you press a key or press the mouse inside the window. This is because that's the only time a event will be dispatched. And Pyglet will only render things if there's a event that is being triggered. There's two ways you can get around this, the hard core OOP way which I'll skip for now.
The second is to use what's called a Pyglet Clock, where you schedule something to happen at a interval. I'm not really good at this part, since I tend to use my own scheduler etc.
But here's the gist of it:
def add_image():
images[len(images)] = get_random_image()
pyglet.clock.schedule_interval(add_image, 2) # Every two seconds
This is a lot cleaner than doing the if time.time() - last_add > 2.
The result should look something like this:
import pyglet
import time
import random
width, height = 800, 600
window = pyglet.window.Window(width, height)
## Image options are the options we have,
## while `images` are the visible images, this is where we add images
## so that they can be rendered later
image_options = ["Image 1.png", "Image 2.png", "Image 3.png"]
images = {}
## Just a helper-function to generate a random image and return it
## as a sprite object (good idea to use sprites, more on that later)
def get_random_image():
choice = image_options[random.randint(0, len(image_options)-1)]
return pyglet.sprite.Sprite(pyglet.image.load(choice))
def add_image(actual_time_passed_since_last_clock_tick):
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image
## Here, we define the `on_draw` replacement for `window.on_draw`,
## and it's here we'll check if we should add a nother image or not
## depending on how much time has passed.
#window.event
def on_draw():
window.clear()
## Then here, is where we loop over all our added ima ges,
## and render them one by one.
for _id_ in images:
images[_id_].draw()
## Ok, lets start off by adding one image.
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image
## Add the schedule interval of adding a image every two seconds.
pyglet.clock.schedule_interval(add_image, 2)
## And then enter the never ending render loop.
pyglet.app.run()
This way, you won't need to press any keys or the mouse to trigger a event in Pyglet, it will handle that for you and do what you scheduled it to do.
Next up, is a small optimization from my part. It's a bonus, and will speed things up. It's called batched rendering, when you're rendering a lot of images and sprites, you're currently sending one image at a time to the graphics card. This is very CPU intensive. What you want to do is put the labor on the GPU. Because after all, you're working with graphics, right?
So, batched rendering is pretty easy in this case. Every time you call pyglet.sprite.Sprite, it has a parameter called batch=None (default). if you add a batch to the sprite object, you can render the entire batch by calling batch.draw() instead of each individual sprite.draw().
The solution would look something like this:
import pyglet
import time
from random import randint
width, height = 800, 600
window = pyglet.window.Window(width, height)
main_batch = pyglet.graphics.Batch()
## Image options are the options we have,
## while `images` are the visible images, this is where we add images
## so that they can be rendered later
image_options = ["Image 1.png", "Image 2.png", "Image 3.png"]
images = {}
## Just a helper-function to generate a random image and return it
## as a sprite object (good idea to use sprites, more on that later)
def get_random_image(x=0, y=0):
choice = image_options[randint(0, len(image_options)-1)]
return pyglet.sprite.Sprite(pyglet.image.load(choice), x=x, y=y, batch=main_batch)
def add_image(actual_time_passed_since_last_clock_tick=0):
image = get_random_image(x=randint(0, width), y=randint(0, height))
images[len(images)] = image
## Here, we define the `on_draw` replacement for `window.on_draw`,
## and it's here we'll check if we should add a nother image or not
## depending on how much time has passed.
#window.event
def on_draw():
window.clear()
## Instead of looping over each image in `images`,
## just do:
main_batch.draw()
## Ok, lets start off by adding one image.
## Instead of doing it manually, use the function add_image.
add_image()
## Add the schedule interval of adding a image every two seconds.
pyglet.clock.schedule_interval(add_image, 2)
## And then enter the never ending render loop.
pyglet.app.run()
I also made some alterations to add_image and get_random_image, mainly so that you can tell what position the image should be in inside the function, because pyglet.sprite.Sprite also takes two other parameters, x and y. So it makes no sense to change x and y after you've created the sprite, unless you want to move them afterwards (for instance, in a pyglet.clock.schedule_interval call).

Simulation using python and graphics.py image

I am trying to create a simulator. (referring to John Zelle's graphics.py)
Basically, my object will make use of graphics.py to display the object as a circle. Then, using the .move method in the class in graphics.py, the object will move in the x direction and y direction. If the object is currently drawn, the circle is adjusted to the new position.
Moving just one object can easily be done with the following codes:
win = GraphWin("My Circle", 100, 100)
c = Circle(Point(50,50), 10)
c.draw(win)
for i in range(40):
c.move(30, 0) #speed=30
time.sleep(1)
win.close()
However, I want the program to display multiple circles at once that moves at different speed. I've created a Circle object class which takes speed as an input, and a list with 3 Circle objects in it
circle = []
circle1 = Car(40)
circle2= Car(50)
circle3 = Car(60)
In summary, my question is, how do make use of this list such that I am able to display and move multiple circles in one window at once using the methods available in graphics.py?
That all depends on how you create your Car class, but nothing stops you from using the same code to move multiple circles in the same refresh cycle, e.g.:
win = GraphWin("My Circle", 1024, 400)
speeds = [40, 50, 60] # we'll create a circle for each 'speed'
circles = [] # hold our circles
for speed in speeds:
c = Circle(Point(50, speed), 10) # use speed as y position, too
c.draw(win) # add it to the window
circles.append((c, speed)) # add it to our circle list as (circle, speed) pair
for i in range(40): # main animation loop
for circle in circles: # loop through the circles list
circle[0].move(circle[1], 0) # move the circle on the x axis by the defined speed
time.sleep(1) # wait a second...
win.close()
Of course, if you're already going to use classes, you might as well implement move() in it so your Car instances can remember their speed and then just apply it when you call move() on them in a loop.

Categories