Preliminary support for Matlab objects
This commit is contained in:
parent
126d8a7483
commit
10aa558152
@ -292,7 +292,38 @@ namespace MatFileHandler.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestObject()
|
public void TestObject()
|
||||||
{
|
{
|
||||||
Assert.That(() => GetTests("bad")["object"], Throws.TypeOf<HandlerException>());
|
var matFile = GetTests("good")["object"];
|
||||||
|
var obj = matFile["object_"].Value as IMatObject;
|
||||||
|
Assert.IsNotNull(obj);
|
||||||
|
Assert.That(obj.ClassName, Is.EqualTo("Point"));
|
||||||
|
Assert.That(obj.FieldNames, Is.EquivalentTo(new[] { "x", "y" }));
|
||||||
|
Assert.That(obj["x", 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 3.0 }));
|
||||||
|
Assert.That(obj["y", 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 5.0 }));
|
||||||
|
Assert.That(obj["x", 1].ConvertToDoubleArray(), Is.EqualTo(new[] { -2.0 }));
|
||||||
|
Assert.That(obj["y", 1].ConvertToDoubleArray(), Is.EqualTo(new[] { 6.0 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test reading another object.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestObject2()
|
||||||
|
{
|
||||||
|
var matFile = GetTests("good")["object2"];
|
||||||
|
var obj = matFile["object2"].Value as IMatObject;
|
||||||
|
Assert.IsNotNull(obj);
|
||||||
|
Assert.That(obj.ClassName, Is.EqualTo("Point"));
|
||||||
|
Assert.That(obj.FieldNames, Is.EquivalentTo(new[] { "x", "y" }));
|
||||||
|
Assert.That(obj["x", 0, 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 3.0 }));
|
||||||
|
Assert.That(obj["y", 0, 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 5.0 }));
|
||||||
|
Assert.That(obj["x", 1, 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 1.0 }));
|
||||||
|
Assert.That(obj["y", 1, 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 0.0 }));
|
||||||
|
Assert.That(obj["x", 0, 1].ConvertToDoubleArray(), Is.EqualTo(new[] { -2.0 }));
|
||||||
|
Assert.That(obj["y", 0, 1].ConvertToDoubleArray(), Is.EqualTo(new[] { 6.0 }));
|
||||||
|
Assert.That(obj["x", 1, 1].ConvertToDoubleArray(), Is.EqualTo(new[] { 0.0 }));
|
||||||
|
Assert.That(obj["y", 1, 1].ConvertToDoubleArray(), Is.EqualTo(new[] { 1.0 }));
|
||||||
|
Assert.That(obj[0, 1]["x"].ConvertToDoubleArray(), Is.EqualTo(new[] { -2.0 }));
|
||||||
|
Assert.That(obj[2]["x"].ConvertToDoubleArray(), Is.EqualTo(new[] { -2.0 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AbstractTestDataFactory<IMatFile> GetTests(string factoryName) =>
|
private static AbstractTestDataFactory<IMatFile> GetTests(string factoryName) =>
|
||||||
|
0
MatFileHandler.Tests/test-data/bad/object.mat → MatFileHandler.Tests/test-data/good/object.mat
Executable file → Normal file
0
MatFileHandler.Tests/test-data/bad/object.mat → MatFileHandler.Tests/test-data/good/object.mat
Executable file → Normal file
BIN
MatFileHandler.Tests/test-data/good/object2.mat
Normal file
BIN
MatFileHandler.Tests/test-data/good/object2.mat
Normal file
Binary file not shown.
@ -85,9 +85,9 @@ namespace MatFileHandler
|
|||||||
MxUInt64 = 15,
|
MxUInt64 = 15,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Undocumented object (?) array type.
|
/// Undocumented opaque object type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MxNewObject = 17,
|
MxOpaque = 17,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -12,14 +12,25 @@ namespace MatFileHandler
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Functions for reading data elements from a .mat file.
|
/// Functions for reading data elements from a .mat file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class DataElementReader
|
internal class DataElementReader
|
||||||
{
|
{
|
||||||
|
private readonly SubsystemData subsystemData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DataElementReader"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subsystemData">Reference to file's SubsystemData.</param>
|
||||||
|
public DataElementReader(SubsystemData subsystemData)
|
||||||
|
{
|
||||||
|
this.subsystemData = subsystemData ?? throw new ArgumentNullException(nameof(subsystemData));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read a data element.
|
/// Read a data element.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">Input reader.</param>
|
/// <param name="reader">Input reader.</param>
|
||||||
/// <returns>Data element.</returns>
|
/// <returns>Data element.</returns>
|
||||||
public static DataElement Read(BinaryReader reader)
|
public DataElement Read(BinaryReader reader)
|
||||||
{
|
{
|
||||||
var (dataReader, tag) = ReadTag(reader);
|
var (dataReader, tag) = ReadTag(reader);
|
||||||
DataElement result;
|
DataElement result;
|
||||||
@ -66,6 +77,7 @@ namespace MatFileHandler
|
|||||||
default:
|
default:
|
||||||
throw new NotSupportedException("Unknown element.");
|
throw new NotSupportedException("Unknown element.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag.Type != DataType.MiCompressed)
|
if (tag.Type != DataType.MiCompressed)
|
||||||
{
|
{
|
||||||
var position = reader.BaseStream.Position;
|
var position = reader.BaseStream.Position;
|
||||||
@ -74,9 +86,109 @@ namespace MatFileHandler
|
|||||||
reader.ReadBytes(8 - (int)(position % 8));
|
reader.ReadBytes(8 - (int)(position % 8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (int[] dimensions, int[] links, int classIndex) ParseOpaqueData(MatNumericalArrayOf<uint> data)
|
||||||
|
{
|
||||||
|
var nDims = data.Data[1];
|
||||||
|
var dimensions = new int[nDims];
|
||||||
|
var position = 2;
|
||||||
|
for (var i = 0; i < nDims; i++)
|
||||||
|
{
|
||||||
|
dimensions[i] = (int)data.Data[position];
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = dimensions.NumberOfElements();
|
||||||
|
var links = new int[count];
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
links[i] = (int)data.Data[position];
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var classIndex = (int)data.Data[position];
|
||||||
|
|
||||||
|
return (dimensions, links, classIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArrayFlags ReadArrayFlags(DataElement element)
|
||||||
|
{
|
||||||
|
var flagData = (element as MiNum<uint>)?.Data ??
|
||||||
|
throw new HandlerException("Unexpected type in array flags.");
|
||||||
|
var class_ = (ArrayType)(flagData[0] & 0xff);
|
||||||
|
var variableFlags = (flagData[0] >> 8) & 0x0e;
|
||||||
|
return new ArrayFlags
|
||||||
|
{
|
||||||
|
Class = class_,
|
||||||
|
Variable = (Variable)variableFlags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataElement ReadData(DataElement element)
|
||||||
|
{
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] ReadDimensionsArray(MiNum<int> element)
|
||||||
|
{
|
||||||
|
return element.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 string ReadName(MiNum<sbyte> element)
|
||||||
|
{
|
||||||
|
return Encoding.ASCII.GetString(element.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 SparseArrayFlags ReadSparseArrayFlags(DataElement element)
|
||||||
|
{
|
||||||
|
var arrayFlags = ReadArrayFlags(element);
|
||||||
|
var flagData = (element as MiNum<uint>)?.Data ??
|
||||||
|
throw new HandlerException("Unexpected type in sparse array flags.");
|
||||||
|
var nzMax = flagData[1];
|
||||||
|
return new SparseArrayFlags
|
||||||
|
{
|
||||||
|
ArrayFlags = arrayFlags,
|
||||||
|
NzMax = nzMax,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static (BinaryReader, Tag) ReadTag(BinaryReader reader)
|
private static (BinaryReader, Tag) ReadTag(BinaryReader reader)
|
||||||
{
|
{
|
||||||
var type = reader.ReadInt32();
|
var type = reader.ReadInt32();
|
||||||
@ -95,87 +207,66 @@ namespace MatFileHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ArrayFlags ReadArrayFlags(DataElement element)
|
private DataElement ContinueReadingCellArray(
|
||||||
|
BinaryReader reader,
|
||||||
|
ArrayFlags flags,
|
||||||
|
int[] dimensions,
|
||||||
|
string name)
|
||||||
{
|
{
|
||||||
var flagData = (element as MiNum<uint>)?.Data ??
|
var numberOfElements = dimensions.NumberOfElements();
|
||||||
throw new HandlerException("Unexpected type in array flags.");
|
var elements = new List<IArray>();
|
||||||
var class_ = (ArrayType)(flagData[0] & 0xff);
|
for (var i = 0; i < numberOfElements; i++)
|
||||||
var variableFlags = (flagData[0] >> 8) & 0x0e;
|
|
||||||
return new ArrayFlags
|
|
||||||
{
|
{
|
||||||
Class = class_,
|
var element = Read(reader) as IArray;
|
||||||
Variable = (Variable)variableFlags,
|
elements.Add(element);
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SparseArrayFlags ReadSparseArrayFlags(DataElement element)
|
|
||||||
{
|
|
||||||
var arrayFlags = ReadArrayFlags(element);
|
|
||||||
var flagData = (element as MiNum<uint>)?.Data ??
|
|
||||||
throw new HandlerException("Unexpected type in sparse array flags.");
|
|
||||||
var nzMax = flagData[1];
|
|
||||||
return new SparseArrayFlags
|
|
||||||
{
|
|
||||||
ArrayFlags = arrayFlags,
|
|
||||||
NzMax = nzMax,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int[] ReadDimensionsArray(MiNum<int> element)
|
|
||||||
{
|
|
||||||
return element.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DataElement ReadData(DataElement element)
|
|
||||||
{
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ReadName(MiNum<sbyte> element)
|
|
||||||
{
|
|
||||||
return Encoding.ASCII.GetString(element.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 MatCellArray(flags, dimensions, name, elements);
|
||||||
return new MiNum<T>(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string[] ReadFieldNames(MiNum<sbyte> element, int fieldNameLength)
|
private DataElement ContinueReadingOpaque(BinaryReader reader)
|
||||||
{
|
{
|
||||||
var numberOfFields = element.Data.Length / fieldNameLength;
|
var nameElement = Read(reader) as MiNum<sbyte> ??
|
||||||
var result = new string[numberOfFields];
|
throw new HandlerException("Unexpected type in object name.");
|
||||||
for (var i = 0; i < numberOfFields; i++)
|
var name = ReadName(nameElement);
|
||||||
|
var anotherElement = Read(reader) as MiNum<sbyte> ??
|
||||||
|
throw new HandlerException("Unexpected type in object type description.");
|
||||||
|
var typeDescription = ReadName(anotherElement);
|
||||||
|
var classNameElement = Read(reader) as MiNum<sbyte> ??
|
||||||
|
throw new HandlerException("Unexpected type in class name.");
|
||||||
|
var className = ReadName(classNameElement);
|
||||||
|
var dataElement = Read(reader);
|
||||||
|
var data = ReadData(dataElement);
|
||||||
|
if (data is MatNumericalArrayOf<uint> linkElement)
|
||||||
{
|
{
|
||||||
var list = new List<byte>();
|
var (dimensions, links, classIndex) = ParseOpaqueData(linkElement);
|
||||||
var position = i * fieldNameLength;
|
return new OpaqueLink(
|
||||||
while (element.Data[position] != 0)
|
name,
|
||||||
{
|
typeDescription,
|
||||||
list.Add((byte)element.Data[position]);
|
className,
|
||||||
position++;
|
dimensions,
|
||||||
}
|
data,
|
||||||
result[i] = Encoding.ASCII.GetString(list.ToArray());
|
links,
|
||||||
|
classIndex,
|
||||||
|
subsystemData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new Opaque(name, typeDescription, className, new int[] { }, data);
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataElement ContinueReadingSparseArray(
|
private DataElement ContinueReadingSparseArray(
|
||||||
BinaryReader reader,
|
BinaryReader reader,
|
||||||
DataElement firstElement,
|
DataElement firstElement,
|
||||||
int[] dimensions,
|
int[] dimensions,
|
||||||
string name)
|
string name)
|
||||||
{
|
{
|
||||||
var sparseArrayFlags = ReadSparseArrayFlags(firstElement);
|
var sparseArrayFlags = ReadSparseArrayFlags(firstElement);
|
||||||
var rowIndex = Read(reader) as MiNum<int> ?? throw new HandlerException("Unexpected type in row indices of a sparse array.");
|
var rowIndex = Read(reader) as MiNum<int> ??
|
||||||
var columnIndex = Read(reader) as MiNum<int> ?? throw new HandlerException("Unexpected type in column indices of a sparse array.");
|
throw new HandlerException("Unexpected type in row indices of a sparse array.");
|
||||||
|
var columnIndex = Read(reader) as MiNum<int> ??
|
||||||
|
throw new HandlerException("Unexpected type in column indices of a sparse array.");
|
||||||
var data = Read(reader);
|
var data = Read(reader);
|
||||||
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsLogical))
|
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsLogical))
|
||||||
{
|
{
|
||||||
@ -187,6 +278,7 @@ namespace MatFileHandler
|
|||||||
columnIndex.Data,
|
columnIndex.Data,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsComplex))
|
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsComplex))
|
||||||
{
|
{
|
||||||
var imaginaryData = Read(reader);
|
var imaginaryData = Read(reader);
|
||||||
@ -199,6 +291,7 @@ namespace MatFileHandler
|
|||||||
data,
|
data,
|
||||||
imaginaryData);
|
imaginaryData);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data)
|
switch (data)
|
||||||
{
|
{
|
||||||
case MiNum<double> _:
|
case MiNum<double> _:
|
||||||
@ -214,23 +307,7 @@ namespace MatFileHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataElement ContinueReadingCellArray(
|
private DataElement ContinueReadingStructure(
|
||||||
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,
|
BinaryReader reader,
|
||||||
ArrayFlags flags,
|
ArrayFlags flags,
|
||||||
int[] dimensions,
|
int[] dimensions,
|
||||||
@ -244,6 +321,7 @@ namespace MatFileHandler
|
|||||||
{
|
{
|
||||||
fields[fieldName] = new List<IArray>();
|
fields[fieldName] = new List<IArray>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var numberOfElements = dimensions.NumberOfElements();
|
var numberOfElements = dimensions.NumberOfElements();
|
||||||
for (var i = 0; i < numberOfElements; i++)
|
for (var i = 0; i < numberOfElements; i++)
|
||||||
{
|
{
|
||||||
@ -253,27 +331,53 @@ namespace MatFileHandler
|
|||||||
fields[fieldName].Add(field);
|
fields[fieldName].Add(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MatStructureArray(flags, dimensions, name, fields);
|
return new MatStructureArray(flags, dimensions, name, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataElement ContinueReadingNewObject()
|
private DataElement Read(Stream stream)
|
||||||
{
|
{
|
||||||
throw new HandlerException("Cannot read objects.");
|
using (var reader = new BinaryReader(stream))
|
||||||
|
{
|
||||||
|
return Read(reader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataElement ReadMatrix(Tag tag, BinaryReader reader)
|
private 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 DataElement ReadMatrix(Tag tag, BinaryReader reader)
|
||||||
{
|
{
|
||||||
if (tag.Length == 0)
|
if (tag.Length == 0)
|
||||||
{
|
{
|
||||||
return MatArray.Empty();
|
return MatArray.Empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
var element1 = Read(reader);
|
var element1 = Read(reader);
|
||||||
var flags = ReadArrayFlags(element1);
|
var flags = ReadArrayFlags(element1);
|
||||||
if (flags.Class == ArrayType.MxNewObject)
|
if (flags.Class == ArrayType.MxOpaque)
|
||||||
{
|
{
|
||||||
return ContinueReadingNewObject();
|
return ContinueReadingOpaque(reader);
|
||||||
}
|
}
|
||||||
var element2 = Read(reader) as MiNum<int> ?? throw new HandlerException("Unexpected type in array dimensions data.");
|
|
||||||
|
var element2 = Read(reader) as MiNum<int> ??
|
||||||
|
throw new HandlerException("Unexpected type in array dimensions data.");
|
||||||
var dimensions = ReadDimensionsArray(element2);
|
var dimensions = ReadDimensionsArray(element2);
|
||||||
var element3 = Read(reader) as MiNum<sbyte> ?? throw new HandlerException("Unexpected type in array name.");
|
var element3 = Read(reader) as MiNum<sbyte> ?? throw new HandlerException("Unexpected type in array name.");
|
||||||
var name = ReadName(element3);
|
var name = ReadName(element3);
|
||||||
@ -281,10 +385,12 @@ namespace MatFileHandler
|
|||||||
{
|
{
|
||||||
return ContinueReadingCellArray(reader, flags, dimensions, name);
|
return ContinueReadingCellArray(reader, flags, dimensions, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.Class == ArrayType.MxSparse)
|
if (flags.Class == ArrayType.MxSparse)
|
||||||
{
|
{
|
||||||
return ContinueReadingSparseArray(reader, element1, dimensions, name);
|
return ContinueReadingSparseArray(reader, element1, dimensions, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
var element4 = Read(reader);
|
var element4 = Read(reader);
|
||||||
var data = ReadData(element4);
|
var data = ReadData(element4);
|
||||||
DataElement imaginaryData = null;
|
DataElement imaginaryData = null;
|
||||||
@ -293,12 +399,15 @@ namespace MatFileHandler
|
|||||||
var element5 = Read(reader);
|
var element5 = Read(reader);
|
||||||
imaginaryData = ReadData(element5);
|
imaginaryData = ReadData(element5);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.Class == ArrayType.MxStruct)
|
if (flags.Class == ArrayType.MxStruct)
|
||||||
{
|
{
|
||||||
var fieldNameLengthElement = data as MiNum<int> ??
|
var fieldNameLengthElement = data as MiNum<int> ??
|
||||||
throw new HandlerException("Unexpected type in structure field name length.");
|
throw new HandlerException(
|
||||||
|
"Unexpected type in structure field name length.");
|
||||||
return ContinueReadingStructure(reader, flags, dimensions, name, fieldNameLengthElement.Data[0]);
|
return ContinueReadingStructure(reader, flags, dimensions, name, fieldNameLengthElement.Data[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (flags.Class)
|
switch (flags.Class)
|
||||||
{
|
{
|
||||||
case ArrayType.MxChar:
|
case ArrayType.MxChar:
|
||||||
@ -339,6 +448,7 @@ namespace MatFileHandler
|
|||||||
data,
|
data,
|
||||||
imaginaryData);
|
imaginaryData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
|
return DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
|
||||||
flags,
|
flags,
|
||||||
dimensions,
|
dimensions,
|
||||||
@ -405,31 +515,5 @@ namespace MatFileHandler
|
|||||||
throw new HandlerException("Unknown data type.");
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ namespace MatFileHandler
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class Header
|
internal class Header
|
||||||
{
|
{
|
||||||
private Header(string text, byte[] subsystemDataOffset, int version)
|
private Header(string text, long subsystemDataOffset, int version)
|
||||||
{
|
{
|
||||||
Text = text;
|
Text = text;
|
||||||
SubsystemDataOffset = subsystemDataOffset;
|
SubsystemDataOffset = subsystemDataOffset;
|
||||||
@ -28,7 +28,7 @@ namespace MatFileHandler
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets subsystem data offset.
|
/// Gets subsystem data offset.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] SubsystemDataOffset { get; }
|
public long SubsystemDataOffset { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets file version.
|
/// Gets file version.
|
||||||
@ -55,7 +55,7 @@ namespace MatFileHandler
|
|||||||
platform = platform.Remove(length);
|
platform = platform.Remove(length);
|
||||||
}
|
}
|
||||||
var text = $"MATLAB 5.0 MAT-file, Platform: {platform}, Created on: {dateTime}{padding}";
|
var text = $"MATLAB 5.0 MAT-file, Platform: {platform}, Created on: {dateTime}{padding}";
|
||||||
return new Header(text, subsystemDataOffset, 256);
|
return new Header(text, 0, 256);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -67,7 +67,8 @@ namespace MatFileHandler
|
|||||||
{
|
{
|
||||||
var textBytes = reader.ReadBytes(116);
|
var textBytes = reader.ReadBytes(116);
|
||||||
var text = System.Text.Encoding.UTF8.GetString(textBytes);
|
var text = System.Text.Encoding.UTF8.GetString(textBytes);
|
||||||
var subsystemDataOffset = reader.ReadBytes(8);
|
var subsystemDataOffsetBytes = reader.ReadBytes(8);
|
||||||
|
var subsystemDataOffset = BitConverter.ToInt64(subsystemDataOffsetBytes, 0);
|
||||||
var version = reader.ReadInt16();
|
var version = reader.ReadInt16();
|
||||||
var endian = reader.ReadInt16();
|
var endian = reader.ReadInt16();
|
||||||
var isLittleEndian = endian == 19785;
|
var isLittleEndian = endian == 19785;
|
||||||
|
34
MatFileHandler/IMatObject.cs
Normal file
34
MatFileHandler/IMatObject.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MatFileHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface to access Matlab objects (more precisely, "object arrays").
|
||||||
|
/// This is very similar to the <see cref="IStructureArray"/> interface:
|
||||||
|
/// an object holds fields that you can access, and the name of its class.
|
||||||
|
/// Additionally, you can treat is as an array of dictionaries mapping
|
||||||
|
/// field names to contents of fields.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMatObject : IArrayOf<IReadOnlyDictionary<string, IArray>>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of object's class.
|
||||||
|
/// </summary>
|
||||||
|
string ClassName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the names of object's fields.
|
||||||
|
/// </summary>
|
||||||
|
IEnumerable<string> FieldNames { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Access a given field of a given object in the array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">Field name.</param>
|
||||||
|
/// <param name="list">Index of the object to access.</param>
|
||||||
|
/// <returns>The value of the field in the selected object.</returns>
|
||||||
|
IArray this[string field, params int[] list] { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
// Copyright 2017-2018 Alexander Luzgarev
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
@ -33,35 +34,72 @@ namespace MatFileHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReadHeader(BinaryReader reader)
|
/// <summary>
|
||||||
|
/// Read raw variables from a .mat file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">Binary reader.</param>
|
||||||
|
/// <param name="subsystemDataOffset">Offset to the subsystem data to use (read from the file header).</param>
|
||||||
|
/// <returns>Raw variables read.</returns>
|
||||||
|
internal static List<RawVariable> ReadRawVariables(BinaryReader reader, long subsystemDataOffset)
|
||||||
{
|
{
|
||||||
Header.Read(reader);
|
var variables = new List<RawVariable>();
|
||||||
}
|
var subsystemData = new SubsystemData();
|
||||||
|
var dataElementReader = new DataElementReader(subsystemData);
|
||||||
private static IMatFile Read(BinaryReader reader)
|
|
||||||
{
|
|
||||||
ReadHeader(reader);
|
|
||||||
var variables = new List<IVariable>();
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dataElement = DataElementReader.Read(reader) as MatArray;
|
var position = reader.BaseStream.Position;
|
||||||
if (dataElement == null)
|
var dataElement = dataElementReader.Read(reader);
|
||||||
|
if (position == subsystemDataOffset)
|
||||||
{
|
{
|
||||||
continue;
|
var subsystemDataElement = dataElement as IArrayOf<byte>;
|
||||||
|
subsystemData.Set(ReadSubsystemData(subsystemDataElement.Data));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
variables.Add(new RawVariable(position, dataElement));
|
||||||
}
|
}
|
||||||
variables.Add(new MatVariable(
|
|
||||||
dataElement,
|
|
||||||
dataElement.Name,
|
|
||||||
dataElement.Flags.Variable.HasFlag(Variable.IsGlobal)));
|
|
||||||
}
|
}
|
||||||
catch (EndOfStreamException)
|
catch (EndOfStreamException)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IMatFile Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
var header = ReadHeader(reader);
|
||||||
|
var rawVariables = ReadRawVariables(reader, header.SubsystemDataOffset);
|
||||||
|
var variables = new List<IVariable>();
|
||||||
|
foreach (var variable in rawVariables)
|
||||||
|
{
|
||||||
|
var array = variable.DataElement as MatArray;
|
||||||
|
if (array is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
variables.Add(new MatVariable(
|
||||||
|
array,
|
||||||
|
array.Name,
|
||||||
|
array.Flags.Variable.HasFlag(Variable.IsGlobal)));
|
||||||
|
}
|
||||||
|
|
||||||
return new MatFile(variables);
|
return new MatFile(variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Header ReadHeader(BinaryReader reader)
|
||||||
|
{
|
||||||
|
return Header.Read(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SubsystemData ReadSubsystemData(byte[] bytes)
|
||||||
|
{
|
||||||
|
return SubsystemDataReader.Read(bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
58
MatFileHandler/Opaque.cs
Normal file
58
MatFileHandler/Opaque.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace MatFileHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Matlab "opaque object" structure.
|
||||||
|
/// If this object appears in the "main" section of the .mat file,
|
||||||
|
/// it just contains a small data structure pointing to the object's
|
||||||
|
/// storage in the "subsystem data" portion of the file.
|
||||||
|
/// In this case, an instance of <see cref="OpaqueLink"/> class
|
||||||
|
/// will be created.
|
||||||
|
/// If this object appears in the "subsystem data" part, it contains
|
||||||
|
/// the data of all opaque objects in the file, and that is what we
|
||||||
|
/// put into <see cref="RawData"/> property.
|
||||||
|
/// </summary>
|
||||||
|
internal class Opaque : MatArray, IArray
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Opaque"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Name of the object.</param>
|
||||||
|
/// <param name="typeDescription">Type description.</param>
|
||||||
|
/// <param name="className">Class name.</param>
|
||||||
|
/// <param name="dimensions">Dimensions of the object.</param>
|
||||||
|
/// <param name="rawData">Raw object's data.</param>
|
||||||
|
public Opaque(string name, string typeDescription, string className, int[] dimensions, DataElement rawData)
|
||||||
|
: base(new ArrayFlags(ArrayType.MxOpaque, 0), dimensions, name)
|
||||||
|
{
|
||||||
|
TypeDescription = typeDescription ?? throw new ArgumentNullException(nameof(typeDescription));
|
||||||
|
ClassName = className ?? throw new ArgumentNullException(nameof(className));
|
||||||
|
RawData = rawData ?? throw new ArgumentNullException(nameof(rawData));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets class name of the opaque object.
|
||||||
|
/// </summary>
|
||||||
|
public string ClassName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets raw object's data: either links to subsystem data, or actual data.
|
||||||
|
/// </summary>
|
||||||
|
public DataElement RawData { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets "type description" of the opaque object.
|
||||||
|
/// </summary>
|
||||||
|
public string TypeDescription { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Complex[] ConvertToComplexArray() => null;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override double[] ConvertToDoubleArray() => null;
|
||||||
|
}
|
||||||
|
}
|
169
MatFileHandler/OpaqueLink.cs
Normal file
169
MatFileHandler/OpaqueLink.cs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace MatFileHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implementation of Matlab's "opaque objects" via links to subsystem data.
|
||||||
|
/// </summary>
|
||||||
|
internal class OpaqueLink : Opaque, IMatObject
|
||||||
|
{
|
||||||
|
private readonly SubsystemData subsystemData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="OpaqueLink"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Name of the object.</param>
|
||||||
|
/// <param name="typeDescription">Description of object's class.</param>
|
||||||
|
/// <param name="className">Name of the object's class.</param>
|
||||||
|
/// <param name="dimensions">Dimensions of the object.</param>
|
||||||
|
/// <param name="data">Raw data containing links to object's storage.</param>
|
||||||
|
/// <param name="links">Links to object's storage.</param>
|
||||||
|
/// <param name="classIndex">Index of object's class.</param>
|
||||||
|
/// <param name="subsystemData">Reference to global subsystem data.</param>
|
||||||
|
public OpaqueLink(
|
||||||
|
string name,
|
||||||
|
string typeDescription,
|
||||||
|
string className,
|
||||||
|
int[] dimensions,
|
||||||
|
DataElement data,
|
||||||
|
int[] links,
|
||||||
|
int classIndex,
|
||||||
|
SubsystemData subsystemData)
|
||||||
|
: base(name, typeDescription, className, dimensions, data)
|
||||||
|
{
|
||||||
|
Links = links ?? throw new ArgumentNullException(nameof(links));
|
||||||
|
ClassIndex = classIndex;
|
||||||
|
this.subsystemData = subsystemData ?? throw new ArgumentNullException(nameof(subsystemData));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets index of this object's class in subsystem data class list.
|
||||||
|
/// </summary>
|
||||||
|
public int ClassIndex { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyDictionary<string, IArray>[] Data => null;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<string> FieldNames => FieldNamesArray;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets links to the fields stored in subsystem data.
|
||||||
|
/// </summary>
|
||||||
|
public int[] Links { get; }
|
||||||
|
|
||||||
|
private string[] FieldNamesArray => subsystemData.ClassInformation[ClassIndex - 1].FieldNames.ToArray();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IArray this[string field, params int[] list]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (TryGetValue(field, out var result, list))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
}
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyDictionary<string, IArray> this[params int[] list]
|
||||||
|
{
|
||||||
|
get => ExtractObject(Dimensions.DimFlatten(list));
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyDictionary<string, IArray> ExtractObject(int i)
|
||||||
|
{
|
||||||
|
return new OpaqueObjectArrayElement(this, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetValue(string field, out IArray output, params int[] list)
|
||||||
|
{
|
||||||
|
var index = Dimensions.DimFlatten(list);
|
||||||
|
var maybeFieldIndex = subsystemData.ClassInformation[ClassIndex - 1].FindField(field);
|
||||||
|
if (!(maybeFieldIndex is int fieldIndex))
|
||||||
|
{
|
||||||
|
output = default(IArray);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= subsystemData.ObjectInformation.Length)
|
||||||
|
{
|
||||||
|
output = default(IArray);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataIndex = subsystemData.ObjectInformation[index].FieldLinks[fieldIndex + 1];
|
||||||
|
output = subsystemData.Data[dataIndex];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to a single object in object array.
|
||||||
|
/// </summary>
|
||||||
|
internal class OpaqueObjectArrayElement : IReadOnlyDictionary<string, IArray>
|
||||||
|
{
|
||||||
|
private readonly int index;
|
||||||
|
private readonly OpaqueLink parent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="OpaqueObjectArrayElement"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">Parent object array.</param>
|
||||||
|
/// <param name="index">Index of the object in the array.</param>
|
||||||
|
public OpaqueObjectArrayElement(OpaqueLink parent, int index)
|
||||||
|
{
|
||||||
|
this.parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int Count => parent.FieldNamesArray.Length;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<string> Keys => parent.FieldNames;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<IArray> Values => parent.FieldNames.Select(fieldName => parent[fieldName, index]);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IArray this[string key] => parent[key, index];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool ContainsKey(string key)
|
||||||
|
{
|
||||||
|
return parent.FieldNames.Contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<KeyValuePair<string, IArray>> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (var field in parent.FieldNamesArray)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<string, IArray>(field, parent[field, index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryGetValue(string key, out IArray value)
|
||||||
|
{
|
||||||
|
return parent.TryGetValue(key, out value, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
MatFileHandler/RawVariable.cs
Normal file
36
MatFileHandler/RawVariable.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MatFileHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Raw variable read from the file.
|
||||||
|
/// This gives a way to deal with "subsystem data" which looks like
|
||||||
|
/// a variable and can only be detected by comparing its offset with
|
||||||
|
/// the value stored in the file's header.
|
||||||
|
/// </summary>
|
||||||
|
internal class RawVariable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RawVariable"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Offset of the variable in the source file.</param>
|
||||||
|
/// <param name="dataElement">Data element parsed from the file.</param>
|
||||||
|
internal RawVariable(long offset, DataElement dataElement)
|
||||||
|
{
|
||||||
|
Offset = offset;
|
||||||
|
DataElement = dataElement ?? throw new ArgumentNullException(nameof(dataElement));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets data element with the variable's contents.
|
||||||
|
/// </summary>
|
||||||
|
public DataElement DataElement { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets offset of the variable in the .mat file.
|
||||||
|
/// </summary>
|
||||||
|
public long Offset { get; }
|
||||||
|
}
|
||||||
|
}
|
144
MatFileHandler/SubsystemData.cs
Normal file
144
MatFileHandler/SubsystemData.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MatFileHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// "Subsystem data" of the .mat file.
|
||||||
|
/// Subsystem data stores the actual contents
|
||||||
|
/// of all the "opaque objects" in the file.
|
||||||
|
/// </summary>
|
||||||
|
internal class SubsystemData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SubsystemData"/> class.
|
||||||
|
/// Default constructor: initializes everything to null.
|
||||||
|
/// </summary>
|
||||||
|
public SubsystemData()
|
||||||
|
{
|
||||||
|
ClassInformation = null;
|
||||||
|
ObjectInformation = null;
|
||||||
|
Data = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SubsystemData"/> class.
|
||||||
|
/// The actual constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="classInformation">Information about the classes.</param>
|
||||||
|
/// <param name="objectInformation">Information about the objects.</param>
|
||||||
|
/// <param name="data">Field values.</param>
|
||||||
|
public SubsystemData(ClassInfo[] classInformation, ObjectInfo[] objectInformation, Dictionary<int, IArray> data)
|
||||||
|
{
|
||||||
|
this.ClassInformation =
|
||||||
|
classInformation ?? throw new ArgumentNullException(nameof(classInformation));
|
||||||
|
this.ObjectInformation =
|
||||||
|
objectInformation ?? throw new ArgumentNullException(nameof(objectInformation));
|
||||||
|
this.Data = data ?? throw new ArgumentNullException(nameof(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets information about all the classes occurring in the file.
|
||||||
|
/// </summary>
|
||||||
|
public ClassInfo[] ClassInformation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the actual data: mapping of "object field" indices to their values.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<int, IArray> Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets information about all the objects occurring in the file.
|
||||||
|
/// </summary>
|
||||||
|
public ObjectInfo[] ObjectInformation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize this object from another object.
|
||||||
|
/// This ugly hack allows us to read the opaque objects and store references to
|
||||||
|
/// the subsystem data in them before parsing the actual subsystem data (which
|
||||||
|
/// comes later in the file).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Another subsystem data.</param>
|
||||||
|
public void Set(SubsystemData data)
|
||||||
|
{
|
||||||
|
this.ClassInformation = data.ClassInformation;
|
||||||
|
this.ObjectInformation = data.ObjectInformation;
|
||||||
|
this.Data = data.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores information about a class.
|
||||||
|
/// </summary>
|
||||||
|
internal class ClassInfo
|
||||||
|
{
|
||||||
|
private readonly string[] fieldNames;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, int> fieldToIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ClassInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Class name.</param>
|
||||||
|
/// <param name="fieldNames">Names of the fields.</param>
|
||||||
|
public ClassInfo(string name, string[] fieldNames)
|
||||||
|
{
|
||||||
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
this.fieldNames = fieldNames ?? throw new ArgumentNullException(nameof(fieldNames));
|
||||||
|
fieldToIndex = new Dictionary<string, int>();
|
||||||
|
for (var i = 0; i < fieldNames.Length; i++)
|
||||||
|
{
|
||||||
|
fieldToIndex[fieldNames[i]] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets names of the fields in the class.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<string> FieldNames => fieldNames;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets name of the class.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find a field index given its name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldName">Field name.</param>
|
||||||
|
/// <returns>Field index.</returns>
|
||||||
|
public int? FindField(string fieldName)
|
||||||
|
{
|
||||||
|
if (fieldToIndex.TryGetValue(fieldName, out var index))
|
||||||
|
{
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores information about an object.
|
||||||
|
/// </summary>
|
||||||
|
internal class ObjectInfo
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, int> fieldLinks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ObjectInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldLinks">A dictionary mapping the field indices to "field values" indices.</param>
|
||||||
|
public ObjectInfo(Dictionary<int, int> fieldLinks)
|
||||||
|
{
|
||||||
|
this.fieldLinks = fieldLinks ?? throw new ArgumentNullException(nameof(fieldLinks));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets mapping between the field indices and "field values" indices.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<int, int> FieldLinks => fieldLinks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
168
MatFileHandler/SubsystemDataReader.cs
Normal file
168
MatFileHandler/SubsystemDataReader.cs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MatFileHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reader for "subsystem data" in .mat files.
|
||||||
|
/// </summary>
|
||||||
|
internal class SubsystemDataReader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Read subsystem data from a given byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">Byte array with the data.</param>
|
||||||
|
/// <returns>Subsystem data read.</returns>
|
||||||
|
public static SubsystemData Read(byte[] bytes)
|
||||||
|
{
|
||||||
|
List<RawVariable> rawVariables = null;
|
||||||
|
using (var stream = new MemoryStream(bytes))
|
||||||
|
{
|
||||||
|
using (var reader = new BinaryReader(stream))
|
||||||
|
{
|
||||||
|
reader.ReadBytes(8);
|
||||||
|
rawVariables = MatFileReader.ReadRawVariables(reader, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse subsystem data.
|
||||||
|
var mainVariable = rawVariables[0].DataElement as IStructureArray;
|
||||||
|
var mcosData = mainVariable["MCOS", 0] as Opaque;
|
||||||
|
var opaqueData = mcosData.RawData as ICellArray;
|
||||||
|
var info = (opaqueData[0] as IArrayOf<byte>).Data;
|
||||||
|
var (offsets, position) = ReadOffsets(info, 0);
|
||||||
|
var fieldNames = ReadFieldNames(info, position, offsets[1]);
|
||||||
|
var numberOfClasses = ((offsets[3] - offsets[2]) / 16) - 1;
|
||||||
|
SubsystemData.ClassInfo[] classInformation = null;
|
||||||
|
using (var stream = new MemoryStream(info, offsets[2], offsets[3] - offsets[2]))
|
||||||
|
{
|
||||||
|
using (var reader = new BinaryReader(stream))
|
||||||
|
{
|
||||||
|
classInformation = ReadClassInformation(reader, fieldNames, numberOfClasses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var numberOfObjects = ((offsets[5] - offsets[4]) / 24) - 1;
|
||||||
|
SubsystemData.ObjectInfo[] objectInformation = null;
|
||||||
|
using (var stream = new MemoryStream(info, offsets[5], offsets[6] - offsets[5]))
|
||||||
|
{
|
||||||
|
using (var reader = new BinaryReader(stream))
|
||||||
|
{
|
||||||
|
objectInformation = ReadObjectInformation(reader, numberOfObjects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var allFields = objectInformation.SelectMany(obj => obj.FieldLinks.Values);
|
||||||
|
var data = new Dictionary<int, IArray>();
|
||||||
|
foreach (var i in allFields)
|
||||||
|
{
|
||||||
|
data[i] = opaqueData[i + 2];
|
||||||
|
}
|
||||||
|
return new SubsystemData(classInformation, objectInformation, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SubsystemData.ObjectInfo ReadObjectInformation(BinaryReader reader)
|
||||||
|
{
|
||||||
|
var length = reader.ReadInt32();
|
||||||
|
var fieldLinks = new Dictionary<int, int>();
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
var x = reader.ReadInt32();
|
||||||
|
var y = reader.ReadInt32();
|
||||||
|
var index = x * y;
|
||||||
|
var link = reader.ReadInt32();
|
||||||
|
fieldLinks[index] = link;
|
||||||
|
}
|
||||||
|
return new SubsystemData.ObjectInfo(fieldLinks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SubsystemData.ObjectInfo[] ReadObjectInformation(BinaryReader reader, int numberOfObjects)
|
||||||
|
{
|
||||||
|
var result = new SubsystemData.ObjectInfo[numberOfObjects];
|
||||||
|
reader.ReadBytes(8);
|
||||||
|
for (var objectIndex = 0; objectIndex < numberOfObjects; objectIndex++)
|
||||||
|
{
|
||||||
|
result[objectIndex] = ReadObjectInformation(reader);
|
||||||
|
var position = reader.BaseStream.Position;
|
||||||
|
if (position % 8 != 0)
|
||||||
|
{
|
||||||
|
reader.ReadBytes(8 - (int)(position % 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SubsystemData.ClassInfo[] ReadClassInformation(
|
||||||
|
BinaryReader reader,
|
||||||
|
string[] fieldNames,
|
||||||
|
int numberOfClasses)
|
||||||
|
{
|
||||||
|
var result = new SubsystemData.ClassInfo[numberOfClasses];
|
||||||
|
var indices = new int[numberOfClasses + 1];
|
||||||
|
for (var i = 0; i <= numberOfClasses; i++)
|
||||||
|
{
|
||||||
|
reader.ReadInt32();
|
||||||
|
indices[i] = reader.ReadInt32();
|
||||||
|
reader.ReadInt32();
|
||||||
|
reader.ReadInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < numberOfClasses; i++)
|
||||||
|
{
|
||||||
|
var numberOfFields = indices[i + 1] - indices[i] - 1;
|
||||||
|
var names = new string[numberOfFields];
|
||||||
|
Array.Copy(fieldNames, indices[i], names, 0, numberOfFields);
|
||||||
|
var className = fieldNames[indices[i + 1] - 1];
|
||||||
|
result[i] = new SubsystemData.ClassInfo(className, names);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int[] offsets, int newPosition) ReadOffsets(byte[] bytes, int startPosition)
|
||||||
|
{
|
||||||
|
var position = startPosition;
|
||||||
|
var offsets = new List<int>();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var next = BitConverter.ToInt32(bytes, position);
|
||||||
|
position += 4;
|
||||||
|
if (next == 0)
|
||||||
|
{
|
||||||
|
if (position % 8 != 0)
|
||||||
|
{
|
||||||
|
position += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offsets.Add(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (offsets.ToArray(), position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] ReadFieldNames(byte[] bytes, int startPosition, int numberOfFields)
|
||||||
|
{
|
||||||
|
var result = new string[numberOfFields];
|
||||||
|
var position = startPosition;
|
||||||
|
for (var i = 0; i < numberOfFields; i++)
|
||||||
|
{
|
||||||
|
var list = new List<byte>();
|
||||||
|
while (bytes[position] != 0)
|
||||||
|
{
|
||||||
|
list.Add(bytes[position]);
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
result[i] = Encoding.ASCII.GetString(list.ToArray());
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user