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

986 lines
53 KiB
C#

/*
* HDF.File.Writer.cs
*
* Copyright © 2017
* Diversified Technical Systems, Inc.
* All Rights Reserved
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using DTS.Common.Enums.Sensors;
using DTS.Common.Utilities.Logging;
using HDF.PInvoke;
namespace DTS.Serialization.HDF
{
public partial class File
{
/// <summary>
/// implementation of the Serialization.File.Writer class for HDF5
/// https://en.wikipedia.org/wiki/Hierarchical_Data_Format
/// http://fogbugz/fogbugz/default.asp?9166
/// </summary>
public class Writer : Writer<File>, IWriter<Test>
{
#region properties
/// <summary>
/// the owning file that controls this writer
/// </summary>
internal File WriterParent { get; private set; }
/// <summary>
/// controls whether to export ADC or not
/// </summary>
public bool ExportADC { get; set; }
/// <summary>
/// controls whether to export EU or not
/// </summary>
public bool ExportEU { get; set; }
/// <summary>
/// controls whether to export MV or not
/// </summary>
public bool ExportMV { get; set; }
/// <summary>
/// controls whether to export logs or not
/// </summary>
public bool ExportLogs { get; set; }
/// <summary>
/// controls whether to export reports or not
/// </summary>
public bool ExportReports { get; set; }
/// <summary>
/// controls whether to export setups or not
/// </summary>
public bool ExportSetup { get; set; }
public bool ExportDTSFile { get; set; }
public string CustomerName { get; set; }
public string TestEngineerName { get; set; }
public string LabName { get; set; }
public bool IsWiamanData { get; set; }
public Dictionary<string, string> ISOToFineLocation3 { get; set; }
public Dictionary<string, string> ISOToPhysicalDimension { get; set; }
public Dictionary<string, string> ISOToPosition { get; set; }
public Dictionary<string, string> ISOToTransducerMainLocation { get; set; }
#endregion
#region enums and constants
/// <summary>
/// common locations where we can fail an HDF interface call
/// </summary>
public enum ErrorLocation
{
File,
Group,
SubGroup,
DataSpace,
DataSet,
Write
}
/// <summary>
/// every 1000 samples update progress
/// </summary>
private const int UPDATE_INTERVAL = 1000;
#endregion
#region methods
/// <summary>
/// writes out test to given path
/// </summary>
/// <param name="pathname"></param>
/// <param name="id"></param>
/// <param name="test"></param>
/// <param name="bFiltering"></param>
/// <param name="includeGroupNameInISOExport"></param>
public void Write(string pathname, string id, Test test, bool bFiltering, bool includeGroupNameInISOExport, double minStartTime, int dataCollectionLength)
{
}
/// <summary>
/// returns a datascaler for the given channel
/// </summary>
/// <param name="currentAnalogChannel"></param>
/// <returns></returns>
private Common.DAS.Concepts.DataScaler GetDataScaler(Test.Module.AnalogInputChannel currentAnalogChannel)
{
var scaler = new Common.DAS.Concepts.DataScaler
{
IsInverted = currentAnalogChannel.IsInverted,
IEPE = currentAnalogChannel.Bridge == SensorConstants.BridgeType.IEPE,
UnitConversion = currentAnalogChannel.UnitConversion,
BasedOnOutputAtCapacity = currentAnalogChannel.AtCapacity,
CapacityOutputIsBasedOn = currentAnalogChannel.CapacityOutputIsBasedOn,
SensitivityUnits = currentAnalogChannel.SensitivityUnits,
Multiplier = currentAnalogChannel.Multiplier,
UserOffsetEU = currentAnalogChannel.UserOffsetEU
};
scaler.SetLinearizationFormula(currentAnalogChannel.LinearizationFormula);
scaler.SetScaleFactorMv(currentAnalogChannel.Data.ScaleFactorMv);
scaler.SetScaleFactorEU(currentAnalogChannel.Data.ScaleFactorEU);
scaler.SetUseEUScaleFactors(currentAnalogChannel.Data.UseEUScaleFactors);
scaler.SetMvPerEu(currentAnalogChannel.Data.MvPerEu);
try
{
scaler.SetInitialOffset(currentAnalogChannel.InitialOffset);
scaler.ZeroMethodType = currentAnalogChannel.ZeroMethod;
scaler.SetRemovedADC(currentAnalogChannel.RemovedADC);
scaler.SetRemovedInternalADC(currentAnalogChannel.RemovedInternalADC);
scaler.SetDataZeroLevelADC(currentAnalogChannel.DataZeroLevelAdc);
scaler.SetZeroMvInADC(currentAnalogChannel.ZeroMvInADC);
try
{
//note the window average is the average over time when the average is not in your dataset
//sliceware is the only software that sets this currently
scaler.SetWindowAverageADC(currentAnalogChannel.WindowAverageADC);
}
catch (System.Exception ex)
{
APILogger.Log("WindowAverageADC failed to set", ex);
}
}
catch (System.Exception ex)
{
APILogger.Log("Failed to set parameters on scaler", ex);
}
scaler.NominalExcitationVoltage = currentAnalogChannel.ExcitationVoltage;
if (currentAnalogChannel.MeasuredExcitationVoltageValid)
{
try
{
scaler.MeasuredExcitationVoltage = currentAnalogChannel.MeasuredExcitationVoltage;
}
catch (System.Exception ex)
{
APILogger.Log("failed to get measured excitation voltage", ex);
}
}
if (currentAnalogChannel.FactoryExcitationVoltageValid)
{
try
{
scaler.FactoryExcitationVoltage = currentAnalogChannel.FactoryExcitationVoltage;
}
catch (System.Exception ex)
{
APILogger.Log("failed to get factory excitation", ex);
}
}
scaler.ProportionalToExcitation = currentAnalogChannel.ProportionalToExcitation;
return scaler;
}
/// <summary>
/// checks the API return status and throws an exception if the status is not successful
/// </summary>
/// <param name="status"></param>
/// <param name="location"></param>
// ReSharper disable once UnusedParameter.Local
private void CheckStatus(long status, ErrorLocation location)
{
if (status < 0)
{
throw new System.Exception(location.ToString());
}
}
private const bool ATTRIBUTE_STRING_DATATYPE_ONLY = true;
private object H5WriteLock = new object();
/// <summary>
/// creates a string attribute attached to the given location
/// returns false if it failed
/// </summary>
/// <param name="objectId">object to attach attributes to</param>
/// <param name="attributeName">attribute name</param>
/// <param name="attributeValue">attribute value</param>
/// <returns>success or failure</returns>
private void CreateStringAttribute(long objectId, string attributeName, string attributeValue)
{
if (string.IsNullOrWhiteSpace(attributeName))
{
return;
}
if (string.IsNullOrWhiteSpace(attributeValue))
{
attributeValue = string.Empty;
}
long attributeSpace = 0;
long stringId = 0;
long attributeId = 0;
try
{
long result;
IntPtr stringToHGlobalAnsi = new IntPtr();
lock (H5WriteLock)
{
attributeSpace = H5S.create(H5S.class_t.SCALAR);
stringId = H5T.copy(H5T.C_S1);
H5T.set_size(stringId, new IntPtr(attributeValue.Length));
attributeId = H5A.create(objectId, attributeName, stringId, attributeSpace);
stringToHGlobalAnsi = Marshal.StringToHGlobalAnsi(attributeValue);
result = H5A.write(attributeId, stringId, stringToHGlobalAnsi);
}
Marshal.FreeHGlobal(stringToHGlobalAnsi);
CheckStatus(result, ErrorLocation.Write);
}
catch (Exception ex)
{
APILogger.Log("failed to write string attribute:", attributeName, attributeValue, ex);
}
finally
{
if (attributeId != 0) H5A.close(attributeId);
if (stringId != 0) H5T.close(stringId);
if (attributeSpace != 0) H5S.close(attributeSpace);
}
}
/// <summary>
/// creates an integer attribute attached to the given location
/// returns false if failed
/// </summary>
/// <param name="objectId"></param>
/// <param name="title"></param>
/// <param name="value"></param>
/// <returns></returns>
private void CreateIntAttribute(long objectId, string title, int value)
{
if (ATTRIBUTE_STRING_DATATYPE_ONLY)
{
CreateStringAttribute(objectId, title, value.ToString());
return;
}
}
/// <summary>
/// Creates a ulong attribute attached to the given location
/// returns false if fails
/// </summary>
/// <param name="objectId"></param>
/// <param name="title"></param>
/// <param name="value"></param>
/// <returns></returns>
private void CreateUlongAttribute(long objectId, string title, ulong value)
{
if (ATTRIBUTE_STRING_DATATYPE_ONLY)
{
CreateStringAttribute(objectId, title, value.ToString());
return;
}
}
/// <summary>
/// creates a double attribute attached to the given location
/// returns false if fails
/// </summary>
/// <param name="objectId"></param>
/// <param name="title"></param>
/// <param name="value"></param>
/// <returns></returns>
private void CreateDoubleAttribute(long objectId, string title, double value)
{
if (ATTRIBUTE_STRING_DATATYPE_ONLY)
{
CreateStringAttribute(objectId, title, value.ToString());
return;
}
}
/// <summary>
/// includes all files in the directory (if it exists) in the HDF
/// </summary>
/// <param name="binaryPath">source path for files</param>
/// <param name="folder">name of the folder</param>
/// <param name="hdfObjectId">HDF object to attach folder to</param>
/// <returns></returns>
private void AddDirectoryIfExists(string binaryPath, string folder, long hdfObjectId, string fileExtension)
{
var directoryInfo = new DirectoryInfo(binaryPath);
if (directoryInfo.Parent != null)
{
if (directoryInfo.Parent.Parent != null)
{
if (directoryInfo.Parent.Parent.Parent != null)
{
var rootDirectory = directoryInfo.Parent.Parent.Parent.FullName;
var sourceDirectory = Path.Combine(rootDirectory, folder);
if (!Directory.Exists(sourceDirectory))
{
return;
}
var files = Directory.GetFiles(sourceDirectory, fileExtension);
if (!files.Any())
{
return;
}
//create group
var key = $"/Files/{folder}";
var subgroup = H5G.create(hdfObjectId, key);
foreach (var sFile in files)
{
var fi = new FileInfo(sFile);
var bytes = System.IO.File.ReadAllBytes(sFile);
var dataspace = H5S.create_simple(1, new[] { Convert.ToUInt64(bytes.Length) }, null);
var dataset = H5D.create(subgroup, $"{key}/{fi.Name}",
H5T.NATIVE_UCHAR, dataspace);
var intPtr = Marshal.AllocHGlobal(bytes.Length);
for (var i = 0; i < bytes.Length; i++)
{
Marshal.WriteByte(intPtr, i, bytes[i]);
}
var mid1 = H5S.create_simple(1, new[] { Convert.ToUInt64(bytes.Length) }, null);
H5D.write(dataset, H5T.NATIVE_UCHAR, mid1, dataspace,
H5P.DEFAULT, intPtr);
Marshal.FreeHGlobal(intPtr);
H5D.close(dataset);
H5S.close(mid1);
H5S.close(dataspace);
}
H5G.close(subgroup);
}
}
}
}
private object _updateProgressLock = new object();
private double _curProg = 0;
/// <summary>
/// updates the progress if possible
/// </summary>
/// <param name="dValue"></param>
/// <param name="tickEventHandler"></param>
private void UpdateProgress(double dValue, TickEventHandler tickEventHandler)
{
if (dValue <= _curProg) { return; }
else
{
_curProg = dValue;
}
lock (_updateProgressLock)
{
tickEventHandler?.Invoke(this, _curProg);
}
}
/// <summary>
/// returns the total number of steps in the export
/// </summary>
/// <param name="test"></param>
/// <returns></returns>
private int ComputeNumberOfSteps(Test test)
{
var totalSteps = 0;
if (ExportEU)
{
totalSteps += test.Channels.Count;
}
if (ExportADC)
{
totalSteps += test.Channels.Count;
}
if (ExportMV)
{
totalSteps += test.Channels.Count;
}
if (ExportLogs)
{
totalSteps++;
}
if (ExportSetup)
{
totalSteps++;
}
if (ExportReports)
{
totalSteps++;
}
if (ExportDTSFile)
{
totalSteps++;
}
return totalSteps;
}
private const string EU_DATASET_GROUP_NAME = "/Datasets_EU";
private const string ADC_DATASET_GROUP_NAME = "/Datasets";
private const string MV_DATASET_GROUP_NAME = "/Datasets_MV";
public string ExtensionPrefix { get; set; } = string.Empty;
/// <summary>
/// writes out test to given path
/// </summary>
/// <param name="pathname"></param>
/// <param name="id"></param>
/// <param name="dataFolder"></param>
/// <param name="test"></param>
/// <param name="bFiltering"></param>
/// <param name="includeGroupNameInISOExport"></param>
/// <param name="fd"></param>
/// <param name="tmChannel"></param>
/// <param name="channelNumber"></param>
/// <param name="beginEventHandler"></param>
/// <param name="cancelEventHandler"></param>
/// <param name="endEventHandler"></param>
/// <param name="tickEventHandler"></param>
/// <param name="errorEventHandler"></param>
/// <param name="cancelRequested"></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)
{
try
{
beginEventHandler?.Invoke(this, 100);
var filename = Path.Combine(pathname, $"{id}_{LabName}_1of1{(ExtensionPrefix ?? "")}.h5");
var fileInfo = new FileInfo(filename);
if (!System.IO.Directory.Exists(fileInfo.DirectoryName))
{
System.IO.Directory.CreateDirectory(fileInfo.DirectoryName);
}
var fileid = H5F.create(fileInfo.FullName, H5F.ACC_TRUNC);
CheckStatus(fileid, ErrorLocation.File);
var totalSteps = ComputeNumberOfSteps(test);
var stepsCompleted = 0D;
_curProg = 0D;
//include binary files if requested
var groupid2 = H5G.create(fileid, "/Files");
CheckStatus(groupid2, ErrorLocation.Group);
if (ExportLogs)
{
AddDirectoryIfExists(pathname, "Logs", fileid, "*.*");
stepsCompleted++;
UpdateProgress(100D * stepsCompleted / totalSteps, tickEventHandler);
}
if (ExportSetup)
{
AddDirectoryIfExists(pathname, "SETUP", fileid, "*.*");
stepsCompleted++;
UpdateProgress(100D * stepsCompleted / totalSteps, tickEventHandler);
}
if (ExportReports)
{
AddDirectoryIfExists(pathname, "Reports", fileid, "*.*");
stepsCompleted++;
UpdateProgress(100D * stepsCompleted / totalSteps, tickEventHandler);
}
if (ExportDTSFile)
{
var dataDir = Path.GetFileName(dataFolder);
AddDirectoryIfExists(pathname, string.Concat("Binary\\", dataDir), fileid, "*.dts");
stepsCompleted++;
UpdateProgress(100D * stepsCompleted / totalSteps, tickEventHandler);
}
if (!ExportEU && !ExportADC && !ExportMV) return;
if (ExportEU)
{
CreateDataset(fileid, HDF_DATASET_TYPE.EU, test, ref stepsCompleted, totalSteps, tickEventHandler, id);
}
if (ExportADC)
{
CreateDataset(fileid, HDF_DATASET_TYPE.ADC, test, ref stepsCompleted, totalSteps, tickEventHandler, id);
}
if (ExportMV)
{
CreateDataset(fileid, HDF_DATASET_TYPE.MV, test, ref stepsCompleted, totalSteps, tickEventHandler, id);
}
H5G.close(groupid2);
H5F.close(fileid);
}
catch (System.Exception ex)
{
APILogger.Log("encountered problem writing HDF test files", ex);
errorEventHandler?.Invoke(this, ex);
}
finally
{
tickEventHandler?.Invoke(this, 100D);
endEventHandler?.Invoke(this);
}
}
private enum HDF_DATASET_TYPE
{
EU,
ADC,
MV
}
private static string GetDataSetName(HDF_DATASET_TYPE datasetType)
{
switch (datasetType)
{
case HDF_DATASET_TYPE.ADC:
return ADC_DATASET_GROUP_NAME;
case HDF_DATASET_TYPE.MV:
return MV_DATASET_GROUP_NAME;
default:
//case HDF_DATASET_TYPE.EU:
return EU_DATASET_GROUP_NAME;
}
}
private const double HDF_EXPORT_VERSION = 7.0D;
private void CreateDataset(long fileid, HDF_DATASET_TYPE datasetType,
Test test, ref double stepsCompletedRef, int totalSteps, TickEventHandler tickEventHandler,
string testID)
{
var stepsCompleted = stepsCompletedRef;
var datasetGroupName = GetDataSetName(datasetType);
var groupid = H5G.create(fileid, datasetGroupName);
CheckStatus(groupid, ErrorLocation.Group);
//include test level attributes
CreateStringAttribute(groupid, "TestName", test.Id);
CreateStringAttribute(groupid, "Description", test.Description);
CreateStringAttribute(groupid, "TestID", testID);
CreateStringAttribute(groupid, "SoftwareVersion", test.SoftwareVersion);
CreateDoubleAttribute(groupid, "HDFExportVersion", HDF_EXPORT_VERSION);
CreateIntAttribute(groupid, "FaultFlag", Convert.ToInt32(test.FaultFlags));
CreateIntAttribute(groupid, "ExtendedFaultFlag1", Convert.ToInt32(test.ExtendedFaultFlags1));
CreateIntAttribute(groupid, "ExtendedFaultFlag2", Convert.ToInt32(test.ExtendedFaultFlags2));
CreateIntAttribute(groupid, "ExtendedFaultFlag3", Convert.ToInt32(test.ExtendedFaultFlags3));
CreateIntAttribute(groupid, "ExtendedFaultFlag4", Convert.ToInt32(test.ExtendedFaultFlags4));
var machineName = Environment.MachineName;
//these are all variables that will be used below as we go through channels
//we declare them out here to just avoid the creation/cleanup overhead
test.Channels.Sort((a, b) => { return a.AbsoluteDisplayOrder.CompareTo(b.AbsoluteDisplayOrder); });
//go through all the channels, and include whatever data is requested
//http://manuscript.dts.local/f/cases/16463/HDF-export-runs-very-slowly
// now runs through list in parallel
Parallel.ForEach(test.Channels, testChannel =>
{
int length, offset;
int status;
short iTemp;
IntPtr intPtr;
byte[] bytes;
var aic = testChannel as Test.Module.AnalogInputChannel;
double dTemp;
long mid1, dataspace, subgroup, dataset;
//for now ignore the channel if it's not an analoginputchannel, this probably includes everything except maybe the calculated channels ...
if (null == aic)
{
return;
}
var ds = GetDataScaler(aic);
var channelName2 = aic.ChannelName2.Replace('/', '_');
//this will be the root key for the channel data
//the root key ends in the channel's name
var key = string.Format("{2}/{1}: {0}", channelName2, (1 + aic.AbsoluteDisplayOrder).ToString("0000"), datasetGroupName);
subgroup = H5G.create(fileid, key);
CheckStatus(subgroup, ErrorLocation.SubGroup);
#region subgroup_Attributes
var numOfSamples = aic.ParentModule.NumberOfSamples;
CreateUlongAttribute(subgroup, "NumberOfSamples", numOfSamples);
var aaFilterRateHz = aic.ParentModule.AaFilterRateHz;
CreateDoubleAttribute(subgroup, "AAFilterRateHz", aaFilterRateHz);
var sampleRateHz = aic.ParentModule.SampleRateHz;
CreateDoubleAttribute(subgroup, "SampleRateHz", sampleRateHz);
CreateDoubleAttribute(subgroup, "RequestedPreTriggerSeconds",
aic.ParentModule.RequestedPreTriggerSeconds);
CreateDoubleAttribute(subgroup, "RequestedPostTriggerSeconds",
aic.ParentModule.RequestedPostTriggerSeconds);
CreateStringAttribute(subgroup, "RecordingMode", aic.ParentModule.RecordingMode.ToString());
CreateDoubleAttribute(subgroup, "PreTriggerSeconds", aic.ParentModule.PreTriggerSeconds);
CreateDoubleAttribute(subgroup, "PostTriggerSeconds", aic.ParentModule.PostTriggerSeconds);
CreateUlongAttribute(subgroup, "StartRecordSampleNumber",
aic.ParentModule.StartRecordSampleNumber);
CreateStringAttribute(subgroup, "Bridge", aic.Bridge.ToString());
CreateDoubleAttribute(subgroup, "BridgeResistanceOhms", aic.BridgeResistanceOhms);
CreateStringAttribute(subgroup, "ChannelDescription", aic.ChannelDescriptionString);
CreateStringAttribute(subgroup, "ChannelName", channelName2);
CreateStringAttribute(subgroup, "SerialNumber", aic.SerialNumber);
CreateStringAttribute(subgroup, "HardwareChannel", aic.HardwareChannelName);
CreateDoubleAttribute(subgroup, "DesiredRange", aic.DesiredRange);
CreateDoubleAttribute(subgroup, "Sensitivity", aic.Sensitivity);
CreateStringAttribute(subgroup, "SoftwareFilter", aic.SoftwareFilter);
CreateIntAttribute(subgroup, "ProportionalToExcitation", aic.ProportionalToExcitation ? 1 : 0);
CreateIntAttribute(subgroup, "IsInverted", aic.IsInverted ? 1 : 0);
CreateStringAttribute(subgroup, "AbsoluteDisplayOrder", aic.AbsoluteDisplayOrder.ToString("0000"));
var lastCalibrationDate = aic.LastCalibrationDate.ToShortDateString();
CreateStringAttribute(subgroup, "LastCalibrationDate", lastCalibrationDate);
//CreateStringAttribute(subgroup, "SensorId", aic.SensorID);
CreateStringAttribute(subgroup, "ExcitationVoltage", aic.ExcitationVoltage.ToString());
var factoryExcitationVoltage = aic.FactoryExcitationVoltage;
CreateDoubleAttribute(subgroup, "FactoryExcitationVoltage", factoryExcitationVoltage);
CreateDoubleAttribute(subgroup, "MeasuredExcitationVoltage", aic.MeasuredExcitationVoltage);
var engineeringUnits = aic.EngineeringUnits.TrimEnd(' ');
CreateStringAttribute(subgroup, "EngineeringUnits", engineeringUnits);
CreateDoubleAttribute(subgroup, "TimeOfSampleZeroSeconds", aic.TimeOfFirstSampleSec);
var triggerSampleNumber = aic.ParentModule.TriggerSampleNumbers[0];
CreateDoubleAttribute(subgroup, "TriggerSampleNumber", triggerSampleNumber);
var dStartTime = (aic.ParentModule.StartRecordSampleNumber / (double)aic.ParentModule.SampleRateHz) * 1000D;
if (aic.ParentModule.TriggerSampleNumbers.Count > 0)
{
dStartTime -= (aic.ParentModule.TriggerSampleNumbers[0] / (double)aic.ParentModule.SampleRateHz) * 1000D;
}
CreateDoubleAttribute(subgroup, "TimeOfFirstSampleSeconds", dStartTime);
var scaleFactorMv = ds.GetScaleFactorMv();
CreateDoubleAttribute(subgroup, "ScaleFactorMv", scaleFactorMv);
var scaleFactorADC = ds.GetAdcToEuScalingFactor();
CreateDoubleAttribute(subgroup, "ScaleFactorADC", scaleFactorADC);
var offsetADC = ds.GetDataZeroLevelADC();
CreateDoubleAttribute(subgroup, "DataZeroLevelADC", offsetADC);
#region WIAMan Attributes
//Begin Time Required Negative milliseconds from first data point to reference time (e.g. -500.0)
//Data Count Required The number of data points in the dataset (e.g. 750001)
//Description Required The name of the channel (e.g. "1:" or "Channel 1 - Head AX")
//Reference Index Required The index of the data point of the trigger (e.g. 256000)
//Reference Time Required The time of The data point at The reference index, in The format yyyy-MM-dd HH:mm:ss.SSSSSSSS (e.g. 2017-02-01 20:44:48.95126555)
//Sampling Rate Required The sampling rate in kHz (e.g. 500.0)
//Scale Factor Required The factor to convert between raw and engineering units (e.g. .00031397038667400326)
//Scale Offset Required The offset for conversion between units (e.g. 0.0)
//Type Required Must be set to BTSXPeriodicXScalableYDataset
//X Description Required e.g. Time(ms)
//Y Description Required e.g. Volts
//X Engineering Units Required e.g. ms
//Y Engineering Units Required e.g. Volts
//Anthropomorphic Label Desired Serial number of the ATD
//Calibration Excitation Desired (excitation used in calibrating transducer for that channel)
//Cutoff Frequency Desired (the cutoff frequency set on this channel)
//DAQ Serial Number Desired Serial number of the DAS
//DSP Version Desired (firmware of DAS, used mainly for diagnostics)
//Director Desired (meta data, Test Director in charge of test event)
//End Time Desired (this can be calculated used end index and sampling rate and gps time)
//Excitation Voltage Desired (used to calculate scale factor, not needed if scale factor is already a parameter)
//GF/Sens. Desired (used to calculate scale factor, not needed if scale factor is already a parameter)
//Gage Factor Units Desired (used to calculate scale factor, not needed if scale factor is already a parameter)
//Gain Desired Total set gain (not calibrated gain) mainly for diagnostics, ScaleFactor should already have factored this in
//Internal Trigger Level 0 Desired Mainly for diagnostics later if there is a problem
//Location Desired (meta data, event location)
//Master Trigger Desired Mainly for diagnostics later if there is a problem
//Number of Datasets Desired (total channels collected in this H5 file)
//Operator Desired (meta data, person who is sitting at the computer)
//Round Desired Name of this current test
//Slope Value 0 Desired Trigger Slope (Positive, Negative, Absolute) Mainly for diagnostics later if there is a problem
//calibrationDate Desired Calibration date of the transducer (when was it calibrated)
//calibrationFactor Desired Counts->voltage factor. Should be accounted for in ScaleFactor, here for diagnostics
//pog Desired post gain (set value of post-gain, here for diagnostics)
//prg Desired pre gain (set value of pre-gain, here for diagnostics)
CreateDoubleAttribute(subgroup, "Begin Time", aic.TimeOfFirstSampleSec * 1000.0D);
CreateUlongAttribute(subgroup, "Data Count", numOfSamples);
CreateStringAttribute(subgroup, "Description", $"{(1 + aic.AbsoluteDisplayOrder).ToString("0000")}:");
CreateDoubleAttribute(subgroup, "Reference Index", Math.Abs(aic.ParentModule.RequestedPreTriggerSeconds * sampleRateHz));
var refTime = new DateTime(1970, 1, 1).AddSeconds(aic.ParentModule.TriggerTimestampSec);
refTime = refTime.AddMilliseconds((aic.ParentModule.TriggerTimestampNanoSec) / 1000000);
CreateStringAttribute(subgroup, "Reference Time", refTime.ToString("yyyy-MM-dd HH:mm:ss.fff") + ((aic.ParentModule.TriggerTimestampNanoSec) % 1000000).ToString("000000"));
CreateDoubleAttribute(subgroup, "Sampling Rate", sampleRateHz / 1000D);
CreateDoubleAttribute(subgroup, "Scale Factor", scaleFactorADC);
CreateDoubleAttribute(subgroup, "Scale Offset", offsetADC);
CreateStringAttribute(subgroup, "Type", "BTSXPeriodicXScalableYDataset");
CreateStringAttribute(subgroup, "X Description", "Time (ms)");
CreateStringAttribute(subgroup, "Y Description", engineeringUnits);
CreateStringAttribute(subgroup, "X Engineering Units", "ms");
CreateStringAttribute(subgroup, "Y Engineering Units", engineeringUnits);
CreateDoubleAttribute(subgroup, "Calibration Excitation", factoryExcitationVoltage);
CreateDoubleAttribute(subgroup, "Cutoff Frequency", aaFilterRateHz);
CreateStringAttribute(subgroup, "DAQ Serial Number", aic.ParentModule.SerialNumber);
CreateStringAttribute(subgroup, "Round", testID);
CreateStringAttribute(subgroup, "calibrationDate", lastCalibrationDate);
CreateDoubleAttribute(subgroup, "calibrationFactor", scaleFactorMv);
CreateStringAttribute(subgroup, "Anthropomorphic Label", $"{ISOToFineLocation3[aic.IsoCode]} SN {aic.ChannelGroupName}");
CreateStringAttribute(subgroup, "Director", CustomerName);
CreateStringAttribute(subgroup, "Operator", TestEngineerName);
CreateStringAttribute(subgroup, "Location", LabName);
CreateStringAttribute(subgroup, "ComputerName", machineName);
CreateStringAttribute(subgroup, "Input Mode", "TRANSDUCER");
CreateStringAttribute(subgroup, "Channel Label:Optional", $"{ISOToPosition[aic.IsoCode]}, {channelName2.Replace(" ", ", ")}");
CreateStringAttribute(subgroup, "Channel Label:Modifier", "Anthropomorphic");
CreateStringAttribute(subgroup, "Channel Label:Category", ISOToPhysicalDimension[aic.IsoCode]);
CreateStringAttribute(subgroup, "Channel Label:Units", engineeringUnits);
CreateStringAttribute(subgroup, "TI:Serial Number", aic.SerialNumber);
CreateStringAttribute(subgroup, "TI:Manufacturer", aic.Manufacturer);
CreateStringAttribute(subgroup, "TI:Model", aic.Model);
CreateDoubleAttribute(subgroup, "Expected Peak Signal", aic.DesiredRange);
CreateDoubleAttribute(subgroup, "GF/Sens.", aic.Sensitivity);
CreateStringAttribute(subgroup, "Gage Factor Type", "Sensitivity");
CreateStringAttribute(subgroup, "Gage Factor Units", string.Concat(engineeringUnits, "/mv"));
CreateStringAttribute(subgroup, "Gage Calibration Due Date", aic.LastCalibrationDate.AddYears(1).ToString("yyyy-MM-dd HH:mm:ss.fff"));
CreateDoubleAttribute(subgroup, "Gage Calibration Excitation", DTS.Common.DAS.Concepts.Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(aic.ExcitationVoltage));
CreateStringAttribute(subgroup, "Is WIAMan Data", (IsWiamanData ? "TRUE" : "FALSE"));
#endregion
#region S6Attributes
Test.Module parentDbModule = null;
if (aic.HardwareChannelName.Contains(":"))
{
var parentDBSN = aic.HardwareChannelName.Trim(new char[] { '[', ']' }).Split(':').FirstOrDefault();
if (!string.IsNullOrEmpty(parentDBSN))
{
parentDbModule = test.Modules.Where(x => x.SerialNumber == parentDBSN).ToList().FirstOrDefault();
}
}
CreateStringAttribute(subgroup, "SystemID", aic.ParentModule.SystemID);
CreateStringAttribute(subgroup, "SystemLocation", aic.ParentModule.SystemLocation);
CreateDoubleAttribute(subgroup, "TargetAxisX", aic.ParentModule.TargetAxisX);
CreateDoubleAttribute(subgroup, "TargetAxisY", aic.ParentModule.TargetAxisY);
CreateDoubleAttribute(subgroup, "TargetAxisZ", aic.ParentModule.TargetAxisZ);
CreateDoubleAttribute(subgroup, "TiltSensorAxisXDegreesPre", aic.ParentModule.TiltSensorAxisXDegreesPre);
CreateDoubleAttribute(subgroup, "TiltSensorAxisYDegreesPre", aic.ParentModule.TiltSensorAxisYDegreesPre);
CreateDoubleAttribute(subgroup, "TiltSensorAxisZDegreesPre", aic.ParentModule.TiltSensorAxisZDegreesPre);
CreateDoubleAttribute(subgroup, "TiltSensorAxisXDegreesPost", aic.ParentModule.TiltSensorAxisXDegreesPost);
CreateDoubleAttribute(subgroup, "TiltSensorAxisYDegreesPost", aic.ParentModule.TiltSensorAxisYDegreesPost);
CreateDoubleAttribute(subgroup, "TiltSensorAxisZDegreesPost", aic.ParentModule.TiltSensorAxisZDegreesPost);
if (null != parentDbModule)
{
CreateDoubleAttribute(subgroup, "TemperatureLocation1Pre", parentDbModule.TemperatureLocation1Pre);
CreateDoubleAttribute(subgroup, "TemperatureLocation2Pre", parentDbModule.TemperatureLocation2Pre);
CreateDoubleAttribute(subgroup, "TemperatureLocation3Pre", parentDbModule.TemperatureLocation3Pre);
CreateDoubleAttribute(subgroup, "TemperatureLocation4Pre", parentDbModule.TemperatureLocation4Pre);
CreateDoubleAttribute(subgroup, "TemperatureLocation1Post", parentDbModule.TemperatureLocation1Post);
CreateDoubleAttribute(subgroup, "TemperatureLocation2Post", parentDbModule.TemperatureLocation2Post);
CreateDoubleAttribute(subgroup, "TemperatureLocation3Post", parentDbModule.TemperatureLocation3Post);
CreateDoubleAttribute(subgroup, "TemperatureLocation4Post", parentDbModule.TemperatureLocation4Post);
}
#endregion
#endregion
dataspace = H5S.create_simple(1, new[] { aic.ParentModule.NumberOfSamples },
null);
CheckStatus(dataspace, ErrorLocation.DataSpace);
length = Convert.ToInt32(aic.ParentModule.NumberOfSamples);
int iSampleIdx;
//http://manuscript.dts.local/f/cases/16463/HDF-export-runs-very-slowly
// Grab all the data from the HDD at once
var allData = aic.PersistentChannelInfo.Data;
switch (datasetType)
{
case HDF_DATASET_TYPE.ADC:
{
dataset = H5D.create(subgroup, string.Format("{0}: Strain_RawYData", (1 + aic.AbsoluteDisplayOrder).ToString("0000")), H5T.NATIVE_SHORT,
dataspace);
CheckStatus(dataset, ErrorLocation.DataSet);
intPtr = Marshal.AllocHGlobal(length * sizeof(Int16));
offset = 0;
for (iSampleIdx = 0; iSampleIdx < length; iSampleIdx++)
{
//since we are using 100 as our ticks in our update, so every UPDATE_INTERVAL update with the status
if (0 == iSampleIdx % UPDATE_INTERVAL)
{
UpdateProgress(
100D * (stepsCompleted + iSampleIdx / (double)length) / totalSteps,
tickEventHandler);
}
iTemp = allData[iSampleIdx];
bytes = BitConverter.GetBytes(iTemp);
foreach (var b in bytes)
{
Marshal.WriteByte(intPtr, offset, b);
offset++;
}
}
mid1 = H5S.create_simple(1, new[] { aic.ParentModule.NumberOfSamples }, null);
CheckStatus(mid1, ErrorLocation.DataSpace);
status = H5D.write(dataset, H5T.NATIVE_SHORT, mid1, dataspace,
H5P.DEFAULT, intPtr);
CheckStatus(status, ErrorLocation.Write);
H5D.close(dataset);
H5S.close(mid1);
Marshal.FreeHGlobal(intPtr);
stepsCompleted++;
UpdateProgress(100D * stepsCompleted / totalSteps, tickEventHandler);
}
break;
case HDF_DATASET_TYPE.MV:
{
dataset = H5D.create(subgroup, string.Format("{0}: Strain_RawYData", (1 + aic.AbsoluteDisplayOrder).ToString("0000")), H5T.NATIVE_DOUBLE,
dataspace);
CheckStatus(dataset, ErrorLocation.DataSet);
mid1 = H5S.create_simple(1, new[] { aic.ParentModule.NumberOfSamples }, null);
CheckStatus(mid1, ErrorLocation.DataSpace);
intPtr = Marshal.AllocHGlobal(length * sizeof(double));
offset = 0;
for (iSampleIdx = 0; iSampleIdx < length; iSampleIdx++)
{
if (0 == iSampleIdx % UPDATE_INTERVAL)
{
UpdateProgress(
100D * (stepsCompleted + iSampleIdx / (double)length) / totalSteps,
tickEventHandler);
}
dTemp = ds.GetMv(allData[iSampleIdx]);
bytes = BitConverter.GetBytes(dTemp);
foreach (var b in bytes)
{
Marshal.WriteByte(intPtr, offset, b);
offset++;
}
}
status = H5D.write(dataset, H5T.NATIVE_DOUBLE, mid1, dataspace,
H5P.DEFAULT, intPtr);
CheckStatus(status, ErrorLocation.Write);
Marshal.FreeHGlobal(intPtr);
H5D.close(dataset);
H5S.close(mid1);
stepsCompleted++;
UpdateProgress(100D * stepsCompleted / totalSteps, tickEventHandler);
}
break;
case HDF_DATASET_TYPE.EU:
{
dataset = H5D.create(subgroup, string.Format("{0}: Strain_RawYData", (1 + aic.AbsoluteDisplayOrder).ToString("0000")), H5T.NATIVE_DOUBLE,
dataspace);
CheckStatus(dataset, ErrorLocation.DataSet);
mid1 = H5S.create_simple(1, new[] { aic.ParentModule.NumberOfSamples }, null);
CheckStatus(mid1, ErrorLocation.DataSpace);
intPtr = Marshal.AllocHGlobal(length * sizeof(double));
offset = 0;
for (iSampleIdx = 0; iSampleIdx < length; iSampleIdx++)
{
if (0 == iSampleIdx % UPDATE_INTERVAL)
{
UpdateProgress(
100D * (stepsCompleted + iSampleIdx / (double)length) / totalSteps,
tickEventHandler);
}
dTemp = ds.GetEU(allData[iSampleIdx]);
bytes = BitConverter.GetBytes(dTemp);
foreach (var b in bytes)
{
Marshal.WriteByte(intPtr, offset, b);
offset++;
}
}
status = H5D.write(dataset, H5T.NATIVE_DOUBLE, mid1, dataspace,
H5P.DEFAULT, intPtr);
CheckStatus(status, ErrorLocation.Write);
Marshal.FreeHGlobal(intPtr);
H5D.close(dataset);
H5S.close(mid1);
stepsCompleted++;
UpdateProgress(100D * stepsCompleted / totalSteps, tickEventHandler);
}
break;
default:
break;
}
H5S.close(dataspace);
H5G.close(subgroup);
aic.PersistentChannelInfo.UnSet();
});
H5G.close(groupid);
}
#endregion
/// <summary>
/// constructs the writer with a given file and encoding
/// </summary>
/// <param name="fileType"></param>
/// <param name="encoding"></param>
internal Writer(File fileType, int encoding)
: base(fileType, encoding)
{
ExportADC = true;
ExportEU = true;
ExportMV = true;
ExportLogs = true;
ExportReports = true;
ExportSetup = true;
WriterParent = fileType;
}
/// <summary>
/// initializes the writer
/// </summary>
/// <param name="pathname"></param>
/// <param name="id"></param>
/// <param name="dataFolder"></param>
/// <param name="test"></param>
/// <param name="bFiltering"></param>
/// <param name="includeGroupNameInISOExport"></param>
/// <param name="fd"></param>
/// <param name="tmChannel"></param>
/// <param name="channelNumber"></param>
/// <param name="beginEventHandler"></param>
/// <param name="cancelEventHandler"></param>
/// <param name="endEventHandler"></param>
/// <param name="tickEventHandler"></param>
/// <param name="errorEventHandler"></param>
/// <param name="cancelRequested"></param>
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)
{
}
}
}
}