913 lines
47 KiB
C#
913 lines
47 KiB
C#
/*
|
|
* FtssTsv.File.Writer.cs
|
|
*
|
|
* Copyright © 2009
|
|
* Diversified Technical Systems, Inc.
|
|
* All Rights Reserved
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.IO;
|
|
using System.Text;
|
|
using DTS.Common.DAS.Concepts;
|
|
using DTS.Common.DAS.Concepts.DAS.Channel;
|
|
using DTS.Common.Utilities;
|
|
using DTS.Common.Utilities.DotNetProgrammingConstructs;
|
|
using DTS.Common.Utilities.Logging;
|
|
using DTS.Common;
|
|
using DTS.Common.Enums.Sensors;
|
|
using DTS.Common.Utils;
|
|
|
|
namespace DTS.Serialization.FtssTsv
|
|
{
|
|
// *** see FtssTsv.File.cs ***
|
|
public partial class File
|
|
{ ///
|
|
/// <summary>
|
|
/// Utility object for serializing <see cref="DTS.Serialization.Test"/>s to disk
|
|
/// in the FTSS CSV format.
|
|
/// </summary>
|
|
///
|
|
public partial class Writer : Writer<File>, IWriter<Test>
|
|
{
|
|
/// <summary>
|
|
/// Initialize an instance of the FtssTsv.File.Writer class.
|
|
/// </summary>
|
|
///
|
|
/// <param name="fileType">
|
|
/// The associated <see cref="DTS.SErialization.FtssTsv.File"/> object.
|
|
/// </param>
|
|
///
|
|
internal Writer(File fileType, int encoding)
|
|
: base(fileType, encoding)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// The different export modes that should theoretically be supported by this format.
|
|
/// </summary>
|
|
public enum ExportMode
|
|
{
|
|
FtssExcel,
|
|
Ttc,
|
|
Standard,
|
|
}
|
|
|
|
private const string NUMBER_FORMAT = "F8";
|
|
private const string TAB_LIST_SEPARATOR = "\t"; //Tab
|
|
|
|
/// <summary>
|
|
/// Get/set the current CSV export format.
|
|
/// </summary>
|
|
public ExportMode CurrentExportMode
|
|
{
|
|
get => _CurrentExportMode.Value;
|
|
set => _CurrentExportMode.Value = value;
|
|
}
|
|
private readonly Property<ExportMode> _CurrentExportMode
|
|
= new Property<ExportMode>(
|
|
typeof(Writer).Namespace + ".File.Writer.CurrentExportMode",
|
|
ExportMode.FtssExcel,
|
|
true
|
|
);
|
|
|
|
/// <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>
|
|
/// notify subscribers that the write was cancelled
|
|
/// </summary>
|
|
public event CancelEventHandler OnCancel;
|
|
|
|
/// <summary>
|
|
/// notify subscribers that the writer encountered fatal error
|
|
/// </summary>
|
|
public event ErrorEventHandler OnError;
|
|
|
|
/// <summary>
|
|
/// The number of data samples that need to be written for a "tick" to be dispatched.
|
|
/// </summary>
|
|
private int DataSamplesPerTick => 1000;
|
|
|
|
/// <summary>
|
|
/// Return the number of data to be written per "tick".
|
|
/// </summary>
|
|
/// <param name="channel"></param>
|
|
/// <returns></returns>
|
|
private uint GetChannelTicks(Test.Module.Channel channel)
|
|
{
|
|
try
|
|
{ //
|
|
// Most of our wait time will be spent writing data, so we need to give
|
|
// the process a little finer granularity.
|
|
//
|
|
return (uint)(channel.PersistentChannelInfo.Length / DataSamplesPerTick);
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem determining number of status ticks for channel " + (null != channel ? "\"" + channel.Number.ToString() + "\"" : "<NULL>"), ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/set the filtered channel data. If this list is supplied, the corresponding test
|
|
/// channel data values will be supplied from this list.
|
|
/// </summary>
|
|
public List<FilteredData> FilteredChannelData
|
|
{
|
|
get => _FilteredChannelData.Value;
|
|
set => _FilteredChannelData.Value = value;
|
|
}
|
|
private readonly Property<List<FilteredData>> _FilteredChannelData
|
|
= new Property<List<FilteredData>>(
|
|
"FilteredChannelData",
|
|
new List<FilteredData>(),
|
|
true
|
|
);
|
|
|
|
public string DataChannelFilename { get; set; }
|
|
public string LaboratoryName { get; set; }
|
|
public string LaboratoryContactName { get; set; }
|
|
public string LaboratoryContactPhone { get; set; }
|
|
public string LaboratoryContactEmail { get; set; }
|
|
public string TestEngineerName { get; set; }
|
|
public string TestEngineerPhone { get; set; }
|
|
public string TestEngineerEmail { get; set; }
|
|
public int NumChannelsWritten { get; set; }
|
|
public bool UseISOCodeFilterMapping { get; set; }
|
|
public bool UseZeroForUnfiltered { get; set; }
|
|
/// <summary>
|
|
/// Write the specified test to the specified pathname.
|
|
/// </summary>
|
|
///
|
|
/// <param name="pathname">
|
|
/// The <see cref="string"/> pathname to which the specified test should be serialized.
|
|
/// </param>
|
|
///
|
|
/// <param name="test">
|
|
/// The <see cref="DTS.Serialization.Test"/> to be written out.
|
|
/// </param>
|
|
///
|
|
public void Write(string pathname, string id, Test test, bool bFiltering, bool includeGroupNameInISOExport, double minStartTime, int dataCollectionLength)
|
|
{
|
|
try
|
|
{
|
|
Write(pathname, id, null, test, bFiltering, includeGroupNameInISOExport, null, null, 0, null, null, null, null, null, null, minStartTime, dataCollectionLength);
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem non-event notified writing test", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write the representation file/files of the specified DTS.Serialization.Test
|
|
/// at the given pathname.
|
|
/// </summary>
|
|
///
|
|
/// <param name="targetPathname">
|
|
/// The <see cref="string"/> pathname of the specified object's resulting file
|
|
/// representation.
|
|
/// </param>
|
|
///
|
|
public void Write(string pathname,
|
|
string id,
|
|
string dataFolder,
|
|
Test test,
|
|
bool bFiltering,
|
|
bool includeGroupNameInISOExport,
|
|
FilteredData fd,
|
|
Test.Module.Channel tmChannel,
|
|
int channelNumber,
|
|
BeginEventHandler beginEventHandler,
|
|
CancelEventHandler cancelEventHandler,
|
|
EndEventHandler endEventHandler,
|
|
TickEventHandler tickEventHandler,
|
|
ErrorEventHandler errorEventHandler,
|
|
CancelRequested cancelRequested,
|
|
double minStartTime,
|
|
int dataCollectionLength)
|
|
{
|
|
System.Exception exception = null;
|
|
try
|
|
{
|
|
//this.OnBegin += beginEventHandler; //Since we are called once for every file, we don't want to continually set progress to 0%
|
|
//this.OnEnd += endEventHandler; //Nor do we want to increment the number of exports that have finished
|
|
OnTick += tickEventHandler;
|
|
OnCancel += cancelEventHandler;
|
|
OnError += errorEventHandler;
|
|
|
|
// Compute the total number of write ticks that will be dispatched during this
|
|
// write, and let the caller know that we're underway.
|
|
uint channelTicks = 0;
|
|
uint totalWriteTicksNeeded = 0;
|
|
if (test.Channels.Count > 0)
|
|
{
|
|
channelTicks = GetChannelTicks(test.Channels[0]);
|
|
totalWriteTicksNeeded = channelTicks * (uint)test.Channels.Count;
|
|
}
|
|
var totalWriteTicksDispatched = channelTicks * (uint)NumChannelsWritten;
|
|
|
|
APILogger.Log("opening ", pathname);
|
|
//DateTime start = DateTime.Now;
|
|
if (!Directory.Exists(Path.GetDirectoryName(pathname)))
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(pathname));
|
|
}
|
|
|
|
if (System.IO.File.Exists(pathname))
|
|
{
|
|
FileUtils.DeleteFileOrMove(pathname, APILogger.Log);
|
|
}
|
|
|
|
using (var fileWriter = new StreamWriter(pathname, false, Encoding.Default))
|
|
{
|
|
//List<Test.Module.Channel> exportChannels = test.Channels;
|
|
|
|
switch (CurrentExportMode)
|
|
{
|
|
case ExportMode.FtssExcel:
|
|
WriteChannelInfo(fileWriter,
|
|
id,
|
|
test,
|
|
FilteredChannelData,
|
|
pathname,
|
|
tickEventHandler,
|
|
totalWriteTicksNeeded,
|
|
ref totalWriteTicksDispatched,
|
|
cancelRequested,
|
|
channelNumber,
|
|
minStartTime,
|
|
dataCollectionLength);
|
|
break;
|
|
|
|
default:
|
|
throw new NotSupportedException("FtssTsv::File::Writer ExportMode not supported: " + CurrentExportMode.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
APILogger.Log("encountered problem writing TSV test files", ex);
|
|
exception = new Exception("encountered problem writing TSV test files", ex);
|
|
}
|
|
|
|
finally
|
|
{
|
|
if (null != cancelRequested && cancelRequested())
|
|
{
|
|
OnCancel?.Invoke(this);
|
|
}
|
|
if (null != exception && null != errorEventHandler)
|
|
{
|
|
errorEventHandler(this, exception);
|
|
}
|
|
else if (null != exception)
|
|
{
|
|
throw exception;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Initialize(string pathname,
|
|
string id,
|
|
string dataFolder,
|
|
Test test,
|
|
bool bFiltering,
|
|
bool includeGroupNameInISOExport,
|
|
FilteredData fd,
|
|
Test.Module.Channel tmChannel,
|
|
int channelNumber,
|
|
BeginEventHandler beginEventHandler,
|
|
CancelEventHandler cancelEventHandler,
|
|
EndEventHandler endEventHandler,
|
|
TickEventHandler tickEventHandler,
|
|
ErrorEventHandler errorEventHandler,
|
|
CancelRequested cancelRequested)
|
|
{
|
|
}
|
|
/// <summary>
|
|
/// The various header lines (and associated text) that appear in this file format.
|
|
/// </summary>
|
|
private enum FtssHeaderLine
|
|
{
|
|
[Description("Headers")]
|
|
Headers = 0,
|
|
|
|
[Description("Test Date")]
|
|
TestDate,
|
|
|
|
[Description("Test Time")]
|
|
TestTime,
|
|
|
|
[Description("Test ID")]
|
|
TestId,
|
|
|
|
[Description("Test Description")]
|
|
TestDescription,
|
|
|
|
[Description("Laboratory Name")]
|
|
LaboratoryName,
|
|
|
|
[Description("Laboratory Contact Name")]
|
|
LaboratoryContactName,
|
|
|
|
[Description("Laboratory Contact Phone")]
|
|
LaboratoryContactPhone,
|
|
|
|
[Description("Laboratory Contact Email")]
|
|
LaboratoryContactEmail,
|
|
|
|
[Description("Test Engineer Name")]
|
|
TestEngineerName,
|
|
|
|
[Description("Test Engineer Phone")]
|
|
TestEngineerPhone,
|
|
|
|
[Description("Test Engineer Email")]
|
|
TestEngineerEmail,
|
|
|
|
[Description("Sample Rate (Hz)")]
|
|
SampleRate,
|
|
|
|
[Description("Hardware AA Filter (-3dB)")]
|
|
HardwareAntiAliasFilter,
|
|
|
|
[Description("Data Channel Name")]
|
|
DataChannelName,
|
|
|
|
[Description("ISO Channel Code")]
|
|
IsoCode,
|
|
|
|
[Description("Channel Description")]
|
|
ChannelDescription,
|
|
|
|
[Description("Channel Location")]
|
|
ChannelLocation,
|
|
|
|
[Description("Sensor S/N")]
|
|
SensorSerialNumber,
|
|
|
|
[Description("Software Filter (SAE Class)")]
|
|
SoftwareFilter,
|
|
|
|
[Description("Software Filter (-3dB)")]
|
|
SoftwareFilterDb,
|
|
|
|
[Description("Engineering Unit")]
|
|
EngineeringUnits,
|
|
|
|
[Description("User Comment")]
|
|
UserComment,
|
|
|
|
[Description("Number of Pre-Zero Data Pts")]
|
|
PreZero,
|
|
|
|
[Description("Number of Post-Zero Data Pts")]
|
|
PostZero,
|
|
|
|
[Description("Data Zero (CNTS)")]
|
|
DataZero,
|
|
|
|
[Description("Scale Factor (EU/CNT)")]
|
|
ScaleEu,
|
|
|
|
[Description("Scale Factor (mV/CNT)")]
|
|
ScaleMv,
|
|
|
|
[Description("Data Starts Here")]
|
|
DataStart,
|
|
|
|
[Description("Time")]
|
|
Labels,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a best-estimate export size for the specified dataset, and check it against the
|
|
/// current disk availability stats.
|
|
/// </summary>
|
|
///
|
|
/// <param name="testname">
|
|
/// The <see cref="string"/> name of the test to be verified.
|
|
/// </param>
|
|
///
|
|
/// <param name="saveLocation">
|
|
/// The <see cref="string"/> name of the target location into which the test must fit.
|
|
/// </param>
|
|
///
|
|
/// <param name="headerLines">
|
|
/// An <see cref="System.Collections.Generic.IDictionary<TKey,TValue>"/> containing pairs
|
|
/// of header entry name <see cref="string"/>s and corresponding value <see cref="string"/>s.
|
|
/// </param>
|
|
///
|
|
/// <param name="coder">
|
|
/// A <see cref="DescriptionAttributeCoder<TargetType>"/> for en/coding
|
|
/// <see cref="DTS.Serialization.FtssTsv.File.Writer.FtssHeaderLine"/> header enumerations.
|
|
/// </param>
|
|
///
|
|
/// <param name="channelsWithMeta">
|
|
/// A populated <see cref="System.Collections.Generic.List<T>"/> of
|
|
/// <see cref="DTS.Serialization.FtssTsv.File.Writer.ChannelWithMeta"/>s generated from
|
|
/// the test to be exported.
|
|
/// </param>
|
|
///
|
|
/// <param name="dataCollectionLength">
|
|
/// The <see cref="int"/> length of the data collection within this test that is to be
|
|
/// exported.
|
|
/// </param>
|
|
///
|
|
/// <param name="minStartTime">
|
|
/// The earliest <see cref="double"/> T0-relative start time on any channel within the
|
|
/// test to be exported.
|
|
/// </param>
|
|
///
|
|
/// <param name="sampleRate">
|
|
/// The <see cref="double"/> sample rate of the test to be exported.
|
|
/// </param>
|
|
///
|
|
private void VerifyExportedFileWillFitOnDisk(
|
|
string testname,
|
|
string saveLocation,
|
|
IDictionary<FtssHeaderLine, List<string>> headerLines,
|
|
DescriptionAttributeCoder<FtssHeaderLine> coder,
|
|
List<ChannelWithMeta> channelsWithMeta,
|
|
int dataCollectionLength,
|
|
double minStartTime,
|
|
double sampleRate,
|
|
CancelRequested cancelRequested
|
|
)
|
|
{
|
|
try
|
|
{ //
|
|
// Compute the size of the header information.
|
|
//
|
|
ulong predictedExportSize = 0;
|
|
foreach (FtssHeaderLine ftssSizeHeaderLine in Enum.GetValues(typeof(FtssHeaderLine)))
|
|
{
|
|
if (headerLines.Keys.Contains(ftssSizeHeaderLine))
|
|
{
|
|
var channelValues = headerLines[ftssSizeHeaderLine];
|
|
predictedExportSize += (ulong)(coder.DecodeAttributeValue(ftssSizeHeaderLine).Length + TAB_LIST_SEPARATOR.Length);
|
|
foreach (var channelValue in channelValues)
|
|
predictedExportSize += (ulong)(channelValue.Length + TAB_LIST_SEPARATOR.Length);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Compute the size of the data portion.
|
|
// this computation is expensive for large data collections, so lets just subsample them for now
|
|
// for every 2 million samples collected we'll increase the size of subsamples by 1.
|
|
var segmentSize = dataCollectionLength / 2000000;
|
|
if (segmentSize < 1) { segmentSize = 1; }
|
|
|
|
for (var i = 0; i < dataCollectionLength; i += segmentSize)
|
|
{
|
|
if (null != cancelRequested && cancelRequested()) { break; }
|
|
var thisSegmentSize = (ulong)((minStartTime + i * 1.0 / sampleRate).ToString(NUMBER_FORMAT) + TAB_LIST_SEPARATOR).Length;
|
|
foreach (var channelWithMeta in channelsWithMeta)
|
|
{
|
|
if (null != cancelRequested && cancelRequested()) { break; }
|
|
var thisChannelsIndexAtCurrentTime = i - (int)((channelWithMeta.StartTime - minStartTime) * channelWithMeta.SampleRate);
|
|
|
|
thisSegmentSize += thisChannelsIndexAtCurrentTime >= 0 && thisChannelsIndexAtCurrentTime < channelWithMeta.Channel.PersistentChannelInfo.Length ?
|
|
9U : 1U;
|
|
}
|
|
var segmentJump = i + segmentSize > dataCollectionLength ? dataCollectionLength - i : segmentSize;
|
|
predictedExportSize += thisSegmentSize * (ulong)segmentJump;
|
|
}
|
|
|
|
predictedExportSize *= sizeof(char);
|
|
|
|
// Get the stats on available disk space.
|
|
var errorcode = DiskUtility.GetDiskFreeSpaceEx(saveLocation, out ulong freeBytesAvailable, out ulong totalNumberOfBytes, out ulong totalNumberOfFreeBytes);
|
|
if (0 != errorcode)
|
|
{
|
|
|
|
// Do the comparison.
|
|
if (freeBytesAvailable < predictedExportSize)
|
|
{
|
|
var bytesNeeded = DiskUtility.GetHumanReadableBytes(predictedExportSize);
|
|
var bytesAvailable = DiskUtility.GetHumanReadableBytes(freeBytesAvailable);
|
|
throw new UserException("Export requires " + bytesNeeded + " but there are only " + bytesAvailable + " bytes available on \"" + saveLocation + "\"");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
APILogger.Log("Failed to get free disk space, windows error: ", errorcode);
|
|
}
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem trying to determing if CSV export of test " + (testname ?? "<NULL>") + " will fit at location " + (saveLocation ?? "<NULL>"), ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write the specified test to the specified stream.
|
|
/// </summary>
|
|
///
|
|
/// <param name="fileWriter">
|
|
/// The <see cref="System.IO.StreamWriter"/> to which the specified test should be serialized.
|
|
/// </param>
|
|
///
|
|
/// <param name="test">
|
|
/// The <see cref="DTS.Serialization.Test"/> to be serialized.
|
|
/// </param>
|
|
///
|
|
/// <param name="writeEvent">
|
|
/// The <see cref="FtssTsv.File.Writer.EventHandler"/> that should handle write-progress
|
|
/// reporting; null if none is to be used.
|
|
/// <param name="targetPath">
|
|
/// The <see cref="string"/> path that the file writer will be writing to.
|
|
/// </param>
|
|
///
|
|
/// <param name="tickEventHandler">
|
|
/// The <see cref="DTS.Serialization.TickEventHandler"/> that will process "tick" events
|
|
/// indicating progress during the export procedure.
|
|
/// </param>
|
|
///
|
|
/// <param name="totalWriteTicksNeeded">
|
|
/// The total <see cref="uint"/> number of progress ticks that will be issued during the
|
|
/// export procedure.
|
|
/// </param>
|
|
///
|
|
/// <param name="totalWriteTicksDispatched">
|
|
/// Returns the total <see cref="uint"/> number of tick events that have been dispatched.
|
|
/// </param>
|
|
///
|
|
protected void WriteChannelInfo
|
|
(
|
|
StreamWriter fileWriter,
|
|
string id,
|
|
Test test,
|
|
List<FilteredData> filteredData,
|
|
string targetPath,
|
|
TickEventHandler tickEventHandler,
|
|
uint totalWriteTicksNeeded,
|
|
ref uint totalWriteTicksDispatched,
|
|
CancelRequested cancelRequested,
|
|
int channelNumber,
|
|
double minStartTime,
|
|
int dataCollectionLength)
|
|
{
|
|
try
|
|
{
|
|
//FB14621: Get the sample rate of the channels we're going to care about (i.e. channels with meta), rather than the whole test/roi
|
|
var sampleRate = test.Channels.Find(ch => DataChannelFilename == Path.GetFileName(((Test.Module.AnalogInputChannel)ch).FileName)).ParentModule.SampleRateHz;
|
|
|
|
if (1 < SubSampleInterval)
|
|
{
|
|
var ChannelList = test.Channels;
|
|
|
|
for (var iChannel = 0; iChannel < ChannelList.Count; iChannel++)
|
|
{
|
|
if (true == Filtered)
|
|
{
|
|
var SubSample = new NHTSASubSample<double>();
|
|
SubSample.data = FilteredChannelData[iChannel].Data;
|
|
SubSample.preTriggerSamples = test.Modules[0].TriggerSampleNumbers[0] - test.Modules[0].StartRecordSampleNumber;
|
|
SubSample.sampleRate = sampleRate;
|
|
SubSample.subSampleInterval = SubSampleInterval;
|
|
SubSample.SubSample();
|
|
FilteredChannelData[iChannel].Data = SubSample.data;
|
|
ChannelList[iChannel].IsSubsampled = true; // I don't think anyone will consume this, but for completeness.
|
|
}
|
|
else
|
|
{
|
|
var SubSample = new NHTSASubSample<short>();
|
|
SubSample.data = ChannelList[iChannel].PersistentChannelInfo.Data;
|
|
SubSample.preTriggerSamples = test.Modules[0].TriggerSampleNumbers[0] - test.Modules[0].StartRecordSampleNumber;
|
|
SubSample.sampleRate = sampleRate;
|
|
SubSample.subSampleInterval = SubSampleInterval;
|
|
SubSample.SubSample();
|
|
ChannelList[iChannel].PersistentChannelInfo.Data = SubSample.data;
|
|
ChannelList[iChannel].IsSubsampled = true; // I don't think anyone will consume this, but for completeness.
|
|
}
|
|
}
|
|
sampleRate = sampleRate / SubSampleInterval;
|
|
}
|
|
|
|
|
|
//
|
|
// Initialize the header information.
|
|
//
|
|
var coder = new DescriptionAttributeCoder<FtssHeaderLine>();
|
|
IDictionary<FtssHeaderLine, List<string>> headerLines = new Dictionary<FtssHeaderLine, List<string>>();
|
|
|
|
headerLines.Add(FtssHeaderLine.Headers, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.TestDate, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.TestTime, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.TestId, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.TestDescription, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.LaboratoryName, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.LaboratoryContactName, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.LaboratoryContactPhone, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.LaboratoryContactEmail, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.TestEngineerName, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.TestEngineerPhone, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.TestEngineerEmail, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.SampleRate, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.HardwareAntiAliasFilter, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.DataChannelName, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.IsoCode, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.ChannelDescription, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.ChannelLocation, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.SensorSerialNumber, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.SoftwareFilter, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.SoftwareFilterDb, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.EngineeringUnits, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.UserComment, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.PreZero, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.PostZero, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.DataZero, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.ScaleEu, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.ScaleMv, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.DataStart, new List<string>());
|
|
headerLines.Add(FtssHeaderLine.Labels, new List<string>());
|
|
|
|
var channelsWithMeta = new List<ChannelWithMeta>();
|
|
var allChannelsWithMeta = new List<ChannelWithMeta>();
|
|
var filteredDataIndex = 0;
|
|
|
|
foreach (var channel in test.Channels)
|
|
{
|
|
if (Path.GetFileName((channel as Test.Module.AnalogInputChannel).FileName) == DataChannelFilename)
|
|
{
|
|
//
|
|
// Tack on the header information for each channel.
|
|
//
|
|
var filteredChannelData = null != filteredData && filteredData.Count == 1 ? filteredData[0] : null;
|
|
|
|
headerLines[FtssHeaderLine.Headers].Add(TAB_LIST_SEPARATOR);
|
|
headerLines[FtssHeaderLine.TestDate].Add(test.InceptionDate.ToShortDateString());
|
|
headerLines[FtssHeaderLine.TestTime].Add(test.InceptionDate.ToLongTimeString());
|
|
headerLines[FtssHeaderLine.TestId].Add(id);
|
|
headerLines[FtssHeaderLine.TestDescription].Add(test.Description);
|
|
headerLines[FtssHeaderLine.LaboratoryName].Add(LaboratoryName);
|
|
headerLines[FtssHeaderLine.LaboratoryContactName].Add(LaboratoryContactName);
|
|
headerLines[FtssHeaderLine.LaboratoryContactPhone].Add(LaboratoryContactPhone);
|
|
headerLines[FtssHeaderLine.LaboratoryContactEmail].Add(LaboratoryContactEmail);
|
|
headerLines[FtssHeaderLine.TestEngineerName].Add(TestEngineerName);
|
|
headerLines[FtssHeaderLine.TestEngineerPhone].Add(TestEngineerPhone);
|
|
headerLines[FtssHeaderLine.TestEngineerEmail].Add(TestEngineerEmail);
|
|
headerLines[FtssHeaderLine.SampleRate].Add(channel.ParentModule.SampleRateHz.ToString("F0"));
|
|
headerLines[FtssHeaderLine.HardwareAntiAliasFilter].Add(channel.ParentModule.AaFilterRateHz.ToString("F0"));
|
|
headerLines[FtssHeaderLine.DataChannelName].Add(DataChannelFilename);
|
|
|
|
//13157 Software filter class (SAE Class) incorrect in TSV filtered export.
|
|
//if we are filtering AND we have use isocode filter mapping on, then adjust the isocode to match
|
|
var isocode = channel is IIsoCodeAware ? (channel as IIsoCodeAware).IsoCode : "";
|
|
var swFilter = Filtered && filteredChannelData != null ? filteredChannelData.FilterDescription : "NONE";
|
|
if (UseISOCodeFilterMapping)
|
|
{
|
|
var iso = new Common.ISO.IsoCode(isocode);
|
|
iso.FilterClass = CFCFilterDTSFileStringConverter.GetIsoCodeFromString(swFilter, UseZeroForUnfiltered);
|
|
isocode = iso.StringRepresentation;
|
|
}
|
|
headerLines[FtssHeaderLine.IsoCode].Add(isocode);
|
|
headerLines[FtssHeaderLine.ChannelDescription].Add(channel.ChannelDescriptionString);
|
|
headerLines[FtssHeaderLine.ChannelLocation].Add(channel.ChannelName2);
|
|
headerLines[FtssHeaderLine.SensorSerialNumber].Add(channel is ISerialNumberAware ? (channel as ISerialNumberAware).SerialNumber : "");
|
|
headerLines[FtssHeaderLine.SoftwareFilter].Add(swFilter);
|
|
headerLines[FtssHeaderLine.SoftwareFilterDb].Add(Filtered && filteredChannelData != null ? filteredChannelData.FilterFrequencyHz.ToString("F0") : "NONE");
|
|
headerLines[FtssHeaderLine.EngineeringUnits].Add(channel is IEngineeringUnitAware ? (channel as IEngineeringUnitAware).EngineeringUnits.TrimEnd() : "");
|
|
headerLines[FtssHeaderLine.UserComment].Add("");
|
|
|
|
var preTriggerSamples = channel.ParentModule.TriggerSampleNumbers[0] - (double)channel.ParentModule.StartRecordSampleNumber;
|
|
if (preTriggerSamples < 0) { preTriggerSamples = 0D; }
|
|
if (preTriggerSamples > channel.ParentModule.NumberOfSamples) { preTriggerSamples = channel.ParentModule.NumberOfSamples; }
|
|
if (preTriggerSamples > Start * sampleRate) { preTriggerSamples = Math.Truncate(Start * sampleRate); }
|
|
headerLines[FtssHeaderLine.PreZero].Add((preTriggerSamples / SubSampleInterval).ToString());
|
|
|
|
var postTriggerSamples = channel.ParentModule.NumberOfSamples - preTriggerSamples;
|
|
if (postTriggerSamples < 0) { postTriggerSamples = 0D; }
|
|
if (postTriggerSamples > Stop * sampleRate) { postTriggerSamples = Math.Truncate(Stop * sampleRate); }
|
|
headerLines[FtssHeaderLine.PostZero].Add((postTriggerSamples / SubSampleInterval).ToString());
|
|
|
|
headerLines[FtssHeaderLine.DataZero].Add(channel.DataZeroLevelAdc.ToString());
|
|
|
|
var scaler = new DataScaler();
|
|
scaler.IsInverted = channel is IInversionAware ? (channel as IInversionAware).IsInverted : false;
|
|
|
|
if ((channel as ILinearized).LinearizationFormula.IsValid())
|
|
{
|
|
scaler.SetLinearizationFormula((channel as ILinearized).LinearizationFormula);
|
|
}
|
|
else
|
|
{
|
|
scaler.SetLinearizationFormula(null);
|
|
}
|
|
|
|
scaler.Digital = (channel as Test.Module.AnalogInputChannel).IsDigital();
|
|
|
|
scaler.SetDigitalMultiplier((channel as Test.Module.AnalogInputChannel).DigitalMultiplier);
|
|
scaler.DigitalMode = (channel as Test.Module.AnalogInputChannel).DigitalMode;
|
|
|
|
scaler.SetScaleFactorMv(channel.Data.ScaleFactorMv);
|
|
scaler.SetScaleFactorEU(channel.Data.ScaleFactorEU);
|
|
scaler.SetUseEUScaleFactors(channel.Data.UseEUScaleFactors);
|
|
scaler.UnitConversion = (channel as Test.Module.AnalogInputChannel).UnitConversion;
|
|
|
|
scaler.BasedOnOutputAtCapacity = (channel as Test.Module.AnalogInputChannel).AtCapacity;
|
|
scaler.CapacityOutputIsBasedOn = (channel as Test.Module.AnalogInputChannel).CapacityOutputIsBasedOn;
|
|
|
|
scaler.SensitivityUnits = (channel as Test.Module.AnalogInputChannel).SensitivityUnits;
|
|
scaler.Multiplier = (channel as Test.Module.AnalogInputChannel).Multiplier;
|
|
|
|
if (channel is Test.Module.AnalogInputChannel)
|
|
{
|
|
scaler.IEPE = (channel as Test.Module.AnalogInputChannel).Bridge == SensorConstants.BridgeType.IEPE;
|
|
scaler.Digital = (channel as Test.Module.AnalogInputChannel).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);
|
|
try { scaler.SetWindowAverageADC(channel.WindowAverageADC); }
|
|
catch (System.Exception) { }
|
|
|
|
if (channel is Test.Module.AnalogInputChannel)
|
|
{ //
|
|
// Maybe these should be replaced by DTS.DAS.Concepts versions for casting? Would need to
|
|
// add excitation voltage as a concept, and proportional to excitation.
|
|
//
|
|
var analogChannel = channel as Test.Module.AnalogInputChannel;
|
|
scaler.SetInitialOffset(analogChannel.InitialOffset);
|
|
scaler.ZeroMethodType = analogChannel.ZeroMethod;
|
|
scaler.NominalExcitationVoltage = analogChannel.ExcitationVoltage;
|
|
if (analogChannel.MeasuredExcitationVoltageValid)
|
|
{
|
|
try { scaler.MeasuredExcitationVoltage = analogChannel.MeasuredExcitationVoltage; }
|
|
catch { };
|
|
}
|
|
if (analogChannel.FactoryExcitationVoltageValid)
|
|
{
|
|
try { scaler.FactoryExcitationVoltage = analogChannel.FactoryExcitationVoltage; }
|
|
catch { };
|
|
}
|
|
|
|
scaler.ProportionalToExcitation = analogChannel.ProportionalToExcitation;
|
|
}
|
|
|
|
var dStartTime = channel.ParentModule.StartRecordSampleNumber / (double)channel.ParentModule.SampleRateHz;
|
|
if (channel.ParentModule.TriggerSampleNumbers.Count > 0)
|
|
{
|
|
dStartTime -= channel.ParentModule.TriggerSampleNumbers[0] / (double)channel.ParentModule.SampleRateHz;
|
|
}
|
|
|
|
channelsWithMeta.Add(
|
|
new ChannelWithMeta(
|
|
channel,
|
|
scaler,
|
|
channel.ParentModule.SampleRateHz,
|
|
dStartTime
|
|
)
|
|
);
|
|
|
|
headerLines[FtssHeaderLine.ScaleEu].Add(scaler.GetAdcToEuScalingFactor().ToString());
|
|
headerLines[FtssHeaderLine.ScaleMv].Add(scaler.GetAdcToMvScalingFactor().ToString());
|
|
headerLines[FtssHeaderLine.DataStart].Add(TAB_LIST_SEPARATOR);
|
|
headerLines[FtssHeaderLine.Labels].Add(channel.ChannelDescriptionString);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//FB14621: Recalculate minStartTime and dataCollectionLength here after pulling out channelsWithMeta,
|
|
// rather than in Export which is doing the whole test/ROI
|
|
minStartTime = ChannelWithMeta.GetMinStartTime(Start, channelsWithMeta);
|
|
var minStopTime = ChannelWithMeta.GetMinStopTime(Stop, channelsWithMeta);
|
|
dataCollectionLength = (int)((minStopTime - minStartTime) * sampleRate);
|
|
|
|
VerifyExportedFileWillFitOnDisk(test.Id,
|
|
targetPath.Remove(targetPath.IndexOf(Path.VolumeSeparatorChar) + 1),
|
|
headerLines,
|
|
coder,
|
|
channelsWithMeta,
|
|
dataCollectionLength,
|
|
minStartTime,
|
|
sampleRate,
|
|
cancelRequested);
|
|
|
|
// Apply filtering to channel data at this point?
|
|
// Thinking maybe since all the cool filter utilities are up in the Slice Control layer, it
|
|
// may just be a better idea to create some filtered test at that level using the event channels
|
|
// and then just add some code here that gracefully handles Data-data instead of PersistentChannelInfo
|
|
// data...?
|
|
|
|
//Dictionary<ChannelWithMeta, double[ ]> filteredData = new Dictionary<ChannelWithMeta, double[ ]>( );
|
|
//SaeJ211.FilterUtility filterUtility = new SaeJ211.FilterUtility( );
|
|
//foreach ( ChannelWithMeta channelWithMeta in channelsWithMeta )
|
|
//{
|
|
// this.CurrentFilter = Slice.Control.Event.Module.Channel.DefaultSaeJ211Filter.Parse( thatAnalogChannel.SoftwareFilter );
|
|
// filterUtility.SampleRate = channelWithMeta.SampleRate;
|
|
// filterUtility.Cfc = ( channelWithMeta.Channel as Test.Module.AnalogInputChannel ).SoftwareFilter;
|
|
//}
|
|
|
|
foreach (FtssHeaderLine ftssHeaderLine in Enum.GetValues(typeof(FtssHeaderLine)))
|
|
{ //
|
|
// Write out the assembled header information to the specified filestream.
|
|
//
|
|
if (headerLines.Keys.Contains(ftssHeaderLine))
|
|
{
|
|
var channelValues = headerLines[ftssHeaderLine];
|
|
fileWriter.Write(coder.DecodeAttributeValue(ftssHeaderLine) + TAB_LIST_SEPARATOR);
|
|
foreach (var channelValue in channelValues)
|
|
fileWriter.Write(channelValue + TAB_LIST_SEPARATOR);
|
|
fileWriter.WriteLine();
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i <= dataCollectionLength; i++)
|
|
{
|
|
if (null != cancelRequested && cancelRequested()) { break; }
|
|
fileWriter.Write(string.Format("{0}{1}", (minStartTime + i * 1.0 / sampleRate).ToString(NUMBER_FORMAT), TAB_LIST_SEPARATOR));
|
|
var bNeedComma = false;
|
|
foreach (var channelWithMeta in channelsWithMeta)
|
|
{
|
|
if (bNeedComma) { fileWriter.Write(TAB_LIST_SEPARATOR); }
|
|
else { bNeedComma = true; }
|
|
//
|
|
// If this channel's data is valid at the current time/index then go ahead and send it out
|
|
// to the file; otherwise write out a blank space.
|
|
//
|
|
var thisChannelsIndexAtCurrentTime = i - (int)((channelWithMeta.StartTime - minStartTime) * channelWithMeta.SampleRate);
|
|
if (Filtered)
|
|
{
|
|
if (filteredDataIndex < filteredData.Count)
|
|
{
|
|
if (thisChannelsIndexAtCurrentTime >= 0 && thisChannelsIndexAtCurrentTime < filteredData[filteredDataIndex].Data.Length)
|
|
{
|
|
fileWriter.Write(filteredData[filteredDataIndex].Data[thisChannelsIndexAtCurrentTime].ToString(NUMBER_FORMAT));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fileWriter.Write(double.NaN.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
double dInitialEU = 0;
|
|
var aChannel = channelWithMeta.Channel as Test.Module.AnalogInputChannel;
|
|
if (null != aChannel) { dInitialEU = aChannel.InitialEu; }
|
|
|
|
if (thisChannelsIndexAtCurrentTime >= 0 && (ulong)thisChannelsIndexAtCurrentTime < aChannel.ParentModule.NumberOfSamples)
|
|
{
|
|
fileWriter.Write(channelWithMeta.Scaler.GetEU(channelWithMeta.Channel.PersistentChannelInfo[thisChannelsIndexAtCurrentTime]).ToString(NUMBER_FORMAT));
|
|
}
|
|
}
|
|
filteredDataIndex++;
|
|
}
|
|
|
|
if (0 == i % DataSamplesPerTick)
|
|
{
|
|
OnTick?.Invoke(this, (double)totalWriteTicksDispatched++ / totalWriteTicksNeeded * 100);
|
|
System.Windows.Forms.Application.DoEvents();
|
|
}
|
|
fileWriter.WriteLine();
|
|
filteredDataIndex = 0;
|
|
}
|
|
fileWriter.Flush();
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem writing FTSS/Excel channel headers", ex);
|
|
}
|
|
}
|
|
public double Start { get; set; } = 0D;
|
|
|
|
public double Stop { get; set; } = 0D;
|
|
|
|
public ushort SubSampleInterval { get; set; }
|
|
|
|
public bool Filtered { get; set; }
|
|
}
|
|
}
|
|
}
|