1 //-----------------------------------------------------------------------------
2 //
3 // ImageLib Sources
4 // Copyright (C) 2000-2009 by Denton Woods
5 // Last modified: 02/14/2009
6 //
7 // Filename: src-IL/src/il_fits.c
8 //
9 // Description: Reads from a Flexible Image Transport System (.fits) file.
10 //                Specifications were found at
11 //                http://www.fileformat.info/format/fits.
12 //
13 //-----------------------------------------------------------------------------
14 
15 
16 #include "il_internal.h"
17 #ifndef IL_NO_FITS
18 
19 typedef struct FITSHEAD
20 {
21 	ILboolean	IsSimple;
22 	ILint		BitsPixel;
23 	ILint		NumAxes;  // Number of dimensions / axes
24 	ILint		Width;
25 	ILint		Height;
26 	ILint		Depth;
27 	ILint		NumChans;
28 
29 	// Not in the header, but it keeps everything together.
30 	ILenum		Type;
31 	ILenum		Format;
32 
33 } FITSHEAD;
34 
35 enum {
36 	CARD_READ_FAIL = -1,
37 	CARD_END = 1,
38 	CARD_SIMPLE,
39 	CARD_NOT_SIMPLE,
40 	CARD_BITPIX,
41 	CARD_NUMAXES,
42 	CARD_AXIS,
43 	CARD_SKIP
44 };
45 
46 ILboolean	iIsValidFits(void);
47 ILboolean	iCheckFits(FITSHEAD *Header);
48 ILboolean	iLoadFitsInternal(void);
49 ILenum		GetCardImage(FITSHEAD *Header);
50 ILboolean	GetCardInt(char *Buffer, ILint *Val);
51 
52 //! Checks if the file specified in FileName is a valid FITS file.
ilIsValidFits(ILconst_string FileName)53 ILboolean ilIsValidFits(ILconst_string FileName)
54 {
55 	ILHANDLE	FitsFile;
56 	ILboolean	bFits = IL_FALSE;
57 
58 	if (!iCheckExtension(FileName, IL_TEXT("fits")) && !iCheckExtension(FileName, IL_TEXT("fit"))) {
59 		ilSetError(IL_INVALID_EXTENSION);
60 		return bFits;
61 	}
62 
63 	FitsFile = iopenr(FileName);
64 	if (FitsFile == NULL) {
65 		ilSetError(IL_COULD_NOT_OPEN_FILE);
66 		return bFits;
67 	}
68 
69 	bFits = ilIsValidFitsF(FitsFile);
70 	icloser(FitsFile);
71 
72 	return bFits;
73 }
74 
75 
76 //! Checks if the ILHANDLE contains a valid FITS file at the current position.
ilIsValidFitsF(ILHANDLE File)77 ILboolean ilIsValidFitsF(ILHANDLE File)
78 {
79 	ILuint		FirstPos;
80 	ILboolean	bRet;
81 
82 	iSetInputFile(File);
83 	FirstPos = itell();
84 	bRet = iIsValidFits();
85 	iseek(FirstPos, IL_SEEK_SET);
86 
87 	return bRet;
88 }
89 
90 
91 //! Checks if Lump is a valid FITS lump.
ilIsValidFitsL(const void * Lump,ILuint Size)92 ILboolean ilIsValidFitsL(const void *Lump, ILuint Size)
93 {
94 	iSetInputLump(Lump, Size);
95 	return iIsValidFits();
96 }
97 
98 
99 // Internal function used to get the FITS header from the current file.
iGetFitsHead(FITSHEAD * Header)100 ILboolean iGetFitsHead(FITSHEAD *Header)
101 {
102 	ILenum	CardKey;
103 
104 //@TODO: Use something other than memset?
105 	memset(Header, 0, sizeof(Header));  // Clear the header to all 0s first.
106 
107 	do {
108 		CardKey = GetCardImage(Header);
109 		if (CardKey == CARD_END)  // End of the header
110 			break;
111 		if (CardKey == CARD_READ_FAIL)
112 			return IL_FALSE;
113 		if (CardKey == CARD_NOT_SIMPLE)
114 			return IL_FALSE;
115 	} while (!ieof());
116 
117 	// The header should never reach the end of the file.
118 	if (ieof())
119 		return IL_FALSE;  // Error needed?
120 
121 	// The header must always be a multiple of 2880, so we skip the padding bytes (spaces).
122 	iseek((2880 - (itell() % 2880)) % 2880, IL_SEEK_CUR);
123 
124 	switch (Header->BitsPixel)
125 	{
126 		case 8:
127 			Header->Type = IL_UNSIGNED_BYTE;
128 			break;
129 		case 16:
130 			Header->Type = IL_SHORT;
131 			break;
132 		case 32:
133 			Header->Type = IL_INT;
134 			break;
135 		case -32:
136 			Header->Type = IL_FLOAT;
137 			break;
138 		case -64:
139 			Header->Type = IL_DOUBLE;
140 			break;
141 		default:
142 			ilSetError(IL_INVALID_FILE_HEADER);
143 			return IL_FALSE;
144 	}
145 
146 	switch (Header->NumAxes)
147 	{
148 		case 1:  // Just a 1D image
149 			Header->Format = IL_LUMINANCE;
150 			Header->Height = 1;
151 			Header->Depth = 1;
152 			Header->NumChans = 1;
153 			break;
154 
155 		case 2:  // Assuming it is a 2D image (width+height)
156 			Header->Format = IL_LUMINANCE;
157 			Header->Depth = 1;
158 			Header->NumChans = 1;
159 			break;
160 
161 		case 3:
162 			// We cannot deal with more than 3 channels in an image.
163 			Header->Format = IL_LUMINANCE;
164 			Header->NumChans = 1;
165 			break;
166 
167 		default:
168 			ilSetError(IL_INVALID_FILE_HEADER);
169 			return IL_FALSE;
170 	}
171 
172 	return IL_TRUE;
173 }
174 
175 
176 // Internal function to get the header and check it.
iIsValidFits(void)177 ILboolean iIsValidFits(void)
178 {
179 	FITSHEAD	Header;
180 	ILuint		Pos = itell();
181 
182 	if (!iGetFitsHead(&Header))
183 		return IL_FALSE;
184 	// The length of the header varies, so we just go back to the original position.
185 	iseek(Pos, IL_SEEK_CUR);
186 
187 	return iCheckFits(&Header);
188 }
189 
190 
191 // Internal function used to check if the HEADER is a valid FITS header.
iCheckFits(FITSHEAD * Header)192 ILboolean iCheckFits(FITSHEAD *Header)
193 {
194 	switch (Header->BitsPixel)
195 	{
196 		case 8:  // These are the only values accepted.
197 		case 16:
198 		case 32:
199 		case -32:
200 		case -64:
201 			break;
202 		default:
203 			return IL_FALSE;
204 	}
205 
206 	switch (Header->NumAxes)
207 	{
208 		case 1:  // Just a 1D image
209 		case 2:  // Assuming it is a 2D image (width+height)
210 		case 3:  // 3D image (with depth)
211 			break;
212 		default:
213 			return IL_FALSE;
214 	}
215 
216 	// Possibility that one of these values is returned as <= 0 by atoi, which we cannot use.
217 	if (Header->Width <= 0 || Header->Height <= 0 || Header->Depth <= 0) {
218 		ilSetError(IL_INVALID_FILE_HEADER);
219 		return IL_FALSE;
220 	}
221 
222 	return IL_TRUE;
223 }
224 
225 
226 //! Reads a FITS file
ilLoadFits(ILconst_string FileName)227 ILboolean ilLoadFits(ILconst_string FileName)
228 {
229 	ILHANDLE	FitsFile;
230 	ILboolean	bFits = IL_FALSE;
231 
232 	FitsFile = iopenr(FileName);
233 	if (FitsFile == NULL) {
234 		ilSetError(IL_COULD_NOT_OPEN_FILE);
235 		return bFits;
236 	}
237 
238 	bFits = ilLoadFitsF(FitsFile);
239 	icloser(FitsFile);
240 
241 	return bFits;
242 }
243 
244 
245 //! Reads an already-opened FITS file
ilLoadFitsF(ILHANDLE File)246 ILboolean ilLoadFitsF(ILHANDLE File)
247 {
248 	ILuint		FirstPos;
249 	ILboolean	bRet;
250 
251 	iSetInputFile(File);
252 	FirstPos = itell();
253 	bRet = iLoadFitsInternal();
254 	iseek(FirstPos, IL_SEEK_SET);
255 
256 	return bRet;
257 }
258 
259 
260 //! Reads from a memory "lump" that contains a FITS
ilLoadFitsL(const void * Lump,ILuint Size)261 ILboolean ilLoadFitsL(const void *Lump, ILuint Size)
262 {
263 	iSetInputLump(Lump, Size);
264 	return iLoadFitsInternal();
265 }
266 
267 
268 // Internal function used to load the FITS.
iLoadFitsInternal(void)269 ILboolean iLoadFitsInternal(void)
270 {
271 	FITSHEAD	Header;
272 	ILuint		i, NumPix;
273 	ILfloat		MaxF = 0.0f;
274 	ILdouble	MaxD = 0.0f;
275 
276 	if (iCurImage == NULL) {
277 		ilSetError(IL_ILLEGAL_OPERATION);
278 		return IL_FALSE;
279 	}
280 
281 	if (!iGetFitsHead(&Header))
282 		return IL_FALSE;
283 	if (!iCheckFits(&Header))
284 		return IL_FALSE;
285 
286 	if (!ilTexImage(Header.Width, Header.Height, Header.Depth, Header.NumChans, Header.Format, Header.Type, NULL))
287 		return IL_FALSE;
288 
289 	/*if (iread(iCurImage->Data, 1, iCurImage->SizeOfData) != iCurImage->SizeOfData)
290 		return IL_FALSE;*/
291 
292 	NumPix = Header.Width * Header.Height * Header.Depth;
293 //@TODO: Do some checks while reading to see if we have hit the end of the file.
294 	switch (Header.Type)
295 	{
296 		case IL_UNSIGNED_BYTE:
297 			if (iread(iCurImage->Data, 1, iCurImage->SizeOfData) != iCurImage->SizeOfData)
298 				return IL_FALSE;
299 			break;
300 		case IL_SHORT:
301 			for (i = 0; i < NumPix; i++) {
302 				((ILshort*)iCurImage->Data)[i] = GetBigShort();
303 			}
304 			break;
305 		case IL_INT:
306 			for (i = 0; i < NumPix; i++) {
307 				((ILint*)iCurImage->Data)[i] = GetBigInt();
308 			}
309 			break;
310 		case IL_FLOAT:
311 			for (i = 0; i < NumPix; i++) {
312 				((ILfloat*)iCurImage->Data)[i] = GetBigFloat();
313 				if (((ILfloat*)iCurImage->Data)[i] > MaxF)
314 					MaxF = ((ILfloat*)iCurImage->Data)[i];
315 			}
316 
317 			// Renormalize to [0..1].
318 			for (i = 0; i < NumPix; i++) {
319 				// Change all negative numbers to 0.
320 				if (((ILfloat*)iCurImage->Data)[i] < 0.0f)
321 					((ILfloat*)iCurImage->Data)[i] = 0.0f;
322 				// Do the renormalization now, dividing by the maximum value.
323 				((ILfloat*)iCurImage->Data)[i] = ((ILfloat*)iCurImage->Data)[i] / MaxF;
324 			}
325 			break;
326 		case IL_DOUBLE:
327 			for (i = 0; i < NumPix; i++) {
328 				((ILdouble*)iCurImage->Data)[i] = GetBigDouble();
329 				if (((ILdouble*)iCurImage->Data)[i] > MaxD)
330 					MaxD = ((ILdouble*)iCurImage->Data)[i];
331 			}
332 
333 			// Renormalize to [0..1].
334 			for (i = 0; i < NumPix; i++) {
335 				// Change all negative numbers to 0.
336 				if (((ILdouble*)iCurImage->Data)[i] < 0.0f)
337 					((ILdouble*)iCurImage->Data)[i] = 0.0f;
338 				// Do the renormalization now, dividing by the maximum value.
339 				((ILdouble*)iCurImage->Data)[i] = ((ILdouble*)iCurImage->Data)[i] / MaxD;
340 			}			break;
341 	}
342 
343 	return ilFixImage();
344 }
345 
346 
347 //@TODO: NAXISx have to come in order.  Check this!
GetCardImage(FITSHEAD * Header)348 ILenum GetCardImage(FITSHEAD *Header)
349 {
350 	char	Buffer[80];
351 
352 	if (iread(Buffer, 1, 80) != 80)  // Each card image is exactly 80 bytes long.
353 		return CARD_READ_FAIL;
354 
355 //@TODO: Use something other than !strncmp?
356 	if (!strncmp(Buffer, "END ", 4))
357 		return CARD_END;
358 
359 	else if (!strncmp(Buffer, "SIMPLE ", 7)) {
360 		// The true value 'T' is always in the 30th position.
361 		if (Buffer[29] != 'T') {
362 			// We cannot support FITS files that do not correspond to the standard.
363 			Header->IsSimple = IL_FALSE;  //@TODO: Does this even need to be set?  Should exit loading anyway.
364 			ilSetError(IL_FORMAT_NOT_SUPPORTED);
365 			return CARD_NOT_SIMPLE;
366 		}
367 		Header->IsSimple = IL_TRUE;
368 		return CARD_SIMPLE;
369 	}
370 
371 	else if (!strncmp(Buffer, "BITPIX ", 7)) {
372 		// The specs state that BITPIX has to come after SIMPLE.
373 		if (Header->IsSimple != IL_TRUE) {
374 			ilSetError(IL_INVALID_FILE_HEADER);
375 			return CARD_READ_FAIL;
376 		}
377 		if (GetCardInt(Buffer, &Header->BitsPixel) != IL_TRUE)
378 			return CARD_READ_FAIL;
379 //@TODO: Should I do this check from the calling function?  Does it really matter?
380 		if (Header->BitsPixel == 0) {
381 			ilSetError(IL_FORMAT_NOT_SUPPORTED);
382 			return CARD_READ_FAIL;
383 		}
384 		return CARD_BITPIX;
385 	}
386 
387 	// Needs the space after NAXIS so that it does not get this confused with NAXIS1, NAXIS2, etc.
388 	else if (!strncmp(Buffer, "NAXIS ", 6)) {
389 		if (GetCardInt(Buffer, &Header->NumAxes) != IL_TRUE)
390 			return CARD_READ_FAIL;
391 //@TODO: Should I do this check from the calling function?  Does it really matter?
392 		if (Header->NumAxes < 1 || Header->NumAxes > 3) {
393 			ilSetError(IL_FORMAT_NOT_SUPPORTED);
394 			return CARD_READ_FAIL;
395 		}
396 		return CARD_NUMAXES;
397 	}
398 
399 	else if (!strncmp(Buffer, "NAXIS1 ", 7)) {
400 		if (Header->NumAxes == 0) {  // Has not been initialized, and it has to come first.
401 			ilSetError(IL_INVALID_FILE_HEADER);
402 			return CARD_READ_FAIL;
403 		}
404 		// First one will always be the width.
405 		if (GetCardInt(Buffer, &Header->Width) != IL_TRUE)
406 			return CARD_READ_FAIL;
407 		return CARD_AXIS;
408 	}
409 
410 	else if (!strncmp(Buffer, "NAXIS2 ", 7)) {
411 		if (Header->NumAxes == 0) {  // Has not been initialized, and it has to come first.
412 			ilSetError(IL_INVALID_FILE_HEADER);
413 			return CARD_READ_FAIL;
414 		}
415 		// Cannot have a 2nd axis for 0 or 1.
416 		if (Header->NumAxes == 0 || Header->NumAxes == 1) {
417 			ilSetError(IL_INVALID_FILE_HEADER);
418 			return CARD_READ_FAIL;
419 		}
420 
421 //@TODO: We are assuming that this is the height right now.  Could it just be a
422 //  1D image with multiple bytes per pixel?
423 		if (GetCardInt(Buffer, &Header->Height) != IL_TRUE)
424 			return CARD_READ_FAIL;
425 		return CARD_AXIS;
426 	}
427 
428 	else if (!strncmp(Buffer, "NAXIS3 ", 7)) {
429 		if (Header->NumAxes == 0) {  // Has not been initialized, and it has to come first.
430 			ilSetError(IL_INVALID_FILE_HEADER);
431 			return CARD_READ_FAIL;
432 		}
433 		// Cannot have a 3rd axis for 0, 1 and 2.
434 		if (Header->NumAxes < 3) {
435 			ilSetError(IL_INVALID_FILE_HEADER);
436 			return CARD_READ_FAIL;
437 		}
438 
439 		if (GetCardInt(Buffer, &Header->Depth) != IL_TRUE)
440 			return CARD_READ_FAIL;
441 		return CARD_AXIS;
442 	}
443 
444 	return CARD_SKIP;  // This is a card that we do not recognize, so skip it.
445 }
446 
447 
GetCardInt(char * Buffer,ILint * Val)448 ILboolean GetCardInt(char *Buffer, ILint *Val)
449 {
450 	ILuint	i;
451 	char	ValString[22];
452 
453 	if (Buffer[7] != '=' && Buffer[8] != '=')
454 		return IL_FALSE;
455 	for (i = 9; i < 30; i++) {
456 		if (Buffer[i] != ' ' && Buffer[i] != 0)  // Right-aligned with ' ' or 0, so skip.
457 			break;
458 	}
459 	if (i == 30)  // Did not find the integer in positions 10 - 30.
460 		return IL_FALSE;
461 
462   //@TODO: Safest way to do this?
463 	memcpy(ValString, &Buffer[i], 30-i);
464 	ValString[30-i] = 0;  // Terminate the string.
465 
466 	//@TODO: Check the return somehow?
467 	*Val = atoi(ValString);
468 
469 	return IL_TRUE;
470 }
471 
472 #endif//IL_NO_FITS
473