This commit is contained in:
2026-04-17 14:55:32 -04:00
commit bc3ac1d4c9
18017 changed files with 4371742 additions and 0 deletions

View File

@@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
using DTS.Common.Enums.Sensors;
using DTS.Common.Interface;
using DTS.Common.Strings;
// ReSharper disable UnassignedGetOnlyAutoProperty
namespace DTS.Viewer.Graph.Model
{
public class TestDataSeries : Common.Base.BasePropertyChanged, ITestDataSeries
{
private bool _HIC = false;
public bool HIC
{
get => _HIC;
set => SetProperty(ref _HIC, value, "HIC");
}
private string _hicValue;
public string HICValue
{
get => _hicValue;
set => SetProperty(ref _hicValue, value, "HICValue");
}
private string _T1Time;
public string T1Time
{
get => _T1Time;
set => SetProperty(ref _T1Time, value, "T1Time");
}
private string _T2Time;
public string T2Time
{
get => _T2Time;
set => SetProperty(ref _T2Time, value, "T2Time");
}
private string _testGroup = string.Empty;
public string TestGroup
{
get => _testGroup;
set => SetProperty(ref _testGroup, value, "TestGroup");
}
private string _testId = string.Empty;
public string TestId
{
get => _testId;
set => SetProperty(ref _testId, value, "TestId");
}
private string _testSetupName = string.Empty;
public string TestSetupName
{
get => _testSetupName;
set => SetProperty(ref _testSetupName, value, "TestSetupName");
}
private string _channelId = string.Empty;
public string ChannelId
{
get => _channelId;
set => SetProperty(ref _channelId, value, "ChannelId");
}
private double[] _x = new double[0];
public double[] Xvalue
{
get => _x;
set => SetProperty(ref _x, value, "X");
}
private double[] _y = new double[0];
public double[] Yvalue
{
get => _y;
set => SetProperty(ref _y, value, "Y");
}
// 34455: Move from Brush to byte[] to make it thread-safe
private byte[] _graphColorARGB = new byte[] { Colors.Blue.A, Colors.Blue.R, Colors.Blue.G, Colors.Blue.B };
public Brush GraphColor
{
get => new SolidColorBrush(Color.FromArgb(_graphColorARGB[0], _graphColorARGB[1], _graphColorARGB[2], _graphColorARGB[3]));
set
{
var valueColor = ((SolidColorBrush)value).Color;
var colorValues = new byte[] { valueColor.A, valueColor.R, valueColor.G, valueColor.B };
SetProperty(ref _graphColorARGB, colorValues, "GraphColor");
}
}
public bool IsSaved { get; }
public string HardwareChannel { get; set; }
public string GroupName { get; set; }
public string SWAAF { get; set; }
public string Bridge { get; set; }
public string HWAAF { get; set; }
public string SampleRate { get; set; }
public string ISOCode { get; set; }
public string ISOChannelName { get; set; }
public string UserCode { get; set; }
public string UserChannelName { get; set; }
public string ChannelName { get; set; }
public string Description { get; set; }
public string SensorSN { get; set; }
public string SensorSNDisplay
{
get => SensorConstants.IsTestSpecificEmbedded(SensorSN) ? Strings.Table_NA : SensorSN;
}
public string EngineeringUnits { get; set; }
public string Excitation { get; set; }
public string Polarity { get; set; }
private string _minY = Strings.Table_NA;
public string MinY
{
get => _minY;
set => SetProperty(ref _minY, value, "MinY");
}
private string _maxY = Strings.Table_NA;
public string MaxY
{
get => _maxY;
set => SetProperty(ref _maxY, value, "MaxY");
}
private string _avgY = Strings.Table_NA;
public string AvgY
{
get => _avgY;
set => SetProperty(ref _avgY, value, "AvgY");
}
private string _stdDevY;
public string StdDevY
{
get => _stdDevY;
set => SetProperty(ref _stdDevY, value, "StdDevY");
}
private double _peakMagnitude = 0;
/// <summary>
/// holds the peak magnitude of frequencies in signal
/// only valid when FFT true
/// 6402 Implement ability to switch to FFT live in the Review
/// </summary>
public double PeakMagnitude
{
get => _peakMagnitude;
set => SetProperty(ref _peakMagnitude, value, "PeakMagnitude");
}
private double _peakFrequency = 0;
/// <summary>
/// holds the frequency of the highest magnitude in signal
/// only valid when FFT true
/// 6402 Implement ability to switch to FFT live in the Review
/// </summary>
public double PeakFrequency
{
get => _peakFrequency;
set => SetProperty(ref _peakFrequency, value, "PeakFrequency");
}
private double _grms = 0;
/// <summary>
/// holds the root-mean-squared acceleration
/// only calculated in PSD results graphs
/// </summary>
public double GRMS
{
get => _grms;
set => SetProperty(ref _grms, value, "GRMS");
}
private bool _fft = false;
/// <summary>
/// holds whether series is an FFT of signal data
/// 6402 Implement ability to switch to FFT live in the Review
/// </summary>
public bool FFT
{
get => _fft;
set => SetProperty(ref _fft, value, "FFT");
}
private string _T0EUValue = "";
/// <summary>
/// this is the T0 value regardless of units ... it's not really EU but since it's already in use I'm not going to change it
/// </summary>
public string T0EUValue
{
get => _T0EUValue;
set => SetProperty(ref _T0EUValue, value, "T0EUValue");
}
private double CalculateStdDev(double[] values)
{
double ret = 0;
if (!values.Any()) return ret;
//Compute the Average
var avg = values.Average();
//Perform the Sum of (value-avg)_2_2
var sum = values.Sum(d => Math.Pow(d - avg, 2));
//Put it all together
ret = Math.Sqrt(sum / (values.Length - 1));
return ret;
}
private const string STAT_FORMAT = "G5";
/// <summary>
/// sets Ave/StdDev/Min/MaxT0 stats using internal YValues (something like PSD or FFT)
/// </summary>
public void SetStatsFromYValues()
{
SetStatsFromYValues(Yvalue);
}
/// <summary>
/// sets Ave/StdDev/Min/Max/T0 stats using the passed in input range (something like PSD or FFT)
/// </summary>
/// <param name="values"></param>
public void SetStatsFromYValues(double[] values)
{
if (null == values || 0 == values.Length)
{
SetStatsFromYValues(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN);
}
else
{
SetStatsFromYValues(values.Min(), values.Max(), values.Average(), CalculateStdDev(values), double.NaN);
}
}
/// <summary>
/// formats and sets the current unit stats
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="ave"></param>
/// <param name="stdDev"></param>
/// <param name="t0Value"></param>
private void SetStatsFromYValues(double min, double max, double ave, double stdDev, double t0Value)
{
StdDevY = double.IsNaN(stdDev) ? Strings.Table_NA : stdDev.ToString(STAT_FORMAT);
AvgY = double.IsNaN(ave) ? Strings.Table_NA : ave.ToString(STAT_FORMAT);
MinY = double.IsNaN(min) ? Strings.Table_NA : min.ToString(STAT_FORMAT);
MaxY = double.IsNaN(max) ? Strings.Table_NA : max.ToString(STAT_FORMAT);
T0EUValue = double.IsNaN(t0Value) ? Strings.Table_NA : t0Value.ToString(STAT_FORMAT);
}
/// <summary>
/// sets stats (ave/min/max) using values from passed in channel where it was already calculated when reading data
/// </summary>
/// <param name="channel"></param>
public void SetStatsFromChannel(ITestChannel channel)
{
SetStatsFromYValues(channel.MinY, channel.MaxY, channel.AveY, channel.StdDevY, channel.T0Value);
}
public string RecordingMode { get; set; }
}
}

View File

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