AsamMdf 0.4.0

dotnet add package AsamMdf --version 0.4.0
                    
NuGet\Install-Package AsamMdf -Version 0.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AsamMdf" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AsamMdf" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="AsamMdf" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AsamMdf --version 0.4.0
                    
#r "nuget: AsamMdf, 0.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AsamMdf@0.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AsamMdf&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=AsamMdf&version=0.4.0
                    
Install as a Cake Tool

AsamMdf - MDF4 File Reader for .NET

A .NET library for reading ASAM MDF (Measurement Data Format) version 4.x files. This library provides a simple and efficient API to access measurement data, channel information, and metadata from MDF4 files commonly used in automotive and industrial measurement applications.

.NET Test Coverage MDF Version Tests

If this library meets your requirements, please consider acquiring a commercial license for full functionality and professional support.

For licensing inquiries, please contact: info@uptux.de

Features

Comprehensive MDF4 Support

  • Read channel data with automatic type conversion
  • Support for all standard data types (integers, floats, strings, byte arrays)
  • CANopen Date and Time types
  • Complex numbers (half/single/double precision, LE/BE) - MDF 4.2.0
  • MIME sample and MIME stream (embedded binary data with content-type) - MDF 4.2.0
  • Event blocks (EVBLOCK) – markers, triggers, recording info, capture blocks
  • Variable-length signal data (VLSD)
  • Compressed data (Deflate, Transpose+Deflate)
  • Physical and virtual master channels

Advanced Conversions

  • Linear, rational, and algebraic conversions
  • Lookup tables (value-to-value, value-to-text, text-to-text, text-to-value)
  • Partial conversions with value ranges
  • Automatic unit conversion

Array Channels (CABLOCK)

  • Multi-dimensional arrays (maps, curves, classification results)
  • Compact storage (CN template) for arrays with fixed axes
  • Automatic array structure parsing and flattening

Performance

  • Streaming API for memory-efficient data access
  • Support for compressed data blocks
  • Lazy loading of channel data

Quick Start

Basic Usage

using AsamMdf;

// Open an MDF4 file
using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // Get all channel names
    var channelNames = reader.GetChannelNames();
    Console.WriteLine($"Found {channelNames.Count()} channels");
    
    // Read a specific channel
    var channelReader = reader.GetChannelReader("Speed");
    var values = channelReader.GetValues<double>().ToList();
    
    Console.WriteLine($"Channel 'Speed' has {values.Count} samples");
}

Reading Channel Data with Master Channels

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // Get a data channel
    var speedChannel = reader.GetChannelReader("Speed");
    
    // Get associated master channels (time, angle, etc.)
    var masterChannels = speedChannel.GetMasterChannels();
    
    if (masterChannels.Count > 0)
    {
        var timeChannel = masterChannels.First();
        var timestamps = timeChannel.GetValues<double>().ToList();
        var speeds = speedChannel.GetValues<double>().ToList();
        
        // Data is synchronized - same index corresponds to same measurement point
        for (int i = 0; i < timestamps.Count; i++)
        {
            Console.WriteLine($"Time: {timestamps[i]:F3}s, Speed: {speeds[i]:F1} km/h");
        }
    }
}

Reading Different Data Types

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // Integer channel
    var counterReader = reader.GetChannelReader("Counter");
    var counterValues = counterReader.GetValues<int>().ToList();
    
    // Floating-point channel
    var tempReader = reader.GetChannelReader("Temperature");
    var tempValues = tempReader.GetValues<float>().ToList();
    
    // String channel (VLSD)
    var statusReader = reader.GetChannelReader("Status");
    var statusValues = statusReader.GetValues<string>().ToList();
    
    // Byte array channel
    var dataReader = reader.GetChannelReader("RawData");
    var dataValues = dataReader.GetValues<byte[]>().ToList();
}

Reading Array Channel Data

using (var reader = new Mdf4FileReader("calibration_data.mf4"))
{
    // Read a 2D array channel (e.g., engine calibration map)
    var mapReader = reader.GetChannelReader("KF_EngineMap");
    
    // Check if it's an array channel
    if (mapReader.IsArrayChannel)
    {
        // Get array data
        var arrayValues = mapReader.GetArrayValues<double>().ToList();
        
        Console.WriteLine($"Number of samples: {arrayValues.Count}");
        
        // Process each sample
        foreach (var sample in arrayValues)
        {
            Console.WriteLine($"Array dimensions: [{string.Join(", ", sample.Dimensions)}]");
            Console.WriteLine($"Total elements: {sample.ElementCount}");
            
            // Access individual array elements
            // For 2D array with dimensions [8, 8]:
            var firstElement = sample.GetValueAt(0, 0);  // Element at [0,0]
            var element_2_3 = sample.GetValueAt(2, 3);   // Element at [2,3]
            
            // Or access flattened array directly
            var allValues = sample.Values;  // T[] array in row-major order
            
            // Convert to multi-dimensional array
            if (sample.Dimensions.Length == 2)
            {
                var multiDimArray = (double[,])sample.ToMultiDimensionalArray();
                Console.WriteLine($"Value at [2,3]: {multiDimArray[2, 3]}");
            }
        }
    }
}

Array Channel Details

Array channels (CABLOCK) are used for multi-dimensional calibration data such as:

  • Engine maps (2D: RPM × Load)
  • Curves (1D: Lookup tables)
  • Classification results (N-D: Multi-parameter classifications)
  • Signal matrices (2D+: Video frames, sensor arrays)
using AsamMdf;

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    var channelReader = reader.GetChannelReader("MapData");
    
    // Check if it's an array channel
    if (channelReader.IsArrayChannel)
    {
        // Read and process array data
        foreach (var arrayData in channelReader.GetArrayValues<double>())
        {
            // Work with the array
            Console.WriteLine($"Sample with {arrayData.ElementCount} elements");
            
            // Efficient access to array elements
            for (int i = 0; i < arrayData.Values.Length; i++)
            {
                var value = arrayData.Values[i];
                // Process value...
            }
        }
    }
}

Reading CANopen Date and Time

using AsamMdf;
using AsamMdf.Types;

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // Read CANopen Date as DateTime
    var dateReader = reader.GetChannelReader("DateChannel");
    var dates = dateReader.GetValues<DateTime>().ToList();
    
    // Or get CANopen Date objects for full access
    var canOpenDates = dateReader.GetValues<CanOpenDate>().ToList();
    foreach (var date in canOpenDates.Take(5))
    {
        Console.WriteLine($"Date: {date.GetFullYear()}-{date.Month:D2}-{date.Day:D2}");
        Console.WriteLine($"Time: {date.Hours:D2}:{date.Minutes:D2}");
        Console.WriteLine($"Day of week: {date.DayOfWeek}, DST: {date.IsSummerTime}");
    }
    
    // Read CANopen Time
    var timeReader = reader.GetChannelReader("TimeChannel");
    var times = timeReader.GetValues<DateTime>().ToList();
}

Reading Complex Numbers (MDF 4.2.0)

Note: Complex number support is fully implemented according to ASAM MDF 4.2.0 specification but has not yet been tested against real-world MDF4 files. Please report any issues you encounter.

using AsamMdf;
using AsamMdf.Types;

using (var reader = new Mdf4FileReader("fft_measurement.mf4"))
{
    // Read complex numbers (e.g., FFT data)
    var fftReader = reader.GetChannelReader("FFT_Signal");
    var fftData = fftReader.GetValues<ComplexNumber>().ToList();
    
    foreach (var complexValue in fftData.Take(5))
    {
        Console.WriteLine($"Complex: {complexValue}");              // "3 + 4i"
        Console.WriteLine($"  Magnitude: {complexValue.Magnitude}"); // 5.0
        Console.WriteLine($"  Phase: {complexValue.PhaseDegrees}°"); // 53.13°
        Console.WriteLine($"  Polar: {complexValue.ToPolarString()}"); // "5.000000∠53.13°"
    }
    
    // Or read only magnitudes directly
    var magnitudes = fftReader.GetValues<double>().ToList();
    
    // Perform operations on complex numbers
    var z1 = fftData[0];
    var z2 = fftData[1];
    var sum = z1 + z2;
    var product = z1 * z2;
}

Reading MIME Sample Data (MDF 4.2.0)

Note: MIME sample support is fully implemented according to ASAM MDF 4.2.0 specification but has not yet been tested against real-world MDF4 files. Please report any issues you encounter.

using AsamMdf;
using AsamMdf.Types;

using (var reader = new Mdf4FileReader("camera_recording.mf4"))
{
    // Read MIME sample channel (e.g., camera images)
    var cameraReader = reader.GetChannelReader("Front_Camera");
    var images = cameraReader.GetValues<MimeSample>().ToList();
    
    foreach (var image in images)
    {
        Console.WriteLine($"Image: {image.ContentType}, Size: {image.Size} bytes");
        
        // Save image to file
        string extension = image.ContentType switch
        {
            "image/png" => "png",
            "image/jpeg" => "jpg",
            _ => "bin"
        };
        image.SaveToFile($"frame_{DateTime.Now.Ticks}.{extension}");
    }
    
    // Or read as raw bytes if you don't need the MIME wrapper
    var rawImages = cameraReader.GetValues<byte[]>().ToList();
    
    // Or get string descriptions
    var descriptions = cameraReader.GetValues<string>().ToList();
    // e.g., "MimeSample(image/jpeg, 102400 bytes)"
}

Reading MIME Stream Data (MDF 4.2.0)

Note: MIME stream support is fully implemented according to ASAM MDF 4.2.0 specification but has not yet been tested against real-world MDF4 files. Please report any issues you encounter.

using AsamMdf;
using AsamMdf.Types;

using (var reader = new Mdf4FileReader("video_recording.mf4"))
{
    // Read MIME stream channel (e.g., video frames)
    var videoReader = reader.GetChannelReader("Video_Stream");
    
    // Each value in a MIME stream represents a frame/chunk
    var videoStream = new MimeStream("video/mpeg");
    
    foreach (var frame in videoReader.GetValues<byte[]>())
    {
        videoStream.AddSample(frame);
    }
    
    // Save complete video stream
    videoStream.SaveToFile("recorded_video.mpg");
    
    Console.WriteLine($"Video recorded:");
    Console.WriteLine($"  Content-Type: {videoStream.ContentType}");
    Console.WriteLine($"  Total frames: {videoStream.SampleCount}");
    Console.WriteLine($"  Total size: {videoStream.TotalSize} bytes");
    
    // Or process each frame individually without creating a stream
    var frames = videoReader.GetValues<byte[]>().ToList();
    for (int i = 0; i < frames.Count; i++)
    {
        Console.WriteLine($"Frame {i}: {frames[i].Length} bytes");
        // Process frame...
    }
}

Reading Events (Markers, Triggers, Recording Info)

Events in MDF4 are file-level time annotations – they are not channel data, but metadata about specific time points or time ranges within a measurement (e.g. user bookmarks, trigger conditions, capture block start/stop). Events are read independently of channels via GetEvents().

using AsamMdf;
using AsamMdf.Types;

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // Get all events from the file
    var events = reader.GetEvents().ToList();

    Console.WriteLine($"Found {events.Count} event(s):");

    foreach (var ev in events)
    {
        Console.WriteLine($"  {ev}");
        // e.g. "[Marker] 90 deg @ 00:00:00.090 (Point, Tool)"
    }

    // Filter by type using LINQ
    var markers = events.Where(e => e.EventType == EventType.Marker).ToList();
    Console.WriteLine($"\nMarkers ({markers.Count}):");
    foreach (var m in markers)
    {
        // TimeFromMeasurementStart is available when SyncType == Seconds
        if (m.TimeFromMeasurementStart.HasValue)
            Console.WriteLine($"  '{m.Name}' at {m.TimeFromMeasurementStart.Value.TotalSeconds:F3}s");
        else
            Console.WriteLine($"  '{m.Name}' (SyncBase={m.SyncBaseValue})");
    }

    // Find capture block ranges (Begin/End pairs)
    var captureBegins = events.Where(e =>
        e.EventType == EventType.AcquisitionInterrupt &&
        e.RangeType  == EventRangeType.Begin).ToList();

    Console.WriteLine($"\nCapture blocks started: {captureBegins.Count}");

    // Access individual event properties
    var firstTrigger = events.FirstOrDefault(e => e.EventType == EventType.Trigger);
    if (firstTrigger != null)
    {
        Console.WriteLine($"\nFirst trigger:");
        Console.WriteLine($"  Name    : {firstTrigger.Name}");
        Console.WriteLine($"  Cause   : {firstTrigger.Cause}");
        Console.WriteLine($"  SyncBase: {firstTrigger.SyncBaseValue}");
        Console.WriteLine($"  Factor  : {firstTrigger.SyncFactor}");
        Console.WriteLine($"  PostProc: {firstTrigger.IsPostProcessing}");
    }
}

Event types (EventType enum): | Value | Meaning | |-------|---------| | Marker | User-defined bookmark | | Trigger | Measurement trigger condition | | Recording | Start / stop of recording | | AcquisitionInterrupt | Capture block boundary (Begin/End pair) | | RecordingInterrupt | Recording was interrupted | | StartRecordingTrigger | Trigger that started the recording | | StopRecordingTrigger | Trigger that stopped the recording |

Range types (EventRangeType enum): Point (single timestamp), Begin / End (paired interval).

Working with Conversions

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // Channel with linear conversion (e.g., raw ADC -> physical units)
    var sensorReader = reader.GetChannelReader("SensorVoltage");
    
    // GetValues automatically applies conversion formula
    var physicalValues = sensorReader.GetValues<double>().ToList();
    
    // Values are returned in physical units (e.g., Volts, not raw ADC counts)
    Console.WriteLine($"Sensor voltage: {physicalValues.First():F3} V");
}

Asynchronous API (Async/Await)

The library provides a comprehensive async API for non-blocking operations. This is perfect for UI applications, web APIs, and server scenarios where you need to keep threads free while reading metadata.

All async methods:

  • Are non-blocking (don't freeze the current thread)
  • Support CancellationToken for timeouts and cancellation
  • Have the same functionality as sync equivalents
  • Return Task<T> for proper async/await support
using AsamMdf;
using System.Threading;

// Load metadata asynchronously (keeps UI responsive)
using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // Get channel names without blocking
    var channelNames = await reader.GetChannelNamesAsync();
    Console.WriteLine($"Found {channelNames.Count} channels");
    
    // Get structured channel metadata
    var metadata = await reader.GetChannelMetadataStructuredAsync("Speed");
    Console.WriteLine($"Channel: {metadata.Name}, Unit: {metadata.Unit}");
    
    // Get file-level metadata
    var fileMetadata = await reader.GetFileMetadataAsync();
    Console.WriteLine($"File created: {fileMetadata["StartTime"]}");
    
    // Get channel reader
    var reader = await reader.GetChannelReaderAsync("Speed");
    var values = reader.GetValues<double>().ToList();
}

Async methods with timeout:

// Cancel if operation takes longer than 10 seconds
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    try
    {
        var names = await reader.GetChannelNamesAsync(cts.Token);
        Console.WriteLine($"Successfully loaded {names.Count} channels");
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Operation timed out - file too large?");
    }
}

Async methods in UI (WPF/WinForms):

// Keep UI responsive while loading large files
private async void LoadButton_Click(object sender, EventArgs e)
{
    loadButton.Enabled = false;
    statusLabel.Text = "Loading...";
    
    try
    {
        using (var reader = new Mdf4FileReader(_filePath))
        {
            // Async loading - UI remains responsive
            var channels = await reader.GetChannelNamesAsync();
            
            // Update UI on UI thread
            channelListBox.Items.Clear();
            foreach (var channel in channels)
            {
                channelListBox.Items.Add(channel);
            }
            
            statusLabel.Text = $"Loaded {channels.Count} channels";
        }
    }
    catch (Exception ex)
    {
        statusLabel.Text = $"Error: {ex.Message}";
    }
    finally
    {
        loadButton.Enabled = true;
    }
}

Available async methods:

  • GetChannelNamesAsync(CancellationToken ct = default)
  • GetChannelReaderAsync(string channelName, CancellationToken ct = default)
  • GetFileMetadataAsync(CancellationToken ct = default)
  • GetChannelMetadataAsync(string channelName, CancellationToken ct = default)
  • GetChannelMetadataStructuredAsync(string channelName, CancellationToken ct = default)
  • GetAttachmentsAsync(CancellationToken ct = default)
  • GetAttachmentDataAsync(string fileName, CancellationToken ct = default)
  • ExtractAttachmentAsync(string fileName, string outputPath, CancellationToken ct = default)

Working with Attachments

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // List all attachments
    var attachments = reader.GetAttachments();
    
    foreach (var attachment in attachments)
    {
        Console.WriteLine($"File: {attachment.FileName}");
        Console.WriteLine($"  Type: {attachment.MimeType}");
        Console.WriteLine($"  Size: {attachment.OriginalSize} bytes");
        Console.WriteLine($"  Embedded: {attachment.IsEmbedded}");
        Console.WriteLine($"  Compressed: {attachment.IsCompressed}");
    }
    
    // Extract an embedded attachment
    var embeddedAttachments = attachments.Where(a => a.IsEmbedded);
    if (embeddedAttachments.Any())
    {
        var attachment = embeddedAttachments.First();
        
        // Get attachment data (automatically decompressed if needed)
        byte[] data = reader.GetAttachmentData(attachment.FileName);
        
        // Or extract directly to a file
        reader.ExtractAttachment(attachment.FileName, "output.txt");
    }
}

Getting File Metadata

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // Get file-level metadata
    var fileMetadata = reader.GetFileMetadata();
    
    Console.WriteLine($"MDF Version: {fileMetadata["FormatIdentifier"]}");
    Console.WriteLine($"Created by: {fileMetadata["ProgramIdentifier"]}");
    Console.WriteLine($"Start time: {fileMetadata["StartTime"]}");
    Console.WriteLine($"Time zone offset: {fileMetadata["UTCTimeZoneOffset_min"]} minutes");
    
    // Check for file comments with XML metadata
    if (fileMetadata.ContainsKey("FileComment"))
    {
        Console.WriteLine($"Comment: {fileMetadata["FileComment"]}");
    }
}

Getting Channel Metadata

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    // Get list of all channels
    var channelNames = reader.GetChannelNames();
    
    foreach (var channelName in channelNames)
    {
        // Get detailed metadata for each channel
        var metadata = reader.GetChannelMetadata(channelName);
        
        Console.WriteLine($"Channel: {metadata["ChannelName"]}");
        Console.WriteLine($"  Data Type: {metadata["DataType"]}");
        Console.WriteLine($"  Channel Type: {metadata["ChannelType"]}");
        Console.WriteLine($"  Sync Type: {metadata["SyncType"]}");
        Console.WriteLine($"  Bit Count: {metadata["BitCount"]}");
        
        // Check for channel comments (may contain XML extensions)
        if (metadata.ContainsKey("Comment"))
        {
            Console.WriteLine($"  Comment: {metadata["Comment"]}");
        }
        
        // Check for unit information
        if (metadata.ContainsKey("Unit"))
        {
            Console.WriteLine($"  Unit: {metadata["Unit"]}");
        }
        
        // Check for conversion information
        if (metadata.ContainsKey("ConversionType"))
        {
            Console.WriteLine($"  Conversion: {metadata["ConversionType"]}");
        }
    }
    
    // Or get metadata for a specific channel
    var speedMetadata = reader.GetChannelMetadata("Speed");
    bool isMaster = speedMetadata["ChannelType"] == "2";  // 2 = master channel
    
    Console.WriteLine($"Speed channel is master: {isMaster}");
}

Advanced Usage

Filtering Master Channels by Sync Type

using (var reader = new Mdf4FileReader("measurement.mf4"))
{
    var channelReader = reader.GetChannelReader("Data");
    
    // Get only time-based master channels
    var timeMasters = channelReader.GetMasterChannels(Channel_SyncType.Time);
    
    // Get only angle-based master channels
    var angleMasters = channelReader.GetMasterChannels(Channel_SyncType.Angle);
    
    // Get all master channels
    var allMasters = channelReader.GetMasterChannels(Channel_SyncType.Undefined);
}

Reading Compressed Data

using (var reader = new Mdf4FileReader("compressed_measurement.mf4"))
{
    // Compressed data is automatically decompressed
    var channelReader = reader.GetChannelReader("Speed");
    var values = channelReader.GetValues<double>().ToList();
    
    // Works transparently with Deflate and Transpose+Deflate compression
}

Memory-Efficient Streaming

using (var reader = new Mdf4FileReader("large_measurement.mf4"))
{
    var channelReader = reader.GetChannelReader("Speed");
    
    // Use IEnumerable for streaming without loading all data into memory
    foreach (var value in channelReader.GetValues<double>())
    {
        // Process each value individually
        ProcessValue(value);
    }
}

Supported Data Types

Numeric Types

  • Integer: byte, sbyte, short, ushort, int, uint, long, ulong
  • Floating-point: float, double
  • Byte order: Little-endian (LE) and Big-endian (BE)

String Types

  • Fixed-length strings: SBC/Latin-1, UTF-8, UTF-16 LE/BE
  • Variable-length strings: VLSD (Variable Length Signal Data)

Special Types

  • Byte arrays: Fixed-length binary data
  • CANopen Date: Date and time with day-of-week and DST flag
  • CANopen Time: Time since epoch (Jan 1, 1984)
  • Complex Numbers (MDF 4.2.0): Real and imaginary parts for FFT/signal processing
    • Half precision (16-bit), Single precision (32-bit), Double precision (64-bit)
    • Little-endian and Big-endian byte order
    • Full arithmetic operations (+, -, *, /)
    • ⚠️ Not yet tested with real-world files
  • MIME Sample (MDF 4.2.0): Single embedded binary data with content-type
    • Images (PNG, JPEG, GIF, BMP)
    • Video frames (MPEG, MP4, AVI)
    • Audio samples (WAV, MP3, OGG)
    • JSON/XML data, Text files
    • Custom binary formats
    • ⚠️ Not yet tested with real-world files
  • MIME Stream (MDF 4.2.0): Continuous stream of binary samples
    • Video streams (multiple frames)
    • Audio streams (audio chunks)
    • Any multi-sample binary stream data
    • ⚠️ Not yet tested with real-world files

Master Channels

  • Physical master channels (ChannelType = 2): Time, angle, distance, index
  • Virtual master channels (ChannelType = 3): Calculated from record index

Conversion Support

Linear Conversion

Formula: phys = int * P2 + P1

Rational Conversion

Formula: phys = (P1 * int² + P2 * int + P3) / (P4 * int² + P5 * int + P6)

Algebraic Conversion

Formula: User-defined algebraic expression (e.g., sin(X * 3.14159 / 180))

Lookup Conversions

  • Value-to-Value: Map discrete input values to output values (with optional interpolation)
  • Value-to-Text: Map numeric ranges to text descriptions
  • Text-to-Value: Convert text inputs to numeric values
  • Text-to-Text: Map text inputs to different text outputs

Partial Conversions

Multiple conversion rules with value ranges and default fallback

Feature Implementation Summary

✅ Fully Implemented Features (23 features)

  • ✓ Fixed-length integer types (signed/unsigned, 8/16/32/64-bit, LE/BE)
  • ✓ Floating-point types (32/64-bit, LE/BE)
  • ✓ Fixed-length strings (SBC/Latin-1, UTF-8, UTF-16 LE/BE)
  • ✓ Variable-length strings (VLSD)
  • ✓ Byte arrays
  • ✓ CANopen Date and Time types
  • Complex numbers (MDF 4.2.0) - Real and imaginary parts, half/single/double precision, LE/BE ⚠️ Not yet tested with real files
  • MIME sample (MDF 4.2.0) - Embedded binary data with content-type (images, video, audio, etc.) ⚠️ Not yet tested with real files
  • MIME stream (MDF 4.2.0) - Continuous stream of binary samples ⚠️ Not yet tested with real files
  • Array channels (CABLOCK) - Multi-dimensional arrays (Lookup, Curves, Classification results) with CN template storage
  • Event blocks (EVBLOCK) - Markers, triggers, recording events, acquisition interrupts
  • ✓ Physical master channels (ChannelType = 2)
  • ✓ Virtual master channels (ChannelType = 3)
  • ✓ Linear conversions
  • ✓ Rational conversions
  • ✓ Algebraic conversions
  • ✓ Lookup conversions (value-to-value, value-to-text, text-to-text, text-to-value)
  • ✓ Partial conversions with default rules
  • ✓ Compressed data (Deflate, Transpose+Deflate)
  • ✓ Data lists
  • ✓ MIME synchronization channels
  • ✓ Bit-level signal reading with sign extension
  • ✓ File and channel metadata extraction (including XML comments and custom extensions)
  • ✓ Attachments (embedded, compressed, external references with MD5 validation)

❌ Not Yet Implemented Features (6 features)

  • ✗ CG/DG template array storage (Arrays with independent element records)
  • ✗ Maximum-length signal data (MLSD, ChannelType = 5)
  • ✗ Virtual data channels (ChannelType = 6) - calculated channels
  • ✗ Sample reduction blocks (SRBLOCK) - downsampled data
  • ✗ Non-byte-aligned signal reading (advanced)
  • ✗ Overlapping signals

Test Coverage Statistics

  • Total tests: 262 passing
    • 18 Event tests (EVBLOCK – Marker, Trigger, Recording/AcquisitionInterrupt)
    • 24 Complex Number tests (DataType 15/16)
    • 48 MIME type tests (DataType 11/12)
    • 17 Array channel tests (CABLOCK)
    • 155 existing tests (data types, conversions, features, attachments)
  • Test coverage: ~87%
    • All remaining code focuses on MDF 4.x with comprehensive test coverage
  • MDF 4.x focus: Library now focuses exclusively on MDF 4.x format
  • Array support: CN template storage fully implemented and tested with real MDF4 files from ASAM standard
  • Note: Complex Numbers and MIME types are fully implemented but not yet tested against real-world files

API Reference

Core Classes

Mdf4FileReader

Main entry point for reading MDF4 files.

public class Mdf4FileReader : IDisposable
{
    public Mdf4FileReader(string filePath);
    public IEnumerable<string> GetChannelNames();
    public ChannelReader GetChannelReader(string channelName);
    public IReadOnlyDictionary<string, string> GetFileMetadata();
    public IReadOnlyDictionary<string, string> GetChannelMetadata(string channelName);
    public IEnumerable<AttachmentInfo> GetAttachments();
    public byte[] GetAttachmentData(string fileName);
    public void ExtractAttachment(string fileName, string outputPath);
    // Events (file-level annotations – NOT channel data)
    public IEnumerable<MdfEvent> GetEvents();
    public Task<IReadOnlyList<MdfEvent>> GetEventsAsync(CancellationToken ct = default);
    public void Dispose();
}
AttachmentInfo

Contains information about an attachment in an MDF4 file.

public class AttachmentInfo
{
    public string FileName { get; }
    public string MimeType { get; }
    public long OriginalSize { get; }
    public long EmbeddedSize { get; }
    public bool IsEmbedded { get; }
    public bool IsCompressed { get; }
    public bool HasMd5Checksum { get; }
    public byte[] Md5Checksum { get; }
    public string Comment { get; }
    public int CreatorIndex { get; }
}
MdfEvent

Represents a single event (EVBLOCK) from an MDF4 file. Events are file-level time annotations, not channel data.

public sealed class MdfEvent
{
    public string        Name                      { get; }  // Event name (from TXBLOCK)
    public string?       Comment                   { get; }  // Optional description
    public EventType     EventType                 { get; }  // Marker, Trigger, Recording, ...
    public EventSyncType SyncType                  { get; }  // Seconds, Index, Radians, Meter
    public EventRangeType RangeType                { get; }  // Point, Begin, End
    public EventCause    Cause                     { get; }  // Tool, Script, User, Error, Other
    public long          SyncBaseValue             { get; }  // Raw sync value
    public double        SyncFactor                { get; }  // Scale: physical = SyncBase * SyncFactor
    public TimeSpan?     TimeFromMeasurementStart  { get; }  // Convenience: only set when SyncType==Seconds
    public ushort        CreatorIndex              { get; }  // Index into file history list
    public bool          IsPostProcessing          { get; }  // Created during post-processing
}
ChannelReader

Provides access to channel data and metadata.

public class ChannelReader
{
    public bool IsMasterChannel { get; }
    public bool IsArrayChannel { get; }
    public IEnumerable<T> GetValues<T>();
    public IEnumerable<ArrayData<T>> GetArrayValues<T>();  // For array channels
    public ChannelReader GetMasterChannelReader(Channel_SyncType syncType = Channel_SyncType.Undefined);
    public List<ChannelReader> GetMasterChannels(Channel_SyncType syncType = Channel_SyncType.Undefined);
}
ArrayData<T>

Represents a single array sample with multi-dimensional structure.

public class ArrayData<T>
{
    public T[] Values { get; }           // Flattened array in row-major order
    public int[] Dimensions { get; }     // Size of each dimension
    public int ElementCount { get; }     // Total number of elements
    
    public T GetValueAt(params int[] indices);       // Access element by coordinates
    public Array ToMultiDimensionalArray();           // Convert to System.Array
}
Channel_SyncType

Enumeration for master channel synchronization types.

public enum Channel_SyncType
{
    None = 0,
    Time = 1,
    Angle = 2,
    Distance = 3,
    Index = 4,
    Undefined = 99
}

CANopen Types

CanOpenDate
public class CanOpenDate
{
    public ushort Milliseconds { get; }
    public byte Minutes { get; }
    public byte Hours { get; }
    public bool IsSummerTime { get; }
    public byte Day { get; }
    public byte DayOfWeek { get; }  // 1=Monday, 7=Sunday
    public byte Month { get; }
    public byte Year { get; }        // 0-99, offset from 2000
    
    public DateTime ToDateTime();
    public int GetFullYear();        // Returns full year (e.g., 2024)
}
CanOpenTime
public class CanOpenTime
{
    public uint Milliseconds { get; }
    public ushort Days { get; }
    public static readonly DateTime Epoch;  // January 1, 1984
    
    public DateTime ToDateTime();
    public TimeSpan ToTimeSpan();
}

Complex Numbers and MIME Types (MDF 4.2.0)

ComplexNumber
public class ComplexNumber
{
    public double Real { get; set; }
    public double Imaginary { get; set; }
    public double Magnitude { get; }        // sqrt(Real² + Imaginary²)
    public double Phase { get; }            // atan2(Imaginary, Real) in radians
    public double PhaseDegrees { get; }     // Phase in degrees
    public ComplexNumber Conjugate { get; } // Real - Imaginary*i
    
    public ComplexNumber(double real, double imaginary);
    public string ToString();               // "3 + 4i" format
    public string ToPolarString();          // "5.000000∠53.13°" format
    
    // Operators: +, -, *, /
}
MimeSample
public class MimeSample
{
    public string ContentType { get; set; }  // e.g., "image/png", "video/mpeg"
    public byte[] Data { get; set; }
    public int Size { get; }                 // Data.Length
    
    public MimeSample(string contentType, byte[] data);
    public void SaveToFile(string filePath);
}
MimeStream
public class MimeStream
{
    public string ContentType { get; set; }
    public List<byte[]> Samples { get; set; }
    public int SampleCount { get; }          // Number of samples
    public long TotalSize { get; }           // Total bytes across all samples
    
    public MimeStream(string contentType);
    public MimeStream(string contentType, IEnumerable<byte[]> samples);
    public void AddSample(byte[] sample);
    public byte[] GetConcatenatedData();     // Concatenate all samples
    public void SaveToFile(string filePath);
}

Requirements

  • .NET Standard 2.1 or higher
  • NuGet packages:
    • NCalc (for algebraic conversions)
    • System.IO.Compression (for compressed data)

Changelog

Version 0.4.0 (Released - March 11, 2026)

  • ✅ MLSD (maximum-length signal data) support
  • ✅ Virtual data channels (ChannelType = 6)
  • ✅ Event block support (EVBLOCK) – GetEvents() / GetEventsAsync()
  • ✅ Unsorted data
  • Improved: 280 passing tests

Version 0.3.1-beta (Released - February 5, 2026)

  • Displays formatted message informing users about beta limitations (50 MB file size limit)

Version 0.3.0 (Released - January 22, 2026)

  • New: Array channel support (CABLOCK) - CN template storage fully implemented
    • Lookup tables (2D calibration maps)
    • Curves (1D function tables)
    • Classification results
    • Signal matrices and multi-dimensional arrays
  • Fixed: Vector MDF4 array channel reading (byte padding for BitConverter operations)
  • Improved: 217 passing tests (was 200)
  • ✅ Support for basic data types (integer, float, string, byte array)
  • ✅ All standard conversion types
  • ✅ Compressed data support
  • ✅ CANopen Date and Time types
  • ✅ Physical and virtual master channels
  • ✅ VLSD (variable-length string data)
  • ✅ File and channel metadata extraction
  • ✅ Attachment extraction (embedded, compressed, external references)
  • ✅ Complex numbers (MDF 4.2.0)
  • ✅ MIME sample and stream (MDF 4.2.0)
  • ⚠️ Note: Complex numbers and MIME types not yet tested against real-world files

Version 0.2.0

  • ✅ Complex numbers support (MDF 4.2.0) - DataType 15/16
  • ✅ MIME sample support (MDF 4.2.0) - DataType 11
  • ✅ MIME stream support (MDF 4.2.0) - DataType 12
  • ✅ 237 passing tests

Version 0.1.0

  • ✅ Initial release
  • ✅ Support for basic data types (integer, float, string, byte array)
  • ✅ All standard conversion types
  • ✅ Compressed data support
  • ✅ CANopen Date and Time types
  • ✅ Physical and virtual master channels
  • ✅ VLSD (variable-length string data)
  • ✅ File and channel metadata extraction
  • ✅ Attachment extraction (embedded, compressed, external references)
  • ✅ 126 passing tests

Last updated: March 11, 2026

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0 326 3/13/2026
0.3.3-beta 138 2/18/2026
0.3.2-beta 143 2/18/2026
0.3.1-beta 154 2/5/2026
0.3.0-beta 149 1/27/2026