Files
2026-04-17 14:55:32 -04:00

385 lines
20 KiB
C#

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