def enableNext(bid, j): [v, s, p] = bid.split(".") try: nextBtn = btn_node("%s.%s.%d" % (v, s, int(p) + 1), j) except LookupError: ## done with this step try: nextBtn = btn_node("%s.%d.%d" % (v, int(s) + 1, 0), j) except LookupError: ## done with this visit vNode = get_visit(bid, j) lib.set_node(vNode, True, jlib.VCOMPLETE) return int(v) + 1 ## we have the next valid button: enable it! ## ... unless we're in redo-mode: skip completed steps until we've resumed resume = lib.get_node(j, jlib.RESUME) if resume: # redo-mode! if compareBids(resume, nextBtn["id"]): # we've caught up to the resume point lib.set_node(j, None, jlib.RESUME) # end redo-mode lib.set_here(nextBtn, "disabled", False) # enable as normal else: # stay in redo-mode if lib.get_node(nextBtn, "time") == "": # not stamped yet, so enable lib.set_here(nextBtn, "disabled", False) else: # timestamped; this was complete before redo began. # don't enable; rather, advance progress & skip to next-next nextID = lib.get_node(nextBtn, "id") setProgress(nextID, get_visit(bid, j)) return enableNext(nextID, j) else: # no redo occurring, normal enable lib.set_here(nextBtn, "disabled", False) return int(v)
def processLogin(self): subject = self.subject print 'SUBJECT['+ subject+']' self.mySubjectDir = j.checkSubjDir(subject) j.dirStructure(subject) # verify/create subject's directory structure (visits, etc.) self.jsonpath = os.path.join(self.mySubjectDir, "%s_experiment_info.json" % subject) if os.path.exists(self.jsonpath): self.json = lib.load_json(self.jsonpath) self.json['flotscript'] = '' if 'flotscript_header' in self.json: del self.json['flotscript_header'] else: lib.set_node(self.json, subject, j.SUBJID) ## get a fresh json_template ## find the next incomplete visit (if study complete, display final visit) for v in range(j.NUM_VISITS): self.setTab(v) if not lib.get_node(self.json, self.vNodePath + j.VCOMPLETE): break visit = v # handle subject's group assignment and create visit/session dir based on group, if needed. group = lib.get_node(self.json, j.GROUP) if not group == "": ### create & populate session dir self.visitDir, correctVisit = j.checkVisitDir(subject, visit, group, self.json) if not correctVisit == visit: self.subjectMoved("<b>Cannot move on to next visit without ROI masks!</b>", "false") self.setTab(correctVisit) return self.renderAndSave() # saves the json, and renders the page else: return self.modalthing() # render modal to assign group -> call setgroup() -> save json & render normally
def completionChecks(self): tab = self.TabID vComplete = lib.get_node(self.json,self.vNodePath + j.VCOMPLETE) print "complete:", vComplete, if vComplete: return progress = lib.get_node(self.json, self.vNodePath + j.VPROGRESS) print "... progress:",progress, "tab:",tab if progress == "": ## activate first step, deactivate all else if (tab == 0): [bt.enableOnly(self.json, t, None) for t in range(1,len(j.VISIT_LIST))] bt.enableOnly(self.json, tab, 'first') else: prevVNodePath = self.vNodePath[:-2] + str(tab-1) + ":" lastVComplete = lib.get_node(self.json, prevVNodePath + j.VCOMPLETE) if lastVComplete: bt.enableOnly(self.json, tab, 'first') else: bt.enableOnly(self.json, tab, None) else: activeVisit = bt.enableNext(progress,self.json) if not activeVisit == tab: bt.enableOnly(self.json, tab, None) if activeVisit < j.NUM_VISITS: bt.enableOnly(self.json, activeVisit, 'first') else: self.subjectMoved("<b>All visits complete!</b>", "false") return
def nameLogfile(node,subject,useInfo=None): """ Out: absolute path where stdout/stderr logfile should go """ # error check: useInfo has 'run','history'; node has 'action', 'id' if not useInfo: ## allow us to (optionally) use a different btn's info useInfo=node ## use this btn's info by default run = useInfo['run'] timeStr = time.strftime("_%b%d_%H%M%S_",time.localtime(float(lib.get_node(useInfo,'history:-1')))) action = node['action'] tab = int(lib.get_node(node,'id')[0]) filename = str(run) + timeStr + action + '.log' return os.path.abspath(os.path.join(lib.SUBJS, subject, "session%d"%tab, filename))
def checkVisitDir(subject,visit,group,myJson): v = int(visit) maxV = len(VISIT_LIST) ## off-by-one error??? if v > maxV: ##obo danger print "ERROR checkVisitDir: requested",v, "but the max visit number is", maxV raise Exception("Invalid visit number requested.") if v < 0: ## supports visits[-1] indexing v = maxV + 1 + v subjDir = checkSubjDir(subject) # checks/creates subjDir myVisitDir = getVisitDir(subject, v) if not os.path.exists(myVisitDir): raise OSError("Can't find this visit directory! %s"%myVisitDir) vType = get_node(bt.get_visit(v, myJson), VTYPE) # for realtime visits, verify murfi templates (in 'scripts' directory) exist if vType == 'realtime': # first check that localizer masks are present if not os.path.exists(os.path.join(subjDir, 'mask')): print "You can't do a realtime run until there are subject masks!" v = 0 return getVisitDir(subject, v), v murfiDir = os.path.abspath(os.path.join(myVisitDir, "scripts")) if not os.path.exists(murfiDir): ### this is dumb. i should make it an importable library. print "Trying to create rt session for visit", v subprocess.Popen(["python", "createRtSession.py", subject, str(v), 'none', group], cwd=RTSCRIPTSDIR) return myVisitDir, v
def doMakoLogin(self,subject=None,visit=None): self.subject = subject ## keep this accessible to other methods self.mySubjectDir = j.checkSubjDir(subject) self.jsonpath = os.path.join(self.mySubjectDir, "%s_experiment_info.json"%subject) if os.path.exists(self.jsonpath): self.json = lib.load_json(self.jsonpath) else: lib.set_node(self.json,subject,j.SUBJID) ## get a fresh json_template visit = lib.get_node(self.json,j.TAB) self.setTab(visit) # activates the tab # handle subject's group assignment and create visit/session dir based on group, if needed. group = lib.get_node(self.json, j.GROUP) if not group == "": self.visitDir = j.checkVisitDir(subject,visit,group, self.json) ### create & populate session dir return self.renderAndSave() # saves the json, and renders the page else: return self.modalthing() # render modal to assign group -> call setgroup() -> save json & render normally
def btn_node(bid,j): """ Helper designed for MakoRoot.formHandler(). Returns a dict of button properties for the desired button. In: * bid (str), "x.y.z" * j (dict-like), MakoRoot.json Out: * node (dict-like), subnode of j, for that button """ [v,s,p] = bid.split('.') return lib.get_node(j,['protocol',v,'steps',s,'parts',p])
def get_visit(info,j): if isinstance(info,str): ## info is a buttonID, like v.s.p visit = int(info[0]) elif isinstance(info,unicode): ## also probably a buttonID? visit = int(info[0]) elif isinstance(info,int): ## info is just a visit number visit = info else: print "info in get_visit:", info raise TypeError("get_visit: Didn't expect a %s"%(type(info))) return lib.get_node(j, [jlib.FULLSTUDY,visit])
def parent_node(bid,j): """ Helper that get's a button's parent-step's parts. Returns the parent node of a button, which must be the "parts" list of the step that the button is a part of. There may be only one part in the list. In: * bid (str), "x.y.z" * j (dict-like), MakoRoot.json Out: * parent (list), subnode of j, for that button """ [v,s,p] = bid.split('.') ## won't be using p return lib.get_node(j, ['protocol',v,'steps',s,'parts'])
def checkPsychoDone(subject, node): tab = int(lib.get_node(node,'id')[0]) expName = os.path.splitext(os.path.basename(node['file']))[0] filename = expName + getTimestamp(node, -2) + 'trials.psydat' doneFile = os.path.abspath(os.path.join(lib.SUBJS, subject, "session%d"%tab, 'ltTaskData', expName, filename)) print "Psychopy complete? Checking", doneFile if os.path.exists(doneFile): datFile = fromFile(doneFile) print "Found .psydat file. Collected",datFile.thisN, "trials, so finished is",datFile.finished return datFile.finished else: return False
def sib_node(bid,j,z): """ Button groups consist of sibling buttons. Get sibling z for the button bid. Returns a dict of button properties for the desired sibling button. In: * bid (str), eg "x.y.z" * j (dict-like), MakoRoot.json * z (int/str), indicating which sibling to return Out: * node (dict-like), subnode of j, for the sibling button """ [v,s,p] = bid.split('.') return lib.get_node(j,['protocol',v,'steps',s,'parts',z])
def movementRedo(j, tab): ## Use this visit's progress to figure out what things to redo. ## Collaborate with enableNext() to use timestamps as a high-water mark. ## j (dict) = full json for the subject ## tab (int) = visit/session number vNode = get_visit(tab, j) vBids = visitBids(tab, j) ## full, ordered list of all button ids for this visit progress = getProgress(vNode) currentBid = vBids[vBids.index(progress) + 1] ## for current step (after progress) ## 1. build prereq button id list # 1.1 get base prereqs, plus prereqs based on current step's action keyword action = lib.get_node(btn_node(currentBid, j), "action") prereqs = [] for btn in [btn_node(bid, j) for bid in visitBids(tab, j)]: if btn.has_key("prereqFor"): prfor = btn["prereqFor"].split(".") # some prereqs are '.'-joined if ("all" in prfor) or (action in prfor): prereqs.append(btn["id"]) # 1.2 get all siblings of the current step sibs = [lib.get_node(sib, "id") for sib in parent_node(currentBid, j)] prereqs.extend(sibs) # 2. prepare prereqs for redo: clear all tstamps, uncheck checkboxes. for prereq in prereqs: prNode = btn_node(prereq, j) clearTimeStamp(prNode) if lib.get_node(prNode, "action") == "": # it's a checkbox lib.set_here(prNode, "checked", False) # clear checkboxes # 3. save progress to study-wide resume pointer, unless we're restarting a redo. resume = lib.get_node(j, jlib.RESUME) if resume: pass ## restarting redo-mode due to another movement else: ## enter fresh redo-mode lib.set_node(j, progress, jlib.RESUME) # 4. reset progress so that the first prereq is the next thing to happen. vBids.insert(0, "") # if 1st prereq is tab.0.0, then progress = "" (as expected) resetBid = vBids[vBids.index(prereqs[0]) - 1] setProgress(resetBid, vNode) return
def updateProgress(self,bid): """ Only call this once an action (or structural scan) is done. """ # do nothing if progress > bid (because we're redoing something) curProg= lib.get_node(self.json, self.vNodePath + j.VPROGRESS) if curProg == "": # beginning of a visit, or we're in redo mode. bt.setProgress(bid, bt.get_visit(bid,self.json)) elif bt.compareBids(curProg, bid): # step print "new progress will be",bid bt.setProgress(bid, bt.get_visit(bid,self.json)) else: print "completed", bid, ", but that's less than", curProg return
def makoRealtimeStim(self, btn_value, node): murfNode = bt.sib_node(node['id'], self.json, 0) stimLog = bt.nameLogfile(node, self.subject, murfNode) self.run = murfNode['run'] ## in case of accidental logout self.flotJavascript() # based on group, use proper stimulus file group = lib.get_node(self.json, j.GROUP) if group == "high": psychofile = j.HIFILE elif group == "low": psychofile = j.LOFILE lib.set_here(node, 'file', psychofile) # record which stimulus file was used # ready to launch! self.stimProc, h = lib.doStim(self.subject, self.TabID, self.run, stimLog, psychofile,self.json["study_info"]["group"]) lib.set_here(node,'disabled', True) # this button only launches. 'End Murfi' cleans up return
def subjectMoved(self, reason, moved="false"): print self.TabID, reason, moved ## timestamp and store comment infoNode = lib.get_node(self.json, ['protocol',self.TabID,'visit_info']) bt.timeStamp(infoNode) infoNode['comments'].append(reason) ## determine which steps need to be redone if moved == "true": ## QUESTION: should we be on an active visit only? ## enable & clear printed timestamp on localizers & test equipment bt.movementRedo(self.json, self.TabID) ## REMINDER: on functional runs, they should get to hit "end" (minor bug) elif moved == "false": pass ## just adding the comment to visit_info else: raise Exception("Invalid value for 'moved' from radio button!") return self.renderAndSave()
def dirStructure(subject): # subject (str): subject id for v, vNode in enumerate(VISIT_LIST): vType = get_node(vNode, VTYPE) vDir = getVisitDir(subject, v) if vType == "prepost": if os.path.exists(vDir): pass else: os.mkdir(vDir) elif vType == "realtime": rtDataDir = os.path.abspath(os.path.join(vDir, "data")) if not os.path.exists(rtDataDir): os.makedirs(rtDataDir) else: print "Didn't understand visit type", vType, "for visit number", v raise Exception("Subject's directory structure creation/verification failed.") return
def rtDone(j, bid): # Purpose: When an RT run completes, this advances the "progress" key # to the last part of the RT step, so that enableNext can enable the # next step or next visit. # This is a helper function for handling an "End Murfi" buttonpress. Nothing # else should call it. # Inputs: # j (dict) = full json the subject # bid (str) = "x.y.z", as above node = btn_node(bid, j) lib.set_here(node, 'done', True) # disable all sibling parts parent = parent_node(bid, j) for p in range(0, len(parent)): lib.set_here(parent[p], 'disabled', True) # progress is set to button ID of the last part of this step. lastID = lib.get_node(parent, ['-1', 'id']) setProgress(lastID, get_visit(bid, j))
def flotSetup(subject): flotcalls = [] actTempl = os.path.join(os.path.abspath("."), "template_active.json") refTempl = os.path.join(os.path.abspath("."), "template_reference.json") u = os.path.expanduser('~') for v, vNode in enumerate(VISIT_LIST): vType = get_node(vNode, VTYPE) vDir = getVisitDir(subject, v) if vType == "realtime": rtDataDir = os.path.abspath(os.path.join(vDir, "data")) for run in range(1, STUDY_INFO['runsPerRtVisit'] + 1): filebase = "run%03d_"%run actFile = os.path.join(rtDataDir, filebase + "active.json") refFile = os.path.join(rtDataDir, filebase + "reference.json") placeholder = '$("#rtgraph%d_%d")' % (v, run) flotcalls.append('flotplot("%s", "%s", %s);' % (os.path.relpath(actFile, u), os.path.relpath(refFile, u), placeholder)) if not os.path.exists(actFile): shutil.copy(actTempl, os.path.join(rtDataDir, actFile)) if not os.path.exists(refFile): shutil.copy(refTempl, os.path.join(rtDataDir, refFile)) return flotcalls
def checkVisitDir(subject,visit,group,myJson): print SUBJS print RTSCRIPTSDIR v = int(visit) maxV = len(VISIT_LIST) ## off-by-one error??? if v > maxV: ##obo danger print "ERROR checkVisitDir: requested",v, "but the max visit number is", maxV raise Exception("Invalid visit number requested.") if v < 0: ## supports visits[-1] indexing v = maxV + 1 + v myVisitDir = os.path.abspath(os.path.join(SUBJS,subject,'session%d'%v)) print myVisitDir if not os.path.exists(myVisitDir): if not os.path.exists(checkSubjDir(subject)): raise OSError("Can't find directory for this subject.") else: os.mkdir(myVisitDir) vType = get_node(myJson, "%s:%d:%s"%(FULLSTUDY, v, VTYPE)) if vType == 'realtime': os.mkdir(os.path.join(myVisitDir, 'data')) ## psychopy data directory. ### this is dumb. i should make it an importable library. print "Trying to create rt session for visit", visit subprocess.Popen(["python", "createRtSession.py", subject, str(v), 'none', group], cwd=RTSCRIPTSDIR) return myVisitDir
def subjectMoved(reason): print self.tab, reason infoNode = lib.get_node(self.json, ['Protocol',self.tab,'visit_info']) bt.timeStamp(infoNode) infoNode['comments'].append(reason) return self.renderAndSave()
def getTimestamp(node,index=-1): ## return time formatted like psychopy.data.getDateStr (but not unicode) return time.strftime("%Y_%b_%d_%H%M",time.localtime(float(lib.get_node(node,'history:%d'%index))))
def visitBids(tab, j): vNode = get_visit(int(tab), j) vBids = [] for s in range(0, len(vNode['steps'])): vBids.extend([str(p) for p in lib.get_node(vNode,"steps:%d:parts:*:id"%s)]) return vBids
def getProgress(visitNode): return lib.get_node(visitNode, jlib.VPROGRESS)