init
This commit is contained in:
@@ -0,0 +1,384 @@
|
||||
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<GraphChannelReadCalcProgressChangedEvent>().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<ITestDataSeries> 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<List<ITestDataSeries>> GetTestDataAsync(List<ITestChannel> 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<ITestDataSeries> GetTestData(List<ITestChannel> 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<double>((int)channelData[1].Length);
|
||||
var x = new List<double>((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<GraphChannelReadCalcProgressChangedEvent>().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<GraphChannelReadCalcProgressChangedEvent>().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<GraphChannelReadCalcProgressChangedEvent>().Publish(new GraphChannelReadCalcProgressChangedEventArgs()
|
||||
{ GraphVM = Parent, ProgressMessage = string.Empty, ProgressPercent = 100 });
|
||||
|
||||
////Step 4: apply a band pass on the input if requested
|
||||
_eventAggregator.GetEvent<GraphChannelReadCalcProgressChangedEvent>().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<GraphChannelReadCalcProgressChangedEvent>().Publish(new GraphChannelReadCalcProgressChangedEventArgs()
|
||||
{ GraphVM = Parent, ProgressMessage = string.Empty, ProgressPercent = 100 });
|
||||
|
||||
//Step 5: get the PSD
|
||||
_eventAggregator.GetEvent<GraphChannelReadCalcProgressChangedEvent>().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<GraphChannelReadCalcProgressChangedEvent>().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<ITestDataSeries> 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<double>();
|
||||
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<double>();
|
||||
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; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user