7.7 KiB
MatFileHandler
Reading a .mat file
using MatFileHandler;
IMatFile matFile;
using (var fileStream = new System.IO.FileStream("example.mat", System.IO.FileMode.Open)) {
var reader = new MatFileReader(fileStream);
matFile = reader.Read();
}
After that, you can access the variables inside using the indexer
IVariable variable = matFile["array"];
or iterating over all the variables:
foreach (IVariable variable in matFile.Variables) {
// Do stuff
}
(all of the interfaces and classes described in this text are in the MatFileHandler
namespace).
Each IVariable
has a name, a value, and a flag indicating if it's a “global” variable:
public interface IVariable
{
string Name { get; set; }
IArray Value { get; }
bool IsGlobal { get; }
}
The interesting part here is the IArray
interface. This is a base interface, which is extended by other interfaces that provide access to more specific MATLAB arrays (numerical, cell, structure, char, etc.).
We can't do much with IArray
itself: check for emptiness, get its dimensions and total number of elements in it, or try to convert it to an array of double (or complex) numbers:
public interface IArray
{
bool IsEmpty { get; }
int[] Dimensions { get; }
int Count { get; }
double[] ConvertToDoubleArray();
System.Numerics.Complex[] ConvertToDoubleArray();
}
Note that Dimensions
is a list, since all arrays in MATLAB are (at least potentially) multi-dimensional. However, ConvertToDoubleArray()
and ConvertToDoubleArray()
return flat arrays, arranging all multi-dimensional data in columns (MATLAB-style). This functions return null
if conversion failed (for example, if you tried to apply it to a structure array, or cell array).
Numerical and logical arrays
The simplest type of array is a numerical array, which implements the IArrayOf<T>
interface, where T
is a numerical type, i. e., one of Int8
, UInt8
, Int16
, UInt16
, Int32
, UInt32
, Int64
, UInt64
, Single
, Double
. Arrays can contain complex values, which are just pairs of ordinary numbers. These pairs of Double
s are represented by System.Numerics.Complex
, and pairs of other numerical types are represented by a simple ComplexOf<T>
struct, which has two properties:
public struct ComplexOf<T> : IEquatable<ComplexOf<T>>
where T : struct
{
public T Real { get; }
public T Imaginary { get; }
// Some other stuff
}
All of this means that you can also have an IArrayOf<T>
for T
being ComplexOf<Int8>
, ComplexOf<UInt8>
, ComplexOf<Int16>
, ComplexOf<UInt16>
, ComplexOf<Int32>
, ComplexOf<UInt32>
, ComplexOf<Int64>
, ComplexOf<UInt64>
, ComplexOf<Single>
, and, of course, Complex
(note that we don't use ComplexOf<Double>
). Finally, you can access a logical array as IArrayOf<Boolean>
.
The IArrayOf<T>
interface allows you to refer to a specific element by using a (multi-dimensional) indexer, or get all data at once as a flat array (multidimensional arrays get converted to flat using MATLAB conventions). Indexes start with 0 (note that in MATLAB they start with 1, so there is a shift in notation).
public interface IArrayOf<T> : IArray
{
T[] Data { get; }
T this[params int[] list] { get; set; }
}
You can use a one-dimensional indexer or a multi-dimensional one, which is consistent with MATLAB notation. For example, a 2×3 array named a
has elements a[0, 0]
, a[1, 0]
(first column), a[0, 1]
, a[1, 1]
(second column), a[0, 2]
, a[1, 2]
(third column), which can also be accessed as a[0]
, a[1]
, a[2]
, a[3]
, a[4]
, and a[5]
, respectively.
Cell arrays
Cell array is just an array of arrays, so ICellArray
implements IArrayOf<IArray>
, and adds nothing to it. This means that you can refer to specific cells in a cell array by using the indexer, or by inspecting the Data
array described in the previous section.
Char arrays
Char arrays implement IArrayOf<char>
, so you can refer to individual chars in it via an indexer. Often a char array is used to carry a string, so there is a property for that:
public interface ICharArray : IArrayOf<char>
{
string String { get; }
}
This can be slightly weird for multi-dimensional arrays: the characters are stuffed into this string by columns (the same way the numerical array elements are flattened into a one-dimensional array). Moreover, each character array you read from a file actually implements either IArrayOf<UInt8>
, or IArrayOf<UInt16>
, depending on whether it was stored as a UTF-8 or UTF-16 encoded string. Characters arrays produced by MatFileHandler are always encoded as UTF-16.
Structure arrays
Structure arrays have elements that are indexed not only by their positions in the array, but also by structure fields. For example, a 1×2 structure array s
with fields x
and y
has four elements: s(1).x
, s(1).y
, s(2).x
, s(2).y
(in MATLAB notation). This means that if you only specify the numerical indices, you get a dictionary that maps string
to IArray
; in order to reach a specific element, you need to provide both the indices and the field name:
public interface IStructureArray : IArrayOf<IReadOnlyDictionary<string, IArray>>
{
IEnumerable<string> FieldNames { get; }
IArray this[string field, params int[] list] { get; set; }
}
Here FieldNames
gives you a list of all fields in the structure.
Sparse arrays
Sparse array is like a numerical array, but not all of the values in it have to be specified; the rest are assumed to be 0.
public interface ISparseArrayOf<T> : IArrayOf<T>
where T : struct
{
new IReadOnlyDictionary<(int, int), T> Data { get; }
}
Since ISparseArrayOf<T>
implements IArrayOf<T>
, you still can access all the elements in a sparse array (you'll get 0 when the element is not present). Alternatively, you can get a dictionary of all (possibly) non-zero elements. MATLAB only supports double, complex, and logical sparse arrays, so T
here can be Double
, Complex
or Boolean
(which, of course, uses false
as the default value).
Writing a .mat file
After reading a file into IMatFile matFile
, you can alter some values using the described interfaces, and write the result to a new file:
using (var fileStream = new System.IO.FileStream("output.mat", System.IO.FileMode.Create)) {
var writer = new MatFileWriter(fileStream);
writer.Write(matFile);
}
Another option is to create a file from scratch. You can do it with DataBuilder
class:
public class DataBuilder
{
public IArrayOf<T> NewArray<T>(params int[] dimensions)
where T : struct;
public IArrayOf<T> NewArray<T>(T[] data, params int[] dimensions)
where T : struct;
public ICellArray NewCellArray(params int[] dimensions);
public IStructureArray NewStructureArray(IEnumerable<string> fields, params int[] dimensions);
public ICharArray NewCharArray(string contents);
public ICharArray NewCharArray(string contents, params int[] dimensions);
public IArray NewEmpty();
public ISparseArrayOf<T> NewSparseArray<T>(params int[] dimensions)
where T : struct;
public IVariable NewVariable(string name, IArray value, bool isGlobal = false);
public IMatFile NewFile(IEnumerable<IVariable> variables);
}
Numerical/logical arrays can be created with NewArray<T>()
using the provided data; char arrays can be created with NewCharArray()
using a string. All other types of arrays are created empty. Then you can wrap an array into a variable with NewVariable()
, and put a bunch of variables into a file using NewFile()
. The resulting file can be written to a stream using MatFileWriter
, as shown above.