Draw multiple polygons one after another

OS (e.g. Win10): Win11 Pro
PsychoPy version (e.g. 1.84.x): 2021.2.3
Standard Standalone? (y/n) If not then what?: y
What are you trying to achieve?:
I am trying to draw a route on which a car will be navigated by participant’s keypresses. This route needs to be random, so I have coded it as follows. The should have a fixed length and starts at the top of the screen and goes down, randomly to left and right, until it reaches the length.

import random
import numpy as np
from psychopy import visual, core
from psychopy.visual import polygon
mywin = visual.Window([1920,1080])

i = 0
lower_bound = .01
upper_bound = .1
final_array = []
sign = [1, -1]
L = .5
l = 0
start_x = [0]
start_y = [0]
orient = 90

while l < L:
    if np.mod(i,2) == 0: #up and down
        final_array.append(random.uniform(lower_bound, upper_bound))
        l += final_array[i]
        start_x.append(start_x[i])
        start_y.append(start_y[i] + (final_array[i])/2)
        orient = 90
    else: #left and right
        index = np.random.randint(0, 1) 
        final_array.append(sign[index] * random.uniform(lower_bound, upper_bound))
        start_y.append(start_y[i])
        start_x.append(start_x[i] + (final_array[i])/2)
        orient = 0
    current_x = start_x[i]
    current_y = start_y[i]
    Polygon = visual.Polygon(mywin)
    #Polygon.edges = 2
    #Polygon.ori = orient
    #Polygon.pos = [current_x, current_y]
    Polygon.draw(mywin)
    mywin.update()
    mywin.flip()
    i += 1

and I have inserted a line polygon in the same routine, that takes (current_x, current_y) and orient on each frame.
I have put the code at the begin experiment.

What specifically went wrong when you tried that?:
It only draws the last line. Although it goes through the loop (I printed something in the loop and it works), no more than one line is drawn.

What change should I make for all the lines to be drawn?

You’ve commented out the lines where the attributes of the polygon get updated, so it’s not surprising that it appears like only one thing is being drawn: in effect you’re just drawing the same shape in the same place multiple times.

Having said that, I had to change the number of edges to 3 to get a visible polygon. Inserting a core.wait(0.5) after the mywin.flip() will make it clear that animation is actually occurring (i.e. I see a triangle march across the screen, sometimes changing orientation).

Also, when doing animation like this, just create your objects once. i.e. in this case, the polygon = visual.Polygon(mywin) step should happen just once, before the loop starts. Then in the loop, just update the attributes (as you are currently doing). In general, creating objects comes with a time penalty - you don’t want to be needlessly re-doing that in time-sensitive situations.

I don’t really understand the geometry, but if you’re wanting to draw a path, then something like this seems to work:

# first define a line stimulus (not a closed polygon), before the loop
line = visual.ShapeStim(win = mywin, lineColor = 'black', closeShape = False)

Then inside the loop, delete the lines referring to the Polygon and do something like this:

line.vertices = list(zip(start_x, start_y))
line.draw()
mywin.flip()
core.wait(0.5)
i += 1

This will show a line creeping across the screen.

1 Like

Thank you @Michael this was very helpful.
Weirdly, the code you wrote for the ShapeStim works for me and creates the lines, but mine, although I uncommented the lines you mentioned, doesn’t work.
Anyway, my problem is solved, thanks.

Now an image of a car needs to run down the screen using another script of mine.
Right now, it doesn’t show up, although separately and not together with the path, it does show up on the screen.
I have also made the image transparent and moved the component on top of the code.
Is it because the path is drawn on mywin and the image component doesn’t have access to this window?
How should I solve this?

We’re going to need a more precise description of the problem:

The meaning of this is unclear.

If transparent it won’t be visible by definition?

You should (usually) only have one window, and every stimulus should refer to the same window name when it is created.

It’s very difficult to know what the actual issue is. Please provide a more precise description of the problem, and ideally your actual code, were you attempt to integrate the drawing of each stimulus.

1 Like

Thanks for your reply @Michael
I have an image component in the same routine as the path we talked about so far, with its position set to (x_rocket, y_rocket) which comes from:

y_rocket = 1
x_rocket = 0

and then is updated every frame like:

gravity = .001
x_drift = 0.05
y_rocket -= gravity
x_rocket += x_drift

Its appearance should happen together with the drawing of the path.
When the path is disabled, the image shows and moves on the screen, but together with the path, it doesn’s show up.
For the image, should I somehow refer to the same window (mywin)? how?

And I mentioned being transparent because I had the issue of multiple images not showing up together, and I was advised that the one on top should have transparent background, which worked then.

no ideas about how to solve this @Michael ? :slight_smile:

I’m guessing the problem is due to the fact that my path is being drawn in mywin, to which the rocket image has no access? could it be? if yes, I should either get the image access to mywin (how?) or define my path on psychopy’s own screen and not on mywin (how?)

Sorry, the problem still wasn’t entirely clear to me - it is very hard to make useful suggestions without seeing more of the code that you are referring to.

From your own code above:

Polygon = visual.Polygon(mywin)

Whenever you create a stimulus, you have to tell it which window it belongs to, just as you did for your polygon stimulus. Generally people have just one PsychoPy window active at a time, so this isn’t usually a source of issues.

We can’t really help with this without seeing your actual code - otherwise we can only speculate about which of many things the issue might be.

Thanks for your reply @Michael
Here’s my entire project as a python file:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This experiment was created using PsychoPy3 Experiment Builder (v2021.2.3),
    on April 20, 2022, at 11:44
If you publish work using this script the most relevant publication is:

    Peirce J, Gray JR, Simpson S, MacAskill M, Höchenberger R, Sogo H, Kastman E, Lindeløv JK. (2019) 
        PsychoPy2: Experiments in behavior made easy Behav Res 51: 195. 
        https://doi.org/10.3758/s13428-018-01193-y

"""

from __future__ import absolute_import, division

from psychopy import locale_setup
from psychopy import prefs
from psychopy import sound, gui, visual, core, data, event, logging, clock, colors
from psychopy.constants import (NOT_STARTED, STARTED, PLAYING, PAUSED,
                                STOPPED, FINISHED, PRESSED, RELEASED, FOREVER)

import numpy as np  # whole numpy lib is available, prepend 'np.'
from numpy import (sin, cos, tan, log, log10, pi, average,
                   sqrt, std, deg2rad, rad2deg, linspace, asarray)
from numpy.random import random, randint, normal, shuffle, choice as randchoice
import os  # handy system and path functions
import sys  # to get file system encoding

from psychopy.hardware import keyboard

import random
import numpy as np
from psychopy import visual, core, event
from psychopy.visual import ShapeStim


# Ensure that relative paths start from the same directory as this script
_thisDir = os.path.dirname(os.path.abspath(__file__))
os.chdir(_thisDir)

# Store info about the experiment session
psychopyVersion = '2021.2.3'
expName = 'rocket'  # from the Builder filename that created this script
expInfo = {'participant': ''}
dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName)
if dlg.OK == False:
    core.quit()  # user pressed cancel
expInfo['date'] = data.getDateStr()  # add a simple timestamp
expInfo['expName'] = expName
expInfo['psychopyVersion'] = psychopyVersion

# Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc
filename = _thisDir + os.sep + u'data/%s_%s_%s' % (expInfo['participant'], expName, expInfo['date'])

# An ExperimentHandler isn't essential but helps with data saving
thisExp = data.ExperimentHandler(name=expName, version='',
    extraInfo=expInfo, runtimeInfo=None,
    originPath='C:\\Users\\zahra\\Desktop\\space\\space.py',
    savePickle=True, saveWideText=True,
    dataFileName=filename)
# save a log file for detail verbose info
logFile = logging.LogFile(filename+'.log', level=logging.EXP)
logging.console.setLevel(logging.WARNING)  # this outputs to the screen, not a file

endExpNow = False  # flag for 'escape' or other condition => quit the exp
frameTolerance = 0.001  # how close to onset before 'same' frame

# Start Code - component code to be run after the window creation

# Setup the Window
win = visual.Window(
    size=[1536, 864], fullscr=False, screen=0, 
    winType='pyglet', allowGUI=True, allowStencil=False,
    monitor='', color=[0,0,0], colorSpace='rgb',
    blendMode='avg', useFBO=True, 
    units='height')
# store frame rate of monitor if we can measure it
expInfo['frameRate'] = win.getActualFrameRate()
if expInfo['frameRate'] != None:
    frameDur = 1.0 / round(expInfo['frameRate'])
else:
    frameDur = 1.0 / 60.0  # could not measure, so guess

# Setup eyetracking
ioDevice = ioConfig = ioSession = ioServer = eyetracker = None

# create a default keyboard (e.g. to check for escape)
defaultKeyboard = keyboard.Keyboard()

# Initialize components for Routine "route"
routeClock = core.Clock()
spaceship = visual.ImageStim(
    win=win,
    name='spaceship', 
    image='images/rocket_transparent.png', mask=None,
    ori=0.0, pos=[0,0], size=(0.1, 0.1),
    color=[1,1,1], colorSpace='rgb', opacity=None,
    flipHoriz=False, flipVert=False,
    texRes=128.0, interpolate=True, depth=0.0)
y_rocket = -1
x_rocket = 0


gravity = .01
x_drift = 0.05

#kb = keyboard.Keyboard()
#theseKeys = kb.getKeys(keyList=["left", "right"], waitRelease=False) 

# Create some handy timers
globalClock = core.Clock()  # to track the time since experiment started
routineTimer = core.CountdownTimer()  # to track time remaining of each (non-slip) routine 

# ------Prepare to start Routine "route"-------
continueRoutine = True
# update component parameters for each repeat
spaceship.setPos((x_rocket, y_rocket))

#create a window
mywin = visual.Window(fullscr=True)
line = visual.ShapeStim(win=mywin, lineColor = 'black', lineWidth = 5, closeShape = False)

i = 0
lower_bound = .2
upper_bound = .3
final_array = []
sign = [1, -1]
L = 3
l = 0
start_x = [0] 
start_y = [1]

while l < L:
    if np.mod(i,2) == 0: #up and down
        final_array.append(random.uniform(lower_bound, upper_bound))
        l += final_array[i]
        start_x.append(start_x[i])
        start_y.append(start_y[i] - (final_array[i])/2)
    else: #left and right
        index = np.random.randint(2) 
        final_array.append(sign[index] * random.uniform(lower_bound, upper_bound))
        start_y.append(start_y[i])
        start_x.append(start_x[i] + (final_array[i])/2)      
    line.vertices = list(zip(start_x, start_y))
    line.draw()
    mywin.flip()
    core.wait(0.5)
    i += 1
# keep track of which components have finished
routeComponents = [spaceship]
for thisComponent in routeComponents:
    thisComponent.tStart = None
    thisComponent.tStop = None
    thisComponent.tStartRefresh = None
    thisComponent.tStopRefresh = None
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED
# reset timers
t = 0
_timeToFirstFrame = win.getFutureFlipTime(clock="now")
routeClock.reset(-_timeToFirstFrame)  # t0 is time of first possible flip
frameN = -1

# -------Run Routine "route"-------
while continueRoutine:
    # get current time
    t = routeClock.getTime()
    tThisFlip = win.getFutureFlipTime(clock=routeClock)
    tThisFlipGlobal = win.getFutureFlipTime(clock=None)
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *spaceship* updates
    if spaceship.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
        # keep track of start time/frame for later
        spaceship.frameNStart = frameN  # exact frame index
        spaceship.tStart = t  # local t and not account for scr refresh
        spaceship.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(spaceship, 'tStartRefresh')  # time at next scr refresh
        spaceship.setAutoDraw(True)
    #
    #if event.getKeys(["right"]):
    #    x_rocket += x_drift
    #elif event.getKeys(["left"]):
    #    x_rocket -= x_drift
    
    y_rocket += gravity
    #x_rocket += x_drift
    
    # check for quit (typically the Esc key)
    if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
        core.quit()
    
    # check if all components have finished
    if not continueRoutine:  # a component has requested a forced-end of Routine
        break
    continueRoutine = False  # will revert to True if at least one component still running
    for thisComponent in routeComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished
    
    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

# -------Ending Routine "route"-------
for thisComponent in routeComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
thisExp.addData('spaceship.started', spaceship.tStartRefresh)
thisExp.addData('spaceship.stopped', spaceship.tStopRefresh)
# the Routine "route" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()

# Flip one final time so any remaining win.callOnFlip() 
# and win.timeOnFlip() tasks get executed before quitting
win.flip()

# these shouldn't be strictly necessary (should auto-save)
thisExp.saveAsWideText(filename+'.csv', delim='auto')
thisExp.saveAsPickle(filename)
logging.flush()
# make sure everything is closed down
thisExp.abort()  # or data files will save again on exit
win.close()
core.quit()

The problem is that the space image does not show up.

and this is what it looks likes in the builder:

I didn’t realise you were using the Builder approach - that changes things quite a bit. For example, we should generally avoid calling win.flip() or core.wait() in Builder scripts, as they will disrupt Builder’s own control of the drawing and event monitoring cycle.

You’ve also created a window (mywin) in code, which is additional to the one (called win) that Builder has automatically created.

You need to take a step back and describe exactly what you are wanting to do, how you are currently trying to achieve that, and exactly what goes wrong.

For example, because I don’t know why you are creating a second window, I can’t give any useful advice on what should be appearing where and why, because I don’t know what that window is for and how it is expected to complement Builder’s own window. And although there are sometimes reasons to create a second window in code (typically for the experimenter to see, rather than the subject, and often on a physically separate monitor), that is unusual and the reason needs to be explained to us. A second window for the subject is particularly unusual and is generally only needed if you are doing something fancy with stereoscopic display systems.

But the good news is that because you are using Builder, you don’t need to show the whole script - just show the contents of the various tabs of your code component(s) and explain what they are intended to do - it is far easier to give code suggestions if we can just say what tab of a code component new or revised code needs to go in and in what routine.

2 Likes

Thanks a lot @Michael, the problem was, as I thought and as you mentioned, that I had defined a new window. I actually didn’t know that Builder has its own window, that it’s accessible as win, and that drawing a line would work without creating a new window.
I removed the new window and all is working fine.
Thank you!