#include "BaseProcess.h"
#include "Worker.h"
#include "ECTrainer.h"
#include "EyeTrack.h"
#include "Stimulus.h"
#include "BitalMonitor.h"
#include "Marker.h"
#include "MovieObject.h"
#include "TobiiREST.h"
#include "ECTrainerGUI.h"
#include "nkcWinUtils.h"
#include "HPTimer.h"
// コンストラクタ
Worker::Worker(ECTrainer* pEct) : BaseProcess(pEct)
, _AppStatus(APP_STATUS::BOOT)
, _pExpTimer(NULL)
, _pContactTimer(NULL)
, _TargetImage()
, _FullScreenImage()
, _FBLevel(1)
, _TrainingLevel(1)
, _Shift(0, 0)
, _StartStage(0)
, _EnableTrainLevel(false)
{
_pExpTimer = new nkc::HPTimer();
_pContactTimer = new nkc::HPTimer();
_Trigger = ::CreateEvent(NULL, FALSE, FALSE, NULL);
}
// デストラクタ
Worker::~Worker() {
nkc::wut::SafeDelete((void**)&_pExpTimer);
nkc::wut::SafeDelete((void**)&_pContactTimer);
::CloseHandle(_Trigger);
}
// 初期化
bool Worker::Init() {
_AppStatus = APP_STATUS::IDLE;
return true;
}
// 基本処理
bool Worker::Routine() {
// 視線情報更新を待つ
if (::WaitForSingleObject(_Trigger, 1) == WAIT_TIMEOUT) return false;
cv::Point2f gazeV = (Ect()->PEyeTrack()->GetGazeV());
int stimNo = Ect()->PStimulus()->GetStimNo();
// 注視点を画像座標に変換
auto gazeI = gazeV.x < 0 ? cv::Point2f(-1, -1) : Ect()->PMarker()->ConvV2I(gazeV) - _Shift;
// 表示画像取得
if (_StimImage.empty() || Ect()->PStimulus()->IsNewDisplay()) {
_StimImage = Ect()->PStimulus()->GetDisplay();
if (_StimImage.cols != Stimulus::SMALL_MOVIE_WIDTH) _FullScreenImage.Put(_StimImage);
}
// ターゲット画像生成
int hit = -1;
double dtMin = 0;
int fb = 0;
double frameInterval = _pContactTimer->Interval();
std::vector<Element> elems = Ect()->PStimulus()->GetMovieObject();
if (stimNo >= 0) {
cv::Mat stimImg = _StimImage.clone();
float scale = (float)stimImg.cols / Ect()->PMarker()->IMGSIZE.width;
if (elems.size() > 0) {
for (int e = 0; e < elems.size(); e++) {
// ターゲット描画
auto target = cv::Point2f(elems[e].x, elems[e].y);
if (elems[e].d > 0) {
float dMod = elems[e].d * (1.0F - TRAINING_LEVEL_EFFECT * (_TrainingLevel - 1));
cv::circle(stimImg, target * scale, (int)(dMod * scale), CV_RGB(255, 0, 0), 2);
if (gazeI.x >= 0) {
// ターゲット判定
auto dt = cv::norm(gazeI - target);
if (dt < dMod && (hit < 0 || dt < dtMin)) {
hit = e;
dtMin = dt;
}
}
}
}
}
if (gazeI.x >= 0) {
// 画像上の注視点描画
cv::circle(stimImg, gazeI * scale, (int)(20 * scale), CV_RGB(0, 0, 255), 3);
if (Ect()->PStimulus()->IsSoftCalib()) {
// ソフトウェアキャリブレーション
if (Ect()->PStimulus()->GetStimTime() >= SHIFT_LOG_STARTTIME && elems.size() > 0) {
auto shift = gazeI - cv::Point2f(elems[0].x, elems[0].y);
_ShiftLog.push_back(shift );
//nkc::wut::DebugPrintf(_T("Shift %.1f,%.1f\n"), shift.x, shift.y);
}
} else {
if (hit >= 0) {
_ContactTime += frameInterval;
if (_ContactTime >= (double)(FEEDBACK_TIME * _FBLevel)) {
this->PostMsg((int)ECTMSG::FB_OK + _FBLevel - 1);
fb = _FBLevel++;
if (_FBLevel > 5) {
this->ResetParams(); // 最終FB後にリセット
if (*Ect()->PWorker()->EnableTrainLevelPtr()) _TrainingLevel++;
}
}
}
}
}
_TargetImage.Put(stimImg);
// ずれ解析
if (!Ect()->PStimulus()->IsSoftCalib() && _ShiftLog.size() > 0) {
cv::Point2f sum(0,0);
for (auto& x : _ShiftLog) sum += x;
_Shift.x = sum.x / _ShiftLog.size();
_Shift.y = sum.y / _ShiftLog.size();
nkc::wut::DebugPrintf(_T("Mean Shift %.1f,%.1f\n"), _Shift.x, _Shift.y);
_ShiftLog.clear();
}
} else {
_TargetImage.Put(_StimImage);
}
// ログ出力
if (stimNo >= 0) {
Record r;
r.ElapTime = _pExpTimer->Elapse() / 1000.; // 経過時間
r.StimNo = stimNo; // 刺激データ番号
r.SceneNo = stimNo < 1 ? 0 : Ect()->PStimulus()->GetScene(stimNo - 1); // シーン番号
r.SceneTime = Ect()->PStimulus()->GetStimTime() / 1000.; // 刺激提示の経過時間
r.GazeV = gazeV; // 注視点(視野カメラ座標)
r.Shift = _ShiftLog.size() > 0 && Ect()->PStimulus()->GetStimTime() >= SHIFT_LOG_STARTTIME
&& gazeI.x >= 0 ? _ShiftLog.back() : _Shift; // ずれ
r.GazeI = gazeI; // 注視点(画像座標)
r.Target = hit + 1; // ターゲット判定
r.ContactTime = _ContactTime / 1000.; // 目標コンタクト時間
r.Feedback = fb; // フィードバック
r.TrainingLevel = _TrainingLevel; // トレーニングレベル
r.RR = Ect()->PBitalMonitor()->GetRR(); // バイタル出力(RR間隔)
auto pd = Ect()->PEyeTrack()->GetPupilDiam(); // 瞳孔径
r.PupilL = pd.l;
r.PupilR = pd.r;
r.H = Ect()->PMarker()->GetHomography(); // ホモグラフィ行列
_DataLog.WriteRecord(r);
}
return true;
}
// 判定パラメータをリセット
void Worker::ResetParams() {
_pContactTimer->Reset();
_FBLevel = 1;
_ContactTime = 0;
}
// イベント処理
bool Worker::EventProc(MSG& msg) {
switch (msg.message) {
case (int)ECTMSG::SOFTWARE_START: // ソフトウェア起動
break;
case (int)ECTMSG::SOFTWARE_END: // ソフトウェア終了
this->EventLog(_T("Software End"));
Ect()->StopApp();
break;
case (int)ECTMSG::CALIB_START: // キャリブレーション開始
if (_AppStatus == APP_STATUS::IDLE) {
_AppStatus = APP_STATUS::CALIB;
Ect()->PStimulus()->PostMsg((int)ECTMSG::CALIB_START);
Ect()->PTobiiREST()->PostMsg((int)ECTMSG::CALIB_START);
this->EventLog(_T("Calibration Start"));
}
break;
case (int)ECTMSG::CALIB_OK: // キャリブレーション成功
// _AppStatus = APP_STATUS::IDLE;
Ect()->PStimulus()->PostMsg((int)ECTMSG::SOFTCALIB_START);
this->EventLog(_T("Soft Calibration Start"));
break;
case (int)ECTMSG::CALIB_FAILED: // キャリブレーション失敗
case (int)ECTMSG::CALIB_ERR: // キャリブレーションエラー
_AppStatus = APP_STATUS::IDLE;
Ect()->PStimulus()->PostMsg((int)ECTMSG::CALIB_FAILED);
this->EventLog(_T("Calibration Failed"));
break;
case (int)ECTMSG::SOFTCALIB_OK: // キャリブレーション成功
_AppStatus = APP_STATUS::IDLE;
Ect()->PStimulus()->PostMsg((int)ECTMSG::SOFTCALIB_OK);
this->EventLog(_T("Calibration OK"));
break;
case (int)ECTMSG::EXP_START: // 実験開始
if (_AppStatus == APP_STATUS::IDLE) {
_DataLog.StartRecord(Ect()->Subject(), Ect()->Visit());
this->EventLog(_T("Experiment Start"));
_pExpTimer->Reset();
this->ResetParams();
Ect()->PStimulus()->PostMsg((int)ECTMSG::EXP_START);
_AppStatus = APP_STATUS::STIM;
}
break;
case (int)ECTMSG::EXP_STOP: // 実験停止
if (_AppStatus == APP_STATUS::STIM) {
_AppStatus = APP_STATUS::IDLE;
Ect()->PStimulus()->PostMsg((int)ECTMSG::EXP_STOP);
this->EventLog(_T("Experiment Stopped"));
}
break;
case (int)ECTMSG::EXP_END: // 実験終了
if (_AppStatus == APP_STATUS::STIM) {
_AppStatus = APP_STATUS::IDLE;
Ect()->PStimulus()->PostMsg((int)ECTMSG::EXP_END);
this->EventLog(_T("Experiment Finished"));
}
break;
case (int)ECTMSG::EXP_NEXT: // 次の刺激に移動
this->EventLog(_T("Next stimulation"));
this->ResetParams();
break;
case (int)ECTMSG::SYSTEM_ERROR: // システムエラー
this->EventLog(_T("System Error"));
_AppStatus = APP_STATUS::IDLE;
Ect()->PStimulus()->PostMsg((int)ECTMSG::SYSTEM_ERROR);
Ect()->PECTrainerGUI()->PostMsg((int)ECTMSG::MOVIE_STOP);
break;
case (int)ECTMSG::FB_OK: // フィードバック:OK
PlaySound(SOUND_OK, NULL, SND_FILENAME | SND_ASYNC);
//this->EventLog(_T("Feedback OK"));
break;
case (int)ECTMSG::FB_GOOD: // フィードバック:Good
PlaySound(SOUND_GOOD, NULL, SND_FILENAME | SND_ASYNC);
//this->EventLog(_T("Feedback Good"));
break;
case (int)ECTMSG::FB_NICE: // フィードバック:Nice
PlaySound(SOUND_NICE, NULL, SND_FILENAME | SND_ASYNC);
//this->EventLog(_T("Feedback Nice"));
break;
case (int)ECTMSG::FB_GREAT: // フィードバック:Great
PlaySound(SOUND_GREAT, NULL, SND_FILENAME | SND_ASYNC);
//this->EventLog(_T("Feedback Great"));
break;
case (int)ECTMSG::FB_EXCELLENT: // フィードバック:Excellent
PlaySound(SOUND_EXCELLENT, NULL, SND_FILENAME | SND_ASYNC);
//this->EventLog(_T("Feedback Excellent"));
break;
case (int)ECTMSG::FB_GOOUT: // フィードバック:視線外れ
PlaySound(SOUND_GOOUT, NULL, SND_FILENAME | SND_ASYNC);
break;
}
return true;
}
// イベントログ出力
void Worker::EventLog(const TCHAR* msg) {
Logger::WriteEvent(msg);
}
// コンタクト累積時間を取得
double Worker::GetContactTime() {
return _AppStatus == APP_STATUS::STIM ? _ContactTime : 0;
}
// 実験経過時間を取得(msec)
double Worker::GetExpTime() {
return _AppStatus == APP_STATUS::STIM ? _pExpTimer->Elapse() : 0;
}
// FPS表示
void Worker::FPS(double fps) {
nkc::wut::DebugPrintf(_T("[Worker] %.1f fps\n"), fps);
}