Files
DP44/Common/DTS.Common.Serialization/SliceRaw/SliceRaw.File.Reader.cs
2026-04-17 14:55:32 -04:00

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