private FITSImageInfo ReadHeader( Stream stream )
{
byte[] headerRecord = new byte[80];
int recordsRead = 1;
bool endKeyWasFound = false;
FITSImageInfo imageInfo = new FITSImageInfo( );
// read first record and check for correct image
if (
( Tools.ReadStream( stream, headerRecord, 0, 80 ) < 80 ) ||
( Encoding.ASCII.GetString( headerRecord, 0, 8 ) != "SIMPLE " ) )
{
throw new FormatException( "The stream does not contatin FITS image." );
}
else
{
// check if the image has standard FITS format
if ( Encoding.ASCII.GetString( headerRecord, 10, 70 ).Split( '/' )[0].Trim( ) != "T" )
{
throw new NotSupportedException( "The stream contains not standard FITS data file." );
}
}
// read header and locate data block
while ( true )
{
// read next record
if ( Tools.ReadStream( stream, headerRecord, 0, 80 ) < 80 )
{
throw new ArgumentException( "The stream does not contain valid FITS image." );
}
recordsRead++;
// get keyword
string keyword = Encoding.ASCII.GetString( headerRecord, 0, 8 );
// skip commenct and history
if ( ( keyword == "COMMENT " ) || ( keyword == "HISTORY " ) )
continue;
// check if it is end of header keyword
if ( keyword == "END " )
endKeyWasFound = true;
if ( endKeyWasFound )
{
if ( recordsRead % 36 == 0 )
{
// found data or extension header
// make a small check of some header values
if ( ( imageInfo.BitsPerPixel == 0 ) || ( imageInfo.Width == 0 ) || ( imageInfo.Height == 0 ) )
{
imageInfo.TotalFrames = 0;
}
// let's return here and let other routines process data
break;
}
}
else
{
// get string representation of value/comments
string strValue = Encoding.ASCII.GetString( headerRecord, 10, 70 );
// check important keywords
if ( keyword == "BITPIX " )
{
int value = ExtractIntegerValue( strValue );
if ( ( value != 8 ) && ( value != 16 ) && ( value != 32 ) && ( value != -32 ) && ( value != -64 ) )
{
throw new NotSupportedException( "Data format (" + value + ") is not supported." );
}
// bits per pixel
imageInfo.BitsPerPixel = ( value == 8 ) ? 8 : 16;
imageInfo.OriginalBitsPerPixl = value;
}
else if ( Encoding.ASCII.GetString( headerRecord, 0, 5 ) == "NAXIS" )
{
// information about data axis
int value = ExtractIntegerValue( strValue );
// check axis
switch ( headerRecord[5] )
{
// number of axis
case (byte) ' ':
switch ( value )
{
case 1:
default:
throw new NotSupportedException( "FITS files with data dimension of " + value + " are not supported." );
case 0:
// the stream does not have an image, do nothing
break;
case 2:
// the stream has 1 2D image
imageInfo.TotalFrames = 1;
break;
case 3:
// the stream has 3D image - series of 2D images
break;
}
break;
// length of 1st axis
case (byte) '1':
imageInfo.Width = value;
break;
// length of 2nd axis
case (byte) '2':
imageInfo.Height = value;
break;
// length of 3rd axis
case (byte) '3':
imageInfo.TotalFrames = value;
break;
}
}
else if ( keyword == "TELESCOP" )
{
imageInfo.Telescope = ExtractStringValue( strValue );
}
else if ( keyword == "OBJECT " )
{
imageInfo.Object = ExtractStringValue( strValue );
}
else if ( keyword == "OBSERVER" )
{
imageInfo.Observer = ExtractStringValue( strValue );
}
else if ( keyword == "INSTRUME" )
{
imageInfo.Instrument = ExtractStringValue( strValue );
}
// --- for debugging ---
/* if ( keyword[0] != ' ' )
{
System.Diagnostics.Debug.Write( keyword );
if ( headerRecord[8] == '=' )
{
System.Diagnostics.Debug.WriteLine( " = " + strValue );
}
else
{
System.Diagnostics.Debug.WriteLine( "" );
}
} */
// --- ---
}
}
// scan all available data to find minimum and maximum values,
// which will be used for scaling. the scan is done here (not while
// reading actual frame) because FITS file may have set of images
// packed into data cube, so entire scan of all the data is required.
// if is stream is seekable
if ( !stream.CanSeek )
{
throw new ArgumentException( "The stream must be seekable." );
}
// remember current position
long dataPos = stream.Seek( 0, SeekOrigin.Current );
// data size
int lineLength = imageInfo.Width * ( Math.Abs( imageInfo.OriginalBitsPerPixl ) / 8 );
int totalLines = imageInfo.Height * imageInfo.TotalFrames;
int originalBitsPerPixel = imageInfo.OriginalBitsPerPixl;
byte[] buffer = new byte[lineLength];
byte[] temp = new byte[8];
// min and max values
double min = double.MaxValue;
double max = double.MinValue;
for ( int i = 0; i < totalLines; i++ )
{
// read next line
if ( Tools.ReadStream( stream, buffer, 0, lineLength ) < lineLength )
throw new ArgumentException( "The stream does not contain valid FITS image." );
// scan the line
for ( int j = 0; j < lineLength; )
{
double value = 0;
// read values accordint to their format
switch ( originalBitsPerPixel )
{
case 8: // 8 bit unsigned integer
value = buffer[j++];
break;
case 16: // 16 bit signed integer
{
short tempValue = 0;
unchecked
{
tempValue = (short) ( ( buffer[j++] << 8 ) | buffer[j++] );
}
value = tempValue;
break;
}
case 32: // 32 bit signed integer
{
temp[3] = buffer[j++];
temp[2] = buffer[j++];
temp[1] = buffer[j++];
temp[0] = buffer[j++];
value = BitConverter.ToInt32( temp, 0 );
break;
}
case -32: // 32 bit float
{
temp[3] = buffer[j++];
temp[2] = buffer[j++];
temp[1] = buffer[j++];
temp[0] = buffer[j++];
value = BitConverter.ToSingle( temp, 0 );
break;
}
case -64: // 64 bit double
{
temp[7] = buffer[j++];
temp[6] = buffer[j++];
temp[5] = buffer[j++];
temp[4] = buffer[j++];
temp[3] = buffer[j++];
temp[2] = buffer[j++];
temp[1] = buffer[j++];
temp[0] = buffer[j++];
value = BitConverter.ToDouble( temp, 0 );
break;
}
}
if ( value > max )
max = value;
if ( value < min )
min = value;
}
}
imageInfo.MaxDataValue = max;
imageInfo.MinDataValue = min;
// restore stream position to the begining of data
stream.Seek( dataPos, SeekOrigin.Begin );
return imageInfo;
}