1969 lines
109 KiB
C#
1969 lines
109 KiB
C#
/*
|
|
* 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
|
|
{
|
|
///
|
|
/// <summary>
|
|
/// Utility object for serializing <see cref="DTS.Serialization.Test"/>s to disk.
|
|
/// </summary>
|
|
///
|
|
public partial class Reader : Reader<File>, IReader<Test>
|
|
{
|
|
///
|
|
/// <summary>
|
|
/// Initialize an instance of the File.Reader class.
|
|
/// </summary>
|
|
///
|
|
/// <param name="fileType">
|
|
/// The <see cref="DTS.Serialization.SliceRaw.File"/>-type this deserializer is associated with.
|
|
/// </param>
|
|
///
|
|
public Reader(File fileType)
|
|
: base(fileType)
|
|
{
|
|
}
|
|
|
|
///// <summary>
|
|
///// Notify <see cref="DTS.Serialization.BeginEventHandler"/> subscribers that the write
|
|
///// is starting.
|
|
///// </summary>
|
|
//public event BeginEventHandler OnBegin;
|
|
|
|
///// <summary>
|
|
///// Notify <see cref="DTS.Serialization.EndEventHandler"/> subscribers that the write
|
|
///// is finished.
|
|
///// </summary>
|
|
//public event EndEventHandler OnEnd;
|
|
|
|
///// <summary>
|
|
///// Notify <see cref="DTS.Serialization.TickEventHandler"/> subscribers that we are one
|
|
///// tick closer to write completion.
|
|
///// </summary>
|
|
//public event TickEventHandler OnTick;
|
|
|
|
/// <summary>
|
|
/// Indicates the type of read event that is represented by the Event that has received it.
|
|
/// </summary>
|
|
public enum Event
|
|
{
|
|
NumberOfFilesDetermined,
|
|
SingleFileReadComplete,
|
|
FullReadComplete,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for read events.
|
|
/// </summary>
|
|
/// <param name="numFiles">
|
|
/// The <see cref="int"/> 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.
|
|
/// </param>
|
|
public delegate void EventHandler(Event readEvent, int numFiles);
|
|
|
|
/// <summary>
|
|
/// Get a complete list of test files in the specified directory.
|
|
/// </summary>
|
|
///
|
|
/// <param name="directory">
|
|
/// The name <see cref="string"/> of the directory to be scoured for test
|
|
/// files.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// A list of filenames naming all test files in the specified directory.
|
|
/// </returns>
|
|
///
|
|
private List<string> GetChannelFilenames(string directory)
|
|
{
|
|
try
|
|
{
|
|
var channelFilenames = new List<string>(Directory.GetFiles(directory, "*" + ChannelFileExtension));
|
|
channelFilenames.Sort(ChFileCompare);
|
|
|
|
return channelFilenames;
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting channel filenames", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get full name of test serialization file.
|
|
/// </summary>
|
|
///
|
|
/// <param name="directory">
|
|
/// The <see cref="string"/> representation of the directory to be searched for test files.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// The <see cref="string"/> representation of the found test filename.
|
|
/// </returns>
|
|
///
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform read test serialization from it's containing file.
|
|
/// </summary>
|
|
///
|
|
/// <param name="path">
|
|
/// The <see cref="string"/> representation of the pathname of the base
|
|
/// directory containing the test file.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// The <see cref="string"/> serialization contained by the specified file.
|
|
/// </returns>
|
|
///
|
|
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 + "\"" : "<<NULL>>"), ex);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Perform read test serialization from it's containing file.
|
|
/// </summary>
|
|
///
|
|
/// <param name="path">
|
|
/// The <see cref="string"/> representation of the pathname of the base
|
|
/// directory containing the test file.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// all lines from given file
|
|
/// </returns>
|
|
///
|
|
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 + "\"" : "<<NULL>>"), ex);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Deserialize the test structure contained in the specified serialization string.
|
|
/// </summary>
|
|
///
|
|
/// <param name="serialization">
|
|
/// The <see cref="string"/> serialization from whence the test will be extracted
|
|
/// and instantiated.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// The <see cref="DTS.Serialization.Test"/> represented by specified serialization
|
|
/// string.
|
|
/// </returns>
|
|
///
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform read of all test and channel data.
|
|
/// </summary>
|
|
///
|
|
/// <param name="directory">
|
|
/// The <see cref="string"/> pathname of the directory containing the serialization to be read.
|
|
/// </param>
|
|
///
|
|
/// <param name="test">
|
|
/// The <see cref="DTS.Serialization.Test"/> generated from the specified serialization.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// A test populated with deserialized test and channel data.
|
|
/// </returns>
|
|
///
|
|
public void Read(string directory, out Test test)
|
|
{
|
|
Read(directory, out test, null);
|
|
}
|
|
/// <summary>
|
|
/// returns true if the channel is a squib channel
|
|
/// </summary>
|
|
/// <param name="channel"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
/// <summary>
|
|
/// Will return a collection of ITestChannels matching channel
|
|
/// This method was created because
|
|
/// The logic added in http://manuscript.dts.local/f/cases/44214/
|
|
/// would cause a filename not to be found in this case:
|
|
/// http://manuscript.dts.local/f/cases/45091/Add-ability-to-export-HDF-using-SLICEWare-data
|
|
/// </summary>
|
|
/// <param name="channel"></param>
|
|
/// <param name="inputChannels"></param>
|
|
/// <returns></returns>
|
|
private static IEnumerable<ITestChannel> GetFileNameForChannel(Test.Module.Channel channel, ITestChannel[] inputChannels)
|
|
{
|
|
if (null == channel) { return []; }
|
|
if (null == inputChannels || 0 == inputChannels.Length) { return []; }
|
|
|
|
var matches = new ITestChannel[0];
|
|
|
|
//if there's only one match using absolute display order, we are done, return that match
|
|
var exists = Array.Exists(inputChannels, x => x.AbsoluteDisplayOrder == channel.AbsoluteDisplayOrder);
|
|
if (exists) //checking the exist here isn't really needed, I just wanted to make it clearer what was going on
|
|
{
|
|
matches = inputChannels.Where(x => x.AbsoluteDisplayOrder == channel.AbsoluteDisplayOrder).ToArray();
|
|
if (null != matches && 1 == matches.Length) { return matches; }
|
|
}
|
|
|
|
//now use the original selection logic - i'm guessing this is for datasets before absolute display order was present
|
|
//and a different id system was present
|
|
//if we've gotten here we can see if that logic fixes the issue, but if not we want to return the matches above
|
|
//as that contains the logic for modern datasets
|
|
exists = Array.Exists(inputChannels, x => x.ChannelId == channel.ChannelId);
|
|
if (exists)
|
|
{
|
|
var matches2 = inputChannels.Where(x => x.ChannelId == channel.ChannelId);
|
|
if (matches2.Count() < matches.Count() && matches2.Any()) { return matches2; }
|
|
}
|
|
//well this is a little unexpected - we had multiple matches using both absolute display order and the old id logic
|
|
//so just go with the absolute display order matches
|
|
return matches;
|
|
}
|
|
/// <summary>
|
|
/// Perform read of all test and channel data.
|
|
/// </summary>
|
|
///
|
|
/// <param name="directory">
|
|
/// The <see cref="string"/> pathname of the directory containing the serialization to be read.
|
|
/// </param>
|
|
///
|
|
/// <param name="test">
|
|
/// The <see cref="DTS.Serialization.Test"/> generated from the specified serialization.
|
|
/// </param>
|
|
///
|
|
/// <param name="onEvent">
|
|
/// An Event to be notified with read
|
|
/// status updates.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// A test populated with deserialized test and channel data.
|
|
/// </returns>
|
|
///
|
|
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<ITestChannel>();
|
|
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<string>();
|
|
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<ulong>();
|
|
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 = GetFileNameForChannel(c, inputChannels.ToArray());
|
|
//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 ?? "<NULL>") + "\"", 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<PersistentChannel.Field>().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<ulong>();
|
|
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<short>
|
|
{
|
|
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
|
|
}
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="timeDomainData"></param>
|
|
/// <param name="length"></param>
|
|
/// <param name="bUseQuick"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="ch"></param>
|
|
/// <param name="fftxaxis"></param>
|
|
/// <param name="fftyaxis"></param>
|
|
/// <param name="ds"></param>
|
|
/// <param name="sampleRate"></param>
|
|
/// <param name="peakMagnitude"></param>
|
|
/// <param name="peakFrequency"></param>
|
|
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<double>();
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// sets the stats for the current unit to the ADC stats, determines the ADC stats if needed
|
|
/// </summary>
|
|
/// <param name="SetProgress"></param>
|
|
/// <param name="data"></param>
|
|
/// <param name="channel"></param>
|
|
/// <param name="ch"></param>
|
|
/// <param name="ds"></param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// switches a raw data range in ADC from file to ADC data to display
|
|
/// </summary>
|
|
/// <param name="SetProgress"></param>
|
|
/// <param name="data"></param>
|
|
/// <param name="ds"></param>
|
|
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
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// sets the current stats to the mV stats, determines the mv stats if needed
|
|
/// </summary>
|
|
/// <param name="SetProgress"></param>
|
|
/// <param name="data"></param>
|
|
/// <param name="channel"></param>
|
|
/// <param name="ch"></param>
|
|
/// <param name="ds"></param>
|
|
/// <param name="volts"></param>
|
|
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;
|
|
}
|
|
/// <summary>
|
|
/// switches Ydata (from ADC) to mV
|
|
/// </summary>
|
|
/// <param name="SetProgress"></param>
|
|
/// <param name="data"></param>
|
|
/// <param name="channel"></param>
|
|
/// <param name="ch"></param>
|
|
/// <param name="ds"></param>
|
|
/// <param name="volts"></param>
|
|
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
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// returns the T0 index
|
|
/// </summary>
|
|
/// <param name="channel"></param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
/// <summary>
|
|
/// sets the current stats to EU, determines the EU stats if needed
|
|
/// </summary>
|
|
/// <param name="SetProgress"></param>
|
|
/// <param name="data"></param>
|
|
/// <param name="channel"></param>
|
|
/// <param name="ch"></param>
|
|
/// <param name="ds"></param>
|
|
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;
|
|
}
|
|
/// <summary>
|
|
/// switches a data y range to EU
|
|
/// </summary>
|
|
/// <param name="SetProgress"></param>
|
|
/// <param name="data"></param>
|
|
/// <param name="channel"></param>
|
|
/// <param name="ch"></param>
|
|
/// <param name="ds"></param>
|
|
/// <param name="SetActualRange"></param>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 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]
|
|
/// </summary>
|
|
/// <param name="channel"></param>
|
|
/// <param name="frequencies"></param>
|
|
/// <param name="peakMagnitude"></param>
|
|
/// <param name="peakFrequency"></param>
|
|
/// <param name="cfc"></param>
|
|
/// <param name="args"></param>
|
|
/// <param name="bVolts"></param>
|
|
/// <returns></returns>
|
|
public static List<double[]> 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, double> 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<PersistentChannel.Field>().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<double[]>();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
channel.ErrorMessage = "deserialized channel header CRC (" + fileCrc.ToString("X") + ") does not match the computed CRC (" + computedCrc.ToString("X") + ")";
|
|
return new List<double[]>();
|
|
}
|
|
}
|
|
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<double[]>();
|
|
}
|
|
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<double[]>();
|
|
}
|
|
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<ulong>();
|
|
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<double[]>();
|
|
}
|
|
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<short>
|
|
{
|
|
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<double[]>();
|
|
}
|
|
}
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log(ex);
|
|
channel.ErrorMessage = "Encountered problem extracting channel data from serialization: " + ex.Message;
|
|
return new List<double[]>();
|
|
}
|
|
#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<double[]> { dataX, dataY };
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log(ex);
|
|
channel.ErrorMessage = "Encountered problem extracting channel data from serialization: " + ex.Message;
|
|
return new List<double[]>();
|
|
}
|
|
}
|
|
|
|
//FB 13120 Get Channel filter and frequency based on FilterClass
|
|
private static Tuple<ChannelFilter, double> 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<ChannelFilter, double> 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<SensorConstants.BridgeType>(channel.Bridge);
|
|
|
|
if (channel.DigitalMode != null)
|
|
{
|
|
ch.DigitalMode = ParseEnum<DigitalInputModes>(channel.DigitalMode);
|
|
}
|
|
|
|
ch.Multiplier = channel.Multiplier;
|
|
ch.CapacityOutputIsBasedOn = channel.CapacityOutputIsBasedOn;
|
|
ch.SensitivityUnits = String.IsNullOrEmpty(channel.SensitivityUnits) ? SensorConstants.SensUnits.NONE : ParseEnum<SensorConstants.SensUnits>(channel.SensitivityUnits);
|
|
ch.UnitConversion = channel.UnitConversion;
|
|
ch.UserOffsetEU = channel.UserOffsetEu;
|
|
ch.ZeroMethod = channel.ZeroMethod == "UsePreCalZero" ? ZeroMethodType.UsePreEventDiagnosticsZero : ParseEnum<ZeroMethodType>(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<ExcitationVoltageOptions.ExcitationVoltageOption>(channel.ExcitationVoltage);
|
|
ch.ProportionalToExcitation = channel.ProportionalToExcitation;
|
|
ch.ZeroAverageWindow = new Test.IntervalSec(channel.ZeroAverageWindowBegin, channel.ZeroAverageWindowEnd);
|
|
ch.Data = new Test.Module.Channel.DataArray<short>();
|
|
ch.Data.UseEUScaleFactors = channel.UseEUScaler;
|
|
ch.ScaleFactorEU = channel.ScaleFactorEU;
|
|
ch.DesiredRange = channel.DesiredRange;
|
|
ch.SensorCapacity = channel.SensorCapacity;
|
|
}
|
|
|
|
public static T ParseEnum<T>(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<double, double>(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<double, double>(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];
|
|
}
|
|
}
|
|
}
|
|
} |