Newer
Older
PrismSoftware / EcomAnalysis / Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;

namespace EcomAnalysis {
    public partial class Form1 : Form {

        private string[] _HeaderItems = {
            "stimNo", "sceneNo", "stimTime", "contact time", "Feedback", "RR", "gazeVx", "gazeVy", "pupilR", "pupilL" };
        private Dictionary<int, int> _SceneNoAssign = new Dictionary<int, int>();
        private List<LogData> _Data = new List<LogData>();

        /// <summary>
        /// コンストラクタ
        /// 基本データ設定
        /// </summary>
        public Form1() {
            InitializeComponent();
            for (int scene = 1, stim = 5; scene <= 10; scene++, stim += 2) {
                _SceneNoAssign.Add(stim, scene);
            }
            for (int scene = 11, stim = 26; scene <= 20; scene++, stim += 2) {
                _SceneNoAssign.Add(stim, scene);
            }
            for (int scene = 21, stim = 47; scene <= 30; scene++, stim += 2) {
                _SceneNoAssign.Add(stim, scene);
            }
            for (int scene = 31, stim = 68; scene <= 40; scene++, stim += 2) {
                _SceneNoAssign.Add(stim, scene);
            }
        }

        /// <summary>
        /// フォームロード時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e) {
            TxtLogFilename.Text = "not opened";
            var args = Environment.GetCommandLineArgs();
            if (args.Length > 1) {
                ReadCSV(args[1]);
                Analyze();
            }
        }

        /// <summary>
        /// 解析
        /// </summary>
        private void Analyze() {
            // 定数
            const double RR2HB = 60000.0;  // RR間隔(ms)から心拍数への変換係数
            const int HB_MIN = 20;          // 心拍数の変動範囲の最小値
            const int HB_MAX = 200;         // 心拍数の変動範囲の最大値
            const int RR_MIN = (int)RR2HB / HB_MAX; // RR間隔の変動範囲の最小値
            const int RR_MAX = (int)RR2HB / HB_MIN; // RR間隔の変動範囲の最大値

            // FB回数
            var fbCounts = new int[5];
            var fbPts = new int[5];
            for (int i=0; i<fbCounts.Length; i++) {
                fbCounts[i] = _Data.Where(s => s.Feedback == i + 1).Count();
                fbPts[i] = fbCounts[i] * (i + 1);
            }

            // シーン別解析
            listView1.Items.Clear();
            if (_Data.Where(s => s.SceneNo > 0).Count() > 0) {
                var sceneBegin = _Data.Where(s => s.SceneNo > 0).Min(s => s.SceneNo);
                var sceneEnd = _Data.Where(s => s.SceneNo > 0).Max(s => s.SceneNo);

                var allData = new List<SceneData>();
                for (int sc = sceneBegin; sc <= sceneEnd; sc++) {
                    var scDat = new SceneData();
                    scDat.SceneNo = sc;
                    var dat = _Data.Where(s => s.SceneNo == sc);
                    if (dat.Count() == 0) continue;
                    scDat.FBCount = dat.Count(c => c.Feedback > 0);
                    scDat.ECTime = dat.Count(c => c.Feedback == 5) * 15.0 +
                        dat.OrderBy(s => s.SceneTime).Select(s=>s.ContactTime).Last();
                    scDat.Latency = dat.Where(c => c.Feedback > 0).Count()==0 ? 0 : 
                        dat.Where(c => c.Feedback > 0).Select(s => s.SceneTime).First();
                    var validRR = dat.Where(s => s.RR >= RR_MIN && s.RR <= RR_MAX);
                    scDat.Beat = validRR.Count() > 0 ? RR2HB / validRR.Select(s => s.RR).Average() : 0;
                    scDat.Blink = CountEyeBlink(dat);
                    scDat.Pupil.R = dat.Select(s => s.Pupil.R).Average();
                    scDat.Pupil.L = dat.Select(s => s.Pupil.L).Average();

                    var items = new string[] { 
                        $"{scDat.SceneNo}", $"{scDat.FBCount} 回",
                        $"{scDat.ECTime:0.00} 秒", $"{scDat.Latency:0.00} 秒",
                        $"{scDat.Beat:0.00} bpm", $"{scDat.Blink} 回",
                        $"{scDat.Pupil.R:0.00} mm", $"{scDat.Pupil.L:0.00} mm"};
                    listView1.Items.Add(new ListViewItem(items));
                    allData.Add(scDat);
                }

                var validBeat = allData.Where(s => s.Beat > 0);
                var meanBeat = validBeat.Count() > 0 ? validBeat.Select(s => s.Beat).Average() : 0;
                var validLatency = allData.Where(s => s.FBCount > 0);
                var meanLatency = validLatency.Count() > 0 ? validLatency.Select(s => s.Latency).Average() : 0;
                listView1.Items.Add(new ListViewItem(new string[] {
                    "平均",
                    $"{allData.Select(s=>s.FBCount).Average():0.00} 回",
                    $"{allData.Select(s=>s.ECTime).Average():0.00} 秒",
                    $"{meanLatency:0.00} 秒",
                    $"{meanBeat:0.00} bpm",
                    $"{allData.Select(s=>s.Blink).Average():0.00} 回",
                    $"{allData.Select(s=>s.Pupil.R).Average():0.00} ms",
                    $"{allData.Select(s=>s.Pupil.L).Average():0.00} ms"}));
            }

            // 表示
            TxtFB1.Text = $"{fbCounts[0],2}回 {fbPts[0],4}ポイント";
            TxtFB2.Text = $"{fbCounts[1],2}回 {fbPts[1],4}ポイント";
            TxtFB3.Text = $"{fbCounts[2],2}回 {fbPts[2],4}ポイント";
            TxtFB4.Text = $"{fbCounts[3],2}回 {fbPts[3],4}ポイント";
            TxtFB5.Text = $"{fbCounts[4],2}回 {fbPts[4],4}ポイント";
            TxtFBTotal.Text = $"{fbCounts.Sum(),2}回 {fbPts.Sum(),4}ポイント";
        }

        /// <summary>
        /// 瞬目回数を算出
        /// </summary>
        /// <param name="dat">瞬目回数</param>
        /// <returns></returns>
        private int CountEyeBlink(IEnumerable<LogData> dat) {
            const int BLINK_LENGTH = 4;    // 瞬目と判断する視線データ未検出回数
            int close = 0;  // 閉じている回数
            int blink = 0;  // 瞬目回数

            foreach (var d in dat.OrderBy(s => s.SceneTime)) {
                if (d.GazeV.x < 0) {
                    ++close;
                } else {
                    if (close >= BLINK_LENGTH) ++blink;
                    close = 0;
                }
            }

            return blink;
        }

        /// <summary>
        /// CSVファイルの読み込み
        /// </summary>
        /// <param name="csvfile"></param>
        private void ReadCSV(string csvfile) {

            char[] sep = { ',' };
            if (!File.Exists(csvfile)) {
                MessageBox.Show("ファイルがありません", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            using (var sr = new StreamReader(csvfile,
                Encoding.GetEncoding("shift_jis"))) {

                // ヘッダー検証
                var readHeader = sr.ReadLine().Split(sep);
                var headerCol = new Dictionary<string, int>();
                foreach (var hi in _HeaderItems) {
                    headerCol.Add(hi, readHeader.ToList().IndexOf(hi));
                }
                if (headerCol.Count < _HeaderItems.Length-1) {
                    MessageBox.Show("必要な列がありません", "Error", 
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }

                // 読み込み
                _Data.Clear();
                var sceneNoExist = headerCol["sceneNo"] >= 0;
                while (sr.Peek() > -1) {
                    var line = sr.ReadLine().Split(sep);
                    var ld = new LogData();
                    ld.StimNo = int.Parse(line[headerCol["stimNo"]]);
                    ld.SceneTime = double.Parse(line[headerCol["stimTime"]]);
                    ld.ContactTime = double.Parse(line[headerCol["contact time"]]);
                    ld.Feedback = int.Parse(line[headerCol["Feedback"]]);
                    ld.RR = int.Parse(line[headerCol["RR"]]);
                    ld.SceneNo = sceneNoExist ? int.Parse(line[headerCol["sceneNo"]]) 
                        : (_SceneNoAssign.ContainsKey(ld.StimNo) ? _SceneNoAssign[ld.StimNo] : 0);
                    ld.GazeV.x = float.Parse(line[headerCol["gazeVx"]]);
                    ld.GazeV.y = float.Parse(line[headerCol["gazeVy"]]);
                    ld.Pupil.R = float.Parse(line[headerCol["pupilR"]]);
                    ld.Pupil.L = float.Parse(line[headerCol["pupilL"]]);
                    _Data.Add(ld);
                }
                //Debug.WriteLine(_Data.Count);

                // 表示
                TxtLogFilename.Text = csvfile;
                var filename = Path.GetFileNameWithoutExtension(csvfile);
                TxtDateTime.Text = string.Format("{0}年{1}月{2}日 {3}時{4}分",
                    filename.Substring(0,4), filename.Substring(4, 2),
                    filename.Substring(6, 2), filename.Substring(9, 2),
                     filename.Substring(11, 2));
                var strs = filename.Split('_');
                TxtSubject.Text = $"{strs[2]}   Visit {strs[3]}";
            }
        }
    }
}