/// /// 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