Newer
Older
ISCamRecorder / ISCamRecorder / SensorData.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO.Ports;
using System.Management;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using System.Drawing;

namespace ISCamRecorder {

    /// <summary>
    /// センサーデータクラス
    /// </summary>
    internal class SensorData {

        enum BUTTON { PRESS, RELEASE  };
        readonly string SERIAL_PORT_NAME = "USB シリアル";  // デバイス名
        readonly int NUM_SAMPLES_SET_THRES = 20;            // 逆血検出閾値を決定するサンプル数(20Hz) 
        SerialPort _Serial = null;                          // シリアル通信オブジェクト
        Queue<int> ValuesB = new Queue<int>();               // センサー値R
        Queue<int> ValuesR = new Queue<int>();               // センサー値G
        Queue<int> ValuesG = new Queue<int>();               // センサー値B
        Queue<int> ValuesSat = new Queue<int>();               // 彩度
        Queue<float> Times = new Queue<float>();            // サンプル時間
        int _QueueLength;                                   // グラフ表示するサンプル数
        StreamWriter _CsvWriter = null;                     // ファイル保存オブジェクト
        StreamReader _CsvReader = null;                     // データファイル読込オブジェクト
        FrameRateCounter _Fps = new FrameRateCounter(10);   // FPS計測
        MainForm _MF = null;                          // メインフォームインスタンス
        bool _IsRecoding = false;           // 記録中フラグ
        bool _ClearPlot = false;            // プロット消去フラグ
        bool _IsReverseBlood = false;       // 逆血フラグ
        int _ReverseBloodThreshold = -1;    // 逆血検出閾値 
        TextAnnotation _AnnoRB;

        public float FrameRate { get { return _Fps.FrameRate; } }   // FPS値

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="mf"></param>
        public SensorData(MainForm mf, int queueLength) {
            _MF = mf;
            _QueueLength = queueLength;
            _AnnoRB = (TextAnnotation)_MF.SensorChart.Annotations["AnnReverseBlood"];
        }

        /// <summary>
        /// 接続
        /// </summary>
        /// <returns></returns>
        public bool Connect() {

            // シリアルポート接続 
            var portname = GetSerialPort();
            if (portname.Length < 1) {
#if DEBUG
                Common.DebugOut($"No Serial Connection.");
#else
                MessageBox.Show("シリアルデバイスに接続できません", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
#endif
                return false;
            }
            _Serial = new SerialPort {
                PortName = portname,
                BaudRate = 115200,
                DataBits = 8,
                Parity = Parity.None,
                StopBits = StopBits.One,
                Handshake = Handshake.None,
                Encoding = Encoding.ASCII,
                WriteTimeout = 1000,
                ReadTimeout = 1000,
                RtsEnable = true,
            };
            try {
                _Serial.Open();
            } catch (Exception ex) {
                Common.DebugOut(ex.Message);
                return false;
            }
            Common.DebugOut($"Serial Connect {portname}.");
            return true;
        }

        /// <summary>
        /// 受信ループ
        /// </summary>
        public void Loop() {
            var baseline = new float[] { 0, 0, 0 };  // ホワイトバランス用RGB基準値
            int lastButtonState = (int)BUTTON.RELEASE;   // ボタンの前の状態

            while (_MF.State != STATE.Exit) {
                // データ受信
                var values = new int[] { 60, 80, 100, 20, (int)BUTTON.RELEASE };  // センサー未接続時のダミー値
                if (_Serial != null) {
                    var str = _Serial.ReadLine();
                    var strs = str.Split(',');
                    if (strs.Length == 5) {
                        values = strs.Select(s => int.Parse(s)).ToArray();
                    } else {
                        values[0] = values[1] = values[2] = values[3] = 0;
                    }
                } else {
                    Thread.Sleep(50);   // センサー未接続時の時間調整
                }

                // ボタン状態
                if (lastButtonState == (int)BUTTON.RELEASE && values[4] == (int)BUTTON.PRESS) _MF.HWButton();
                lastButtonState = values[4];

                // サンプリング時間
                var dt = DateTime.Now;
                var elapsed = (float)((dt - _MF.RecodingTime).TotalMilliseconds);

                // CSV読み込み
                if (_IsRecoding && _CsvReader != null) {
                    if (_CsvReader.EndOfStream) {
                        _CsvReader.BaseStream.Seek(0, SeekOrigin.Begin);
                        _CsvReader.ReadLine();
                    }
                    var items = _CsvReader.ReadLine().Split(',');
                    for (var i = 0; i < 4; i++) values[i] = int.Parse(items[i + 3]);
                }

                // 彩度計算
                var wbvals = new float[] { 1F, 1F, 1F };
                if (_ReverseBloodThreshold >= 0) {
                    for (var i = 0; i < 3; i++) wbvals[i] = Math.Min(values[i] / baseline[i], 1F);
                }
                var sat = (int)(1000F * (1F - wbvals.Min() / wbvals.Max()));
                _MF.SensorChart.Invoke((MethodInvoker)delegate {
                    _MF.SensorChart.ChartAreas[0].BackColor = Color.FromArgb((int)(wbvals[1]*255F), (int)(wbvals[2] * 255F), (int)(wbvals[0] * 255F));
                });

                // プロット追加
                if (_ClearPlot) {
                    _ClearPlot = false;
                    ValuesB.Clear();
                    ValuesR.Clear();
                    ValuesG.Clear();
                    ValuesSat.Clear();
                    Times.Clear();
                }
                ValuesB.Enqueue(values[0]);
                ValuesR.Enqueue(values[1]);
                ValuesG.Enqueue(values[2]);
                ValuesSat.Enqueue(sat);
                Times.Enqueue(elapsed / 1000F);
                while (ValuesB.Count > _QueueLength) {
                    ValuesB.Dequeue();
                    ValuesR.Dequeue();
                    ValuesG.Dequeue();
                    ValuesSat.Dequeue();
                    Times.Dequeue();
                }

                // 検出チャネル決定
                Queue<int> detectCh;
                switch (_MF.Setting.RBDetectCh) {
                case "R": detectCh = ValuesR; break;
                case "G": detectCh = ValuesG; break;
                case "B": detectCh = ValuesB; break;
                default: detectCh = ValuesSat; break;
                }
                // 逆血閾値決定
                var minValue = detectCh.Min();
                if (_ReverseBloodThreshold < 0 && detectCh.Count >= NUM_SAMPLES_SET_THRES) {
                    _ReverseBloodThreshold = _MF.Setting.RBDetectCh.Equals("Sat") ? _MF.Setting.RBThreshold : minValue - _MF.Setting.RBThreshold;
                    baseline[0] = (float)ValuesB.Average();
                    baseline[1] = (float)ValuesR.Average();
                    baseline[2] = (float)ValuesG.Average();
                    //Common.DebugOut($"逆血閾値 {_ReverseBloodThreshold} Baseline B,R,G={baseline[0]:0.0}, {baseline[1]:0.0}, {baseline[2]:0.0}");
                }

                if (_IsRecoding) {

                    // 逆血判定
                    if (_ReverseBloodThreshold >= 0) {
                        var detectData = detectCh.Skip(ValuesG.Count - _MF.Setting.RBDetectSize);
                        var count = 0;
                        if (_MF.Setting.RBDetectCh.Equals("Sat"))
                            count = detectData.Where(v => v >= _ReverseBloodThreshold).Count();
                        else
                            count = detectData.Where(v => v <= _ReverseBloodThreshold).Count();
                        //Common.DebugOut($"逆血検出数 {count}");
                        if (!_IsReverseBlood && count >= _MF.Setting.RBDetectCount) {
                            _IsReverseBlood = true;
                            _MF.ReverseBlood();
                            _MF.SensorChart.Invoke((MethodInvoker)delegate {
                                _AnnoRB.Text = "逆血あり";
                                _MF.SensorChart.ChartAreas[0].BackColor = Color.LightPink;
                            });
                        }
                    }
                }

                // プロット表示
                if (_MF.State != STATE.Exit) {
                    _MF.SensorChart.Invoke((MethodInvoker)delegate {
                        foreach (var s in _MF.SensorChart.Series) {
                            s.Points.Clear();
                            if (s.Name.Equals("B")) s.Points.DataBindXY(Times, ValuesB);
                            if (s.Name.Equals("G")) s.Points.DataBindXY(Times, ValuesG);
                            if (s.Name.Equals("R")) s.Points.DataBindXY(Times, ValuesR);
                            if (s.Name.Equals("Sat")) s.Points.DataBindXY(Times, ValuesSat);
                            s.BorderWidth = _MF.Setting.RBDetectCh.Equals(s.Name) ? 5 : 1;
                        }
                    });
                }

                // CSV保存
                if (_CsvWriter != null && _IsRecoding) {
                    var reverseBlood = _IsReverseBlood ? 1 : 0;
                    _CsvWriter.WriteLine(
                        $"{dt.ToString("yyyy/MM/dd,HH:mm:ss.fff")},{elapsed:0.00}" +
                        $",{values[0]},{values[1]},{values[2]},{values[3]},{sat},{1 - values[4]},{reverseBlood}");
                }

                _Fps.Shot();
            }

            _Serial?.Close();
            _Serial = null;
        }

        /// <summary>
        /// 記録開始
        /// </summary>
        /// <param name="csvFile"></param>
        public void StartRecoding(string csvFile) {
            if (File.Exists(csvFile)) {
                _CsvReader = new StreamReader(csvFile);
                if (_CsvReader != null) _CsvReader.ReadLine();
            }
            var sensorFile = Path.Combine(_MF.OutputDir, $"Sensor_{_MF.RecodingTimeStr}.csv");
            _CsvWriter = new StreamWriter(sensorFile);
            if (_CsvWriter != null) {
                _CsvWriter.WriteLine($"Date,Time,elapsed,B,R,G,IR,Sat,Switch,ReverseBlood");
            }
            _MF.SensorChart.Invoke((MethodInvoker)delegate {
                _AnnoRB.Visible = true;
                _AnnoRB.Text = "逆血なし";
            });
            _ReverseBloodThreshold = -1;
            _IsReverseBlood = false;
            _ClearPlot = true;
            _IsRecoding = true;
        }

        /// <summary>
        /// 記録終了
        /// </summary>
        public void StopRecoding() {
            _IsRecoding = false;
            if (_CsvWriter != null) {
                _CsvWriter.Close();
                _CsvWriter = null;
            }
            if (_CsvReader != null) {
                _CsvReader.Close();
                _CsvReader = null;
            }
            _MF.SensorChart.Invoke((MethodInvoker)delegate {
                _AnnoRB.Text = "逆血なし";
                _AnnoRB.Visible = false;
                _MF.SensorChart.ChartAreas[0].BackColor = Color.LemonChiffon;
            });
        }

        /// <summary>
        /// シリアルポートに1文字送信
        /// </summary>
        /// <param name="c"></param>
        public void SendSignal(char c) {
            if (_Serial != null) {
                _Serial.Write(new char[] { c }, 0, 1);
            }
        }

        /// <summary>
        /// シリアルポート取得
        /// </summary>
        /// <returns></returns>
        private string GetSerialPort() {
            var portname = "";
            var mcW32SerPort = new ManagementClass("Win32_SerialPort");
            foreach (var port in mcW32SerPort.GetInstances()) {
                if (port.GetPropertyValue("Caption").ToString().Contains(SERIAL_PORT_NAME)) {
                    portname = port.GetPropertyValue("DeviceID").ToString();
                }
            }
            return portname;
        }
    }
}