using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Threading.Tasks; using System.Windows.Media; using DTS.Common.Base; using DTS.Common.Converters; using DTS.Common.Enums.DASFactory; using DTS.Common.Interface; using DTS.Common.Utils; using DTS.Common.Utilities; using DTS.Common.Utilities.Logging; using DTS.Viewer.Graph.Model; using DTS.Common.Events; using Prism.Events; // ReSharper disable CheckNamespace // ReSharper disable InconsistentNaming // ReSharper disable UnusedMember.Local // ReSharper disable UnassignedGetOnlyAutoProperty namespace DTS.Viewer.Graph { public class TestDataSeriesModel : IBaseModel { public IGraphViewModel Parent { get; set; } public IEventAggregator _eventAggregator { get; set; } private IChartOptionsModel ChartOptions { get; set; } private string _errorMessage = string.Empty; public string ErrorMessage { get => _errorMessage; set { _errorMessage = value; OnPropertyChanged("ErrorMessage"); } } private SetReadCalcProgressValueDelegate ReadCalcProgressDelegate { get; set; } private void SetReadCalcProgressValue(string message, double progress) { _eventAggregator.GetEvent().Publish(new GraphChannelReadCalcProgressChangedEventArgs() { ProgressMessage = message, ProgressPercent = progress, GraphVM = (GraphViewModel)Parent }); } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task GetTestDataAsync(ITestChannel channel, IChartOptionsModel chartOptions, bool bVolts, IPSDReportSettingsModel psdSettings = null) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { return GetTestData(channel, chartOptions, bVolts, psdSettings); } #pragma warning disable 1998 public async Task> GetTestDataAsync(List channels, IChartOptionsModel chartOptions, bool bVolts, IPSDReportSettingsModel psdSettings = null) #pragma warning restore 1998 { var testData = channels.Select(channel => { try { chartOptions.IsDigitalChannel = channel.Bridge.StartsWith(Common.Enums.Sensors.SensorConstants.BridgeType.DigitalInput.ToString()); return GetTestData(channel, chartOptions, bVolts, psdSettings); } catch (DTS.Common.Exceptions.OutOfDataException ex) { throw new Exception($"Failed to read {channel.BinaryFileName} sample {ex.Index}"); } catch (Exception ex) { var msg = $"Failed to read {channel.BinaryFileName}"; APILogger.Log(msg, ex); throw new Exception(msg); } }).ToList(); if (null != psdSettings && psdSettings.ShowEnvelope) { testData.Add(GetEnvelopeChannel(testData)); } return testData; } public List GetTestData(List channels, IChartOptionsModel chartOptions, bool bVolts, IPSDReportSettingsModel psdSettings = null) { var testData = channels.Select(channel => { try { chartOptions.IsDigitalChannel = channel.Bridge.StartsWith(Common.Enums.Sensors.SensorConstants.BridgeType.DigitalInput.ToString()); return GetTestData(channel, chartOptions, bVolts, psdSettings); } catch (DTS.Common.Exceptions.OutOfDataException ex) { throw new Exception($"Failed to read {channel.BinaryFileName} sample {ex.Index}"); } catch (Exception ex) { var msg = $"Failed to read {channel.BinaryFileName}"; APILogger.Log(msg, ex); throw new Exception(msg); } }).ToList(); if (null != psdSettings && psdSettings.ShowEnvelope) { testData.Add(GetEnvelopeChannel(testData)); } return testData; } public ITestDataSeries GetTestData(ITestChannel channel, IChartOptionsModel chartOptions, bool bVolts, IPSDReportSettingsModel psdSettings = null) { return AddTestChannelToChart(channel, chartOptions, bVolts, psdSettings); } private const string IEPE_BRIDGE = "IEPE"; public TestDataSeries AddTestChannelToChart(ITestChannel channel, IChartOptionsModel chartOptions, bool bVolts, IPSDReportSettingsModel psdSettings = null) { if (!string.IsNullOrEmpty(channel.ErrorMessage)) return null; if (null == ReadCalcProgressDelegate) ReadCalcProgressDelegate = SetReadCalcProgressValue; //keep FFT always unfiltered if (chartOptions.UnitType == Common.Enums.Viewer.ChartUnitTypeEnum.FFT || chartOptions.UnitType == Common.Enums.Viewer.ChartUnitTypeEnum.PSD) { channel.SoftwareFilter = "none"; } var channelData = Serialization.SliceRaw.File.Reader.ReadChannelsBinaryData(channel, out var frequencies, out var peakMagnitude, out var peakFrequency, ChannelFilter.AdHoc, chartOptions, bVolts, ReadCalcProgressDelegate); if (!string.IsNullOrEmpty(channel.ErrorMessage)) return null; //try to get a translated version of the recording mode var recordingModeString = channel.ParentModule.RecordingMode; try { if (Enum.TryParse(channel.ParentModule.RecordingMode, out DFConstantsAndEnums.RecordingMode mode)) { var recordingMode = RecordingModeExtensions.ToRecordingModes(mode); recordingModeString = EnumDescriptionTypeConverter.GetEnumDescription(recordingMode); } } catch (Exception) { } var chData = new TestDataSeries() { TestGroup = channel.Group, TestId = channel.TestId, TestSetupName = channel.TestSetupName, ChannelId = channel.ChannelId, GroupName = channel.ChannelGroupName, HardwareChannel = channel.HardwareChannelName, Bridge = channel.Bridge, SWAAF = channel.SoftwareFilter, HWAAF = channel.ParentModule.AaFilterRateHz.ToString(CultureInfo.CurrentCulture), SampleRate = channel.ParentModule.SampleRateHz.ToString(CultureInfo.CurrentCulture), RecordingMode = recordingModeString, ISOCode = channel.IsoCode, ISOChannelName = channel.IsoChannelName, UserCode = channel.UserCode, UserChannelName = channel.UserChannelName, ChannelName = channel.ChannelName2, Description = channel.Description, SensorSN = channel.SerialNumber, Excitation = channel.Bridge == IEPE_BRIDGE ? "---" : channel.MeasuredExcitationVoltage.ToString("N3"), Polarity = channel.SensorPolarity, EngineeringUnits = channel.Eu }; var y = new List((int)channelData[1].Length); var x = new List((int)channelData[0].Length); var curSample = 0; var timeMultiplier = Convert.ToDecimal(chartOptions.TimeUnitType == Common.Enums.Viewer.TimeUnitTypeEnum.MS ? 1000D : 1D); if (chartOptions.UnitType == Common.Enums.Viewer.ChartUnitTypeEnum.FFT && null == psdSettings) //FFT (and also not PSD) { chData.FFT = true; chData.PeakFrequency = peakFrequency; chData.PeakMagnitude = peakMagnitude; chData.Xvalue = channelData[0]; chData.Yvalue = channelData[1]; chData.SetStatsFromYValues(); } else if (null == psdSettings) //regular data? { chData.FFT = false; var timeZeroIndex = channel.ParentModule.TriggerSampleNumbers.Count != 0 ? channel.ParentModule.TriggerSampleNumbers[0] : 0; var timeUnitRatio = channel.ParentModule.SampleRateHz / timeMultiplier; var startingSample = (int)timeZeroIndex - channel.ParentModule.StartRecordSampleNumber; if (channel.HIC != 0 && channel.T2Sample > 0) { chData.HIC = true; chData.HICValue = channel.HIC.ToString("N2"); var time1 = (channel.T1Sample - (double)startingSample) / channel.SampleRateHz; var time2 = (channel.T2Sample - (double)startingSample) / channel.SampleRateHz; chData.T1Time = (time1 * 1000).ToString("N4"); chData.T2Time = (time2 * 1000).ToString("N4"); } chData.Xvalue = channelData[0].Select(val => Convert.ToDouble((decimal)val * timeMultiplier)).ToArray(); chData.Yvalue = channelData[1]; chData.SetStatsFromChannel(channel); } else //PSD { _eventAggregator.GetEvent().Publish(new GraphChannelReadCalcProgressChangedEventArgs() { GraphVM = Parent, ProgressMessage = DTS.Common.Strings.Strings.GeneratingPSD_ResizingData, ProgressPercent = 0 }); var timeZeroIndex = channel.ParentModule.TriggerSampleNumbers.Count != 0 ? channel.ParentModule.TriggerSampleNumbers[0] : 0; var timeUnitRatio = channel.ParentModule.SampleRateHz / timeMultiplier; var startingSample = (int)timeZeroIndex - channel.ParentModule.StartRecordSampleNumber; //Apply PSD Settings //Step 1: trim data to selected range var selectStart = (int)(startingSample + psdSettings.DataStart * (double)timeUnitRatio); var selectEnd = (int)(startingSample + psdSettings.DataEnd * (double)timeUnitRatio); if (selectEnd > selectStart) { channelData[0] = channelData[0].Skip(selectStart).Take(selectEnd - selectStart).ToArray(); channelData[1] = channelData[1].Skip(selectStart).Take(selectEnd - selectStart).ToArray(); } else { selectEnd = curSample; } _eventAggregator.GetEvent().Publish(new GraphChannelReadCalcProgressChangedEventArgs() { GraphVM = Parent, ProgressMessage = string.Empty, ProgressPercent = 50 }); //Step 2: get window type FftSharp.WindowType type; switch (psdSettings.WindowType) { case Common.Enums.Viewer.Reports.WindowType.Rectangle: type = FftSharp.WindowType.Rectangular; break; case Common.Enums.Viewer.Reports.WindowType.Hamming: type = FftSharp.WindowType.Hamming; break; case Common.Enums.Viewer.Reports.WindowType.Blackman: type = FftSharp.WindowType.Blackman; break; case Common.Enums.Viewer.Reports.WindowType.BlackmanHarris: type = FftSharp.WindowType.BlackmanHarris; break; case Common.Enums.Viewer.Reports.WindowType.FlatTop: type = FftSharp.WindowType.FlatTop; break; case Common.Enums.Viewer.Reports.WindowType.Hanning: default: type = FftSharp.WindowType.Hanning; break; } FftSharp.WindowAveragingType averagingType; switch (psdSettings.WindowAveragingType) { case Common.Enums.Viewer.Reports.WindowAveragingType.PeakHoldMax: averagingType = FftSharp.WindowAveragingType.PeakHoldMax; break; case Common.Enums.Viewer.Reports.WindowAveragingType.PeakHoldMin: averagingType = FftSharp.WindowAveragingType.PeakHoldMin; break; case Common.Enums.Viewer.Reports.WindowAveragingType.Averaging: default: averagingType = FftSharp.WindowAveragingType.Averaging; break; } //Step 3: ffts require input length be an even power of 2 var next = Utils.GetEnclosingPower2(channelData[1].Length); var values = channelData[1]; if (values.Length < next) { Array.Resize(ref values, next); } _eventAggregator.GetEvent().Publish(new GraphChannelReadCalcProgressChangedEventArgs() { GraphVM = Parent, ProgressMessage = string.Empty, ProgressPercent = 100 }); ////Step 4: apply a band pass on the input if requested _eventAggregator.GetEvent().Publish(new GraphChannelReadCalcProgressChangedEventArgs() { GraphVM = Parent, ProgressMessage = DTS.Common.Strings.Strings.GeneratingPSD_ApplyingFilters, ProgressPercent = 0 }); if (psdSettings.LowPassFilterEnabled) { values = Exocortex.DSP.PassFilter.LowPass(values, channel.ParentModule.SampleRateHz, (double)psdSettings.LowPassFilterFrequency, (Exocortex.DSP.PassFilterType)psdSettings.LowPassFilterType, (uint)psdSettings.LowPassFilterOrder); } if (psdSettings.HighPassFilterEnabled) { values = Exocortex.DSP.PassFilter.HighPass(values, channel.ParentModule.SampleRateHz, (double)psdSettings.HighPassFilterFrequency, (Exocortex.DSP.PassFilterType)psdSettings.HighPassFilterType, (uint)psdSettings.HighPassFilterOrder); } _eventAggregator.GetEvent().Publish(new GraphChannelReadCalcProgressChangedEventArgs() { GraphVM = Parent, ProgressMessage = string.Empty, ProgressPercent = 100 }); //Step 5: get the PSD _eventAggregator.GetEvent().Publish(new GraphChannelReadCalcProgressChangedEventArgs() { GraphVM = Parent, ProgressMessage = DTS.Common.Strings.Strings.GeneratingPSD, ProgressPercent = 0 }); var psd = FftSharp.Transform.PSD_Welch(values, channel.ParentModule.SampleRateHz, type, (int)psdSettings.WindowWidth, (int)psdSettings.WindowOverlappingPercent, averagingType, ReadCalcProgressDelegate); var freq = FftSharp.Transform.FFTfreq(channel.ParentModule.SampleRateHz, psd.Length); freq[0] = 1; _eventAggregator.GetEvent().Publish(new GraphChannelReadCalcProgressChangedEventArgs() { GraphVM = Parent, ProgressMessage = string.Empty, ProgressPercent = 100 }); //Step 6: Calculate GRMS //math from https://blog.endaq.com/why-the-power-spectral-density-psd-is-the-gold-standard-of-vibration-analysis#Benefits chData.GRMS = CalculateGRMS(freq, psd); chData.Yvalue = psd; chData.Xvalue = freq; chData.FFT = true; chData.SetStatsFromYValues(); } channel.Xmax = chData.Xvalue.Max(); channel.Xmin = chData.Xvalue.Min(); chData.GraphColor = new SolidColorBrush(channel.ChannelColor == Colors.Transparent ? Colors.Blue : channel.ChannelColor); return chData; } private ITestDataSeries GetEnvelopeChannel(List data) { if (null == data || data.Count == 0) return new TestDataSeries(); //Create blank-ish data series but set to envelope var chData = new TestDataSeries { TestGroup = DTS.Common.Strings.Strings.EnvelopeUnique, TestId = data[0].TestId, TestSetupName = data[0].TestSetupName, ChannelId = DTS.Common.Strings.Strings.EnvelopeUnique, GroupName = DTS.Common.Strings.Strings.EnvelopeUnique, HardwareChannel = DTS.Common.Strings.Strings.EnvelopeUnique, Bridge = DTS.Common.Strings.Strings.Table_NA, SWAAF = DTS.Common.Strings.Strings.Table_NA, HWAAF = DTS.Common.Strings.Strings.Table_NA, SampleRate = data[0].SampleRate, RecordingMode = string.Empty, ISOCode = string.Empty, ISOChannelName = string.Empty, UserCode = string.Empty, UserChannelName = string.Empty, ChannelName = DTS.Common.Strings.Strings.Envelope, Description = DTS.Common.Strings.Strings.Envelope, SensorSN = DTS.Common.Strings.Strings.Envelope, Excitation = DTS.Common.Strings.Strings.Table_NA, Polarity = DTS.Common.Strings.Strings.Table_NA }; //freq series is the same var freq = data[0].Xvalue.ToList(); //now get the max value at each frequency (the "envelope") var psd = new List(); for (var i = 0; i < data[0].Yvalue.Length; i++) { psd.Add(data.Max(tds => tds.Yvalue[i])); } //calculate GRMS of this new "channel" chData.GRMS = CalculateGRMS(freq.ToArray(), psd.ToArray()); chData.Yvalue = psd.ToArray(); chData.Xvalue = freq.ToArray(); chData.FFT = true; chData.GraphColor = new SolidColorBrush(Colors.Black); return chData; } private double CalculateGRMS(double[] freq, double[] psd) { var aRMS = new List(); for (var i = 0; i < psd.Length - 2 && i < freq.Length - 2; i++) { var N = Math.Log10(psd[i + 1] / psd[i]) / Math.Log10(freq[i + 1] / freq[i]); var ai = N.EqualsDigitPrecision(-1, 1) ? (psd[i] * freq[i]) * Math.Log(freq[i + 1] / freq[i]) : (psd[i] / Math.Pow(freq[i], N)) * (1 / (N + 1)) * (Math.Pow(freq[i + 1], N + 1) - Math.Pow(freq[i], N + 1)); if (!double.IsNaN(ai) && !double.IsInfinity(ai)) aRMS.Add(ai); } return Math.Sqrt(aRMS.Sum()); } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public bool IsSaved { get; } } }