repo time
Dependencies: mbed MAX14720 MAX30205 USBDevice
HspGuiSourceV301/HSPGui/CustomControls/EcgView.cs
- Committer:
- darienf
- Date:
- 2021-04-06
- Revision:
- 20:6d2af70c92ab
File content as of revision 20:6d2af70c92ab:
/******************************************************************************* * Copyright (C) 2016 Maxim Integrated Products, Inc., All rights Reserved. * * This software is protected by copyright laws of the United States and * of foreign countries. This material may also be protected by patent laws * and technology transfer regulations of the United States and of foreign * countries. This software is furnished under a license agreement and/or a * nondisclosure agreement and may only be used or reproduced in accordance * with the terms of those agreements. Dissemination of this information to * any party or parties not specified in the license agreement and/or * nondisclosure agreement is expressly prohibited. * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Except as contained in this notice, the name of Maxim Integrated * Products, Inc. shall not be used except as stated in the Maxim Integrated * Products, Inc. Branding Policy. * * The mere transfer of this software does not imply any licenses * of trade secrets, proprietary technology, copyrights, patents, * trademarks, maskwork rights, or any other form of intellectual * property whatsoever. Maxim Integrated Products, Inc. retains all * ownership rights. ******************************************************************************* */ //#define CES_DEMO //#define FILE_LOG using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using RPCSupport.Streaming; using RPCSupport.Devices; using HealthSensorPlatform.View; using HealthSensorPlatform.Presenter; using HealthSensorPlatform.Model; #if CES_DEMO using System.Collections; #endif // DEBUG using System.IO; namespace HealthSensorPlatform.CustomControls { public partial class EcgView : UserControl, IDeviceView, IEcgView { /* Constants */ const int Rbias_FMSTR_Init = 0; const int CAL_init = 1; const int ECG_InitStart = 2; const int PACE_InitStart = 3; const int BIOZ_InitStart = 4; const int ECGFast_Init = 5; const int RtoR_InitStart = 6; const int RToRMaxValue = 0x3FFF; #if CES_DEMO const int[] averageOptions = new int[] { 1, 2, 4, 8, 10 }; #endif /* Fields */ public InitArgs.RbiasInit rbiasInit; public InitArgs.EcgInitStart ecgArgs; public InitArgs.EcgFastInit ecgFastArgs; public InitArgs.PACEInitStart paceArgs; public InitArgs.BIOZInitStart biozArgs; public InitArgs.RToRInitStart rtorArgs; public InitArgs.CalInitStart calArgs; public GetEcgInitArgs GetEcgInitArgsPointer; public GetRToRInitArgs GetRToRInitArgsPointer; public GetBioZInitArgs GetBioZInitArgsPointer; public GetPaceInitArgs GetPaceInitArgsPointer; /// <summary> /// Number of R To R points which need to be removed from the chart /// </summary> int rToRRemoveOffset; long lastRToRTime; double timeResolution = 0.00001525878; double[] clockFreq = { 32768, 32000, 32000, 32768 * 640 / 656 }; // 31968.7804878 = 32768 * 640 / 656 int frequencyMasterField = 0; public double masterClockFrequency = 32768; #if CES_DEMO public int AverageECG = 1; #endif private RPCSupport.RPCClient rpcClient; private bool connected = false; //private bool stream = true; /// <summary> /// Use to scale the bioZ chart data to milliohm range /// </summary> private int bioZRangeMultiplier = 1; bool startedCal = false; bool startedEcg = false; bool startedPace = false; bool startedBioz = false; bool startedRtoR = false; private bool enableECG = false; private bool enableRToR = false; private bool enableBioZ = false; private bool enablePace = false; int rToRInterval = -1; ChartInfo ecgInfo; ChartInfo rToRInfo; ChartInfo bioZInfo; ChartInfo paceInfo; Queue<int> paceTag = new Queue<int>(); public PaceData paceData; // TODO: public for testing only RToRCalculator rToRCalculator; List<double> heartRateList = new List<double>(10); /* CES DEMO average feature, normal operation average is 1 */ //int averageEcgCount = 0; //int averageEcgSum = 0; StringBuilder fileLogData = new StringBuilder(); int[, ,] ecgDecimationDelay = new int[,,] { {{650, 1034}, {2922, 3690}, {3370, 4906}, {3370, 4906}}, {{650, 1034}, {2922, 3690}, {3370, 4906}, {3370, 4906}}, {{1242, 2202}, {1242, 2202}, {1242, 2202}, {1242, 2202}}, {{1242, 2202}, {1242, 2202}, {1242, 2202}, {1242, 2202}} }; public int RToRWindowField = 0; #if CES_DEMO FirFilter hpf; #endif #if FILE_LOG // DEBUG StreamWriter file; //= new StreamWriter("hsp_ecg_output_data.csv"); #endif /* Constructor */ public EcgView() { InitializeComponent(); //paceInfo = ecgInfo; /*foreach(int item in averageOptions) { cboAverageEcg.Items.Add(item.ToString()); }*/ //cboAverageEcg.SelectedIndex = 0; //cboMaxScale.SelectedIndex = 1; chartRtoR.ChartAreas[0].AxisY.Maximum = 100; ecgInfo = new ChartInfo { SampleRate = 128, // Power on defaults for ECG Length = 10, Average = 1, Shift = 6, Gain = 20, // ECG is 18 bits, with MSB as sign bit Threshold = 0x1ffff, Offset = 0x40000, Chart = chartECG, SeriesName = "Series", }; rToRInfo = new ChartInfo { SampleRate = 128, Length = 10, Chart = chartECG, SeriesName = "SeriesRToR", }; bioZInfo = new ChartInfo { SampleRate = 64, Length = 10, Shift = 4, Gain = 20, // BioZ is 20 bits, with MSB as sign bit Threshold = 0x7ffff, Offset = 0x100000, Chart = chartBioz, SeriesName = "Series", }; paceInfo = new ChartInfo { SampleRate = 65536 * ecgInfo.SampleRate, // 2 * fMSTR Length = 10, Average = 1, Shift = 0, // Keep tag info Chart = chartECG, SeriesName = "SeriesPace", }; NamedImage paceFalling = new NamedImage("PaceFallingImage", HealthSensorPlatform.Properties.Resources.pace_falling); chartECG.Images.Add(paceFalling); NamedImage paceRising = new NamedImage("PaceRisingImage", HealthSensorPlatform.Properties.Resources.pace_rising); chartECG.Images.Add(paceRising); #if CES_DEMO //grpAverage.Visible = true; hpf = new FirFilter(); //cboEcgHpf.SelectedIndex = 0; #endif } /* Delegates */ public delegate InitArgs.EcgInitStart GetEcgInitArgs(); public delegate InitArgs.RToRInitStart GetRToRInitArgs(); public delegate InitArgs.BIOZInitStart GetBioZInitArgs(); public delegate InitArgs.PACEInitStart GetPaceInitArgs(); //public delegate void StreamingStartStopEventHandler(StreamingStartStopEventArgs e); /* Events */ /// <summary> /// Streaming event /// </summary> public event EventHandler<StreamingStartStopEventArgs> StreamingStartStop; /* Enums */ public enum StreamDataType { Ecg, RToR, Pace, BioZ }; /* Properties */ public RPCSupport.RPCClient RPCClient { set { rpcClient = value; //streamHandler = new EventHandler<PartialArrayIntAvailableEventArgs>(On_AppendChart); //rpcClient.streaming.PartialArrayIntAvailable += streamHandler; } } /* public bool Stream { get { return stream; } set { stream = value; if (stream == true) rpcClient.streaming.PartialArrayIntAvailable += streamHandler; else rpcClient.streaming.PartialArrayIntAvailable -= streamHandler; } } */ public bool Connected { get { return connected; } set { connected = value; } } public bool EnableECG { get { return enableECG; } set { enableECG = value; chartECG.Enabled = value; } } public bool EnableRToR { get { return enableRToR; } set { enableRToR = value; chartRtoR.Enabled = value; grpBxHeartRate.Enabled = value; } } public bool EnableBioZ { get { return enableBioZ; } set { enableBioZ = value; chartBioz.Enabled = value; } } public bool EnablePace { get { return enablePace; } set { enablePace = value; chartPace.Enabled = value; } } public bool EnableDCLeadOff { get { return grpBxDCLeadOff.Enabled; } set { grpBxDCLeadOff.Enabled = value; } } public bool EnableEcgDCLeadOff { get; set; } public bool EnableBioZOverUnderRange { get { return grpBxBioZOverUnderRange.Enabled; } set { grpBxBioZOverUnderRange.Enabled = value; } } public bool BioZMilliOhmRange { get { return (bioZRangeMultiplier == 1000); } set { if (value) { bioZRangeMultiplier = 1000; chartBioz.ChartAreas["ChartArea"].AxisY.Title = "Bioz (mΩ)"; } else { bioZRangeMultiplier = 1; chartBioz.ChartAreas["ChartArea"].AxisY.Title = "Bioz (Ω)"; } } } public double SampleRateEcg // Human readable sample rate { get { return ecgInfo.SampleRate; } set { ecgInfo.SampleRate = value; //rToRInfo.SampleRate = value; paceInfo.SampleRate = MasterClockFrequency * 2 * value; } } public double SampleRateBioZ // Human readable sample rate { get { return bioZInfo.SampleRate; } set { bioZInfo.SampleRate = value; } } public int GainECG // Human readable gain { get { return ecgInfo.Gain; } set { ecgInfo.Gain = value; } } public int GainBioZ // Human readable gain { get { return bioZInfo.Gain; } set { bioZInfo.Gain = value; } } public int CurrentBioZ // Human readable bioZ current generator magnitude { get { return bioZInfo.CurrentGenerator; } set { bioZInfo.CurrentGenerator = value; } } /// <summary> /// This value is t_RES = 1 / (2 * fMSTR) = 1 / (2 * 32768). For R-to-R the LSB time is 512*t_RES, while for /// PACE, the value is t_RES. /// </summary> public double TimeResolution { get { return timeResolution; } } public ChartInfo EcgInfo { get { return ecgInfo; } } public ChartInfo RToRInfo { get { return rToRInfo; } } public ChartInfo BioZInfo { get { return bioZInfo; } } public ChartInfo PaceInfo { get { return paceInfo; } } public InitArgs.EcgInitStart EcgArgs { get { return ecgArgs; } } public InitArgs.RToRInitStart RToRArgs { get { return rtorArgs; } } public int MasterClockField { get { return frequencyMasterField; } } public int FrequencyMasterField { get { return frequencyMasterField; } set { frequencyMasterField = value; masterClockFrequency = clockFreq[frequencyMasterField]; paceInfo.SampleRate = 2 * masterClockFrequency; rToRInfo.SampleRate = masterClockFrequency / 256.0; timeResolution = 1.0 / (2 * masterClockFrequency); } } public double MasterClockFrequency { get { return masterClockFrequency; } /* set { masterClockFrequency = value; paceInfo.SampleRate = 2 * value; TimeResolution = 1 / (2 * value); }*/ } public int EcgDecimationDelay { get { int fmstr = frequencyMasterField; int ecgRate = ecgArgs.Rate; int lpf = ecgArgs.Dlpf > 0 ? 1 : 0; return ecgDecimationDelay[fmstr, ecgRate, lpf]; } } public int RToRDelay { get { return 5376 + 3370 + 256 * RToRWindowField; } } public Chart ChartECG { get { return chartECG; } } // Testing public IHspSetting HspSetting { get; set; } private void EcgView_Load(object sender, EventArgs e) { InitChart(); } String ValueToBits(int value, int width) { String str = ""; String val = ""; int mask = 1; for (int i = 0; i < width; i++) { if ((mask & value) == mask) { val = "1"; } else { val = "0"; } mask = mask << 1; str = val + str; } return str; } String[] ValueToBitStringArray(int width) { List<String> stringList = new List<string>(); for (int i = 0; i < Math.Pow(2, width); i++) { stringList.Add(ValueToBits(i, width)); } return stringList.ToArray(); } private void InitChart() { // Reset Averaging //averageEcgSum = 0; //averageEcgCount = 0; //ecgInfo.Average = averageOptions[cboAverageEcg.SelectedIndex]; ecgInfo.Average = 1; // Rest Pace Tags paceTag.Clear(); //UpdateChart(chartPace, new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); //paceData = new PaceData(new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); //UpdateChart(StreamDataType.Ecg, new int[] {1}); DisplayEcg(new double[] { 0 }, null, null); //UpdateChart(chartECG, "SeriesRToR", new int[] {0}); //UpdateChart(StreamDataType.BioZ, new int[] {0}); DisplayBioZ(new double[] { 0 }); UpdateChartAxis(chartECG); //UpdateChartAxis(chartRtoR); UpdateChartAxis(chartBioz); //UpdateChartAxis(chartPace); paceData = null; //chartRtoR.ChartAreas[0].AxisY.Maximum = 100; } private void ResetChart() { paceData = null; } private void UpdateChartAxis(System.Windows.Forms.DataVisualization.Charting.Chart chart) { ChartInfo chartInfo = new ChartInfo(); if (chart.Name == "chartECG") { chartInfo = ecgInfo; } else if (chart.Name == "chartBioz") { chartInfo = bioZInfo; } else if (chart.Name == "chartPace") { chartInfo = paceInfo; } else if (chart.Name == "chartRtoR") { chartInfo = rToRInfo; } chart.SuspendLayout(); chart.ChartAreas[0].AxisX.Minimum = 0; chart.ChartAreas[0].AxisX.Maximum = chartInfo.Points; chart.ChartAreas[0].AxisX.Interval = (float)chartInfo.Points / chartInfo.Length; if (chart.Name != "chartRtoR") { chart.ChartAreas[0].AxisX.CustomLabels.Clear(); // Set X-axis range in seconds for (int i = 0; i < 11; i ++ ) { chart.ChartAreas[0].AxisX.CustomLabels.Add(Math.Ceiling(((float)chartInfo.SampleRate / chartInfo.Average) * (2 * i - 1) / 2), Math.Floor(((float)chartInfo.SampleRate / chartInfo.Average) * (2 * i + 1) / 2), i.ToString()); } } chart.ResumeLayout(); } public void DisplayEcg(double[] ecg, PacePoint[] pace, double[] rToR) { List<double> xList = new List<double>(); List<double> yList = new List<double>(); List<double> xListFalling = new List<double>(); List<double> yListFalling = new List<double>(); int offset = chartECG.Series["Series"].Points.Count; // Pace Offset chartECG.SuspendLayout(); for(int i = 0; i < ecg.Length; i++ ) { chartECG.Series["Series"].Points.Add(ecg[i]); } if (pace != null) { // Shift Old Points - Rising for (int i = 0; i < chartECG.Series["SeriesPace"].Points.Count; i++) { double xValue = chartECG.Series["SeriesPace"].Points[i].XValue; if (offset > ecgInfo.Points) // Do not shift until chart is full xValue -= ecg.Length; if (xValue >= 0) { xList.Add(xValue); yList.Add(chartECG.Series["SeriesPace"].Points[i].YValues[0]); } } // Shift Old Points - Falling for (int i = 0; i < chartECG.Series["SeriesPaceFalling"].Points.Count; i++ ) { double xValueFalling = chartECG.Series["SeriesPaceFalling"].Points[i].XValue; if (offset > ecgInfo.Points) xValueFalling -= ecg.Length; if (xValueFalling >= 0) { xListFalling.Add(xValueFalling); yListFalling.Add(chartECG.Series["SeriesPaceFalling"].Points[i].YValues[0]); } } // Add New Points for (int i = 0; i < pace.Length; i++) { if (pace[i].Polarity == true) { //xList.Add(pace[i].Time + offset); //yList.Add(0.05); chartECG.Series["SeriesPace"].Points.AddXY(pace[i].Time + offset, 0.05*(0+1)); //System.Diagnostics.Debug.Print("Rising: " + (pace[i].Time + offset)); } else { //xListFalling.Add(pace[i].Time + offset); //yListFalling.Add(-0.05); chartECG.Series["SeriesPaceFalling"].Points.AddXY(pace[i].Time + offset, -0.08*(0+1)); //System.Diagnostics.Debug.Print("Falling: " + (pace[i].Time + offset)); } } // Bind data to chart //chartECG.Series["SeriesPace"].Points.DataBindXY(xList, yList); //chartECG.Series["SeriesPaceFalling"].Points.DataBindXY(xListFalling, yListFalling); } UpdateChartFormat(StreamDataType.Ecg, ecgInfo); //if (rToR != null) // Remove extra points // UpdateChartFormat(StreamDataType.RToR, rToRInfo); if (pace != null) // Not needed anymore due to new plot style UpdateChartFormat(StreamDataType.Pace, PaceInfo, offset + ecg.Length - ecgInfo.Points); chartECG.ResumeLayout(); } public void DisplayBioZ(double[] bioZ) { chartBioz.SuspendLayout(); for(int i = 0; i < bioZ.Length; i++) { chartBioz.Series["Series"].Points.Add(bioZ[i] * bioZRangeMultiplier); } UpdateChartFormat(StreamDataType.BioZ, bioZInfo); chartBioz.ResumeLayout(); } public double DisplayRToR(int rToR) { double rate; int rToREcg; long now = DateTime.Now.Ticks; if (rToR == RToRMaxValue) // Ignore the max value, counter overflow condition return 0; rate = rToRCalculator.BeatsPerMinute(rToR); // RToRBeatsPerMinute(rToR); if (heartRateList.Count < 10) heartRateList.Add(rate); else if (heartRateList.Count == 10) { heartRateList.RemoveAt(0); heartRateList.Add(rate); double sum = 0; foreach (double d in heartRateList) sum += d; lblHeartRateAverage10.Text = (sum / 10).ToString("F1"); } lblHeartRateBeatToBeat.Text = rate.ToString("F1"); if (heartRateList.Count != 1) { rToREcg = rToRCalculator.EcgPoints(rToR, false); // RToREcgPoints(rToR, false); } else { // First Point delay by Tdrtor_ecg //rToREcg = RToREcgPoints(rToR, true) + 1; // DEBUG 3 extra 0x07 are sent from FW at start of stream, 1 extra point for GUI chart init int rToRInEcg = rToRCalculator.EcgPoints(rToR, true); rToREcg = rToRInEcg + 1; } if (lastRToRTime < 0) lastRToRTime = now; else { long diff = now - lastRToRTime; TimeSpan elaspedTicks = new TimeSpan(diff); int fullScaleTime = (int)(Math.Pow(2, 14) * 512 * TimeResolution + 0.5); if (elaspedTicks.TotalSeconds > fullScaleTime) { int overflowCount = (int)(elaspedTicks.TotalSeconds / fullScaleTime); for (int i = 0; i < overflowCount; i++ ) rToREcg += rToRCalculator.EcgPoints(EcgDelay.RToRMaxValue, false); //RToREcgPoints(0x3FFF, false); } lastRToRTime = now; } for (int i = 1; i < rToREcg - rToRRemoveOffset; i++ ) { chartECG.Series["SeriesRToR"].Points.Add(-5000); } int rToRCount = chartECG.Series["SeriesRToR"].Points.Count; if (rToRCount < chartECG.Series["Series"].Points.Count) chartECG.Series["SeriesRToR"].Points.Add(chartECG.Series["Series"].Points[rToRCount].YValues[0]); // R to R comes in after ECG else chartECG.Series["SeriesRToR"].Points.Add(0); // R to R comes in faster than ECG, add as 0 for now rToRRemoveOffset = 0; return rate; } public void BioZFunction(bool enable) { chartBioz.Visible = enable; if (enable == false) { //tableLayoutPanelCharts.RowCount = 1; tableLayoutPanelCharts.RowStyles[1] = new RowStyle(SizeType.Percent, 0); tableLayoutPanelCharts.RowStyles[0] = new RowStyle(SizeType.Percent, 100); } else { tableLayoutPanelCharts.RowStyles[0] = new RowStyle(SizeType.Percent, 50); tableLayoutPanelCharts.RowStyles[1] = new RowStyle(SizeType.Percent, 50); } } /* public double RToRBeatsPerMinute(int rToRCode) { return 60 * 1000/ RToRMillisecond(rToRCode); } public double RToRMillisecond(int rToRCode) { return rToRCode * 512.0 * 1000 / (2 * MasterClockFrequency); } public int RToREcgPoints(int rToR, bool first) { double sampleRateRToR = MasterClockFrequency / 256; int rToRDifferentialDelay = RToRDelay - EcgDecimationDelay; double rToRInEcgSamples; int rToRInEcgSamplesInt; if (first) { rToRInEcgSamples = (rToR - rToRDifferentialDelay / 256.0) * (SampleRateECG / sampleRateRToR); rToRInEcgSamplesInt = (int)(rToRInEcgSamples + 0.5); rToRInEcgSampleError = rToRInEcgSamplesInt - rToRInEcgSamples; return rToRInEcgSamplesInt; } else { rToRInEcgSamples = rToR * (SampleRateECG / sampleRateRToR) - rToRInEcgSampleError; rToRInEcgSamplesInt = (int)(rToRInEcgSamples + 0.5); rToRInEcgSampleError = rToRInEcgSamplesInt - rToRInEcgSamples; return rToRInEcgSamplesInt; } } */ private double[] UpdateChartFormat(StreamDataType dataType, ChartInfo chartInfo) { Chart chart = null; String series = ""; double[] chartData; int minRound = 0, maxRound = 0; double min = Double.MaxValue, max = Double.MinValue; int count; switch (dataType) { case StreamDataType.Ecg: chart = chartECG; series = "Series"; break; case StreamDataType.BioZ: chart = chartBioz; series = "Series"; break; case StreamDataType.RToR: chart = chartECG; series = "SeriesRToR"; break; case StreamDataType.Pace: chart = chartECG; series = "SeriesPace"; break; } count = chart.Series[series].Points.Count; // Remove extra points while (count > chartInfo.Points) { chart.Series[series].Points.RemoveAt(0); if (dataType == StreamDataType.Ecg && EnableRToR) // Scroll R To R with ECG if (chart.Series["SeriesRToR"].Points.Count > 0) // TODO chart.Series["SeriesRToR"].Points.RemoveAt(0); else rToRRemoveOffset++; count = chart.Series[series].Points.Count; } chartData = new double[chart.Series[series].Points.Count]; // Copy data points and find min/max value for (int i = count / 2; i < count; i++) // Autoscale on last half of data only { chartData[i] = chart.Series[series].Points[i].YValues[0]; if (chartData[i] < min) min = chartData[i]; if (chartData[i] > max) max = chartData[i]; } if (min == max) // prevent any invalid ranges max = max + 1; if (chartInfo == ecgInfo) { // Round to 1mV for ECG minRound = ((int)(min / 1) - 1) * 1; maxRound = ((int)(max / 1) + 1) * 1; if (minRound == -1 && maxRound == 1) // Manual control of auto interval chart.ChartAreas[0].AxisY.Interval = 0.5; else chart.ChartAreas[0].AxisY.Interval = 0; } else if (chartInfo == bioZInfo) { // Round to 100's for automatic axis scaling - for raw codes minRound = (((int)min / 100 - 1) * 100); maxRound = (((int)max / 100 + 1) * 100); } /*else if (chartInfo == rToRInfo) { // Round to 100ms minRound = 0; // R to R rate should never be negative; maxRound = ((int)(max / 100) + 1) * 100; } else { // Round to 100's for automatic axis scaling - for us of Pace length minRound = (((int)min / 100 - 1) * 100); maxRound = (((int)max / 100 + 1) * 100); }*/ if (chartInfo == ecgInfo || chartInfo == bioZInfo) { // Set full Y-axis range chart.ChartAreas[0].AxisY.Minimum = minRound; chart.ChartAreas[0].AxisY.Maximum = maxRound; } return chartData; } private double[] UpdateChartFormat(StreamDataType dataType, ChartInfo chartInfo, int shift) { List<double> xList = new List<double>(); List<double> yList = new List<double>(); List<double> xListFalling = new List<double>(); List<double> yListFalling = new List<double>(); int offset = chartECG.Series["Series"].Points.Count; // Pace Offset // Shift Old Points - Rising for (int i = 0; i < chartECG.Series["SeriesPace"].Points.Count; i++) { double xValue = chartECG.Series["SeriesPace"].Points[i].XValue; if (offset >= ecgInfo.Points) // Do not shift until chart is full xValue -= shift; if (xValue >= 0) { xList.Add(xValue); yList.Add(chartECG.Series["SeriesPace"].Points[i].YValues[0]); } } // Shift Old Points - Falling for (int i = 0; i < chartECG.Series["SeriesPaceFalling"].Points.Count; i++) { double xValueFalling = chartECG.Series["SeriesPaceFalling"].Points[i].XValue; if (offset >= ecgInfo.Points) xValueFalling -= shift; if (xValueFalling >= 0) { xListFalling.Add(xValueFalling); yListFalling.Add(chartECG.Series["SeriesPaceFalling"].Points[i].YValues[0]); } } chartECG.Series["SeriesPace"].Points.DataBindXY(xList, yList); chartECG.Series["SeriesPaceFalling"].Points.DataBindXY(xListFalling, yListFalling); return yList.ToArray(); } /* public EcgFifo[] ConvertEcg(int[] data) { EcgFifo[] voltage = new EcgFifo[data.Length]; ChartInfo chartInfo = ecgInfo; int dataShift; for (int i = 0; i < data.Length; i++ ) { dataShift = data[i] >> chartInfo.Shift; // Two's Complement Conversions if (dataShift > chartInfo.Threshold) { dataShift -= chartInfo.Offset; } voltage[i].Data = 1000 * 7.62939453125e-6 * dataShift / chartInfo.Gain; voltage[i].PTag = data[i] & 0x07; voltage[i].ETag = (data[i] >> 3) & 0x07; } return voltage; } public BioZFifo[] ConvertBioZ(int[] data) { BioZFifo[] impedence = new BioZFifo[data.Length]; ChartInfo chartInfo = bioZInfo; int dataShift; int dataPoint; for (int i = 0; i < data.Length; i ++) { dataShift = data[i] >> chartInfo.Shift; // Two's Complement Conversions if (dataShift > chartInfo.Threshold) { dataShift -= chartInfo.Offset; } // 1.9734 = 1/2^19 * 1e-6 impedence[i].Data = dataShift * bioZRangeMultiplier * 1.9073486328125 / (chartInfo.Gain * ((chartInfo.CurrentGenerator == 0) ? 1 : chartInfo.CurrentGenerator)); impedence[i].BTag = data[i] & 0x07; } return impedence; } public PaceData ConvertPace(int[] data) { paceData = new PaceData(data); return paceData; } public double ConvertRToR(int data) { rToRInterval = data; return rToRInterval; } */ int[] ProcessRToR(int[] ecgData) { int[] rToRArray; rToRArray = new int[ecgData.Length]; for (int i = 0; i < rToRArray.Length; i++) rToRArray[i] = Int32.MinValue; if (rToRInterval > 0) // Valid R to R { var ecgCode = EcgMaximumCode(ecgData); rToRArray[ecgCode.Item1] = ecgCode.Item2; } return rToRArray; } public Tuple<int, int> EcgMaximumCode(int[] data) { int i; int max = Int32.MinValue; int maxCode = 0; int maxIndex = 0; int point; for (i = 0; i < data.Length; i++ ) { point = data[i] >> ecgInfo.Shift; if (data[i] > ecgInfo.Threshold) point -= ecgInfo.Offset; if (point > max) // Two's complement offset { max = point; maxCode = data[i]; maxIndex = i; } } return new Tuple<int, int>(maxIndex, maxCode); } private bool LeadOffBit(int value, int bit) { int state; int mask = 1 << bit; state = ((value & mask) == mask) ? 1 : 0; return state == 1; } //public void On_AppendChart(object sender, PartialArrayIntAvailableEventArgs e) //{ /* int leadOffState; if (e.array1.Length > 0) // Occurs with streaming from flash { leadOffState = e.array1[0]; switch (e.reportID) { case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_LEADOFF_DC: if (LeadOffBit(leadOffState, 8)) // check for ECG { UpdateLeadOffState(lblLdoffPh, LeadOffBit(leadOffState, 3)); UpdateLeadOffState(lblLdoffPl, LeadOffBit(leadOffState, 2)); UpdateLeadOffState(lblLdoffNh, LeadOffBit(leadOffState, 1)); UpdateLeadOffState(lblLdoffNl, LeadOffBit(leadOffState, 0)); } if (LeadOffBit(leadOffState, 9)) // check for BIOZ { UpdateLeadOffState(lblLdoffPh, LeadOffBit(leadOffState, 3)); UpdateLeadOffState(lblLdoffPl, LeadOffBit(leadOffState, 2)); UpdateLeadOffState(lblLdoffNh, LeadOffBit(leadOffState, 1)); UpdateLeadOffState(lblLdoffNl, LeadOffBit(leadOffState, 0)); } break; case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_LEADOFF_AC: UpdateLeadOffState(lblBioZACOver, LeadOffBit(leadOffState, 0)); UpdateLeadOffState(lblBioZACUnder, LeadOffBit(leadOffState, 1)); break; } } */ /* StringBuilder outputData = new StringBuilder(); if (e.reportID == PartialArrayIntAvailableEventArgs.PACKET_MAX30001_ECG) { if (e.array1.Length != 0) { if (ecgBuffer != null) { UpdateChart(StreamDataType.Ecg, ecgBuffer); if (EnableRToR) { int[] rToRArray = ProcessRToR(ecgBuffer); UpdateChart(StreamDataType.RToR, rToRArray); rToRInterval = -1; // R to R processed reset value } } ecgBuffer = ecgBuffer1; ecgBuffer1 = e.array1; // DEBUG foreach (int data in e.array1) { outputData.Append(data.ToString()); outputData.Append(Environment.NewLine); } file.Write(outputData.ToString()); ConvertEcg(e.array1); } } if (e.reportID == PartialArrayIntAvailableEventArgs.PACKET_MAX30001_PACE) { if (e.array1.Length != 0) { // Store Pace data for update with ECG PTAGs ConvertPace(e.array1); } } if (e.reportID == PartialArrayIntAvailableEventArgs.PACKET_MAX30001_RTOR) { if (e.array1.Length != 0) { ConvertRToR(e.array1[0]); } } if (e.reportID == PartialArrayIntAvailableEventArgs.PACKET_MAX30001_BIOZ) { if (e.array1.Length != 0) { ConvertBioZ(e.array1); //UpdateChart(StreamDataType.BioZ, e.array1); } }*/ //} private void Start() { //ParseRbiasArgs(); /*rpcClient.MAX30001.Rbias_FMSTR_Init( rbiasInit.En_rbias, rbiasInit.Rbias, rbiasInit.Rbiasp, rbiasInit.Rbiasn, rbiasInit.Fmstr );*/ //if (ckbxCal.Checked) /*{ startedCal = true; //ParseCalArgs(); calArgs.En_Vcal = 0; calArgs.Vmode = 0; calArgs.Vmag = 1; calArgs.Fcal = 3; calArgs.Thigh = 0x1F; calArgs.Fifty = 0; rpcClient.MAX30001.CAL_InitStart( calArgs.En_Vcal, calArgs.Vmode, calArgs.Vmag, calArgs.Fcal, calArgs.Thigh, calArgs.Fifty); }*/ /* rpcClient.MAX30001.INT_assignment(MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_enint_loc, en_eovf_loc, en_fstint_loc, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_dcloffint_loc, en_bint_loc, en_bovf_loc, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_bover_loc, en_bundr_loc, en_bcgmon_loc, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_pint_loc, en_povf_loc, en_pedge_loc, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_lonint_loc, en_rrint_loc, en_samp_loc, MAX30001.max30001_intrpt_type.MAX30001_INT_ODNR, MAX30001.max30001_intrpt_type.MAX30001_INT_ODNR); // intb_Type, int2b_Type) */ if (connected) { rpcClient.MAX30001.WriteReg(0x02, 0x03); // Stop Interrupts rpcClient.MAX30001.WriteReg(0x03, 0x03); // Stop Interrupts } if (enableECG) { startedEcg = true; if (GetEcgInitArgsPointer != null) { ecgArgs = GetEcgInitArgsPointer(); rpcClient.MAX30001.ECG_InitStart( ecgArgs.En_ecg, ecgArgs.Openp, ecgArgs.Openn, ecgArgs.Pol, ecgArgs.Calp_sel, ecgArgs.Caln_sel, ecgArgs.E_fit, ecgArgs.Rate, ecgArgs.Gain, ecgArgs.Dhpf, ecgArgs.Dlpf); } /* rpcClient.MAX30001.ECGFast_Init( ecgFastArgs.Clr_Fast, ecgFastArgs.Fast, ecgFastArgs.Fast_Th);*/ } if (enablePace) { startedPace = true; if (GetPaceInitArgsPointer != null) { paceArgs = GetPaceInitArgsPointer(); rpcClient.MAX30001.PACE_InitStart( paceArgs.En_pace, paceArgs.Clr_pedge, paceArgs.Pol, paceArgs.Gn_diff_off, paceArgs.Gain, paceArgs.Aout_lbw, paceArgs.Aout, paceArgs.Dacp, paceArgs.Dacn); } } if (enableBioZ) { startedBioz = true; if (GetBioZInitArgsPointer != null) { biozArgs = GetBioZInitArgsPointer(); rpcClient.MAX30001.BIOZ_InitStart( biozArgs.En_bioz, biozArgs.Openp, biozArgs.Openn, biozArgs.Calp_sel, biozArgs.Caln_sel, biozArgs.CG_mode, biozArgs.B_fit, biozArgs.Rate, biozArgs.Ahpf, biozArgs.Ext_rbias, biozArgs.Gain, biozArgs.Dhpf, biozArgs.Dlpf, biozArgs.Fcgen, biozArgs.Cgmon, biozArgs.Cgmag, biozArgs.Phoff); } } if (enableRToR) { startedRtoR = true; if (GetRToRInitArgsPointer != null) { rtorArgs = GetRToRInitArgsPointer(); rpcClient.MAX30001.RtoR_InitStart( rtorArgs.En_rtor, rtorArgs.Wndw, rtorArgs.Gain, rtorArgs.Pavg, rtorArgs.Ptsf, rtorArgs.Hoff, rtorArgs.Ravg, rtorArgs.Rhsf, rtorArgs.Clr_rrint); } rToRCalculator = new RToRCalculator(frequencyMasterField, ecgArgs.Rate, ecgArgs.Dlpf, rtorArgs.Wndw); } /*rpcClient.MAX30001.Rbias_FMSTR_Init( 0, 0, 0, 0, 0);*/ rpcClient.MAX30001.INT_assignment(MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_enint_loc, en_eovf_loc, en_fstint_loc, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_dcloffint_loc, en_bint_loc, en_bovf_loc, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_bover_loc, en_bundr_loc, en_bcgmon_loc, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_pint_loc, en_povf_loc, en_pedge_loc, MAX30001.max30001_intrpt_Location.MAX30001_INT_2B, MAX30001.max30001_intrpt_Location.MAX30001_INT_B, MAX30001.max30001_intrpt_Location.MAX30001_NO_INT, // en_lonint_loc, en_rrint_loc, en_samp_loc, MAX30001.max30001_intrpt_type.MAX30001_INT_ODNR, MAX30001.max30001_intrpt_type.MAX30001_INT_ODNR); // intb_Type, int2b_Type) if (startedEcg | startedCal | startedPace | startedBioz | startedRtoR) { rpcClient.MAX30001.StartStreaming(); } // Clear Lead Off UpdateLeadOffState(lblLdoffPh, false); UpdateLeadOffState(lblLdoffPl, false); UpdateLeadOffState(lblLdoffNh, false); UpdateLeadOffState(lblLdoffNl, false); UpdateLeadOffState(lblBioZACOver, false); UpdateLeadOffState(lblBioZACUnder, false); // Clear Charts chartECG.Series["Series"].Points.Clear(); chartECG.Series["SeriesRToR"].Points.Clear(); chartECG.Series["SeriesPace"].Points.Clear(); chartECG.Series["SeriesPaceFalling"].Points.Clear(); //chartRtoR.Series["Series"].Points.Clear(); chartBioz.Series["Series"].Points.Clear(); //chartPace.Series["Series"].Points.Clear(); InitChart(); fileLogData.Clear(); // Clear initiation points // Reset R To R state rToRRemoveOffset = 0; lastRToRTime = -1; // Clear Heart Rate heartRateList = new List<double>(10); lblHeartRateAverage10.Text = "--"; lblHeartRateBeatToBeat.Text = "--"; #if CES_DEMO hpf.Reset(); #endif btnMonitor.Text = "Stop Monitor"; if (StreamingStartStop != null) { StreamingStartStop(this, new StreamingStartStopEventArgs() { state = true }); } #if FILE_LOG // Debug DateTime localDate = DateTime.Now; file = new StreamWriter("hsp_ecg_output_data.csv"); file.WriteLine("# " + localDate.ToString() + ", Sample Rate Register Setting: " + ecgArgs.Rate); file.WriteLine(@"# ECG (μV) Raw Integer"); #endif // DEBUG - simulate the "first" R To R value as a > 10 second value to see what happens //DisplayRToR(0x600); } private void btnMonitor_Click(object sender, EventArgs e) { if (btnMonitor.Text == "Start Monitor") { if (Connected) { //if (ckbxEcg.Checked | ckbxPace.Checked | ckbxBioz.Checked | ckbxRtoR.Checked) if (EnableECG | EnableBioZ) Start(); else if (EnableRToR) MessageBox.Show("Also enable ECG for R-to-R streaming", "Streaming"); else MessageBox.Show("Enable ECG or BioZ before starting streaming", "Streaming"); } } else { Stop(); } } private void Stop() { if (startedEcg | startedCal | startedPace | startedBioz | startedRtoR) { rpcClient.MAX30001.StopStreaming(connected); //rpcClient.MAX30001.WriteReg(0x02, 0); //rpcClient.MAX30001.WriteReg(0x03, 0); } startedCal = false; startedEcg = false; startedPace = false; startedBioz = false; startedRtoR = false; btnMonitor.Text = "Start Monitor"; if (StreamingStartStop != null) StreamingStartStop(this, new StreamingStartStopEventArgs() { state = false }); #if FILE_LOG // Debug file.Close(); #endif } // Clean up streaming public void Close() { // Disable streaming if enabled if (StreamingStartStop != null && btnMonitor.Text == "Stop Monitor") { StreamingStartStop(this, new StreamingStartStopEventArgs() { state = false }); Stop(); } } private void chkbxCheckedChanged(object sender, EventArgs e) { /* EnableDisableControls(CAL_init, ckbxCal.Checked); EnableDisableControls(ECG_InitStart, ckbxEcg.Checked); EnableDisableControls(PACE_InitStart, ckbxPace.Checked); EnableDisableControls(BIOZ_InitStart, ckbxBioz.Checked); EnableDisableControls(ECGFast_Init, ckbxEcg.Checked); EnableDisableControls(RtoR_InitStart, ckbxRtoR.Checked); */ } private void btnStartTest_Click(object sender, EventArgs e) { System.Windows.Forms.DataVisualization.Charting.Chart test = chartECG; double ymin = test.ChartAreas[0].AxisY.Minimum; double ymax = test.ChartAreas[0].AxisY.Maximum; if (btnStartTest.Text == "Start Test") { rpcClient.MAX30001.StartTest(); btnStartTest.Text = "Stop Test"; } else { Stop(); btnStartTest.Text = "Start Test"; } } public void SetDCLeadOff(int leadOffState) { if (LeadOffBit(leadOffState, 8)) // check for ECG { UpdateLeadOffState(lblLdoffPh, LeadOffBit(leadOffState, 3)); UpdateLeadOffState(lblLdoffPl, LeadOffBit(leadOffState, 2)); UpdateLeadOffState(lblLdoffNh, LeadOffBit(leadOffState, 1)); UpdateLeadOffState(lblLdoffNl, LeadOffBit(leadOffState, 0)); } if (LeadOffBit(leadOffState, 9)) // check for BIOZ { UpdateLeadOffState(lblLdoffPh, LeadOffBit(leadOffState, 3)); UpdateLeadOffState(lblLdoffPl, LeadOffBit(leadOffState, 2)); UpdateLeadOffState(lblLdoffNh, LeadOffBit(leadOffState, 1)); UpdateLeadOffState(lblLdoffNl, LeadOffBit(leadOffState, 0)); } } public void SetACLeadOff(int leadOffState) { UpdateLeadOffState(lblBioZACOver, LeadOffBit(leadOffState, 0)); UpdateLeadOffState(lblBioZACUnder, LeadOffBit(leadOffState, 1)); } // // Update LeadOff GUI State // private void UpdateLeadOffState(TextBox tb, bool state) { tb.Text = (state == true) ? "1" : "0"; if (state == true) tb.BackColor = Color.LightGreen; else tb.BackColor = Color.Yellow; } private void UpdateLeadOffState(Label lbl, bool state) { lbl.ForeColor = (state == true) ? MaximStyle.MaximColor.Red : MaximStyle.MaximColor.Green; } /* Inner Classes of EcgView */ /// <summary> /// Information used to generate the X-axis of a chart /// </summary> public class ChartInfo { /// <summary> /// Sample Rate /// </summary> private double sampleRate = 0; public double SampleRate { get { return sampleRate; } set { sampleRate = value; points = Convert.ToInt32(length * sampleRate / average); } } /// <summary> /// Length of chart in seconds /// </summary> private int length = 1; public int Length { get { return length; } set { length = value; points = Convert.ToInt32(length * sampleRate / average); } } /// <summary> /// Number of total points in chart /// </summary> private int points = 1; public int Points { get { return points; } } public int Gain; private int average = 1; public int Average { get { return average; } set { average = value; points = Convert.ToInt32(length * sampleRate / average); } } /// <summary> /// Value passed the threshold is negative /// </summary> private int threshold = Int32.MaxValue; public int Threshold { get { return threshold; } set { threshold = value; } } /// <summary> /// Amount to offset value by to convert to negative /// </summary> private int offset = 0; public int Offset { get { return offset; } set { offset = value; } } /// <summary> /// Number of bits to shift raw data by /// </summary> public int Shift; /// <summary> /// Current generator for BioZ /// </summary> public int CurrentGenerator; public Chart Chart; public String SeriesName; } public class PacePoint { public double Time; public bool Polarity; public PacePoint(double time, bool polarity) { this.Time = time; this.Polarity = polarity; } } // DEBUG int offsetTest = 0; private void button1_Click(object sender, EventArgs e) { //timer1.Enabled = !timer1.Enabled; chartECG.Series["SeriesPace"].Points.AddXY(1.99354553222656 + offsetTest, 0.05 * (1)); chartECG.Series["SeriesPaceFalling"].Points.AddXY(1.99455261230469 + offsetTest, -0.08 * (1)); offsetTest += 100; } // DEBUG private void timer1_Tick(object sender, EventArgs e) { /* PartialArrayIntAvailableEventArgs ecg1 = new PartialArrayIntAvailableEventArgs(); PartialArrayIntAvailableEventArgs ecg2 = new PartialArrayIntAvailableEventArgs(); PartialArrayIntAvailableEventArgs rr1 = new PartialArrayIntAvailableEventArgs(); ecg1.array1 = new int[] { 0x00, 0x00, 0x00, 0x00, 0xfffff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ecg2.array1 = new int[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; rr1.array1 = new int[] { 0x88 }; ecg1.reportID = PartialArrayIntAvailableEventArgs.PACKET_MAX30001_ECG; ecg2.reportID = PartialArrayIntAvailableEventArgs.PACKET_MAX30001_ECG; rr1.reportID = PartialArrayIntAvailableEventArgs.PACKET_MAX30001_RTOR; if (clicks % 15 == 0) { On_AppendChart(this, ecg2); } else if (clicks % 5 == 0) { On_AppendChart(this, ecg1); On_AppendChart(this, rr1); } else On_AppendChart(this, ecg2); clicks++; */ DisplayEcg(new double[] { 0, 0, 0, 0, 1, 2, 3, 4, 5 }, null, new double[] {-10, -10, -10, -10, -10, -10, 4, -10, -10}); } } }