private static 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.UTF8.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.UTF8.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.UTF8.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.UTF8.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.UTF8.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;
}