Skip to content

Commit

Permalink
add rest crounting, escaping, getting number, and passing objects to …
Browse files Browse the repository at this point in the history
…objects
  • Loading branch information
Yi-Chia-Chen committed Nov 27, 2018
1 parent 64b1723 commit 2a959d1
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 94 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ This is a tiny python toolbox for PsychoPy experiment by Yi-Chia Chen.

## Version History
- 1.0.0 (2018.10.18): First version with instructions controlling, subject data, and trial data objects
- Used in Mazes Game pilot 1
- Used in Mazes Game Pilot 1
- 1.1.0 (2018.11.27): Passing experiment info between objects;
adding rest counting to instructions, escape to trial, and get number to subject;
fixing format
- Used in Size Preference Pilot 1

## Planned Improvements
- Create parent objects for some common functions
- Create object controlling experimental blocks and trials with mixed and blocked design, different randomization units, etc.
- splitting up the instructions object to an instructions and an experiment objects
268 changes: 176 additions & 92 deletions myExpt.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# -*- coding: utf-8 -*-

import math, string
from datetime import datetime
from psychopy import core, visual, info, event


# ██████ ███████ ███ ██ ███████ ██████ █████ ██ ███████ ██ ██ ███ ██ ██████ ████████ ██ ██████ ███ ██ ███████
# ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██
# ██ ███ █████ ██ ██ ██ █████ ██████ ███████ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
# ██████ ███████ ██ ████ ███████ ██ ██ ██ ██ ███████ ██ ██████ ██ ████ ██████ ██ ██ ██████ ██ ████ ███████
# ███████ ██ ██ ███ ██ ██████ ████████ ██ ██████ ███ ██ ███████
# ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██
# █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
# ██ ██████ ██ ████ ██████ ██ ██ ██████ ██ ████ ███████

# polar coordinate to Cartesian coordinate
def polarToCart(r, theta):
Expand Down Expand Up @@ -37,40 +38,183 @@ def getSystemInfo(w):
# sysDic['windowRefreshTimeSD_ms'] # SD of monitor refresh intervals
# sysDic['windowSize_pix'] # window size
# sysDic['windowIsFullScr'] # whether window is fullscreen
# full list of keys: ['psychopyVersion', 'systemRebooted', 'windowWinType', 'windowRgb', 'systemUserID', 'windowRefreshTimeMedian_ms', 'windowScreen', 'systemTimeNumpySD1000000_sec', 'windowSize_pix', 'systemUser', 'systemHaveInternetAccess', 'windowMonitor.name', 'systemMemFreeRAM', 'windowMonitor.getDistance_cm', 'psychopyHaveExtRush', 'windowRefreshTimeAvg_ms', 'windowUnits', 'systemLocale', 'systemPlatform', 'experimentRunTime', 'windowRefreshTimeSD_ms', 'experimentScript.directory', 'systemSec.pythonSSL', 'windowMonitor.getWidth_cm', 'experimentRunTime.epoch', 'experimentScript', 'experimentScript.digestSHA1', 'systemUsersCount', 'windowPos_pix', 'pythonVersion', 'windowMonitor.currentCalibName', 'systemMemTotalRAM', 'windowIsFullScr', 'systemHostName']
return sysDic['pythonVersion'], sysDic['psychopyVersion'], sysDic['systemHostName'], sysDic['windowRefreshTimeAvg_ms'], sysDic['windowRefreshTimeSD_ms'], sysDic['windowSize_pix'], sysDic['windowIsFullScr']
# full list of keys:
# ['psychopyVersion', 'systemRebooted', 'windowWinType', 'windowRgb',
# 'systemUserID', 'windowRefreshTimeMedian_ms', 'windowScreen',
# 'systemTimeNumpySD1000000_sec', 'windowSize_pix', 'systemUser',
# 'systemHaveInternetAccess', 'windowMonitor.name', 'systemMemFreeRAM',
# 'windowMonitor.getDistance_cm', 'psychopyHaveExtRush',
# 'windowRefreshTimeAvg_ms', 'windowUnits', 'systemLocale',
# 'systemPlatform', 'experimentRunTime', 'windowRefreshTimeSD_ms',
# 'experimentScript.directory', 'systemSec.pythonSSL',
# 'windowMonitor.getWidth_cm', 'experimentRunTime.epoch',
# 'experimentScript', 'experimentScript.digestSHA1', 'systemUsersCount',
# 'windowPos_pix', 'pythonVersion', 'windowMonitor.currentCalibName',
# 'systemMemTotalRAM', 'windowIsFullScr', 'systemHostName']
return (sysDic['pythonVersion'], sysDic['psychopyVersion'],
sysDic['systemHostName'], sysDic['windowRefreshTimeAvg_ms'],
sysDic['windowRefreshTimeSD_ms'], sysDic['windowSize_pix'],
sysDic['windowIsFullScr'])

# capitalize the first letter ONLY, instead of using string.capitalize() or string.capwords() where the rest of the letter will be .lower()
# capitalize the first letter ONLY,
# instead of using string.capitalize() or string.capwords()
# where the rest of the letter will be .lower()
def capFirstLetter(stringText):
return stringText[0].upper() + stringText[1:]

# convert RGB 256 scale to -1.0-1.0
def RGBConvert(rgb):
return [(2.0*i/255.0)-1 for i in rgb]


# ███████ ██ ██ ██████ ██ ███████ ██████ ████████
# ██ ██ ██ ██ ██ ██ ██ ██ ██
# ███████ ██ ██ ██████ ██ █████ ██ ██
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
# ███████ ██████ ██████ █████ ███████ ██████ ██

class subjObject(object):
def __init__(self, checked, exptName, screenColor='gray', additionalVar={}):
self.checked = checked
self.exptName = exptName
self.timer = core.Clock()
self.titles = ['checked','num','date','startTime',
'python','psychopy','system',
'frameD_M','frameD_SD',
'winWidth','winHeight','fullScreen',
'instrD','duration']
self.instrD = 'X' # default value before completion
self.duration = 'X' # default value before completion
self.getSubjNo()
self.screenColor = screenColor
self.w = visual.Window(
color=self.screenColor, units='pix', fullscr=True,
allowGUI=False, autoLog=False)
self.getBasicInfo()
if additionalVar != {}:
self.addTitles(additionalVar)

def getSubjNo(self):
while True:
self.num = raw_input('Subj No.: ')
try:
float(self.num)
break
except ValueError:
print "That's not a number."
self.formal = bool(int(raw_input('Formal? ')))

if self.formal:
self.fileName = 'subj_'+self.exptName+'.txt'
else:
self.fileName = 'testingSubj_'+self.exptName+'.txt'

def addTitles(self, additionalVar):
for key, value in additionalVar:
self.titles.append(key)
setattr(self, key, value)

def getBasicInfo(self):
self.date, self.startTime = formattedTime()
(self.python, self.psychopy, self.system,
self.frameD_M, self.frameD_SD, (self.winWidth,
self.winHeight), self.fullScreen) = getSystemInfo(self.w)

def save(self, complete=True):
self.duration = self.timer.getTime()/60.0 # experiment duration in minutes
if not complete:
self.duration = 'HALT_'+str(self.duration)
with open(self.fileName, 'a') as subjFile:
subjFile.write(tabString([capFirstLetter(x) for x in self.titles])+'\n')
subjFile.write(tabString([getattr(self, x) for x in self.titles])+'\n')


# ████████ ██████ ██ █████ ██
# ██ ██ ██ ██ ██ ██ ██
# ██ ██████ ██ ███████ ██
# ██ ██ ██ ██ ██ ██ ██
# ██ ██ ██ ██ ██ ██ ███████

class trialObject(object):
def __init__(self, subj, titles):
self.subj = subj
self.subjNo = subj.num
if subj.formal:
self.fileName = 'data_'+subj.exptName+'.txt'
else:
self.fileName = 'testingData_'+subj.exptName+'.txt'
self.exptName = subj.exptName
self.titles = titles
self.clearTrial() # to create all attributes and set default values

def openFile(self):
self.file = open(self.fileName, 'a')
self.file.write(tabString([capFirstLetter(x) for x in self.titles])+'\n')

def saveTrial(self, clear=True):
# save and clear data (except for the subject numbers)
self.file.write(tabString([getattr(self, x) for x in self.titles])+'\n')
if clear:
for x in self.titles:
if x != 'subjNo':
setattr(self, x, '')

def clearTrial(self): # clear data
for x in self.titles:
if x != 'subjNo':
setattr(self, x, '')

def closeFile(self):
try:
self.file.close()
except AttributeError:
print 'The data file is not opened yet.'

def escapeExpt(self):
self.subj.w.close()
self.subj.save(complete=False)
self.closeFile()
exit(0)


# ██ ███ ██ ███████ ████████ ██████ ██ ██ ██████ ████████ ██ ██████ ███ ██ ███████
# ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
# ██ ██ ██ ██ ███████ ██ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
# ██ ██ ████ ███████ ██ ██ ██ ██████ ██████ ██ ██ ██████ ██ ████ ███████

class instrObject:
def __init__(self, w, fileName, color, beforeFormalText, restText, exptEndText, advancedKeyList=['backslash']):
self.w = w
# ██ ███ ██ ███████ ████████ ██████
# ██ ████ ██ ██ ██ ██ ██
# ██ ██ ██ ██ ███████ ██ ██████
# ██ ██ ██ ██ ██ ██ ██ ██
# ██ ██ ████ ███████ ██ ██ ██

class instrObject(object):
def __init__(self, subj, trial, fileName='instructions.txt', color='black',
beforeFormalText='X', restText='X', restN=0, restKey=['space'],
exptEndText='X', advancedKeyList=['backslash']):
self.subj = subj
self.trial = trial
self.w = self.subj.w
self.list, self.length = self.readInstr(fileName)
self.stim = visual.TextStim(self.w, text='', color=color, height=30, wrapWidth=900)
self.stim = visual.TextStim(
self.w, text='', color=color,
height=30, wrapWidth=900
)
self.restText = restText
self.advancedKeyList = advancedKeyList
self.exptEndText = exptEndText
self.index = 0
self.beforeFormalText = beforeFormalText
self.advancedKeyList = advancedKeyList
self.restCount = 0
self.restN = restN
self.restKey = restKey

def readInstr(self,fileName):
tempFile = open(fileName, 'r')
tempText = tempFile.readlines()
tempFile.close()
instrList = [] # even indexes store instructions, odd indexes store name of function to run before or during the instructions are shown
instrList = [] # even indices: instructions; odd indices: function name
thisPage = ''
for line in tempText:
if line[:3] == 'XXX':
thisPage = thisPage[:-1] # remove the last line break in the page before adding to the list
thisPage = thisPage[:-1]
# remove last line break in the page before adding to the list
try:
if thisPage[-1] == '\r': # for Windows
thisPage = thisPage[:-1]
Expand All @@ -79,9 +223,10 @@ def readInstr(self,fileName):
instrList.append(thisPage)
thisPage = ''
if line[3] == 'S': # line == 'XXXS[functionName-parameters]'
instrList.append(line[4:].rstrip('\r\n')) # append function name
instrList.append(line[4:].rstrip('\r\n'))
# append function name
else: # line == 'XXXN', normal instructions page
instrList.append('') # no function to be run
instrList.append('') # no function to run
else:
thisPage += line
return instrList, len(instrList)/2 # number of instructions pages
Expand All @@ -91,11 +236,15 @@ def formal(self):
self.stim.draw()
self.w.flip()

def rest(self,b,blockN):
self.restText.replace( 'XX__XX', str(int(round( 100*(b-1)/(blockN-1) ))) )
def rest(self):
self.restCount += 1
completePerc = int(round( 100.0*self.restCount/(self.restN+1.0) ))
self.restText = self.restText.replace('XX__XX', str(completePerc))
self.stim.setText(self.restText)
self.stim.draw()
self.w.flip()
event.waitKeys(keyList=self.restKey)
self.w.flip()

def next(self):
self.stim.setText(self.list[self.index])
Expand All @@ -107,73 +256,8 @@ def endExpt(self):
self.stim.setText(self.exptEndText)
self.stim.draw()
self.w.flip()
self.trial.closeFile()
self.subj.save()
event.waitKeys(keyList=self.advancedKeyList, maxWait=60)
self.w.close()
core.quit()


# ███████ ██ ██ ██████ ██ ███████ ██████ ████████
# ██ ██ ██ ██ ██ ██ ██ ██ ██
# ███████ ██ ██ ██████ ██ █████ ██ ██
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
# ███████ ██████ ██████ █████ ███████ ██████ ██

class subjObject:
def __init__(self, checked, subjNo, w, fileName):
self.checked = checked
self.subjNo = subjNo
self.w = w
self.fileName = fileName
self.titles = ['checked','subjNo','date','startTime','python','psychopy','system','frameD_M','frameD_SD','winWidth','winHeight','fullScreen']
self.duration = 'X' # default value before completion
self.getBasicInfo()

def addTitles(self, titles):
self.titles += titles
for x in titles:
setattr(self, x, '') # default values

def getBasicInfo(self):
self.date, self.startTime = formattedTime()
self.python, self.psychopy, self.system, self.frameD_M, self.frameD_SD, (self.winWidth, self.winHeight), self.fullScreen = getSystemInfo(self.w)

def save(self):
self.titles.append('duration') # the duration will always be the last column
with open(self.fileName, 'a') as subjFile:
subjFile.write(tabString([capFirstLetter(x) for x in self.titles])+'\n')
subjFile.write(tabString([getattr(self, x) for x in self.titles])+'\n')


# ████████ ██████ ██ █████ ██
# ██ ██ ██ ██ ██ ██ ██
# ██ ██████ ██ ███████ ██
# ██ ██ ██ ██ ██ ██ ██
# ██ ██ ██ ██ ██ ██ ███████

class dataObject:
def __init__(self, subjNo, fileName, titles):
self.subjNo = subjNo
self.fileName = fileName
self.titles = titles

def openFile(self):
self.file = open(self.fileName, 'a')
self.file.write(tabString([capFirstLetter(x) for x in self.titles])+'\n')

def saveTrial(self, clear=True):
self.file.write(tabString([getattr(self, x) for x in self.titles])+'\n')
if clear:
for x in self.titles:
if x != 'subjNo': # clear data after saving to prepare for the next trial, except for the subject number
setattr(self, x, '')

def clearTrial(self):
for x in self.titles:
if x != 'subjNo':
setattr(self, x, '') # clear data after saving to prepare for the next trial

def closeFile(self):
try:
self.file.close()
except AttributeError:
print 'The data file is not opened yet.'
exit(0)

0 comments on commit 2a959d1

Please sign in to comment.