psychopy.org | Reference | Downloads | Github

Script for calling other experiments

I have several standalone experiments that I’ve coded in Builder and I would like to run them as a battery, while still having some flexibility to start individual tasks at a time. I need to have a simple gui that has a button for launching each experiment.

I was wondering what is the most efficient way to accomplish this. One option is to make an experiment in builder, then within a code component, use someting like subprocess.call(['python', 'task1.py']) to call each .py file, but I’m having trouble getting that to work because it doesn’t try to run within the standalone PsychoPy environment. I would like to rely on the standalone PsychoPy since installing all the dependencies will be complicated for other people working on this project.

Any help would be much appreciated!

Have you looked at execfile()?

Nice! I didn’t realize about that. It does successfully run a file. The problem is, there is something funny about the path that it runs in. I have each experiment in its own subfolder. I created a simple script that basically says:

import os
os.chdir('/path/to/subfolder')
execfile('task1.py')

The problem is, it always reverts back to folder that this “master” script resides. Then any part of the experiment that relies on a relative path (to the conditions excel file for instance), then it says it cannot find it. From the error message it’s clear that it’s searching in the same folder as the master script, instead of the subfolder, depsite me explicitly changing to that folder.

_thisDir = os.path.dirname(os.path.abspath(__file__)).decode(sys.getfilesystemencoding())
os.chdir(_thisDir)

The line above is Inside the task1.py file, and the variable _thisDir gets set to the directory of the master script, not the location of task1.py. There must be something about os.path.abspath__file__ that points to the original script that contains execfile. I might be able to work around this, but none of the solutions are too pretty. Any suggestions would be helpful!

Thanks

Well, honestly, use of execfile doesn’t often lead to elegant solutions.

But if you were to keep going with it, my first thought would be to see about sending some type of argument to the script, but execfile can’t pass arguments. This answer on stack overflow offers an interesting workaround though, to modify sys.argv (remembering the first argument should be the script name).

It might be worth a try to do something like this:

Before calling execfile, replace sys.argv in your “master” file:

# import sys at the top, of course
sys.argv = ["scriptName.py", "/path/to/script/folder"]

Add a code component to the builder (sub)experiment to check if there is an extra argument in sys.argv (so the experiment can still run independently), and reset _thisDir, and any other paths that were set before our code is called.

if len(sys.argv) > 1:
  newPath = sys.argv[1]
  os.chdir(newPath)
  _thisDir = newPath
  # reset things like filename, logFile, etc.
  thisExp.filename = ...etc

This is all theoretical, don’t take the code literally of course :slight_smile:

Thanks! This was super helpful actually. Essentially what I did was this:

in the master script:

import sys
sys.argv = ["./task1/task1.py', os.path.abspath('./task1/')]

Inside task1.py:

import os
#if we got an input argument, change to that directory
if len(sys.argv) > 1:
    newPath = sys.argv[1]
    print(newPath)
    os.chdir(newPath)
    _thisDir = newPath

#make sure and update the path to the data file
filename = _thisDir + os.sep + u'data/%s_%s_%s' % (expInfo['participant'], expName, expInfo['date'])

thisExp.dataFileName = filename

I was also able to pass a subject ID as a second argument to task1.py:

if len(sys.argv) > 2:
    subnum = sys.argv[2]
    expInfo['participant'] = subnum

and I also have some helper functions in the top-level directory that all the tasks use, so I also do:

import sys
import os.path as op
sys.path.append(op.abspath('..'))

The last trick is to comment out core.quit() at the bottom of task1.py. Otherwise it can’t continue on to subsequent tasks…

Thanks for your help. Hopefully this helps other folks too.

1 Like

I’ve tried to implement this for my project and have run into a hurdle. Basically, I can make a master script to run a second task just fine using the method I described above. However, when the task ends, it does something that prevents the master script from updating the screen. I’ve created a minimal example and uploaded here: https://www.dropbox.com/sh/h2xz3mv65arecny/AADsZyk1b0-4B8H9KPhoS4-ka?dl=0

My master script has 2 routines, 1 that calls the external script using execfile, and displays “task1 running” while this is happening, then a second routine that says “task complete” when it’s over. Both routines end via a keypress.

It is clear from this example and my project that it actually works, except that it always displays the “task running” message on the screen even when the task is over. But it’s not frozen-- it responds to keypresses as if it’s running fine, but the screen just isn’t updated. If I blindly press keys I can get it to continue to advance through my master script.

I have a feeling there is something subtle going on here that I’m not appreciating. Any help would be greatly appreciated. I’m trying to make this project for someone else who needs a streamlined workflow. I imagine I’m not the only one who wants this functionality either. It’d probably be difficult, but it’d be fantastic if there were a component for “run another psychopy experiment”. That could really expand the functionality of PsychoPy quite a bit.

Thanks!
Jason