Kitaya lab

オリジナル課題の追加7(改良)



これから作る課題91では一番左、もしくは右のパネルのどちらかが点灯し、点灯したパネルをタッチすると報酬が、点灯していないパネルをタッチすると罰が与えられる課題です。中心のマスクホールは今回は使用しません。





新しい課題を91番として作成して行きます。


=======================================================================================================================
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
=======================================================================================================================
まずは課題選択画面での課題91のボタンを作成します。


ボタンが追加されました。



=======================================================================================================================
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
=======================================================================================================================
ボタンを押したら実行される関数に課題91を追加します。




これでボタンを押したらTask91()に入るようになりました。
次に課題91本体のコードを書いていきましょう。課題90をベースにするので課題90のコードを丸ごとコピーしてその下に課題91として貼り付けてください。
=======================================================================================================================
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
=======================================================================================================================


=======================================================================================================================
               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 and link 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)
=======================================================================================================================
課題のパラメータを追加します。またそれに合わせて既存のボタンの位置もrowとcolumnの値を変えてずらします。


これで以下の欄が追加されました。



=======================================================================================================================
               else:  # If save file doesn't exist
                   MaxCorrectNumVar.set(80)  # Substitute 80 into the variable

               if 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()]:
=======================================================================================================================
追加した課題パラメータのロード処理も追加します。


パネルが1つ増えたのでそのパネルへのタッチを検出するROIを追加します。
=======================================================================================================================
               PutRoiGui(0, 1, 1, 0)  # Put setting GUI of the indicated ROI on ROI window (ROI number, Detection mode, Thresholddirection, ShowSymbol or not)
               PutRoiGui(1, 1, 1, 0)
               PutRoiGui(19, 0, 0, 1)
=======================================================================================================================


=======================================================================================================================
           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()])
=======================================================================================================================
追加課題パラメータのセーブ処理も追加します。


=======================================================================================================================
       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 Mainwindow
               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")
=======================================================================================================================
課題名はTest2なのでフェーズ1のメッセージを変更します。





ここから課題本体のコードがあるフェーズ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')    # Substitute latest information about current task into"mStatusVar"
               mOngoingResult = ttk.Label(MainWindowRightFrame, textvariable=mOngoingResultVar)
               mOngoingResult.place(x=10, y=18)
=======================================================================================================================
まずフェーズ2のメッセージを変えます。


=======================================================================================================================
               # Substitute 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 stringto integer and substitute into variable named "MaxCorrectNum"
               MaxTrialNum = int(MaxTrialNumVar.get())
               TimeLimit = int(TimeLimitVar.get())
               PunishDur = int(PunishDurVar.get())
               LickDur = int(LickDurVar.get())
=======================================================================================================================
最大試行数や罰時間の課題パラメータが増えたので対応する変数を追加します。


=======================================================================================================================
               # 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
=======================================================================================================================
さらに現在の試行数や正解数、不正解数、正解パネルのIDを保持するための変数も必要なため、それぞれTrilalNum, IncorrectNum,CorrectRate, CorrectPanelIDとして追加します。


=======================================================================================================================
               CreateNormalPanel(1)
=======================================================================================================================
パネルは2つ必要なのでパネルオブジェクト#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 textexporter 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 theconsole 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
=======================================================================================================================
この課題では試行の冒頭でパネル#0と#1のどちらのパネルが正解になるか、ランダムで決めます。ここでは乱数を発生させる関数"random.random()"を用います。
random.random()を実行すると0以上1未満の実数が返されるのでそれに2を掛けて小数点以下を切り捨てると、半々の確率で整数の0もしくは1になります。
この整数を正解パネルの番号としてCorrectPanelIDへ代入します。
最後にShowPanel()関数で正解パネルを表示させます。
そしてタッチ待機フェーズであるPhase2=1へ移行します。


=======================================================================================================================
           if Phase2 == 1:   # Panel presentation
               TouchedPanelID = DetectRoiNosepoke()  # Examine which panel is touched (return panel ID. If none of the panelstouched, return -1)
               if TouchedPanelID != -1 and TouchedPanelID != 19: # If mouse touches the panel
                   TrialNum += 1
=======================================================================================================================
左右どちらかのパネルがタッチされると-1以外の値をDetectRoiNosepoke() が返すので現在の試行数を1増やします。


=======================================================================================================================
                   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 text file
                       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 csv file

                       Phase2 = 2  # Start reward phase
=======================================================================================================================
もしタッチされたパネルが正解パネルであれば報酬フェーズの状態にし、正解タッチをログに記録し、Phase2=2として報酬フェーズへ移行します。




次に不正解パネルをタッチした場合の処理を追加します。


=======================================================================================================================
                   if TouchedPanelID != CorrectPanelID:    # If touched panel is assigned as incorrect
                       ServoPosOutside(3)    # Move the water nozzle into inside position
=======================================================================================================================
不正解時に正解時と同じノイズを出すため水ノズルを外側に動かします(ノイズが正解Cueになると他の装置のノイズが結果に干渉するため)。


=======================================================================================================================
                       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 on the 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 csv file
=======================================================================================================================
罰フェーズのために天井の照明をオンにし、不正解数を増やし、不正解イベントをログへ記入します。


=======================================================================================================================
                       Timer_Start(0)  # Start punishment timer
=======================================================================================================================
罰時間は指定した時間だけ持続する必要があるのでタイマー#0を使って、罰が開始してからの時間を測ります。


=======================================================================================================================
                       Phase2 = 3  # Start punish phase
=======================================================================================================================
罰フェーズであるフェーズ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")
=======================================================================================================================
この課題では正解数、不正解数、試行数が見れたら便利なので、メインウィンドウに表示される途中結果の表記を変更します。






=======================================================================================================================
           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 trial phaseを追加するので、報酬フェーズ終了後はAfter trial phaseへ移行させます


=======================================================================================================================
           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
=======================================================================================================================
罰フェーズとしてただ経過時間をチェックし続け、時間が来たらAfter trial phaseへ移行するフェーズを作ります。
タイマー#0の時間を毎フレームチェックして、もし課題パラメータとしてセットした罰時間を超えたら罰を終了し、After trial phaseへ移行するコードを追加します。




次に正解数や試行数が上限に達したかをチェックするAfter trial phaseを追加します。


=======================================================================================================================
          if Phase2 == 4:  # After trial phase
               if CorrectNum < MaxCorrectNum and TrialNum < MaxTrialNum:  # If the touch number doesn't exceed the maximumnumber
                   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
=======================================================================================================================
もし達した場合、セッションの経過時間をTaskDurへ保持し、セッション終了処理へ移行するコードを記入します。




最後にセッション終了処理を改変します。


=======================================================================================================================
           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
=======================================================================================================================
この課題では不正解があるので新たにIncorrectNumTotalNum, CorrectRateを計算して結果ファイルに記入します。またMaxTrialNumPunishDurは新しい課題パラメータなのでこれらも記録します。




=======================================================================================================================
               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','Thetask is finished.') # Send a email (correct number and task duration are added to email title)
=======================================================================================================================
メールのタイトルにIncorrectNum,TotalNum, CorrectRateを追加します。


=======================================================================================================================
               Phase = 1  # Go back to the task-waiting phase
               Phase2 = 0
               Phase2_Init = 0
=======================================================================================================================




これで課題91は完成しました。ちゃんと実行されるか試してみて下さい。


なお課題90と91でどこがどのように変わったのか知りたい場合はソースコードの差分をハイライトしてくれるWinMergeというソフトがあるのでそれでOperantHouse\TutorialにあるTask90と91を比較してみて下さい。