Real initial commit

This commit is contained in:
Alexander Luzgarev 2017-11-15 20:08:24 +01:00
parent c9435cd0cb
commit 856ae49f20
58 changed files with 4715 additions and 0 deletions

9
LICENSE.md Executable file
View File

@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright 2017 Alexander Luzgarev
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,68 @@
// Copyright 2017 Alexander Luzgarev
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace MatFileHandler.Tests
{
/// <summary>
/// Abstract factory of test data.
/// </summary>
/// <typeparam name="TTestData">Type of test data.</typeparam>
public abstract class AbstractTestDataFactory<TTestData>
{
/// <summary>
/// Initializes a new instance of the <see cref="AbstractTestDataFactory{TTestData}"/> class.
/// </summary>
/// <param name="dataDirectory">Directory with test files.</param>
/// <param name="testFilenameConvention">A convention used to filter test files.</param>
protected AbstractTestDataFactory(string dataDirectory, ITestFilenameConvention testFilenameConvention)
{
DataDirectory = dataDirectory;
TestFilenameConvention = testFilenameConvention;
}
private string DataDirectory { get; }
private ITestFilenameConvention TestFilenameConvention { get; }
/// <summary>
/// Get test data set by name.
/// </summary>
/// <param name="dataSet">Name of the data set.</param>
/// <returns>Test data.</returns>
public TTestData this[string dataSet] =>
ReadTestData(FixPath(TestFilenameConvention.ConvertTestNameToFilename(dataSet)));
/// <summary>
/// Get a sequence of all test data sets in the factory.
/// </summary>
/// <returns>A sequence of data sets.</returns>
public IEnumerable<TTestData> GetAllTestData()
{
var files = Directory.EnumerateFiles(DataDirectory).Where(TestFilenameConvention.FilterFile);
foreach (var filename in files)
{
yield return ReadTestData(filename);
}
}
/// <summary>
/// Read test data from a stream.
/// </summary>
/// <param name="stream">Input stream.</param>
/// <returns>Test data.</returns>
protected abstract TTestData ReadDataFromStream(Stream stream);
private string FixPath(string filename) => Path.Combine(DataDirectory, filename);
private TTestData ReadTestData(string filename)
{
using (var stream = new FileStream(filename, FileMode.Open))
{
return ReadDataFromStream(stream);
}
}
}
}

View File

@ -0,0 +1,123 @@
// Copyright 2017 Alexander Luzgarev
using System.Collections.Generic;
using System.Numerics;
using NUnit.Framework;
namespace MatFileHandler.Tests
{
/// <summary>
/// Tests of Matlab array manipulation.
/// </summary>
[TestFixture]
public class ArrayHandlingTests
{
private DataBuilder Builder { get; set; }
/// <summary>
/// Set up a DataBuilder.
/// </summary>
[SetUp]
public void Setup()
{
Builder = new DataBuilder();
}
/// <summary>
/// Test numerical array creation.
/// </summary>
[Test]
public void TestCreate()
{
TestCreateArrayOf<sbyte>();
TestCreateArrayOf<ComplexOf<sbyte>>();
TestCreateArrayOf<byte>();
TestCreateArrayOf<ComplexOf<byte>>();
TestCreateArrayOf<short>();
TestCreateArrayOf<ComplexOf<short>>();
TestCreateArrayOf<ushort>();
TestCreateArrayOf<ComplexOf<ushort>>();
TestCreateArrayOf<int>();
TestCreateArrayOf<ComplexOf<int>>();
TestCreateArrayOf<uint>();
TestCreateArrayOf<ComplexOf<uint>>();
TestCreateArrayOf<long>();
TestCreateArrayOf<ComplexOf<long>>();
TestCreateArrayOf<ulong>();
TestCreateArrayOf<ComplexOf<ulong>>();
TestCreateArrayOf<float>();
TestCreateArrayOf<ComplexOf<float>>();
TestCreateArrayOf<double>();
TestCreateArrayOf<Complex>();
}
/// <summary>
/// Test numerical array manipulation.
/// </summary>
[Test]
public void TestNumArray()
{
var array = Builder.NewArray<int>(2, 3);
array[0, 1] = 2;
Assert.That(array[0, 1], Is.EqualTo(2));
}
/// <summary>
/// Test cell array manipulation.
/// </summary>
[Test]
public void TestCellArray()
{
var array = Builder.NewCellArray(2, 3);
Assert.That(array.Dimensions, Is.EqualTo(new[] { 2, 3 }));
array[0, 1] = Builder.NewArray<int>(1, 2);
Assert.That(array[1, 2].IsEmpty, Is.True);
Assert.That(array[0, 1].IsEmpty, Is.False);
var assigned = (IArrayOf<int>)array[0, 1];
Assert.That(assigned, Is.Not.Null);
Assert.That(assigned.Dimensions, Is.EqualTo(new[] { 1, 2 }));
}
/// <summary>
/// Test structure array manipulation.
/// </summary>
[Test]
public void TestStructureArray()
{
var array = Builder.NewStructureArray(new[] { "x", "y" }, 2, 3);
array["x", 0, 1] = Builder.NewArray<int>(1, 2);
Assert.That(array["y", 0, 1].IsEmpty, Is.True);
Assert.That(array["x", 0, 1].IsEmpty, Is.False);
var assigned = (IArrayOf<int>)array["x", 0, 1];
Assert.That(assigned, Is.Not.Null);
Assert.That(assigned.Dimensions, Is.EqualTo(new[] { 1, 2 }));
}
/// <summary>
/// Test character array manipulation.
/// </summary>
[Test]
public void TestString()
{
var array = Builder.NewCharArray("🍆");
Assert.That(array.Dimensions, Is.EqualTo(new[] { 1, 2 }));
}
/// <summary>
/// Test file creation.
/// </summary>
[Test]
public void TestFile()
{
var file = Builder.NewFile(new List<IVariable>());
Assert.That(file, Is.Not.Null);
}
private static void TestCreateArrayOf<T>()
where T : struct
{
var array = new DataBuilder().NewArray<T>(2, 3);
Assert.That(array, Is.Not.Null);
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler.Tests
{
/// <summary>
/// Data used in reading/writing tests.
/// </summary>
public static class CommonData
{
/// <summary>
/// Limits of Int8.
/// </summary>
public static readonly sbyte[] Int8Limits = { -128, 127 };
/// <summary>
/// Limits of UInt8.
/// </summary>
public static readonly byte[] UInt8Limits = { 0, 255 };
/// <summary>
/// Limits of Int16.
/// </summary>
public static readonly short[] Int16Limits = { -32768, 32767 };
/// <summary>
/// Limits of UInt16.
/// </summary>
public static readonly ushort[] UInt16Limits = { 0, 65535 };
/// <summary>
/// Limits of Int32.
/// </summary>
public static readonly int[] Int32Limits = { -2147483648, 2147483647 };
/// <summary>
/// Limits of UInt32.
/// </summary>
public static readonly uint[] UInt32Limits = { 0U, 4294967295U };
/// <summary>
/// Limits of Int64.
/// </summary>
public static readonly long[] Int64Limits = { -9223372036854775808L, 9223372036854775807L };
/// <summary>
/// Limits of UInt64.
/// </summary>
public static readonly ulong[] UInt64Limits = { 0UL, 18446744073709551615UL };
}
}

View File

@ -0,0 +1,51 @@
// Copyright 2017 Alexander Luzgarev
using NUnit.Framework;
namespace MatFileHandler.Tests
{
/// <summary>
/// Tests of the ComplexOf value type.
/// </summary>
[TestFixture]
public class ComplexOfTests
{
/// <summary>
/// Test getting real and imaginary parts.
/// </summary>
[Test]
public void TestAccessors()
{
var c = new ComplexOf<byte>(1, 2);
Assert.That(c.Real, Is.EqualTo(1));
Assert.That(c.Imaginary, Is.EqualTo(2));
}
/// <summary>
/// Test equality operators.
/// </summary>
[Test]
public void TestEquals()
{
var c1 = new ComplexOf<byte>(1, 2);
var c2 = new ComplexOf<byte>(3, 4);
var c3 = new ComplexOf<byte>(1, 2);
Assert.That(c1 == c3, Is.True);
Assert.That(c1 != c2, Is.True);
Assert.That(c2 != c3, Is.True);
}
/// <summary>
/// Test hash code calculation.
/// </summary>
[Test]
public void TestGetHashCode()
{
var c1 = new ComplexOf<byte>(1, 2);
var c2 = new ComplexOf<byte>(1, 2);
var h1 = c1.GetHashCode();
var h2 = c2.GetHashCode();
Assert.That(h1, Is.EqualTo(h2));
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright 2017 Alexander Luzgarev
using System.IO;
namespace MatFileHandler.Tests
{
/// <summary>
/// A filename convention based on file extensions.
/// </summary>
internal class ExtensionTestFilenameConvention : ITestFilenameConvention
{
/// <summary>
/// Initializes a new instance of the <see cref="ExtensionTestFilenameConvention"/> class.
/// </summary>
/// <param name="extension">File extension.</param>
public ExtensionTestFilenameConvention(string extension)
{
Extension = extension;
}
private string Extension { get; }
/// <summary>
/// Convert test name to filename by adding the extension.
/// </summary>
/// <param name="testName">Test name.</param>
/// <returns>The corresponding filename.</returns>
public string ConvertTestNameToFilename(string testName)
{
return Path.ChangeExtension(testName, Extension);
}
/// <summary>
/// Compare file's extension to the one specified during initialization.
/// </summary>
/// <param name="filename">Filename.</param>
/// <returns>True iff the file has the extension stored in the class.</returns>
public bool FilterFile(string filename)
{
return Path.GetExtension(filename) == "." + Extension;
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler.Tests
{
/// <summary>
/// Interface for handling filtering tests based on filenames.
/// </summary>
public interface ITestFilenameConvention
{
/// <summary>
/// Convert test name to a filename (e.g., by adding an appropriate extension).
/// </summary>
/// <param name="testName">Name of a test.</param>
/// <returns>Filename.</returns>
string ConvertTestNameToFilename(string testName);
/// <summary>
/// Decide if a file contains a test based on the filename.
/// </summary>
/// <param name="filename">A filename.</param>
/// <returns>True iff the file should contain a test.</returns>
bool FilterFile(string filename);
}
}

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\Debug\netcoreapp2.0\MatFileHandler.Tests.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="NUnit" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MatFileHandler\MatFileHandler.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="test-data\**\*.mat" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,318 @@
// Copyright 2017 Alexander Luzgarev
using System.IO;
using System.Linq;
using System.Numerics;
using NUnit.Framework;
namespace MatFileHandler.Tests
{
/// <summary>
/// Tests of file reading API.
/// </summary>
[TestFixture]
public class MatFileReaderTests
{
private const string TestDirectory = "test-data";
/// <summary>
/// Test reading all files in a given test set.
/// </summary>
/// <param name="testSet">Name of the set.</param>
[TestCase("good")]
public void TestReader(string testSet)
{
foreach (var matFile in GetTests(testSet).GetAllTestData())
{
Assert.That(matFile.Variables, Is.Not.Empty);
}
}
/// <summary>
/// Test reading lower and upper limits of integer data types.
/// </summary>
[Test]
public void TestLimits()
{
var matFile = GetTests("good")["limits"];
IArray array;
array = matFile["int8_"].Value;
CheckLimits(array as IArrayOf<sbyte>, CommonData.Int8Limits);
Assert.That(array.ConvertToDoubleArray(), Is.EqualTo(new[] { -128.0, 127.0 }));
array = matFile["uint8_"].Value;
CheckLimits(array as IArrayOf<byte>, CommonData.UInt8Limits);
array = matFile["int16_"].Value;
CheckLimits(array as IArrayOf<short>, CommonData.Int16Limits);
array = matFile["uint16_"].Value;
CheckLimits(array as IArrayOf<ushort>, CommonData.UInt16Limits);
array = matFile["int32_"].Value;
CheckLimits(array as IArrayOf<int>, CommonData.Int32Limits);
array = matFile["uint32_"].Value;
CheckLimits(array as IArrayOf<uint>, CommonData.UInt32Limits);
array = matFile["int64_"].Value;
CheckLimits(array as IArrayOf<long>, CommonData.Int64Limits);
array = matFile["uint64_"].Value;
CheckLimits(array as IArrayOf<ulong>, CommonData.UInt64Limits);
}
/// <summary>
/// Test writing lower and upper limits of integer-based complex data types.
/// </summary>
[Test]
public void TestComplexLimits()
{
var matFile = GetTests("good")["limits_complex"];
IArray array;
array = matFile["int8_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<sbyte>>, CommonData.Int8Limits);
Assert.That(
array.ConvertToComplexArray(),
Is.EqualTo(new[] { -128.0 + (127.0 * Complex.ImaginaryOne), 127.0 - (128.0 * Complex.ImaginaryOne) }));
array = matFile["uint8_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<byte>>, CommonData.UInt8Limits);
array = matFile["int16_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<short>>, CommonData.Int16Limits);
array = matFile["uint16_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<ushort>>, CommonData.UInt16Limits);
array = matFile["int32_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<int>>, CommonData.Int32Limits);
array = matFile["uint32_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<uint>>, CommonData.UInt32Limits);
array = matFile["int64_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<long>>, CommonData.Int64Limits);
array = matFile["uint64_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<ulong>>, CommonData.UInt64Limits);
}
/// <summary>
/// Test reading an ASCII-encoded string.
/// </summary>
[Test]
public void TestAscii()
{
var matFile = GetTests("good")["ascii"];
var arrayAscii = matFile["s"].Value as ICharArray;
Assert.That(arrayAscii, Is.Not.Null);
Assert.That(arrayAscii.Dimensions, Is.EqualTo(new[] { 1, 3 }));
Assert.That(arrayAscii.String, Is.EqualTo("abc"));
Assert.That(arrayAscii[2], Is.EqualTo('c'));
}
/// <summary>
/// Test reading a Unicode string.
/// </summary>
[Test]
public void TestUnicode()
{
var matFile = GetTests("good")["unicode"];
var arrayUnicode = matFile["s"].Value as ICharArray;
Assert.That(arrayUnicode, Is.Not.Null);
Assert.That(arrayUnicode.Dimensions, Is.EqualTo(new[] { 1, 2 }));
Assert.That(arrayUnicode.String, Is.EqualTo("必フ"));
Assert.That(arrayUnicode[0], Is.EqualTo('必'));
Assert.That(arrayUnicode[1], Is.EqualTo('フ'));
}
/// <summary>
/// Test reading a wide Unicode string.
/// </summary>
[Test]
public void TestUnicodeWide()
{
var matFile = GetTests("good")["unicode-wide"];
var arrayUnicodeWide = matFile["s"].Value as ICharArray;
Assert.That(arrayUnicodeWide, Is.Not.Null);
Assert.That(arrayUnicodeWide.Dimensions, Is.EqualTo(new[] { 1, 2 }));
Assert.That(arrayUnicodeWide.String, Is.EqualTo("🍆"));
}
/// <summary>
/// Test converting a structure array to a Double array.
/// </summary>
/// <returns>Should return null.</returns>
[Test(ExpectedResult = null)]
public double[] TestConvertToDoubleArray()
{
var matFile = GetTests("good")["struct"];
var array = matFile.Variables[0].Value;
return array.ConvertToDoubleArray();
}
/// <summary>
/// Test converting a structure array to a Complex array.
/// </summary>
/// <returns>Should return null.</returns>
[Test(ExpectedResult = null)]
public Complex[] TestConvertToComplexArray()
{
var matFile = GetTests("good")["struct"];
var array = matFile.Variables[0].Value;
return array.ConvertToComplexArray();
}
/// <summary>
/// Test reading a structure array.
/// </summary>
[Test]
public void TestStruct()
{
var matFile = GetTests("good")["struct"];
var structure = matFile["struct_"].Value as IStructureArray;
Assert.That(structure, Is.Not.Null);
Assert.That(structure.FieldNames, Is.EquivalentTo(new[] { "x", "y" }));
var element = structure[0, 0];
Assert.That(element.ContainsKey("x"), Is.True);
Assert.That(element.Count, Is.EqualTo(2));
Assert.That(element.TryGetValue("x", out var _), Is.True);
Assert.That(element.TryGetValue("z", out var _), Is.False);
Assert.That(element.Keys, Has.Exactly(2).Items);
Assert.That(element.Values, Has.Exactly(2).Items);
var keys = element.Select(pair => pair.Key);
Assert.That(keys, Is.EquivalentTo(new[] { "x", "y" }));
Assert.That((element["x"] as IArrayOf<double>)?[0], Is.EqualTo(12.345));
Assert.That((structure["x", 0, 0] as IArrayOf<double>)?[0], Is.EqualTo(12.345));
Assert.That((structure["y", 0, 0] as ICharArray)?.String, Is.EqualTo("abc"));
Assert.That((structure["x", 1, 0] as ICharArray)?.String, Is.EqualTo("xyz"));
Assert.That(structure["y", 1, 0].IsEmpty, Is.True);
Assert.That((structure["x", 0, 1] as IArrayOf<double>)?[0], Is.EqualTo(2.0));
Assert.That((structure["y", 0, 1] as IArrayOf<double>)?[0], Is.EqualTo(13.0));
Assert.That(structure["x", 1, 1].IsEmpty, Is.True);
Assert.That((structure["y", 1, 1] as ICharArray)?[0, 0], Is.EqualTo('a'));
Assert.That(((structure["x", 0, 2] as ICellArray)?[0] as ICharArray)?.String, Is.EqualTo("x"));
Assert.That(((structure["x", 0, 2] as ICellArray)?[1] as ICharArray)?.String, Is.EqualTo("yz"));
Assert.That((structure["y", 0, 2] as IArrayOf<double>)?.Dimensions, Is.EqualTo(new[] { 2, 3 }));
Assert.That((structure["y", 0, 2] as IArrayOf<double>)?[0, 2], Is.EqualTo(3.0));
Assert.That((structure["x", 1, 2] as IArrayOf<float>)?[0], Is.EqualTo(1.5f));
Assert.That(structure["y", 1, 2].IsEmpty, Is.True);
}
/// <summary>
/// Test reading a sparse array.
/// </summary>
[Test]
public void TestSparse()
{
var matFile = GetTests("good")["sparse"];
var sparseArray = matFile["sparse_"].Value as ISparseArrayOf<double>;
Assert.That(sparseArray, Is.Not.Null);
Assert.That(sparseArray.Dimensions, Is.EqualTo(new[] { 4, 5 }));
Assert.That(sparseArray.Data[(1, 1)], Is.EqualTo(1.0));
Assert.That(sparseArray[1, 1], Is.EqualTo(1.0));
Assert.That(sparseArray[1, 2], Is.EqualTo(2.0));
Assert.That(sparseArray[2, 1], Is.EqualTo(3.0));
Assert.That(sparseArray[2, 3], Is.EqualTo(4.0));
Assert.That(sparseArray[0, 4], Is.EqualTo(0.0));
Assert.That(sparseArray[3, 0], Is.EqualTo(0.0));
Assert.That(sparseArray[3, 4], Is.EqualTo(0.0));
}
/// <summary>
/// Test reading a logical array.
/// </summary>
[Test]
public void TestLogical()
{
var matFile = GetTests("good")["logical"];
var array = matFile["logical_"].Value;
var logicalArray = array as IArrayOf<bool>;
Assert.That(logicalArray, Is.Not.Null);
Assert.That(logicalArray[0, 0], Is.True);
Assert.That(logicalArray[0, 1], Is.True);
Assert.That(logicalArray[0, 2], Is.False);
Assert.That(logicalArray[1, 0], Is.False);
Assert.That(logicalArray[1, 1], Is.True);
Assert.That(logicalArray[1, 2], Is.True);
}
/// <summary>
/// Test reading a sparse logical array.
/// </summary>
[Test]
public void TestSparseLogical()
{
var matFile = GetTests("good")["sparse_logical"];
var array = matFile["sparse_logical"].Value;
var sparseArray = array as ISparseArrayOf<bool>;
Assert.That(sparseArray, Is.Not.Null);
Assert.That(sparseArray.Data[(0, 0)], Is.True);
Assert.That(sparseArray[0, 0], Is.True);
Assert.That(sparseArray[0, 1], Is.True);
Assert.That(sparseArray[0, 2], Is.False);
Assert.That(sparseArray[1, 0], Is.False);
Assert.That(sparseArray[1, 1], Is.True);
Assert.That(sparseArray[1, 2], Is.True);
}
/// <summary>
/// Test reading a global variable.
/// </summary>
[Test]
public void TestGlobal()
{
var matFile = GetTests("good")["global"];
var variable = matFile.Variables.First();
Assert.That(variable.IsGlobal, Is.True);
}
/// <summary>
/// Test reading a sparse complex array.
/// </summary>
[Test]
public void TextSparseComplex()
{
var matFile = GetTests("good")["sparse_complex"];
var array = matFile["sparse_complex"].Value;
var sparseArray = array as ISparseArrayOf<Complex>;
Assert.That(sparseArray, Is.Not.Null);
Assert.That(sparseArray[0, 0], Is.EqualTo(-1.5 + (2.5 * Complex.ImaginaryOne)));
Assert.That(sparseArray[1, 0], Is.EqualTo(2 - (3 * Complex.ImaginaryOne)));
Assert.That(sparseArray[0, 1], Is.EqualTo(Complex.Zero));
Assert.That(sparseArray[1, 1], Is.EqualTo(0.5 + (1.0 * Complex.ImaginaryOne)));
}
/// <summary>
/// Test reading an object.
/// </summary>
[Test]
public void TestObject()
{
Assert.That(() => GetTests("bad")["object"], Throws.TypeOf<HandlerException>());
}
private static AbstractTestDataFactory<IMatFile> GetTests(string factoryName) =>
new MatTestDataFactory(Path.Combine(TestDirectory, factoryName));
private static void CheckLimits<T>(IArrayOf<T> array, T[] limits)
where T : struct
{
Assert.That(array, Is.Not.Null);
Assert.That(array.Dimensions, Is.EqualTo(new[] { 1, 2 }));
Assert.That(array.Data, Is.EqualTo(limits));
}
private static void CheckComplexLimits<T>(IArrayOf<ComplexOf<T>> array, T[] limits)
where T : struct
{
Assert.That(array, Is.Not.Null);
Assert.That(array.Dimensions, Is.EqualTo(new[] { 1, 2 }));
Assert.That(array[0], Is.EqualTo(new ComplexOf<T>(limits[0], limits[1])));
Assert.That(array[1], Is.EqualTo(new ComplexOf<T>(limits[1], limits[0])));
}
}
}

View File

@ -0,0 +1,403 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.IO;
using System.Numerics;
using NUnit.Framework;
namespace MatFileHandler.Tests
{
/// <summary>
/// Tests of file writing API.
/// </summary>
[TestFixture]
public class MatFileWriterTests
{
private const string TestDirectory = "test-data";
/// <summary>
/// Test writing a simple Double array.
/// </summary>
[Test]
public void TestWrite()
{
var builder = new DataBuilder();
var array = builder.NewArray<double>(1, 2);
array[0] = -13.5;
array[1] = 17.0;
var variable = builder.NewVariable("test", array);
var actual = builder.NewFile(new[] { variable });
MatCompareWithTestData("good", "double-array", actual);
}
/// <summary>
/// Test writing a large file.
/// </summary>
[Test]
public void TestHuge()
{
var builder = new DataBuilder();
var array = builder.NewArray<double>(1000, 10000);
array[0] = -13.5;
array[1] = 17.0;
var variable = builder.NewVariable("test", array);
var matFile = builder.NewFile(new[] { variable });
using (var stream = new MemoryStream())
{
var writer = new MatFileWriter(stream);
writer.Write(matFile);
}
}
/// <summary>
/// Test writing lower and upper limits of integer data types.
/// </summary>
[Test]
public void TestLimits()
{
var builder = new DataBuilder();
var int8 = builder.NewVariable("int8_", builder.NewArray(CommonData.Int8Limits, 1, 2));
var uint8 = builder.NewVariable("uint8_", builder.NewArray(CommonData.UInt8Limits, 1, 2));
var int16 = builder.NewVariable("int16_", builder.NewArray(CommonData.Int16Limits, 1, 2));
var uint16 = builder.NewVariable("uint16_", builder.NewArray(CommonData.UInt16Limits, 1, 2));
var int32 = builder.NewVariable("int32_", builder.NewArray(CommonData.Int32Limits, 1, 2));
var uint32 = builder.NewVariable("uint32_", builder.NewArray(CommonData.UInt32Limits, 1, 2));
var int64 = builder.NewVariable("int64_", builder.NewArray(CommonData.Int64Limits, 1, 2));
var uint64 = builder.NewVariable("uint64_", builder.NewArray(CommonData.UInt64Limits, 1, 2));
var actual = builder.NewFile(new[] { int16, int32, int64, int8, uint16, uint32, uint64, uint8 });
MatCompareWithTestData("good", "limits", actual);
}
/// <summary>
/// Test writing lower and upper limits of integer-based complex data types.
/// </summary>
[Test]
public void TestLimitsComplex()
{
var builder = new DataBuilder();
var int8Complex = builder.NewVariable(
"int8_complex",
builder.NewArray(CreateComplexLimits(CommonData.Int8Limits), 1, 2));
var uint8Complex = builder.NewVariable(
"uint8_complex",
builder.NewArray(CreateComplexLimits(CommonData.UInt8Limits), 1, 2));
var int16Complex = builder.NewVariable(
"int16_complex",
builder.NewArray(CreateComplexLimits(CommonData.Int16Limits), 1, 2));
var uint16Complex = builder.NewVariable(
"uint16_complex",
builder.NewArray(CreateComplexLimits(CommonData.UInt16Limits), 1, 2));
var int32Complex = builder.NewVariable(
"int32_complex",
builder.NewArray(CreateComplexLimits(CommonData.Int32Limits), 1, 2));
var uint32Complex = builder.NewVariable(
"uint32_complex",
builder.NewArray(CreateComplexLimits(CommonData.UInt32Limits), 1, 2));
var int64Complex = builder.NewVariable(
"int64_complex",
builder.NewArray(CreateComplexLimits(CommonData.Int64Limits), 1, 2));
var uint64Complex = builder.NewVariable(
"uint64_complex",
builder.NewArray(CreateComplexLimits(CommonData.UInt64Limits), 1, 2));
var actual = builder.NewFile(new[]
{
int16Complex, int32Complex, int64Complex, int8Complex,
uint16Complex, uint32Complex, uint64Complex, uint8Complex,
});
MatCompareWithTestData("good", "limits_complex", actual);
}
/// <summary>
/// Test writing a wide-Unicode symbol.
/// </summary>
[Test]
public void TestUnicodeWide()
{
var builder = new DataBuilder();
var s = builder.NewVariable("s", builder.NewCharArray("🍆"));
var actual = builder.NewFile(new[] { s });
MatCompareWithTestData("good", "unicode-wide", actual);
}
/// <summary>
/// Test writing a sparse array.
/// </summary>
[Test]
public void TestSparseArray()
{
var builder = new DataBuilder();
var sparseArray = builder.NewSparseArray<double>(4, 5);
sparseArray[1, 1] = 1;
sparseArray[1, 2] = 2;
sparseArray[2, 1] = 3;
sparseArray[2, 3] = 4;
var sparse = builder.NewVariable("sparse_", sparseArray);
var actual = builder.NewFile(new[] { sparse });
MatCompareWithTestData("good", "sparse", actual);
}
/// <summary>
/// Test writing a structure array.
/// </summary>
[Test]
public void TestStructure()
{
var builder = new DataBuilder();
var structure = builder.NewStructureArray(new[] { "x", "y" }, 2, 3);
structure["x", 0, 0] = builder.NewArray(new[] { 12.345 }, 1, 1);
structure["y", 0, 0] = builder.NewCharArray("abc");
structure["x", 1, 0] = builder.NewCharArray("xyz");
structure["y", 1, 0] = builder.NewEmpty();
structure["x", 0, 1] = builder.NewArray(new[] { 2.0 }, 1, 1);
structure["y", 0, 1] = builder.NewArray(new[] { 13.0 }, 1, 1);
structure["x", 1, 1] = builder.NewEmpty();
structure["y", 1, 1] = builder.NewCharArray("acbd", 2, 2);
var cellArray = builder.NewCellArray(1, 2);
cellArray[0] = builder.NewCharArray("x");
cellArray[1] = builder.NewCharArray("yz");
structure["x", 0, 2] = cellArray;
structure["y", 0, 2] = builder.NewArray(new[] { 1.0, 4.0, 2.0, 5.0, 3.0, 6.0 }, 2, 3);
structure["x", 1, 2] = builder.NewArray(new[] { 1.5f }, 1, 1);
structure["y", 1, 2] = builder.NewEmpty();
var struct_ = builder.NewVariable("struct_", structure);
var actual = builder.NewFile(new[] { struct_ });
MatCompareWithTestData("good", "struct", actual);
}
/// <summary>
/// Test writing a logical array.
/// </summary>
[Test]
public void TestLogical()
{
var builder = new DataBuilder();
var logical = builder.NewArray(new[] { true, false, true, true, false, true }, 2, 3);
var logicalVariable = builder.NewVariable("logical_", logical);
var actual = builder.NewFile(new[] { logicalVariable });
MatCompareWithTestData("good", "logical", actual);
}
/// <summary>
/// Test writing a sparse logical array.
/// </summary>
[Test]
public void TestSparseLogical()
{
var builder = new DataBuilder();
var array = builder.NewSparseArray<bool>(2, 3);
array[0, 0] = true;
array[0, 1] = true;
array[1, 1] = true;
array[1, 2] = true;
var sparseLogical = builder.NewVariable("sparse_logical", array);
var actual = builder.NewFile(new[] { sparseLogical });
MatCompareWithTestData("good", "sparse_logical", actual);
}
/// <summary>
/// Test writing a sparse complex array.
/// </summary>
[Test]
public void TestSparseComplex()
{
var builder = new DataBuilder();
var array = builder.NewSparseArray<Complex>(2, 2);
array[0, 0] = -1.5 + (2.5 * Complex.ImaginaryOne);
array[1, 0] = 2 - (3 * Complex.ImaginaryOne);
array[1, 1] = 0.5 + Complex.ImaginaryOne;
var sparseComplex = builder.NewVariable("sparse_complex", array);
var actual = builder.NewFile(new[] { sparseComplex });
MatCompareWithTestData("good", "sparse_complex", actual);
}
/// <summary>
/// Test writing a global variable.
/// </summary>
[Test]
public void TestGlobal()
{
var builder = new DataBuilder();
var array = builder.NewArray(new double[] { 1, 3, 5 }, 1, 3);
var global = builder.NewVariable("global_", array, true);
var actual = builder.NewFile(new[] { global });
MatCompareWithTestData("good", "global", actual);
}
private static AbstractTestDataFactory<IMatFile> GetMatTestData(string factoryName) =>
new MatTestDataFactory(Path.Combine(TestDirectory, factoryName));
private void CompareSparseArrays<T>(ISparseArrayOf<T> expected, ISparseArrayOf<T> actual)
where T : struct
{
Assert.That(actual, Is.Not.Null);
Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
Assert.That(expected.Data, Is.EquivalentTo(actual.Data));
}
private void CompareStructureArrays(IStructureArray expected, IStructureArray actual)
{
Assert.That(actual, Is.Not.Null);
Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
Assert.That(expected.FieldNames, Is.EquivalentTo(actual.FieldNames));
foreach (var name in expected.FieldNames)
{
for (var i = 0; i < expected.NumberOfElements; i++)
{
CompareMatArrays(expected[name, i], actual[name, i]);
}
}
}
private void CompareCellArrays(ICellArray expected, ICellArray actual)
{
Assert.That(actual, Is.Not.Null);
Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
for (var i = 0; i < expected.NumberOfElements; i++)
{
CompareMatArrays(expected[i], actual[i]);
}
}
private void CompareNumericalArrays<T>(IArrayOf<T> expected, IArrayOf<T> actual)
{
Assert.That(actual, Is.Not.Null);
Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
Assert.That(expected.Data, Is.EqualTo(actual.Data));
}
private void CompareCharArrays(ICharArray expected, ICharArray actual)
{
Assert.That(actual, Is.Not.Null);
Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
Assert.That(expected.String, Is.EqualTo(actual.String));
}
private void CompareMatArrays(IArray expected, IArray actual)
{
switch (expected)
{
case ISparseArrayOf<double> expectedSparseArrayOfDouble:
CompareSparseArrays(expectedSparseArrayOfDouble, actual as ISparseArrayOf<double>);
return;
case ISparseArrayOf<Complex> expectedSparseArrayOfComplex:
CompareSparseArrays(expectedSparseArrayOfComplex, actual as ISparseArrayOf<Complex>);
return;
case IStructureArray expectedStructureArray:
CompareStructureArrays(expectedStructureArray, actual as IStructureArray);
return;
case ICharArray expectedCharArray:
CompareCharArrays(expectedCharArray, actual as ICharArray);
return;
case IArrayOf<byte> byteArray:
CompareNumericalArrays(byteArray, actual as IArrayOf<byte>);
return;
case IArrayOf<sbyte> sbyteArray:
CompareNumericalArrays(sbyteArray, actual as IArrayOf<sbyte>);
return;
case IArrayOf<short> shortArray:
CompareNumericalArrays(shortArray, actual as IArrayOf<short>);
return;
case IArrayOf<ushort> ushortArray:
CompareNumericalArrays(ushortArray, actual as IArrayOf<ushort>);
return;
case IArrayOf<int> intArray:
CompareNumericalArrays(intArray, actual as IArrayOf<int>);
return;
case IArrayOf<uint> uintArray:
CompareNumericalArrays(uintArray, actual as IArrayOf<uint>);
return;
case IArrayOf<long> longArray:
CompareNumericalArrays(longArray, actual as IArrayOf<long>);
return;
case IArrayOf<ulong> ulongArray:
CompareNumericalArrays(ulongArray, actual as IArrayOf<ulong>);
return;
case IArrayOf<float> floatArray:
CompareNumericalArrays(floatArray, actual as IArrayOf<float>);
return;
case IArrayOf<double> doubleArray:
CompareNumericalArrays(doubleArray, actual as IArrayOf<double>);
return;
case IArrayOf<ComplexOf<byte>> byteArray:
CompareNumericalArrays(byteArray, actual as IArrayOf<ComplexOf<byte>>);
return;
case IArrayOf<ComplexOf<sbyte>> sbyteArray:
CompareNumericalArrays(sbyteArray, actual as IArrayOf<ComplexOf<sbyte>>);
return;
case IArrayOf<ComplexOf<short>> shortArray:
CompareNumericalArrays(shortArray, actual as IArrayOf<ComplexOf<short>>);
return;
case IArrayOf<ComplexOf<ushort>> ushortArray:
CompareNumericalArrays(ushortArray, actual as IArrayOf<ComplexOf<ushort>>);
return;
case IArrayOf<ComplexOf<int>> intArray:
CompareNumericalArrays(intArray, actual as IArrayOf<ComplexOf<int>>);
return;
case IArrayOf<ComplexOf<uint>> uintArray:
CompareNumericalArrays(uintArray, actual as IArrayOf<ComplexOf<uint>>);
return;
case IArrayOf<ComplexOf<long>> longArray:
CompareNumericalArrays(longArray, actual as IArrayOf<ComplexOf<long>>);
return;
case IArrayOf<ComplexOf<ulong>> ulongArray:
CompareNumericalArrays(ulongArray, actual as IArrayOf<ComplexOf<ulong>>);
return;
case IArrayOf<ComplexOf<float>> floatArray:
CompareNumericalArrays(floatArray, actual as IArrayOf<ComplexOf<float>>);
return;
case IArrayOf<Complex> doubleArray:
CompareNumericalArrays(doubleArray, actual as IArrayOf<Complex>);
return;
case IArrayOf<bool> boolArray:
CompareNumericalArrays(boolArray, actual as IArrayOf<bool>);
return;
case ICellArray cellArray:
CompareCellArrays(cellArray, actual as ICellArray);
return;
}
if (expected.IsEmpty)
{
Assert.That(actual.IsEmpty, Is.True);
return;
}
throw new NotSupportedException();
}
private void CompareMatFiles(IMatFile expected, IMatFile actual)
{
Assert.That(expected.Variables.Length, Is.EqualTo(actual.Variables.Length));
for (var i = 0; i < expected.Variables.Length; i++)
{
var expectedVariable = expected.Variables[i];
var actualVariable = actual.Variables[i];
Assert.That(expectedVariable.Name, Is.EqualTo(actualVariable.Name));
Assert.That(expectedVariable.IsGlobal, Is.EqualTo(actualVariable.IsGlobal));
CompareMatArrays(expectedVariable.Value, actualVariable.Value);
}
}
private void MatCompareWithTestData(string factoryName, string testName, IMatFile actual)
{
var expected = GetMatTestData(factoryName)[testName];
byte[] buffer;
using (var stream = new MemoryStream())
{
var writer = new MatFileWriter(stream);
writer.Write(actual);
buffer = stream.ToArray();
}
using (var stream = new MemoryStream(buffer))
{
var reader = new MatFileReader(stream);
var actualRead = reader.Read();
CompareMatFiles(expected, actualRead);
}
}
private ComplexOf<T>[] CreateComplexLimits<T>(T[] limits)
where T : struct
{
return new[] { new ComplexOf<T>(limits[0], limits[1]), new ComplexOf<T>(limits[1], limits[0]) };
}
}
}

View File

@ -0,0 +1,35 @@
// Copyright 2017 Alexander Luzgarev
using System.IO;
namespace MatFileHandler.Tests
{
/// <summary>
/// Factory providing the parsed contents of .mat files.
/// </summary>
public class MatTestDataFactory : AbstractTestDataFactory<IMatFile>
{
/// <summary>
/// Initializes a new instance of the <see cref="MatTestDataFactory"/> class.
/// </summary>
/// <param name="testDirectory">Directory containing test files.</param>
public MatTestDataFactory(string testDirectory)
: base(
testDirectory,
new ExtensionTestFilenameConvention("mat"))
{
}
/// <summary>
/// Read and parse data from a .mat file.
/// </summary>
/// <param name="stream">Input stream.</param>
/// <returns>Parsed contents of the file.</returns>
protected override IMatFile ReadDataFromStream(Stream stream)
{
var matFileReader = new MatFileReader(stream);
var matFile = matFileReader.Read();
return matFile;
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

76
MatFileHandler.ruleset Executable file
View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Microsoft Managed Recommended Rules" Description="These rules focus on the most critical problems in your code, including potential security holes, application crashes, and other important logic and design errors. It is recommended to include this rule set in any custom rule set you create for your projects." ToolsVersion="15.0">
<Localization ResourceAssembly="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.dll" ResourceBaseName="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.Localized">
<Name Resource="MinimumRecommendedRules_Name" />
<Description Resource="MinimumRecommendedRules_Description" />
</Localization>
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CA1001" Action="Warning" />
<Rule Id="CA1009" Action="Warning" />
<Rule Id="CA1016" Action="Warning" />
<Rule Id="CA1033" Action="Warning" />
<Rule Id="CA1049" Action="Warning" />
<Rule Id="CA1060" Action="Warning" />
<Rule Id="CA1061" Action="Warning" />
<Rule Id="CA1063" Action="Warning" />
<Rule Id="CA1065" Action="Warning" />
<Rule Id="CA1301" Action="Warning" />
<Rule Id="CA1400" Action="Warning" />
<Rule Id="CA1401" Action="Warning" />
<Rule Id="CA1403" Action="Warning" />
<Rule Id="CA1404" Action="Warning" />
<Rule Id="CA1405" Action="Warning" />
<Rule Id="CA1410" Action="Warning" />
<Rule Id="CA1415" Action="Warning" />
<Rule Id="CA1821" Action="Warning" />
<Rule Id="CA1900" Action="Warning" />
<Rule Id="CA1901" Action="Warning" />
<Rule Id="CA2002" Action="Warning" />
<Rule Id="CA2100" Action="Warning" />
<Rule Id="CA2101" Action="Warning" />
<Rule Id="CA2108" Action="Warning" />
<Rule Id="CA2111" Action="Warning" />
<Rule Id="CA2112" Action="Warning" />
<Rule Id="CA2114" Action="Warning" />
<Rule Id="CA2116" Action="Warning" />
<Rule Id="CA2117" Action="Warning" />
<Rule Id="CA2122" Action="Warning" />
<Rule Id="CA2123" Action="Warning" />
<Rule Id="CA2124" Action="Warning" />
<Rule Id="CA2126" Action="Warning" />
<Rule Id="CA2131" Action="Warning" />
<Rule Id="CA2132" Action="Warning" />
<Rule Id="CA2133" Action="Warning" />
<Rule Id="CA2134" Action="Warning" />
<Rule Id="CA2137" Action="Warning" />
<Rule Id="CA2138" Action="Warning" />
<Rule Id="CA2140" Action="Warning" />
<Rule Id="CA2141" Action="Warning" />
<Rule Id="CA2146" Action="Warning" />
<Rule Id="CA2147" Action="Warning" />
<Rule Id="CA2149" Action="Warning" />
<Rule Id="CA2200" Action="Warning" />
<Rule Id="CA2202" Action="Warning" />
<Rule Id="CA2207" Action="Warning" />
<Rule Id="CA2212" Action="Warning" />
<Rule Id="CA2213" Action="Warning" />
<Rule Id="CA2214" Action="Warning" />
<Rule Id="CA2216" Action="Warning" />
<Rule Id="CA2220" Action="Warning" />
<Rule Id="CA2229" Action="Warning" />
<Rule Id="CA2231" Action="Warning" />
<Rule Id="CA2232" Action="Warning" />
<Rule Id="CA2235" Action="Warning" />
<Rule Id="CA2236" Action="Warning" />
<Rule Id="CA2237" Action="Warning" />
<Rule Id="CA2238" Action="Warning" />
<Rule Id="CA2240" Action="Warning" />
<Rule Id="CA2241" Action="Warning" />
<Rule Id="CA2242" Action="Warning" />
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<Rule Id="SA1101" Action="None" />
<Rule Id="SA1513" Action="None" />
<Rule Id="SA1309" Action="None" />
</Rules>
</RuleSet>

28
MatFileHandler.sln Executable file
View File

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.0.0
MinimumVisualStudioVersion = 10.0.0.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatFileHandler", "MatFileHandler/MatFileHandler.csproj", "{C0CD11D3-016A-4FCD-AF0B-D745F79F3749}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatFileHandler.Tests", "MatFileHandler.Tests\MatFileHandler.Tests.csproj", "{4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C0CD11D3-016A-4FCD-AF0B-D745F79F3749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0CD11D3-016A-4FCD-AF0B-D745F79F3749}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0CD11D3-016A-4FCD-AF0B-D745F79F3749}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0CD11D3-016A-4FCD-AF0B-D745F79F3749}.Release|Any CPU.Build.0 = Release|Any CPU
{4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

157
MatFileHandler/ArrayFlags.cs Executable file
View File

@ -0,0 +1,157 @@
// Copyright 2017 Alexander Luzgarev
using System;
namespace MatFileHandler
{
/// <summary>
/// Type of a Matlab array.
/// </summary>
internal enum ArrayType
{
/// <summary>
/// Cell array.
/// </summary>
MxCell = 1,
/// <summary>
/// Structure array.
/// </summary>
MxStruct = 2,
/// <summary>
/// Matlab object.
/// </summary>
MxObject = 3,
/// <summary>
/// Character array.
/// </summary>
MxChar = 4,
/// <summary>
/// Sparse array.
/// </summary>
MxSparse = 5,
/// <summary>
/// Double array.
/// </summary>
MxDouble = 6,
/// <summary>
/// Single array.
/// </summary>
MxSingle = 7,
/// <summary>
/// Int8 array.
/// </summary>
MxInt8 = 8,
/// <summary>
/// UInt8 array.
/// </summary>
MxUInt8 = 9,
/// <summary>
/// Int16 array.
/// </summary>
MxInt16 = 10,
/// <summary>
/// UInt16 array.
/// </summary>
MxUInt16 = 11,
/// <summary>
/// Int32 array.
/// </summary>
MxInt32 = 12,
/// <summary>
/// UInt32 array.
/// </summary>
MxUInt32 = 13,
/// <summary>
/// Int64 array.
/// </summary>
MxInt64 = 14,
/// <summary>
/// UInt64 array.
/// </summary>
MxUInt64 = 15,
/// <summary>
/// Undocumented object (?) array type.
/// </summary>
MxNewObject = 17,
}
/// <summary>
/// Variable flags.
/// </summary>
[Flags]
internal enum Variable
{
/// <summary>
/// Indicates a logical array.
/// </summary>
IsLogical = 2,
/// <summary>
/// Indicates a global variable.
/// </summary>
IsGlobal = 4,
/// <summary>
/// Indicates a complex array.
/// </summary>
IsComplex = 8,
}
/// <summary>
/// Array properties.
/// </summary>
internal struct ArrayFlags
{
/// <summary>
/// Array type.
/// </summary>
public ArrayType Class;
/// <summary>
/// Variable flags.
/// </summary>
public Variable Variable;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayFlags"/> struct.
/// </summary>
/// <param name="class_">Array type.</param>
/// <param name="variable">Variable flags.</param>
public ArrayFlags(ArrayType class_, Variable variable)
{
Class = class_;
Variable = variable;
}
}
/// <summary>
/// Sparse array properties.
/// </summary>
internal struct SparseArrayFlags
{
/// <summary>
/// Usual array properties.
/// </summary>
public ArrayFlags ArrayFlags;
/// <summary>
/// Maximal number of non-zero elements.
/// </summary>
public uint NzMax;
}
}

93
MatFileHandler/ComplexOf.cs Executable file
View File

@ -0,0 +1,93 @@
// Copyright 2017 Alexander Luzgarev
using System;
namespace MatFileHandler
{
/// <summary>
/// A structure representing a complex number where real and imaginary parts are of type T.
/// </summary>
/// <typeparam name="T">Type of real and imaginary parts.</typeparam>
public struct ComplexOf<T> : IEquatable<ComplexOf<T>>
where T : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="ComplexOf{T}"/> struct.
/// </summary>
/// <param name="real">Real part.</param>
/// <param name="imaginary">Imaginary part.</param>
public ComplexOf(T real, T imaginary)
{
Real = real;
Imaginary = imaginary;
}
/// <summary>
/// Gets real part.
/// </summary>
public T Real { get; }
/// <summary>
/// Gets imaginary part.
/// </summary>
public T Imaginary { get; }
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="left">Left argument.</param>
/// <param name="right">Right argument.</param>
/// <returns>True iff the numbers are equal.</returns>
public static bool operator ==(ComplexOf<T> left, ComplexOf<T> right)
{
return left.Equals(right);
}
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="left">Left argument.</param>
/// <param name="right">Right argument.</param>
/// <returns>True iff the numbers are not equal.</returns>
public static bool operator !=(ComplexOf<T> left, ComplexOf<T> right)
{
return !left.Equals(right);
}
/// <summary>
/// Equality check.
/// </summary>
/// <param name="other">Another complex number.</param>
/// <returns>True iff the number is equal to another.</returns>
public bool Equals(ComplexOf<T> other)
{
return Real.Equals(other.Real) && Imaginary.Equals(other.Imaginary);
}
/// <summary>
/// Equality check.
/// </summary>
/// <param name="obj">Another object.</param>
/// <returns>True iff another object is a complex number equal to this.</returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is ComplexOf<T> other && Equals(other);
}
/// <summary>
/// Gets has code of the number.
/// </summary>
/// <returns>Hash code.</returns>
public override int GetHashCode()
{
unchecked
{
return (Real.GetHashCode() * 397) ^ Imaginary.GetHashCode();
}
}
}
}

269
MatFileHandler/DataBuilder.cs Executable file
View File

@ -0,0 +1,269 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace MatFileHandler
{
/// <summary>
/// Class for building arrays that later can be written to a .mat file.
/// </summary>
public class DataBuilder
{
/// <summary>
/// Create a new numerical/logical array.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
/// <param name="dimensions">Dimensions of the array.</param>
/// <returns>An array of given element type and dimensions, initialized by zeros.</returns>
/// <remarks>
/// Possible values of T:
/// Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double,
/// ComplexOf&lt;TReal&gt; (where TReal is one of Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single),
/// Complex, Boolean.
/// </remarks>
public IArrayOf<T> NewArray<T>(params int[] dimensions)
where T : struct
{
return new MatNumericalArrayOf<T>(
GetStandardFlags<T>(),
dimensions,
string.Empty,
new T[dimensions.NumberOfElements()]);
}
/// <summary>
/// Create a new numerical/logical array and initialize it with the given data.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
/// <param name="data">Initial data.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <returns>An array of given dimensions, initialized by the provided data.</returns>
/// <remarks>
/// Possible values of T:
/// Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double,
/// ComplexOf&lt;TReal&gt; (where TReal is one of Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single),
/// Complex, Boolean.
/// </remarks>
public IArrayOf<T> NewArray<T>(T[] data, params int[] dimensions)
where T : struct
{
if (data.Length != dimensions.NumberOfElements())
{
throw new ArgumentException("Data size does not match the specified dimensions", "data");
}
return new MatNumericalArrayOf<T>(GetStandardFlags<T>(), dimensions, string.Empty, data);
}
/// <summary>
/// Create a new cell array.
/// </summary>
/// <param name="dimensions">Dimensions of the array.</param>
/// <returns>A cell array of given dimensions, consisting of empty arrays.</returns>
public ICellArray NewCellArray(params int[] dimensions)
{
var flags = ConstructArrayFlags(ArrayType.MxCell);
var elements = Enumerable.Repeat(MatArray.Empty() as IArray, dimensions.NumberOfElements()).ToList();
return new MatCellArray(flags, dimensions, string.Empty, elements);
}
/// <summary>
/// Create a new structure array.
/// </summary>
/// <param name="fields">Names of structure fields.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <returns>A structure array of given dimensions with given fields, consisting of empty arrays.</returns>
public IStructureArray NewStructureArray(IEnumerable<string> fields, params int[] dimensions)
{
var flags = ConstructArrayFlags(ArrayType.MxStruct);
var elements = Enumerable.Repeat(MatArray.Empty() as IArray, dimensions.NumberOfElements()).ToList();
var dictionary = new Dictionary<string, List<IArray>>();
foreach (var field in fields)
{
dictionary[field] = elements.ToList();
}
return new MatStructureArray(flags, dimensions, string.Empty, dictionary);
}
/// <summary>
/// Create a new character array.
/// </summary>
/// <param name="contents">A string to initialize the array.</param>
/// <returns>A 1xn character array with the given string as contents.</returns>
public ICharArray NewCharArray(string contents)
{
return NewCharArray(contents, 1, contents.Length);
}
/// <summary>
/// Create a new character array of specified dimensions.
/// </summary>
/// <param name="contents">A string to initialize the array.</param>
/// <param name="dimensions">The dimensions of the array.</param>
/// <returns>A character array of given dimensions with the given string as contents.</returns>
public ICharArray NewCharArray(string contents, params int[] dimensions)
{
var flags = ConstructArrayFlags(ArrayType.MxChar);
var ushortArray = contents.ToCharArray().Select(c => (ushort)c).ToArray();
return new MatCharArrayOf<ushort>(flags, dimensions, string.Empty, ushortArray, contents);
}
/// <summary>
/// Create a new empty array.
/// </summary>
/// <returns>An empty array.</returns>
public IArray NewEmpty()
{
return MatArray.Empty();
}
/// <summary>
/// Create a new sparse array.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
/// <param name="dimensions">The dimensions of the array.</param>
/// <returns>An empty sparse array of given type and given dimensions.</returns>
public ISparseArrayOf<T> NewSparseArray<T>(params int[] dimensions)
where T : struct
{
return new MatSparseArrayOf<T>(
GetStandardSparseArrayFlags<T>(),
dimensions,
string.Empty,
new Dictionary<(int, int), T>());
}
/// <summary>
/// Create a new variable.
/// </summary>
/// <param name="name">Name of the variable.</param>
/// <param name="value">Value of the variable.</param>
/// <param name="isGlobal">Global flag for the variable.</param>
/// <returns>A new variable with given name and value.</returns>
public IVariable NewVariable(string name, IArray value, bool isGlobal = false)
{
return new MatVariable(value, name, isGlobal);
}
/// <summary>
/// Create a new Matlab file.
/// </summary>
/// <param name="variables">Variables in the file.</param>
/// <returns>A file containing the provided variables.</returns>
public IMatFile NewFile(IEnumerable<IVariable> variables)
{
return new MatFile(variables);
}
private ArrayFlags ConstructArrayFlags(ArrayType class_, bool isComplex = false, bool isLogical = false)
{
return new ArrayFlags
{
Class = class_,
Variable = (isComplex ? Variable.IsComplex : 0) |
(isLogical ? Variable.IsLogical : 0),
};
}
private ArrayFlags GetStandardFlags<T>()
{
if (typeof(T) == typeof(sbyte))
{
return ConstructArrayFlags(ArrayType.MxInt8);
}
if (typeof(T) == typeof(ComplexOf<sbyte>))
{
return ConstructArrayFlags(ArrayType.MxInt8, isComplex: true);
}
if (typeof(T) == typeof(byte))
{
return ConstructArrayFlags(ArrayType.MxUInt8);
}
if (typeof(T) == typeof(ComplexOf<byte>))
{
return ConstructArrayFlags(ArrayType.MxUInt8, isComplex: true);
}
if (typeof(T) == typeof(short))
{
return ConstructArrayFlags(ArrayType.MxInt16);
}
if (typeof(T) == typeof(ComplexOf<short>))
{
return ConstructArrayFlags(ArrayType.MxInt16, isComplex: true);
}
if (typeof(T) == typeof(ushort))
{
return ConstructArrayFlags(ArrayType.MxUInt16);
}
if (typeof(T) == typeof(ComplexOf<ushort>))
{
return ConstructArrayFlags(ArrayType.MxUInt16, isComplex: true);
}
if (typeof(T) == typeof(int))
{
return ConstructArrayFlags(ArrayType.MxInt32);
}
if (typeof(T) == typeof(ComplexOf<int>))
{
return ConstructArrayFlags(ArrayType.MxInt32, isComplex: true);
}
if (typeof(T) == typeof(uint))
{
return ConstructArrayFlags(ArrayType.MxUInt32);
}
if (typeof(T) == typeof(ComplexOf<uint>))
{
return ConstructArrayFlags(ArrayType.MxUInt32, isComplex: true);
}
if (typeof(T) == typeof(long))
{
return ConstructArrayFlags(ArrayType.MxInt64);
}
if (typeof(T) == typeof(ComplexOf<long>))
{
return ConstructArrayFlags(ArrayType.MxInt64, isComplex: true);
}
if (typeof(T) == typeof(ulong))
{
return ConstructArrayFlags(ArrayType.MxUInt64);
}
if (typeof(T) == typeof(ComplexOf<ulong>))
{
return ConstructArrayFlags(ArrayType.MxUInt64, isComplex: true);
}
if (typeof(T) == typeof(float))
{
return ConstructArrayFlags(ArrayType.MxSingle);
}
if (typeof(T) == typeof(ComplexOf<float>))
{
return ConstructArrayFlags(ArrayType.MxSingle, isComplex: true);
}
if (typeof(T) == typeof(double))
{
return ConstructArrayFlags(ArrayType.MxDouble);
}
if (typeof(T) == typeof(Complex))
{
return ConstructArrayFlags(ArrayType.MxDouble, isComplex: true);
}
if (typeof(T) == typeof(bool))
{
return ConstructArrayFlags(ArrayType.MxInt8, isLogical: true);
}
return ConstructArrayFlags(ArrayType.MxObject);
}
private SparseArrayFlags GetStandardSparseArrayFlags<T>()
{
var arrayFlags = GetStandardFlags<T>();
return new SparseArrayFlags
{
ArrayFlags = arrayFlags,
NzMax = 0,
};
}
}
}

11
MatFileHandler/DataElement.cs Executable file
View File

@ -0,0 +1,11 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// Base class for all data elements in .mat files.
/// </summary>
internal class DataElement
{
}
}

View File

@ -0,0 +1,246 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
namespace MatFileHandler
{
/// <summary>
/// Static class for constructing various arrays from raw data elements read from .mat files.
/// </summary>
internal static class DataElementConverter
{
/// <summary>
/// Construct a complex sparse array.
/// </summary>
/// <param name="flags">Array flags.</param>
/// <param name="dimensions">Array dimensions.</param>
/// <param name="name">Array name.</param>
/// <param name="rowIndex">Row indices.</param>
/// <param name="columnIndex">Denotes index ranges for each column.</param>
/// <param name="data">Real parts of the values.</param>
/// <param name="imaginaryData">Imaginary parts of the values.</param>
/// <returns>A constructed array.</returns>
public static MatArray ConvertToMatSparseArrayOfComplex(
SparseArrayFlags flags,
int[] dimensions,
string name,
int[] rowIndex,
int[] columnIndex,
DataElement data,
DataElement imaginaryData)
{
var realParts = DataExtraction.GetDataAsDouble(data).ToArrayLazily();
var imaginaryParts = DataExtraction.GetDataAsDouble(imaginaryData).ToArrayLazily();
if (realParts == null)
{
throw new HandlerException("Couldn't read sparse array.");
}
var dataDictionary =
ConvertMatlabSparseToDictionary(
rowIndex,
columnIndex,
j => new Complex(realParts[j], imaginaryParts[j]));
return new MatSparseArrayOf<Complex>(flags, dimensions, name, dataDictionary);
}
/// <summary>
/// Construct a double sparse array or a logical sparse array.
/// </summary>
/// <typeparam name="T">Element type (Double or Boolean).</typeparam>
/// <param name="flags">Array flags.</param>
/// <param name="dimensions">Array dimensions.</param>
/// <param name="name">Array name.</param>
/// <param name="rowIndex">Row indices.</param>
/// <param name="columnIndex">Denotes index ranges for each column.</param>
/// <param name="data">The values.</param>
/// <returns>A constructed array.</returns>
public static MatArray ConvertToMatSparseArrayOf<T>(
SparseArrayFlags flags,
int[] dimensions,
string name,
int[] rowIndex,
int[] columnIndex,
DataElement data)
where T : struct
{
if (dimensions.Length != 2)
{
throw new NotSupportedException("Only 2-dimensional sparse arrays are supported");
}
if (data == null)
{
throw new ArgumentException("Null data found.", "data");
}
var elements =
ConvertDataToSparseProperType<T>(data, flags.ArrayFlags.Variable.HasFlag(Variable.IsLogical));
if (elements == null)
{
throw new HandlerException("Couldn't read sparse array.");
}
var dataDictionary =
ConvertMatlabSparseToDictionary(rowIndex, columnIndex, j => elements[j]);
return new MatSparseArrayOf<T>(flags, dimensions, name, dataDictionary);
}
/// <summary>
/// Construct a numerical array.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
/// <param name="flags">Array flags.</param>
/// <param name="dimensions">Array dimensions.</param>
/// <param name="name">Array name.</param>
/// <param name="realData">Real parts of the values.</param>
/// <param name="imaginaryData">Imaginary parts of the values.</param>
/// <returns>A constructed array.</returns>
/// <remarks>
/// Possible values for T: Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double,
/// ComplexOf&lt;TReal&gt; (where TReal is one of Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single),
/// Complex.
/// </remarks>
public static MatArray ConvertToMatNumericalArrayOf<T>(
ArrayFlags flags,
int[] dimensions,
string name,
DataElement realData,
DataElement imaginaryData)
where T : struct
{
if (flags.Variable.HasFlag(Variable.IsLogical))
{
var data = DataExtraction.GetDataAsUInt8(realData).ToArrayLazily().Select(x => x != 0).ToArray();
return new MatNumericalArrayOf<bool>(flags, dimensions, name, data);
}
switch (flags.Class)
{
case ArrayType.MxChar:
switch (realData)
{
case MiNum<byte> dataByte:
return ConvertToMatCharArray(flags, dimensions, name, dataByte);
case MiNum<ushort> dataUshort:
return ConvertToMatCharArray(flags, dimensions, name, dataUshort);
default:
throw new NotSupportedException("Only utf8, utf16 or ushort char arrays are supported.");
}
case ArrayType.MxDouble:
case ArrayType.MxSingle:
case ArrayType.MxInt8:
case ArrayType.MxUInt8:
case ArrayType.MxInt16:
case ArrayType.MxUInt16:
case ArrayType.MxInt32:
case ArrayType.MxUInt32:
case ArrayType.MxInt64:
case ArrayType.MxUInt64:
var dataArray = ConvertDataToProperType<T>(realData, flags.Class);
if (flags.Variable.HasFlag(Variable.IsComplex))
{
var dataArray2 = ConvertDataToProperType<T>(imaginaryData, flags.Class);
if (flags.Class == ArrayType.MxDouble)
{
var complexArray =
(dataArray as double[])
.Zip(dataArray2 as double[], (x, y) => new Complex(x, y))
.ToArray();
return new MatNumericalArrayOf<Complex>(flags, dimensions, name, complexArray);
}
var complexDataArray = dataArray.Zip(dataArray2, (x, y) => new ComplexOf<T>(x, y)).ToArray();
return new MatNumericalArrayOf<ComplexOf<T>>(flags, dimensions, name, complexDataArray);
}
return new MatNumericalArrayOf<T>(flags, dimensions, name, dataArray);
default:
throw new NotSupportedException();
}
}
private static MatCharArrayOf<byte> ConvertToMatCharArray(
ArrayFlags flags,
int[] dimensions,
string name,
MiNum<byte> dataElement)
{
var data = dataElement?.Data;
return new MatCharArrayOf<byte>(flags, dimensions, name, data, Encoding.UTF8.GetString(data));
}
private static T[] ConvertDataToProperType<T>(DataElement data, ArrayType arrayType)
{
switch (arrayType)
{
case ArrayType.MxDouble:
return DataExtraction.GetDataAsDouble(data).ToArrayLazily() as T[];
case ArrayType.MxSingle:
return DataExtraction.GetDataAsSingle(data).ToArrayLazily() as T[];
case ArrayType.MxInt8:
return DataExtraction.GetDataAsInt8(data).ToArrayLazily() as T[];
case ArrayType.MxUInt8:
return DataExtraction.GetDataAsUInt8(data).ToArrayLazily() as T[];
case ArrayType.MxInt16:
return DataExtraction.GetDataAsInt16(data).ToArrayLazily() as T[];
case ArrayType.MxUInt16:
return DataExtraction.GetDataAsUInt16(data).ToArrayLazily() as T[];
case ArrayType.MxInt32:
return DataExtraction.GetDataAsInt32(data).ToArrayLazily() as T[];
case ArrayType.MxUInt32:
return DataExtraction.GetDataAsUInt32(data).ToArrayLazily() as T[];
case ArrayType.MxInt64:
return DataExtraction.GetDataAsInt64(data).ToArrayLazily() as T[];
case ArrayType.MxUInt64:
return DataExtraction.GetDataAsUInt64(data).ToArrayLazily() as T[];
default:
throw new NotSupportedException();
}
}
private static T[] ConvertDataToSparseProperType<T>(DataElement data, bool isLogical)
{
if (isLogical)
{
return DataExtraction.GetDataAsUInt8(data).ToArrayLazily().Select(x => x != 0).ToArray() as T[];
}
switch (data)
{
case MiNum<double> _:
return DataExtraction.GetDataAsDouble(data).ToArrayLazily() as T[];
default:
throw new NotSupportedException();
}
}
private static MatCharArrayOf<ushort> ConvertToMatCharArray(
ArrayFlags flags,
int[] dimensions,
string name,
MiNum<ushort> dataElement)
{
var data = dataElement?.Data;
return new MatCharArrayOf<ushort>(
flags,
dimensions,
name,
data,
new string(data.Select(x => (char)x).ToArray()));
}
private static Dictionary<(int, int), T> ConvertMatlabSparseToDictionary<T>(
int[] rowIndex,
int[] columnIndex,
Func<int, T> get)
{
var result = new Dictionary<(int, int), T>();
for (var column = 0; column < columnIndex.Length - 1; column++)
{
for (var j = columnIndex[column]; j < columnIndex[column + 1]; j++)
{
var row = rowIndex[j];
result[(row, column)] = get(j);
}
}
return result;
}
}
}

View File

@ -0,0 +1,434 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
namespace MatFileHandler
{
/// <summary>
/// Functions for reading data elements from a .mat file.
/// </summary>
internal static class DataElementReader
{
/// <summary>
/// Read a data element.
/// </summary>
/// <param name="reader">Input reader.</param>
/// <returns>Data element.</returns>
public static DataElement Read(BinaryReader reader)
{
var (dataReader, tag) = ReadTag(reader);
DataElement result;
switch (tag.Type)
{
case DataType.MiInt8:
result = ReadNum<sbyte>(tag, dataReader);
break;
case DataType.MiUInt8:
case DataType.MiUtf8:
result = ReadNum<byte>(tag, dataReader);
break;
case DataType.MiInt16:
result = ReadNum<short>(tag, dataReader);
break;
case DataType.MiUInt16:
case DataType.MiUtf16:
result = ReadNum<ushort>(tag, dataReader);
break;
case DataType.MiInt32:
result = ReadNum<int>(tag, dataReader);
break;
case DataType.MiUInt32:
result = ReadNum<uint>(tag, dataReader);
break;
case DataType.MiSingle:
result = ReadNum<float>(tag, dataReader);
break;
case DataType.MiDouble:
result = ReadNum<double>(tag, dataReader);
break;
case DataType.MiInt64:
result = ReadNum<long>(tag, dataReader);
break;
case DataType.MiUInt64:
result = ReadNum<ulong>(tag, dataReader);
break;
case DataType.MiMatrix:
result = ReadMatrix(tag, dataReader);
break;
case DataType.MiCompressed:
result = ReadCompressed(tag, dataReader);
break;
default:
throw new NotSupportedException("Unknown element.");
}
if (tag.Type != DataType.MiCompressed)
{
var position = reader.BaseStream.Position;
if (position % 8 != 0)
{
reader.ReadBytes(8 - (int)(position % 8));
}
}
return result;
}
private static (BinaryReader, Tag) ReadTag(BinaryReader reader)
{
var type = reader.ReadInt32();
var typeHi = type >> 16;
if (typeHi == 0)
{
var length = reader.ReadInt32();
return (reader, new Tag((DataType)type, length));
}
else
{
var length = typeHi;
type = type & 0xffff;
var smallReader = new BinaryReader(new MemoryStream(reader.ReadBytes(4)));
return (smallReader, new Tag((DataType)type, length));
}
}
private static ArrayFlags ReadArrayFlags(DataElement element)
{
var flagData = (element as MiNum<uint>)?.Data ?? throw new HandlerException("Couldn't read array flags.");
var class_ = (ArrayType)(flagData[0] & 0xff);
var variableFlags = (flagData[0] >> 8) & 0x0e;
return new ArrayFlags
{
Class = class_,
Variable = (Variable)variableFlags,
};
}
private static SparseArrayFlags ReadSparseArrayFlags(DataElement element)
{
var arrayFlags = ReadArrayFlags(element);
var flagData = (element as MiNum<uint>)?.Data ??
throw new HandlerException("Couldn't read sparse array flags.");
var nzMax = flagData[1];
return new SparseArrayFlags
{
ArrayFlags = arrayFlags,
NzMax = nzMax,
};
}
private static int[] ReadDimensionsArray(DataElement element)
{
return (element as MiNum<int>)?.Data;
}
private static DataElement ReadData(DataElement element)
{
return element;
}
private static string ReadName(DataElement element)
{
return Encoding.ASCII.GetString((element as MiNum<sbyte>)?.Data.Select(x => (byte)x).ToArray());
}
private static DataElement ReadNum<T>(Tag tag, BinaryReader reader)
where T : struct
{
var bytes = reader.ReadBytes(tag.Length);
if (tag.Type == DataType.MiUInt8)
{
return new MiNum<byte>(bytes);
}
var result = new T[bytes.Length / tag.ElementSize];
Buffer.BlockCopy(bytes, 0, result, 0, bytes.Length);
return new MiNum<T>(result);
}
private static string[] ReadFieldNames(MiNum<sbyte> element, int fieldNameLength)
{
var numberOfFields = element.Data.Length / fieldNameLength;
var result = new string[numberOfFields];
for (var i = 0; i < numberOfFields; i++)
{
var list = new List<byte>();
var position = i * fieldNameLength;
while (element.Data[position] != 0)
{
list.Add((byte)element.Data[position]);
position++;
}
result[i] = Encoding.ASCII.GetString(list.ToArray());
}
return result;
}
private static DataElement ContinueReadingSparseArray(
BinaryReader reader,
DataElement firstElement,
int[] dimensions,
string name)
{
var sparseArrayFlags = ReadSparseArrayFlags(firstElement);
var rowIndex = Read(reader) as MiNum<int>;
var columnIndex = Read(reader) as MiNum<int>;
var data = Read(reader);
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsLogical))
{
return DataElementConverter.ConvertToMatSparseArrayOf<bool>(
sparseArrayFlags,
dimensions,
name,
rowIndex?.Data,
columnIndex?.Data,
data);
}
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsComplex))
{
var imaginaryData = Read(reader);
return DataElementConverter.ConvertToMatSparseArrayOfComplex(
sparseArrayFlags,
dimensions,
name,
rowIndex?.Data,
columnIndex?.Data,
data,
imaginaryData);
}
switch (data)
{
case MiNum<double> _:
return DataElementConverter.ConvertToMatSparseArrayOf<double>(
sparseArrayFlags,
dimensions,
name,
rowIndex?.Data,
columnIndex?.Data,
data);
default:
throw new NotSupportedException("Only double and logical sparse arrays are supported.");
}
}
private static DataElement ContinueReadingCellArray(
BinaryReader reader,
ArrayFlags flags,
int[] dimensions,
string name)
{
var numberOfElements = dimensions.NumberOfElements();
var elements = new List<IArray>();
for (var i = 0; i < numberOfElements; i++)
{
var element = Read(reader) as IArray;
elements.Add(element);
}
return new MatCellArray(flags, dimensions, name, elements);
}
private static DataElement ContinueReadingStructure(
BinaryReader reader,
ArrayFlags flags,
int[] dimensions,
string name,
int fieldNameLength)
{
var element = Read(reader);
var fieldNames = ReadFieldNames(element as MiNum<sbyte>, fieldNameLength);
var fields = new Dictionary<string, List<IArray>>();
foreach (var fieldName in fieldNames)
{
fields[fieldName] = new List<IArray>();
}
var numberOfElements = dimensions.NumberOfElements();
for (var i = 0; i < numberOfElements; i++)
{
foreach (var fieldName in fieldNames)
{
var field = Read(reader) as IArray;
fields[fieldName].Add(field);
}
}
return new MatStructureArray(flags, dimensions, name, fields);
}
private static DataElement ContinueReadingNewObject()
{
throw new HandlerException("Cannot read objects.");
}
private static DataElement ReadMatrix(Tag tag, BinaryReader reader)
{
if (tag.Length == 0)
{
return MatArray.Empty();
}
var element1 = Read(reader);
var flags = ReadArrayFlags(element1);
if (flags.Class == ArrayType.MxNewObject)
{
return ContinueReadingNewObject();
}
var element2 = Read(reader);
var dimensions = ReadDimensionsArray(element2);
var element3 = Read(reader);
var name = ReadName(element3);
if (flags.Class == ArrayType.MxCell)
{
return ContinueReadingCellArray(reader, flags, dimensions, name);
}
if (flags.Class == ArrayType.MxSparse)
{
return ContinueReadingSparseArray(reader, element1, dimensions, name);
}
var element4 = Read(reader);
var data = ReadData(element4);
DataElement imaginaryData = null;
if (flags.Variable.HasFlag(Variable.IsComplex))
{
var element5 = Read(reader);
imaginaryData = ReadData(element5);
}
if (flags.Class == ArrayType.MxStruct)
{
var fieldNameLengthData = (data as MiNum<int>)?.Data ??
throw new HandlerException("Couldn't read structure field name length.");
return ContinueReadingStructure(reader, flags, dimensions, name, fieldNameLengthData[0]);
}
switch (flags.Class)
{
case ArrayType.MxChar:
switch (data)
{
case MiNum<byte> _:
return DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
flags,
dimensions,
name,
data,
imaginaryData);
case MiNum<ushort> _:
return DataElementConverter.ConvertToMatNumericalArrayOf<ushort>(
flags,
dimensions,
name,
data,
imaginaryData);
default:
throw new NotSupportedException(
$"This type of char array ({data.GetType()}) is not supported.");
}
case ArrayType.MxInt8:
return DataElementConverter.ConvertToMatNumericalArrayOf<sbyte>(
flags,
dimensions,
name,
data,
imaginaryData);
case ArrayType.MxUInt8:
if (flags.Variable.HasFlag(Variable.IsLogical))
{
return DataElementConverter.ConvertToMatNumericalArrayOf<bool>(
flags,
dimensions,
name,
data,
imaginaryData);
}
return DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
flags,
dimensions,
name,
data,
imaginaryData);
case ArrayType.MxInt16:
return DataElementConverter.ConvertToMatNumericalArrayOf<short>(
flags,
dimensions,
name,
data,
imaginaryData);
case ArrayType.MxUInt16:
return DataElementConverter.ConvertToMatNumericalArrayOf<ushort>(
flags,
dimensions,
name,
data,
imaginaryData);
case ArrayType.MxInt32:
return DataElementConverter.ConvertToMatNumericalArrayOf<int>(
flags,
dimensions,
name,
data,
imaginaryData);
case ArrayType.MxUInt32:
return DataElementConverter.ConvertToMatNumericalArrayOf<uint>(
flags,
dimensions,
name,
data,
imaginaryData);
case ArrayType.MxInt64:
return DataElementConverter.ConvertToMatNumericalArrayOf<long>(
flags,
dimensions,
name,
data,
imaginaryData);
case ArrayType.MxUInt64:
return DataElementConverter.ConvertToMatNumericalArrayOf<ulong>(
flags,
dimensions,
name,
data,
imaginaryData);
case ArrayType.MxSingle:
return DataElementConverter.ConvertToMatNumericalArrayOf<float>(
flags,
dimensions,
name,
data,
imaginaryData);
case ArrayType.MxDouble:
return DataElementConverter.ConvertToMatNumericalArrayOf<double>(
flags,
dimensions,
name,
data,
imaginaryData);
default:
throw new HandlerException("Unknown data type.");
}
}
private static DataElement ReadCompressed(Tag tag, BinaryReader reader)
{
reader.ReadBytes(2);
var compressedData = new byte[tag.Length - 6];
reader.BaseStream.Read(compressedData, 0, tag.Length - 6);
reader.ReadBytes(4);
var resultStream = new MemoryStream();
using (var compressedStream = new MemoryStream(compressedData))
{
using (var stream = new DeflateStream(compressedStream, CompressionMode.Decompress, leaveOpen: true))
{
stream.CopyTo(resultStream);
}
}
resultStream.Position = 0;
return Read(resultStream);
}
private static DataElement Read(Stream stream)
{
using (var reader = new BinaryReader(stream))
{
return Read(reader);
}
}
}
}

366
MatFileHandler/DataExtraction.cs Executable file
View File

@ -0,0 +1,366 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
namespace MatFileHandler
{
/// <summary>
/// Functions for extracting values from data elements.
/// </summary>
internal static class DataExtraction
{
/// <summary>
/// Convert IEnumerable to array.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
/// <param name="somethingEnumerable">Input IEnumerable.</param>
/// <returns>An equivalent array.</returns>
/// <remarks>In contrast to the stanard ToArray() method, this doesn't create a copy if the input already was an array.</remarks>
public static T[] ToArrayLazily<T>(this IEnumerable<T> somethingEnumerable)
{
return somethingEnumerable as T[] ?? somethingEnumerable.ToArray();
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of Double values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to Double.</returns>
public static IEnumerable<double> GetDataAsDouble(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data.Select(Convert.ToDouble);
case MiNum<byte> byteElement:
return byteElement.Data.Select(Convert.ToDouble);
case MiNum<int> intElement:
return intElement.Data.Select(Convert.ToDouble);
case MiNum<uint> uintElement:
return uintElement.Data.Select(Convert.ToDouble);
case MiNum<short> shortElement:
return shortElement.Data.Select(Convert.ToDouble);
case MiNum<ushort> ushortElement:
return ushortElement.Data.Select(Convert.ToDouble);
case MiNum<long> longElement:
return longElement.Data.Select(Convert.ToDouble);
case MiNum<ulong> ulongElement:
return ulongElement.Data.Select(Convert.ToDouble);
case MiNum<float> floatElement:
return floatElement.Data.Select(Convert.ToDouble);
case MiNum<double> doubleElement:
return doubleElement.Data;
}
throw new HandlerException(
$"Expected data element that would be convertible to double, found {element.GetType()}.");
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of Single values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to Single.</returns>
public static IEnumerable<float> GetDataAsSingle(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data.Select(Convert.ToSingle);
case MiNum<byte> byteElement:
return byteElement.Data.Select(Convert.ToSingle);
case MiNum<int> intElement:
return intElement.Data.Select(Convert.ToSingle);
case MiNum<uint> uintElement:
return uintElement.Data.Select(Convert.ToSingle);
case MiNum<short> shortElement:
return shortElement.Data.Select(Convert.ToSingle);
case MiNum<ushort> ushortElement:
return ushortElement.Data.Select(Convert.ToSingle);
case MiNum<long> longElement:
return longElement.Data.Select(Convert.ToSingle);
case MiNum<ulong> ulongElement:
return ulongElement.Data.Select(Convert.ToSingle);
case MiNum<float> floatElement:
return floatElement.Data;
case MiNum<double> doubleElement:
return doubleElement.Data.Select(Convert.ToSingle);
}
throw new HandlerException(
$"Expected data element that would be convertible to float, found {element.GetType()}.");
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of Int8 values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to Int8.</returns>
public static IEnumerable<sbyte> GetDataAsInt8(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data;
case MiNum<byte> byteElement:
return byteElement.Data.Select(Convert.ToSByte);
case MiNum<int> intElement:
return intElement.Data.Select(Convert.ToSByte);
case MiNum<uint> uintElement:
return uintElement.Data.Select(Convert.ToSByte);
case MiNum<short> shortElement:
return shortElement.Data.Select(Convert.ToSByte);
case MiNum<ushort> ushortElement:
return ushortElement.Data.Select(Convert.ToSByte);
case MiNum<long> longElement:
return longElement.Data.Select(Convert.ToSByte);
case MiNum<ulong> ulongElement:
return ulongElement.Data.Select(Convert.ToSByte);
case MiNum<float> floatElement:
return floatElement.Data.Select(Convert.ToSByte);
case MiNum<double> doubleElement:
return doubleElement.Data.Select(Convert.ToSByte);
}
throw new HandlerException(
$"Expected data element that would be convertible to int8, found {element.GetType()}.");
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of Uint8 values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to UInt8.</returns>
public static IEnumerable<byte> GetDataAsUInt8(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data.Select(Convert.ToByte);
case MiNum<byte> byteElement:
return byteElement.Data;
case MiNum<int> intElement:
return intElement.Data.Select(Convert.ToByte);
case MiNum<uint> uintElement:
return uintElement.Data.Select(Convert.ToByte);
case MiNum<short> shortElement:
return shortElement.Data.Select(Convert.ToByte);
case MiNum<ushort> ushortElement:
return ushortElement.Data.Select(Convert.ToByte);
case MiNum<long> longElement:
return longElement.Data.Select(Convert.ToByte);
case MiNum<ulong> ulongElement:
return ulongElement.Data.Select(Convert.ToByte);
case MiNum<float> floatElement:
return floatElement.Data.Select(Convert.ToByte);
case MiNum<double> doubleElement:
return doubleElement.Data.Select(Convert.ToByte);
}
throw new HandlerException(
$"Expected data element that would be convertible to uint8, found {element.GetType()}.");
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of Int16 values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to Int16.</returns>
public static IEnumerable<short> GetDataAsInt16(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data.Select(Convert.ToInt16);
case MiNum<byte> byteElement:
return byteElement.Data.Select(Convert.ToInt16);
case MiNum<int> intElement:
return intElement.Data.Select(Convert.ToInt16);
case MiNum<uint> uintElement:
return uintElement.Data.Select(Convert.ToInt16);
case MiNum<short> shortElement:
return shortElement.Data;
case MiNum<ushort> ushortElement:
return ushortElement.Data.Select(Convert.ToInt16);
case MiNum<long> longElement:
return longElement.Data.Select(Convert.ToInt16);
case MiNum<ulong> ulongElement:
return ulongElement.Data.Select(Convert.ToInt16);
case MiNum<float> floatElement:
return floatElement.Data.Select(Convert.ToInt16);
case MiNum<double> doubleElement:
return doubleElement.Data.Select(Convert.ToInt16);
}
throw new HandlerException(
$"Expected data element that would be convertible to int16, found {element.GetType()}.");
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of UInt16 values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to UInt16.</returns>
public static IEnumerable<ushort> GetDataAsUInt16(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data.Select(Convert.ToUInt16);
case MiNum<byte> byteElement:
return byteElement.Data.Select(Convert.ToUInt16);
case MiNum<int> intElement:
return intElement.Data.Select(Convert.ToUInt16);
case MiNum<uint> uintElement:
return uintElement.Data.Select(Convert.ToUInt16);
case MiNum<short> shortElement:
return shortElement.Data.Select(Convert.ToUInt16);
case MiNum<ushort> ushortElement:
return ushortElement.Data;
case MiNum<long> longElement:
return longElement.Data.Select(Convert.ToUInt16);
case MiNum<ulong> ulongElement:
return ulongElement.Data.Select(Convert.ToUInt16);
case MiNum<float> floatElement:
return floatElement.Data.Select(Convert.ToUInt16);
case MiNum<double> doubleElement:
return doubleElement.Data.Select(Convert.ToUInt16);
}
throw new HandlerException(
$"Expected data element that would be convertible to uint16, found {element.GetType()}.");
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of Int32 values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to Int32.</returns>
public static IEnumerable<int> GetDataAsInt32(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data.Select(Convert.ToInt32);
case MiNum<byte> byteElement:
return byteElement.Data.Select(Convert.ToInt32);
case MiNum<int> intElement:
return intElement.Data;
case MiNum<uint> uintElement:
return uintElement.Data.Select(Convert.ToInt32);
case MiNum<short> shortElement:
return shortElement.Data.Select(Convert.ToInt32);
case MiNum<ushort> ushortElement:
return ushortElement.Data.Select(Convert.ToInt32);
case MiNum<long> longElement:
return longElement.Data.Select(Convert.ToInt32);
case MiNum<ulong> ulongElement:
return ulongElement.Data.Select(Convert.ToInt32);
case MiNum<float> floatElement:
return floatElement.Data.Select(Convert.ToInt32);
case MiNum<double> doubleElement:
return doubleElement.Data.Select(Convert.ToInt32);
}
throw new HandlerException(
$"Expected data element that would be convertible to int32, found {element.GetType()}.");
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of UInt32 values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to UInt32.</returns>
public static IEnumerable<uint> GetDataAsUInt32(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data.Select(Convert.ToUInt32);
case MiNum<byte> byteElement:
return byteElement.Data.Select(Convert.ToUInt32);
case MiNum<int> intElement:
return intElement.Data.Select(Convert.ToUInt32);
case MiNum<uint> uintElement:
return uintElement.Data;
case MiNum<short> shortElement:
return shortElement.Data.Select(Convert.ToUInt32);
case MiNum<ushort> ushortElement:
return ushortElement.Data.Select(Convert.ToUInt32);
case MiNum<long> longElement:
return longElement.Data.Select(Convert.ToUInt32);
case MiNum<ulong> ulongElement:
return ulongElement.Data.Select(Convert.ToUInt32);
case MiNum<float> floatElement:
return floatElement.Data.Select(Convert.ToUInt32);
case MiNum<double> doubleElement:
return doubleElement.Data.Select(Convert.ToUInt32);
}
throw new HandlerException(
$"Expected data element that would be convertible to uint32, found {element.GetType()}.");
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of Int64 values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to Int64.</returns>
public static IEnumerable<long> GetDataAsInt64(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data.Select(Convert.ToInt64);
case MiNum<byte> byteElement:
return byteElement.Data.Select(Convert.ToInt64);
case MiNum<int> intElement:
return intElement.Data.Select(Convert.ToInt64);
case MiNum<uint> uintElement:
return uintElement.Data.Select(Convert.ToInt64);
case MiNum<short> shortElement:
return shortElement.Data.Select(Convert.ToInt64);
case MiNum<ushort> ushortElement:
return ushortElement.Data.Select(Convert.ToInt64);
case MiNum<long> longElement:
return longElement.Data;
case MiNum<ulong> ulongElement:
return ulongElement.Data.Select(Convert.ToInt64);
case MiNum<float> floatElement:
return floatElement.Data.Select(Convert.ToInt64);
case MiNum<double> doubleElement:
return doubleElement.Data.Select(Convert.ToInt64);
}
throw new HandlerException(
$"Expected data element that would be convertible to int64, found {element.GetType()}.");
}
/// <summary>
/// Convert the contents of the Matlab data element to a sequence of UInt64 values.
/// </summary>
/// <param name="element">Data element.</param>
/// <returns>Contents of the elements, converted to UInt64.</returns>
public static IEnumerable<ulong> GetDataAsUInt64(DataElement element)
{
switch (element)
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data.Select(Convert.ToUInt64);
case MiNum<byte> byteElement:
return byteElement.Data.Select(Convert.ToUInt64);
case MiNum<int> intElement:
return intElement.Data.Select(Convert.ToUInt64);
case MiNum<uint> uintElement:
return uintElement.Data.Select(Convert.ToUInt64);
case MiNum<short> shortElement:
return shortElement.Data.Select(Convert.ToUInt64);
case MiNum<ushort> ushortElement:
return ushortElement.Data.Select(Convert.ToUInt64);
case MiNum<long> longElement:
return longElement.Data.Select(Convert.ToUInt64);
case MiNum<ulong> ulongElement:
return ulongElement.Data;
case MiNum<float> floatElement:
return floatElement.Data.Select(Convert.ToUInt64);
case MiNum<double> doubleElement:
return doubleElement.Data.Select(Convert.ToUInt64);
}
throw new HandlerException(
$"Expected data element that would be convertible to uint64, found {element.GetType()}.");
}
}
}

View File

@ -0,0 +1,56 @@
// Copyright 2017 Alexander Luzgarev
using System;
namespace MatFileHandler
{
/// <summary>
/// Helpers for working with Matlab data types.
/// </summary>
internal static class DataTypeExtensions
{
/// <summary>
/// Get data type size in bytes.
/// </summary>
/// <param name="type">A data type.</param>
/// <returns>Size in bytes.</returns>
public static int Size(this DataType type)
{
switch (type)
{
case DataType.MiInt8:
return 1;
case DataType.MiUInt8:
return 1;
case DataType.MiInt16:
return 2;
case DataType.MiUInt16:
return 2;
case DataType.MiInt32:
return 4;
case DataType.MiUInt32:
return 4;
case DataType.MiSingle:
return 4;
case DataType.MiDouble:
return 8;
case DataType.MiInt64:
return 8;
case DataType.MiUInt64:
return 8;
case DataType.MiMatrix:
return 0;
case DataType.MiCompressed:
return 0;
case DataType.MiUtf8:
return 1;
case DataType.MiUtf16:
return 2;
case DataType.MiUtf32:
return 4;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
}
}

View File

@ -0,0 +1,40 @@
// Copyright 2017 Alexander Luzgarev
using System.Linq;
namespace MatFileHandler
{
/// <summary>
/// Extension method related to dimension calculations.
/// </summary>
public static class DimensionCalculator
{
/// <summary>
/// Convert a sequence of indices into a single index (according to Matlab rules).
/// </summary>
/// <param name="dimensions">Dimensions of an array.</param>
/// <param name="indices">A sequence of indices.</param>
/// <returns>Index of the corresponding element in the array.</returns>
public static int DimFlatten(this int[] dimensions, int[] indices)
{
var product = 1;
var result = 0;
for (var i = 0; i < indices.Length; i++)
{
result += product * indices[i];
product *= dimensions[i];
}
return result;
}
/// <summary>
/// Calculate the number of elements in an array given its dimensions.
/// </summary>
/// <param name="dimensions">Dimensions of the array.</param>
/// <returns>Total number of elements in an array.</returns>
public static int NumberOfElements(this int[] dimensions)
{
return dimensions.Aggregate(1, (x, y) => x * y);
}
}
}

View File

@ -0,0 +1,22 @@
// Copyright 2017 Alexander Luzgarev
using System;
namespace MatFileHandler
{
/// <summary>
/// Exception related to Matlab data handling
/// </summary>
public class HandlerException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="HandlerException"/> class.
/// </summary>
/// <param name="message">Error message.</param>
/// <param name="innerException">Inner exception.</param>
public HandlerException(string message, Exception innerException = null)
: base(message, innerException)
{
}
}
}

98
MatFileHandler/Header.cs Executable file
View File

@ -0,0 +1,98 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace MatFileHandler
{
/// <summary>
/// Header of a .mat file.
/// </summary>
internal class Header
{
private Header(string text, byte[] subsystemDataOffset, int version)
{
Text = text;
SubsystemDataOffset = subsystemDataOffset;
Version = version;
}
/// <summary>
/// Gets the header text.
/// </summary>
public string Text { get; }
/// <summary>
/// Gets subsystem data offset.
/// </summary>
public byte[] SubsystemDataOffset { get; }
/// <summary>
/// Gets file version.
/// </summary>
public int Version { get; }
/// <summary>
/// Creates a new header for a .mat file.
/// </summary>
/// <returns>A header.</returns>
public static Header CreateNewHeader()
{
var subsystemDataOffset = Enumerable.Repeat((byte)0, 8).ToArray();
var dateTime = DateTime.Now.ToString(CultureInfo.InvariantCulture);
var length = 116 - "MATLAB 5.0 MAT-file, Platform: , Created on: ".Length - dateTime.Length;
var platform = GetOperatingSystem();
var padding = string.Empty;
if (platform.Length < length)
{
padding = padding.PadRight(length - platform.Length);
}
else
{
platform = platform.Remove(length);
}
var text = $"MATLAB 5.0 MAT-file, Platform: {platform}, Created on: {dateTime}{padding}";
return new Header(text, subsystemDataOffset, 256);
}
/// <summary>
/// Read header from file.
/// </summary>
/// <param name="reader">Binary reader.</param>
/// <returns>The header read.</returns>
public static Header Read(BinaryReader reader)
{
var textBytes = reader.ReadBytes(116);
var text = System.Text.Encoding.UTF8.GetString(textBytes);
var subsystemDataOffset = reader.ReadBytes(8);
var version = reader.ReadInt16();
var endian = reader.ReadInt16();
var isLittleEndian = endian == 19785;
if (!isLittleEndian)
{
throw new NotSupportedException("Big-endian files are not supported.");
}
return new Header(text, subsystemDataOffset, version);
}
private static string GetOperatingSystem()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return "Windows";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return "macOS";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "Linux";
}
return "Unknown";
}
}
}

39
MatFileHandler/IArray.cs Executable file
View File

@ -0,0 +1,39 @@
// Copyright 2017 Alexander Luzgarev
using System.Numerics;
namespace MatFileHandler
{
/// <summary>
/// Parent data accessing interface for all Matlab classes.
/// </summary>
public interface IArray
{
/// <summary>
/// Gets a value indicating whether the array is empty.
/// </summary>
bool IsEmpty { get; }
/// <summary>
/// Gets dimensions of the array.
/// </summary>
int[] Dimensions { get; }
/// <summary>
/// Gets the number of elements in the array.
/// </summary>
int NumberOfElements { get; }
/// <summary>
/// Tries to convert the array to an array of Double values.
/// </summary>
/// <returns>Array of values of the array, converted to Double, or null if the conversion is not possible.</returns>
double[] ConvertToDoubleArray();
/// <summary>
/// Tries to convert the array to an array of Complex values.
/// </summary>
/// <returns>Array of values of the array, converted to Complex, or null if the conversion is not possible.</returns>
Complex[] ConvertToComplexArray();
}
}

39
MatFileHandler/IArrayOf.cs Executable file
View File

@ -0,0 +1,39 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// An interface providing access to array's contents.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
/// <remarks>
/// Possible values of T
/// * for numerical arrays:
/// Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double,
/// ComplexOf&lt;TReal&gt; (where TReal is one of Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single),
/// Complex;
/// * for sparse arrays:
/// Double, Complex, Boolean;
/// * for character arrays:
/// UInt8, UInt16, Char;
/// * for logical arrays:
/// Boolean;
/// * for cell arrays:
/// IArray;
/// * for structure arrays:
/// IReadOnlyDictionary&lt;string, IArray&gt;;
/// </remarks>
public interface IArrayOf<T> : IArray
{
/// <summary>
/// Gets all data as an array.
/// </summary>
T[] Data { get; }
/// <summary>
/// Get an element by index.
/// </summary>
/// <param name="list">Index of the element.</param>
T this[params int[] list] { get; set; }
}
}

11
MatFileHandler/ICellArray.cs Executable file
View File

@ -0,0 +1,11 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// Matlab's cell array.
/// </summary>
public interface ICellArray : IArrayOf<IArray>
{
}
}

15
MatFileHandler/ICharArray.cs Executable file
View File

@ -0,0 +1,15 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// Matlab's character array.
/// </summary>
public interface ICharArray : IArrayOf<char>
{
/// <summary>
/// Gets the contained string.
/// </summary>
string String { get; }
}
}

24
MatFileHandler/IMatFile.cs Executable file
View File

@ -0,0 +1,24 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// An interface for accessing the contents of .mat files.
/// </summary>
public interface IMatFile
{
/// <summary>
/// Gets a list of variables present in the file.
/// </summary>
/// <remarks>
/// Variables are in the order in which they appear in the .mat file.
/// </remarks>
IVariable[] Variables { get; }
/// <summary>
/// Lookup variable by name.
/// </summary>
/// <param name="variableName">Variable name.</param>
IVariable this[string variableName] { get; set; }
}
}

View File

@ -0,0 +1,20 @@
// Copyright 2017 Alexander Luzgarev
using System.Collections.Generic;
namespace MatFileHandler
{
/// <summary>
/// Matlab's sparse array.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
/// <remarks>Possible values of T: Double, Complex, Boolean.</remarks>
public interface ISparseArrayOf<T> : IArrayOf<T>
where T : struct
{
/// <summary>
/// Gets a dictionary mapping indices to values.
/// </summary>
new IReadOnlyDictionary<(int, int), T> Data { get; }
}
}

View File

@ -0,0 +1,24 @@
// Copyright 2017 Alexander Luzgarev
using System.Collections.Generic;
namespace MatFileHandler
{
/// <summary>
/// Matlab's structure array.
/// </summary>
public interface IStructureArray : IArrayOf<IReadOnlyDictionary<string, IArray>>
{
/// <summary>
/// Gets a list of all fields in the structure.
/// </summary>
IEnumerable<string> FieldNames { get; }
/// <summary>
/// Get value of a given structure's field.
/// </summary>
/// <param name="field">Field name.</param>
/// <param name="list">Index of the element in the structure array.</param>
IArray this[string field, params int[] list] { get; set; }
}
}

25
MatFileHandler/IVariable.cs Executable file
View File

@ -0,0 +1,25 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// An interface for accessing the variable contents.
/// </summary>
public interface IVariable
{
/// <summary>
/// Gets or sets the name of the variable.
/// </summary>
string Name { get; set; }
/// <summary>
/// Gets the value of the variable.
/// </summary>
IArray Value { get; }
/// <summary>
/// Gets a value indicating whether the variable is global.
/// </summary>
bool IsGlobal { get; }
}
}

68
MatFileHandler/MatArray.cs Executable file
View File

@ -0,0 +1,68 @@
// Copyright 2017 Alexander Luzgarev
using System.Numerics;
namespace MatFileHandler
{
/// <summary>
/// Base class for various Matlab arrays.
/// </summary>
internal class MatArray : DataElement, IArray
{
/// <summary>
/// Initializes a new instance of the <see cref="MatArray"/> class.
/// </summary>
/// <param name="flags">Array properties.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <param name="name">Array name.</param>
protected MatArray(
ArrayFlags flags,
int[] dimensions,
string name)
{
Flags = flags;
Dimensions = dimensions;
Name = name;
}
/// <inheritdoc />
public int[] Dimensions { get; }
/// <summary>
/// Gets the array name.
/// </summary>
public string Name { get; }
/// <inheritdoc />
public int NumberOfElements => Dimensions.NumberOfElements();
/// <inheritdoc />
public bool IsEmpty => Dimensions.Length == 0;
/// <summary>
/// Gets properties of the array.
/// </summary>
internal ArrayFlags Flags { get; }
/// <summary>
/// Returns a new empty array.
/// </summary>
/// <returns>Empty array.</returns>
public static MatArray Empty()
{
return new MatArray(new ArrayFlags { Class = ArrayType.MxCell, Variable = 0 }, new int[] { }, string.Empty);
}
/// <inheritdoc />
public virtual double[] ConvertToDoubleArray()
{
return null;
}
/// <inheritdoc />
public virtual Complex[] ConvertToComplexArray()
{
return null;
}
}
}

36
MatFileHandler/MatCellArray.cs Executable file
View File

@ -0,0 +1,36 @@
// Copyright 2017 Alexander Luzgarev
using System.Collections.Generic;
using System.Linq;
namespace MatFileHandler
{
/// <summary>
/// Cell array.
/// </summary>
internal class MatCellArray : MatArray, ICellArray
{
/// <summary>
/// Initializes a new instance of the <see cref="MatCellArray"/> class.
/// </summary>
/// <param name="flags">Array properties.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <param name="name">Array name.</param>
/// <param name="elements">Array elements.</param>
public MatCellArray(ArrayFlags flags, int[] dimensions, string name, IEnumerable<IArray> elements)
: base(flags, dimensions, name)
{
Data = elements.ToArray();
}
/// <inheritdoc />
public IArray[] Data { get; }
/// <inheritdoc />
public IArray this[params int[] indices]
{
get => Data[Dimensions.DimFlatten(indices)];
set => Data[Dimensions.DimFlatten(indices)] = value;
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// Character array.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
/// <remarks>
/// Possible values of T: UInt8 (for UTF-8 encoded arrays), UInt16 (for UTF-16 encoded arrays).
/// </remarks>
internal class MatCharArrayOf<T> : MatNumericalArrayOf<T>, ICharArray
where T : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="MatCharArrayOf{T}"/> class.
/// </summary>
/// <param name="flags">Array parameters.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <param name="name">Array name.</param>
/// <param name="rawData">Raw data (UTF-8 or UTF-16).</param>
/// <param name="stringData">Contents as a string.</param>
internal MatCharArrayOf(ArrayFlags flags, int[] dimensions, string name, T[] rawData, string stringData)
: base(flags, dimensions, name, rawData)
{
StringData = stringData;
}
/// <summary>
/// Gets the contents of the array as a string.
/// </summary>
public string String => StringData;
/// <summary>
/// Gets the contents of the array as a char array.
/// </summary>
char[] IArrayOf<char>.Data => StringData.ToCharArray();
private string StringData { get; set; }
/// <summary>
/// Provides access to the characters of the string contents.
/// </summary>
/// <param name="indices">Indices of an element.</param>
/// <returns>Value of the element.</returns>
char IArrayOf<char>.this[params int[] indices]
{
get => StringData[Dimensions.DimFlatten(indices)];
set
{
var chars = StringData.ToCharArray();
chars[Dimensions.DimFlatten(indices)] = value;
StringData = chars.ToString();
}
}
}
}

38
MatFileHandler/MatFile.cs Executable file
View File

@ -0,0 +1,38 @@
// Copyright 2017 Alexander Luzgarev
using System.Collections.Generic;
using System.Linq;
namespace MatFileHandler
{
/// <summary>
/// .mat file.
/// </summary>
internal class MatFile : IMatFile
{
private readonly Dictionary<string, IVariable> _variables;
/// <summary>
/// Initializes a new instance of the <see cref="MatFile"/> class.
/// </summary>
/// <param name="variables">List of variables.</param>
public MatFile(IEnumerable<IVariable> variables)
{
_variables = new Dictionary<string, IVariable>();
foreach (var variable in variables)
{
_variables[variable.Name] = variable;
}
}
/// <inheritdoc />
public IVariable[] Variables => _variables.Values.ToArray();
/// <inheritdoc />
public IVariable this[string variableName]
{
get => _variables[variableName];
set => _variables[variableName] = value;
}
}
}

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageVersion>1.0.0</PackageVersion>
<PackageId>MatFileHandler</PackageId>
<Title>A library for reading MATLAB .mat files.</Title>
<Authors>Alexander Luzgarev</Authors>
<Description>MatFileHandler provides a simple interface for reading MATLAB-produced
.mat files (of so-called "Level 5") and extracting the contents of numerical arrays,
logical arrays, sparse arrays, char arrays, cell arrays and structure arrays.
</Description>
<Copyright>Copyright 2017 Alexander Luzgarev</Copyright>
<PackageLicenseUrl>https://raw.githubusercontent.com/mahalex/MatFileHandler/master/LICENSE.md</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/mahalex/MatFileHandler</PackageProjectUrl>
<PackageReleaseNotes>First release.</PackageReleaseNotes>
<PackageTags>Matlab</PackageTags>
<RepositoryUrl>https://github.com/mahalex/MatFileHandler</RepositoryUrl>
<DocumentationFile>bin\Debug\netstandard2.0\MatFileHandler.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004" />
</ItemGroup>
</Project>

67
MatFileHandler/MatFileReader.cs Executable file
View File

@ -0,0 +1,67 @@
// Copyright 2017 Alexander Luzgarev
using System.Collections.Generic;
using System.IO;
namespace MatFileHandler
{
/// <summary>
/// Class for reading .mat files.
/// </summary>
public class MatFileReader
{
/// <summary>
/// Initializes a new instance of the <see cref="MatFileReader"/> class with a stream.
/// </summary>
/// <param name="stream">Input stream.</param>
public MatFileReader(Stream stream)
{
Stream = stream;
}
private Stream Stream { get; }
/// <summary>
/// Reads the contents of a .mat file from the stream.
/// </summary>
/// <returns>Contents of the file.</returns>
public IMatFile Read()
{
using (var reader = new BinaryReader(Stream))
{
return Read(reader);
}
}
private static void ReadHeader(BinaryReader reader)
{
Header.Read(reader);
}
private static IMatFile Read(BinaryReader reader)
{
ReadHeader(reader);
var variables = new List<IVariable>();
while (true)
{
try
{
var dataElement = DataElementReader.Read(reader) as MatArray;
if (dataElement == null)
{
continue;
}
variables.Add(new MatVariable(
dataElement,
dataElement.Name,
dataElement.Flags.Variable.HasFlag(Variable.IsGlobal)));
}
catch (EndOfStreamException)
{
break;
}
}
return new MatFile(variables);
}
}
}

643
MatFileHandler/MatFileWriter.cs Executable file
View File

@ -0,0 +1,643 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Numerics;
using System.Text;
namespace MatFileHandler
{
/// <summary>
/// Class for writing .mat files.
/// </summary>
public class MatFileWriter
{
/// <summary>
/// Initializes a new instance of the <see cref="MatFileWriter"/> class with a stream.
/// </summary>
/// <param name="stream">Output stream.</param>
public MatFileWriter(Stream stream)
{
Stream = stream;
}
private Stream Stream { get; }
/// <summary>
/// Writes a .mat file.
/// </summary>
/// <param name="file">A file to write.</param>
public void Write(IMatFile file)
{
var header = Header.CreateNewHeader();
using (var writer = new BinaryWriter(Stream))
{
WriteHeader(writer, header);
foreach (var variable in file.Variables)
{
WriteCompressedVariable(writer, variable);
}
}
}
private static uint CalculateAdler32Checksum(Stream stream)
{
uint s1 = 1;
uint s2 = 0;
const uint bigPrime = 0xFFF1;
const int bufferSize = 2048;
var buffer = new byte[bufferSize];
while (true)
{
var bytesRead = stream.Read(buffer, 0, bufferSize);
for (var i = 0; i < bytesRead; i++)
{
s1 = (s1 + buffer[i]) % bigPrime;
s2 = (s2 + s1) % bigPrime;
}
if (bytesRead < bufferSize)
{
break;
}
}
return (s2 << 16) | s1;
}
private void WriteHeader(BinaryWriter writer, Header header)
{
writer.Write(Encoding.UTF8.GetBytes(header.Text));
writer.Write(header.SubsystemDataOffset);
writer.Write((short)header.Version);
writer.Write((short)19785); // Magic number, 'IM'.
}
private void WriteTag(BinaryWriter writer, Tag tag)
{
writer.Write((int)tag.Type);
writer.Write(tag.Length);
}
private void WriteShortTag(BinaryWriter writer, Tag tag)
{
writer.Write((short)tag.Type);
writer.Write((short)tag.Length);
}
private void WritePadding(BinaryWriter writer)
{
var positionMod8 = writer.BaseStream.Position % 8;
if (positionMod8 != 0)
{
writer.Write(new byte[8 - positionMod8]);
}
}
private void WriteDataElement(BinaryWriter writer, DataType type, byte[] data)
{
if (data.Length > 4)
{
WriteTag(writer, new Tag(type, data.Length));
writer.Write(data);
}
else
{
WriteShortTag(writer, new Tag(type, data.Length));
writer.Write(data);
if (data.Length < 4)
{
var padding = new byte[4 - data.Length];
writer.Write(padding);
}
}
WritePadding(writer);
}
private void WriteDimensions(BinaryWriter writer, int[] dimensions)
{
var buffer = ConvertToByteArray(dimensions);
WriteDataElement(writer, DataType.MiInt32, buffer);
}
private byte[] ConvertToByteArray<T>(T[] data)
where T : struct
{
int size;
if (typeof(T) == typeof(sbyte))
{
size = sizeof(sbyte);
}
else if (typeof(T) == typeof(byte))
{
size = sizeof(byte);
}
else if (typeof(T) == typeof(short))
{
size = sizeof(short);
}
else if (typeof(T) == typeof(ushort))
{
size = sizeof(ushort);
}
else if (typeof(T) == typeof(int))
{
size = sizeof(int);
}
else if (typeof(T) == typeof(uint))
{
size = sizeof(uint);
}
else if (typeof(T) == typeof(long))
{
size = sizeof(long);
}
else if (typeof(T) == typeof(ulong))
{
size = sizeof(ulong);
}
else if (typeof(T) == typeof(float))
{
size = sizeof(float);
}
else if (typeof(T) == typeof(double))
{
size = sizeof(double);
}
else
{
throw new NotSupportedException();
}
var buffer = new byte[data.Length * size];
Buffer.BlockCopy(data, 0, buffer, 0, buffer.Length);
return buffer;
}
private (byte[], byte[]) ConvertToPairOfByteArrays<T>(ComplexOf<T>[] data)
where T : struct
{
return (ConvertToByteArray(data.Select(x => x.Real).ToArray()),
ConvertToByteArray(data.Select(x => x.Imaginary).ToArray()));
}
private (byte[], byte[]) ConvertToPairOfByteArrays(Complex[] data)
{
return (ConvertToByteArray(data.Select(x => x.Real).ToArray()),
ConvertToByteArray(data.Select(x => x.Imaginary).ToArray()));
}
private void WriteComplexValues(BinaryWriter writer, DataType type, (byte[] real, byte[] complex) data)
{
WriteDataElement(writer, type, data.real);
WriteDataElement(writer, type, data.complex);
}
private void WriteArrayFlags(BinaryWriter writer, ArrayFlags flags)
{
var flag = (byte)flags.Variable;
WriteTag(writer, new Tag(DataType.MiUInt32, 8));
writer.Write((byte)flags.Class);
writer.Write(flag);
writer.Write(new byte[] { 0, 0, 0, 0, 0, 0 });
}
private void WriteSparseArrayFlags(BinaryWriter writer, SparseArrayFlags flags)
{
var flag = (byte)flags.ArrayFlags.Variable;
WriteTag(writer, new Tag(DataType.MiUInt32, 8));
writer.Write((byte)flags.ArrayFlags.Class);
writer.Write(flag);
writer.Write(new byte[] { 0, 0 });
writer.Write(flags.NzMax);
}
private void WriteName(BinaryWriter writer, string name)
{
var nameBytes = Encoding.ASCII.GetBytes(name);
WriteDataElement(writer, DataType.MiInt8, nameBytes);
}
private void WriteNumericalArrayValues(BinaryWriter writer, IArray value)
{
switch (value)
{
case IArrayOf<sbyte> sbyteArray:
WriteDataElement(writer, DataType.MiInt8, ConvertToByteArray(sbyteArray.Data));
break;
case IArrayOf<byte> byteArray:
WriteDataElement(writer, DataType.MiUInt8, ConvertToByteArray(byteArray.Data));
break;
case IArrayOf<short> shortArray:
WriteDataElement(writer, DataType.MiInt16, ConvertToByteArray(shortArray.Data));
break;
case IArrayOf<ushort> ushortArray:
WriteDataElement(writer, DataType.MiUInt16, ConvertToByteArray(ushortArray.Data));
break;
case IArrayOf<int> intArray:
WriteDataElement(writer, DataType.MiInt32, ConvertToByteArray(intArray.Data));
break;
case IArrayOf<uint> uintArray:
WriteDataElement(writer, DataType.MiUInt32, ConvertToByteArray(uintArray.Data));
break;
case IArrayOf<long> longArray:
WriteDataElement(writer, DataType.MiInt64, ConvertToByteArray(longArray.Data));
break;
case IArrayOf<ulong> ulongArray:
WriteDataElement(writer, DataType.MiUInt64, ConvertToByteArray(ulongArray.Data));
break;
case IArrayOf<float> floatArray:
WriteDataElement(writer, DataType.MiSingle, ConvertToByteArray(floatArray.Data));
break;
case IArrayOf<double> doubleArray:
WriteDataElement(writer, DataType.MiDouble, ConvertToByteArray(doubleArray.Data));
break;
case IArrayOf<bool> boolArray:
WriteDataElement(
writer,
DataType.MiUInt8,
boolArray.Data.Select(element => element ? (byte)1 : (byte)0).ToArray());
break;
case IArrayOf<ComplexOf<sbyte>> complexSbyteArray:
WriteComplexValues(writer, DataType.MiInt8, ConvertToPairOfByteArrays(complexSbyteArray.Data));
break;
case IArrayOf<ComplexOf<byte>> complexByteArray:
WriteComplexValues(writer, DataType.MiUInt8, ConvertToPairOfByteArrays(complexByteArray.Data));
break;
case IArrayOf<ComplexOf<short>> complexShortArray:
WriteComplexValues(writer, DataType.MiInt16, ConvertToPairOfByteArrays(complexShortArray.Data));
break;
case IArrayOf<ComplexOf<ushort>> complexUshortArray:
WriteComplexValues(writer, DataType.MiUInt16, ConvertToPairOfByteArrays(complexUshortArray.Data));
break;
case IArrayOf<ComplexOf<int>> complexIntArray:
WriteComplexValues(writer, DataType.MiInt32, ConvertToPairOfByteArrays(complexIntArray.Data));
break;
case IArrayOf<ComplexOf<uint>> complexUintArray:
WriteComplexValues(writer, DataType.MiUInt32, ConvertToPairOfByteArrays(complexUintArray.Data));
break;
case IArrayOf<ComplexOf<long>> complexLongArray:
WriteComplexValues(writer, DataType.MiInt64, ConvertToPairOfByteArrays(complexLongArray.Data));
break;
case IArrayOf<ComplexOf<ulong>> complexUlongArray:
WriteComplexValues(writer, DataType.MiUInt64, ConvertToPairOfByteArrays(complexUlongArray.Data));
break;
case IArrayOf<ComplexOf<float>> complexFloatArray:
WriteComplexValues(writer, DataType.MiSingle, ConvertToPairOfByteArrays(complexFloatArray.Data));
break;
case IArrayOf<Complex> complexDoubleArray:
WriteComplexValues(writer, DataType.MiDouble, ConvertToPairOfByteArrays(complexDoubleArray.Data));
break;
default:
throw new NotSupportedException();
}
}
private ArrayFlags GetArrayFlags(IArray array, bool isGlobal)
{
var variableFlags = isGlobal ? Variable.IsGlobal : 0;
switch (array)
{
case IArrayOf<sbyte> _:
return new ArrayFlags(ArrayType.MxInt8, variableFlags);
case IArrayOf<byte> _:
return new ArrayFlags(ArrayType.MxUInt8, variableFlags);
case IArrayOf<short> _:
return new ArrayFlags(ArrayType.MxInt16, variableFlags);
case IArrayOf<ushort> _:
return new ArrayFlags(ArrayType.MxUInt16, variableFlags);
case IArrayOf<int> _:
return new ArrayFlags(ArrayType.MxInt32, variableFlags);
case IArrayOf<uint> _:
return new ArrayFlags(ArrayType.MxUInt32, variableFlags);
case IArrayOf<long> _:
return new ArrayFlags(ArrayType.MxInt64, variableFlags);
case IArrayOf<ulong> _:
return new ArrayFlags(ArrayType.MxUInt64, variableFlags);
case IArrayOf<float> _:
return new ArrayFlags(ArrayType.MxSingle, variableFlags);
case IArrayOf<double> _:
return new ArrayFlags(ArrayType.MxDouble, variableFlags);
case IArrayOf<bool> _:
return new ArrayFlags(ArrayType.MxUInt8, variableFlags | Variable.IsLogical);
case IArrayOf<ComplexOf<sbyte>> _:
return new ArrayFlags(ArrayType.MxInt8, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<byte>> _:
return new ArrayFlags(ArrayType.MxUInt8, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<short>> _:
return new ArrayFlags(ArrayType.MxInt16, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<ushort>> _:
return new ArrayFlags(ArrayType.MxUInt16, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<int>> _:
return new ArrayFlags(ArrayType.MxInt32, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<uint>> _:
return new ArrayFlags(ArrayType.MxUInt32, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<long>> _:
return new ArrayFlags(ArrayType.MxInt64, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<ulong>> _:
return new ArrayFlags(ArrayType.MxUInt64, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<float>> _:
return new ArrayFlags(ArrayType.MxSingle, variableFlags | Variable.IsComplex);
case IArrayOf<Complex> _:
return new ArrayFlags(ArrayType.MxDouble, variableFlags | Variable.IsComplex);
case IStructureArray _:
return new ArrayFlags(ArrayType.MxStruct, variableFlags);
case ICellArray _:
return new ArrayFlags(ArrayType.MxCell, variableFlags);
default:
throw new NotSupportedException();
}
}
private SparseArrayFlags GetSparseArrayFlags<T>(ISparseArrayOf<T> array, bool isGlobal, uint nonZero)
where T : struct
{
var flags = GetArrayFlags(array, isGlobal);
return new SparseArrayFlags
{
ArrayFlags = new ArrayFlags
{
Class = ArrayType.MxSparse,
Variable = flags.Variable,
},
NzMax = nonZero,
};
}
private ArrayFlags GetCharArrayFlags(bool isGlobal)
{
return new ArrayFlags(ArrayType.MxChar, isGlobal ? Variable.IsGlobal : 0);
}
private void WriteWrappingContents<T>(BinaryWriter writer, T array, Action<BinaryWriter> writeContents)
where T : IArray
{
if (array.IsEmpty)
{
WriteTag(writer, new Tag(DataType.MiMatrix, 0));
return;
}
using (var contents = new MemoryStream())
{
using (var contentsWriter = new BinaryWriter(contents))
{
writeContents(contentsWriter);
WriteTag(writer, new Tag(DataType.MiMatrix, (int)contents.Length));
contents.Position = 0;
contents.CopyTo(writer.BaseStream);
}
}
}
private void WriteNumericalArrayContents(BinaryWriter writer, IArray array, string name, bool isGlobal)
{
WriteArrayFlags(writer, GetArrayFlags(array, isGlobal));
WriteDimensions(writer, array.Dimensions);
WriteName(writer, name);
WriteNumericalArrayValues(writer, array);
}
private void WriteNumericalArray(
BinaryWriter writer,
IArray numericalArray,
string name = "",
bool isGlobal = false)
{
WriteWrappingContents(
writer,
numericalArray,
contentsWriter => { WriteNumericalArrayContents(contentsWriter, numericalArray, name, isGlobal); });
}
private void WriteCharArrayContents(BinaryWriter writer, ICharArray charArray, string name, bool isGlobal)
{
WriteArrayFlags(writer, GetCharArrayFlags(isGlobal));
WriteDimensions(writer, charArray.Dimensions);
WriteName(writer, name);
var array = charArray.String.ToCharArray().Select(c => (ushort)c).ToArray();
WriteDataElement(writer, DataType.MiUtf16, ConvertToByteArray(array));
}
private void WriteCharArray(BinaryWriter writer, ICharArray charArray, string name, bool isGlobal)
{
WriteWrappingContents(
writer,
charArray,
contentsWriter => { WriteCharArrayContents(contentsWriter, charArray, name, isGlobal); });
}
private void WriteSparseArrayValues<T>(
BinaryWriter writer, int[] rows, int[] columns, T[] data)
where T : struct
{
WriteDataElement(writer, DataType.MiInt32, ConvertToByteArray(rows));
WriteDataElement(writer, DataType.MiInt32, ConvertToByteArray(columns));
if (data is double[])
{
WriteDataElement(writer, DataType.MiDouble, ConvertToByteArray(data));
}
else if (data is Complex[])
{
var complexData = data as Complex[];
WriteDataElement(
writer,
DataType.MiDouble,
ConvertToByteArray(complexData.Select(c => c.Real).ToArray()));
WriteDataElement(
writer,
DataType.MiDouble,
ConvertToByteArray(complexData.Select(c => c.Imaginary).ToArray()));
}
else if (data is bool[])
{
var boolData = data as bool[];
WriteDataElement(
writer,
DataType.MiUInt8,
boolData.Select(element => element ? (byte)1 : (byte)0).ToArray());
}
}
private (int[] rowIndex, int[] columnIndex, T[] data, uint nonZero) PrepareSparseArrayData<T>(
ISparseArrayOf<T> array)
where T : struct, IEquatable<T>
{
var dict = array.Data;
var keys = dict.Keys.ToArray();
var rowIndexList = new List<int>();
var valuesList = new List<T>();
var numberOfColumns = array.Dimensions[1];
var columnIndex = new int[numberOfColumns + 1];
columnIndex[0] = 0;
for (var column = 0; column < numberOfColumns; column++)
{
var column1 = column;
var thisColumn = keys.Where(pair => pair.Item2 == column1 && !dict[pair].Equals(default(T)));
var thisRow = thisColumn.Select(pair => pair.Item1).OrderBy(x => x).ToArray();
rowIndexList.AddRange(thisRow);
valuesList.AddRange(thisRow.Select(row => dict[(row, column1)]));
columnIndex[column + 1] = rowIndexList.Count;
}
return (rowIndexList.ToArray(), columnIndex, valuesList.ToArray(), (uint)rowIndexList.Count);
}
private void WriteSparseArrayContents<T>(
BinaryWriter writer,
ISparseArrayOf<T> array,
string name,
bool isGlobal)
where T : struct, IEquatable<T>
{
(var rows, var columns, var data, var nonZero) = PrepareSparseArrayData(array);
WriteSparseArrayFlags(writer, GetSparseArrayFlags(array, isGlobal, nonZero));
WriteDimensions(writer, array.Dimensions);
WriteName(writer, name);
WriteSparseArrayValues(writer, rows, columns, data);
}
private void WriteSparseArray<T>(BinaryWriter writer, ISparseArrayOf<T> sparseArray, string name, bool isGlobal)
where T : struct, IEquatable<T>
{
WriteWrappingContents(
writer,
sparseArray,
contentsWriter => { WriteSparseArrayContents(contentsWriter, sparseArray, name, isGlobal); });
}
private void WriteFieldNames(BinaryWriter writer, IEnumerable<string> fieldNames)
{
var fieldNamesArray = fieldNames.Select(name => Encoding.ASCII.GetBytes(name)).ToArray();
var maxFieldName = fieldNamesArray.Select(name => name.Length).Max() + 1;
WriteDataElement(writer, DataType.MiInt32, ConvertToByteArray(new[] { maxFieldName }));
var buffer = new byte[fieldNamesArray.Length * maxFieldName];
var startPosition = 0;
foreach (var name in fieldNamesArray)
{
for (var i = 0; i < name.Length; i++)
{
buffer[startPosition + i] = name[i];
}
startPosition += maxFieldName;
}
WriteDataElement(writer, DataType.MiInt8, buffer);
}
private void WriteStructureArrayValues(BinaryWriter writer, IStructureArray array)
{
for (var i = 0; i < array.NumberOfElements; i++)
{
foreach (var name in array.FieldNames)
{
WriteArray(writer, array[name, i]);
}
}
}
private void WriteStructureArrayContents(BinaryWriter writer, IStructureArray array, string name, bool isGlobal)
{
WriteArrayFlags(writer, GetArrayFlags(array, isGlobal));
WriteDimensions(writer, array.Dimensions);
WriteName(writer, name);
WriteFieldNames(writer, array.FieldNames);
WriteStructureArrayValues(writer, array);
}
private void WriteStructureArray(
BinaryWriter writer,
IStructureArray structureArray,
string name,
bool isGlobal)
{
WriteWrappingContents(
writer,
structureArray,
contentsWriter => { WriteStructureArrayContents(contentsWriter, structureArray, name, isGlobal); });
}
private void WriteCellArrayValues(BinaryWriter writer, ICellArray array)
{
for (var i = 0; i < array.NumberOfElements; i++)
{
WriteArray(writer, array[i]);
}
}
private void WriteCellArrayContents(BinaryWriter writer, ICellArray array, string name, bool isGlobal)
{
WriteArrayFlags(writer, GetArrayFlags(array, isGlobal));
WriteDimensions(writer, array.Dimensions);
WriteName(writer, name);
WriteCellArrayValues(writer, array);
}
private void WriteCellArray(BinaryWriter writer, ICellArray cellArray, string name, bool isGlobal)
{
WriteWrappingContents(
writer,
cellArray,
contentsWriter => { WriteCellArrayContents(contentsWriter, cellArray, name, isGlobal); });
}
private void WriteArray(BinaryWriter writer, IArray array, string variableName = "", bool isGlobal = false)
{
switch (array)
{
case ICharArray charArray:
WriteCharArray(writer, charArray, variableName, isGlobal);
break;
case ISparseArrayOf<double> doubleSparseArray:
WriteSparseArray(writer, doubleSparseArray, variableName, isGlobal);
break;
case ISparseArrayOf<Complex> complexSparseArray:
WriteSparseArray(writer, complexSparseArray, variableName, isGlobal);
break;
case ISparseArrayOf<bool> boolSparseArray:
WriteSparseArray(writer, boolSparseArray, variableName, isGlobal);
break;
case ICellArray cellArray:
WriteCellArray(writer, cellArray, variableName, isGlobal);
break;
case IStructureArray structureArray:
WriteStructureArray(writer, structureArray, variableName, isGlobal);
break;
default:
WriteNumericalArray(writer, array, variableName, isGlobal);
break;
}
}
private void WriteVariable(BinaryWriter writer, IVariable variable)
{
WriteArray(writer, variable.Value, variable.Name, variable.IsGlobal);
}
private void WriteCompressedVariable(BinaryWriter writer, IVariable variable)
{
using (var compressedStream = new MemoryStream())
{
uint crc;
using (var originalStream = new MemoryStream())
{
using (var internalWriter = new BinaryWriter(originalStream))
{
WriteVariable(internalWriter, variable);
originalStream.Position = 0;
crc = CalculateAdler32Checksum(originalStream);
originalStream.Position = 0;
using (var compressionStream =
new DeflateStream(compressedStream, CompressionMode.Compress, leaveOpen: true))
{
originalStream.CopyTo(compressionStream);
}
}
}
compressedStream.Position = 0;
WriteTag(writer, new Tag(DataType.MiCompressed, (int)(compressedStream.Length + 6)));
writer.Write((byte)0x78);
writer.Write((byte)0x9c);
compressedStream.CopyTo(writer.BaseStream);
writer.Write(BitConverter.GetBytes(crc).Reverse().ToArray());
}
}
}
}

View File

@ -0,0 +1,100 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace MatFileHandler
{
/// <summary>
/// A numerical array.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
internal class MatNumericalArrayOf<T> : MatArray, IArrayOf<T>
where T : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="MatNumericalArrayOf{T}"/> class.
/// </summary>
/// <param name="flags">Array parameters.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <param name="name">Array name.</param>
/// <param name="data">Array contents.</param>
public MatNumericalArrayOf(ArrayFlags flags, int[] dimensions, string name, T[] data)
: base(flags, dimensions, name)
{
Data = data;
}
/// <inheritdoc />
public T[] Data { get; }
/// <inheritdoc />
public T this[params int[] list]
{
get => Data[Dimensions.DimFlatten(list)];
set => Data[Dimensions.DimFlatten(list)] = value;
}
/// <summary>
/// Tries to convert the array to an array of Double values.
/// </summary>
/// <returns>Array of values of the array, converted to Double, or null if the conversion is not possible.</returns>
public override double[] ConvertToDoubleArray()
{
return Data as double[] ?? Data.Select(x => Convert.ToDouble(x)).ToArray();
}
/// <summary>
/// Tries to convert the array to an array of Complex values.
/// </summary>
/// <returns>Array of values of the array, converted to Complex, or null if the conversion is not possible.</returns>
public override Complex[] ConvertToComplexArray()
{
if (Data is Complex[])
{
return Data as Complex[];
}
if (Data is ComplexOf<sbyte>[])
{
return ConvertToComplex(Data as ComplexOf<sbyte>[]);
}
if (Data is ComplexOf<byte>[])
{
return ConvertToComplex(Data as ComplexOf<byte>[]);
}
if (Data is ComplexOf<short>[])
{
return ConvertToComplex(Data as ComplexOf<short>[]);
}
if (Data is ComplexOf<ushort>[])
{
return ConvertToComplex(Data as ComplexOf<ushort>[]);
}
if (Data is ComplexOf<int>[])
{
return ConvertToComplex(Data as ComplexOf<int>[]);
}
if (Data is ComplexOf<uint>[])
{
return ConvertToComplex(Data as ComplexOf<uint>[]);
}
if (Data is ComplexOf<long>[])
{
return ConvertToComplex(Data as ComplexOf<long>[]);
}
if (Data is ComplexOf<ulong>[])
{
return ConvertToComplex(Data as ComplexOf<ulong>[]);
}
return ConvertToDoubleArray().Select(x => new Complex(x, 0.0)).ToArray();
}
private static Complex[] ConvertToComplex<TS>(IEnumerable<ComplexOf<TS>> array)
where TS : struct
{
return array.Select(x => new Complex(Convert.ToDouble(x.Real), Convert.ToDouble(x.Imaginary))).ToArray();
}
}
}

View File

@ -0,0 +1,90 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace MatFileHandler
{
/// <summary>
/// Sparse array.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
/// <remarks>Possible values of T: Double, Complex, Boolean.</remarks>
internal class MatSparseArrayOf<T> : MatArray, ISparseArrayOf<T>
where T : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="MatSparseArrayOf{T}"/> class.
/// </summary>
/// <param name="flags">Array properties.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <param name="name">Array name.</param>
/// <param name="data">Array contents.</param>
public MatSparseArrayOf(
SparseArrayFlags flags,
int[] dimensions,
string name,
Dictionary<(int, int), T> data)
: base(flags.ArrayFlags, dimensions, name)
{
DataDictionary = data;
}
/// <inheritdoc />
T[] IArrayOf<T>.Data =>
Enumerable.Range(0, Dimensions[0] * Dimensions[1])
.Select(i => this[i])
.ToArray();
/// <inheritdoc />
public IReadOnlyDictionary<(int, int), T> Data => DataDictionary;
private Dictionary<(int, int), T> DataDictionary { get; }
/// <inheritdoc />
public T this[params int[] list]
{
get
{
var rowAndColumn = GetRowAndColumn(list);
return DataDictionary.ContainsKey(rowAndColumn) ? DataDictionary[rowAndColumn] : default(T);
}
set => DataDictionary[GetRowAndColumn(list)] = value;
}
/// <summary>
/// Tries to convert the array to an array of Double values.
/// </summary>
/// <returns>Array of values of the array, converted to Double, or null if the conversion is not possible.</returns>
public override double[] ConvertToDoubleArray()
{
var data = ((IArrayOf<T>)this).Data;
return data as double[] ?? data.Select(x => Convert.ToDouble(x)).ToArray();
}
/// <summary>
/// Tries to convert the array to an array of Complex values.
/// </summary>
/// <returns>Array of values of the array, converted to Complex, or null if the conversion is not possible.</returns>
public override Complex[] ConvertToComplexArray()
{
var data = ((IArrayOf<T>)this).Data;
return data as Complex[] ?? ConvertToDoubleArray().Select(x => new Complex(x, 0.0)).ToArray();
}
private (int row, int column) GetRowAndColumn(int[] indices)
{
switch (indices.Length)
{
case 1:
return (indices[0] % Dimensions[0], indices[0] / Dimensions[0]);
case 2:
return (indices[0], indices[1]);
default:
throw new NotSupportedException("Invalid index for sparse array.");
}
}
}
}

View File

@ -0,0 +1,154 @@
// Copyright 2017 Alexander Luzgarev
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace MatFileHandler
{
/// <summary>
/// Structure array.
/// </summary>
internal class MatStructureArray : MatArray, IStructureArray
{
/// <summary>
/// Initializes a new instance of the <see cref="MatStructureArray"/> class.
/// </summary>
/// <param name="flags">Array properties.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <param name="name">Array name.</param>
/// <param name="fields">Array contents.</param>
public MatStructureArray(
ArrayFlags flags,
int[] dimensions,
string name,
Dictionary<string, List<IArray>> fields)
: base(flags, dimensions, name)
{
Fields = fields;
}
/// <inheritdoc />
public IEnumerable<string> FieldNames => Fields.Keys;
/// <summary>
/// Gets null: not implemented.
/// </summary>
public IReadOnlyDictionary<string, IArray>[] Data => null;
/// <summary>
/// Gets a dictionary that maps field names to lists of values.
/// </summary>
internal Dictionary<string, List<IArray>> Fields { get; }
/// <inheritdoc />
public IArray this[string field, params int[] list]
{
get => Fields[field][Dimensions.DimFlatten(list)];
set => Fields[field][Dimensions.DimFlatten(list)] = value;
}
/// <inheritdoc />
IReadOnlyDictionary<string, IArray> IArrayOf<IReadOnlyDictionary<string, IArray>>.this[params int[] list]
{
get => ExtractStructure(Dimensions.DimFlatten(list));
set => throw new NotSupportedException(
"Cannot set structure elements via this[params int[]] indexer. Use this[string, int[]] instead.");
}
private IReadOnlyDictionary<string, IArray> ExtractStructure(int i)
{
return new MatStructureArrayElement(this, i);
}
/// <summary>
/// Provides access to an element of a structure array by fields.
/// </summary>
internal class MatStructureArrayElement : IReadOnlyDictionary<string, IArray>
{
/// <summary>
/// Initializes a new instance of the <see cref="MatStructureArrayElement"/> class.
/// </summary>
/// <param name="parent">Parent structure array.</param>
/// <param name="index">Index in the structure array.</param>
internal MatStructureArrayElement(MatStructureArray parent, int index)
{
Parent = parent;
Index = index;
}
/// <summary>
/// Gets the number of fields.
/// </summary>
public int Count => Parent.Fields.Count;
/// <summary>
/// Gets a list of all fields.
/// </summary>
public IEnumerable<string> Keys => Parent.Fields.Keys;
/// <summary>
/// Gets a list of all values.
/// </summary>
public IEnumerable<IArray> Values => Parent.Fields.Values.Select(array => array[Index]);
private MatStructureArray Parent { get; }
private int Index { get; }
/// <summary>
/// Gets the value of a given field.
/// </summary>
/// <param name="key">Field name.</param>
/// <returns>The corresponding value.</returns>
public IArray this[string key] => Parent.Fields[key][Index];
/// <summary>
/// Enumerates fieldstructure/value pairs of the dictionary.
/// </summary>
/// <returns>All field/value pairs in the structure.</returns>
public IEnumerator<KeyValuePair<string, IArray>> GetEnumerator()
{
foreach (var field in Parent.Fields)
{
yield return new KeyValuePair<string, IArray>(field.Key, field.Value[Index]);
}
}
/// <summary>
/// Enumerates field/value pairs of the structure.
/// </summary>
/// <returns>All field/value pairs in the structure.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Checks if the structure has a given field.
/// </summary>
/// <param name="key">Field name</param>
/// <returns>True iff the structure has a given field.</returns>
public bool ContainsKey(string key) => Parent.Fields.ContainsKey(key);
/// <summary>
/// Tries to get the value of a given field.
/// </summary>
/// <param name="key">Field name.</param>
/// <param name="value">Value (or null if the field is not present).</param>
/// <returns>Success status of the query.</returns>
public bool TryGetValue(string key, out IArray value)
{
var success = Parent.Fields.TryGetValue(key, out var array);
if (!success)
{
value = default(IArray);
return false;
}
value = array[Index];
return true;
}
}
}
}

30
MatFileHandler/MatVariable.cs Executable file
View File

@ -0,0 +1,30 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <inheritdoc />
internal class MatVariable : IVariable
{
/// <summary>
/// Initializes a new instance of the <see cref="MatVariable"/> class.
/// </summary>
/// <param name="value">The value of the variable.</param>
/// <param name="name">Variable name.</param>
/// <param name="isGlobal">Indicates if the variable is global.</param>
public MatVariable(IArray value, string name, bool isGlobal)
{
Value = value;
Name = name;
IsGlobal = isGlobal;
}
/// <inheritdoc />
public string Name { get; set; }
/// <inheritdoc />
public IArray Value { get; }
/// <inheritdoc />
public bool IsGlobal { get; }
}
}

26
MatFileHandler/MiNum.cs Executable file
View File

@ -0,0 +1,26 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// A matrix of type T.
/// </summary>
/// <typeparam name="T">Element type.</typeparam>
internal class MiNum<T> : DataElement
where T : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="MiNum{T}"/> class.
/// </summary>
/// <param name="data">Contents of the matrix.</param>
public MiNum(T[] data)
{
Data = data;
}
/// <summary>
/// Gets the contents of the matrix.
/// </summary>
public T[] Data { get; }
}
}

117
MatFileHandler/Tag.cs Executable file
View File

@ -0,0 +1,117 @@
// Copyright 2017 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// Type of the data attached to the tag.
/// </summary>
internal enum DataType
{
/// <summary>
/// An array of Int8 elements.
/// </summary>
MiInt8 = 1,
/// <summary>
/// An array of UInt8 elements.
/// </summary>
MiUInt8 = 2,
/// <summary>
/// An array of Int16 elements.
/// </summary>
MiInt16 = 3,
/// <summary>
/// An array of UInt16 elements.
/// </summary>
MiUInt16 = 4,
/// <summary>
/// An array of Int32 elements.
/// </summary>
MiInt32 = 5,
/// <summary>
/// An array of UInt32 elements.
/// </summary>
MiUInt32 = 6,
/// <summary>
/// An array of Single elements.
/// </summary>
MiSingle = 7,
/// <summary>
/// An array of Double elements.
/// </summary>
MiDouble = 9,
/// <summary>
/// An array of Int64 elements.
/// </summary>
MiInt64 = 12,
/// <summary>
/// An array of UInt64 elements.
/// </summary>
MiUInt64 = 13,
/// <summary>
/// A matrix.
/// </summary>
MiMatrix = 14,
/// <summary>
/// A compressed data element.
/// </summary>
MiCompressed = 15,
/// <summary>
/// An array of UTF-8 elements.
/// </summary>
MiUtf8 = 16,
/// <summary>
/// An array of UTF-16 elements.
/// </summary>
MiUtf16 = 17,
/// <summary>
/// An array of UTF-32 elements.
/// </summary>
MiUtf32 = 18,
}
/// <summary>
/// Data element tag.
/// </summary>
internal class Tag
{
/// <summary>
/// Initializes a new instance of the <see cref="Tag"/> class.
/// </summary>
/// <param name="type">Data type.</param>
/// <param name="length">Data length (number of elements).</param>
public Tag(DataType type, int length)
{
Type = type;
Length = length;
}
/// <summary>
/// Gets the type of the attached data.
/// </summary>
public DataType Type { get; }
/// <summary>
/// Gets data length (number of elements).
/// </summary>
public int Length { get; }
/// <summary>
/// Gets size of a data element in bytes.
/// </summary>
public int ElementSize => Type.Size();
}
}

13
stylecop.json Executable file
View File

@ -0,0 +1,13 @@
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"documentationRules": {
"companyName": "None",
"copyrightText": "Copyright 2017 Alexander Luzgarev",
"xmlHeader": false
},
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace"
}
}
}