diff --git a/ECTrainer2/ECTrainer2.vcxproj b/ECTrainer2/ECTrainer2.vcxproj index fe83797..f1b0c0f 100644 --- a/ECTrainer2/ECTrainer2.vcxproj +++ b/ECTrainer2/ECTrainer2.vcxproj @@ -153,6 +153,7 @@ + @@ -176,6 +177,7 @@ + diff --git a/ECTrainer2/ECTrainer2.vcxproj.filters b/ECTrainer2/ECTrainer2.vcxproj.filters index f78d800..42a1250 100644 --- a/ECTrainer2/ECTrainer2.vcxproj.filters +++ b/ECTrainer2/ECTrainer2.vcxproj.filters @@ -75,6 +75,9 @@ ソース ファイル + + ソース ファイル + @@ -143,6 +146,9 @@ ヘッダー ファイル + + ヘッダー ファイル + diff --git a/ECTrainer2/ECTrainerGUI.cpp b/ECTrainer2/ECTrainerGUI.cpp index eec6f1c..444356d 100644 --- a/ECTrainer2/ECTrainerGUI.cpp +++ b/ECTrainer2/ECTrainerGUI.cpp @@ -184,7 +184,8 @@ cv::imshow(WIN_MAIN, _MainFrame); // 全画面表示 - if (Ect()->PWorker()->IsNewFullScreenImg() && !Ect()->PDSMovie()->IsPlaying()) { + //if (Ect()->PWorker()->IsNewFullScreenImg() && !Ect()->PDSMovie()->IsPlaying()) { + if (Ect()->PWorker()->IsNewFullScreenImg()) { this->MakeFullDispBuffer(Ect()->PWorker()->GetFullScreenImg()); cv::imshow(WIN_DISP, _FullDispBuf); } diff --git a/ECTrainer2/Logger.cpp b/ECTrainer2/Logger.cpp new file mode 100644 index 0000000..3bc47c9 --- /dev/null +++ b/ECTrainer2/Logger.cpp @@ -0,0 +1,83 @@ +#include "Logger.h" +#include "nkcWinUtils.h" + +// コンストラクタ +Logger::Logger() + : _logFileName(_T("")) +{ + +} + +// ファイル出力(static) +bool Logger::Write(TCHAR* filename, TCHAR* msg) { + if (_tcslen(filename) < 1) return false; + FILE* fp = NULL; + if (_tfopen_s(&fp, filename, _T("a")) != 0) { + nkc::wut::DebugPrintf(_T("Can't open data log file.\n")); + return false; + } + +} + +// ログ開始 +void Logger::Start(std::string subject, int visit) { + + _logFileName = nkc::wut::Multi2Wide( + cv::format(DATA_LOG_FILE, nkc::wut::DateTimeStr().c_str(), subject, visit)); + nkc::wut::DebugPrintf(_T("Log file : %s\n"), _logFileName.c_str()); + + FILE* fp; + if (_tfopen_s(&fp, _logFileName.c_str(), _T("w")) != 0) { + nkc::wut::DebugPrintf(_T("Can't open data log file.\n")); + return; + } + + // ヘッダ行 + _ftprintf(fp, _T("time,stimNo,stimTime,gazeVx,gazeVy,shiftX,shiftY," + "gazeIx,gazeIy,target,contact time,Feedback,TrainLevel,RR,pupilR,pupilL," + "H11,H12,H13,H21,H22,H23,H31,H32,H33\n")); + + fclose(fp); +} + +// ログ出力 +void Logger::Write(Record& r) { + + if (_logFileName.length() < 1) { + nkc::wut::DebugPrintf(_T("Log file is not set.\n")); + return; + } + + FILE* fp = NULL; + if (_tfopen_s(&fp, _logFileName.c_str(), _T("a")) != 0) { + nkc::wut::DebugPrintf(_T("Can't open data log file.\n")); + return; + } + + _ftprintf(fp, _T("%.3f"), r.ElapTime); // 経過時間 + _ftprintf(fp, _T(",%d"), r.StimNo); // 刺激データ番号 + _ftprintf(fp, _T(",%.3f"), r.SceneTime); // 刺激提示の経過時間 + _ftprintf(fp, _T(",%.1f,%.1f"), r.GazeV.x, r.GazeV.y); // 注視点(視野カメラ座標) + _ftprintf(fp, _T(",%.1f,%.1f"), r.Shift.x, r.Shift.y); // ずれ + _ftprintf(fp, _T(",%.1f,%.1f"), r.GazeI.x, r.GazeI.y); // 注視点(画像座標) + _ftprintf(fp, _T(",%d"), r.Target); // ターゲット判定 + _ftprintf(fp, _T(",%.2f"), r.ContactTime); // 目標コンタクト時間 + _ftprintf(fp, _T(",%d"), r.Feedback); // フィードバック + _ftprintf(fp, _T(",%d"), r.TrainingLevel); // トレーニングレベル + _ftprintf(fp, _T(",%d"), r.RR); // バイタル出力(RR間隔) + _ftprintf(fp, _T(",%.1f,%.1f"), r.PupilR, r.PupilL); // 瞳孔径 + if (!r.H.empty() && r.H.rows == 3 && r.H.cols == 3) { // ホモグラフィ行列出力 + double* ptr = r.H.ptr(0); + for (int i = 0; i < 9; i++) { + _ftprintf(fp, _T(",%lf"), *(ptr + i)); + } + } + _ftprintf(fp, _T("\n")); + + fclose(fp); +} + +// ログ停止 +void Logger::Stop() { + _logFileName = _T(""); +} diff --git a/ECTrainer2/Logger.h b/ECTrainer2/Logger.h new file mode 100644 index 0000000..065c355 --- /dev/null +++ b/ECTrainer2/Logger.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include "nkcOpenCV.h" + +#define LOG_DIR "../log/" + +struct Record { + double ElapTime; // 経過時間 + int StimNo; // 刺激データ番号 + double SceneTime; // 刺激提示の経過時間 + cv::Point2f GazeV; // 注視点(視野カメラ座標) + cv::Point2f Shift; // ずれ + cv::Point2f GazeI; // 注視点(画像座標) + int Target; // ターゲット判定 + double ContactTime; // 目標コンタクト時間 + int Feedback; // フィードバック + int TrainingLevel; // トレーニングレベル + int RR; // バイタル出力(RR間隔) + float PupilL; // 瞳孔径 + float PupilR; // 瞳孔径 + cv::Mat H; // ホモグラフィ行列 +}; + +// ログ記録クラス +class Logger { + const char* DATA_LOG_FILE = LOG_DIR "%s_%s_%d.csv"; + const TCHAR* EVENT_LOG_FILE = _T(LOG_DIR "events.txt"); + + std::wstring _logFileName; +public: + Logger(); + static bool Write(TCHAR* filename, TCHAR* msg); + void Start(std::string subject, int visit); + void Write(Record& r); + void Stop(); +}; + +// 将来ログクラスを階層化する +#if 0 +class DataLogger : public Logger { + void Start(std::string subject, int visit); +}; +#endif \ No newline at end of file diff --git a/ECTrainer2/Stimulus.cpp b/ECTrainer2/Stimulus.cpp index da88b01..d7f97b8 100644 --- a/ECTrainer2/Stimulus.cpp +++ b/ECTrainer2/Stimulus.cpp @@ -171,6 +171,7 @@ } if (_StimInfoSet[newStimNo].type == StimInfo::TYPE_MOVIE) { + this->SetImage(BLACK_FILE); _cap.open(_StimInfoSet[newStimNo].smallmovie); if (!_cap.isOpened()) { Ect()->PWorker()->EventLog((_T("Can't open small movie file: ") + diff --git a/ECTrainer2/Stimulus.h b/ECTrainer2/Stimulus.h index d9ab41d..8159b28 100644 --- a/ECTrainer2/Stimulus.h +++ b/ECTrainer2/Stimulus.h @@ -29,13 +29,14 @@ class Stimulus : public BaseProcess { public: + static const int SMALL_MOVIE_WIDTH = 640; // 縮小動画の幅(px) 全画面表示抑制の判定に使う private: const std::string STIM_CONFIG_FILE = "../config/visit%02d.txt"; const cv::String OPENING_FILE = "../images/StartPage.png"; const cv::String CALIB_FILE = "../images/TobiiCalib.png"; const cv::String CALIB_COMPLETE_FILE = "../images/CalibOK.png"; const cv::String CALIB_FAILED_FILE = "../images/CalibFailed.png"; - const cv::String WHITE_FILE = "../images/White.png"; + const cv::String BLACK_FILE = "../images/Black.png"; const cv::String ERROR_FILE = "../images/SystemError.png"; const cv::String EXP_STOP_FILE = "../images/ExpStop.png"; const cv::String EXP_DONE_FILE = "../images/ExpDone.png"; diff --git a/ECTrainer2/Worker.cpp b/ECTrainer2/Worker.cpp index 096007e..98b3b31 100644 --- a/ECTrainer2/Worker.cpp +++ b/ECTrainer2/Worker.cpp @@ -14,7 +14,6 @@ // コンストラクタ Worker::Worker(ECTrainer* pEct) : BaseProcess(pEct) , _AppStatus(APP_STATUS::BOOT) - , _fpLogData(NULL) , _pExpTimer(NULL) , _pContactTimer(NULL) , _TargetImage() @@ -59,7 +58,7 @@ // 表示画像取得 if (_StimImage.empty() || Ect()->PStimulus()->IsNewDisplay()) { _StimImage = Ect()->PStimulus()->GetDisplay(); - _FullScreenImage.Put(_StimImage); + if (_StimImage.cols != Stimulus::SMALL_MOVIE_WIDTH) _FullScreenImage.Put(_StimImage); } // ターゲット画像生成 @@ -133,31 +132,25 @@ } // ログ出力 - 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); // 注視点(視野カメラ座標) - auto logShift = _ShiftLog.size() > 0 && Ect()->PStimulus()->GetStimTime() >= SHIFT_LOG_STARTTIME - && gazeI.x >= 0 ? _ShiftLog.back() : _Shift; - _ftprintf(_fpLogData, _T(",%.1f,%.1f"), logShift.x, logShift.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"), _TrainingLevel); // トレーニングレベル - _ftprintf(_fpLogData, _T(",%d"), Ect()->PBitalMonitor()->GetRR()); // バイタル出力(RR間隔) - auto pd = Ect()->PEyeTrack()->GetPupilDiam(); - _ftprintf(_fpLogData, _T(",%.1f,%.1f"), pd.r, pd.l); // 瞳孔径 - // ホモグラフィ行列出力 - cv::Mat h = Ect()->PMarker()->GetHomography(); - if (!h.empty() && h.rows == 3 && h.cols == 3) { - double* ptr = h.ptr(0); - for (int i = 0; i < 9; i++) { - _ftprintf(_fpLogData, _T(",%lf"), *(ptr + i)); - } - } - _ftprintf(_fpLogData, _T("\n")); + if (stimNo >= 0) { + Record r; + r.ElapTime = _pExpTimer->Elapse() / 1000.; // 経過時間 + r.StimNo = stimNo; // 刺激データ番号 + 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.Write(r); } return true; @@ -206,7 +199,7 @@ case (int)ECTMSG::EXP_START: // 実験開始 if (_AppStatus == APP_STATUS::IDLE) { - this->OpenDataLog(); + _DataLog.Start(Ect()->Subject(), Ect()->Visit()); this->EventLog(_T("Experiment Start")); _pExpTimer->Reset(); this->ResetParams(); @@ -217,7 +210,7 @@ case (int)ECTMSG::EXP_STOP: // 実験停止 if (_AppStatus == APP_STATUS::STIM) { - this->CloseDataLog(); + _DataLog.Stop(); _AppStatus = APP_STATUS::IDLE; Ect()->PStimulus()->PostMsg((int)ECTMSG::EXP_STOP); this->EventLog(_T("Experiment Stopped")); @@ -226,7 +219,7 @@ case (int)ECTMSG::EXP_END: // 実験終了 if (_AppStatus == APP_STATUS::STIM) { - this->CloseDataLog(); + _DataLog.Stop(); _AppStatus = APP_STATUS::IDLE; Ect()->PStimulus()->PostMsg((int)ECTMSG::EXP_END); this->EventLog(_T("Experiment Finished")); @@ -277,32 +270,32 @@ return true; } -// データログファイルを開く -void Worker::OpenDataLog() { - this->CloseDataLog(); // 念のため閉じる - - auto filename = nkc::wut::Multi2Wide(cv::format(DATA_LOG_FILE, nkc::wut::DateTimeStr().c_str(), - Ect()->Subject().c_str(), Ect()->Visit())); - - 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,shiftX,shiftY," - "gazeIx,gazeIy,target,contact time,Feedback,TrainLevel,RR,pupilR,pupilL," - "H11,H12,H13,H21,H22,H23,H31,H32,H33\n")); -} - -// データログファイルを開く -void Worker::CloseDataLog() { - if (_fpLogData) { - fclose(_fpLogData); - _fpLogData = NULL; - } -} +//// データログファイルを開く +//void Worker::OpenDataLog() { +// this->CloseDataLog(); // 念のため閉じる +// +// auto filename = nkc::wut::Multi2Wide(cv::format(DATA_LOG_FILE, nkc::wut::DateTimeStr().c_str(), +// Ect()->Subject().c_str(), Ect()->Visit())); +// +// 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,shiftX,shiftY," +// "gazeIx,gazeIy,target,contact time,Feedback,TrainLevel,RR,pupilR,pupilL," +// "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) { @@ -310,7 +303,7 @@ CreateDirectory(_T(LOG_DIR), NULL); FILE* fp = NULL; // イベントログファイルポインタ - if (_tfopen_s(&fp, EVENT_LOG_FILE, _T("a")) != 0) { + if (_tfopen_s(&fp, _T("../log/events.txt"), _T("a")) != 0) { Ect()->MsgBox(_T("Can't open event log file."), MB_ICONERROR); return; } diff --git a/ECTrainer2/Worker.h b/ECTrainer2/Worker.h index 828ed9c..a23124a 100644 --- a/ECTrainer2/Worker.h +++ b/ECTrainer2/Worker.h @@ -6,6 +6,7 @@ #include "nkcOpenCV.h" #include "RingBuffer.h" #include "HPTimer.h" +#include "Logger.h" class BaseProcess; @@ -24,14 +25,10 @@ const TCHAR* SOUND_GREAT = _T("../voices/Great_S.wav"); const TCHAR* SOUND_EXCELLENT = _T("../voices/Excellent_S.wav"); const TCHAR* SOUND_GOOUT = _T("../voices/KbdKeyTap.wav"); - #define LOG_DIR "../log/" - const char* DATA_LOG_FILE = LOG_DIR "%s_%s_%d.csv"; - const TCHAR* EVENT_LOG_FILE = _T(LOG_DIR "events.txt"); const int FEEDBACK_TIME = 3000; // フィードバックの時間(1レベル)msec const int SHIFT_LOG_STARTTIME = 2000; // 注視点ずれ記録開始時間 const float TRAINING_LEVEL_EFFECT = 0.1F; // トレーニングレベルによる目標領域縮小量 APP_STATUS _AppStatus; // アプリケーション状態 - FILE* _fpLogData; // データログファイルポインタ nkc::HPTimer* _pExpTimer; // 実験経過タイマー nkc::HPTimer* _pContactTimer; // 目標コンタクトタイマー //mwut::HPTimer* _pNohitTimer; // 視線外れ時間 @@ -46,6 +43,7 @@ std::vector _ShiftLog; // 注視点のずれ記録 cv::Point2f _Shift; // 注視点のずれ int _StartStage; // 開始ステージ + Logger _DataLog; // データログ // ECTrainerインスタンス取得 ECTrainer* Ect() { return (ECTrainer*)_pUserdata; } @@ -55,10 +53,10 @@ bool EventProc(MSG& msg); // 判定パラメータをリセット void ResetParams(); - // データログファイルを開く - void OpenDataLog(); - // データログファイルを開く - void CloseDataLog(); + //// データログファイルを開く + //void OpenDataLog(); + //// データログファイルを開く + //void CloseDataLog(); // FPS表示 void FPS(double fps);