MatFileHandler/MatFileHandler/DataElementConverter.cs

246 lines
10 KiB
C#
Executable File

// Copyright 2017-2018 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);
var imaginaryParts = DataExtraction.GetDataAsDouble(imaginaryData);
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).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))
{
if (imaginaryData is null)
{
throw new HandlerException("Imaginary part of a complex variable not found.");
}
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)
{
return TryConvertDataToProperType<T>(data, arrayType)
?? throw new HandlerException($"Unexpected data type.");
}
private static T[]? ConvertDataToSparseProperType<T>(DataElement data, bool isLogical)
{
if (isLogical)
{
return DataExtraction.GetDataAsUInt8(data).Select(x => x != 0).ToArray() as T[];
}
switch (data)
{
case MiNum<double> _:
return DataExtraction.GetDataAsDouble(data) 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 row, int column), 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;
}
private static T[]? TryConvertDataToProperType<T>(DataElement data, ArrayType arrayType)
{
return arrayType switch
{
ArrayType.MxDouble => DataExtraction.GetDataAsDouble(data) as T[],
ArrayType.MxSingle => DataExtraction.GetDataAsSingle(data) as T[],
ArrayType.MxInt8 => DataExtraction.GetDataAsInt8(data) as T[],
ArrayType.MxUInt8 => DataExtraction.GetDataAsUInt8(data) as T[],
ArrayType.MxInt16 => DataExtraction.GetDataAsInt16(data) as T[],
ArrayType.MxUInt16 => DataExtraction.GetDataAsUInt16(data) as T[],
ArrayType.MxInt32 => DataExtraction.GetDataAsInt32(data) as T[],
ArrayType.MxUInt32 => DataExtraction.GetDataAsUInt32(data) as T[],
ArrayType.MxInt64 => DataExtraction.GetDataAsInt64(data) as T[],
ArrayType.MxUInt64 => DataExtraction.GetDataAsUInt64(data) as T[],
_ => throw new NotSupportedException()
};
}
}
}