Newer
Older
PrismSoftware / ECTrainer2 / Worker.cpp
#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)
	, _fpLogData(NULL)
	, _pExpTimer(NULL)
	, _pContactTimer(NULL)
	, _TargetImage()
	, _FullScreenImage()
	, _FBLevel(1)
{
	_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;
	//if (!Ect()->PEyeTrack()->IsNewGazeV()) {
	//	Sleep(0);
	//	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);

	// 表示画像取得
	if (_StimImage.empty() || Ect()->PStimulus()->IsNewDisplay()) {
		_StimImage = Ect()->PStimulus()->GetDisplay();
		_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) {
					cv::circle(stimImg, target * scale, (int)(elems[e].d * scale), CV_RGB(255, 0, 0), 2);
				}
				if (gazeI.x >= 0) {
					// ターゲット判定
					auto dt = cv::norm(gazeI - target);
					if (dt < elems[e].d && (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 (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後にリセット
				}
			}
		}

		_TargetImage.Put(stimImg);
	} else {
		_TargetImage.Put(_StimImage);
	}

	// ログ出力
	if (stimNo >= 0 && _fpLogData != NULL) {
		_ftprintf(_fpLogData, _T("%.3f"), _pExpTimer->Elapse() / 1000.);	// 経過時間
		_ftprintf(_fpLogData, _T(",%d"), stimNo);	// 刺激データ番号
		_ftprintf(_fpLogData, _T(",%.3f"), Ect()->PStimulus()->GetStimTime() / 1000.);	// 刺激提示の経過時間
		_ftprintf(_fpLogData, _T(",%.1f,%.1f"), gazeV.x, gazeV.y);	// 注視点(視野カメラ座標)
		_ftprintf(_fpLogData, _T(",%.1f,%.1f"), gazeI.x, gazeI.y);	// 注視点(画像座標)
		_ftprintf(_fpLogData, _T(",%d"), hit + 1);	// ターゲット判定
		_ftprintf(_fpLogData, _T(",%.2f"), _ContactTime / 1000.);	// 目標コンタクト時間
		_ftprintf(_fpLogData, _T(",%d"), fb);	// フィードバック
		_ftprintf(_fpLogData, _T(",%d"), Ect()->PBitalMonitor()->GetRR());	// バイタル出力(RR間隔)
		// ホモグラフィ行列出力
		cv::Mat h = Ect()->PMarker()->GetHomography();
		if (!h.empty() && h.rows == 3 && h.cols == 3) {
			double* ptr = h.ptr<double>(0);
			for (int i = 0; i < 9; i++) {
				_ftprintf(_fpLogData, _T(",%lf"), *(ptr + i));
			}
		}
		_ftprintf(_fpLogData, _T("\n"));
	}

	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::CALIB_OK);
		this->EventLog(_T("Calibration Success"));
		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::EXP_START: // 実験開始
		if (_AppStatus == APP_STATUS::IDLE) {
			this->OpenDataLog();
			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) {
			this->CloseDataLog();
			_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) {
			this->CloseDataLog();
			_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::OpenDataLog() {
	this->CloseDataLog();	// 念のため閉じる

	auto filename = DATA_LOG_FILE + nkc::wut::Multi2Wide(nkc::wut::DateTimeStr()) + _T(".csv");

	if (_tfopen_s(&_fpLogData, filename.c_str(), _T("w")) != 0) {
		Ect()->MsgBox(_T("Can't open data log file."), MB_ICONERROR);
		return;
	}

	// ヘッダ行
	_ftprintf(_fpLogData, _T("time,stimNo,stimTime,gazeVx,gazeVy,gazeIx,gazeIy,target,contact time,Feedback,RR,"
		"H11,H12,H13,H21,H22,H23,H31,H32,H33\n"));
}

// データログファイルを開く
void Worker::CloseDataLog() {
	if (_fpLogData) {
		fclose(_fpLogData);
		_fpLogData = NULL;
	}
}

// イベントログ出力
void Worker::EventLog(const TCHAR* msg) {

	FILE* fp = NULL;		// イベントログファイルポインタ
	if (_tfopen_s(&fp, EVENT_LOG_FILE, _T("a")) != 0) {
		Ect()->MsgBox(_T("Can't open event log file."), MB_ICONERROR);
		return;
	}

	if (_tcslen(msg) < 1) {
		_ftprintf(fp, _T("\n"));
	} else {
		std::wstring datestr = nkc::wut::Multi2Wide(nkc::wut::DateTimeStr());
		_ftprintf(fp, _T("%s %s\n"), datestr.c_str(), msg);
		nkc::wut::DebugPrintf(_T("%s %s\n"), datestr.c_str(), msg);
	}

	fclose(fp);
}

// コンタクト累積時間を取得
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);
}