User-defined task #7 (Modify1)
This chapter explains how to add a new task (task 91) where the right or left panel is lit and touching the lit panel is rewarded while the touching of the non-lit panel is punished.
=================================================================================================
def PutTaskButton():
ButtonWidth = 12
Row=0
・・・
bTask50 = ttk.Button(MainWindowRightFrame, text='ProbRevers(50)', command=functools.partial(SwitchToSelectedTask, 50), width=ButtonWidth).grid(row=Row, column=Column)
Column += 1
bTask90 = ttk.Button(MainWindowRightFrame, text='Test(90)', command=functools.partial(SwitchToSelectedTask, 90), width=ButtonWidth).grid(row=Row,column=Column)
Column += 1
bTask91 = ttk.Button(MainWindowRightFrame, text='Test2(91)', command=functools.partial(SwitchToSelectedTask, 91), width=ButtonWidth).grid(row=Row,column=Column)
return
=================================================================================================
First, create a button for the task 91 in the task selection with the following code (the blue code is the additional part),
The task #91 is added.
=================================================================================================
def SwitchToSelectedTask(ChosenTask): # Initialize for the starting of the task
global Task, Phase, DispVariable
Task = ChosenTask
・・・
Task50()
if Task == 90:
Task90()
if Task == 91:
Task91()
return
=================================================================================================
Next, add a code which is executed when the new button is clicked,
Now it jumps to the task 91 function when the task 91 button is clicked. Next step is an addition of the task #91 function. Copy and paste the task 90 as abase of the task 91 as shown below.
=================================================================================================
def Task90(): # Test task
global Phase
Phase0_Init = 0
・・・
SetDispVariable(0, 'Phase2', str(Phase2)) # Display "Phase2" value on the bottom of main window
return
def Task91(): # Test2 task
global Phase
Phase0_Init = 0
・・・
SetDispVariable(0, 'Phase2', str(Phase2)) # Display "Phase2" value on the bottom of main window
return
def StartRecording(): # Start camera caputring
=================================================================================================
This task has additional 2 task parameters so add those parameters to the code and change the position of other task buttons accordingly.
=================================================================================================
MaxCorrectNumVar = IntVar(MainWindowRoot) # Declare a variable for input column
iMaxCorrectNum = ttk.Entry(MainWindowRightFrame, textvariable=MaxCorrectNumVar, width=ColumnWidth).grid(row=1, column=0) # Create input column andlink it with the variable
mMaxTrialNum = ttk.Label(MainWindowRightFrame, text='MaxTrialNum', width=ColumnWidth).grid(row=0, column=1, sticky=W)
MaxTrialNumVar = IntVar(MainWindowRoot) # Declare a variable for input column
iMaxTrialNum = ttk.Entry(MainWindowRightFrame, textvariable=MaxTrialNumVar, width=ColumnWidth).grid(row=1, column=1)
mTimeLimit = ttk.Label(MainWindowRightFrame, text='TimeLimit(min)', width=ColumnWidth).grid(row=0, column=2, sticky=W)
TimeLimitVar = IntVar(MainWindowRoot)
iTimeLimit = ttk.Entry(MainWindowRightFrame, textvariable=TimeLimitVar, width=ColumnWidth).grid(row=1, column=2)
mPunishDur = ttk.Label(MainWindowRightFrame, text='PunishDur(s)', width=ColumnWidth).grid(row=0, column=3, sticky=W)
PunishDurVar = IntVar(MainWindowRoot)
iPunishDur = ttk.Entry(MainWindowRightFrame, textvariable=PunishDurVar, width=ColumnWidth).grid(row=1, column=3)
mLickDur = ttk.Label(MainWindowRightFrame, text='LickDur(s)', width=ColumnWidth).grid(row=0, column=4, sticky=W)
LickDurVar = IntVar(MainWindowRoot)
iLickDur = ttk.Entry(MainWindowRightFrame, textvariable=LickDurVar, width=ColumnWidth).grid(row=1, column=4)
=================================================================================================
Now those 2 entries are implemented.
Add the load process for the new parameters.
=================================================================================================
else: # If save file doesn't exist
MaxCorrectNumVar.set(80) # Assign 80 into the variable
i
f os.path.exists(Str + '/MaxTrialNum.dat') == True:
with open(Str + '/MaxTrialNum.dat', 'rb') as PickleInst[GetTaskID()]:
MaxTrialNumVar.set(pickle.load(PickleInst[GetTaskID()]))
else:
MaxTrialNumVar.set(110)
if os.path.exists(Str + '/TimeLimit.dat') == True:
with open(Str + '/TimeLimit.dat', 'rb') as PickleInst[GetTaskID()]:
TimeLimitVar.set(pickle.load(PickleInst[GetTaskID()]))
else:
TimeLimitVar.set(600)
if os.path.exists(Str + '/PunishDur.dat') == True:
with open(Str + '/PunishDur.dat', 'rb') as PickleInst[GetTaskID()]:
PunishDurVar.set(pickle.load(PickleInst[GetTaskID()]))
else:
PunishDurVar.set(10)
if os.path.exists(Str + '/LickDur.dat') == True:
with open(Str + '/LickDur.dat', 'rb') as PickleInst[GetTaskID()]:
=================================================================================================
This task uses two panels so add a new ROI for the additional panel.
=================================================================================================
PutRoiGui(0, 1, 1, 0) # Put setting GUI of the indicated ROI on ROI window (ROI number, Detection mode, Threshold direction, ShowSymbol or not)
PutRoiGui(1, 1, 1, 0)
PutRoiGui(19, 0, 0, 1)
=================================================================================================
Add the save process for the new task parameters.
=================================================================================================
with open(Str+'/MaxCorrectNum.dat', 'wb') as PickleInst[GetTaskID()]:
pickle.dump(MaxCorrectNumVar.get(),PickleInst[GetTaskID()]) # Save a value of "MaxCorrectNumVar" as "MaxCorrectNum.dat" file
with open(Str+'/MaxTrialNum.dat', 'wb') as PickleInst[GetTaskID()]:
pickle.dump(MaxTrialNumVar.get(),PickleInst[GetTaskID()]) # Save a value of "MaxCorrectNumVar" as "MaxCorrectNum.dat" file
with open(Str+'/TimeLimit.dat', 'wb') as PickleInst[GetTaskID()]:
pickle.dump(TimeLimitVar.get(),PickleInst[GetTaskID()])
with open(Str+'/PunishDur.dat', 'wb') as PickleInst[GetTaskID()]:
pickle.dump(PunishDurVar.get(), PickleInst[GetTaskID()])
with open(Str+'/LickDur.dat', 'wb') as PickleInst[GetTaskID()]:
pickle.dump(LickDurVar.get(), PickleInst[GetTaskID()])
=================================================================================================
Change the message to show “Test2 task (91)”
=================================================================================================
if Phase == 1: # Waiting phase (Task will start when the set time arrives)
if Phase1_Init == 0: # If the initialization for phase1 has not done
PutPreTaskButton() # Put "StartNow" and "Back" button on Main window
mStatusVar = StringVar(MainWindowRoot) # Create a variable for status display
mStatus = ttk.Label(MainWindowRightFrame, textvariable=mStatusVar) # Create label object and link it with Main window
mStatus.place(x=10, y=0) # Place label object on the Main window
mOngoingResultVar = StringVar(MainWindowRoot) # Create a variable for progress display
Phase1_Init = 1 # Flat that phase1 has done
mStatusVar.set('Test2 task(' + str(GetTaskID()) + ') Waiting...') # Show current status of the Operant House
if IsStartTime() == 1: # Check whether task start time arrives
StartNow() # Start task (Phase number will be "2")
=================================================================================================
Do the same in
Phase == 2.
=================================================================================================
if Phase == 2: # If it is during task
if Phase2_Init == 0: # Initialization of the task
PutEndTaskNowButton() # Put "TaskEnd" button on Main window
mStatusVar.set('Test2 task(' + str(GetTaskID()) + ') Start time ' + str(TaskStartedMonth) + '/' + str(TaskStartedDay) + ' ' + str(TaskStartedHour)+ ':' + str(TaskStartedMinute) + ' Running') # Assign latest information about current task into "mStatusVar"
mOngoingResult = ttk.Label(MainWindowRightFrame, textvariable=mOngoingResultVar)
mOngoingResult.place(x=10, y=18)
=================================================================================================
Add new variables for the new task parameters.
=================================================================================================
# Assign task parameter values in StringVars into integer or string variable (to make the cord easeir to read)
MaxCorrectNum = int(MaxCorrectNumVar.get()) # Get the value of "MaxCorrectNumVar" and convert it from string to integer and assign intovariable named "MaxCorrectNum"
MaxTrialNum = int(MaxTrialNumVar.get())
TimeLimit = int(TimeLimitVar.get())
PunishDur = int(PunishDurVar.get())
LickDur = int(LickDurVar.get())
=================================================================================================
This task needs additional variables which hold total/incorrect trial numbers and an ID of the current correct panel.
=================================================================================================
# Declar local variables for this task
TrialNum = 0 # Current trial number
CorrectNum = 0 # Current correct trial number
IncorrectNum = 0 # Current incorrect trial number
CorrectRate = 0.0 # Current correct rate
TaskDur = 0 # This will keep the elapsed time during of task
NowDrinking = 0 # Use as trigger
CorrectPanelID = 0 # ID number of current correct panel (0 or 1)
StartRecording() # Start camera capture / TTL signal output
StartLickRecording() # Start an entry of lick log
LightCycleControlOff() # Deactivate automatic Light/Dark cycle illumination
RoofLightOff() # Turn off the lights on roof (Digital output Ch13)
InfraredLightOn() # Turn on the infrared LED illumination (Digital output Ch12)
DigitalOutOn(10) # Turn on cue LED connected to Ch10
ServoPosInside(3) # Change the angle of water arm servo connected to Ch3 to inside position
CreateNormalPanel(0) # Create a white-filled square panel as panel #0
=================================================================================================
Add a command to generate the additional panel.
=================================================================================================
CreateNormalPanel(1)
Writer_TouchEventTxt = open(Path + "/" + str(TimeNow.year) + "_" + str(TimeNow.month) + "_" + str(TimeNow.day) + " " + str(TimeNow.hour) + "h" + str(TimeNow.minute) + "m Task" + str(GetTaskID()) + " Touch.txt", 'w') # Initialize the text exporter for a result file
Writer_TouchEventTxt.write('TrialNum\tResult\t\t\tyyyy/mm/dd\th:m\ts\n') # Write item name on the result file
Writer_TouchEventCsv = open(Path + "/" + str(TimeNow.year) + "_" + str(TimeNow.month) + "_" + str(TimeNow.day) + " " + str(TimeNow.hour) + "h" + str(TimeNow.minute) + "m Task" + str(GetTaskID()) + " Touch.csv", 'w')
print("Task #" + str(GetTaskID()) + " is started at " + str(GetTaskStartedMonth()) + '/' + str(GetTaskStartedDay()) + ' ' + str(GetTaskStartedHour()) + ':' + str(GetTaskStartedMinute()) + ':' + str(GetTaskStartedSecond())) # Enter the start time in the console window of Pycharm
Timer_Start(5) # Start a timer #5 to measure the duration of the task
Phase2 = 2 # Start task from the reward phase
Phase2_Init = 1 # Flag indicating that initialization of Phase2 has done
if Phase2 == 0: # Initiation of new trial
TaskDur = Timer_GetSec(5)
=================================================================================================
=================================================================================================
CorrectPanelID = int(random.random()*2) # Determine which panel will be assigned as "correct"
print('CorrectPanelID: ' + str(CorrectPanelID))
ShowPanel(CorrectPanelID) # Display panel #0
Phase2 = 1
=================================================================================================
The correct and incorrect panel is assigned randomly. Hererandom.random()is used to generate “0” or “1” to determine which panel should be the correctpanel. To generate “0” or “1” with the same probability, userandom.random()to generate a random real number (? 0 and <1). Then multiply it by 2 and rounddown by converting to an integer. Show the randomly chosen panel withShowPanel().Then move toPhase2 == 1.
=================================================================================================
if Phase2 == 1: # Panel presentation
TouchedPanelID = DetectRoiNosepoke() # Examine which panel is touched (return panel ID. If none of the panels touched, return -1)
if TouchedPanelID != -1 and TouchedPanelID != 19: # If mouse touches the panel
TrialNum += 1
=================================================================================================
To count the number of touching (= trial number), increase the TrialNum when DetectRoiNosepoke() returns a value other than -1 and 19 (-1 is returnedwhen there is no ROI detecting. 19 is returned when the water slit ROI detects the animal).
If the ROI for the correct panel is activated, make it to the reward phase state and log the correct response and go to the reward phase (Phase2 = 2).
=================================================================================================
if TouchedPanelID == CorrectPanelID: # If touched panel is assigned as correct
ServoPosInside(3) # Move the water nozzle into inside position
DigitalOutOn(10) # Onset cue light
HidePanel(0) # Turn off panel #0
HidePanel(1) # Turn off panel #1
NowDrinking = 0
CorrectNum += 1 # Increase the number of correct response
Writer_TouchEventTxt.write(str(TrialNum)+'\tPanelTouched(Correct)\t'+ str(TimeNow.year)+"/"+str(TimeNow.month)+"/"+str(TimeNow.day)+"\t"+str(TimeNow.hour)+":"+str(TimeNow.minute)+"\t"+str(TimeNow.second)+"."+str(TimeNow.microsecond//1000)+"\n") # Write the response on the textfile
Writer_TouchEventCsv.write(str(TrialNum) + ',1,' + str(TimeNow.year) + "," + str(TimeNow.month) + "," + str(TimeNow.day) + "," + str(TimeNow.hour) + "," + str(TimeNow.minute) + "," + str(TimeNow.second) + "." + str(TimeNow.microsecond//1000)+"\n") # Write the response on the csvfile
Phase2 = 2 # Start reward phase
=================================================================================================
The next code is for the case of an incorrect response.
=================================================================================================
if TouchedPanelID != CorrectPanelID: # If touched panel is assigned as incorrect
ServoPosOutside(3) # Move the water nozzle into inside position
HidePanel(0)
HidePanel(1)
RoofLightOn() # Turn on the ceiling illumination
IncorrectNum += 1 # Increase the number of correct response
Writer_TouchEventTxt.write(str(TrialNum)+'\tPanelTouched(Incorrect)\t'+ str(TimeNow.year)+"/"+str(TimeNow.month)+"/"+str(TimeNow.day)+"\t"+str(TimeNow.hour)+":"+str(TimeNow.minute)+"\t"+str(TimeNow.second)+"."+str(TimeNow.microsecond//1000)+"\n") # Write the response onthe text file
Writer_TouchEventCsv.write(str(TrialNum) + ',2,' + str(TimeNow.year) + "," + str(TimeNow.month) + "," + str(TimeNow.day) + "," + str(TimeNow.hour) + "," + str(TimeNow.minute) + "," + str(TimeNow.second) + "." + str(TimeNow.microsecond//1000)+"\n") # Write the response on the csvfile
Timer_Start(0) # Start punishment timer
Phase2 = 3 # Start punish phase
=================================================================================================
For the punishment, the ceiling illumination are turned on. The incorrect number is increased and this event is logged to the result text file. Start timer #0withTimer_Start(0)to apply the punishment for a specified amount of time. Then move to the punishment phase (Phase2==3).
=================================================================================================
mOngoingResultVar.set('CorrectNum: ' + str(CorrectNum) + ' IncorrectNum: ' + str(IncorrectNum) + ' TrialNum: ' + str(TrialNum))
if Timer_GetSec(5) >= TimeLimit * 60: # If time limit of the task comes
TaskDur = Timer_GetSec(5)
print("Time limit elapsed")
=================================================================================================
Show the correct, incorrect and total number of the trials as intermediate results in the main window using mOngoingResultVar.set().
=================================================================================================
if Phase2 == 2: # Reward phase
if DetectRoiNosepoke() == 19 and NowDrinking == 0: # If the mouse initiates nose poking
NowDrinking = 1
Timer_Start(0) # Start lick timer
if NowDrinking == 1: # If the nose poke has begun
if Timer_GetSec(0) >= LickDur: # If the nosepoke duration exceeds the lick duration
Timer_End(0) # End timer for measuring lick duration
ServoPosMiddle(3) # Move water nozzle back to the middle position
DigitalOutOff(10) # Turn off the cue LED
NowDrinking = 0
Phase2 = 4 # Go to After trial phase
=================================================================================================
After the reward or punishment phase, an "after trial phase" is added to determine whether to continue the session or not, so add a line to go to the aftertrial phase (Phase2 == 4) in the last of reward phase process.
=================================================================================================
if Phase2 == 3: # Punishment phase
if Timer_GetSec(0) >= PunishDur: # If punishment time is passed
Timer_End(0) # End punishment timer1
RoofLightOff()
ServoPosMiddle(3) # Move water nozzle to the intermediate position
Phase2 = 4 # Go to After trial phase
=================================================================================================
Add a process of the punishment phase that keeps checking the elapse of the punishment time usingTimer_GetSec(0)and moves to the "after trial phase"when the time has elapsed.
=================================================================================================
if Phase2 == 4: # After trial phase
if CorrectNum < MaxCorrectNum and TrialNum < MaxTrialNum: # If the touch number doesn't exceed the maximum number
Phase2 = 0
if CorrectNum >= MaxCorrectNum or TrialNum >= MaxTrialNum: # If the touch number exceeds the maximum number
TaskDur = Timer_GetSec(5) # Keep the task time
Phase2 = -1 # Go to the task finalizing phase
=================================================================================================
Add the "after trial phase" after the punishment phase.This code checks whether the correct trials or total trials have reached the limits. If those limits have not been reached, it goes back to the initialization of the next trial. If the limit has reached, assigns the task lapsed time to the
TaskDur and goes to the task end phase (
Phase2 = -1).
=================================================================================================
if Phase2 == -1 or GetEndTaskNowButtonStat() == 1: # If the flag is set to finish the task
LightCycleControlOn() # Activate automatic light/dark cycle
ServoPosMiddle(3) # Moze servo nozzle into middle position
DeleteAllPanel() # Remove a panel on touch screen
InfraredLightOff() # Turn off the infrared LED
if GetRecordingStat() == 1: # If camera is capturing
SetEndRecordingTimer(60) # Onset a timer to finish video recording after 60 frames (correspond about 2sec) from now
# Add summary of results into the result file
if CorrectNum > 0 or IncorrectNum > 0:
CorrectRate = int(CorrectNum * 100.0 / TrialNum)
Writer_TouchEventTxt.write('TotalNum:' + str(TrialNum) + ' CorrectNum:' + str(CorrectNum) + ' IncorrectNum:' + str(IncorrectNum) + ' CorrectRate:' + str(CorrectRate) + '%\n')
Writer_TouchEventTxt.write('SessionStartTime: ' + str(GetTaskStartedMonth()) + '/' + str(GetTaskStartedDay()) + ' ' + str(GetTaskStartedHour()) +':' + str(GetTaskStartedMinute()) + ':' + str(GetTaskStartedSecond()) + "\n")
Writer_TouchEventTxt.write('SessionEndTime: ' + str(TimeNow.month) + '/' + str(TimeNow.day) + ' ' + str(TimeNow.hour) + ':' + str(TimeNow.minute) + ':' + str(TimeNow.second + (TimeNow.microsecond // 1000) / 1000) + "\n")
Writer_TouchEventTxt.write('TaskDuration(sec): ' + str(TaskDur) + "\n")
# Add experimental conditions into the result file
Writer_TouchEventTxt.write('MaxCorrectNum: '+str(MaxCorrectNum)+"\n")
Writer_TouchEventTxt.write('MaxTrialNum: ' + str(MaxTrialNum) + "\n")
Writer_TouchEventTxt.write('TimeLimit: ' + str(TimeLimit) + "\n")
Writer_TouchEventTxt.write('PunishDur: ' + str(TimeLimit) + "\n")
Writer_TouchEventTxt.write('LickDur: ' + str(LickDur)+"\n")
Writer_TouchEventTxt.write('RecordFPS: ' + str(GetRecordFps()) + "\n") # Recorded frame number per second
Writer_TouchEventTxt.write(GetRoiSensitivity() + "\n") # Settings of each ROI
Writer_TouchEventTxt.write(GetServoAngle() + "\n") # Set angles of each servo
Writer_TouchEventTxt.close() # Close the text exporter for the result file
Writer_TouchEventCsv.close()
EndLickRecording() # End lick log recording
=================================================================================================
Modify the session termination process. Because there is a right and wrong response, addIncorrectNumandTrialNum.Also calculate the correct rate andrecord it to the result text. Add lines to record new task parameters (MaxTrialNumandPunishDur).
=================================================================================================
SendMail(DeviceNameVar.get()+' finished task '+str(GetTaskID())+'. TrialNum:'+str(TrialNum)+' Correct:'+str(CorrectNum)+' Incorrect:'+str(IncorrectNum)+' Rate:'+str(CorrectRate)+'% Dur:'+str(round(Timer_GetSec(5) / 60,1))+' min','The task is finished.') # Send a email (correct number and taskduration are added to email title)
Phase = 1 # Go back to the task-waiting phase
Phase2 = 0
Phase2_Init = 0
=================================================================================================
Finally include the new result parameters to the title of the email.
Task 91 is now complete. Please try it to see if it runs properly.
If you would like to see how Task 90 and 91 have changed, you can open Task 90 and 91 in the "Tutorial" folder in the OperantHouse program with WinMerge,which highlights differences between two source codes.