/* * SliceRaw.File.Reader.cs * * Copyright © 2009 * Diversified Technical Systems, Inc. * All Rights Reserved */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml; using System.Xml.Serialization; using DTS.Common; using DTS.Common.Classes.Sensors; using DTS.Common.DAS.Concepts; using DTS.Common.DAS.Concepts.DAS.Channel; using DTS.Common.Enums; using DTS.Common.Enums.Sensors; using DTS.Common.Enums.Viewer; using DTS.Common.Interface; using DTS.Common.Interface.Sensors.SoftwareFilters; using DTS.Common.Utilities; using DTS.Common.Utilities.Logging; using DTS.Common.Utilities.SaeJ211; using LinearizationFormula = DTS.Common.Classes.Sensors.LinearizationFormula; using DTS.Common.Classes.Viewer.TestMetadata; using DTS.Common.Interface.TestDefinition; using DTS.Common.Utils; using DTS.Common.Enums.DASFactory; using System.Threading.Tasks; // ReSharper disable SuspiciousTypeConversion.Global // ReSharper disable InconsistentNaming namespace DTS.Serialization.SliceRaw { // *** see SliceRaw.File.cs *** public partial class File { /// /// /// Utility object for serializing s to disk. /// /// public partial class Reader : Reader, IReader { /// /// /// Initialize an instance of the File.Reader class. /// /// /// /// The -type this deserializer is associated with. /// /// public Reader(File fileType) : base(fileType) { } ///// ///// Notify subscribers that the write ///// is starting. ///// //public event BeginEventHandler OnBegin; ///// ///// Notify subscribers that the write ///// is finished. ///// //public event EndEventHandler OnEnd; ///// ///// Notify subscribers that we are one ///// tick closer to write completion. ///// //public event TickEventHandler OnTick; /// /// Indicates the type of read event that is represented by the Event that has received it. /// public enum Event { NumberOfFilesDetermined, SingleFileReadComplete, FullReadComplete, } /// /// Handler for read events. /// /// /// The number of files to be generated by the file read process. /// It should match the number of SliceRaw.File.Reader.Event.SingleFileReadComplete events /// that will be received during the full write. /// public delegate void EventHandler(Event readEvent, int numFiles); /// /// Get a complete list of test files in the specified directory. /// /// /// /// The name of the directory to be scoured for test /// files. /// /// /// /// A list of filenames naming all test files in the specified directory. /// /// private List GetChannelFilenames(string directory) { try { var channelFilenames = new List(Directory.GetFiles(directory, "*" + ChannelFileExtension)); channelFilenames.Sort(ChFileCompare); return channelFilenames; } catch (System.Exception ex) { throw new Exception("encountered problem getting channel filenames", ex); } } /// /// Get full name of test serialization file. /// /// /// /// The representation of the directory to be searched for test files. /// /// /// /// The representation of the found test filename. /// /// private string GetTestFilename(string directory) { try { if (null == directory) throw new ArgumentNullException($"cannot get test filename from null directory"); if (null == TestFileExtension) throw new ArgumentException("cannot get test filename with null extension"); var testFiles = Directory.GetFiles(directory, "*" + TestFileExtension); if (testFiles.Length < 1) throw new MissingFileException("could not find required file " + "\"" + directory + TestFileExtension + "\""); if (testFiles.Length > 1) throw new TooManyFilesException("found " + testFiles.Length.ToString() + " \"" + TestFileExtension + "\" " + " files in directory \"" + directory + "\"; there should be only 1"); return testFiles[0]; } catch (System.Exception ex) { //throw new SliceRaw.File.Reader.Exception(ex.Message, ex); throw new UserException(ex.Message); } } /// /// Perform read test serialization from it's containing file. /// /// /// /// The representation of the pathname of the base /// directory containing the test file. /// /// /// /// The serialization contained by the specified file. /// /// private string ReadTestStringFromFile(string path) { try { //APILogger.Log("reading 65 ", path); using (var mutex = new System.Threading.Mutex(false, path.Replace(Path.DirectorySeparatorChar, '_'))) { while (!mutex.WaitOne(50, false)) { System.Threading.Thread.Sleep(5); } try { using (var reader = new StreamReader(path)) try { return reader.ReadToEnd(); } catch (System.Exception ex) { throw new Exception("encountered problem reading filestream to end", ex); } } finally { mutex.ReleaseMutex(); } } } catch (System.Exception ex) { throw new Exception( "encountered problem reading test string from file " + (null != path ? "\"" + path + "\"" : "<>"), ex); } } /// /// Perform read test serialization from it's containing file. /// /// /// /// The representation of the pathname of the base /// directory containing the test file. /// /// /// /// all lines from given file /// /// public static string[] ReadTestStringsFromFile(string path) { try { //APILogger.Log("reading 65 ", path); using (var mutex = new System.Threading.Mutex(false, path.Replace(Path.DirectorySeparatorChar, '_'))) { while (!mutex.WaitOne(50, false)) { System.Threading.Thread.Sleep(5); } try { return System.IO.File.ReadAllLines(path); } finally { mutex.ReleaseMutex(); } } } catch (System.Exception ex) { throw new Exception( "encountered problem reading test string from file " + (null != path ? "\"" + path + "\"" : "<>"), ex); } } /// /// Deserialize the test structure contained in the specified serialization string. /// /// /// /// The serialization from whence the test will be extracted /// and instantiated. /// /// /// /// The represented by specified serialization /// string. /// /// private Test DeserializeTestFromString(string serialization) { try { if (null == serialization) throw new ArgumentNullException($"cannot deserialize test from null string"); var settings = new XmlReaderSettings(); settings.IgnoreWhitespace = true; var reader = XmlReader.Create(new StringReader(serialization), settings); var serializer = new XmlSerializer(typeof(Test)); return serializer.Deserialize(reader) as Test; } catch (System.Exception ex) { throw new Exception("encountered problem deserializing test from string", ex); } } /// /// Perform read of all test and channel data. /// /// /// /// The pathname of the directory containing the serialization to be read. /// /// /// /// The generated from the specified serialization. /// /// /// /// A test populated with deserialized test and channel data. /// /// public void Read(string directory, out Test test) { Read(directory, out test, null); } /// /// returns true if the channel is a squib channel /// /// /// private bool IsSquib(ITestChannel channel) { if (null != channel.Bridge && channel.Bridge.ToLower().Equals("squib")) { return true; } if (null != channel.HardwareChannelName && (channel.HardwareChannelName.Contains("TOM") || channel.HardwareChannelName.Contains("SPT") || channel.HardwareChannelName.Contains("SLT"))) { return true; } return false; } /// /// Perform read of all test and channel data. /// /// /// /// The pathname of the directory containing the serialization to be read. /// /// /// /// The generated from the specified serialization. /// /// /// /// An Event to be notified with read /// status updates. /// /// /// /// A test populated with deserialized test and channel data. /// /// public void Read(string directory, out Test test, EventHandler onEvent) { string testFileName = null; try { testFileName = GetTestFilename(directory); var channelFileNames = GetChannelFilenames(directory); var tmd = new TestMetadataList(); var list = tmd.GetTestSummaryList(directory, string.Empty); var inputChannels = new List(); foreach (var ts in list) { if (ts.Channels.Any() && ts.Channels.FirstOrDefault() == null) { Utils.SetChannelInfo(ts.TestMetadata, Path.GetDirectoryName(ts.TestMetadata.TestRun.FilePath), PersistentChannel.GetIsoCode); ts.Channels = ts.TestMetadata.TestRun.Channels; ts.CalculatedChannels = ts.TestMetadata.TestRun.CalculatedChannels; } if (ts.Channels?.Count > 0) { var alreadyProcessed = new HashSet(); foreach (var c in ts.Channels.Where(ch => null != ch)) { //originally code here was restricting all duplicate ids ... I'm not sure that's what we want //but to minimize impact I need linear added channels not to be taken out ... //http://manuscript.dts.local/f/cases/29993/ISO-MME-data-does-not-match-expectations //http://manuscript.dts.local/f/cases/26913/RDF-Export-uses-incorrect-data-files if (!IsSquib(c)) { alreadyProcessed.Add(c.ChannelId); inputChannels.Add(c); } else { if (alreadyProcessed.Contains(c.ChannelId)) { continue; } alreadyProcessed.Add(c.ChannelId); inputChannels.Add(c); } } } } onEvent?.Invoke(Event.NumberOfFilesDetermined, channelFileNames.Count + 1); test = DeserializeTestFromString(ReadTestStringFromFile(testFileName)); // Generated from a serialization. Get the timestamp. test.InceptionDate = System.IO.File.GetLastWriteTime(testFileName); //Hack Alert. Test Id doesn't always match the TestID -- use the dts file name as the test id. var testId = Path.GetFileNameWithoutExtension(testFileName); if (false == test.Id.Equals(testId)) { APILogger.Log("DTS File Name: " + testId + " does not match Test Id: " + test.Id + " . Using file name as test Id"); } var bByPass = false; foreach (var m in test.Modules) { //Why is this here? m.Number = m.Number; if (0 >= m.Channels.Count) { try { // // If we have no channels on this module, we're still going to have to fill in // the trigger sample numbers from somewhere. // m.TriggerSampleNumbers = new List(); for (var i = 0; i < m.UnsubsampledTriggerSampleNumbers.Count; i++) { m.TriggerSampleNumbers.Add(m.UnsubsampledTriggerSampleNumbers[i]); } } catch (System.Exception ex) { throw new Exception( "encountered problem extracting trigger sample numbers from serialization on channel-less module", ex); } } else { foreach (var c in m.Channels) { string channelFileName = null; var matches = from ch in inputChannels where ch.ChannelId == c.ChannelId select ch; //44214 Don't crash when exporting old WIAMan export if ((null != matches) && (1 == matches.Count()) && (matches.First().BinaryFileName != null)) { channelFileName = Path.Combine(directory, matches.First().BinaryFileName); } else if (2 == matches.Count()) { //if we have multiple files for a given channel id we could be in a situation where //we have a linear and a non linear file, if so find the right file, otherwise just use the first one //http://manuscript.dts.local/f/cases/29993/ISO-MME-data-does-not-match-expectations var lin = matches.FirstOrDefault(ch => null != ch.BinaryFileName && ch.BinaryFileName.ToLower().Contains(".lin")); var nonlin = matches.FirstOrDefault(ch => null != ch.BinaryFileName && !ch.BinaryFileName.ToLower().Contains(".lin")); var aic = c as Test.Module.AnalogInputChannel; if (null != aic && !aic.LinearizationFormula.IsValid() && null != lin) { channelFileName = Path.Combine(directory, lin.BinaryFileName); } else if (null != aic && aic.LinearizationFormula.IsValid() && null != nonlin) { channelFileName = Path.Combine(directory, nonlin.BinaryFileName); } else { channelFileName = Path.Combine(directory, matches.First().BinaryFileName); } } if (!System.IO.File.Exists(channelFileName)) { APILogger.Log("File not found ", channelFileName); throw new FileNotFoundException(channelFileName); } ReadChannel(c, m, channelFileName, ref bByPass); } //Read the Calculated Channels foreach (var c in m.CalculatedChannels) { var filename = directory + "\\" + testId + "." + c.Number + CalculatedChannelFileExtension; ReadChannel(c, m, filename, ref bByPass); onEvent?.Invoke(Event.SingleFileReadComplete, channelFileNames.Count + 1); } } } onEvent?.Invoke(Event.FullReadComplete, channelFileNames.Count + 1); } //44214 Don't crash when exporting old WIAMan export catch (System.Exception ex) { APILogger.Log($"Exception is '{ex.Message}'"); if (ex is MissingFileException || ex is BadCrcException) { throw ex; } else { throw new Exception("Encountered problem reading test file \"" + (testFileName ?? "") + "\"", ex); } } finally { onEvent?.Invoke(Event.FullReadComplete, 0); } } public void ReadChannel(Test.Module.Channel channel, Test.Module module, string channelFileName, ref bool bByPass) { //APILogger.Log("Getting stream"); var attempt = 0; var bFailed = true; while (attempt < 30 && bFailed) { attempt++; try { //APILogger.Log("reading file 66 ", channelFileName); using (var mutex = new System.Threading.Mutex(false, channelFileName.Replace(Path.DirectorySeparatorChar, '_'))) { while (!mutex.WaitOne(50, false)) { System.Threading.Thread.Sleep(5); } try { using (var stream = System.IO.File.OpenRead(channelFileName)) { //APILogger.Log("Getting reader"); using (var reader = new BinaryReader(stream)) { var channelInfo = new BinaryChannelHeader(); var fields = Enum.GetValues(typeof(PersistentChannel.Field)) .Cast().ToArray(); //APILogger.Log("Got fields"); foreach (var field in fields) { switch (field) { case PersistentChannel.Field.AreSamplesSigned: channelInfo.AreSamplesSigned = reader.ReadUInt32(); break; case PersistentChannel.Field.BeginningOfData: break; case PersistentChannel.Field.BeginningOfFile: break; case PersistentChannel.Field.Crc32: //APILogger.Log($"[DTM] ReadChannel: {channelFileName}"); var fileCrc = reader.ReadUInt32(); //APILogger.Log($"[DTM] fileCRC read: {fileCrc}"); var computedCrc = channelInfo.Crc32; //APILogger.Log($"[DTM] computedCRC32: {computedCrc}"); var unpaddedCrc = channelInfo.UnpaddedEuCrc32; //APILogger.Log($"[DTM] unpaddedCrc: {unpaddedCrc}"); var unpaddedPaddedCrc = channelInfo.UnpaddedEuStringPaddedEuLengthCrc32; //APILogger.Log($"[DTM] unpaddedPaddedCrc: {unpaddedPaddedCrc}"); if (computedCrc != fileCrc && !bByPass && fileCrc != unpaddedCrc && fileCrc != unpaddedPaddedCrc) { if (Environment.UserInteractive) { var dlg = new BadCRCBypass(); dlg.FileName = channelFileName; if (dlg.ShowDialog() == System.Windows.Forms.DialogResult .Cancel) { throw new BadCrcException( "deserialized channel header CRC (" + fileCrc.ToString("X") + ") does not match the computed CRC (" + computedCrc.ToString("X") + ")"); } if (dlg.RememberChoice) { bByPass = true; } } else { throw new BadCrcException( "deserialized channel header CRC (" + fileCrc.ToString("X") + ") does not match the computed CRC (" + computedCrc.ToString("X") + ")"); } } break; case PersistentChannel.Field.DataZeroLevelCounts: channelInfo.DataZeroLevelCounts = reader .ReadInt32(); // Valid, but autogenerated by Event&Test channels break; case PersistentChannel.Field.EngineeringUnit: var euBA = reader.ReadBytes( channelInfo.EuFieldLengthWithTerminator - 1); channelInfo.EngineeringUnit = Encoding.UTF8.GetString(euBA).ToCharArray(); if (channel is IEngineeringUnitAware) (channel as IEngineeringUnitAware).EngineeringUnits = new String(channelInfo.EngineeringUnit); break; case PersistentChannel.Field.EuFieldLengthWithTerminator: channelInfo.EuFieldLengthWithTerminator = reader.ReadUInt16(); break; case PersistentChannel.Field.Excitation: if (channelInfo.HeaderVersionNumber >= 3) { channelInfo.Excitation = reader.ReadDouble(); } break; case PersistentChannel.Field.HeaderVersionNumber: channelInfo.HeaderVersionNumber = reader.ReadUInt32(); //if (channelInfo.HeaderVersionNumber != BinaryChannelHeader.RequiredHeaderVersionNumber) if (!BinaryChannelHeader.KnownHeaderVersionNumbers.Contains( channelInfo.HeaderVersionNumber)) throw new Exception("Channel header version number (" + channelInfo.HeaderVersionNumber.ToString("X") + ") current reader version (" + BinaryChannelHeader.CurrentVersionNumber.ToString("X") + ")"); break; case PersistentChannel.Field.IsoCode: channelInfo.IsoCode = Encoding.UTF8.GetString(reader.ReadBytes(16)).ToCharArray(); if (channel is IIsoCodeAware isoCodeAware) { //as long as the DTS file already has an isocode, preserve what we have in the isocode field //note the persistentchannelinfo now could have a different isocode, //but we have to preserve that since it's part of the CRC calculation if (string.IsNullOrWhiteSpace(isoCodeAware.IsoCode)) { isoCodeAware.IsoCode = new string(channelInfo.IsoCode); } } break; case PersistentChannel.Field.MagicKey: channelInfo.MagicKey = reader.ReadUInt32(); if (channelInfo.MagicKey != BinaryChannelHeader.RequiredMagicKey) throw new Exception("Magic Key value for this channel file header (" + channelInfo.MagicKey.ToString("X") + ") doesn't match expected value (" + BinaryChannelHeader.RequiredMagicKey.ToString("X") + ")"); break; case PersistentChannel.Field.MvPerEu: channelInfo.MvPerEu = reader.ReadDouble(); //Valid, but already read from test XML //APILogger.Log(string.Format("Got MvPerEu '{0}'", channelInfo.MvPerEu)); break; case PersistentChannel.Field.NumberOfBitsPerSample: channelInfo.NumberOfBitsPerSample = reader.ReadUInt32(); break; case PersistentChannel.Field.NumberOfSamples: channelInfo.NumberOfSamples = module.NumberOfSamples = reader.ReadUInt64(); break; case PersistentChannel.Field.NumberOfTriggers: channelInfo.NumberOfTriggers = reader.ReadUInt16(); break; case PersistentChannel.Field.OffsetOfSampleDataStart: channelInfo.OffsetOfSampleDataStart = reader.ReadUInt64(); break; case PersistentChannel.Field.OriginalOffsetADC: if (channelInfo.HeaderVersionNumber >= 3) { channelInfo.OriginalOffsetADC = reader.ReadInt32(); } break; case PersistentChannel.Field.PostTestDiagnosticsLevelCounts: channelInfo.PostTestDiagnosticsLevelCounts = reader.ReadInt32(); break; case PersistentChannel.Field.PostTestZeroLevelCounts: channelInfo.PostTestZeroLevelCounts = reader.ReadInt32(); break; case PersistentChannel.Field.PreTestDiagnosticsLevelCounts: channelInfo.PreTestDiagnosticsLevelCounts = reader.ReadInt32(); break; case PersistentChannel.Field.PreTestNoisePercentageOfFullScale: channelInfo.PreTestNoisePercentageOfFullScale = channel.NoiseAsPercentageOfFullScale = reader.ReadDouble(); break; case PersistentChannel.Field.PreTestZeroLevelCounts: channelInfo.PreTestZeroLevelCounts = channel.PreTestZeroLevelAdc = (short)reader.ReadInt32(); break; case PersistentChannel.Field.RemovedADC: if (channelInfo.HeaderVersionNumber >= 2) { channelInfo.RemovedADC = channel.RemovedADC = reader.ReadInt32(); } break; case PersistentChannel.Field.SampleRate: channelInfo.SampleRate = module.SampleRateHz = (float)reader.ReadDouble(); break; case PersistentChannel.Field.ScaleFactorMv: channelInfo.ScaleFactorMv = reader.ReadDouble(); //Valid, but already read from test XML break; case PersistentChannel.Field.TriggerAdjustmentSamples: if (channelInfo.HeaderVersionNumber >= 3) { channelInfo.TriggerAdjustmentSamples = reader.ReadInt32(); } break; case PersistentChannel.Field.TriggerSampleNumbers: try { channelInfo.TriggerSampleNumbers = new UInt64[channelInfo.NumberOfTriggers]; module.TriggerSampleNumbers = new List(); for (var i = 0; i < channelInfo.NumberOfTriggers; i++) module.TriggerSampleNumbers.Add(channelInfo.TriggerSampleNumbers[i] = reader.ReadUInt64()); } catch (System.Exception ex) { throw new Exception("encountered problem extracting trigger sample numbers from serialization", ex); } break; case PersistentChannel.Field.ZeroMvInADC: if (channelInfo.HeaderVersionNumber >= 3) { channelInfo.ZeroMvInADC = channel.ZeroMvInADC = (short)reader.ReadInt32(); } break; case PersistentChannel.Field.WindowAverageADC: if (channelInfo.HeaderVersionNumber >= 4) { channelInfo.WindowAverageADC = channel.WindowAverageADC = (short)reader.ReadInt32(); } break; } } //end foreach // Remove any word-aligning padding from the EU field. channelInfo.EngineeringUnit = new string(channelInfo.EngineeringUnit).Trim().ToCharArray(); try { const int DataChunkSize = 0x1000; var dataChunk = new short[DataChunkSize]; var analogChannel = channel as Test.Module.AnalogInputChannel; analogChannel.Multiplier = channel.Data.Multiplier; analogChannel.UnitConversion = channel.Data.UnitConversion; analogChannel.UserOffsetEU = channel.Data.UserOffsetEU; analogChannel.UseEUScaler = channel.Data.UseEUScaleFactors; analogChannel.ScaleFactorEU = channel.Data.ScaleFactorEU; if (null != analogChannel) { if (channel is Test.Module.CalculatedChannel) { var cc = channel as Test.Module.CalculatedChannel; analogChannel.AverageAdcOverTime = new AverageShortValueOverTime(0, cc.SampleRateHz, "s"); } else { analogChannel.AverageAdcOverTime = new AverageShortValueOverTime(0, analogChannel.ParentModule.SampleRateHz, "s"); } analogChannel.FileName = channelFileName; } channel.PersistentChannelInfo = new PersistentChannel(channelFileName, null, false); channel.Data = new Test.Module.Channel.DataArray { ScaleFactorMv = channelInfo.ScaleFactorMv, MvPerEu = channelInfo.MvPerEu, Multiplier = analogChannel.Multiplier, UnitConversion = analogChannel.UnitConversion, UserOffsetEU = analogChannel.UserOffsetEU, ScaleFactorEU = analogChannel.ScaleFactorEU, UseEUScaleFactors = analogChannel.Data.UseEUScaleFactors, }; } catch (System.Exception ex) { APILogger.Log(ex); if (ex is MissingFileException) { throw ex; } throw new Exception("encountered problem extracting channel data from serialization", ex); } } //using binary stream } //using file stream } finally { mutex.ReleaseMutex(); } } //APILogger.Log("successfully opened file after, ", attempt, " attempts"); bFailed = false; } //end try catch (FileNotFoundException) { APILogger.Log(string.Format("File not found " + channelFileName)); throw; } catch (System.Exception ex) { //HACK ALERT! This method is eating legitimate exceptions in an attempt to satisfy some retry algorithm. if (attempt == 30) { throw; } APILogger.Log("attempt number: ", attempt, ex); System.Threading.Thread.Sleep(200); } } //end while } /// /// performs FFT on passed in time domain information /// the passed in information has already been manipulated to be of size of power of 2 /// see DoFFT /// this was originally taken loosely from FWTU viewer /// 6402 Implement ability to switch to FFT live in the Review /// /// /// /// /// private static Exocortex.DSP.Complex[] FFT(double[] timeDomainData, int length, bool bUseQuick) { Exocortex.DSP.Complex[] data = new Exocortex.DSP.Complex[timeDomainData.Length]; for (int i = 0; i < timeDomainData.Length; i++) { data[i] = (Exocortex.DSP.Complex)timeDomainData[i]; } if (bUseQuick) { Exocortex.DSP.Fourier.FFT_Quick(data, length, Exocortex.DSP.FourierDirection.Forward); } else { Exocortex.DSP.Fourier.FFT(data, length, Exocortex.DSP.FourierDirection.Forward); } // Eliminate the semi-bogus first point. Exocortex.DSP.Complex[] TrimmedData = new Exocortex.DSP.Complex[data.Length - 1]; Array.Copy(data, 1, TrimmedData, 0, TrimmedData.Length); return TrimmedData; } /// /// takes in a channel, outputs data in terms of freq/mag, and the frequency with the peak magnitude /// adapted from code in FWTU /// 6402 Implement ability to switch to FFT live in the Review /// /// /// /// /// /// /// /// private static void DoFFT(Test.Module.AnalogInputChannel ch, out double[] fftxaxis, out double[] fftyaxis, DataScaler ds, double sampleRate, out double peakMagnitude, out double peakFrequency) { peakFrequency = 0D; peakMagnitude = double.MinValue; var eu = new List(); foreach (var datum in ch.PersistentChannelInfo.Data) { var d = ds.GetEU(datum); eu.Add(d); } fftxaxis = new double[0]; fftyaxis = new double[0]; var timedomaindata = new double[Common.Utils.Utils.GetEnclosingPower2(eu.Count)]; int padding = timedomaindata.Length - eu.Count; for (int i = 0; i < padding / 2; i++) { timedomaindata[i] = eu[padding / 2 - i]; } for (int i = 0; i < eu.Count; i++) { timedomaindata[i + padding / 2] = eu[i]; } for (int i = eu.Count + padding / 2; i < timedomaindata.Length; i++) { timedomaindata[i] = eu[eu.Count - 1 - (i - (eu.Count + padding / 2))]; } var result = FFT(timedomaindata, timedomaindata.Length, true); int cutoffIndex = result.Length / 2; fftxaxis = new double[Math.Min(result.Length / 2, cutoffIndex)]; fftyaxis = new double[Math.Min(result.Length / 2, cutoffIndex)]; for (var i = 0; i < fftyaxis.Length; i++) { fftxaxis[i] = i * sampleRate / timedomaindata.Length; fftyaxis[i] = 20 * Math.Log10(Math.Sqrt(result[i].Re * result[i].Re + result[i].Im * result[i].Im) / result.Length) - 80; if (fftyaxis[i] > peakMagnitude) { peakMagnitude = fftyaxis[i]; peakFrequency = fftxaxis[i]; } } } /// /// sets the stats for the current unit to the ADC stats, determines the ADC stats if needed /// /// /// /// /// /// private static void SetADCStats(SetReadCalcProgressValueDelegate SetProgress, ref double[] data, ITestChannel channel, Test.Module.AnalogInputChannel ch, DataScaler ds) { if (double.IsNaN(channel.AveADC)) { SwitchDataToADC(SetProgress, ref data, ds); try { channel.MaxADC = data.Max(); channel.MinADC = data.Min(); channel.AveADC = data.Average(); channel.StdDevADC = data.StandardDeviation(); } catch (Exception ex) { APILogger.Log(ex); } var t0Index = GetT0Index(ch); if (t0Index >= 0 && t0Index < data.Length) { channel.T0ADC = data[t0Index]; } } channel.MaxY = channel.MaxADC; channel.MinY = channel.MinADC; channel.AveY = channel.AveADC; channel.StdDevY = channel.StdDevADC; channel.T0Value = channel.T0ADC; } /// /// switches a raw data range in ADC from file to ADC data to display /// /// /// /// private static void SwitchDataToADC(SetReadCalcProgressValueDelegate SetProgress, ref double[] data, DataScaler ds) { if (null == data || 0 == data.Length) { return; } SetProgress?.Invoke(DTS.Common.Strings.Strings.ReadChannelBinaryData_GettingADCValues, 0D); var progress = 0D; var completed = 0; for (var i = 0; i < data.Length; i++) { try { data[i] = ds.GetCorrectedADC((short)data[i], DFConstantsAndEnums.AlwaysShowUnsignedADC); } catch (Exception ex) { APILogger.Log(ex); //should already be in ADC, so just leave it alone } completed++; var step = (double)completed / data.Length * 100; if (Math.Floor(step) > progress) { progress = step; SetProgress?.Invoke(string.Empty, progress);//only update on whole % changes } } } /// /// sets the current stats to the mV stats, determines the mv stats if needed /// /// /// /// /// /// /// private static void SetMVStats(SetReadCalcProgressValueDelegate SetProgress, ref double[] data, ITestChannel channel, Test.Module.AnalogInputChannel ch, DataScaler ds, bool volts) { if (double.IsNaN(channel.AveMV)) { SwitchDataToMV(SetProgress, ref data, ds, volts); try { channel.MaxMV = data.Max(); channel.MinMV = data.Min(); channel.AveMV = data.Average(); channel.StdDevMV = data.StandardDeviation(); } catch (Exception ex) { APILogger.Log(ex); } var t0Index = GetT0Index(ch); if (t0Index >= 0 && t0Index < data.Length) { channel.T0MV = data[t0Index]; } } channel.MaxY = channel.MaxMV; channel.MinY = channel.MinMV; channel.AveY = channel.AveMV; channel.StdDevY = channel.StdDevMV; channel.T0Value = channel.T0MV; } /// /// switches Ydata (from ADC) to mV /// /// /// /// /// /// /// private static void SwitchDataToMV(SetReadCalcProgressValueDelegate SetProgress, ref double[] data, DataScaler ds, bool volts) { if (null == data || 0 == data.Length) { return; } SetProgress?.Invoke(DTS.Common.Strings.Strings.ReadChannelBinaryData_GettingMVValues, 0D); var progress = 0D; var completed = 0; for (var i = 0; i < data.Length; i++) { try { data[i] = ds.GetMvOrV(data[i], volts); } catch (Exception ex) { APILogger.Log(ex); data[i] = double.NaN; } completed++; var step = (double)completed / data.Length * 100; if (Math.Floor(step) > progress) { progress = step; SetProgress?.Invoke(string.Empty, progress);//only update on whole % changes } } } /// /// returns the T0 index /// /// /// private static int GetT0Index(Test.Module.AnalogInputChannel channel) { var timeZeroIndex = channel.ParentModule.TriggerSampleNumbers.Count != 0 ? channel.ParentModule.TriggerSampleNumbers[0] : 0; var startingSample = (long)timeZeroIndex - (long)channel.ParentModule.StartRecordSampleNumber; return Convert.ToInt32(startingSample); } /// /// sets the current stats to EU, determines the EU stats if needed /// /// /// /// /// /// private static void SetEUStats(SetReadCalcProgressValueDelegate SetProgress, ref double[] data, ITestChannel channel, Test.Module.AnalogInputChannel ch, DataScaler ds) { if (double.IsNaN(channel.AveEU)) { try { var maxADC = double.MinValue; var minADC = double.MaxValue; var mean = 0D; var sum = 0D; var progress = 0D; for (var i = 0; i < data.Length; i++) { var x = data[i]; if (minADC > x) { minADC = x; } if (maxADC < x) { maxADC = x; } var delta = x - mean; mean += delta / (1 + i); sum += delta * (x - mean); var percent = Math.Floor(100D * i / data.Length); if (percent > progress) { SetProgress?.Invoke(Common.Strings.Strings.ReadChannelBinaryData_GettingEUValues, percent); progress = percent; } } var aveADC = mean; var stdDevADC = Math.Sqrt(sum / data.Length); channel.MaxEU = ds.GetEU(maxADC); channel.MinEU = ds.GetEU(minADC); channel.AveEU = ds.GetEU(aveADC); channel.StdDevEU = ds.GetEU(stdDevADC); } catch (Exception ex) { APILogger.Log(ex); } var t0Index = GetT0Index(ch); if (t0Index >= 0 && t0Index < data.Length) { channel.T0EU = ds.GetEU(data[t0Index]); } } channel.MaxY = channel.MaxEU; channel.MinY = channel.MinEU; channel.AveY = channel.AveEU; channel.StdDevY = channel.StdDevEU; channel.T0Value = channel.T0EU; } /// /// switches a data y range to EU /// /// /// /// /// /// /// private static void SwitchDataToEU(SetReadCalcProgressValueDelegate SetProgress, ref double[] data, ITestChannel channel, Test.Module.AnalogInputChannel ch, DataScaler ds, bool SetActualRange) { if (null == data || 0 == data.Length) { return; } var progress = 0D; SetProgress?.Invoke(DTS.Common.Strings.Strings.ReadChannelBinaryData_GettingEUValues); var completed = 0; for (var i = 0; i < data.Length; i++) { try { data[i] = ds.GetEU(data[i]); } catch (Exception ex) { APILogger.Log(ex); data[i] = double.NaN; } completed++; var step = (double)completed / data.Length * 100; if (Math.Floor(step) > progress) { progress = step; SetProgress?.Invoke(string.Empty, progress);//only update on whole % changes } } //18461 Auto range not working for non - linear data //channel actual max range was being calculated using the max ADC short.Min/short.Max //this resulted in astronomical eu max/mins, instead use the max/min eu values //this property is used in a couple places, but primarily for display if (!ch.IsDigital() && SetActualRange) { try { if (channel.UseEUScaler) { channel.ActualMinRangeEu = channel.ScaleFactorEU * short.MinValue; channel.ActualMaxRangeEu = channel.ScaleFactorEU * short.MaxValue; } else { channel.ActualMaxRangeEu = ch.SensorCapacity; if (double.IsNaN(channel.ActualMaxRangeEu)) { channel.ActualMaxRangeEu = 150; } channel.ActualMinRangeEu = -1 * channel.ActualMaxRangeEu; } } catch (Exception ex) { APILogger.Log(ex); channel.ActualMinRangeEu = -150; channel.ActualMaxRangeEu = 150; } } } /// /// reads channel data from a binary .chn file, applying any needed transformations and returns /// y data [and in the case of FFT x and peak data too] /// /// /// /// /// /// /// /// /// public static List ReadChannelsBinaryData(ITestChannel channel, out double[] frequencies, out double peakMagnitude, out double peakFrequency, ChannelFilter cfc = ChannelFilter.AdHoc, IChartOptionsModel args = null, bool bVolts = false, SetReadCalcProgressValueDelegate SetProgress = null) { peakMagnitude = 0D; peakFrequency = 0D; //FB 13120 keep the ChannelFilter and frequency for AdHoc filters Tuple channelFilter; frequencies = null; try { var fullFileName = channel.BinaryFilePath + $"\\" + channel.BinaryFileName; fullFileName = Path.GetFullPath(fullFileName); var module = new Test.Module(new Test()) { AaFilterRateHz = channel.ParentModule.AaFilterRateHz, SerialNumber = channel.ParentModule.SerialNumber, SampleRateHz = channel.ParentModule.SampleRateHz, StartRecordSampleNumber = (ulong)channel.ParentModule.StartRecordSampleNumber, RequestedPostTriggerSeconds = channel.ParentModule.RequestedPostTriggerSeconds, PostTriggerSeconds = channel.ParentModule.PostTriggerSeconds, PreTriggerSeconds = channel.ParentModule.PreTriggerSeconds, StartRecordTimestampNanoSec = channel.ParentModule.StartRecordTimestampNanoSec, TriggerSampleNumbers = channel.ParentModule.TriggerSampleNumbers, TriggerTimestampNanoSec = channel.ParentModule.TriggerTimestampNanoSec, }; var ch = new Test.Module.AnalogInputChannel(module); InitializeFromIChannel(ref ch, channel); var channelInfo = new BinaryChannelHeader(); double[] dataX; double[] dataY = new double[0]; double[] rawDataY; double progress; var graphPoints = Convert.ToInt64(channelInfo.NumberOfSamples) - 1L; #region file system try { //using (var stream = System.IO.File.OpenRead(fullFileName)) using (var stream = new FileStream(fullFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { #region stream using (var reader = new BinaryReader(stream)) { var fields = Enum.GetValues(typeof(PersistentChannel.Field)).Cast().ToArray(); #region fields foreach (var field in fields) { switch (field) { case PersistentChannel.Field.AreSamplesSigned: channelInfo.AreSamplesSigned = reader.ReadUInt32(); break; case PersistentChannel.Field.BeginningOfData: break; case PersistentChannel.Field.BeginningOfFile: break; case PersistentChannel.Field.Crc32: var fileCrc = reader.ReadUInt32(); var computedCrc = channelInfo.Crc32; var unpaddedCrc = channelInfo.UnpaddedEuCrc32; var unpaddedPaddedCrc = channelInfo.UnpaddedEuStringPaddedEuLengthCrc32; if (computedCrc != fileCrc && fileCrc != unpaddedCrc && fileCrc != unpaddedPaddedCrc) { if (Environment.UserInteractive) { var dlg = new BadCRCBypass { FileName = channel.BinaryFileName }; if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.Cancel) { channel.ErrorMessage = "deserialized channel header CRC (" + fileCrc.ToString("X") + ") does not match the computed CRC (" + computedCrc.ToString("X") + ")"; return new List(); } } else { channel.ErrorMessage = "deserialized channel header CRC (" + fileCrc.ToString("X") + ") does not match the computed CRC (" + computedCrc.ToString("X") + ")"; return new List(); } } break; // Valid, but autogenerated by Event&Test channels case PersistentChannel.Field.DataZeroLevelCounts: channelInfo.DataZeroLevelCounts = reader.ReadInt32(); break; case PersistentChannel.Field.EngineeringUnit: var euBa = reader.ReadBytes(channelInfo.EuFieldLengthWithTerminator - 1); channelInfo.EngineeringUnit = Encoding.UTF8.GetString(euBa).ToCharArray(); if (channel is IEngineeringUnitAware) ((IEngineeringUnitAware)channel).EngineeringUnits = new string(channelInfo.EngineeringUnit); break; case PersistentChannel.Field.EuFieldLengthWithTerminator: channelInfo.EuFieldLengthWithTerminator = reader.ReadUInt16(); break; case PersistentChannel.Field.Excitation: if (channelInfo.HeaderVersionNumber >= 3) { channelInfo.Excitation = reader.ReadDouble(); } break; case PersistentChannel.Field.HeaderVersionNumber: channelInfo.HeaderVersionNumber = reader.ReadUInt32(); if (!BinaryChannelHeader.KnownHeaderVersionNumbers.Contains(channelInfo.HeaderVersionNumber)) { channel.ErrorMessage = "Channel header version number (" + channelInfo.HeaderVersionNumber.ToString("X") + ") current reader version (" + BinaryChannelHeader.CurrentVersionNumber.ToString("X") + ")"; return new List(); } break; case PersistentChannel.Field.IsoCode: channelInfo.IsoCode = Encoding.UTF8.GetString(reader.ReadBytes(16)).ToCharArray(); if (channel is IIsoCodeAware) ((IIsoCodeAware)channel).IsoCode = new string(channelInfo.IsoCode); break; case PersistentChannel.Field.MagicKey: channelInfo.MagicKey = reader.ReadUInt32(); if (channelInfo.MagicKey != BinaryChannelHeader.RequiredMagicKey) { channel.ErrorMessage = "Magic Key value for this channel file header (" + channelInfo.MagicKey.ToString("X") + ") doesn't match expected value (" + BinaryChannelHeader.RequiredMagicKey.ToString("X") + ")"; return new List(); } break; //Valid, but already read from test XML case PersistentChannel.Field.MvPerEu: channelInfo.MvPerEu = reader.ReadDouble(); break; case PersistentChannel.Field.NumberOfBitsPerSample: channelInfo.NumberOfBitsPerSample = reader.ReadUInt32(); break; case PersistentChannel.Field.NumberOfSamples: channelInfo.NumberOfSamples = module.NumberOfSamples = reader.ReadUInt64(); break; case PersistentChannel.Field.NumberOfTriggers: channelInfo.NumberOfTriggers = reader.ReadUInt16(); break; case PersistentChannel.Field.OffsetOfSampleDataStart: channelInfo.OffsetOfSampleDataStart = reader.ReadUInt64(); break; case PersistentChannel.Field.OriginalOffsetADC: if (channelInfo.HeaderVersionNumber >= 3) { channelInfo.OriginalOffsetADC = reader.ReadInt32(); } break; case PersistentChannel.Field.PostTestDiagnosticsLevelCounts: channelInfo.PostTestDiagnosticsLevelCounts = reader.ReadInt32(); break; case PersistentChannel.Field.PostTestZeroLevelCounts: channelInfo.PostTestZeroLevelCounts = reader.ReadInt32(); break; case PersistentChannel.Field.PreTestDiagnosticsLevelCounts: channelInfo.PreTestDiagnosticsLevelCounts = reader.ReadInt32(); break; case PersistentChannel.Field.PreTestNoisePercentageOfFullScale: channelInfo.PreTestNoisePercentageOfFullScale = ch.NoiseAsPercentageOfFullScale = reader.ReadDouble(); break; case PersistentChannel.Field.PreTestZeroLevelCounts: channelInfo.PreTestZeroLevelCounts = ch.PreTestZeroLevelAdc = (short)reader.ReadInt32(); break; case PersistentChannel.Field.RemovedADC: if (channelInfo.HeaderVersionNumber >= 2) { channelInfo.RemovedADC = ch.RemovedADC = reader.ReadInt32(); } break; case PersistentChannel.Field.SampleRate: channelInfo.SampleRate = module.SampleRateHz = (float)reader.ReadDouble(); break; //Valid, but already read from test XML case PersistentChannel.Field.ScaleFactorMv: channelInfo.ScaleFactorMv = reader.ReadDouble(); break; case PersistentChannel.Field.TriggerAdjustmentSamples: if (channelInfo.HeaderVersionNumber >= 3) { channelInfo.TriggerAdjustmentSamples = reader.ReadInt32(); } break; case PersistentChannel.Field.TriggerSampleNumbers: try { channelInfo.TriggerSampleNumbers = new UInt64[channelInfo.NumberOfTriggers]; module.TriggerSampleNumbers = new List(); for (var i = 0; i < channelInfo.NumberOfTriggers; i++) { module.TriggerSampleNumbers.Add(channelInfo.TriggerSampleNumbers[i] = reader.ReadUInt64()); } channel.ParentModule.TriggerSampleNumbers = module.TriggerSampleNumbers; } catch (System.Exception ex) { channel.ErrorMessage = "Encountered problem extracting channel data from serialization: " + ex.Message; return new List(); } break; case PersistentChannel.Field.ZeroMvInADC: if (channelInfo.HeaderVersionNumber >= 3) { channelInfo.ZeroMvInADC = ch.ZeroMvInADC = (short)reader.ReadInt32(); } break; case PersistentChannel.Field.WindowAverageADC: if (channelInfo.HeaderVersionNumber >= 4) { channelInfo.WindowAverageADC = ch.WindowAverageADC = (short)reader.ReadInt32(); } break; } } #endregion fields long startIndex, endIndex = 0L; long startRecord = (long)channel.ParentModule.StartRecordSampleNumber; long triggerSampleNumber = Convert.ToInt64(channelInfo.TriggerSampleNumbers[0]); var timeMultiplier = args.TimeUnitType == TimeUnitTypeEnum.MS ? 1000D : 1D; if (args.MinFixedT != 0 && args.MaxFixedT != 0 && args.LockedT) { //normalize fixedT to seconds before getting index startIndex = (long)((args.MinFixedT / timeMultiplier * channelInfo.SampleRate) + triggerSampleNumber - startRecord); endIndex = (long)((args.MaxFixedT / timeMultiplier * channelInfo.SampleRate) + triggerSampleNumber - startRecord); } else { //if we're not locking the view to a subset, get all samples startIndex = 0L; endIndex = Convert.ToInt64(channelInfo.NumberOfSamples) - 1L; } stream.Seek(Convert.ToInt64(channelInfo.OffsetOfSampleDataStart) + startIndex * 2L, SeekOrigin.Begin); var width = args.WidthPoints > 0 ? args.WidthPoints * 5 : 10000; graphPoints = endIndex - startIndex > width ? width : endIndex - startIndex; //38048 enforce points limit if (graphPoints > Constants.MAX_VIEWER_POINTS) graphPoints = Constants.MAX_VIEWER_POINTS; long interval = 1; double[] rawDataX = new double[(endIndex - startIndex) / interval + 1]; rawDataY = new double[(endIndex - startIndex) / interval + 1]; long j = 0; progress = 0D; if (null != SetProgress) SetProgress(string.Empty, progress); for (long i = startIndex; i <= endIndex; i += interval) { var curX = (startRecord - triggerSampleNumber + i) / channelInfo.SampleRate; rawDataX[j] = curX; rawDataY[j] = reader.ReadInt16(); j++; //if interval is 0, we are already in place, if it's greater than 0 we need to skip ahead 2 places for each sample if (interval > 1) { stream.Seek((interval - 1) * 2, SeekOrigin.Current); } var step = (double)j / rawDataX.Length * 100; if (Math.Floor(step) > progress) { progress = step; if (null != SetProgress) SetProgress(string.Empty, progress);//only update on whole % changes } } reader.Close(); if (null != SetProgress) SetProgress(string.Empty, 100); if (ch.IsDigital()) { channelFilter = Tuple.Create(ChannelFilter.Unfiltered, 0.0); } //chart options specifies different filter types //the custom/adhoc option isn't currently handled, we'll have to add that else { switch (args.Filter) { case FilterOptionEnum.TestSetupDefault: //FB 13120 Get Channel filter and frequency channelFilter = GetChannelFilter(channel.SoftwareFilter); break; case FilterOptionEnum.Custom: //FB 13120 Get Channel filter and frequency channelFilter = GetChannelFilter(args.SelectedFilter); break; default: channelFilter = Tuple.Create(ChannelFilter.Unfiltered, 0.0); break; } } //make FFT always unfiltered ... //the reasoning behind this was that the FFT is automatically going to be breaking out //the frequencies anyhow if (args?.UnitType == ChartUnitTypeEnum.FFT || args?.UnitType == ChartUnitTypeEnum.PSD) { channelFilter = Tuple.Create(ChannelFilter.Unfiltered, 0.0); } else if (rawDataY.Length > DFConstantsAndEnums.MAX_SAMPLES_TO_FILTER) { channelFilter = Tuple.Create(ChannelFilter.Unfiltered, 0.0); args.SetSelectedFilterToUnfilteredNoRead(); args.Filter = FilterOptionEnum.Unfiltered; } if (null != SetProgress) SetProgress(DTS.Common.Strings.Strings.ReadChannelBinaryData_ApplyingFilter, 0D); var fu = new FilterUtility { //FB 13120 Set Channel filter Cfc = channelFilter.Item1, //FB 13120 Set frequency AdHocFrequency = channelFilter.Item2, SampleRate = args?.UnitType == ChartUnitTypeEnum.FFT || args?.UnitType == ChartUnitTypeEnum.PSD ? channel.ParentModule.SampleRateHz : (int)(1.0 / (rawDataX[1] - rawDataX[0])) }; var dataYFiltered = fu.ApplyFilter(rawDataY, null, false, (double d) => SetProgress(DTS.Common.Strings.Strings.ReadChannelBinaryData_ApplyingFilter, d)); if (null != SetProgress) SetProgress(string.Empty, 100D); rawDataY = dataYFiltered; if (args.DecimateData && !(args?.UnitType == ChartUnitTypeEnum.FFT || args?.UnitType == ChartUnitTypeEnum.PSD)) { if (null != SetProgress) SetProgress(DTS.Common.Strings.Strings.ReadChannelBinaryData_GettingDecimation, 0D); //only decimate w/LTTB at this point if EU, mV, or ADC LargestTriangleThreeBuckets(rawDataX, rawDataY, (int)graphPoints, out dataX, out dataY, SetProgress); if (null != SetProgress) SetProgress(string.Empty, 100D); } else { dataX = rawDataX; dataY = rawDataY; } } stream.Close(); #endregion stream // Remove any word-aligning padding from the EU field. channelInfo.EngineeringUnit = new string(channelInfo.EngineeringUnit).Trim().ToCharArray(); try { var analogChannel = ch; if (null != analogChannel) { if (channel is Test.Module.CalculatedChannel) { var cc = (Test.Module.CalculatedChannel)channel; analogChannel.AverageAdcOverTime = new AverageShortValueOverTime(0, cc.SampleRateHz, "s"); } else { analogChannel.AverageAdcOverTime = new AverageShortValueOverTime(0, analogChannel.ParentModule.SampleRateHz, "s"); } analogChannel.FileName = channel.BinaryFileName; } ch.PersistentChannelInfo = new PersistentChannel(fullFileName, null, false); ch.Data = new Test.Module.Channel.DataArray { ScaleFactorMv = channelInfo.ScaleFactorMv, MvPerEu = channelInfo.MvPerEu, Multiplier = analogChannel.Multiplier, UnitConversion = analogChannel.UnitConversion, UserOffsetEU = analogChannel.UserOffsetEU, ScaleFactorEU = analogChannel.ScaleFactorEU, UseEUScaleFactors = analogChannel.Data.UseEUScaleFactors, }; analogChannel.Multiplier = ch.Data.Multiplier; analogChannel.UnitConversion = ch.Data.UnitConversion; analogChannel.UserOffsetEU = ch.Data.UserOffsetEU; } catch (System.Exception ex) { APILogger.Log(ex); channel.ErrorMessage = "Encountered problem extracting channel data from serialization: " + ex.Message; return new List(); } } } catch (Exception ex) { APILogger.Log(ex); channel.ErrorMessage = "Encountered problem extracting channel data from serialization: " + ex.Message; return new List(); } #endregion file system var ds = GetDataScaler(ch); LinearizationFormula lf = new LinearizationFormula(ch.LinearizationFormula); lf.FromSerializeString(channel.LinearizationFormula); ch.LinearizationFormula = lf; ds.SetLinearizationFormula(ch.LinearizationFormula); channel.ActualMaxRangeMv = ds.GetAdcToMvScalingFactor() >= 0 ? ds.GetMvOrV(channel.ActualMaxRangeAdc, bVolts) : ds.GetMvOrV(channel.ActualMinRangeAdc, bVolts); channel.ActualMinRangeMv = ds.GetAdcToMvScalingFactor() >= 0 ? ds.GetMvOrV(channel.ActualMinRangeAdc, bVolts) : ds.GetMvOrV(channel.ActualMaxRangeAdc, bVolts); if (ch.IsDigital()) { channel.ActualMaxRangeEu = Math.Max(ch.DigitalMultiplier.DefaultValue, ch.DigitalMultiplier.ActiveValue); channel.ActualMinRangeEu = Math.Min(ch.DigitalMultiplier.ActiveValue, ch.DigitalMultiplier.DefaultValue); } progress = 0D; switch (args?.UnitType) { case ChartUnitTypeEnum.PSD: //25554 PSD is in g^2/Hz, so get EU val in g case ChartUnitTypeEnum.EU: SwitchDataToEU(SetProgress, ref dataY, channel, ch, ds, true); SetEUStats(SetProgress, ref rawDataY, channel, ch, ds); break; case ChartUnitTypeEnum.mV: SwitchDataToMV(SetProgress, ref dataY, ds, bVolts); SetMVStats(SetProgress, ref rawDataY, channel, ch, ds, bVolts); break; case ChartUnitTypeEnum.ADC: SwitchDataToADC(SetProgress, ref dataY, ds); SetADCStats(SetProgress, ref rawDataY, channel, ch, ds); break; // 6402 Implement ability to switch to FFT live in the Review case ChartUnitTypeEnum.FFT: if (null != SetProgress) SetProgress(DTS.Common.Strings.Strings.ReadChannelBinaryData_CalculatingFFT, 0D); DoFFT(ch, out var fftxaxis, out var fftyaxis, ds, module.SampleRateHz, out var magnitude, out var frequency); peakFrequency = frequency; peakMagnitude = magnitude; dataY = fftyaxis; frequencies = fftxaxis; dataX = fftxaxis; if (args.DecimateData) { //only decimate w/LTTB at this point if EU, mV, or ADC LargestTriangleThreeBuckets(dataX, dataY, (int)graphPoints, out var fftDataX, out var fftDataY, SetProgress); dataX = fftDataX; dataY = fftDataY; } if (null != SetProgress) SetProgress(string.Empty, 100D); break; } ch.PersistentChannelInfo.Dispose(); ch.PersistentChannelInfo = null; return new List { dataX, dataY }; } catch (Exception ex) { APILogger.Log(ex); channel.ErrorMessage = "Encountered problem extracting channel data from serialization: " + ex.Message; return new List(); } } //FB 13120 Get Channel filter and frequency based on FilterClass private static Tuple GetChannelFilter(IFilterClass filter) { if (filter == null) return Tuple.Create(ChannelFilter.Unfiltered, 0.0); switch (filter.FClass) { case FilterClassType.CFC10: return Tuple.Create(ChannelFilter.Class10, 17.0); case FilterClassType.CFC60: return Tuple.Create(ChannelFilter.Class60, 100.0); case FilterClassType.CFC180: return Tuple.Create(ChannelFilter.Class180, 300.0); case FilterClassType.CFC600: return Tuple.Create(ChannelFilter.Class600, 1000.0); case FilterClassType.CFC1000: return Tuple.Create(ChannelFilter.Class1000, 1650.0); case FilterClassType.AdHoc: return Tuple.Create(ChannelFilter.AdHoc, filter.Frequency); case FilterClassType.Unfiltered: return Tuple.Create(ChannelFilter.Unfiltered, 0.0); case FilterClassType.None: return Tuple.Create(ChannelFilter.Unfiltered, 0.0); default: return Tuple.Create(ChannelFilter.UnfilteredZero, 0.0); } } //FB 13120 Get Channel filter and frequency based on FilterClass string private static Tuple GetChannelFilter(string arg) { if (string.IsNullOrEmpty(arg)) { return Tuple.Create(ChannelFilter.Unfiltered, 0.0); } arg = arg.ToLower().Replace("hz", ""); switch (arg) { case "cfc 10": return Tuple.Create(ChannelFilter.Class10, 17.0); case "cfc 60": return Tuple.Create(ChannelFilter.Class60, 100.0); case "cfc 180": return Tuple.Create(ChannelFilter.Class180, 300.0); case "cfc 600": return Tuple.Create(ChannelFilter.Class600, 1000.0); case "cfc 1000": return Tuple.Create(ChannelFilter.Class1000, 1650.0); case "none": return Tuple.Create(ChannelFilter.Unfiltered, 0.0); } double freq = 0; if (double.TryParse(arg, out freq)) { return Tuple.Create(ChannelFilter.AdHoc, freq); } return Tuple.Create(ChannelFilter.Unfiltered, 0.0); } private static void InitializeFromIChannel(ref Test.Module.AnalogInputChannel ch, ITestChannel channel) { ch.IsInverted = channel.IsInverted; ch.DigitalMultiplier = new DigitalInputScaleMultiplier(); ch.DigitalMultiplier.FromDbSerializeString(channel.DigitalMultiplier); ch.AtCapacity = channel.AtCapacity; ch.Bridge = ParseEnum(channel.Bridge); if (channel.DigitalMode != null) { ch.DigitalMode = ParseEnum(channel.DigitalMode); } ch.Multiplier = channel.Multiplier; ch.CapacityOutputIsBasedOn = channel.CapacityOutputIsBasedOn; ch.SensitivityUnits = String.IsNullOrEmpty(channel.SensitivityUnits) ? SensorConstants.SensUnits.NONE : ParseEnum(channel.SensitivityUnits); ch.UnitConversion = channel.UnitConversion; ch.UserOffsetEU = channel.UserOffsetEu; ch.ZeroMethod = channel.ZeroMethod == "UsePreCalZero" ? ZeroMethodType.UsePreEventDiagnosticsZero : ParseEnum(channel.ZeroMethod); ch.FactoryExcitationVoltage = channel.FactoryExcitationVoltage; ch.MeasuredExcitationVoltage = channel.MeasuredExcitationVoltage; ch.SensorPolarity = channel.SensorPolarity; ch.InitialOffset = channel.InitialOffset; ch.ExcitationVoltage = String.IsNullOrEmpty(channel.ExcitationVoltage) ? ExcitationVoltageOptions.ExcitationVoltageOption.Undefined : ParseEnum(channel.ExcitationVoltage); ch.ProportionalToExcitation = channel.ProportionalToExcitation; ch.ZeroAverageWindow = new Test.IntervalSec(channel.ZeroAverageWindowBegin, channel.ZeroAverageWindowEnd); ch.Data = new Test.Module.Channel.DataArray(); ch.Data.UseEUScaleFactors = channel.UseEUScaler; ch.ScaleFactorEU = channel.ScaleFactorEU; ch.DesiredRange = channel.DesiredRange; ch.SensorCapacity = channel.SensorCapacity; } public static T ParseEnum(string value) { return (T)Enum.Parse(typeof(T), value, true); } public static DataScaler GetDataScaler(Test.Module.AnalogInputChannel channel) { var scaler = new DataScaler(); try { scaler.IsInverted = channel?.IsInverted ?? false; scaler.SetLinearizationFormula(channel != null && channel.LinearizationFormula.IsValid() ? channel.LinearizationFormula : null); scaler.Digital = channel.IsDigital(); scaler.SetDigitalMultiplier(channel.DigitalMultiplier); scaler.DigitalMode = channel.DigitalMode; scaler.SetScaleFactorMv(channel.Data.ScaleFactorMv); //Set this so that if it is needed (TDAS, TSR AIR) it will be written to the .dts file and used by View and Export. scaler.SetScaleFactorEU(channel.Data.ScaleFactorEU); scaler.SetUseEUScaleFactors(channel.Data.UseEUScaleFactors); scaler.UnitConversion = channel.UnitConversion; scaler.BasedOnOutputAtCapacity = channel.AtCapacity; scaler.CapacityOutputIsBasedOn = channel.CapacityOutputIsBasedOn; scaler.SensitivityUnits = channel.SensitivityUnits; scaler.Multiplier = channel.Multiplier; scaler.UserOffsetEU = channel.UserOffsetEU; scaler.IEPE = channel.Bridge == SensorConstants.BridgeType.IEPE; scaler.Digital = channel.Bridge == SensorConstants.BridgeType.DigitalInput; scaler.SetMvPerEu(channel.Data.MvPerEu); scaler.SetDataZeroLevelADC(channel.DataZeroLevelAdc); scaler.SetRemovedADC(channel.RemovedADC); scaler.SetRemovedInternalADC(channel.RemovedInternalADC); scaler.SetZeroMvInADC(channel.ZeroMvInADC); scaler.UserOffsetEU = channel.UserOffsetEU; try { scaler.SetWindowAverageADC(channel.WindowAverageADC); } catch (Exception ex) { APILogger.Log(ex); } if (!(channel is Test.Module.AnalogInputChannel)) return scaler; { var analogChannel = channel; scaler.SetInitialOffset(analogChannel.InitialOffset); scaler.ZeroMethodType = analogChannel.ZeroMethod; scaler.NominalExcitationVoltage = analogChannel.ExcitationVoltage; if (analogChannel.MeasuredExcitationVoltageValid) { try { scaler.MeasuredExcitationVoltage = analogChannel.MeasuredExcitationVoltage; } catch (Exception ex) { APILogger.Log(ex); } } if (analogChannel.FactoryExcitationVoltageValid) { try { scaler.FactoryExcitationVoltage = analogChannel.FactoryExcitationVoltage; } catch (Exception ex) { APILogger.Log(ex); } } scaler.ProportionalToExcitation = analogChannel.ProportionalToExcitation; } } catch (Exception ex) { APILogger.Log(ex); } return scaler; } public static void LargestTriangleThreeBuckets(double[] dataX, double[] dataY, int threshold, out double[] lttbDataX, out double[] lttbDataY, SetReadCalcProgressValueDelegate SetProgress = null) { var dataLength = dataX.Length; if (threshold >= dataLength || threshold == 0) { lttbDataX = new double[0]; lttbDataY = new double[0]; return; // Nothing to do } var progress = 0D; var samples = threshold; lttbDataX = new double[samples]; lttbDataY = new double[samples]; // Bucket size. Leave room for start and end data points var every = (double)(dataLength - 2) / (threshold - 2); var a = 0; var maxAreaPoint = new Tuple(0, 0); var nextA = 0; lttbDataX[0] = dataX[0]; lttbDataY[0] = dataY[0]; var lttb = 1; for (var i = 0; i < threshold - 2; i++) { // Calculate point average for next bucket (containing c) var avgX = 0D; var avgY = 0D; var avgRangeStart = (int)(Math.Floor((i + 1) * every) + 1); var avgRangeEnd = (int)(Math.Floor((i + 2) * every) + 1); avgRangeEnd = avgRangeEnd < dataLength ? avgRangeEnd : dataLength; var avgRangeLength = avgRangeEnd - avgRangeStart; for (; avgRangeStart < avgRangeEnd; avgRangeStart++) { avgX += dataX[avgRangeStart]; // * 1 enforces Number (value may be Date) avgY += dataY[avgRangeStart]; } avgX /= avgRangeLength; avgY /= avgRangeLength; // Get the range for this bucket var rangeOffs = (int)(Math.Floor((i + 0) * every) + 1); var rangeTo = (int)(Math.Floor((i + 1) * every) + 1); // Point a double pointAx = dataX[a]; // enforce Number (value may be Date) double pointAy = dataY[a]; var maxArea = -1D; for (; rangeOffs < rangeTo; rangeOffs++) { // Calculate triangle area over three buckets var area = Math.Abs((pointAx - avgX) * (dataY[rangeOffs] - pointAy) - (pointAx - dataX[rangeOffs]) * (avgY - pointAy) ) * 0.5; if (area > maxArea) { maxArea = area; maxAreaPoint = new Tuple(dataX[rangeOffs], dataY[rangeOffs]); nextA = rangeOffs; // Next a is this b } } lttbDataX[lttb] = maxAreaPoint.Item1; lttbDataY[lttb] = maxAreaPoint.Item2; lttb++; a = nextA; // This a is the next a (chosen b) var step = (double)i / (threshold - 2) * 100; if (Math.Floor(step) > progress) { progress = step; if (null != SetProgress) SetProgress(string.Empty, progress);//only update on whole % changes } } lttbDataX[samples - 1] = dataX[dataLength - 1]; lttbDataY[samples - 1] = dataY[dataLength - 1]; } } } }