Newer
Older
PrismSoftware / ECTrainer2 / ECTrainerGUI.cpp

#include "DShowMovie.h"
#include "ECTrainerGUI.h"
#include "ECTrainer.h"
#include "Worker.h"
#include "EyeTrack.h"
#include "ImageProc.h"
#include "BitalMonitor.h"
#include "TobiiREST.h"
#include "Stimulus.h"
#include "Marker.h"
#include "nkcWinUtils.h"
#pragma comment(lib,"winmm.lib")

#define CVUI_IMPLEMENTATION
#include "cvui.h"

// コンストラクタ
ECTrainerGUI::ECTrainerGUI(ECTrainer* pEct) : BaseProcess(pEct)
	, _hWndMain(NULL)
{
}

// 初期化
bool ECTrainerGUI::Init() {

	// GUIウインドウ
	cvui::init(WIN_MAIN);
	_MainFrame.create(MAIN_FRAME_SIZE, CV_8UC3);

	// 刺激提示ウインドウ
	std::vector<RECT> displays = nkc::wut::GetDisplayInfo();	// 実行環境の情報取得
	if (displays.size() > 1 && !FORCE_SINGLE_MONITOR) {
		// マルチモニタの場合
		// サブ画面に操作パネル表示
		cv::moveWindow(WIN_MAIN, displays[1].left, displays[1].top);

		// メイン画面に被験者映像提示(DirectShow動画がサブ画面に出力できないため)
		cv::namedWindow(WIN_DISP, cv::WINDOW_NORMAL | cv::WINDOW_OPENGL);
		cv::moveWindow(WIN_DISP, displays[0].left, displays[0].top);
		cv::resizeWindow(WIN_DISP, cv::Size(640, 400));
		cv::setWindowProperty(WIN_DISP, cv::WND_PROP_FULLSCREEN, cv::WINDOW_FULLSCREEN);
	} else {
		// シングルモニタの場合
		cv::moveWindow(WIN_MAIN, displays[0].left + 100, displays[0].top + 100);

		displays[0].right /= 2;
		displays[0].bottom /= 2;
		cv::namedWindow(WIN_DISP, cv::WINDOW_AUTOSIZE);
		cv::moveWindow(WIN_DISP, displays[0].left, displays[0].top);
	}

	_FullDispBuf = cv::Mat(cv::Size(displays[0].right - displays[0].left,
		displays[0].bottom - displays[0].top), CV_8UC3, cv::Scalar(0));
	_blank = cv::Mat(DISP_SIZE, CV_8UC3, cv::Scalar(0));
	_Logo = cv::imread(HEADER_FILE);
	_hWndMain = ::FindWindowA(NULL, WIN_MAIN.c_str());

	Ect()->PDSMovie()->Init(displays[0]);

	return true;
}

// 基本処理
bool ECTrainerGUI::Routine() {

	// メインウインドウフレームクリア
	_MainFrame = cv::Scalar(49, 52, 49);

	// 視野画像バッファ生成
	cv::Mat sceneBuf = Ect()->PImageProc()->GetImage();
	if (sceneBuf.empty()) sceneBuf = _blank;

	// 表示バッファ生成(実験中はWorkerクラスの生成画像,非実験中はStimulusクラスの画像を使う)
	//bool fTarget = Ect()->PWorker()->GetAppStatus() == APP_STATUS::STIM;
	//bool isNew = fTarget ?  : Ect()->PStimulus()->IsNewDisplay();
	cv::Mat dispBuf = Ect()->PWorker()->GetTargetImg().clone();
	if (dispBuf.empty()) dispBuf = _blank;

	// 注視点の表示
	if (Ect()->PEyeTrack()->GetGazeV().x >= 0) {
		cv::circle(sceneBuf, Ect()->PEyeTrack()->GetGazeV(), 20, CV_RGB(0, 0, 255), 3);
	}

	// ヘッダー描画
	cv::Mat header = _Logo.clone();
	double contactTime = Ect()->PWorker()->GetContactTime() / 1000.0;
	double expTime = Ect()->PWorker()->GetExpTime() / 1000.0;
	double progress = expTime / Ect()->PStimulus()->GetTotalExpTime();
	cv::rectangle(header, cv::Rect(1000, 14, (int)(400 * progress), 22), CV_RGB(0, 199, 0), cv::FILLED);
	cv::rectangle(header, cv::Rect(1000, 54, (int)(400 * contactTime / 15.0), 22), CV_RGB(240, 116, 95), cv::FILLED);
	cvui::image(_MainFrame, 0, 0, header);
	cvui::text(_MainFrame, 850, 18, cv::format("%2d:%02d/%2d:%02d",
		(int)expTime / 60, (int)expTime % 60,
		(int)Ect()->PStimulus()->GetTotalExpTime()/60, (int)Ect()->PStimulus()->GetTotalExpTime() % 60), 0.6, 0);
	cvui::text(_MainFrame, 880, 58, cv::format("%5.1f sec", contactTime), 0.6, 0);

	// UI描画
	cvui::beginColumn(_MainFrame, 10, 100, 140, -1, 10);
	switch (Ect()->PWorker()->GetAppStatus()) {
	case APP_STATUS::IDLE:
		if (cvui::button(140, 30, "CALIBRATION")) {
			Ect()->PWorker()->PostMsg((int)ECTMSG::CALIB_START);
		}
		if (cvui::button(140, 30, "START")) {
			Ect()->PWorker()->PostMsg((int)ECTMSG::EXP_START);
		}
		break;
	case APP_STATUS::STIM:
		//if (cvui::button(140, 30, "NEXT")) {
		//	Ect()->PDSMovie()->StopMovie();
		//	((BaseProcess*)Ect()->PWorker())->PostMsg(ECTMSG::EXP_STOP);
		//}
		if (cvui::button(140, 30, "STOP")) {
			Ect()->PDSMovie()->StopMovie();
			Ect()->PWorker()->PostMsg((int)ECTMSG::EXP_STOP);
		}
		//cvui::trackbar(140, &targetSize, (float)0, (float)3.0);
		//cvui::checkbox("Show Eyes", &fShowEyesPos);
		//cvui::text(fContact ? "Eyes Contact!" : "No contact");
		break;
	}
	cvui::printf("Training Level %d", Ect()->PWorker()->GetTrainingLevel());
	cvui::printf("View Gaze %.0f, %.0f", Ect()->PEyeTrack()->GetGazeV().x, Ect()->PEyeTrack()->GetGazeV().y);
	//cvui::printf("Img Gaze %.2f, %.2f", Ect()->GetGazeI().x, Ect()->GetGazeI().y);
	cvui::text(Ect()->PMarker()->IsDetected() ? "AR Markers OK" : "AR Markers NG");
	cvui::printf("Battery %d %%", Ect()->PTobiiREST()->GetBatteryLevel());
	if (Ect()->PStimulus()->GetStimNo() >= 0) {
		cvui::printf("%s", Ect()->PStimulus()->GetStimFile().c_str());
		cvui::printf("Showing %.1f s", Ect()->PStimulus()->GetStimTime() / 1000.F);
	}
	bool snapshot = cvui::button(140, 30, "SNAPSHOT");
	if (cvui::button(140, 30, "QUIT")) {
		Ect()->PWorker()->PostMsg((int)ECTMSG::SOFTWARE_END);
	}
	if (Ect()->PBitalMonitor()->IsUseDevice()) {
		cvui::printf("HeartBeat (bpm)");
		cvui::text(cv::format("%3d", Ect()->PBitalMonitor()->GetHB()), 2.0);
	} else {
		cvui::printf("HeartBeat no device");
	}
	cvui::endColumn();

	// 画像表示
	cvui::text(_MainFrame, SCENE_BUFFER_POS.x, 100, "SCENE CAMERA");
	cv::Mat sceneResized = nkc::ocv::KeepAspectResize(sceneBuf, DISP_SIZE.width);
	cvui::image(_MainFrame, SCENE_BUFFER_POS.x, SCENE_BUFFER_POS.y, sceneResized);

	cvui::text(_MainFrame, DISP_IMAGE_POS.x, 100, "STIMULUS IMAGE");
	cv::Mat dispResized = nkc::ocv::KeepAspectResize(dispBuf, DISP_SIZE.width);
	cvui::image(_MainFrame, DISP_IMAGE_POS.x, DISP_IMAGE_POS.y, dispResized);

	// 動画の末尾まで再生チェック
	if (Ect()->PDSMovie()->IsReachToEnd()) {
		Ect()->PWorker()->PostMsg((int)ECTMSG::MOVIE_END);
	}

	// メイン画面表示
	cvui::update();
	cv::imshow(WIN_MAIN, _MainFrame);

	// 全画面表示
	if (Ect()->PWorker()->IsNewFullScreenImg() && !Ect()->PDSMovie()->IsPlaying()) {
		this->MakeFullDispBuffer(Ect()->PWorker()->GetFullScreenImg());
		cv::imshow(WIN_DISP, _FullDispBuf);
	}

	// スナップショット保存
	if (snapshot) cv::imwrite(SNAPSHOT_FILE, _MainFrame);

	// ウインドウ×ボタンで閉じた時
	if (_hWndMain != ::FindWindowA(NULL, WIN_MAIN.c_str())) { 
		Ect()->PWorker()->PostMsg((int)ECTMSG::SOFTWARE_END);
	}

	Sleep(1);

	return true;
}

// イベント処理
bool ECTrainerGUI::EventProc(MSG& msg) {
	switch (msg.message) {

	case WM_CHAR:	// キー入力
		if (msg.wParam == 27) {
#ifdef _DEBUG
			Ect()->PWorker()->PostMsg((int)ECTMSG::SOFTWARE_END);
#endif
		}
		nkc::wut::DebugPrintf(_T("Key '%c'\n"), msg.wParam);
		break;

	case (int)ECTMSG::MOVIE_START:
		if (!Ect()->PDSMovie()->PlayMovie(Ect()->PStimulus()->GetMovie())) {
			Ect()->PWorker()->EventLog((_T("Can't play movie file :")
				+ Ect()->PStimulus()->GetMovie()).c_str());
			Ect()->PWorker()->PostMsg((int)ECTMSG::SYSTEM_ERROR);
		}
		break;

	case (int)ECTMSG::MOVIE_STOP:
		Ect()->PDSMovie()->StopMovie();
		break;

	default:
		//mwut::DebugPrintf(_T("Event %04X\n"), msg.message);
		break;
	}

	return true;
}

// 刺激画像バッファに画像を設定
void ECTrainerGUI::MakeFullDispBuffer(cv::Mat img) {
	cv::Mat buf(_FullDispBuf.size(), CV_8UC3, cv::Scalar(0));
	cv::Rect roiRect;
	if (img.rows * buf.cols / buf.rows > img.cols) {
		roiRect.width = img.cols * buf.rows / img.rows;
		roiRect.height = buf.rows;
	} else {
		roiRect.width = buf.cols;
		roiRect.height = img.rows * buf.cols / img.cols;
	}
	roiRect.x = (buf.cols - roiRect.width) / 2;
	roiRect.y = (buf.rows - roiRect.height) / 2;
	if (roiRect.x + roiRect.width > buf.cols) roiRect.width = buf.cols - roiRect.x;
	if (roiRect.y + roiRect.height > buf.rows) roiRect.height = buf.rows - roiRect.y;
	cv::Mat roi(buf, roiRect);
	cv::resize(img, roi, roi.size());
	buf.copyTo(_FullDispBuf);
}

// FPS表示
void ECTrainerGUI::FPS(double fps) {
	nkc::wut::DebugPrintf(_T("[GUI] %.1f fps\n"), fps);
}