Kitaya lab

オリジナル課題の追加6(Phase2)



Phase2からようやく課題の動作のコードになります。
Phase2もまずは初期化から始まります。

=======================================================================================================================
           if Phase2_Init == 0: # Initialization of the task
               PutEndTaskNowButton()   # Put "TaskEnd" button on Main window
               mStatusVar.set('Test 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)

               # 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 string tointeger and substitute into variable named "MaxCorrectNum"
               TimeLimit = int(TimeLimitVar.get())
               LickDur = int(LickDurVar.get())

               # Declar local variables for this task
               CorrectNum = 0
               TaskDur = 0  # This will keep the elapsed time during of task
               NowDrinking = 0

               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

               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
=======================================================================================================================

PutEndTaskNowButton()で"TaskEnd"ボタンをメインウィンドウに設置し、mStatusVarの内容を更新します。
次に課題の途中経過を示すラベルであるmOngoingResultをメインウィンドウに設置します。

=======================================================================================================================
               MaxCorrectNum=int(MaxCorrectNumVar.get())   # Get the value of "MaxCorrectNumVar" and convert it from string tointeger and substitute into variable named "MaxCorrectNum"
               TimeLimit = int(TimeLimitVar.get())
               LickDur = int(LickDurVar.get())
=======================================================================================================================

下のコードはIntVar変数のままだとコードが見づらくなるので通常のInt型の変数に課題パラメータの値を代入しています。

=======================================================================================================================
               CorrectNum = 0
               TaskDur = 0  # This will keep the elapsed time during of task
               NowDrinking = 0
=======================================================================================================================

課題で使う変数を宣言します。




次はオペラントハウスの初期動作を指示します(マウスを活性化させるため課題は報酬フェーズから始まります)。


StartRecording()
カメラによる録画開始

StartLickRecording()
水場ROIの検出ログの記録開始

LightCycleControlOff()
RoofLightOff()
天井照明の明暗周期に従った点灯/消灯機能をオフ (この時間はメインウィンドウの"Setting"で変えられます)にして天井照明をオフ

InfraredLightOn()
赤外線照明オン

DigitalOutOn(10)
デジタル入出力チャネル(10番)をオン→Cue LED点灯

ServoPosInside(3)
水ノズルをチャンバー内へ移動

CreateNormalPanel(0)
パネル0番を生成


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 fora result file
次に結果ファイルであるテキストファイルを作成します。ファイルパスとファイル名をこのように指定します。'w'は記入モードと言う意味です。


Writer_TouchEventTxt.write('TrialNum\tResult\t\t\tyyyy/mm/dd\th:m\ts\n')  # Write item name on the result file
結果ファイルに項目名を記入します。


テキストファイルと同じ要領でCSVファイルも作成します(CSVファイルとはカンマで区分けしたテキストファイルの事です)。


次にTimerの5番をTimer_Start(5)でスタートさせます。このタイマーは課題の時間制限で使用します。
なおここで使ったタイマーですが、オペラントハウスには8つのタイマー(ID:0-7)が用意されておりTimer_Start(ID)でスタート、Timer_GetSec(ID)で経過を秒で取得、Timer_End(ID)でタイマーを終了させられます。


最後にPhase2を報酬フェーズである2にして初期化を終わります。
Phase2はオペラント課題のフェーズの遷移状況を保持する変数です。
値が0だとトライアルの初期化フェーズ、1だとパネルのタッチフェーズ、2だと報酬フェーズ、-1だと課題終了フェーズになります。




if Phase2 == 0:   # Initiation of new trial
  TaskDur = Timer_GetSec(5)
  ShowPanel(0)    # Display panel #0
  Phase2 = 1

ここでは単にShowPanel(0)で0番パネルを表示するだけです。




また課題の経過時間もチェックし、制限時間に達したら課題終了フェーズへ移行します。

=======================================================================================================================
           if Phase2 == 1:   # Panel presentation
               TouchedPanelID = DetectRoiNosepoke()  # Examine which panel is touched (return panel ID. If none of the panelstouched, return -1)
               if TouchedPanelID == 0: # If mouse touches the panel
                   ServoPosInside(3)    # Move the water nozzle into inside position
                   DigitalOutOn(10)    # Onset cue light
                   HidePanel(0)
                   NowDrinking = 0
                   CorrectNum += 1     # Increase the number of correct response
                   mOngoingResultVar.set('CorrectNum:' + str(CorrectNum))
                   Writer_TouchEventTxt.write(str(CorrectNum)+'\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(CorrectNum) + ',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 == 1ではマウスがパネルをタッチするまで待ち続け、タッチをしたら報酬フェーズへ移行します。
パネルタッチはDetectRoiNosepoke()毎フレームチェックされ、タッチされた場合タッチ番号が(今回は0)、そうでなければ-1が返されます。
タッチされた場合、報酬フェーズへ移行するため以下の処理が実行されます。

ServoPosInside(3)
水ノズルをチャンバーの内側へ移動

DigitalOutOn(10)
Cue LEDをオン

HidePanel(0)
パネルを非表示

mOngoingResultVar.set('CorrectNum:' + str(CorrectNum))
途中経過ラベルを更新

Writer_TouchEventTxt.write(str(CorrectNum)+'\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(CorrectNum) + ',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
報酬フェーズへ移行




=======================================================================================================================
               if Timer_GetSec(5) >= TimeLimit * 60:   # If time limit of the task comes
                   TaskDur = Timer_GetSec(5)
                   print("Time limit elapsed")
                   Timer_End(5)    # Stop the timer
                   Phase2 = -1
=======================================================================================================================

また同時にセッション時間が上限に達したかもif Timer_GetSec(5) >= TimeLimit * 60:でチェックされ、達した場合、課題終了フェーズ(Phase2 == -1)へ移行します。




=======================================================================================================================
           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
                       if CorrectNum < MaxCorrectNum:  # If the touch number doesn't exceed the maximum number
                           Phase2 = 0
                       if CorrectNum >= MaxCorrectNum:   # If the touch number exceeds the maximum number
                           TaskDur = Timer_GetSec(5)  # Keep the task time
                           Phase2 = -1 # Go to the task finalizing phase
=======================================================================================================================

報酬フェーズ(Phase2 == 2)ではマウスが水場ROIへノーズポークした後2秒後に水ノズルをチャンバー外に出し、正解数が上限に達したかをチェック後、初期化フェーズまたは課題終了フェーズへ移行します。
水場ROIの番号は19なのでDetectRoiNosepoke() == 19で水へのアクセスの開始を検出します。アクセスを始めたらそこから一定時間後に水ノズルを外に出すのでTimer_Start(0)で時間計測を開始します。
NowDrinking == 1では水へのアクセス時間が課題パラメータの飲水時間(LickDur)を超えたかチェックし続け、もし超えた場合、水ノズルをニュートラルポジションに戻します。
その後CorrectNum(現在の正解数)とMaxCorrectNum(最大正解数)の比較を行い、まだ上限に達していなければ初期化フェーズへ、達していれば経過時間を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
               Writer_TouchEventTxt.write('CorrectNum:'+str(CorrectNum)+"\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('TimeLimit: ' + 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

               SendMail(DeviceNameVar.get()+' finished task '+str(GetTaskID())+'. Correct:'+str(CorrectNum)+' Dur:'+str(round(Timer_GetSec(5) / 60,1))+' min','The task is finished.') # Send a email (correct number and task duration are added to email title)

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

課題終了フェーズ(Phase2 == -1)ではオペラントハウスを待機状態へ戻し、実験条件を結果ファイルへ書き出し、メールを送信します。
※末尾の\nは「改行」を表します。

録画の終了は念のため現在録画しているかをGetRecordingStat()でチェックし(録画中では1,そうでなければ0を返す)、録画していればSetEndRecordingTimer(60)で録画の終了を指示します。
SetEndRecordingTimer(60)を実行すると60フレーム後に録画が自動的に終了します。このように遅延を入れる事で課題終了後ノズルがちゃんとチャンバーの外に出たかを後で動画でチェックできます。


次の行からはテキストで以下のように結果のまとめを出力する処理です。

正解数、課題開始時刻、課題終了時刻、課題時間が記入されます。


その次が課題条件です。

最大正解数、課題の制限時間、1回の報酬当たりの飲水時間、録画FPS(1秒間に何フレーム記録したか。この値はメインウィンドウの"Setting2"ボタンで変えられます)、各ROIの設定値、給水アームのサーボの設定値


出力するテキストファイルは最後にこのようにクローズする必要があります。
Writer_TouchEventTxt.close()


そして最後にメールを送ります。
引数にタイトルと本文を入れる事が出来ます(タイトルに結果のまとめ情報を入れておくと便利です)。
SendMail(タイトル, 本文)



また最後にある1行

SetDispVariable(0, 'Phase2', str(Phase2))

ですが、これはプログラムの変数を値をメインウィンドウの右下に表示するために使うコマンドです。


例を出した方が分かり易いと思うので例を出しますと、例えば引数を

SetDispVariable(0, '色', 赤)
SetDispVariable(2, '手触り', ざらざら)
SetDispVariable(1, '大きさ', 1mくらい)

とするとメインウィンドウでは

色:赤 大きさ:1mくらい 手触り:ざらざら

と表示されます。一番左の引数は表示する順番で0-9までの整数を入れてください。




以上がこの課題のコードの説明になります。
恐らくこれをパッと読んだだけでは頭に入ってこないと思いますので是非自身でコードを色々と変更し、装置の挙動がどう変わるかを確かめてみて下さい。


また次章ではもう1段階複雑な課題をこのコードを改良して作成する解説を行います。