From 899b379cf71755612776bc6fdaa9a711b6637209 Mon Sep 17 00:00:00 2001 From: Milan Hanajik Date: Thu, 20 Jan 2022 13:51:57 +0100 Subject: [PATCH] FeatureVectorCalculator updated to current version. --- Common/Common.csproj | 1 + Common/Iperl/OptoTelegramRaw.cs | 12 +- Common/StatisticalMetrics.cs | 338 ++++++++++++++++++ .../CalculatorWnd.Designer.cs | 229 ++++++++++-- FeatureVectorCalculator/CalculatorWnd.cs | 127 +++++-- .../FeatureVectorCalculator.csproj | 2 + FeatureVectorCalculator/SignalChart.cs | 108 ++++++ 7 files changed, 755 insertions(+), 62 deletions(-) create mode 100644 Common/StatisticalMetrics.cs create mode 100644 FeatureVectorCalculator/SignalChart.cs diff --git a/Common/Common.csproj b/Common/Common.csproj index 5d608daf8..c18b5b1b7 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -76,6 +76,7 @@ + UserControl diff --git a/Common/Iperl/OptoTelegramRaw.cs b/Common/Iperl/OptoTelegramRaw.cs index c17bdff67..6a51f94cf 100644 --- a/Common/Iperl/OptoTelegramRaw.cs +++ b/Common/Iperl/OptoTelegramRaw.cs @@ -35,7 +35,7 @@ namespace Common.Iperl public float RefFlow; /// [m3/h] public int Counter; - public UInt32 EmfRaw; /// From iPerl opto data + public Int32 EmfRaw; /// Signed EMF from iPerl opto data public Int16 MagneticFieldRaw; public Int16 FlowRaw; public UInt32 VolumeRaw; @@ -50,8 +50,7 @@ namespace Common.Iperl /// public double EMF() { - Int32 signedEmf = (EmfRaw > 0x7FFFFF) ? ((int)EmfRaw - 0x1000000) : (int)EmfRaw; - return 0.000000333 * (double)signedEmf; + return 0.000000333 * (double)EmfRaw; } public double MagneticField() { return (double)MagneticFieldRaw; } public double Flow(double scalingFactor) { return 0.225 * scalingFactor * (double)FlowRaw; } @@ -120,7 +119,10 @@ namespace Common.Iperl return false; } - bool f1 = UInt32.TryParse(telegram.Substring(0, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out EmfRaw); + UInt32 uEmfRaw; + bool f1 = UInt32.TryParse(telegram.Substring(0, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out uEmfRaw); + EmfRaw = (uEmfRaw > 0x7FFFFF) ? ((int)uEmfRaw - 0x1000000) : (int)uEmfRaw; + bool f2 = Int16.TryParse(telegram.Substring(7, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out MagneticFieldRaw); bool f3 = Int16.TryParse(telegram.Substring(12, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out FlowRaw); bool f4 = UInt32.TryParse(telegram.Substring(17, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out VolumeRaw); @@ -243,7 +245,7 @@ namespace Common.Iperl DateTime.Second.ToString("D2"), DateTime.Millisecond.ToString("D4"), Counter, - EmfRaw.ToString("X6"), + (EmfRaw & 0x00FFFFFF).ToString("X6"), MagneticFieldRaw.ToString("X4"), FlowRaw.ToString("X4"), VolumeRaw.ToString("X6"), diff --git a/Common/StatisticalMetrics.cs b/Common/StatisticalMetrics.cs new file mode 100644 index 000000000..3332c8386 --- /dev/null +++ b/Common/StatisticalMetrics.cs @@ -0,0 +1,338 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Common.Iperl; + +namespace Common +{ + public static class Extensions + { + public static float[] SubArray(this float[] array, int offset, int length) + { + float[] result = new float[length]; + Array.Copy(array, offset, result, 0, length); + return result; + } + } + + public class StatisticalMetrics + { + const int VectorSize = 7; + const int MinLength = 100; + const float AdcBit = (float)57e-9; + const int NeighbourhoodSize = 30; /// To determine extended outliers + + /// + /// Calculates a 7-dimensional vector with these components: + /// x[0] = X1 = Number of outliers + /// x[1] = X2 = Relative cal-factor shift due to outliers + /// x[2] = X3 = Standard deviation of high-pass filteres ++++ demodulation of EMF (cut-off at Nyquist frequency) + /// x[3] = X4 = Robust standard deviation of high-pass filtered ++++ demodulation + /// x[4] = X5 = Peak-to-peak of low-pass filtered ++++ demodulation of EMF (cut-off TBD) + /// x[5] = X6 = Peak-to-peak of reference flow rate + /// x[6] = X7 = Mean impedance (++-- demodulation) + /// + /// + /// + /// + /// + /// + public static float[] Calculate(OptoTelegramRaw[] optoData, int optoDataCount, int startIx, int endIx, bool downsample, + out float[] offsetV, out float[] kOhmsR, out float[] kOhmsC, out float[] dutFlow, + out float[] refFlow, out float[] flowRatio, out float[] magField, out float[] emfV, + out PointF[] outliers, out PointF[] extendedOutliers) + { + offsetV = kOhmsR = kOhmsC = dutFlow = refFlow = flowRatio = magField = emfV = null; + outliers = extendedOutliers = null; + + if (optoData == null || optoData.Length < optoDataCount || + startIx < 0 || endIx >= optoDataCount || endIx <= startIx + MinLength + 3) return null; + + float[] modulatedData = GetModulatedEmf(optoData, optoDataCount, AdcBit); + + FindShiftAndDemodulate(modulatedData, downsample, + new float[] { +1, +1, +1, +1 }, out offsetV, + new float[] { +1, +1, -1, -1 }, out kOhmsR, + new float[] { +1, -1, -1, +1 }, out kOhmsC, + new float[] { +1, -1, +1, -1 }, out dutFlow); + //offsetV = FirFilter(modulatedData, new float[] { 1, 3, 4, 4, 3, 1}, 0.0625F); + + magField = GetMagField(optoData, optoDataCount, downsample, new float[] { 1, 1, 1, 1 }); + + refFlow = GetRefFlow(optoData, optoDataCount, downsample, new float[] { 1, 1, 1, 1 }); + + flowRatio = new float[Math.Min(dutFlow.Length, refFlow.Length)]; + for (int i = 0; i < flowRatio.Length; i++) + { + flowRatio[i] = (refFlow[i] != 0) ? dutFlow[i] / refFlow[i] : 1; + } + + + float[] x = new float[VectorSize]; + + /// X1 = Number of outliers + int outliersCount = GetOutliers(flowRatio, out outliers, out extendedOutliers); + + + /// X2 = Relative cal-factor shift due to outliers + + /// X3 = Standard deviation of high-pass filteres ++++ demodulation of EMF (cut-off at Nyquist frequency) + + /// X4 = Robust standard deviation of high-pass filtered ++++ demodulation + + /// X5 = Peak-to-peak of low-pass filtered ++++ demodulation of EMF (cut-off TBD) + + /// X6 = Peak-to-peak of reference flow rate + + /// X7 = Mean impedance (++-- demodulation) + double sum = 0; + foreach (var z in kOhmsR) sum += z; + x[6] = Convert.ToSingle(sum / kOhmsR.Length); + + return x; + } + + + static float[] GetModulatedEmf(OptoTelegramRaw[] optoData, int optoDataCount, float factor) + { + if (optoData == null || optoDataCount < 0) return null; + + float[] result = new float[optoDataCount]; + for (int i = 0; i < optoDataCount; i++) + { + result[i] = optoData[i].EmfRaw * factor; + } + return result; + } + + + static float[] GetMagField(OptoTelegramRaw[] optoData, int optoDataCount, bool downsample, float[] kernel) + { + if (optoData == null || optoDataCount < 0 || (downsample && (kernel == null || kernel.Length < 4))) return null; + + int resultLen = downsample ? (optoDataCount / 4) : optoDataCount; + float[] result = new float[resultLen]; + + if (downsample) + { + float ksum = 0; + for (int j = 0; j < kernel.Length; j++) ksum += kernel[j]; + for (int j = 0; j < kernel.Length; j++) kernel[j] /= ksum; + + for (int i = 0; (4 * i) + kernel.Length - 1 < optoDataCount; i++) + { + float sum = 0; + for (int j = 0; j < kernel.Length; j++) sum += Convert.ToSingle(optoData[4 * i + j].MagneticFieldRaw) * kernel[j]; + result[i] = sum; + } + } + else + { + for (int i = 0; i < optoDataCount; i++) result[i] = optoData[i].MagneticFieldRaw; + } + + return result; + } + + + static float[] GetRefFlow(OptoTelegramRaw[] optoData, int optoDataCount, bool downsample, float[] kernel) + { + if (optoData == null || optoDataCount < 0 || (downsample && (kernel == null || kernel.Length < 4))) return null; + + int resultLen = downsample ? (optoDataCount / 4) : optoDataCount; + float[] result = new float[resultLen]; + + if (downsample) + { + float ksum = 0; + for (int j = 0; j < kernel.Length; j++) ksum += kernel[j]; + for (int j = 0; j < kernel.Length; j++) kernel[j] /= ksum; + + for (int i = 0; (4 * i) + kernel.Length - 1 < optoDataCount; i++) + { + float sum = 0; + for (int j = 0; j < kernel.Length; j++) sum += Convert.ToSingle(optoData[4 * i + j].RefFlow) * kernel[j]; + result[i] = sum; + } + } + else + { + for (int i = 0; i < optoDataCount; i++) result[i] = optoData[i].RefFlow; + } + + return result; + } + + + public static void FindShiftAndDemodulate(float[] modulatedData, bool downsample, + float[] kernel1, out float[] data1, + float[] kernel2, out float[] data2, + float[] kernel3, out float[] data3, + float[] kernel4, out float[] data4) + { + int shift = GetShift(modulatedData.SubArray(0, MinLength)); + + data1 = (kernel1 != null) ? Demodulate(modulatedData, kernel1, shift, downsample) : null; + data2 = (kernel2 != null) ? Demodulate(modulatedData, kernel2, shift, downsample) : null; + data3 = (kernel3 != null) ? Demodulate(modulatedData, kernel3, shift, downsample) : null; + data4 = (kernel4 != null) ? Demodulate(modulatedData, kernel4, shift, downsample) : null; + } + + + /// + /// Unlike FIR filtering, demodulation shifts the kernel in 4 phases + /// + /// Input data + /// Demodulation kernel + /// shift 0..3 + /// Output data + static float[] Demodulate(float[] data, float[] kernel, int shift, bool downsample) + { + if (data == null || data.Length < MinLength || kernel == null || kernel.Length != 4) + { + return null; + } + + float ksum = 0; + for (int i = 0; i < 4; i++) ksum += Math.Abs(kernel[i]); + for (int i = 0; i < 4; i++) kernel[i] /= ksum; + + int rsltLen = downsample ? (data.Length - 3) / 4 : data.Length - 3; + float[] result = new float[rsltLen]; + + if (downsample) + { + for (int i = shift; i < 4 * rsltLen; i += 4) + { + float sum = 0; + for (int j = 0; j < 4; j++) sum += data[i + j] * kernel[(i + j + 4 - shift) % 4]; + result[i / 4] = sum; + } + } + else + { + for (int i = 0; i < rsltLen; i++) + { + float sum = 0; + for (int j = 0; j < 4; j++) sum += data[i + j] * kernel[(i + j + 4 - shift) % 4]; + result[i] = sum; + } + } + + return result; + } + + + /// + /// Determine modulation phase by maximizing ++-- demodulation result. + /// + /// Input data + /// 0..3 = modulation phase or, -1 = error + static int GetShift(float[] modulatedData) + { + float[] kernel = new float[4] { 1, 1, -1, -1 }; + int maximizingShift = -1; + float maximum = float.MinValue; + + for (int shift = 0; shift <= 3; shift++) + { + var demodulatedCandidate = Demodulate(modulatedData, kernel, shift, false); + float sum = 0; + foreach (var d in demodulatedCandidate) sum += d; + if (sum > maximum) + { + maximum = sum; + maximizingShift = shift; + } + } + + return maximizingShift; + } + + + /// + /// FIR filtering = convolution with a kernel + /// + /// Input data + /// Convolution kernel + /// Output data + static float[] FirFilter(float[] data, float[] kernel, float factor) + { + if (data == null || kernel == null) return null; + + int kernelLen = kernel.Length; + int rsltLen = data.Length - kernelLen + 1; + if (rsltLen < 0) return null; + + float[] result = new float[rsltLen]; + for (int i = 0; i < rsltLen; i++) + { + float sum = 0; + for (int j = 0; j < kernelLen; j++) sum += data[i + j] * kernel[j]; + result[i] = sum * factor; + } + return result; + } + + + static int GetOutliers(float[] flowRatio, out PointF[] outliers, out PointF[] extendedOutliers) + { + float mean = Enumerable.Average(flowRatio); + var fr = new float[flowRatio.Length]; + for (int i = 0; i < fr.Length; i++) fr[i] = flowRatio[i] - mean; + + Array.Sort(fr); + float qLo = fr[fr.Length / 4]; + float qHi = fr[3 * fr.Length / 4]; + + int N = fr.Length / 2; + float sumY = 0; + float sumYY = 0; + for (int i = fr.Length / 4; i < 3 * fr.Length / 4; i++) + { + sumY += fr[i]; + sumYY += fr[i] * fr[i]; + } + float std = (float)Math.Sqrt(sumYY / N - (sumY / N) * (sumY / N)); + float robustStd = std * 5.1812824F; + float threshold = 7 * robustStd; + + /// Restore fr as it was before sorting + for (int i = 0; i < fr.Length; i++) fr[i] = flowRatio[i] - mean; + + IList listOfOutliers = new List(); + bool[] boolExtendedOutliers = new bool[fr.Length]; + int outliersCount = 0; + for (int i = 0; i < fr.Length; i++) + { + if (fr[i] < -threshold || fr[i] > threshold) + { + /// This is an outlier + listOfOutliers.Add(new PointF(Convert.ToSingle(i), fr[i] + mean)); + outliersCount++; + for (int j = Math.Max(0, i - NeighbourhoodSize); j <= Math.Min(i + NeighbourhoodSize, fr.Length - 1); j++) + { + boolExtendedOutliers[j] = true; + } + } + } + + IList listOfExtendedOutliers = new List(); + for (int i = 0; i < fr.Length; i++) + { + if (boolExtendedOutliers[i]) + { + listOfExtendedOutliers.Add(new PointF(Convert.ToSingle(i), fr[i] + mean)); + } + } + + outliers = listOfOutliers.ToArray(); + extendedOutliers = listOfExtendedOutliers.ToArray(); + + return outliersCount; + } + } +} diff --git a/FeatureVectorCalculator/CalculatorWnd.Designer.cs b/FeatureVectorCalculator/CalculatorWnd.Designer.cs index c84bdc799..b1945f854 100644 --- a/FeatureVectorCalculator/CalculatorWnd.Designer.cs +++ b/FeatureVectorCalculator/CalculatorWnd.Designer.cs @@ -30,21 +30,46 @@ { this.rawFileComboBox = new System.Windows.Forms.ComboBox(); this.browseButton = new System.Windows.Forms.Button(); - this.processButton = new System.Windows.Forms.Button(); this.resultsTextBox = new System.Windows.Forms.TextBox(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.splitContainer2 = new System.Windows.Forms.SplitContainer(); + this.splitContainer3 = new System.Windows.Forms.SplitContainer(); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.tabPage3 = new System.Windows.Forms.TabPage(); + this.tabPage4 = new System.Windows.Forms.TabPage(); + this.tabPage5 = new System.Windows.Forms.TabPage(); + this.tabPage6 = new System.Windows.Forms.TabPage(); + this.tabPage7 = new System.Windows.Forms.TabPage(); + this.tabPage8 = new System.Windows.Forms.TabPage(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); + this.splitContainer2.Panel1.SuspendLayout(); + this.splitContainer2.Panel2.SuspendLayout(); + this.splitContainer2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).BeginInit(); + this.splitContainer3.Panel1.SuspendLayout(); + this.splitContainer3.Panel2.SuspendLayout(); + this.splitContainer3.SuspendLayout(); + this.tabControl1.SuspendLayout(); this.SuspendLayout(); // // rawFileComboBox // + this.rawFileComboBox.Dock = System.Windows.Forms.DockStyle.Fill; this.rawFileComboBox.FormattingEnabled = true; - this.rawFileComboBox.Location = new System.Drawing.Point(29, 28); + this.rawFileComboBox.Location = new System.Drawing.Point(0, 0); this.rawFileComboBox.Name = "rawFileComboBox"; - this.rawFileComboBox.Size = new System.Drawing.Size(715, 21); + this.rawFileComboBox.Size = new System.Drawing.Size(1040, 21); this.rawFileComboBox.TabIndex = 0; // // browseButton // - this.browseButton.Location = new System.Drawing.Point(769, 19); + this.browseButton.Location = new System.Drawing.Point(7, 0); this.browseButton.Name = "browseButton"; this.browseButton.Size = new System.Drawing.Size(66, 37); this.browseButton.TabIndex = 1; @@ -52,37 +77,184 @@ this.browseButton.UseVisualStyleBackColor = true; this.browseButton.Click += new System.EventHandler(this.browseButton_Click); // - // processButton - // - this.processButton.Location = new System.Drawing.Point(29, 66); - this.processButton.Name = "processButton"; - this.processButton.Size = new System.Drawing.Size(91, 46); - this.processButton.TabIndex = 2; - this.processButton.Text = "Process"; - this.processButton.UseVisualStyleBackColor = true; - this.processButton.Click += new System.EventHandler(this.processButton_Click); - // // resultsTextBox // - this.resultsTextBox.Location = new System.Drawing.Point(29, 131); + this.resultsTextBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.resultsTextBox.Location = new System.Drawing.Point(0, 0); this.resultsTextBox.Multiline = true; this.resultsTextBox.Name = "resultsTextBox"; - this.resultsTextBox.Size = new System.Drawing.Size(715, 181); + this.resultsTextBox.Size = new System.Drawing.Size(1127, 156); this.resultsTextBox.TabIndex = 3; // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.splitContainer2); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.tabControl1); + this.splitContainer1.Size = new System.Drawing.Size(1127, 733); + this.splitContainer1.SplitterDistance = 200; + this.splitContainer1.TabIndex = 4; + // + // splitContainer2 + // + this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.splitContainer2.Location = new System.Drawing.Point(0, 0); + this.splitContainer2.Name = "splitContainer2"; + this.splitContainer2.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer2.Panel1 + // + this.splitContainer2.Panel1.Controls.Add(this.splitContainer3); + // + // splitContainer2.Panel2 + // + this.splitContainer2.Panel2.Controls.Add(this.resultsTextBox); + this.splitContainer2.Size = new System.Drawing.Size(1127, 200); + this.splitContainer2.SplitterDistance = 40; + this.splitContainer2.TabIndex = 3; + // + // splitContainer3 + // + this.splitContainer3.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer3.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer3.Location = new System.Drawing.Point(0, 0); + this.splitContainer3.Name = "splitContainer3"; + // + // splitContainer3.Panel1 + // + this.splitContainer3.Panel1.Controls.Add(this.rawFileComboBox); + // + // splitContainer3.Panel2 + // + this.splitContainer3.Panel2.Controls.Add(this.browseButton); + this.splitContainer3.Size = new System.Drawing.Size(1127, 40); + this.splitContainer3.SplitterDistance = 1040; + this.splitContainer3.TabIndex = 0; + // + // tabControl1 + // + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Controls.Add(this.tabPage3); + this.tabControl1.Controls.Add(this.tabPage4); + this.tabControl1.Controls.Add(this.tabPage5); + this.tabControl1.Controls.Add(this.tabPage6); + this.tabControl1.Controls.Add(this.tabPage7); + this.tabControl1.Controls.Add(this.tabPage8); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControl1.Location = new System.Drawing.Point(0, 0); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(1127, 529); + this.tabControl1.TabIndex = 0; + // + // tabPage1 + // + this.tabPage1.Location = new System.Drawing.Point(4, 22); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Padding = new System.Windows.Forms.Padding(3); + this.tabPage1.Size = new System.Drawing.Size(1119, 503); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "OffsetV"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // tabPage2 + // + this.tabPage2.Location = new System.Drawing.Point(4, 22); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3); + this.tabPage2.Size = new System.Drawing.Size(1119, 503); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "KOhmsR"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // tabPage3 + // + this.tabPage3.Location = new System.Drawing.Point(4, 22); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.Size = new System.Drawing.Size(1119, 503); + this.tabPage3.TabIndex = 2; + this.tabPage3.Text = "KOhmsC"; + this.tabPage3.UseVisualStyleBackColor = true; + // + // tabPage4 + // + this.tabPage4.Location = new System.Drawing.Point(4, 22); + this.tabPage4.Name = "tabPage4"; + this.tabPage4.Size = new System.Drawing.Size(1119, 503); + this.tabPage4.TabIndex = 3; + this.tabPage4.Text = "DutFlowLph"; + this.tabPage4.UseVisualStyleBackColor = true; + // + // tabPage5 + // + this.tabPage5.Location = new System.Drawing.Point(4, 22); + this.tabPage5.Name = "tabPage5"; + this.tabPage5.Size = new System.Drawing.Size(1119, 503); + this.tabPage5.TabIndex = 4; + this.tabPage5.Text = "RefFlowLph"; + this.tabPage5.UseVisualStyleBackColor = true; + // + // tabPage6 + // + this.tabPage6.Location = new System.Drawing.Point(4, 22); + this.tabPage6.Name = "tabPage6"; + this.tabPage6.Size = new System.Drawing.Size(1119, 503); + this.tabPage6.TabIndex = 5; + this.tabPage6.Text = "FlowRatio"; + this.tabPage6.UseVisualStyleBackColor = true; + // + // tabPage7 + // + this.tabPage7.Location = new System.Drawing.Point(4, 22); + this.tabPage7.Name = "tabPage7"; + this.tabPage7.Size = new System.Drawing.Size(1119, 503); + this.tabPage7.TabIndex = 6; + this.tabPage7.Text = "MagField"; + this.tabPage7.UseVisualStyleBackColor = true; + // + // tabPage8 + // + this.tabPage8.Location = new System.Drawing.Point(4, 22); + this.tabPage8.Name = "tabPage8"; + this.tabPage8.Size = new System.Drawing.Size(1119, 503); + this.tabPage8.TabIndex = 7; + this.tabPage8.Text = "EmfV"; + this.tabPage8.UseVisualStyleBackColor = true; + // // CalculatorWnd // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(859, 335); - this.Controls.Add(this.resultsTextBox); - this.Controls.Add(this.processButton); - this.Controls.Add(this.browseButton); - this.Controls.Add(this.rawFileComboBox); + this.ClientSize = new System.Drawing.Size(1127, 733); + this.Controls.Add(this.splitContainer1); this.Name = "CalculatorWnd"; this.Text = "Feature vector calculator"; + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.splitContainer2.Panel1.ResumeLayout(false); + this.splitContainer2.Panel2.ResumeLayout(false); + this.splitContainer2.Panel2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); + this.splitContainer2.ResumeLayout(false); + this.splitContainer3.Panel1.ResumeLayout(false); + this.splitContainer3.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit(); + this.splitContainer3.ResumeLayout(false); + this.tabControl1.ResumeLayout(false); this.ResumeLayout(false); - this.PerformLayout(); } @@ -90,8 +262,19 @@ private System.Windows.Forms.ComboBox rawFileComboBox; private System.Windows.Forms.Button browseButton; - private System.Windows.Forms.Button processButton; private System.Windows.Forms.TextBox resultsTextBox; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.SplitContainer splitContainer2; + private System.Windows.Forms.SplitContainer splitContainer3; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.TabPage tabPage3; + private System.Windows.Forms.TabPage tabPage4; + private System.Windows.Forms.TabPage tabPage5; + private System.Windows.Forms.TabPage tabPage6; + private System.Windows.Forms.TabPage tabPage7; + private System.Windows.Forms.TabPage tabPage8; } } diff --git a/FeatureVectorCalculator/CalculatorWnd.cs b/FeatureVectorCalculator/CalculatorWnd.cs index a425c59cf..6e3445b84 100644 --- a/FeatureVectorCalculator/CalculatorWnd.cs +++ b/FeatureVectorCalculator/CalculatorWnd.cs @@ -4,13 +4,20 @@ using System; using System.IO; using System.Windows.Forms; +using System.Windows.Forms.DataVisualization.Charting; using Common.Iperl; +using System.Globalization; +using System.Drawing; namespace FeatureVectorCalculator { public partial class CalculatorWnd : Form { public const int MaxOptoDataCount = 40000; + public const bool Downsample = true; + public const bool Extend = true; + + OptoTelegramRaw[] optoData; Int64 volumeRawExtLast; Int64 timestampExtLast; @@ -27,63 +34,115 @@ namespace FeatureVectorCalculator } } + void ClearOutput() + { + resultsTextBox.Clear(); + tabControl1.TabPages[0].Controls.Clear(); + tabControl1.TabPages[1].Controls.Clear(); + tabControl1.TabPages[2].Controls.Clear(); + tabControl1.TabPages[3].Controls.Clear(); + tabControl1.TabPages[4].Controls.Clear(); + tabControl1.TabPages[5].Controls.Clear(); + tabControl1.TabPages[6].Controls.Clear(); + tabControl1.TabPages[7].Controls.Clear(); + } + private void browseButton_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); - var dr = ofd.ShowDialog(); - rawFileComboBox.Text = ofd.FileName; + + if (ofd.ShowDialog() == DialogResult.OK) + { + rawFileComboBox.Text = ofd.FileName; + ProcessRawDataFile(ofd.FileName); + } } - private void processButton_Click(object sender, EventArgs e) + void ProcessRawDataFile(string fileName) { - if (optoData == null) return; - int counter = 0; int startIx = -1; int endIx = -1; - using (StreamReader reader = new StreamReader(rawFileComboBox.Text)) + ClearOutput(); + + resultsTextBox.Text = string.Format("File name: {0}{1}", fileName, Environment.NewLine); + + try { - string line; - while ((line = reader.ReadLine()) != null && counter < MaxOptoDataCount) + using (StreamReader reader = new StreamReader(fileName)) { - int ix = line.IndexOf(" :\t"); - - if (ix >= 0) + string line; + while ((line = reader.ReadLine()) != null && counter < MaxOptoDataCount) { - if (optoData[counter].UpdateFromString(line.Substring(ix + 3), counter, 0, ref volumeRawExtLast, ref timestampExtLast, true)) + int ix = line.IndexOf(" :\t"); + string[] items = line.Split('\t'); + + if (items.Length > 18) { - if (line.EndsWith("#### start test ####")) + string telegram = string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\r\n", + items[2], items[3], items[4], items[5], items[6], items[7], items[8]); + + if (optoData[counter].UpdateFromString(telegram, counter, 0, ref volumeRawExtLast, ref timestampExtLast)) { - startIx = counter; - optoData[counter].Flags = OptoTelegramFlags.OK_TestStart; - } - else if (line.EndsWith("#### end of test ####")) - { - endIx = counter; - optoData[counter].Flags = OptoTelegramFlags.OK_TestEnd; + if (line.EndsWith("#### start test ####")) + { + startIx = counter; + optoData[counter].Flags = OptoTelegramFlags.OK_TestStart; + } + else if (line.EndsWith("#### end of test ####")) + { + endIx = counter; + optoData[counter].Flags = OptoTelegramFlags.OK_TestEnd; + } + else + { + optoData[counter].Flags = OptoTelegramFlags.OK; + } } + + optoData[counter].RefFlow = float.Parse(items[15].Replace(',', '.'), CultureInfo.InvariantCulture); } - } - else - { - optoData[counter].Flags = OptoTelegramFlags.InvalidTelegram; + else + { + optoData[counter].Flags = OptoTelegramFlags.InvalidTelegram; + } + + counter++; } - counter++; + reader.Close(); } - - reader.Close(); } - - float[] featureVector = new float[9]; - Common.Utils.CalculateFeatureVector(optoData, counter, startIx, endIx, ref featureVector); - - resultsTextBox.Clear(); - for (int i = 0; i < 9; i++) + catch (Exception exc) { - resultsTextBox.Text += string.Format("X{0} = {1}\r\n", i + 1, featureVector[i]); + MessageBox.Show(string.Format("Exception: {0}", exc.Message)); + return; } + + int start = Extend ? 0 : startIx; + int end = Extend ? (counter - 1) : endIx; + float[] offsetV, kOhmsR, kOhmsC, dutFlow, refFlow, flowRatio, magField, emfV; + PointF[] outliers, extendedOutliers; + float[] featureVector = Common.StatisticalMetrics.Calculate(optoData, counter, start, end, Downsample, + out offsetV, out kOhmsR, out kOhmsC, out dutFlow, + out refFlow, out flowRatio, out magField, out emfV, + out outliers, out extendedOutliers); + + for (int i = 0; i < Math.Min(9, featureVector.Length); i++) + { + resultsTextBox.Text += string.Format("X{0} = {1}{2}", i + 1, featureVector[i], Environment.NewLine); + } + + float period = Downsample ? 0.5F : 0.125F; + if (offsetV != null) tabControl1.TabPages[0].Controls.Add(SignalChart.GetChart(offsetV, period, "OffsetV", "OffsetV [μV]")); + if (kOhmsR != null) tabControl1.TabPages[1].Controls.Add(SignalChart.GetChart(kOhmsR, period, "kOhmsR", "kΩ")); + if (kOhmsC != null) tabControl1.TabPages[2].Controls.Add(SignalChart.GetChart(kOhmsC, period, "kOhmsC", "kΩ")); + if (dutFlow != null) tabControl1.TabPages[3].Controls.Add(SignalChart.GetChart(dutFlow, period, "dutFlow", "L/h")); + if (refFlow != null) tabControl1.TabPages[4].Controls.Add(SignalChart.GetChart(refFlow, period, "refFlow", "L/h")); + if (flowRatio != null) tabControl1.TabPages[5].Controls.Add(SignalChart.GetChart(flowRatio, period, "flowRatio", "", true, extendedOutliers, outliers)); + if (magField != null) tabControl1.TabPages[6].Controls.Add(SignalChart.GetChart(magField, period, "magField", "μV", false)); + if (emfV != null) tabControl1.TabPages[7].Controls.Add(SignalChart.GetChart(emfV, period, "emfV", "μV")); } } } diff --git a/FeatureVectorCalculator/FeatureVectorCalculator.csproj b/FeatureVectorCalculator/FeatureVectorCalculator.csproj index 827349aa4..0c4ba5275 100644 --- a/FeatureVectorCalculator/FeatureVectorCalculator.csproj +++ b/FeatureVectorCalculator/FeatureVectorCalculator.csproj @@ -37,6 +37,7 @@ + @@ -55,6 +56,7 @@ + CalculatorWnd.cs diff --git a/FeatureVectorCalculator/SignalChart.cs b/FeatureVectorCalculator/SignalChart.cs new file mode 100644 index 000000000..645a52de4 --- /dev/null +++ b/FeatureVectorCalculator/SignalChart.cs @@ -0,0 +1,108 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using System.Windows.Forms.DataVisualization.Charting; + +namespace FeatureVectorCalculator +{ + public class SignalChart + { + public static Chart GetChart(float[] data, float period, string caption, string captionY, bool fromZero = true, + PointF[] extendedOutliers = null, PointF[] outliers = null) + { + Series series1 = null, series2 = null, series3 = null; + + series1 = new Series { Name = caption, ChartArea = "area1", ChartType = SeriesChartType.Line, Color = Color.DeepSkyBlue }; + float minY = float.MaxValue; + float maxY = float.MinValue; + for (int x = 0; x < data.Length; x++) + { + float y = data[x]; + if (minY > y) minY = y; + if (maxY < y) maxY = y; + series1.Points.AddXY(x * period, y); + } + + if (extendedOutliers != null) + { + series2 = new Series { Name = "extended outliers", ChartArea = "area1", ChartType = SeriesChartType.Point, Color = Color.Yellow }; + for (int i = 0; i < extendedOutliers.Length; i++) + { + series2.Points.AddXY(extendedOutliers[i].X * period, extendedOutliers[i].Y); + } + } + + if (outliers != null) + { + series3 = new Series { Name = "outliers", ChartArea = "area1", ChartType = SeriesChartType.Point, Color = Color.DarkRed }; + for (int i = 0; i < outliers.Length; i++) + { + series3.Points.AddXY(outliers[i].X * period, outliers[i].Y); + } + } + + ChartArea chArea = new ChartArea { Name = "area1" }; + chArea.AxisX.Title = "Time [s]"; + chArea.AxisX.IsLogarithmic = false; + chArea.AxisX.IsLabelAutoFit = true; + chArea.AxisX.Minimum = 0; + chArea.AxisX.Maximum = (data.Length - 1) * period; + // for (int j = i; j < i + 3; j++) + // { + // CustomLabel cl = new CustomLabel(); + // cl.Text = string.Format("{0} {1}", xs[j], flowUnit.ToDescription()); + // cl.FromPosition = Math.Log10(xs[j] * spacer); + // cl.ToPosition = Math.Log10(xs[j] / spacer); + // chArea.AxisX.CustomLabels.Add(cl); + // } + + chArea.AxisY.Title = captionY; + chArea.AxisY.IsLogarithmic = false; + chArea.AxisY.IsLabelAutoFit = true; + + if (!fromZero) + { + /// fromZero = false + chArea.AxisY.IsStartedFromZero = false; + float diff = maxY - minY; + chArea.AxisY.Minimum = minY - 0.05 * diff; + chArea.AxisY.Maximum = maxY + 0.05 * diff; + } + else if (minY >= 0) + { + /// fromZero = true, All points are above zero + chArea.AxisY.IsStartedFromZero = true; + chArea.AxisY.Minimum = 0; + chArea.AxisY.Maximum = 1.05 * maxY; + } + else if (maxY <= 0) + { + /// fromZero = true, All points are below zero + chArea.AxisY.IsStartedFromZero = true; + chArea.AxisY.Minimum = 1.05 * minY; + chArea.AxisY.Maximum = 0; + } + else + { + /// Points are both below and above zero + chArea.AxisY.IsStartedFromZero = false; + float diff = maxY - minY; + chArea.AxisY.Minimum = minY - 0.05 * diff; + chArea.AxisY.Maximum = maxY + 0.05 * diff; + } + + if (chArea.AxisY.Maximum == chArea.AxisY.Minimum) + { + chArea.AxisY.Maximum = chArea.AxisY.Maximum + 1; + } + + Chart chart = new Chart { Text = caption, Dock = DockStyle.Fill }; + chart.ChartAreas.Add(chArea); + chart.Series.Add(series1); + if (series2 != null) chart.Series.Add(series2); + if (series3 != null) chart.Series.Add(series3); + + return chart; + } + } +}