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;
}
}
}