///
/// Class supporting R&D processes involving SimFile objects.
/// 3 Mar 2009 - D.Bozarth - Sonoma State Engineering Science
///
#region Using directives
using System;
using System.Collections.Generic;
using System.IO;
using Utility;
#endregion
namespace SimFile
{
public class DataTools : SimFile
{
/*
* Pre: byteBlock has been refreshed with any content.
* Post: The existing byteBlock data has been written to outStream, using format f.
*/
public void WriteByteBlock( Stream outStream, FileFormat f ) {
int height = byteBlock.Count;
int width = 0;
for ( int i = 0; i < height; ++i ) {
if ( width < byteBlock[i].Length ) {
width = byteBlock[i].Length;
}
}
byte[,] block = new byte[height, width];
for ( int i = 0; i < height; ++i ) {
for ( int j = 0; j < width; ++j ) {
int limit = byteBlock[i].Length;
if ( j < limit ) {
block[i, j] = byteBlock[i][j];
} else {
block[i, j] = 0;
}
}
}
WriteSignal( outStream, f, block );
} // method WriteByteBlock
/*
* Pre: byteBlock has been refreshed with the natural complex-only content.
* Post: A regular 2D array has been returned modeling the specified signal,
* ordered naturally (first by component, then by increasing byte position).
*/
public byte[,] GetSignal(
SignalId[] sarIn,
ComponentId[] carIn,
ByteOffset[] barIn,
Pair startPos,
Pair finitPos
){
int wmin;
int[] extra;
SignalId[] sar = ExpandSignalId( sarIn );
ComponentId[] car = carIn;
ByteOffset[] bar = ExpandByteOffset( barIn );
Pair scale = GetByteBlockScale( out wmin, out extra );
// srcV, srcH are the dimensions of a fundamental single-byte signal array.
int srcV = scale.First;
int srcH = scale.Second / nBytesSignalPerSample;
// Allocate the returned array.
int dstV = srcV;
int dstH = srcH * sar.Length * car.Length * bar.Length;
byte[,] signal = new byte[dstV, dstH];
// coordinates of starting & ending points of signal per (sweepId, sampleId).
int bgnV = startPos.First;
int endV = finitPos.First;
int bgnH = startPos.Second;
int endH = finitPos.Second;
ArrayQuery.JustifyLimits(ref bgnV, ref endV, 0, srcV - 1);
ArrayQuery.JustifyLimits(ref bgnH, ref endH, 0, srcH - 1);
int ndxV = bgnV; // Sweep index on byteBlock
int ndxH = bgnH * nBytesSignalPerSample; // Sample pointer on byteBlock
int nPtsV = endV - bgnV + 1;
int nPtsH = endH - bgnH + 1;
for (int i = 0; i < nPtsV; ++i) { // for each sweep
int posV = ndxV + i; // row index on byteBlock
for (int j = 0; j < nPtsH; ++j) { // for each sample
for (int k = 0; k < sar.Length; ++k) {
SignalId sid = sar[k];
for (int m = 0; m < car.Length; ++m) {
ComponentId cid = car[m];
for (int n = 0; n < bar.Length; ++n) {
ByteOffset bof = bar[n];
int myH = j * sar.Length * car.Length * bar.Length // sample
+ k * car.Length * bar.Length // signal
+ m * bar.Length // component
+ n // byte
; // column index on signal
int posH = ndxH
+ j * nBytesSignalPerSample // sample
+ (int)sid * nBytesPerSignal // signal
+ (int)cid * nBytesPerComponent // component
+ (int)bof // byte
; // column index on byteBlock
if (posH < byteBlock[posV].Length) {
signal[i, myH] = byteBlock[posV][posH];
} else {
signal[i, myH] = 0;
}
}
}
}
}
}
return signal;
} // method GetSignal
/*
* Note: Here the term "signal" merely refers to an homogeneous block of bytes. (Not A, B, etc.)
* Pre: signal width in bytes must divide nBytesPerComponent.
* Post: signal has been written to outStream using format f.
* Note: Some argument combinations may not be consistent or useful. No checking is done.
*/
public static void WriteSignal( Stream outStream, FileFormat f, byte[,] signal ) {
int height = signal.GetLength(0);
int width = signal.GetLength(1);
char[] cn = Environment.NewLine.ToCharArray();
byte[] bn = new byte[cn.Length];
switch ( f ) {
case FileFormat.Xml : return;
case FileFormat.Binary :
for ( int i = 0; i < height; ++i ) {
for ( int j = 0; j < width; ++j ) {
outStream.WriteByte( signal[i, j] );
}
}
break;
case FileFormat.CsvByte :
for ( int i = 0; i < cn.Length; ++i ) {
bn[i] = (byte) cn[i];
}
for ( int i = 0; i < height; ++i ) {
for ( int j = 0; j < width; ++j ) {
string str = signal[i, j].ToString().PadLeft(3, '0');
if ( j + 1 < width ) str += ",";
char[] car = str.ToCharArray();
byte[] bar = new byte[ car.Length ];
for ( int k = 0; k < car.Length; ++k ) {
bar[k] = (byte) car[k];
}
outStream.Write( bar, 0, bar.Length );
}
outStream.Write( bn, 0, bn.Length );
}
break;
case FileFormat.Csv :
width /= nBytesPerComponent;
for ( int i = 0; i < cn.Length; ++i ) {
bn[i] = (byte) cn[i];
}
for ( int i = 0; i < height; ++i ) {
for ( int j = 0; j < width; ++j ) {
int ndxH = j * nBytesPerComponent;
byte[] word = new byte[nBytesPerComponent];
for ( int k = 0; k < nBytesPerComponent; ++k ) {
word[k] = signal[i, ndxH + k];
}
//
float val = BitConverter.ToSingle( word, 0 );
//
//string str = val.ToString("R");
string str = val.ToString( "E9" );
if ( j + 1 < width ) str += ",";
char[] car = str.ToCharArray();
byte[] bar = new byte[ car.Length ];
for ( int k = 0; k < car.Length; ++k ) {
bar[k] = (byte) car[k];
}
outStream.Write( bar, 0, bar.Length );
}
outStream.Write( bn, 0, bn.Length );
}
break;
default : return;
}
} // method WriteSignal
/*
* ReadSignal
* Note: Here the term "signal" merely refers to an homogeneous block of bytes. (Not A, B, etc.)
* Pre: inStream renders a signal corresponding to the specified parameters.
* In particular, inStream "raw equivalent" length
* .. must divide (height * width * nBytesPerComponent).
* .. If FileFormat is Csv, then each separated value = 4 "raw equivalents".
* .. If format is CsvByte, then each separated value = 1 "raw equivalent".
* .. If format is Binary, then each byte = 1 "raw equivalent".
* Note: signal must be formatted in terms of "raw equivalents" (bytes) also.
* .. In practical terms, since a float is 4 bytes wide, 201 floats = 804 bytes wide.
* Post: (1) signal has been parsed from inStream using format f.
* (2) return -1 => inStream did not fill signal, and signal was padded with zero bytes.
* return 0 => inStream filled signal exactly.
* return 1 => inStream overflowed signal, and one or more bytes were dropped.
*/
public static Io.Result ReadSignal( Stream inStream, FileFormat f, byte[,] signal ) {
int height = signal.GetLength(0);
int width = signal.GetLength(1);
char[] cn = Environment.NewLine.ToCharArray();
byte[] bn = new byte[cn.Length];
bool padFlg = false;
switch ( f ) {
case FileFormat.Xml : return Io.Result.Underflow; // need to pad
case FileFormat.Binary :
for ( int i = 0; i < height; ++i ) {
for ( int j = 0; j < width; ++j ) {
int next = 0;
if ( padFlg == false ) {
next = inStream.ReadByte();
if (next == -1) padFlg = true;
}
if ( padFlg == false ) {
signal[i, j] = Convert.ToByte( next );
} else {
signal[i, j] = 0;
}
}
}
if ( padFlg == true ) return Io.Result.Underflow;
if ( inStream.Position + 1 < inStream.Length ) return Io.Result.Overflow;
return Io.Result.OK;
break;
case FileFormat.CsvByte :
byte myByte = 0;
for ( int i = 0; i < height; ++i ) {
for ( int j = 0; j < width; ++j ) {
if ( padFlg == false ) {
int nok = ByteArrayConverter.ReadByteFromCsvStream(inStream, ref myByte);
if ( nok == 0 ) {
signal[i, j] = myByte;
} else {
padFlg = true;
}
}
if ( padFlg == true ) signal[i, j] = 0;
}
}
if ( padFlg == true ) return Io.Result.Underflow;
if ( inStream.Position + 1 < inStream.Length ) return Io.Result.Overflow;
return Io.Result.OK;
case FileFormat.Csv :
float myFloat = 0;
byte[] bar = new byte[nBytesPerComponent];
for ( int i = 0; i < height; ++i ) {
int limit = width / nBytesPerComponent;
for ( int j = 0; j < limit; ++j ) {
if ( padFlg == false ) {
int nok = ByteArrayConverter.ReadFloatFromCsvStream(inStream, ref myFloat);
if ( nok == 0 ) {
int zro = 0;
ByteArrayConverter.SetFloat( bar, ref zro, myFloat );
//** bar = BitConverter.GetBytes( myFloat ); **
for ( int k = 0; k < nBytesPerComponent; ++k ) {
signal[i, j*nBytesPerComponent + k] = bar[k];
}
} else {
padFlg = true;
int zro = 0;
ByteArrayConverter.SetFloat( bar, ref zro, (float) 0 );
}
}
if ( padFlg == true ) {
for ( int k = 0; k < nBytesPerComponent; ++k ) {
signal[i, j*nBytesPerComponent + k] = bar[k];
}
}
} // for j
} // for i
if ( padFlg == true ) return Io.Result.Underflow;
if ( inStream.Position + 1 < inStream.Length ) return Io.Result.Overflow;
return Io.Result.OK;
default : return Io.Result.Underflow;
}
} // method ReadSignal
/*
* Pre: streamIn0 and streamIn1 represent Binary files.
* The input streams must be the same size, and their size must divide nBytesPerComponent.
* Post: streamOut is the absolute value byte-wise difference between the input streams.
* rtn: zero => No byte position other than zero, has a nonzero difference.
* nonzero => Some byte position other than zero, has a nonzero difference.
* ... Note: method returns immediately if this condition is detected.
*/
public static int DiffByteAbs( Stream streamIn0, Stream streamIn1, Stream streamOut ) {
int rtn = 0;
streamIn0.Position = 0;
streamIn1.Position = 0;
streamOut.Position = 0;
while ( streamIn0.Position < streamIn0.Length ) {
int val0 = streamIn0.ReadByte();
int val1 = streamIn1.ReadByte();
byte dif = (byte) Math.Abs( val0 - val1 );
if ( (0 < dif) && (streamOut.Position % nBytesPerComponent != 0) ) {
rtn += 1;
//break;
}
streamOut.WriteByte( dif );
}
return rtn;
}
/*
* Pre: All 4 streams must be the same size.
* The size of each stream must divide nBytesPerComponent.
* Each nBytesPerComponent-byte set of stream positions represents
* .. a single-precision floating-point number.
* Post: Using the first pair of streams as input, the conversion indicated by
* ... toPolFlg (0 => polar-to-rectangular, 1 => rectangular-to-polar)
* ... appears in the second pair of streams.
* Returns false if size mismatch detected, true otherwise.
* (See also the description of ReadSignal.)
*/
public static bool ConvertCoordinates(
Stream reOrMa, Stream imOrAn, FileFormat fIn,
Stream maOrRe, Stream anOrIm, FileFormat fOut,
int height, int width, bool toPolFlg
){
byte[,] a = new byte[height, width];
byte[,] b = new byte[height, width];
byte[,] x = new byte[height, width];
byte[,] y = new byte[height, width];
Io.Result rtn0 = DataTools.ReadSignal(reOrMa, fIn, a);
Io.Result rtn1 = DataTools.ReadSignal(imOrAn, fIn, b);
ByteArrayManipulator.ConvertCoordinates(a, b, x, y, nBytesPerComponent, toPolFlg);
DataTools.WriteSignal(maOrRe, fOut, x);
DataTools.WriteSignal(anOrIm, fOut, y);
switch ( rtn0 ) {
case Io.Result.Underflow :
case Io.Result.Overflow : return false;
case Io.Result.OK :
default :
switch ( rtn1 ) {
case Io.Result.Underflow :
case Io.Result.Overflow : return false;
case Io.Result.OK :
default : return true;
}
}
} // method ConvertCoordinates
public static string AppendSuffixToFileStem( string fileStem, FileFormat ff ) {
string rtn = fileStem;
switch (ff) {
case FileFormat.Binary : rtn += ".raw"; break;
case FileFormat.Csv : rtn += ".csv"; break;
case FileFormat.CsvByte : rtn += ".byt.csv"; break;
}
return rtn;
}
/*
* Pre: The content of fileIn represents a homogeneous block of bytes
* ... described by formatIn, height, width.
* Post: The content of fileOut represents a homogeneous block of bytes
* ... described by formatIn, height, width.
*/
public static Io.Result ConvertFileRaw(
string fileIn, FileFormat formatIn,
string fileOut, FileFormat formatOut,
int height,
int width
){
byte[,] signal = new byte[height, width];
Stream inStream = File.OpenRead(fileIn);
Io.Result result = DataTools.ReadSignal(inStream, formatIn, signal);
inStream.Close();
switch ( result ) {
case Io.Result.OK :
File.Delete(fileOut);
Stream outStream = File.OpenWrite(fileOut);
DataTools.WriteSignal(outStream, formatOut, signal);
outStream.Close();
break;
case Io.Result.Underflow :
case Io.Result.Overflow :
default : break;
}
return result;
} // method ConvertFileRaw
/*
* Pre: (1) byteBlock has been refreshed with the native sequence of complex-only content.
* (2) The horizontal dimension of byteBlock must divide nBytesSignalPerSample.
* .. Actually (2) is implied by (1) "the native sequence of complex-only content".
* (There is no presumption that byteBlock models a regular array.)
* Post: The interleaved RAW-sequence data has been written to outStream, using format f.
* The assumption is that different signals (A, B, etc.) are streamed sequentially.
* Note: Some argument combinations may not be consistent or useful. No checking is done here.
*/
public void WriteByteBlockRaw(
Stream outStream,
FileFormat f,
SignalId[] sar,
ComponentId[] car,
ByteOffset[] bar
){
int wmin;
int[] extra;
Pair scale = GetByteBlockScale( out wmin, out extra );
int srcV = scale.First;
int srcH = scale.Second / nBytesSignalPerSample;
Pair startPos = new Pair(0, 0);
Pair finitPos = new Pair(srcV-1, srcH-1 );
byte[,] signal = GetSignal(sar, car, bar, startPos, finitPos);
WriteSignal( outStream, f, signal );
} // method WriteByteBlockRaw
/*
* See notes on WriteByteBlockRaw
*/
public void WriteByteBlockComplexRaw(
Stream outStreamX,
Stream outStreamY,
FileFormat f,
SignalId[] sar,
bool toPolFlg
){
int wmin;
int[] extra;
Pair scale = GetByteBlockScale( out wmin, out extra );
int srcV = scale.First;
int srcH = scale.Second / nBytesSignalPerSample;
Pair startPos = new Pair(0, 0);
Pair finitPos = new Pair(srcV-1, srcH-1 );
ByteOffset[] bar = { ByteOffset.All };
ComponentId[] carRe = { ComponentId.Real };
ComponentId[] carIm = { ComponentId.Imag };
byte[,] re = GetSignal(sar, carRe, bar, startPos, finitPos);
byte[,] im = GetSignal(sar, carIm, bar, startPos, finitPos);
if ( toPolFlg ) {
byte[,] ma = new byte[re.GetLength(0), re.GetLength(1)];
byte[,] an = new byte[re.GetLength(0), re.GetLength(1)];
ByteArrayManipulator.ConvertCoordinates( re, im, ma, an, nBytesPerComponent, toPolFlg );
WriteSignal( outStreamX, f, ma );
WriteSignal( outStreamY, f, an );
} else {
WriteSignal( outStreamX, f, re );
WriteSignal( outStreamY, f, im );
}
} // method WriteByteBlockComplexRaw
public SignalId[] ExpandSignalId( SignalId[] sarIn ) {
List list = new List();
for ( int i = 0; i < sarIn.Length; ++i ) {
if ( sarIn[i] == SignalId.All ) {
list.Add( SignalId.A );
list.Add( SignalId.B );
list.Add( SignalId.C );
list.Add( SignalId.D );
list.Add( SignalId.E );
} else {
list.Add( sarIn[i] );
}
}
return list.ToArray();
} // method ExpandSignalId
public ByteOffset[] ExpandByteOffset( ByteOffset[] barIn ) {
List list = new List();
for ( int i = 0; i < barIn.Length; ++i ) {
if ( barIn[i] == ByteOffset.All ) {
list.Add( ByteOffset.B0 );
list.Add( ByteOffset.B1 );
list.Add( ByteOffset.B2 );
list.Add( ByteOffset.B3 );
} else {
list.Add( barIn[i] );
}
}
return list.ToArray();
} // method ExpandByteOffset
/*
* Note: These routines are mis-named. They don't relate to ".jpg" files.
* They decompose the signal data into components like exponent, mantissa.
* The point was to transform a portion of the mantissa ..
* .. into a file ready for JPEG2000 compression.
* Pre: Content of the input files accords with the called method.
* Post: Content has been decomposed and re-routed.
* If height, width match both input file sizes, return value is true.
* ... Otherwise, return value is false.
*/
public static bool WriteJpgRaw(
string fileOutJpg,
string fileOutExpReal,
string fileOutExpImag,
string fileOutMtaReal,
string fileOutMtaImag,
string fileInReal,
string fileInImag,
FileFormat formatIn,
int height,
int width
){
Stream outJpg = File.OpenWrite( fileOutJpg );
Stream outExpReal = File.OpenWrite( fileOutExpReal );
Stream outExpImag = File.OpenWrite( fileOutExpImag );
Stream outMtaReal = File.OpenWrite( fileOutMtaReal );
Stream outMtaImag = File.OpenWrite( fileOutMtaImag );
Stream inStreamRe = File.OpenRead( fileInReal );
Stream inStreamIm = File.OpenRead( fileInImag );
byte[,] real = new byte[height, width];
byte[,] imag = new byte[height, width];
Io.Result resultRe = DataTools.ReadSignal( inStreamRe, formatIn, real );
Io.Result resultIm = DataTools.ReadSignal( inStreamIm, formatIn, imag );
DataTools.WriteJpgRaw( outJpg, outExpReal, outExpImag, outMtaReal, outMtaImag, real, imag );
switch ( resultRe ) {
case Io.Result.Underflow :
case Io.Result.Overflow : return false;
case Io.Result.OK :
default :
switch ( resultIm ) {
case Io.Result.Underflow :
case Io.Result.Overflow : return false;
case Io.Result.OK :
default : return true;
}
}
}
/*
* Pre: real & imag have same dimensions. nBytesPerComponent divides width.
* Post: real & imag have been decomposed and re-routed.
*/
public static void WriteJpgRaw(
Stream outJpg,
Stream outExpReal,
Stream outExpImag,
Stream outMtaReal,
Stream outMtaImag,
byte[,] real,
byte[,] imag
){
int height = real.GetLength(0);
int width = real.GetLength(1) / nBytesPerComponent;
for ( int i = 0; i < height; ++i ) {
for ( int j = 0; j < width; ++j ) {
byte[] ar = new byte[nBytesPerComponent];
byte[] ai = new byte[nBytesPerComponent];
for ( int k = 0; k < nBytesPerComponent; ++k ) {
ar[k] = real[i, j + k];
ai[k] = imag[i, j + k];
}
byte[] jpg;
byte exp, mta;
ConvertToJpg( out jpg, out exp, out mta, ar );
outJpg.Write( jpg, 0, jpg.Length );
//outJpg.Write( jpg, 0, 1 ); // MSB only! (JPEG2K only accepting 8 bits per component.)
outExpReal.WriteByte( exp );
outMtaReal.WriteByte( mta );
/*
ConvertToJpg( out jpg, out exp, out mta, ai );
outJpg.Write( jpg, 0, jpg.Length );
//outJpg.Write( jpg, 0, 1 ); // MSB only! (JPEG2K only accepting 8 bits per component.)
outExpImag.WriteByte( exp );
outMtaImag.WriteByte( mta );
*/
}
}
}
/*
* Pre: inWord has size 4.
* Post: Content of inWord has been split and redirected
* .. to a 2-byte array (jpg) and 2 single bytes (exp, mta).
* The order of jpg[0] and jpg[1] have been swapped
* .. to accomodate big-endian requirement of JPEG2000.
*/
public static void ConvertToJpg( out byte[] jpg, out byte exp, out byte mta, byte[] inWord ) {
jpg = new byte[2];
mta = inWord[0];
/*
jpg[1] = inWord[1];
jpg[0] = (byte)(inWord[2] & 0x7f); // mask bit 23 to 0
byte sign = (byte)(inWord[3] & 0x80); // bit 31
// jpg[0] |= sign; // append sign bit to jpg
*/
/**/
// un-swap for testing:
jpg[0] = inWord[1];
jpg[1] = (byte)(inWord[2] & 0x7f); // mask bit 23 to 0
byte sign = (byte)(inWord[3] & 0x80); // bit 31
// jpg[1] |= sign; // append sign bit to jpg
/**/
byte hibit = (byte)(inWord[2] & 0x80); // bit 23
byte lobit = (byte)(hibit >> 7); // reposition
exp = (byte)(inWord[3] << 1); // reposition
exp |= lobit;
} // method ConvertToJpg
} // class DataTools
} // namespace Precompress