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