1 /*********************************************************************
2 Functions to work with FITS image data.
3 This is part of GNU Astronomy Utilities (Gnuastro) package.
4
5 Original author:
6 Mohammad Akhlaghi <mohammad@akhlaghi.org>
7 Contributing author(s):
8 Copyright (C) 2015-2021, Free Software Foundation, Inc.
9
10 Gnuastro is free software: you can redistribute it and/or modify it
11 under the terms of the GNU General Public License as published by the
12 Free Software Foundation, either version 3 of the License, or (at your
13 option) any later version.
14
15 Gnuastro is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with Gnuastro. If not, see <http://www.gnu.org/licenses/>.
22 **********************************************************************/
23 #include <config.h>
24
25 #include <time.h>
26 #include <errno.h>
27 #include <error.h>
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33
34 #include <gsl/gsl_version.h>
35
36 #include <gnuastro/git.h>
37 #include <gnuastro/wcs.h>
38 #include <gnuastro/list.h>
39 #include <gnuastro/fits.h>
40 #include <gnuastro/tile.h>
41 #include <gnuastro/blank.h>
42 #include <gnuastro/pointer.h>
43
44 #include <gnuastro-internal/checkset.h>
45 #include <gnuastro-internal/tableintern.h>
46 #include <gnuastro-internal/fixedstringmacros.h>
47
48
49
50
51
52
53
54
55
56
57 /*************************************************************
58 ************** Reporting errors: ***************
59 *************************************************************/
60 void
gal_fits_io_error(int status,char * message)61 gal_fits_io_error(int status, char *message)
62 {
63 char defmessage[]="Error in CFITSIO, see above.";
64 if(status)
65 {
66 fits_report_error(stderr, status);
67 if(message)
68 error(EXIT_FAILURE, 0, "%s", message);
69 else
70 error(EXIT_FAILURE, 0, "%s", defmessage);
71 }
72 }
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 /*************************************************************
94 ************** FITS name and file identification ************
95 *************************************************************/
96 /* IMPORTANT NOTE: if other compression suffixes are add to this function,
97 include them in 'gal_checkset_automatic_output', so the compression
98 suffix can be skipped when the user doesn't specify an output
99 filename.*/
100 int
gal_fits_name_is_fits(char * name)101 gal_fits_name_is_fits(char *name)
102 {
103 size_t len;
104
105 if(name)
106 {
107 len=strlen(name);
108 if ( ( len>=3 && strcmp(&name[len-3], "fit" ) == 0 )
109 || ( len>=4 && strcmp(&name[len-4], "fits" ) == 0 )
110 || ( len>=7 && strcmp(&name[len-7], "fits.gz" ) == 0 )
111 || ( len>=6 && strcmp(&name[len-6], "fits.Z" ) == 0 )
112 || ( len>=3 && strcmp(&name[len-3], "imh" ) == 0 )
113 || ( len>=7 && strcmp(&name[len-7], "fits.fz" ) == 0 ) )
114 return 1;
115 else
116 return 0;
117 }
118 else return 0;
119 }
120
121
122
123
124
125 /* IMPORTANT NOTE: if other compression suffixes are add to this function,
126 include them in 'gal_checkset_automatic_output', so the compression
127 suffix can be skipped when the user doesn't specify an output
128 filename.*/
129 int
gal_fits_suffix_is_fits(char * suffix)130 gal_fits_suffix_is_fits(char *suffix)
131 {
132 char *nodot;
133
134 if(suffix)
135 {
136 nodot=suffix[0]=='.' ? (suffix+1) : suffix;
137 if ( strcmp( nodot, "fit" ) == 0
138 || strcmp(nodot, "fits" ) == 0
139 || strcmp(nodot, "fits.gz" ) == 0
140 || strcmp(nodot, "fits.Z" ) == 0
141 || strcmp(nodot, "imh" ) == 0
142 || strcmp(nodot, "fits.fz" ) == 0 )
143 return 1;
144 else
145 return 0;
146 }
147 else return 0;
148 }
149
150
151
152
153
154 /* If the name is a FITS name, then put a '(hdu: ...)' after it and return
155 the string. If it isn't a FITS file, just print the name. Note that the
156 space is allocated. */
157 char *
gal_fits_name_save_as_string(char * filename,char * hdu)158 gal_fits_name_save_as_string(char *filename, char *hdu)
159 {
160 char *name;
161
162 /* Small sanity check. */
163 if(filename==NULL)
164 gal_checkset_allocate_copy("stdin", &name);
165 else
166 {
167 if( gal_fits_name_is_fits(filename) )
168 {
169 if( asprintf(&name, "%s (hdu: %s)", filename, hdu)<0 )
170 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
171 }
172 else gal_checkset_allocate_copy(filename, &name);
173 }
174 return name;
175 }
176
177
178
179
180
181 int
gal_fits_file_recognized(char * filename)182 gal_fits_file_recognized(char *filename)
183 {
184 FILE *tmpfile;
185 fitsfile *fptr;
186 int out=0, status=0;
187
188 /* Opening a FITS file can be CPU intensive (for example when its
189 compressed). So if the name has the correct suffix, we'll trust the
190 suffix. In this way, if the file name is correct, but the contents
191 doesn't follow the FITS standard, the opening function will complain
192 if its not a FITS file when trying to open it for its usage. */
193 if( gal_fits_name_is_fits(filename) ) return 1;
194
195 /* CFITSIO has some special conventions for file names (for example if
196 its '-', which can happen in the Arithmetic program). So before
197 passing to CFITSIO, we'll check if a file is actually associated with
198 the string. */
199 errno=0;
200 tmpfile = fopen(filename, "r");
201 if(tmpfile) /* The file existed and opened. */
202 {
203 /* Close the opened filed. */
204 if(fclose(tmpfile)==EOF)
205 error(EXIT_FAILURE, errno, "%s", filename);
206
207 /* Open the file with CFITSIO to see if its actually a FITS file. */
208 fits_open_file(&fptr, filename, READONLY, &status);
209
210 /* If it opened successfully then status will be zero and we can
211 safely close the file. Otherwise, this is not a recognized FITS
212 file for CFITSIO and we should just return 0. */
213 if(status==0)
214 {
215 if( fits_close_file(fptr, &status) )
216 gal_fits_io_error(status, NULL);
217 out=1;
218 }
219 }
220
221 /* Return the final value. */
222 return out;
223 }
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244 /*************************************************************
245 ************** Type codes ***************
246 *************************************************************/
247 uint8_t
gal_fits_bitpix_to_type(int bitpix)248 gal_fits_bitpix_to_type(int bitpix)
249 {
250 switch(bitpix)
251 {
252 case BYTE_IMG: return GAL_TYPE_UINT8;
253 case SBYTE_IMG: return GAL_TYPE_INT8;
254 case USHORT_IMG: return GAL_TYPE_UINT16;
255 case SHORT_IMG: return GAL_TYPE_INT16;
256 case ULONG_IMG: return GAL_TYPE_UINT32;
257 case LONG_IMG: return GAL_TYPE_INT32;
258 case LONGLONG_IMG: return GAL_TYPE_INT64;
259 case FLOAT_IMG: return GAL_TYPE_FLOAT32;
260 case DOUBLE_IMG: return GAL_TYPE_FLOAT64;
261 default:
262 error(EXIT_FAILURE, 0, "%s: bitpix value of %d not recognized",
263 __func__, bitpix);
264 }
265 return 0;
266 }
267
268
269
270
271
272 int
gal_fits_type_to_bitpix(uint8_t type)273 gal_fits_type_to_bitpix(uint8_t type)
274 {
275 switch(type)
276 {
277 case GAL_TYPE_UINT8: return BYTE_IMG;
278 case GAL_TYPE_INT8: return SBYTE_IMG;
279 case GAL_TYPE_UINT16: return USHORT_IMG;
280 case GAL_TYPE_INT16: return SHORT_IMG;
281 case GAL_TYPE_UINT32: return ULONG_IMG;
282 case GAL_TYPE_INT32: return LONG_IMG;
283 case GAL_TYPE_INT64: return LONGLONG_IMG;
284 case GAL_TYPE_FLOAT32: return FLOAT_IMG;
285 case GAL_TYPE_FLOAT64: return DOUBLE_IMG;
286
287 case GAL_TYPE_BIT:
288 case GAL_TYPE_STRLL:
289 case GAL_TYPE_STRING:
290 case GAL_TYPE_UINT64:
291 case GAL_TYPE_COMPLEX32:
292 case GAL_TYPE_COMPLEX64:
293 error(EXIT_FAILURE, 0, "%s: type %s not recognized for FITS image "
294 "BITPIX", __func__, gal_type_name(type, 1));
295
296 default:
297 error(EXIT_FAILURE, 0, "%s: type value of %d not recognized",
298 __func__, type);
299 }
300 return 0;
301 }
302
303
304
305
306
307 /* The values to the TFORM header keywords of FITS binary tables are single
308 letter capital letters, but that is useless in identifying the data type
309 of the column. So this function will do the conversion based on the
310 CFITSIO manual.*/
311 char
gal_fits_type_to_bin_tform(uint8_t type)312 gal_fits_type_to_bin_tform(uint8_t type)
313 {
314 switch(type)
315 {
316 /* Recognized by CFITSIO. */
317 case GAL_TYPE_STRING: return 'A';
318 case GAL_TYPE_BIT: return 'X';
319 case GAL_TYPE_UINT8: return 'B';
320 case GAL_TYPE_INT8: return 'S';
321 case GAL_TYPE_UINT16: return 'U';
322 case GAL_TYPE_INT16: return 'I';
323 case GAL_TYPE_UINT32: return 'V';
324 case GAL_TYPE_INT32: return 'J';
325 case GAL_TYPE_INT64: return 'K';
326 case GAL_TYPE_FLOAT32: return 'E';
327 case GAL_TYPE_FLOAT64: return 'D';
328 case GAL_TYPE_COMPLEX32: return 'C';
329 case GAL_TYPE_COMPLEX64: return 'M';
330
331 /* Not recognized by CFITSIO. */
332 case GAL_TYPE_UINT64:
333 error(EXIT_FAILURE, 0, "%s: type %s not recognized for FITS binary "
334 "table TFORM", __func__, gal_type_name(type, 1));
335 break;
336
337 /* Wrong type code. */
338 default:
339 error(EXIT_FAILURE, 0, "%s: type code %d not recognized", __func__, type);
340 }
341
342 error(EXIT_FAILURE, 0, "%s: s bug! Please contact us so we can fix this. "
343 "Control must not reach the end of this function", __func__);
344 return '\0';
345 }
346
347
348
349
350
351 int
gal_fits_type_to_datatype(uint8_t type)352 gal_fits_type_to_datatype(uint8_t type)
353 {
354 int w=0;
355
356 switch(type)
357 {
358 /* Recognized CFITSIO types. */
359 case GAL_TYPE_BIT: return TBIT;
360 case GAL_TYPE_UINT8: return TBYTE;
361 case GAL_TYPE_INT8: return TSBYTE;
362 case GAL_TYPE_FLOAT32: return TFLOAT;
363 case GAL_TYPE_FLOAT64: return TDOUBLE;
364 case GAL_TYPE_COMPLEX32: return TCOMPLEX;
365 case GAL_TYPE_COMPLEX64: return TDBLCOMPLEX;
366 case GAL_TYPE_STRING: return TSTRING;
367
368 /* Types that depend on the host system. The C standard says that the
369 'short', 'int' and 'long' types are ATLEAST 2, 2, 4 bytes, so to be
370 safe, we will check all of them for the 32-bit types.*/
371 case GAL_TYPE_UINT16:
372 w=2;
373 if ( sizeof(short) == w ) return TUSHORT;
374 else if( sizeof(int) == w ) return TUINT;
375 break;
376
377 case GAL_TYPE_INT16:
378 w=2;
379 if ( sizeof(short) == w ) return TSHORT;
380 else if( sizeof(int) == w ) return TINT;
381 break;
382
383 /* On 32-bit systems, the length of 'int' and 'long' are both
384 32-bits. But CFITSIO's LONG type is preferred because it is designed
385 to be 32-bit. Its 'INT' type is not clearly defined and caused
386 problems when reading keywords.*/
387 case GAL_TYPE_UINT32:
388 w=4;
389 if ( sizeof(long) == w ) return TULONG;
390 else if( sizeof(int) == w ) return TUINT;
391 else if( sizeof(short) == w ) return TUSHORT;
392 break;
393
394 /* Similar to UINT32 above. */
395 case GAL_TYPE_INT32:
396 w=4;
397 if ( sizeof(long) == w ) return TLONG;
398 else if( sizeof(int) == w ) return TINT;
399 else if( sizeof(short) == w ) return TSHORT;
400 break;
401
402 case GAL_TYPE_UINT64:
403 w=8;
404 if ( sizeof(long) == w ) return TULONG;
405 break;
406
407 case GAL_TYPE_INT64:
408 w=8;
409 if ( sizeof(long) == w ) return TLONG;
410 else if( sizeof(LONGLONG) == w ) return TLONGLONG;
411 break;
412
413 /* Wrong type. */
414 default:
415 error(EXIT_FAILURE, 0, "%s: type code %d is not a recognized",
416 __func__, type);
417 }
418
419 /* If control reaches, here, there was a problem with the host types. */
420 if(w)
421 error(EXIT_FAILURE, 0, "%s: this system doesn't have a %d byte integer "
422 "type, so type '%s' cannot be written to FITS", __func__, w,
423 gal_type_name(type, 1));
424 else
425 error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s so we can "
426 "fix the problem. Control must not have reached the end for the "
427 "given type '%s'", __func__, PACKAGE_BUGREPORT,
428 gal_type_name(type, 1));
429 return -1;
430 }
431
432
433
434
435
436 uint8_t
gal_fits_datatype_to_type(int datatype,int is_table_column)437 gal_fits_datatype_to_type(int datatype, int is_table_column)
438 {
439 int inttype;
440
441 switch(datatype)
442 {
443 case TBIT: return GAL_TYPE_BIT;
444 case TBYTE: return GAL_TYPE_UINT8;
445 case TSBYTE: return GAL_TYPE_INT8;
446 case TFLOAT: return GAL_TYPE_FLOAT32;
447 case TDOUBLE: return GAL_TYPE_FLOAT64;
448 case TCOMPLEX: return GAL_TYPE_COMPLEX32;
449 case TDBLCOMPLEX: return GAL_TYPE_COMPLEX64;
450 case TSTRING: return GAL_TYPE_STRING;
451
452 /* Sizes that depend on the host system. */
453 case TUSHORT:
454 switch( sizeof(short) )
455 {
456 case 2: return GAL_TYPE_UINT16;
457 case 4: return GAL_TYPE_UINT32;
458 case 8: return GAL_TYPE_UINT64;
459 }
460 break;
461
462 case TSHORT:
463 switch( sizeof(short) )
464 {
465 case 2: return GAL_TYPE_INT16;
466 case 4: return GAL_TYPE_INT32;
467 case 8: return GAL_TYPE_INT64;
468 }
469 break;
470
471 case TUINT:
472 switch( sizeof(int) )
473 {
474 case 2: return GAL_TYPE_UINT16;
475 case 4: return GAL_TYPE_UINT32;
476 case 8: return GAL_TYPE_UINT64;
477 }
478 break;
479
480 case TINT:
481 switch( sizeof(int) )
482 {
483 case 2: return GAL_TYPE_INT16;
484 case 4: return GAL_TYPE_INT32;
485 case 8: return GAL_TYPE_INT64;
486 }
487 break;
488
489 case TULONG:
490 switch( sizeof(long) )
491 {
492 case 4: return GAL_TYPE_UINT32;
493 case 8: return GAL_TYPE_UINT64;
494 }
495 break;
496
497 case TLONG: /* ==TINT32BIT when in a table column. */
498 if(is_table_column) return GAL_TYPE_INT32;
499 else
500 switch( sizeof(long) )
501 {
502 case 4: return GAL_TYPE_INT32;
503 case 8: return GAL_TYPE_INT64;
504 }
505 break;
506
507 case TLONGLONG:
508 return GAL_TYPE_INT64;
509 break;
510
511 /* The TLOGICAL depends on the context: for keywords, it is int32, for
512 table columns, it is int8. */
513 case TLOGICAL:
514 switch( sizeof(int) )
515 {
516 case 2: inttype=GAL_TYPE_INT16; break;
517 case 4: inttype=GAL_TYPE_INT32; break;
518 case 8: inttype=GAL_TYPE_INT64; break;
519 }
520 return is_table_column ? GAL_TYPE_INT8 : inttype;
521 break;
522
523 /* A bug! */
524 default:
525 error(EXIT_FAILURE, 0, "%s: %d is not a recognized CFITSIO datatype",
526 __func__, datatype);
527 }
528
529 error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s so we can fix "
530 "this. Control must not have reached the end of this function.",
531 __func__, PACKAGE_BUGREPORT);
532 return GAL_TYPE_INVALID;
533 }
534
535
536
537
538
539 /* When there is a BZERO or TZERO and BSCALE or TSCALE keywords, then the
540 type that must be used to store the actual values of the pixels may be
541 different from the type from BITPIX. This function does the necessary
542 corrections.*/
543 static void
fits_type_correct(int * type,double bscale,char * bzero_str)544 fits_type_correct(int *type, double bscale, char *bzero_str)
545 {
546 double bzero;
547 int tofloat=1;
548 char *tailptr, *bzero_u64="9223372036854775808";
549
550 /* If bzero_str is given and 'bscale=1.0' the case might be that we are
551 dealing with an integer dataset that just needs a different sign. */
552 if(bzero_str && bscale==1.0f)
553 {
554 /* Read the 'bzero' string as a 'double' number. */
555 bzero = strtod(bzero_str, &tailptr);
556 if(tailptr==bzero_str)
557 error(EXIT_FAILURE, 0, "%s: BZERO value '%s' couldn't be "
558 "parsed as a number", __func__, bzero_str);
559
560 /* Work based on type. For the default conversions defined by the
561 FITS standard to change the signs of integers, make the proper
562 correction, otherwise set the type to float. */
563 switch(*type)
564 {
565 case GAL_TYPE_UINT8:
566 if(bzero == -128.0f) { *type = GAL_TYPE_INT8; tofloat=0; }
567 break;
568
569 case GAL_TYPE_INT16:
570 if(bzero == 32768) { *type = GAL_TYPE_UINT16; tofloat=0; }
571 break;
572
573 case GAL_TYPE_INT32:
574 if(bzero == 2147483648LU) { *type = GAL_TYPE_UINT32; tofloat=0; }
575 break;
576
577 /* The 'bzero' value for unsigned 64-bit integers has 19 decimal
578 digits, but a 64-bit floating point ('double' type) can only
579 safely store 15 decimal digits. As a result, the safest way to
580 check the 'bzero' value for this type is to compare it as a
581 string. But all integers nearby (for example
582 '9223372036854775807') get rounded to this same value (when
583 stored as 'double'). So we will also check the parsed number and
584 if it equals this number, a warning will be printed. */
585 case GAL_TYPE_INT64:
586 if( !strcmp(bzero_str, bzero_u64) )
587 {*type = GAL_TYPE_UINT64; tofloat=0;}
588 else
589 if( bzero == 9223372036854775808LLU )
590 {
591 fprintf(stderr, "\nWARNING in %s: the BZERO header keyword "
592 "value ('%s') is very close (but not exactly equal) "
593 "to '%s'. The latter value in the FITS standard is "
594 "used to identify that the dataset should be read as "
595 "unsigned 64-bit integers instead of signed 64-bit "
596 "integers. Depending on your version of CFITSIO, "
597 "it may be read as a signed or unsigned 64-bit "
598 "integer array\n\n", __func__, bzero_str, bzero_u64);
599 tofloat=0;
600 }
601 break;
602
603 /* For the other types (when 'BSCALE=1.0f'), currently no
604 correction is necessary, maybe later we can check if the
605 scales are integers and set the integer output type to the
606 smallest type that can allow the scaled values. */
607 default: tofloat=0;
608 }
609 }
610
611 /* If the type must be a float, then do the conversion. */
612 if(tofloat) *type=GAL_TYPE_FLOAT32;
613 }
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634 /**************************************************************/
635 /********** HDU ************/
636 /**************************************************************/
637 fitsfile *
gal_fits_open_to_write(char * filename)638 gal_fits_open_to_write(char *filename)
639 {
640 int status=0;
641 long naxes=0;
642 fitsfile *fptr;
643
644 /* When the file exists just open it. Otherwise, create the file. But we
645 want to leave the first extension as a blank extension and put the
646 image in the next extension to be consistent between tables and
647 images. */
648 if(access(filename,F_OK) == -1 )
649 {
650 /* Create the file. */
651 if( fits_create_file(&fptr, filename, &status) )
652 gal_fits_io_error(status, NULL);
653
654 /* Create blank extension. */
655 if( fits_create_img(fptr, BYTE_IMG, 0, &naxes, &status) )
656 gal_fits_io_error(status, NULL);
657
658 /* Close the blank extension. */
659 if( fits_close_file(fptr, &status) )
660 gal_fits_io_error(status, NULL);
661 }
662
663 /* Open the file, ready for later steps. */
664 if( fits_open_file(&fptr, filename, READWRITE, &status) )
665 gal_fits_io_error(status, NULL);
666
667 /* Return the pointer. */
668 return fptr;
669 }
670
671
672
673
674
675 size_t
gal_fits_hdu_num(char * filename)676 gal_fits_hdu_num(char *filename)
677 {
678 fitsfile *fptr;
679 int num, status=0;
680
681 /* We don't need to check for an error everytime, because we don't
682 make any non CFITSIO usage of the output. It is necessary to
683 check every CFITSIO call, only when you will need to use the
684 outputs. */
685 fits_open_file(&fptr, filename, READONLY, &status);
686
687 fits_get_num_hdus(fptr, &num, &status);
688
689 fits_close_file(fptr, &status);
690
691 gal_fits_io_error(status, NULL);
692
693 return num;
694 }
695
696
697
698
699
700 /* Calculate the datasum of the given HDU in the given file. */
701 unsigned long
gal_fits_hdu_datasum(char * filename,char * hdu)702 gal_fits_hdu_datasum(char *filename, char *hdu)
703 {
704 int status=0;
705 fitsfile *fptr;
706 unsigned long datasum;
707
708 /* Read the desired extension (necessary for reading the rest). */
709 fptr=gal_fits_hdu_open(filename, hdu, READONLY);
710
711 /* Calculate the datasum. */
712 datasum=gal_fits_hdu_datasum_ptr(fptr);
713
714 /* Close the file and return. */
715 fits_close_file(fptr, &status);
716 gal_fits_io_error(status, "closing file");
717 return datasum;
718 }
719
720
721
722
723
724 /* Calculate the FITS standard datasum for the opened FITS pointer. */
725 unsigned long
gal_fits_hdu_datasum_ptr(fitsfile * fptr)726 gal_fits_hdu_datasum_ptr(fitsfile *fptr)
727 {
728 int status=0;
729 unsigned long datasum, hdusum;
730
731 /* Calculate the checksum and datasum of the opened HDU. */
732 fits_get_chksum(fptr, &datasum, &hdusum, &status);
733 gal_fits_io_error(status, "estimating datasum");
734
735 /* Return the datasum. */
736 return datasum;
737 }
738
739
740
741
742
743 /* Given the filename and HDU, this function will return the CFITSIO code
744 for the type of data it contains (table, or image). The CFITSIO codes
745 are:
746
747 IMAGE_HDU: An image HDU.
748 ASCII_TBL: An ASCII table HDU.
749 BINARY_TBL: BINARY TABLE HDU. */
750 int
gal_fits_hdu_format(char * filename,char * hdu)751 gal_fits_hdu_format(char *filename, char *hdu)
752 {
753 fitsfile *fptr;
754 int hdutype, status=0;
755
756 /* Open the HDU. */
757 fptr=gal_fits_hdu_open(filename, hdu, READONLY);
758
759 /* Check the type of the given HDU: */
760 if (fits_get_hdu_type(fptr, &hdutype, &status) )
761 gal_fits_io_error(status, NULL);
762
763 /* Clean up and return.. */
764 if( fits_close_file(fptr, &status) )
765 gal_fits_io_error(status, NULL);
766 return hdutype;
767 }
768
769
770
771
772
773 /* Return 1 if the HDU appears to be a HEALpix grid. */
774 int
gal_fits_hdu_is_healpix(fitsfile * fptr)775 gal_fits_hdu_is_healpix(fitsfile *fptr)
776 {
777 long value;
778 int hdutype, status=0;
779
780 /* An ASCII table can't be a healpix table. */
781 if (fits_get_hdu_type(fptr, &hdutype, &status) )
782 gal_fits_io_error(status, NULL);
783 if(hdutype==ASCII_TBL) return 0;
784
785 /* If all these keywords are present, then 'status' will be 0
786 afterwards. */
787 fits_read_key_lng(fptr, "NSIDE", &value, 0x0, &status);
788 fits_read_key_lng(fptr, "FIRSTPIX", &value, 0x0, &status);
789 fits_read_key_lng(fptr, "LASTPIX", &value, 0x0, &status);
790 return !status;
791 }
792
793
794
795
796
797 /* Open a given HDU and return the FITS pointer. 'iomode' determines how
798 the FITS file will be opened: only to read or to read and write. You
799 should use the macros given by the CFITSIO header:
800
801 READONLY: read-only.
802 READWRITE: read and write. */
803 fitsfile *
gal_fits_hdu_open(char * filename,char * hdu,int iomode)804 gal_fits_hdu_open(char *filename, char *hdu, int iomode)
805 {
806 int status=0;
807 char *ffname;
808 fitsfile *fptr;
809
810 /* Add hdu to filename: */
811 if( asprintf(&ffname, "%s[%s#]", filename, hdu)<0 )
812 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
813
814 /* Open the FITS file: */
815 if( fits_open_file(&fptr, ffname, iomode, &status) )
816 {
817 switch(status)
818 {
819 /* Since the default HDU is '1', when the file only has one
820 extension, this error is common, so we will put a special
821 notice. */
822 case END_OF_FILE:
823 if( hdu[0]=='1' && hdu[1]=='\0' )
824 error(EXIT_FAILURE, 0, "%s: only has one HDU.\n\n"
825 "You should tell Gnuastro's command-line "
826 "programs to look for data in the primary HDU with the "
827 "'--hdu=0' option (or '-h0'). For library users, you can "
828 "put \"0\" (a string literal) for the function's HDU "
829 "argument. For more, see the FOOTNOTE below.\n\n"
830 "Pro TIP: if your desired HDU has a name (value to "
831 "'EXTNAME' keyword), it is best to just use that name "
832 "with '--hdu' instead of relying on a counter. You can "
833 "see the list of HDUs in a FITS file (with their data "
834 "format, type, size and possibly HDU name) using "
835 "Gnuastro's 'astfits' program, for example:\n\n"
836 " astfits %s\n\n"
837 "FOOTNOTE -- When writing a new FITS file, Gnuastro leaves "
838 "the pimary HDU only for metadata. The output datasets "
839 "(tables, images or cubes) are written after the primary "
840 "HDU. In this way the keywords of the the first HDU can be "
841 "used as metadata of the whole file (which may contain many "
842 "extensions, this is stipulated in the FITS standard). "
843 "Usually the primary HDU keywords contains the option names "
844 "and values that the program was run with. Because of this, "
845 "Gnuastro's default HDU to read data in a FITS file is the "
846 "second (or '--hdu=1'). This error is commonly caused when "
847 "the FITS file wasn't created by Gnuastro or by a program "
848 "respecting this convention.", filename, filename);
849 break;
850
851 /* Generic error below is fine for this case */
852 case BAD_HDU_NUM:
853 break;
854
855 /* In case an un-expected error occurs, use the general CFITSIO
856 reporting that we have already prepared. */
857 default:
858 gal_fits_io_error(status, "opening the given extension/HDU in "
859 "the given file");
860 }
861
862 error(EXIT_FAILURE, 0, "%s: extension/HDU '%s' doesn't exist. Please "
863 "run the following command to see the extensions/HDUs in "
864 "'%s':\n\n"
865 " $ astfits %s\n\n"
866 "The respective HDU number (or name, when present) may be used "
867 "with the '--hdu' option in Gnuastro's programs (or the 'hdu' "
868 "argument in Gnuastro's libraries) to open the respective HDU.",
869 filename, hdu, filename, filename);
870 }
871
872 /* Clean up and the pointer. */
873 free(ffname);
874 return fptr;
875 }
876
877
878
879
880
881 /* Check the desired HDU in a FITS image and also if it has the
882 desired type. */
883 fitsfile *
gal_fits_hdu_open_format(char * filename,char * hdu,int img0_tab1)884 gal_fits_hdu_open_format(char *filename, char *hdu, int img0_tab1)
885 {
886 fitsfile *fptr;
887 int status=0, hdutype;
888
889 /* A small sanity check. */
890 if(hdu==NULL)
891 error(EXIT_FAILURE, 0, "no HDU specified for %s", filename);
892
893 /* Open the HDU. */
894 fptr=gal_fits_hdu_open(filename, hdu, READONLY);
895
896 /* Check the type of the given HDU: */
897 if (fits_get_hdu_type(fptr, &hdutype, &status) )
898 gal_fits_io_error(status, NULL);
899
900 /* Check if the type of the HDU is the expected type. We could have
901 written these as && conditions, but this is easier to read, it makes
902 no meaningful difference to the compiler. */
903 if(img0_tab1)
904 {
905 if(hdutype==IMAGE_HDU)
906 error(EXIT_FAILURE, 0, "%s (hdu: %s): is not a table",
907 filename, hdu);
908 }
909 else
910 {
911 if(hdutype!=IMAGE_HDU)
912 {
913 /* Let the user know. */
914 if( gal_fits_hdu_is_healpix(fptr) )
915 error(EXIT_FAILURE, 0, "%s (hdu: %s): appears to be a HEALPix table "
916 "(which is a 2D dataset on a spherical surface: stored as "
917 "a 1D table). You can use the 'HPXcvt' command-line utility "
918 "to convert it to a 2D image that can easily be used by "
919 "other programs. 'HPXcvt' is built and installed as part of "
920 "WCSLIB (which is a mandatory dependency of Gnuastro, so "
921 "you should already have it), run 'man HPXcvt' for more",
922 filename, hdu);
923 else
924 error(EXIT_FAILURE, 0, "%s (hdu: %s): not an image",
925 filename, hdu);
926 }
927 }
928
929 /* Clean up and return. */
930 return fptr;
931 }
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952 /**************************************************************/
953 /********** Header keywords ************/
954 /**************************************************************/
955 /* Return allocated pointer to the blank value to use in a FITS file header
956 keyword. */
957 void *
gal_fits_key_img_blank(uint8_t type)958 gal_fits_key_img_blank(uint8_t type)
959 {
960 void *out=NULL, *tocopy=NULL;
961 uint8_t u8=0; /* Equivalent of minimum in signed with BZERO. */
962 int16_t s16=INT16_MAX; /* Equivalend of maximum in unsigned with BZERO. */
963 int32_t s32=INT32_MAX; /* Equivalend of maximum in unsigned with BZERO. */
964 int64_t s64=INT64_MAX; /* Equivalend of maximum in unsigned with BZERO. */
965
966 switch(type)
967 {
968 /* Types with no special treatment: */
969 case GAL_TYPE_BIT:
970 case GAL_TYPE_UINT8:
971 case GAL_TYPE_INT16:
972 case GAL_TYPE_INT32:
973 case GAL_TYPE_INT64:
974 case GAL_TYPE_FLOAT32:
975 case GAL_TYPE_FLOAT64:
976 case GAL_TYPE_COMPLEX32:
977 case GAL_TYPE_COMPLEX64:
978 case GAL_TYPE_STRING:
979 case GAL_TYPE_STRLL:
980 out = gal_blank_alloc_write(type);
981 break;
982
983 /* Types that need values from their opposite-signed types. */
984 case GAL_TYPE_INT8: tocopy=&u8; break;
985 case GAL_TYPE_UINT16: tocopy=&s16; break;
986 case GAL_TYPE_UINT32: tocopy=&s32; break;
987 case GAL_TYPE_UINT64: tocopy=&s64; break;
988
989 default:
990 error(EXIT_FAILURE, 0, "%s: type code %u not recognized as a Gnuastro "
991 "data type", __func__, type);
992 }
993
994 /* If 'gal_blank_alloc_write' wasn't used (copy!=NULL), then allocate the
995 necessary space and fill it in. Note that the width of the signed and
996 unsigned values doesn't differ, so we can use the actual input
997 type. */
998 if(tocopy)
999 {
1000 out = gal_pointer_allocate(type, 1, 0, __func__, "out");
1001 memcpy(out, tocopy, gal_type_sizeof(type));
1002 }
1003
1004 /* Return. */
1005 return out;
1006 }
1007
1008
1009
1010
1011
1012 /* CFITSIO doesn't remove the two single quotes around the string value, so
1013 the strings it reads are like: 'value ', or 'some_very_long_value'. To
1014 use the value, it is commonly necessary to remove the single quotes (and
1015 possible extra spaces). This function will modify the string in its own
1016 allocated space. You can use this to later free the original string (if
1017 it was allocated). */
1018 void
gal_fits_key_clean_str_value(char * string)1019 gal_fits_key_clean_str_value(char *string)
1020 {
1021 int end; /* Has to be int because size_t is always >=0. */
1022 char *c, *cf;
1023
1024 /* Start from the second last character (the last is a single quote) and
1025 go down until you hit a non-space character. This will also work when
1026 there is no space characters between the last character of the value
1027 and ending single-quote: it will be set to '\0' after this loop. */
1028 for(end=strlen(string)-2;end>=0;--end)
1029 if(string[end]!=' ')
1030 break;
1031
1032 /* Shift all the characters after the first one (which is a ''' back by
1033 one and put the string ending characters on the 'end'th element. */
1034 cf=(c=string)+end; do *c=*(c+1); while(++c<cf);
1035 *cf='\0';
1036 }
1037
1038
1039
1040
1041 /* Fill the 'tm' structure (defined in 'time.h') with the values derived
1042 from a FITS format date-string and return the (optional) sub-second
1043 information as a double.
1044
1045 The basic FITS string is defined under the 'DATE' keyword in the FITS
1046 standard. For the more complete format which includes timezones, see the
1047 W3 standard: https://www.w3.org/TR/NOTE-datetime */
1048 char *
gal_fits_key_date_to_struct_tm(char * fitsdate,struct tm * tp)1049 gal_fits_key_date_to_struct_tm(char *fitsdate, struct tm *tp)
1050 {
1051 int hasT=0, hassq=0, usesdash=0, usesslash=0, hasZ=0;
1052 char *C, *cc, *c=NULL, *cf, *subsec=NULL, *nosubsec=fitsdate;
1053
1054 /* Initialize the 'tm' structure to all-zero elements. In particular, The
1055 FITS standard times are written in UTC, so, the time zone ('tm_zone'
1056 element, which specifies number of seconds to shift for the time zone)
1057 has to be zero. The day-light saving flag ('isdst' element) also has
1058 to be set to zero. */
1059 tp->tm_sec=tp->tm_min=tp->tm_hour=tp->tm_mday=tp->tm_mon=tp->tm_year=0;
1060 tp->tm_wday=tp->tm_yday=tp->tm_isdst=tp->tm_gmtoff=0;
1061 tp->tm_zone=NULL;
1062
1063 /* According to the FITS standard the 'T' in the middle of the date and
1064 time of day is optional (the time is not mandatory). */
1065 cf=(c=fitsdate)+strlen(fitsdate);
1066 do
1067 switch(*c)
1068 {
1069 case 'T': hasT=1; break; /* With 'T' HH:MM:SS are defined. */
1070 case '-': usesdash=1; break; /* Day definition: YYYY-MM-DD. */
1071 case '/': usesslash=1; break; /* Day definition(old): DD/MM/YY. */
1072 case '\'': hassq=1; break; /* Wholly Wrapped in a single-quote. */
1073 case 'Z': hasZ=1; break; /* When ends in 'Z', means UTC. See */
1074 /* https://www.w3.org/TR/NOTE-datetime */
1075
1076 /* In case we have sub-seconds in the string, we need to remove it
1077 because 'strptime' doesn't recognize sub-second resolution.*/
1078 case '.':
1079 /* Allocate space (by copying the remaining full string and adding
1080 a '\0' where necessary. */
1081 gal_checkset_allocate_copy(c, &subsec);
1082 gal_checkset_allocate_copy(fitsdate, &nosubsec);
1083
1084 /* Parse the sub-second part and remove it from the copy. */
1085 cc=nosubsec+(c-fitsdate);
1086 for(C=subsec+1;*C!='\0';C++)
1087 if(!isdigit(*C)) {*cc++=*C; *C='\0';}
1088 *cc='\0';
1089 }
1090 while(++c<cf);
1091
1092 /* Convert this date into seconds since 1970/01/01, 00:00:00. */
1093 c = ( (usesdash==0 && usesslash==0)
1094 ? NULL
1095 : ( usesdash
1096 ? ( hasT
1097 ? ( hasZ
1098 ? strptime(nosubsec, hassq?"'%FT%TZ'":"%FT%TZ", tp)
1099 : strptime(nosubsec, hassq?"'%FT%T'":"%FT%T", tp) )
1100 : strptime(nosubsec, hassq?"'%F'" :"%F" , tp))
1101 : ( hasT
1102 ? ( hasZ
1103 ? strptime(nosubsec, hassq?"'%d/%m/%yT%TZ'":"%d/%m/%yT%TZ", tp)
1104 : strptime(nosubsec, hassq?"'%d/%m/%yT%T'":"%d/%m/%yT%T", tp))
1105 : strptime(nosubsec, hassq?"'%d/%m/%y'" :"%d/%m/%y" , tp)
1106 )
1107 )
1108 );
1109
1110 /* The value might have sub-seconds. In that case, 'c' will point to a
1111 '.' and we'll have to parse it as double. */
1112 if( c==NULL || (*c!='.' && *c!='\0') )
1113 error(EXIT_FAILURE, 0, "'%s' isn't in the FITS date format.\n\n"
1114 "According to the FITS standard, the date must be in one of "
1115 "these formats:\n"
1116 " YYYY-MM-DD\n"
1117 " YYYY-MM-DDThh:mm:ss\n"
1118 " YYYY-MM-DDThh:mm:ssZ (Note the 'Z', see *) \n"
1119 " DD/MM/YY (Note the 'YY', see ^)\n"
1120 " DD/MM/YYThh:mm:ss\n"
1121 " DD/MM/YYThh:mm:ssZ\n\n"
1122 "[*]: The 'Z' is interpreted as being in the UTC Timezone.\n"
1123 "[^]: Gnuastro's FITS library (this program), interprets the "
1124 "older (two character for year) format, year values 68 to 99 as "
1125 "the years 1969 to 1999 and values 0 to 68 as the years 2000 to "
1126 "2068.", fitsdate);
1127
1128 /* If the subseconds were removed (and a new string was allocated), free
1129 that extra new string. */
1130 if(nosubsec!=fitsdate) free(nosubsec);
1131
1132 /* Return the subsecond value. */
1133 return subsec;
1134 }
1135
1136
1137
1138
1139
1140 /* Convert the FITS standard date format (as a string, already read from
1141 the keywords) into number of seconds since 1970/01/01, 00:00:00. Very
1142 useful to avoid calendar issues like number of days in a different
1143 months or leap years and etc. The remainder of the format string
1144 (sub-seconds) will be put into the two pointer arguments: 'subsec' is in
1145 double-precision floating point format and */
1146 size_t
gal_fits_key_date_to_seconds(char * fitsdate,char ** subsecstr,double * subsec)1147 gal_fits_key_date_to_seconds(char *fitsdate, char **subsecstr,
1148 double *subsec)
1149 {
1150 time_t t;
1151 char *tmp;
1152 struct tm tp;
1153 size_t seconds;
1154 void *subsecptr=subsec;
1155
1156 /* If the string is blank, return a blank value. */
1157 if( strcmp(fitsdate, GAL_BLANK_STRING)==0 )
1158 {
1159 if(subsec) *subsec=NAN;
1160 if(subsecstr) *subsecstr=NULL;
1161 return GAL_BLANK_SIZE_T;
1162 }
1163
1164 /* Fill in the 'tp' elements with values read from the string. */
1165 tmp=gal_fits_key_date_to_struct_tm(fitsdate, &tp);
1166
1167 /* If the user wanted a possible sub-second string/value, then
1168 'subsecstr!=NULL'. */
1169 if(subsecstr)
1170 {
1171 /* Set the output pointer. Note that it may be NULL if there was no
1172 sub-second string, but that is fine (and desired because the user
1173 can use this to check if there was a sub-string or not). */
1174 *subsecstr=tmp;
1175
1176 /* If there was a sub-second string, then also read it as a
1177 double-precision floating point. */
1178 if(tmp)
1179 {
1180 if(subsec)
1181 if( gal_type_from_string(&subsecptr, tmp, GAL_TYPE_FLOAT64) )
1182 error(EXIT_FAILURE, 0, "%s: the sub-second portion of '%s' (or "
1183 "'%s') couldn't be read as a number", __func__, fitsdate,
1184 tmp);
1185 }
1186 else { if(subsec) *subsec=NAN; }
1187 }
1188
1189 /* Convert the contents of the 'tm' structure to 'time_t' (a positive
1190 integer) with 'mktime'. Note that by design, the system's timezone is
1191 included in the returned value of 'mktime' (leading to situations like
1192 bug #57995). But it writes the given time's timezone (number of
1193 seconds ahead of UTC) in the 'tm_gmtoff' element of its input.
1194
1195 IMPORTANT NOTE: the timezone that is calculated by 'mktime' (in
1196 'tp.tm_gmtoff') belongs to the time that is already within 'tp' (this
1197 is exactly what we want!). So for example when daylight saving is
1198 activated at run-time, but at the time inside 'tp', there was no
1199 daylight saving, the value of 'tp.tm_gmtoff' will be different from
1200 the 'timezone' global variable. */
1201 t=mktime(&tp);
1202
1203 /* Calculate the seconds and return it. */
1204 seconds = (t == (time_t)(-1)) ? GAL_BLANK_SIZE_T : (t+tp.tm_gmtoff);
1205 return seconds;
1206 }
1207
1208
1209
1210
1211
1212 /* Read the keyword values from a FITS pointer. The input should be a
1213 linked list of 'gal_data_t'. Before calling this function, you just have
1214 to set the 'name' and desired 'type' values of each element in the list
1215 to the keyword you want it to keep the value of. The given 'name' value
1216 will be directly passed to CFITSIO to read the desired keyword. This
1217 function will allocate space to keep the value. Here is one example of
1218 using this function:
1219
1220 gal_data_t *keysll=gal_data_array_calloc(N);
1221
1222 for(i=0;i<N-2;++i) keysll[i]->next=keysll[i+1];
1223
1224 \\ Put a name and type for each element.
1225
1226 gal_fits_key_read_from_ptr(fptr, keysll, 0, 0);
1227
1228 \\ use the values as you like.
1229
1230 gal_data_array_free(keysll, N, 1);
1231
1232 If the 'array' pointer of each keyword's dataset is not NULL, then it is
1233 assumed that the space has already been allocated. If it is NULL, then
1234 space will be allocated internally here.
1235
1236 Strings need special consideration: the reason is that generally,
1237 'gal_data_t' needs to also allow for array of strings (as it supports
1238 arrays of integers for example). Hence two allocations will be done here
1239 (one if 'array!=NULL') and 'keysll[i].array' must be interpretted as
1240 'char **': one allocation for the pointer, one for the actual
1241 characters. You don't have to worry about the freeing,
1242 'gal_data_array_free' will free both allocations. So to read a string,
1243 one easy way would be the following:
1244
1245 char *str, **strarray;
1246 strarr = keysll[i].array;
1247 str = strarray[0];
1248
1249 If CFITSIO is unable to read a keyword for any reason the 'status'
1250 element of the respective 'gal_data_t' will be non-zero. You can check
1251 the successful reading of the keyword from the 'status' value in each
1252 keyword's 'gal_data_t'. If it is zero, then the keyword was found and
1253 succesfully read. Otherwise, it a CFITSIO status value. You can use
1254 CFITSIO's error reporting tools or 'gal_fits_io_error' for reporting the
1255 reason. A tip: when the keyword doesn't exist, then CFITSIO's status
1256 value will be 'KEY_NO_EXIST'.
1257
1258 CFITSIO will start searching for the keywords from the last place in the
1259 header that it searched for a keyword. So it is much more efficient if
1260 the order that you ask for keywords is based on the order they are
1261 stored in the header.
1262 */
1263 void
gal_fits_key_read_from_ptr(fitsfile * fptr,gal_data_t * keysll,int readcomment,int readunit)1264 gal_fits_key_read_from_ptr(fitsfile *fptr, gal_data_t *keysll,
1265 int readcomment, int readunit)
1266 {
1267 uint8_t numtype;
1268 gal_data_t *tmp;
1269 char **strarray;
1270 int typewasinvalid;
1271 void *numptr, *valueptr;
1272
1273 /* Get the desired keywords. */
1274 for(tmp=keysll;tmp!=NULL;tmp=tmp->next)
1275 if(tmp->name)
1276 {
1277 /* Initialize the status: */
1278 tmp->status=0;
1279
1280 /* For each keyword, this function stores one value currently. So
1281 set the size and ndim to 1. But first allocate dsize if it
1282 wasn't already allocated. */
1283 if(tmp->dsize==NULL)
1284 tmp->dsize=gal_pointer_allocate(GAL_TYPE_SIZE_T, 1, 0, __func__,
1285 "tmp->dsize");
1286 tmp->ndim=tmp->size=tmp->dsize[0]=1;
1287
1288 /* If no type has been given, temporarily set it to a string, we
1289 will then deduce the type afterwards. */
1290 typewasinvalid=0;
1291 if(tmp->type==GAL_TYPE_INVALID)
1292 {
1293 typewasinvalid=1;
1294 tmp->type=GAL_TYPE_STRING;
1295 }
1296
1297 /* When the type is a string, 'tmp->array' is an array of pointers
1298 to a separately allocated piece of memory. So we have to
1299 allocate that space here. If its not a string, then the
1300 allocated space above is enough to keep the value.*/
1301 switch(tmp->type)
1302 {
1303 case GAL_TYPE_STRING:
1304 tmp->array=strarray=( tmp->array
1305 ? tmp->array
1306 : gal_pointer_allocate(tmp->type, 1, 0,
1307 __func__,
1308 "tmp->array") );
1309 errno=0;
1310 valueptr=strarray[0]=malloc(FLEN_VALUE * sizeof *strarray[0]);
1311 if(strarray[0]==NULL)
1312 error(EXIT_FAILURE, errno, "%s: %zu bytes for strarray[0]",
1313 __func__, FLEN_VALUE * sizeof *strarray[0]);
1314 break;
1315
1316 default:
1317 tmp->array=valueptr=( tmp->array
1318 ? tmp->array
1319 : gal_pointer_allocate(tmp->type, 1, 0,
1320 __func__,
1321 "tmp->array") );
1322 }
1323
1324 /* Allocate space for the keyword comment if necessary.*/
1325 if(readcomment)
1326 {
1327 errno=0;
1328 tmp->comment=calloc(FLEN_COMMENT, sizeof *tmp->comment);
1329 if(tmp->comment==NULL)
1330 error(EXIT_FAILURE, errno, "%s: %zu bytes for tmp->comment",
1331 __func__, FLEN_COMMENT * sizeof *tmp->comment);
1332 }
1333 else
1334 tmp->comment=NULL;
1335
1336 /* Allocate space for the keyword unit if necessary. Note that
1337 since there is no precise CFITSIO length for units, we will use
1338 the 'FLEN_COMMENT' length for units too (theoretically, the unit
1339 might take the full remaining area in the keyword). Also note
1340 that the unit is only optional, so it needs a separate CFITSIO
1341 function call which is done here.*/
1342 if(readunit)
1343 {
1344 /* Allocate space for the unit and read it in. */
1345 errno=0;
1346 tmp->unit=calloc(FLEN_COMMENT, sizeof *tmp->unit);
1347 if(tmp->unit==NULL)
1348 error(EXIT_FAILURE, errno, "%s: %zu bytes for tmp->unit",
1349 __func__, FLEN_COMMENT * sizeof *tmp->unit);
1350 fits_read_key_unit(fptr, tmp->name, tmp->unit, &tmp->status);
1351
1352 /* If the string is empty, free the space and set it to NULL. */
1353 if(tmp->unit[0]=='\0') {free(tmp->unit); tmp->unit=NULL;}
1354 }
1355 else
1356 tmp->unit=NULL;
1357
1358 /* Read the keyword and place its value in the pointer. */
1359 fits_read_key(fptr, gal_fits_type_to_datatype(tmp->type),
1360 tmp->name, valueptr, tmp->comment, &tmp->status);
1361
1362 /* Correct the type if no type was requested and the key has been
1363 successfully read. */
1364 if(tmp->status==0 && typewasinvalid)
1365 {
1366 /* If the string can be parsed as a number, then number will be
1367 allocated and placed in 'numptr', otherwise, 'numptr' will
1368 be NULL. */
1369 numptr=gal_type_string_to_number(valueptr, &numtype);
1370 if(numptr)
1371 {
1372 free(valueptr);
1373 free(tmp->array);
1374 tmp->array=numptr;
1375 tmp->type=numtype;
1376 }
1377 }
1378
1379 /* If the comment was empty, free the space and set it to NULL. */
1380 if(tmp->comment && tmp->comment[0]=='\0')
1381 {free(tmp->comment); tmp->comment=NULL;}
1382 }
1383 }
1384
1385
1386
1387
1388
1389 /* Same as 'gal_fits_read_keywords_fptr', but accepts the filename and HDU
1390 as input instead of an already opened CFITSIO 'fitsfile' pointer. */
1391 void
gal_fits_key_read(char * filename,char * hdu,gal_data_t * keysll,int readcomment,int readunit)1392 gal_fits_key_read(char *filename, char *hdu, gal_data_t *keysll,
1393 int readcomment, int readunit)
1394 {
1395 size_t len;
1396 int status=0;
1397 char *ffname;
1398 fitsfile *fptr;
1399
1400 /* Add hdu to filename: */
1401 errno=0;
1402 len=strlen(filename)+strlen(hdu)+4;
1403 ffname=malloc(len*sizeof *ffname);
1404 if(ffname==NULL)
1405 error(EXIT_FAILURE, errno, "%s: %zu characters", __func__, len);
1406 sprintf(ffname, "%s[%s#]", filename, hdu);
1407
1408 /* Open the FITS file: */
1409 if( fits_open_file(&fptr, ffname, READONLY, &status) )
1410 gal_fits_io_error(status, "reading this FITS file");
1411
1412 /* Read the keywords. */
1413 gal_fits_key_read_from_ptr(fptr, keysll, readcomment, readunit);
1414
1415 /* Close the FITS file. */
1416 fits_close_file(fptr, &status);
1417 gal_fits_io_error(status, NULL);
1418
1419 /* Clean up. */
1420 free(ffname);
1421 }
1422
1423
1424
1425
1426
1427 /* Add on keyword to the list of header keywords that need to be added
1428 to a FITS file. In the end, the keywords will have to be freed, so
1429 it is important to know before hand if they were allocated or
1430 not. If not, they don't need to be freed.
1431
1432 NOTE FOR STRINGS: the value should be the pointer to the string its-self
1433 (char *), not a pointer to a pointer (char **). */
1434 void
gal_fits_key_list_add(gal_fits_list_key_t ** list,uint8_t type,char * keyname,int kfree,void * value,int vfree,char * comment,int cfree,char * unit,int ufree)1435 gal_fits_key_list_add(gal_fits_list_key_t **list, uint8_t type,
1436 char *keyname, int kfree, void *value, int vfree,
1437 char *comment, int cfree, char *unit, int ufree)
1438 {
1439 gal_fits_list_key_t *newnode;
1440
1441 /* Allocate space for the new node and fill it in. */
1442 errno=0;
1443 newnode=malloc(sizeof *newnode);
1444 if(newnode==NULL)
1445 error(EXIT_FAILURE, errno, "%s: allocating new node", __func__);
1446
1447 /* Write the given values into the key structure. */
1448 newnode->title=NULL;
1449 newnode->fullcomment=NULL;
1450 newnode->type=type;
1451 newnode->keyname=keyname;
1452 newnode->value=value;
1453 newnode->comment=comment;
1454 newnode->unit=unit;
1455 newnode->kfree=kfree; /* Free pointers after using them. */
1456 newnode->vfree=vfree;
1457 newnode->cfree=cfree;
1458 newnode->ufree=ufree;
1459
1460 /* Set the next pointer. */
1461 newnode->next=*list;
1462 *list=newnode;
1463 }
1464
1465
1466
1467
1468
1469 void
gal_fits_key_list_add_end(gal_fits_list_key_t ** list,uint8_t type,char * keyname,int kfree,void * value,int vfree,char * comment,int cfree,char * unit,int ufree)1470 gal_fits_key_list_add_end(gal_fits_list_key_t **list, uint8_t type,
1471 char *keyname, int kfree, void *value, int vfree,
1472 char *comment, int cfree, char *unit, int ufree)
1473 {
1474 gal_fits_list_key_t *tmp, *newnode;
1475
1476 /* Allocate space for the new node and fill it in. */
1477 errno=0;
1478 newnode=malloc(sizeof *newnode);
1479 if(newnode==NULL)
1480 error(EXIT_FAILURE, errno, "%s: allocation of new node", __func__);
1481
1482 /* Write the given values into the key structure. */
1483 newnode->title=NULL;
1484 newnode->fullcomment=NULL;
1485 newnode->type=type;
1486 newnode->keyname=keyname;
1487 newnode->value=value;
1488 newnode->comment=comment;
1489 newnode->unit=unit;
1490 newnode->kfree=kfree; /* Free pointers after using them. */
1491 newnode->vfree=vfree;
1492 newnode->cfree=cfree;
1493 newnode->ufree=ufree;
1494
1495 /* Set the next pointer. */
1496 if(*list) /* List is already full, add this node to the end */
1497 {
1498 /* After this line, tmp points to the last node. */
1499 tmp=*list; while(tmp->next!=NULL) tmp=tmp->next;
1500 tmp->next=newnode;
1501 newnode->next=NULL;
1502 }
1503 else /* List is empty */
1504 {
1505 newnode->next=NULL;
1506 *list=newnode;
1507 }
1508 }
1509
1510
1511
1512
1513 /* Only set this key to be a title. */
1514 void
gal_fits_key_list_title_add(gal_fits_list_key_t ** list,char * title,int tfree)1515 gal_fits_key_list_title_add(gal_fits_list_key_t **list, char *title, int tfree)
1516 {
1517 gal_fits_list_key_t *newnode;
1518
1519 /* Allocate space (and initialize to 0/NULL) for the new node and fill it
1520 in. */
1521 errno=0;
1522 newnode=calloc(1, sizeof *newnode);
1523 if(newnode==NULL)
1524 error(EXIT_FAILURE, errno, "%s: allocating new node", __func__);
1525
1526 /* Set the arguments. */
1527 newnode->title=title;
1528 newnode->tfree=tfree;
1529
1530 /* Set the next pointer. */
1531 newnode->next=*list;
1532 *list=newnode;
1533 }
1534
1535
1536
1537
1538
1539 /* Put the title keyword in the end. */
1540 void
gal_fits_key_list_title_add_end(gal_fits_list_key_t ** list,char * title,int tfree)1541 gal_fits_key_list_title_add_end(gal_fits_list_key_t **list, char *title,
1542 int tfree)
1543 {
1544 gal_fits_list_key_t *tmp, *newnode;
1545
1546 /* Allocate space (and initialize to 0/NULL) for the new node and fill it
1547 in. */
1548 errno=0;
1549 newnode=calloc(1, sizeof *newnode);
1550 if(newnode==NULL)
1551 error(EXIT_FAILURE, errno, "%s: allocating new node", __func__);
1552
1553 /* Set the arguments. */
1554 newnode->title=title;
1555 newnode->tfree=tfree;
1556
1557 /* Set the next pointer. */
1558 if(*list) /* List is already full, add this node to the end */
1559 {
1560 /* After this line, tmp points to the last node. */
1561 tmp=*list; while(tmp->next!=NULL) tmp=tmp->next;
1562 tmp->next=newnode;
1563 newnode->next=NULL;
1564 }
1565 else /* List is empty */
1566 {
1567 newnode->next=*list;
1568 *list=newnode;
1569 }
1570 }
1571
1572
1573
1574
1575
1576 /* Only set this key to be a full comment */
1577 void
gal_fits_key_list_fullcomment_add(gal_fits_list_key_t ** list,char * fullcomment,int fcfree)1578 gal_fits_key_list_fullcomment_add(gal_fits_list_key_t **list,
1579 char *fullcomment, int fcfree)
1580 {
1581 gal_fits_list_key_t *newnode;
1582
1583 /* Allocate space (and initialize to 0/NULL) for the new node and fill it
1584 in. */
1585 errno=0;
1586 newnode=calloc(1, sizeof *newnode);
1587 if(newnode==NULL)
1588 error(EXIT_FAILURE, errno, "%s: allocating new node", __func__);
1589
1590 /* Set the arguments. */
1591 newnode->fullcomment=fullcomment;
1592 newnode->fcfree=fcfree;
1593
1594 /* Set the next pointer. */
1595 newnode->next=*list;
1596 *list=newnode;
1597 }
1598
1599
1600
1601
1602
1603 /* Put the title keyword in the end. */
1604 void
gal_fits_key_list_fullcomment_add_end(gal_fits_list_key_t ** list,char * fullcomment,int fcfree)1605 gal_fits_key_list_fullcomment_add_end(gal_fits_list_key_t **list,
1606 char *fullcomment, int fcfree)
1607 {
1608 gal_fits_list_key_t *tmp, *newnode;
1609
1610 /* Allocate space (and initialize to 0/NULL) for the new node and fill it
1611 in. */
1612 errno=0;
1613 newnode=calloc(1, sizeof *newnode);
1614 if(newnode==NULL)
1615 error(EXIT_FAILURE, errno, "%s: allocating new node", __func__);
1616
1617 /* Set the arguments. */
1618 newnode->fullcomment=fullcomment;
1619 newnode->fcfree=fcfree;
1620
1621 /* Set the next pointer. */
1622 if(*list) /* List is already full, add this node to the end */
1623 {
1624 /* After this line, tmp points to the last node. */
1625 tmp=*list; while(tmp->next!=NULL) tmp=tmp->next;
1626 tmp->next=newnode;
1627 newnode->next=NULL;
1628 }
1629 else /* List is empty */
1630 {
1631 newnode->next=*list;
1632 *list=newnode;
1633 }
1634 }
1635
1636
1637
1638
1639
1640 void
gal_fits_key_list_reverse(gal_fits_list_key_t ** list)1641 gal_fits_key_list_reverse(gal_fits_list_key_t **list)
1642 {
1643 gal_fits_list_key_t *in=*list, *tmp=in, *reversed=NULL;
1644
1645 /* Only do the reversal if there is more than one element. */
1646 if(in && in->next)
1647 {
1648 /* Fill the 'reversed' list. */
1649 while(in!=NULL)
1650 {
1651 tmp=in->next;
1652 in->next=reversed;
1653 reversed=in;
1654 in=tmp;
1655 }
1656
1657 /* Write the reversed list into the input pointer. */
1658 *list=reversed;
1659 }
1660 }
1661
1662
1663
1664
1665
1666 /* Write a blank keyword field and a title under it in the specified FITS
1667 file pointer. */
1668 void
gal_fits_key_write_title_in_ptr(char * title,fitsfile * fptr)1669 gal_fits_key_write_title_in_ptr(char *title, fitsfile *fptr)
1670 {
1671 size_t i;
1672 int status=0;
1673 char *cp, *cpf, blankrec[80], titlerec[80];
1674
1675 /* Just in case title is 'NULL'. */
1676 if(title)
1677 {
1678 /* A small sanity check. */
1679 if( strlen(title) + strlen(GAL_FITS_KEY_TITLE_START) > 78 )
1680 fprintf(stderr, "%s: FITS keyword title '%s' is too long to be fully "
1681 "included in the keyword record (80 characters, where the "
1682 "title is prefixed with %zu space characters)",
1683 __func__, title, strlen(GAL_FITS_KEY_TITLE_START));
1684
1685 /* Set the last element of the blank array. */
1686 cpf=blankrec+79;
1687 *cpf='\0';
1688 titlerec[79]='\0';
1689 cp=blankrec; do *cp=' '; while(++cp<cpf);
1690
1691 /* Print the blank lines before the title */
1692 if(fits_write_record(fptr, blankrec, &status))
1693 gal_fits_io_error(status, NULL);
1694
1695 /* Print the title */
1696 sprintf(titlerec, "%s%s", GAL_FITS_KEY_TITLE_START, title);
1697 for(i=strlen(titlerec);i<79;++i)
1698 titlerec[i]=' ';
1699 if(fits_write_record(fptr, titlerec, &status))
1700 gal_fits_io_error(status, NULL);
1701 }
1702 }
1703
1704
1705
1706
1707
1708 /* Write a filename into keyword values. */
1709 void
gal_fits_key_write_filename(char * keynamebase,char * filename,gal_fits_list_key_t ** list,int top1end0,int quiet)1710 gal_fits_key_write_filename(char *keynamebase, char *filename,
1711 gal_fits_list_key_t **list, int top1end0,
1712 int quiet)
1713 {
1714 char *keyname, *value;
1715 size_t numkey=1, maxlength;
1716 size_t i, j, len=strlen(filename), thislen;
1717
1718 /* When you give string arguments, CFITSIO puts them within two ''s,
1719 so the actual length available is two less. It seems this length
1720 also didn't include the null character, so, ultimately you have
1721 to take three from it.*/
1722 maxlength=FLEN_VALUE-3;
1723
1724 /* Parse the filename and see when it is necessary to break it. */
1725 i=0;
1726 while(i<len)
1727 {
1728 /* Set the keyname: */
1729 errno=0;
1730 keyname=malloc(FLEN_KEYWORD);
1731 if(keyname==NULL)
1732 error(EXIT_FAILURE, errno, "%s: %d bytes for 'keyname'", __func__,
1733 FLEN_KEYWORD);
1734 if(len<maxlength)
1735 strcpy(keyname, keynamebase);
1736 else
1737 sprintf(keyname, "%s_%zu", keynamebase, numkey++);
1738
1739 /* Set the keyword value: */
1740 errno=0;
1741 thislen=strlen(&filename[i]);
1742 value=malloc(maxlength+1);
1743 if(value==NULL)
1744 error(EXIT_FAILURE, errno, "%s: allocating %zu bytes", __func__,
1745 thislen);
1746 strncpy(value, &filename[i], maxlength);
1747
1748 /* If the FROM string (=&filename[i]) in strncpy is shorter than
1749 SIZE (=maxlength), then the rest of the space will be filled
1750 with null characters. So we can use this to check if the full
1751 length was copied. */
1752 if(value[maxlength-1]=='\0')
1753 {
1754 if(top1end0)
1755 gal_fits_key_list_add(list, GAL_TYPE_STRING, keyname, 1,
1756 value, 1, NULL, 0, NULL, 0);
1757 else
1758 gal_fits_key_list_add_end(list, GAL_TYPE_STRING, keyname, 1,
1759 value, 1, NULL, 0, NULL, 0);
1760 break;
1761 }
1762 else
1763 {
1764 /* Find the last place in the copied array that contains a
1765 '/' and put j on the next character (so it can be turned
1766 into a null character.*/
1767 for(j=maxlength-2;j>0;--j)
1768 if(value[j]=='/')
1769 {
1770 value[j+1]='\0';
1771 break;
1772 }
1773 if(j==0)
1774 {
1775 /* So the loop doesn't continue after this. */
1776 i=len+1;
1777
1778 /* There will only be one keyword, so we'll just use the
1779 basename. */
1780 strcpy(keyname, keynamebase);
1781
1782 /* Let the user know that the name will be truncated. */
1783 if(quiet==0)
1784 error(0,0, "%s: WARNING: the filename '%s' (not "
1785 "including directories) is too long to fit into "
1786 "a FITS keyword value (max of %zu characters). "
1787 "It will therefore be truncated. If you are "
1788 "using Gnuastro's programs, this message is "
1789 "only about the metadata (keyword that keeps "
1790 "name of input), so it won't affect the output "
1791 "analysis and data. In this case, you can suppress "
1792 "this warning message with a '--quiet' option",
1793 __func__, filename, maxlength);
1794 }
1795
1796 /* Convert the last useful character and save the file name.*/
1797 if(top1end0)
1798 gal_fits_key_list_add(list, GAL_TYPE_STRING, keyname, 1,
1799 value, 1, NULL, 0, NULL, 0);
1800 else
1801 gal_fits_key_list_add_end(list, GAL_TYPE_STRING, keyname, 1,
1802 value, 1, NULL, 0, NULL, 0);
1803 i+=j+1;
1804 }
1805 }
1806 }
1807
1808
1809
1810
1811
1812 /* A bug was found in WCSLIB that has existed since WCSLIB 5.9 (released on
1813 2015/07/21) and will be fixed in the version after WCSLIB 7.3.1
1814 (released in 2020). However, it will take time for many user package
1815 managers to update their WCSLIB version. So we need to check if that bug
1816 has occurred here and fix it manually.
1817
1818 In short the bug was originally seen on a dataset with this input CDELT
1819 values:
1820
1821 CDELT1 = 0.0007778 / [deg]
1822 CDELT2 = 0.0007778 / [deg]
1823 CDELT3 = 30000.0000000 / [Hz]
1824
1825 The values are read into the 'wcsprm' structure properly, but upon
1826 writing into the keyword string structure, the 'CDELT1' and 'CDELT2'
1827 values are printed as 0. Mark Calabretta (creator of WCSLIB) described
1828 the issue as follows:
1829
1830 """wcshdo() tries to find a single sprintf() floating point format
1831 to use for groups of keywords, such as CDELTj as a group, or
1832 CRPIXi, PCi_j, and CRVALj, each as separate groups. It aims to
1833 present the keyvalues in human-readable form, i.e. with decimal
1834 points lined up, no unnecessary trailing zeroes, and avoiding
1835 exponential ('E') format where its use is not warranted.
1836
1837 The problem here arose because of the large range of CDELT values
1838 formatted using 'f' format, but not being so large that it would
1839 force wcshdo() to revert to 'E' format. There is also the
1840 troubling possibility that in less extreme cases, precision of the
1841 CDELT (or other) values could be lost without being noticed."""
1842
1843 To implement the check we will follow this logic: large dimensional
1844 differences will not commonly happen in 2D datasets, so we will only
1845 attempt the check in 3D datasets. We'll read each written CDELT value
1846 with CFITSIO and if its zero, we'll correct it. */
1847 static void
fits_bug_wrapper_cdelt_zero(fitsfile * fptr,struct wcsprm * wcs,char * keystr)1848 fits_bug_wrapper_cdelt_zero(fitsfile *fptr, struct wcsprm *wcs, char *keystr)
1849 {
1850 char *keyname;
1851 double keyvalue;
1852 size_t dim=GAL_BLANK_SIZE_T;
1853 int status=0, datatype=TDOUBLE;
1854
1855 /* Only do this check when we have more than two dimensions. */
1856 if(wcs->naxis>2 && !strncmp(keystr, "CDELT", 5))
1857 {
1858 /* Find the dimension number and keyword string. This can later be
1859 improved/generalized by actually parsing the keyword name to
1860 extract the dimension, but I didn't have time to implement it in
1861 the first implementation. It will rarely be necessary to go beyond
1862 the third dimension, so this has almost no extra burden on the
1863 computer's processing. */
1864 if( !strncmp(keystr, "CDELT1", 6) ) { keyname="CDELT1"; dim=0; }
1865 else if( !strncmp(keystr, "CDELT2", 6) ) { keyname="CDELT2"; dim=1; }
1866 else if( !strncmp(keystr, "CDELT3", 6) ) { keyname="CDELT3"; dim=2; }
1867 else if( !strncmp(keystr, "CDELT4", 6) ) { keyname="CDELT4"; dim=3; }
1868 else if( !strncmp(keystr, "CDELT5", 6) ) { keyname="CDELT5"; dim=4; }
1869 else if( !strncmp(keystr, "CDELT6", 6) ) { keyname="CDELT6"; dim=5; }
1870 else if( !strncmp(keystr, "CDELT7", 6) ) { keyname="CDELT7"; dim=6; }
1871 else if( !strncmp(keystr, "CDELT8", 6) ) { keyname="CDELT8"; dim=7; }
1872 else if( !strncmp(keystr, "CDELT9", 6) ) { keyname="CDELT9"; dim=8; }
1873 else
1874 error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to fix "
1875 "the problem. There appears to be more than 9 dimensions in "
1876 "the input dataset", __func__, PACKAGE_BUGREPORT);
1877
1878 /* Read the keyword value. */
1879 fits_read_key(fptr, datatype, keyname, &keyvalue, NULL, &status);
1880 gal_fits_io_error(status, NULL);
1881
1882 /* If the written value is not the same by more than 10 decimals,
1883 re-write the value. */
1884 if( fabs( wcs->cdelt[dim] - keyvalue ) > 1e-10 )
1885 {
1886 fits_update_key(fptr, datatype, keyname, &wcs->cdelt[dim],
1887 NULL, &status);
1888 gal_fits_io_error(status, NULL);
1889 }
1890 }
1891 }
1892
1893
1894
1895
1896
1897 /* Write the WCS header string into a FITS files*/
1898 void
gal_fits_key_write_wcsstr(fitsfile * fptr,struct wcsprm * wcs,char * wcsstr,int nkeyrec)1899 gal_fits_key_write_wcsstr(fitsfile *fptr, struct wcsprm *wcs,
1900 char *wcsstr, int nkeyrec)
1901 {
1902 size_t i;
1903 int status=0;
1904 char *keystart;
1905
1906 /* If the 'wcsstr' string is empty, don't do anything, simply return. */
1907 if(wcsstr==NULL) return;
1908
1909 /* Write the title. */
1910 gal_fits_key_write_title_in_ptr("World Coordinate System (WCS)", fptr);
1911
1912 /* Write the keywords one by one: */
1913 for(i=0;i<nkeyrec;++i)
1914 {
1915 /* Set the start of this header. */
1916 keystart=&wcsstr[i*80];
1917
1918 /* Write it if it isn't blank (first character is a space), or not a
1919 comment (first 7 characters equal to 'COMMENT'). The reason is
1920 that WCSLIB adds a blank line and a 'COMMENT' keyword saying its
1921 own version. But Gnuastro writes the version of WCSLIB as a
1922 separate keyword along with all other important software, so it is
1923 redundant and just makes the keywrods hard to read by eye.*/
1924 if( keystart[0]!=' ' && strncmp(keystart, "COMMENT", 7) )
1925 {
1926 fits_write_record(fptr, keystart, &status);
1927 if(wcs) fits_bug_wrapper_cdelt_zero(fptr, wcs, keystart);
1928 }
1929 }
1930 gal_fits_io_error(status, NULL);
1931
1932 /* WCSLIB is going to write PC+CDELT keywords in any case. But when we
1933 have a TPV, TNX or ZPX distortion, it is cleaner to use a CD matrix
1934 (WCSLIB can't convert coordinates properly if the PC matrix is used
1935 with the TPV distortion). So to help users avoid the potential
1936 problems with WCSLIB, upon reading the WCS structure, in such cases,
1937 we set CDELTi=1.0 and 'altlin=2' (signifying that the CD matrix
1938 should be used). Therefore, effectively the CD matrix and PC matrix
1939 are equivalent, we just need to convert the keyword names and delete
1940 the CDELT keywords. Note that zero-valued PC/CD elements may not be
1941 present, so we'll manually set 'status' to zero to avoid CFITSIO from
1942 crashing. */
1943 if(wcs && wcs->altlin==2)
1944 {
1945 status=0; fits_delete_str(fptr, "CDELT1", &status);
1946 status=0; fits_delete_str(fptr, "CDELT2", &status);
1947 status=0; fits_modify_name(fptr, "PC1_1", "CD1_1", &status);
1948 status=0; fits_modify_name(fptr, "PC1_2", "CD1_2", &status);
1949 status=0; fits_modify_name(fptr, "PC2_1", "CD2_1", &status);
1950 status=0; fits_modify_name(fptr, "PC2_2", "CD2_2", &status);
1951 if(wcs->naxis==3)
1952 {
1953 status=0; fits_delete_str(fptr, "CDELT3", &status);
1954 status=0; fits_modify_name(fptr, "PC1_3", "CD1_3", &status);
1955 status=0; fits_modify_name(fptr, "PC2_3", "CD2_3", &status);
1956 status=0; fits_modify_name(fptr, "PC3_1", "CD3_1", &status);
1957 status=0; fits_modify_name(fptr, "PC3_2", "CD3_2", &status);
1958 status=0; fits_modify_name(fptr, "PC3_3", "CD3_3", &status);
1959 }
1960 status=0;
1961 }
1962 }
1963
1964
1965
1966
1967
1968 /* Write the given list of header keywords into the specified HDU of the
1969 specified FITS file. */
1970 void
gal_fits_key_write(gal_fits_list_key_t ** keylist,char * title,char * filename,char * hdu)1971 gal_fits_key_write(gal_fits_list_key_t **keylist, char *title,
1972 char *filename, char *hdu)
1973 {
1974 int status=0;
1975 fitsfile *fptr=gal_fits_hdu_open(filename, hdu, READWRITE);
1976
1977 /* Write the title */
1978 gal_fits_key_write_title_in_ptr(title, fptr);
1979
1980 /* Write the keywords into the */
1981 gal_fits_key_write_in_ptr(keylist, fptr);
1982
1983 /* Close the input FITS file. */
1984 fits_close_file(fptr, &status);
1985 gal_fits_io_error(status, NULL);
1986 }
1987
1988
1989
1990
1991
1992 /* Fits doesn't allow NaN values, so if the type of float or double, we'll
1993 just check to see if its NaN or not and let the user know the keyword
1994 name (to help them fix it). */
1995 static void
gal_fits_key_write_in_ptr_nan_check(gal_fits_list_key_t * tmp)1996 gal_fits_key_write_in_ptr_nan_check(gal_fits_list_key_t *tmp)
1997 {
1998 int nanwarning=0;
1999
2000 /* Check the value. */
2001 switch(tmp->type)
2002 {
2003 case GAL_TYPE_FLOAT32:
2004 if( isnan( ((float *)(tmp->value))[0] ) ) nanwarning=1;
2005 break;
2006 case GAL_TYPE_FLOAT64:
2007 if( isnan( ((double *)(tmp->value))[0] ) ) nanwarning=1;
2008 break;
2009 }
2010
2011 /* Print the warning. */
2012 if(nanwarning)
2013 error(EXIT_SUCCESS, 0, "%s: (WARNING) value of '%s' is NaN "
2014 "and FITS doesn't recognize a NaN key value", __func__,
2015 tmp->keyname);
2016 }
2017
2018
2019
2020
2021
2022 /* Write the keywords in the gal_fits_list_key_t linked list to the FITS
2023 file. Every keyword that is written is freed, that is why we need the
2024 pointer to the linked list (to correct it after we finish). */
2025 void
gal_fits_key_write_in_ptr(gal_fits_list_key_t ** keylist,fitsfile * fptr)2026 gal_fits_key_write_in_ptr(gal_fits_list_key_t **keylist, fitsfile *fptr)
2027 {
2028 int status=0;
2029 gal_fits_list_key_t *tmp, *ttmp;
2030
2031 tmp=*keylist;
2032 while(tmp!=NULL)
2033 {
2034 /* If a title is requested, only put a title. */
2035 if(tmp->title)
2036 {
2037 gal_fits_key_write_title_in_ptr(tmp->title, fptr);
2038 if(tmp->tfree) free(tmp->title);
2039 }
2040 else if (tmp->fullcomment)
2041 {
2042 if( fits_write_comment(fptr, tmp->fullcomment, &status) )
2043 gal_fits_io_error(status, NULL);
2044 if(tmp->fcfree) free(tmp->fullcomment);
2045 }
2046 else
2047 {
2048 /* Write the basic key value and comments. */
2049 if(tmp->value)
2050 {
2051 /* Print a warning if the value is NaN. */
2052 gal_fits_key_write_in_ptr_nan_check(tmp);
2053
2054 /* Write/Update the keyword value. */
2055 if( fits_update_key(fptr, gal_fits_type_to_datatype(tmp->type),
2056 tmp->keyname, tmp->value, tmp->comment,
2057 &status) )
2058 gal_fits_io_error(status, NULL);
2059 }
2060 else
2061 {
2062 if(fits_update_key_null(fptr, tmp->keyname, tmp->comment,
2063 &status))
2064 gal_fits_io_error(status, NULL);
2065 }
2066
2067 /* Write the units if it was given. */
2068 if( tmp->unit
2069 && fits_write_key_unit(fptr, tmp->keyname, tmp->unit, &status) )
2070 gal_fits_io_error(status, NULL);
2071
2072 /* Free the value pointer if desired: */
2073 if(tmp->kfree) free(tmp->keyname);
2074 if(tmp->vfree) free(tmp->value);
2075 if(tmp->cfree) free(tmp->comment);
2076 if(tmp->ufree) free(tmp->unit);
2077 }
2078
2079 /* Keep the pointer to the next keyword and free the allocated
2080 space for this keyword.*/
2081 ttmp=tmp->next;
2082 free(tmp);
2083 tmp=ttmp;
2084 }
2085
2086 /* Set it to NULL so it isn't mistakenly used later. */
2087 *keylist=NULL;
2088 }
2089
2090
2091
2092
2093
2094 /* Write the given list of header keywords and version info into the give
2095 file. */
2096 void
gal_fits_key_write_version(gal_fits_list_key_t ** keylist,char * title,char * filename,char * hdu)2097 gal_fits_key_write_version(gal_fits_list_key_t **keylist, char *title,
2098 char *filename, char *hdu)
2099 {
2100 int status=0;
2101 fitsfile *fptr=gal_fits_hdu_open(filename, hdu, READWRITE);
2102
2103 /* Write the given keys followed by the versions. */
2104 gal_fits_key_write_version_in_ptr(keylist, title, fptr);
2105
2106 /* Close the input FITS file. */
2107 fits_close_file(fptr, &status);
2108 gal_fits_io_error(status, NULL);
2109 }
2110
2111
2112
2113
2114
2115 void
gal_fits_key_write_version_in_ptr(gal_fits_list_key_t ** keylist,char * title,fitsfile * fptr)2116 gal_fits_key_write_version_in_ptr(gal_fits_list_key_t **keylist, char *title,
2117 fitsfile *fptr)
2118 {
2119 int status=0;
2120 char *gitdescribe;
2121 char cfitsioversion[20];
2122
2123 /* Before WCSLIB 5.0, the wcslib_version function was not
2124 defined. Sometime in the future were everyone has moved to more
2125 recent versions of WCSLIB, we can remove this macro and its check
2126 in configure.ac.*/
2127 #if GAL_CONFIG_HAVE_WCSLIB_VERSION == 1
2128 int wcslibvers[3];
2129 char wcslibversion[20];
2130 const char *wcslibversion_const;
2131 #endif
2132
2133 /* Small sanity check. */
2134 if(fptr==NULL)
2135 error(EXIT_FAILURE, 0, "%s: input FITS pointer is NULL", __func__);
2136
2137 /* If any header keywords are specified, add them: */
2138 if(keylist && *keylist)
2139 {
2140 gal_fits_key_write_title_in_ptr(title?title:"Specific keys", fptr);
2141 gal_fits_key_write_in_ptr(keylist, fptr);
2142 }
2143
2144 /* Print 'Versions and date' title. */
2145 gal_fits_key_write_title_in_ptr("Versions and date", fptr);
2146
2147 /* Set the version of CFITSIO as a string: before version 4.0.0 of
2148 CFITSIO, there were only two numbers in the version (for example
2149 '3.49' and '3.48'), but from the 4th major release, there are three
2150 numbers in the version string. The third number corresponds to a new
2151 'CFITSIO_MICRO' macro. So if it doesn't exist, we'll just print two
2152 numbers, otherwise, we'll print the three. */
2153 #ifdef CFITSIO_MICRO
2154 sprintf(cfitsioversion, "%d.%d.%d", CFITSIO_MAJOR,
2155 CFITSIO_MINOR, CFITSIO_MICRO);
2156 #else
2157 sprintf(cfitsioversion, "%d.%d", CFITSIO_MAJOR,
2158 CFITSIO_MINOR);
2159 #endif
2160
2161 /* Write all the information: */
2162 fits_write_date(fptr, &status);
2163
2164 /* Write the version of CFITSIO */
2165 fits_update_key(fptr, TSTRING, "CFITSIO", cfitsioversion,
2166 "CFITSIO version.", &status);
2167
2168 /* Write the WCSLIB version. */
2169 #if GAL_CONFIG_HAVE_WCSLIB_VERSION == 1
2170 wcslibversion_const=wcslib_version(wcslibvers);
2171 strcpy(wcslibversion, wcslibversion_const);
2172 fits_update_key(fptr, TSTRING, "WCSLIB", wcslibversion,
2173 "WCSLIB version.", &status);
2174 #endif
2175
2176 /* Write the GSL version. */
2177 fits_update_key(fptr, TSTRING, "GSL", GSL_VERSION,
2178 "GNU Scientific Library version.", &status);
2179
2180 /* Write the Gnuastro version. */
2181 fits_update_key(fptr, TSTRING, "GNUASTRO", PACKAGE_VERSION,
2182 "GNU Astronomy Utilities version.", &status);
2183
2184 /* If we are in a version controlled directory and have libgit2
2185 installed, write the commit description into the FITS file. */
2186 gitdescribe=gal_git_describe();
2187 if(gitdescribe)
2188 {
2189 fits_update_key(fptr, TSTRING, "COMMIT", gitdescribe,
2190 "Git commit in running directory.", &status);
2191 free(gitdescribe);
2192 }
2193
2194 /* Report any error if a problem came up */
2195 gal_fits_io_error(status, NULL);
2196 }
2197
2198
2199
2200
2201
2202
2203 /* Write the given keywords into the given extension of the given file,
2204 ending it with version information. This is primarily intended for
2205 writing configuration settings of a program into the zero-th extension
2206 of the FITS file (which is empty when the FITS file is created by
2207 Gnuastro's program and this library). */
2208 void
gal_fits_key_write_config(gal_fits_list_key_t ** keylist,char * title,char * extname,char * filename,char * hdu)2209 gal_fits_key_write_config(gal_fits_list_key_t **keylist, char *title,
2210 char *extname, char *filename, char *hdu)
2211 {
2212 int status=0;
2213 fitsfile *fptr=gal_fits_hdu_open(filename, hdu, READWRITE);
2214
2215 /* Delete the two extra comment lines describing the FITS standard that
2216 CFITSIO puts in when it creates a new extension. We'll set status to 0
2217 afterwards so even if they don't exist, the program continues
2218 normally. */
2219 fits_delete_key(fptr, "COMMENT", &status);
2220 fits_delete_key(fptr, "COMMENT", &status);
2221 status=0;
2222
2223 /* Put a name for the zero-th extension. */
2224 if(fits_write_key(fptr, TSTRING, "EXTNAME", extname, "", &status))
2225 gal_fits_io_error(status, NULL);
2226
2227 /* Write all the given keywords. */
2228 gal_fits_key_write_version_in_ptr(keylist, title, fptr);
2229
2230 /* Close the FITS file. */
2231 if( fits_close_file(fptr, &status) )
2232 gal_fits_io_error(status, NULL);
2233 }
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254 /*************************************************************
2255 *********** Array functions ***********
2256 *************************************************************/
2257
2258 /* Note that the FITS standard defines any array as an 'image',
2259 irrespective of how many dimensions it has. This function will return
2260 the Gnuastro-type, the number of dimensions and size along each
2261 dimension of the image along with its name and units if necessary (not
2262 NULL). Note that '*dsize' will be allocated here, so it must not point
2263 to any already allocated space. */
2264 void
gal_fits_img_info(fitsfile * fptr,int * type,size_t * ndim,size_t ** dsize,char ** name,char ** unit)2265 gal_fits_img_info(fitsfile *fptr, int *type, size_t *ndim, size_t **dsize,
2266 char **name, char **unit)
2267 {
2268 double bscale=NAN;
2269 size_t i, dsize_key=1;
2270 char **str, *bzero_str=NULL;
2271 int bitpix, status=0, naxis;
2272 gal_data_t *key, *keysll=NULL;
2273 long naxes[GAL_FITS_MAX_NDIM];
2274
2275 /* Get the BITPIX, number of dimensions and size of each dimension. */
2276 if( fits_get_img_param(fptr, GAL_FITS_MAX_NDIM, &bitpix, &naxis,
2277 naxes, &status) )
2278 gal_fits_io_error(status, NULL);
2279 *ndim=naxis;
2280
2281
2282 /* Convert bitpix to Gnuastro's known types. */
2283 *type=gal_fits_bitpix_to_type(bitpix);
2284
2285
2286 /* Define the names of the possibly existing important keywords about the
2287 dataset. We are defining these in the opposite order to be read by
2288 CFITSIO. The way Gnuastro writes the FITS keywords, the output will
2289 first have 'BZERO', then 'BSCALE', then 'EXTNAME', then, 'BUNIT'.*/
2290 gal_list_data_add_alloc(&keysll, NULL, GAL_TYPE_STRING, 1, &dsize_key,
2291 NULL, 0, -1, 1, "BUNIT", NULL, NULL);
2292 gal_list_data_add_alloc(&keysll, NULL, GAL_TYPE_STRING, 1, &dsize_key,
2293 NULL, 0, -1, 1, "EXTNAME", NULL, NULL);
2294 gal_list_data_add_alloc(&keysll, NULL, GAL_TYPE_FLOAT64, 1, &dsize_key,
2295 NULL, 0, -1, 1, "BSCALE", NULL, NULL);
2296 gal_list_data_add_alloc(&keysll, NULL, GAL_TYPE_STRING, 1, &dsize_key,
2297 NULL, 0, -1, 1, "BZERO", NULL, NULL);
2298 gal_fits_key_read_from_ptr(fptr, keysll, 0, 0);
2299
2300
2301 /* Read the special keywords. */
2302 i=1;
2303 for(key=keysll;key!=NULL;key=key->next)
2304 {
2305 /* Recall that the order is the opposite (this is a last-in-first-out
2306 list. */
2307 if(key->status==0)
2308 {
2309 switch(i)
2310 {
2311 case 4: if(unit) {str = key->array; *unit = *str; *str=NULL;} break;
2312 case 3: if(name) {str = key->array; *name = *str; *str=NULL;} break;
2313 case 2: bscale = *(double *)(key->array); break;
2314 case 1: str = key->array; bzero_str = *str; break;
2315 default:
2316 error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to "
2317 "fix the problem. For some reason, there are more "
2318 "keywords requested ", __func__, PACKAGE_BUGREPORT);
2319 }
2320 }
2321 ++i;
2322 }
2323
2324
2325 /* If a type correction is necessary, then do it. */
2326 if( !isnan(bscale) || bzero_str )
2327 fits_type_correct(type, bscale, bzero_str);
2328
2329
2330 /* Allocate the array to keep the dimension size and fill it in, note
2331 that its order is the opposite of naxes. */
2332 *dsize=gal_pointer_allocate(GAL_TYPE_INT64, *ndim, 0, __func__, "dsize");
2333 for(i=0; i<*ndim; ++i)
2334 (*dsize)[i]=naxes[*ndim-1-i];
2335
2336
2337 /* Clean up. Note that bzero_str, gets freed by 'gal_data_free' (which is
2338 called by 'gal_list_data_free'. */
2339 gal_list_data_free(keysll);
2340 }
2341
2342
2343
2344
2345
2346 /* Get the basic array info to remove extra dimensions if necessary. */
2347 size_t *
gal_fits_img_info_dim(char * filename,char * hdu,size_t * ndim)2348 gal_fits_img_info_dim(char *filename, char *hdu, size_t *ndim)
2349 {
2350 fitsfile *fptr;
2351 size_t *dsize=NULL;
2352 int status=0, type;
2353
2354 /* Open the given header, read the basic image information and close it
2355 again. */
2356 fptr=gal_fits_hdu_open(filename, hdu, READONLY);
2357 gal_fits_img_info(fptr, &type, ndim, &dsize, NULL, NULL);
2358 if( fits_close_file(fptr, &status) ) gal_fits_io_error(status, NULL);
2359
2360 return dsize;
2361 }
2362
2363
2364
2365
2366
2367 /* Read a FITS image HDU into a Gnuastro data structure. */
2368 gal_data_t *
gal_fits_img_read(char * filename,char * hdu,size_t minmapsize,int quietmmap)2369 gal_fits_img_read(char *filename, char *hdu, size_t minmapsize,
2370 int quietmmap)
2371 {
2372 void *blank;
2373 long *fpixel;
2374 fitsfile *fptr;
2375 gal_data_t *img;
2376 size_t i, ndim, *dsize;
2377 char *name=NULL, *unit=NULL;
2378 int status=0, type, anyblank;
2379
2380
2381 /* Check HDU for realistic conditions: */
2382 fptr=gal_fits_hdu_open_format(filename, hdu, 0);
2383
2384
2385 /* Get the info and allocate the data structure. */
2386 gal_fits_img_info(fptr, &type, &ndim, &dsize, &name, &unit);
2387
2388
2389 /* Check if there is any dimensions (the first header can sometimes have
2390 no images). */
2391 if(ndim==0)
2392 error(EXIT_FAILURE, 0, "%s (hdu: %s) has 0 dimensions! The most common "
2393 "cause for this is a wrongly specified HDU. In some FITS images, "
2394 "the first HDU doesn't have any data, the data is in subsequent "
2395 "extensions. So probably reading the second HDU (with '--hdu=1' "
2396 "or '-h1') will solve the problem (following CFITSIO's "
2397 "convention, currently HDU counting starts from 0)." , filename,
2398 hdu);
2399
2400
2401 /* Set the fpixel array (first pixel in all dimensions). Note that the
2402 'long' type will not be larger than 64-bits, so, we'll just assume it
2403 is 64-bits for space allocation. On 32-bit systems, this won't be a
2404 problem, the space will be written/read as 32-bit 'long' any way,
2405 we'll just have a few empty bytes that will be freed anyway at the end
2406 of this function. */
2407 fpixel=gal_pointer_allocate(GAL_TYPE_INT64, ndim, 0, __func__, "fpixel");
2408 for(i=0;i<ndim;++i) fpixel[i]=1;
2409
2410
2411 /* Allocate the space for the array and for the blank values. */
2412 img=gal_data_alloc(NULL, type, ndim, dsize, NULL, 0, minmapsize,
2413 quietmmap, name, unit, NULL);
2414 blank=gal_blank_alloc_write(type);
2415 if(name) free(name);
2416 if(unit) free(unit);
2417 free(dsize);
2418
2419
2420 /* Read the image into the allocated array: */
2421 fits_read_pix(fptr, gal_fits_type_to_datatype(type), fpixel,
2422 img->size, blank, img->array, &anyblank, &status);
2423 if(status) gal_fits_io_error(status, NULL);
2424 free(fpixel);
2425 free(blank);
2426
2427
2428 /* Close the input FITS file. */
2429 fits_close_file(fptr, &status);
2430 gal_fits_io_error(status, NULL);
2431
2432
2433 /* Return the filled data structure */
2434 return img;
2435 }
2436
2437
2438
2439
2440
2441 /* The user has specified an input file + extension, and your program needs
2442 this input to be a special type. For such cases, this function can be
2443 used to convert the input file to the desired type. */
2444 gal_data_t *
gal_fits_img_read_to_type(char * inputname,char * hdu,uint8_t type,size_t minmapsize,int quietmmap)2445 gal_fits_img_read_to_type(char *inputname, char *hdu, uint8_t type,
2446 size_t minmapsize, int quietmmap)
2447 {
2448 gal_data_t *in, *converted;
2449
2450 /* Read the specified input image HDU. */
2451 in=gal_fits_img_read(inputname, hdu, minmapsize, quietmmap);
2452
2453 /* If the input had another type, convert it to float. */
2454 if(in->type!=type)
2455 {
2456 converted=gal_data_copy_to_new_type(in, type);
2457 gal_data_free(in);
2458 in=converted;
2459 }
2460
2461 /* Return the final structure. */
2462 return in;
2463 }
2464
2465
2466
2467
2468
2469 gal_data_t *
gal_fits_img_read_kernel(char * filename,char * hdu,size_t minmapsize,int quietmmap)2470 gal_fits_img_read_kernel(char *filename, char *hdu, size_t minmapsize,
2471 int quietmmap)
2472 {
2473 size_t i;
2474 int check=0;
2475 double sum=0;
2476 gal_data_t *kernel;
2477 float *f, *fp, tmp;
2478
2479 /* Read the image as a float and if it has a WCS structure, free it. */
2480 kernel=gal_fits_img_read_to_type(filename, hdu, GAL_TYPE_FLOAT32,
2481 minmapsize, quietmmap);
2482 if(kernel->wcs) { wcsfree(kernel->wcs); kernel->wcs=NULL; }
2483
2484 /* Check if the size along each dimension of the kernel is an odd
2485 number. If they are all an odd number, then the for each dimension,
2486 check will be incremented once. */
2487 for(i=0;i<kernel->ndim;++i)
2488 check += kernel->dsize[i]%2;
2489 if(check!=kernel->ndim)
2490 error(EXIT_FAILURE, 0, "%s: the kernel image has to have an odd number "
2491 "of pixels in all dimensions (there has to be one element/pixel "
2492 "in the center). At least one of the dimensions of %s (hdu: %s) "
2493 "doesn't have an odd number of pixels", __func__, filename, hdu);
2494
2495 /* If there are any NaN pixels, set them to zero and normalize it. A
2496 blank pixel in a kernel is going to make a completely blank output.*/
2497 fp=(f=kernel->array)+kernel->size;
2498 do
2499 {
2500 if(isnan(*f)) *f=0.0f;
2501 else sum+=*f;
2502 }
2503 while(++f<fp);
2504 kernel->flag |= GAL_DATA_FLAG_BLANK_CH;
2505 kernel->flag &= ~GAL_DATA_FLAG_HASBLANK;
2506 f=kernel->array; do *f++ *= 1/sum; while(f<fp);
2507
2508 /* Flip the kernel about the center (necessary for non-symmetric
2509 kernels). */
2510 f=kernel->array;
2511 for(i=0;i<kernel->size/2;++i)
2512 {
2513 tmp=f[i];
2514 f[i]=f[ kernel->size - i - 1 ];
2515 f[ kernel->size - i - 1 ]=tmp;
2516 }
2517
2518 /* Return the kernel*/
2519 return kernel;
2520 }
2521
2522
2523
2524
2525
2526 /* This function will write all the data array information (including its
2527 WCS information) into a FITS file, but will not close it. Instead it
2528 will pass along the FITS pointer for further modification. */
2529 fitsfile *
gal_fits_img_write_to_ptr(gal_data_t * input,char * filename)2530 gal_fits_img_write_to_ptr(gal_data_t *input, char *filename)
2531 {
2532 void *blank;
2533 int64_t *i64;
2534 char *u64key;
2535 fitsfile *fptr;
2536 uint64_t *u64, *u64f;
2537 long fpixel=1, *naxes;
2538 size_t i, ndim=input->ndim;
2539 int hasblank, status=0, datatype=0;
2540 gal_data_t *i64data, *towrite, *block=gal_tile_block(input);
2541
2542 /* Small sanity check. */
2543 if( gal_fits_name_is_fits(filename)==0 )
2544 error(EXIT_FAILURE, 0, "%s: not a FITS suffix", filename);
2545
2546
2547 /* If the input is a tile (isn't a contiguous region of memory), then
2548 copy it into a contiguous region. */
2549 towrite = input==block ? input : gal_data_copy(input);
2550 hasblank=gal_blank_present(towrite, 0);
2551
2552
2553 /* Allocate the naxis area. */
2554 naxes=gal_pointer_allocate( ( sizeof(long)==8
2555 ? GAL_TYPE_INT64
2556 : GAL_TYPE_INT32 ), ndim, 0, __func__,
2557 "naxes");
2558
2559
2560 /* Open the file for writing */
2561 fptr=gal_fits_open_to_write(filename);
2562
2563
2564 /* Fill the 'naxes' array (in opposite order, and 'long' type): */
2565 for(i=0;i<ndim;++i) naxes[ndim-1-i]=towrite->dsize[i];
2566
2567
2568 /* Create the FITS file. Unfortunately CFITSIO doesn't have a macro for
2569 UINT64, TLONGLONG is only for (signed) INT64. So if the dataset has
2570 that type, we'll have to convert it to 'INT64' and in the mean-time
2571 shift its zero, we will then have to write the BZERO and BSCALE
2572 keywords accordingly. */
2573 if(block->type==GAL_TYPE_UINT64)
2574 {
2575 /* Allocate the necessary space. */
2576 i64data=gal_data_alloc(NULL, GAL_TYPE_INT64, ndim, towrite->dsize,
2577 NULL, 0, block->minmapsize, block->quietmmap,
2578 NULL, NULL, NULL);
2579
2580 /* Copy the values while making the conversion. */
2581 i64=i64data->array;
2582 u64f=(u64=towrite->array)+towrite->size;
2583 if(hasblank)
2584 {
2585 do *i64++ = ( *u64==GAL_BLANK_UINT64
2586 ? GAL_BLANK_INT64
2587 : (*u64 + INT64_MIN) );
2588 while(++u64<u64f);
2589 }
2590 else
2591 do *i64++ = (*u64 + INT64_MIN); while(++u64<u64f);
2592
2593 /* We can now use CFITSIO's signed-int64 type macros. */
2594 datatype=TLONGLONG;
2595 fits_create_img(fptr, LONGLONG_IMG, ndim, naxes, &status);
2596 gal_fits_io_error(status, NULL);
2597
2598 /* Write the image into the file. */
2599 fits_write_img(fptr, datatype, fpixel, i64data->size, i64data->array,
2600 &status);
2601 gal_fits_io_error(status, NULL);
2602
2603
2604 /* We need to write the BZERO and BSCALE keywords manually. VERY
2605 IMPORTANT: this has to be done after writing the array. We cannot
2606 write this huge integer as a variable, so we'll simply write the
2607 full record/card. It is just important that the string be larger
2608 than 80 characters, CFITSIO will trim the rest of the string. */
2609 u64key="BZERO = 9223372036854775808 / Offset of data ";
2610 fits_write_record(fptr, u64key, &status);
2611 u64key="BSCALE = 1 / Default scaling factor ";
2612 fits_write_record(fptr, u64key, &status);
2613 gal_fits_io_error(status, NULL);
2614 }
2615 else
2616 {
2617 /* Set the datatype */
2618 datatype=gal_fits_type_to_datatype(block->type);
2619
2620 /* Create the FITS file. */
2621 fits_create_img(fptr, gal_fits_type_to_bitpix(towrite->type),
2622 ndim, naxes, &status);
2623 gal_fits_io_error(status, NULL);
2624
2625 /* Write the image into the file. */
2626 fits_write_img(fptr, datatype, fpixel, towrite->size, towrite->array,
2627 &status);
2628 gal_fits_io_error(status, NULL);
2629 }
2630
2631
2632 /* Remove the two comment lines put by CFITSIO. Note that in some cases,
2633 it might not exist. When this happens, the status value will be
2634 non-zero. We don't care about this error, so to be safe, we will just
2635 reset the status variable after these calls. */
2636 fits_delete_key(fptr, "COMMENT", &status);
2637 fits_delete_key(fptr, "COMMENT", &status);
2638 status=0;
2639
2640
2641 /* If we have blank pixels, we need to define a BLANK keyword when we are
2642 dealing with integer types. */
2643 if(hasblank)
2644 switch(towrite->type)
2645 {
2646 case GAL_TYPE_FLOAT32:
2647 case GAL_TYPE_FLOAT64:
2648 /* Do nothing! Since there are much fewer floating point types
2649 (that don't need any BLANK keyword), we are checking them.*/
2650 break;
2651
2652 default:
2653 blank=gal_fits_key_img_blank(towrite->type);
2654 if(fits_write_key(fptr, datatype, "BLANK", blank,
2655 "Pixels with no data.", &status) )
2656 gal_fits_io_error(status, "adding the BLANK keyword");
2657 free(blank);
2658 }
2659
2660
2661 /* Write the extension name to the header. */
2662 if(towrite->name)
2663 fits_write_key(fptr, TSTRING, "EXTNAME", towrite->name, "", &status);
2664
2665
2666 /* Write the units to the header. */
2667 if(towrite->unit)
2668 fits_write_key(fptr, TSTRING, "BUNIT", towrite->unit, "", &status);
2669
2670
2671 /* Write comments if they exist. */
2672 if(towrite->comment)
2673 fits_write_comment(fptr, towrite->comment, &status);
2674
2675
2676 /* If a WCS structure is present, write it in */
2677 if(towrite->wcs)
2678 gal_wcs_write_in_fitsptr(fptr, towrite->wcs);
2679
2680
2681 /* Report any errors if we had any */
2682 free(naxes);
2683 gal_fits_io_error(status, NULL);
2684 if(towrite!=input) gal_data_free(towrite);
2685 return fptr;
2686 }
2687
2688
2689
2690
2691
2692 void
gal_fits_img_write(gal_data_t * data,char * filename,gal_fits_list_key_t * headers,char * program_string)2693 gal_fits_img_write(gal_data_t *data, char *filename,
2694 gal_fits_list_key_t *headers, char *program_string)
2695 {
2696 int status=0;
2697 fitsfile *fptr;
2698
2699 /* Write the data array into a FITS file and keep it open: */
2700 fptr=gal_fits_img_write_to_ptr(data, filename);
2701
2702 /* Write all the headers and the version information. */
2703 gal_fits_key_write_version_in_ptr(&headers, program_string, fptr);
2704
2705 /* Close the FITS file. */
2706 fits_close_file(fptr, &status);
2707 gal_fits_io_error(status, NULL);
2708 }
2709
2710
2711
2712
2713
2714 void
gal_fits_img_write_to_type(gal_data_t * data,char * filename,gal_fits_list_key_t * headers,char * program_string,int type)2715 gal_fits_img_write_to_type(gal_data_t *data, char *filename,
2716 gal_fits_list_key_t *headers, char *program_string,
2717 int type)
2718 {
2719 /* If the input dataset is not the correct type, then convert it,
2720 otherwise, use the input data structure. */
2721 gal_data_t *towrite = (data->type==type
2722 ? data
2723 : gal_data_copy_to_new_type(data, type));
2724
2725 /* Write the converted dataset into an image. */
2726 gal_fits_img_write(towrite, filename, headers, program_string);
2727
2728 /* Free the dataset if it was allocated. */
2729 if(towrite!=data) gal_data_free(towrite);
2730 }
2731
2732
2733
2734
2735
2736 /* This function is mainly useful when you want to make FITS files in
2737 parallel (from one main WCS structure, with just differing CRPIX) for
2738 two reasons:
2739
2740 - When a large number of FITS images (with WCS) need to be created in
2741 parallel, it can be much more efficient to write the header's WCS
2742 keywords once at first, write them in the FITS file, then just
2743 correct the CRPIX values.
2744
2745 - WCSLIB's header writing function is not thread safe. So when
2746 writing FITS images in parallel, we can't write the header keywords
2747 in each thread. */
2748 void
gal_fits_img_write_corr_wcs_str(gal_data_t * input,char * filename,char * wcsstr,int nkeyrec,double * crpix,gal_fits_list_key_t * headers,char * program_string)2749 gal_fits_img_write_corr_wcs_str(gal_data_t *input, char *filename,
2750 char *wcsstr, int nkeyrec, double *crpix,
2751 gal_fits_list_key_t *headers,
2752 char *program_string)
2753 {
2754 int status=0;
2755 fitsfile *fptr;
2756
2757 /* The data should not have any WCS structure for this function. */
2758 if(input->wcs)
2759 error(EXIT_FAILURE, 0, "%s: input must not have WCS meta-data",
2760 __func__);
2761
2762 /* Write the data array into a FITS file and keep it open. */
2763 fptr=gal_fits_img_write_to_ptr(input, filename);
2764
2765 /* Write the WCS headers into the FITS file. */
2766 gal_fits_key_write_wcsstr(fptr, NULL, wcsstr, nkeyrec);
2767
2768 /* Update the CRPIX keywords. Note that we don't want to change the
2769 values in the WCS information of gal_data_t. Because, it often happens
2770 that things are done in parallel, so we don't want to touch the
2771 original version, we just want to change the copied version. */
2772 if(crpix)
2773 {
2774 fits_update_key(fptr, TDOUBLE, "CRPIX1", &crpix[0], NULL, &status);
2775 fits_update_key(fptr, TDOUBLE, "CRPIX2", &crpix[1], NULL, &status);
2776 if(input->ndim==3)
2777 fits_update_key(fptr, TDOUBLE, "CRPIX3", &crpix[2], NULL, &status);
2778 gal_fits_io_error(status, NULL);
2779 }
2780
2781 /* Write all the headers and the version information. */
2782 gal_fits_key_write_version_in_ptr(&headers, program_string, fptr);
2783
2784 /* Close the file and return. */
2785 fits_close_file(fptr, &status);
2786 gal_fits_io_error(status, NULL);
2787 }
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808 /**************************************************************/
2809 /********** Table ************/
2810 /**************************************************************/
2811 /* Get the size of a table HDU. CFITSIO doesn't use size_t, also we want to
2812 check status here.*/
2813 void
gal_fits_tab_size(fitsfile * fitsptr,size_t * nrows,size_t * ncols)2814 gal_fits_tab_size(fitsfile *fitsptr, size_t *nrows, size_t *ncols)
2815 {
2816 long lnrows;
2817 int incols, status=0;
2818
2819 /* Read the sizes and put them in. */
2820 fits_get_num_rows(fitsptr, &lnrows, &status);
2821 fits_get_num_cols(fitsptr, &incols, &status);
2822 *ncols=incols;
2823 *nrows=lnrows;
2824
2825 /* Report an error if any was issued. */
2826 gal_fits_io_error(status, NULL);
2827 }
2828
2829
2830
2831
2832
2833 int
gal_fits_tab_format(fitsfile * fitsptr)2834 gal_fits_tab_format(fitsfile *fitsptr)
2835 {
2836 int status=0;
2837 char value[FLEN_VALUE];
2838
2839 fits_read_key(fitsptr, TSTRING, "XTENSION", value, NULL, &status);
2840
2841 if(status==0)
2842 {
2843 if(!strcmp(value, "TABLE"))
2844 return GAL_TABLE_FORMAT_AFITS;
2845 else if(!strcmp(value, "BINTABLE"))
2846 return GAL_TABLE_FORMAT_BFITS;
2847 else
2848 error(EXIT_FAILURE, 0, "%s: the 'XTENSION' keyword of this FITS "
2849 "table ('%s') doesn't have a standard value", __func__, value);
2850 }
2851 else
2852 {
2853 if(status==KEY_NO_EXIST)
2854 error(EXIT_FAILURE, 0, "%s: input fitsfile pointer isn't a table",
2855 __func__);
2856 else
2857 gal_fits_io_error(status, NULL);
2858 }
2859
2860 error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s so we can fix it. "
2861 "Control should not have reached the end of this function", __func__,
2862 PACKAGE_BUGREPORT);
2863 return -1;
2864 }
2865
2866
2867
2868
2869
2870 /* The general format of the TDISPn keywords in FITS is like this: 'Tw.p',
2871 where 'T' specifies the general format, 'w' is the width to be given to
2872 this column and 'p' is the precision. For integer types, percision is
2873 actually the minimum number of integers, for floats, it is the number of
2874 decimal digits beyond the decimal point. */
2875 static void
set_display_format(char * tdisp,gal_data_t * data,char * filename,char * hdu,char * keyname)2876 set_display_format(char *tdisp, gal_data_t *data, char *filename, char *hdu,
2877 char *keyname)
2878 {
2879 int isanint=0;
2880 char *tailptr;
2881
2882 /* First, set the general display format */
2883 switch(tdisp[0])
2884 {
2885 case 'A':
2886 data->disp_fmt=GAL_TABLE_DISPLAY_FMT_STRING;
2887 break;
2888
2889 case 'I':
2890 isanint=1;
2891 data->disp_fmt=GAL_TABLE_DISPLAY_FMT_DECIMAL;
2892 break;
2893
2894 case 'O':
2895 isanint=1;
2896 data->disp_fmt=GAL_TABLE_DISPLAY_FMT_OCTAL;
2897 break;
2898
2899 case 'Z':
2900 isanint=1;
2901 data->disp_fmt=GAL_TABLE_DISPLAY_FMT_HEX;
2902 break;
2903
2904 case 'F':
2905 data->disp_fmt=GAL_TABLE_DISPLAY_FMT_FLOAT;
2906 break;
2907
2908 case 'E':
2909 case 'D':
2910 data->disp_fmt=GAL_TABLE_DISPLAY_FMT_EXP;
2911 break;
2912
2913 case 'G':
2914 data->disp_fmt=GAL_TABLE_DISPLAY_FMT_GENERAL;
2915 break;
2916
2917 default:
2918 error(EXIT_FAILURE, 0, "%s (hdu: %s): Format character '%c' in the "
2919 "value (%s) of the keyword %s not recognized in %s", filename, hdu,
2920 tdisp[0], tdisp, keyname, __func__);
2921 }
2922
2923 /* Parse the rest of the string to see if a width and precision are given
2924 or not. */
2925 data->disp_width=strtol(&tdisp[1], &tailptr, 0);
2926 switch(*tailptr)
2927 {
2928 case '.': /* Width is set, go onto finding the precision. */
2929 data->disp_precision = strtol(&tailptr[1], &tailptr, 0);
2930 if(*tailptr!='\0')
2931 error(EXIT_FAILURE, 0, "%s (hdu: %s): The value '%s' of the "
2932 "'%s' keyword could not recognized (it doesn't finish after "
2933 "the precision) in %s", filename, hdu, tdisp, keyname, __func__);
2934 break;
2935
2936 case '\0': /* No precision given, use a default value. */
2937 data->disp_precision = ( isanint
2938 ? GAL_TABLE_DEF_PRECISION_INT
2939 : GAL_TABLE_DEF_PRECISION_FLT );
2940 break;
2941
2942 default:
2943 error(EXIT_FAILURE, 0, "%s (hdu: %s): The value '%s' of the "
2944 "'%s' keyword could not recognized (it doesn't have a '.', or "
2945 "finish, after the width) in %s", filename, hdu, tdisp,
2946 keyname, __func__);
2947 }
2948
2949
2950 }
2951
2952
2953
2954
2955 /* The FITS standard for binary tables (not ASCII tables) does not allow
2956 unsigned types for short, int and long types, or signed char! So it has
2957 'TSCALn' and 'TZEROn' to scale the signed types to an unsigned type. It
2958 does this internally, but since we need to define our data type and
2959 allocate space for it before actually reading the array, it is necessary
2960 to do this setting here. */
2961 static void
fits_correct_bin_table_int_types(gal_data_t * allcols,int tfields,int * tscal,long long * tzero)2962 fits_correct_bin_table_int_types(gal_data_t *allcols, int tfields,
2963 int *tscal, long long *tzero)
2964 {
2965 size_t i;
2966
2967 for(i=0;i<tfields;++i)
2968 {
2969 /* If TSCALn is not 1, the reason for it isn't to use a different
2970 signed/unsigned type, so don't change anything. */
2971 if(tscal[i]!=1) continue;
2972
2973 /* For a check
2974 printf("Column %zu initial type: %s (s: %d, z: %lld)\n", i+1,
2975 gal_data_type_as_string(allcols[i].type, 1), tscal[i], tzero[i]);
2976 */
2977
2978 /* Correct the type based on the initial read type and the value to
2979 tzero. If tzero is any other value, then again, its not a type
2980 conversion, so just ignore it. */
2981 if(allcols[i].type==GAL_TYPE_UINT8 && tzero[i]==INT8_MIN)
2982 allcols[i].type = GAL_TYPE_INT8;
2983
2984 else if ( allcols[i].type==GAL_TYPE_INT16
2985 && tzero[i] == -(long long)INT16_MIN )
2986 allcols[i].type = GAL_TYPE_UINT16;
2987
2988 else if (allcols[i].type==GAL_TYPE_INT32
2989 && tzero[i] == -(long long)INT32_MIN)
2990 allcols[i].type = GAL_TYPE_UINT32;
2991
2992 /* For a check
2993 printf("Column %zu corrected type: %s\n", i+1,
2994 gal_data_type_as_string(allcols[i].type, 1));
2995 */
2996 }
2997 }
2998
2999
3000
3001
3002
3003 /* See the descriptions of 'gal_table_info'. */
3004 gal_data_t *
gal_fits_tab_info(char * filename,char * hdu,size_t * numcols,size_t * numrows,int * tableformat)3005 gal_fits_tab_info(char *filename, char *hdu, size_t *numcols,
3006 size_t *numrows, int *tableformat)
3007 {
3008 long repeat;
3009 int tfields; /* The maximum number of fields in FITS is 999 */
3010 char *tailptr;
3011 fitsfile *fptr;
3012 size_t i, index;
3013 long long *tzero;
3014 gal_data_t *allcols;
3015 int status=0, datatype, *tscal;
3016 char keyname[FLEN_KEYWORD]="XXXXXXXXXXXXX", value[FLEN_VALUE];
3017
3018 /* Necessary when a keyword can't be written immediately as it is read in
3019 the FITS header and it actually depends on other data before. */
3020 gal_list_str_t *tmp_n, *later_name=NULL;
3021 gal_list_str_t *tmp_v, *later_value=NULL;
3022 gal_list_sizet_t *tmp_i, *later_index=NULL;
3023
3024 /* Open the FITS file and get the basic information. */
3025 fptr=gal_fits_hdu_open_format(filename, hdu, 1);
3026 *tableformat=gal_fits_tab_format(fptr);
3027 gal_fits_tab_size(fptr, numrows, numcols);
3028
3029
3030 /* Read the total number of fields, then allocate space for the data
3031 structure array and store the information within it. */
3032 fits_read_key(fptr, TINT, "TFIELDS", &tfields, NULL, &status);
3033 allcols=gal_data_array_calloc(tfields);
3034
3035
3036 /* See comments of 'fits_correct_bin_table_int_types'. Here we are
3037 allocating the space to keep these values. */
3038 errno=0;
3039 tscal=calloc(tfields, sizeof *tscal);
3040 if(tscal==NULL)
3041 error(EXIT_FAILURE, errno, "%s: %zu bytes for tscal", __func__,
3042 tfields*sizeof *tscal);
3043 errno=0;
3044 tzero=calloc(tfields, sizeof *tzero);
3045 if(tzero==NULL)
3046 error(EXIT_FAILURE, errno, "%s: %zu bytes for tzero", __func__,
3047 tfields*sizeof *tzero);
3048
3049
3050 /* Read all the keywords one by one and if they match, then put them in
3051 the correct value. Note that we are starting from keyword 9 because
3052 according to the FITS standard, the first 8 keys in a FITS table are
3053 reserved. */
3054 for(i=9; strcmp(keyname, "END"); ++i)
3055 {
3056 /* Read the next keyword. */
3057 fits_read_keyn(fptr, i, keyname, value, NULL, &status);
3058
3059 /* For string valued keywords, CFITSIO's function above, keeps the
3060 single quotes around the value string, one before and one
3061 after. 'gal_fits_key_clean_str_value' will remove these single
3062 quotes and any possible trailing space within the allocated
3063 space.*/
3064 if(value[0]=='\'') gal_fits_key_clean_str_value(value);
3065
3066 /* COLUMN DATA TYPE. According the the FITS standard, the value of
3067 TFORM is most generally in this format: 'rTa'. 'T' is actually a
3068 code of the datatype. 'r' is the 'repeat' counter and 'a' is
3069 depreciated. Currently we can only read repeat==1 cases. When no
3070 number exists before the defined capital letter, it defaults to 1,
3071 but if a number exists (for example '5D'), then the repeat is 5
3072 (there are actually five values in each column). Note that
3073 value[0] is a single quote.*/
3074 if(strncmp(keyname, "TFORM", 5)==0)
3075 {
3076 /* See which column this information was for and add it, if the
3077 index is larger than the number of columns, then ignore
3078 the . The FITS standard says there should be no extra TFORM
3079 keywords beyond the number of columns, but we don't want to be
3080 that strict here.*/
3081 index = strtoul(&keyname[5], &tailptr, 10) - 1;
3082 if(index<tfields) /* Counting from zero was corrected above. */
3083 {
3084 /* The FITS standard's value to this option for FITS ASCII
3085 and binary files differ. */
3086 if(*tableformat==GAL_TABLE_FORMAT_AFITS)
3087 fits_ascii_tform(value, &datatype, NULL, NULL, &status);
3088 else
3089 fits_binary_tform(value, &datatype, &repeat, NULL, &status);
3090
3091 /* Write the type into the data structure. */
3092 allcols[index].type=gal_fits_datatype_to_type(datatype, 1);
3093
3094 /* If we are dealing with a string type, we need to know the
3095 number of bytes in both cases for printing later. */
3096 if( allcols[index].type==GAL_TYPE_STRING )
3097 {
3098 if(*tableformat==GAL_TABLE_FORMAT_AFITS)
3099 {
3100 repeat=strtol(value+1, &tailptr, 0);
3101 if(*tailptr!='\0')
3102 error(EXIT_FAILURE, 0, "%s (hdu: %s): the value to "
3103 "keyword '%s' ('%s') is not in 'Aw' format "
3104 "(for strings) as required by the FITS "
3105 "standard in %s", filename, hdu, keyname, value,
3106 __func__);
3107 }
3108 allcols[index].disp_width=repeat;
3109 }
3110 }
3111 }
3112
3113 /* COLUMN SCALE FACTOR. */
3114 else if(strncmp(keyname, "TSCAL", 5)==0)
3115 {
3116 index = strtoul(&keyname[5], &tailptr, 10) - 1;
3117 if(index<tfields)
3118 {
3119 tscal[index]=strtol(value, &tailptr, 0);
3120 if(*tailptr!='\0')
3121 error(EXIT_FAILURE, 0, "%s (hdu: %s): value to %s keyword "
3122 "('%s') couldn't be read as a number in %s", filename,
3123 hdu, keyname, value, __func__);
3124 }
3125 }
3126
3127 /* COLUMN ZERO VALUE (for signed/unsigned types). */
3128 else if(strncmp(keyname, "TZERO", 5)==0)
3129 {
3130 index = strtoul(&keyname[5], &tailptr, 10) - 1;
3131 if(index<tfields)
3132 {
3133 tzero[index]=strtoll(value, &tailptr, 0);
3134 if(*tailptr!='\0')
3135 error(EXIT_FAILURE, 0, "%s (hdu: %s): value to %s keyword "
3136 "('%s') couldn't be read as a number in %s", filename,
3137 hdu, keyname, value, __func__);
3138 }
3139 }
3140
3141 /* COLUMN NAME. All strings in CFITSIO start and finish with single
3142 quotation marks, CFITSIO puts them in itsself, so if we don't
3143 remove them here, we might have duplicates later, its easier to
3144 just remove them to have a simple string that might be used else
3145 where too (without the single quotes).*/
3146 else if(strncmp(keyname, "TTYPE", 5)==0)
3147 {
3148 index = strtoul(&keyname[5], &tailptr, 10) - 1;
3149 if(index<tfields)
3150 gal_checkset_allocate_copy(value, &allcols[index].name);
3151 }
3152
3153 /* COLUMN UNITS. */
3154 else if(strncmp(keyname, "TUNIT", 5)==0)
3155 {
3156 index = strtoul(&keyname[5], &tailptr, 10) - 1;
3157 if(index<tfields)
3158 gal_checkset_allocate_copy(value, &allcols[index].unit);
3159 }
3160
3161 /* COLUMN COMMENTS */
3162 else if(strncmp(keyname, "TCOMM", 5)==0)
3163 {
3164 index = strtoul(&keyname[5], &tailptr, 10) - 1;
3165 if(index<tfields)
3166 gal_checkset_allocate_copy(value, &allcols[index].comment);
3167 }
3168
3169 /* COLUMN BLANK VALUE. Note that to interpret the blank value the
3170 type of the column must already have been defined for this column
3171 in previous keywords. Otherwise, there will be a warning and it
3172 won't be used. */
3173 else if(strncmp(keyname, "TNULL", 5)==0)
3174 {
3175 index = strtoul(&keyname[5], &tailptr, 10) - 1;
3176 if(index<tfields )
3177 {
3178 if(allcols[index].type==0)
3179 {
3180 gal_list_str_add(&later_name, keyname, 1);
3181 gal_list_str_add(&later_value, value, 1);
3182 gal_list_sizet_add(&later_index, index);
3183 }
3184 else
3185 {
3186 /* Put in the blank value. */
3187 gal_tableintern_read_blank(&allcols[index], value);
3188
3189 /* This flag is not relevant for FITS tables. */
3190 if(allcols[index].flag
3191 ==GAL_TABLEINTERN_FLAG_ARRAY_IS_BLANK_STRING)
3192 {
3193 allcols[index].flag=0;
3194 free(allcols[index].array);
3195 }
3196 }
3197 }
3198 }
3199
3200 /* COLUMN DISPLAY FORMAT */
3201 else if(strncmp(keyname, "TDISP", 5)==0)
3202 {
3203 index = strtoul(&keyname[5], &tailptr, 10) - 1;
3204 if(index<tfields)
3205 set_display_format(value, &allcols[index], filename, hdu,
3206 keyname);
3207 }
3208
3209 /* Column zero. */
3210 }
3211
3212
3213 /* If any columns should be added later because of missing information,
3214 add them here. */
3215 if(later_name)
3216 {
3217 /* Interpret the necessary 'later_' keys. */
3218 tmp_i=later_index;
3219 tmp_v=later_value;
3220 for(tmp_n=later_name; tmp_n!=NULL; tmp_n=tmp_n->next)
3221 {
3222 /* Go over the known types and do the job. */
3223 if(strncmp(tmp_n->v, "TNULL", 5)==0)
3224 {
3225 /* Put in the blank value. */
3226 gal_tableintern_read_blank(&allcols[tmp_i->v], tmp_v->v);
3227
3228 /* This flag is not relevant for FITS tables. */
3229 if(allcols[tmp_i->v].flag
3230 ==GAL_TABLEINTERN_FLAG_ARRAY_IS_BLANK_STRING)
3231 {
3232 allcols[tmp_i->v].flag=0;
3233 free(allcols[tmp_i->v].array);
3234 }
3235 }
3236 else
3237 error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to "
3238 "fix the problem. Post-processing of keyword '%s' failed",
3239 __func__, PACKAGE_BUGREPORT, tmp_n->v);
3240
3241 /* Increment the other two lists too. */
3242 tmp_v=tmp_v->next;
3243 tmp_i=tmp_i->next;
3244 }
3245
3246 /* Clean up. */
3247 gal_list_sizet_free(later_index);
3248 gal_list_str_free(later_name, 1);
3249 gal_list_str_free(later_value, 1);
3250 }
3251
3252
3253 /* Correct integer types, then free the allocated arrays. */
3254 fits_correct_bin_table_int_types(allcols, tfields, tscal, tzero);
3255 free(tscal);
3256 free(tzero);
3257
3258
3259 /* Close the FITS file and report an error if we had any. */
3260 fits_close_file(fptr, &status);
3261 gal_fits_io_error(status, NULL);
3262 return allcols;
3263 }
3264
3265
3266
3267
3268
3269 /* Read CFITSIO un-readable (INF, -INF or NAN) floating point values in
3270 FITS ASCII tables. */
3271 static void
fits_tab_read_ascii_float_special(char * filename,char * hdu,fitsfile * fptr,gal_data_t * out,size_t colnum,size_t numrows,size_t minmapsize,int quietmmap)3272 fits_tab_read_ascii_float_special(char *filename, char *hdu, fitsfile *fptr,
3273 gal_data_t *out, size_t colnum,
3274 size_t numrows, size_t minmapsize,
3275 int quietmmap)
3276 {
3277 double tmp;
3278 char **strarr;
3279 char *tailptr;
3280 gal_data_t *strrows;
3281 size_t i, colwidth=50;
3282 int anynul=0, status=0;
3283
3284 /* Allocate the dataset to keep the string values. */
3285 strrows=gal_data_alloc(NULL, GAL_TYPE_STRING, 1, &numrows, NULL, 0,
3286 minmapsize, quietmmap, NULL, NULL, NULL);
3287
3288 /* Allocate the space to keep the string values. */
3289 strarr=strrows->array;
3290 for(i=0;i<numrows;++i)
3291 {
3292 errno=0;
3293 strarr[i]=calloc(colwidth, sizeof *strarr[i]);
3294 if(strarr[i]==NULL)
3295 error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for "
3296 "strarr[%zu]", __func__, colwidth * sizeof *strarr[i], i);
3297 }
3298
3299 /* Read the column as a string. */
3300 fits_read_col(fptr, TSTRING, colnum, 1, 1, out->size, NULL, strrows->array,
3301 &anynul, &status);
3302 gal_fits_io_error(status, NULL);
3303
3304 /* Convert the strings to float. */
3305 for(i=0;i<numrows;++i)
3306 {
3307 /* Parse the string, if its not readable as a special number (like
3308 'inf' or 'nan', then just read it as a NaN. */
3309 tmp=strtod(strarr[i], &tailptr);
3310 if(tailptr==strarr[i]) tmp=NAN;
3311
3312 /* Write it into the output dataset. */
3313 if(out->type==GAL_TYPE_FLOAT32)
3314 ((float *)(out->array))[i]=tmp;
3315 else
3316 ((double *)(out->array))[i]=tmp;
3317 }
3318
3319 /* Clean up. */
3320 gal_data_free(strrows);
3321 }
3322
3323
3324
3325
3326
3327 /* Read the column indexs into a dataset. */
3328 gal_data_t *
gal_fits_tab_read(char * filename,char * hdu,size_t numrows,gal_data_t * allcols,gal_list_sizet_t * indexll,size_t minmapsize,int quietmmap)3329 gal_fits_tab_read(char *filename, char *hdu, size_t numrows,
3330 gal_data_t *allcols, gal_list_sizet_t *indexll,
3331 size_t minmapsize, int quietmmap)
3332 {
3333 size_t i=0;
3334 void *blank;
3335 char **strarr;
3336 fitsfile *fptr;
3337 gal_data_t *out=NULL;
3338 gal_list_sizet_t *ind;
3339 int isfloat, status=0, anynul=0, hdutype;
3340
3341 /* We actually do have columns to read. */
3342 if(numrows)
3343 {
3344 /* Open the FITS file */
3345 fptr=gal_fits_hdu_open_format(filename, hdu, 1);
3346
3347 /* See if its a Binary or ASCII table (necessary for floating point
3348 blank values). */
3349 if (fits_get_hdu_type(fptr, &hdutype, &status) )
3350 gal_fits_io_error(status, NULL);
3351
3352 /* Pop each index and read/store the array. */
3353 for(ind=indexll; ind!=NULL; ind=ind->next)
3354 {
3355 /* Allocate the necessary data structure (including the array)
3356 for this column. */
3357 gal_list_data_add_alloc(&out, NULL, allcols[ind->v].type, 1,
3358 &numrows, NULL, 0, minmapsize, quietmmap,
3359 allcols[ind->v].name, allcols[ind->v].unit,
3360 allcols[ind->v].comment);
3361
3362 /* For a string column, we need an allocated array for each element,
3363 even in binary values. This value should be stored in the
3364 disp_width element of the data structure, which is done
3365 automatically in 'gal_fits_table_info'. */
3366 if(out->type==GAL_TYPE_STRING)
3367 {
3368 strarr=out->array;
3369 for(i=0;i<numrows;++i)
3370 {
3371 errno=0;
3372 strarr[i]=calloc(allcols[ind->v].disp_width+1,
3373 sizeof *strarr[i]);
3374 if(strarr[i]==NULL)
3375 error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for "
3376 "strarr[%zu]", __func__,
3377 (allcols[ind->v].disp_width+1) * sizeof *strarr[i],
3378 i);
3379 }
3380 }
3381
3382 /* Allocate a blank value for the given type and read/store the
3383 column using CFITSIO. Note that for binary tables, we only
3384 need blank values for integer types. For binary floating point
3385 types, the FITS standard defines blanks as NaN (same as almost
3386 any other software like Gnuastro). However if a blank value is
3387 specified, CFITSIO will convert other special numbers like
3388 'inf' to NaN also. We want to be able to distringuish 'inf'
3389 and NaN here, so for floating point types in binary tables, we
3390 won't define any blank value. In ASCII tables, CFITSIO doesn't
3391 read the 'NAN' values (that it has written itself) unless we
3392 specify a blank pointer/value. */
3393 isfloat = ( out->type==GAL_TYPE_FLOAT32
3394 || out->type==GAL_TYPE_FLOAT64 );
3395 blank = ( ( hdutype==BINARY_TBL && isfloat )
3396 ? NULL
3397 : gal_blank_alloc_write(out->type) );
3398 fits_read_col(fptr, gal_fits_type_to_datatype(out->type), ind->v+1,
3399 1, 1, out->size, blank, out->array, &anynul, &status);
3400
3401 /* In the ASCII table format, CFITSIO might not be able to read
3402 'INF' or '-INF'. In this case, it will set status to 'BAD_C2D'
3403 or 'BAD_C2F'. So, we'll use our own parser for the column
3404 values. */
3405 if( hdutype==ASCII_TBL
3406 && isfloat
3407 && (status==BAD_C2D || status==BAD_C2F) )
3408 {
3409 fits_tab_read_ascii_float_special(filename, hdu, fptr, out,
3410 ind->v+1, numrows,
3411 minmapsize, quietmmap);
3412 status=0;
3413 }
3414
3415 /* Clean up and sanity check. */
3416 if(blank) free(blank);
3417 gal_fits_io_error(status, NULL);
3418 }
3419
3420 /* Close the FITS file */
3421 fits_close_file(fptr, &status);
3422 gal_fits_io_error(status, NULL);
3423 }
3424
3425 /* There are no rows to read ('numrows==NULL'). Make an empty-sized
3426 array. */
3427 else
3428 {
3429 /* We are setting a 1-element array to avoid any allocation
3430 errors. Then we are freeing the allocated spaces and correcting
3431 the sizes. */
3432 numrows=1;
3433 for(ind=indexll; ind!=NULL; ind=ind->next)
3434 {
3435 /* Do the allocation. */
3436 gal_list_data_add_alloc(&out, NULL, allcols[ind->v].type, 1,
3437 &numrows, NULL, 0, minmapsize, quietmmap,
3438 allcols[ind->v].name, allcols[ind->v].unit,
3439 allcols[ind->v].comment);
3440
3441 /* Correct the array and sizes. */
3442 out->size=0;
3443 out->array=NULL;
3444 out->dsize[0]=0;
3445 free(out->array);
3446 }
3447 }
3448
3449 /* Return the output. */
3450 return out;
3451 }
3452
3453
3454
3455
3456
3457 /* This function will allocate new copies for all elements to have the same
3458 length as the maximum length and set all trailing elements to '\0' for
3459 those that are shorter than the length. The return value is the
3460 allocated space. If the dataset is not a string, the returned value will
3461 be -1 (largest number of 'size_t'). */
3462 static size_t
fits_string_fixed_alloc_size(gal_data_t * data)3463 fits_string_fixed_alloc_size(gal_data_t *data)
3464 {
3465 size_t i, j, maxlen=0;
3466 char *tmp, **strarr=data->array;
3467
3468 /* Return 0 if the dataset is not a string. */
3469 if(data->type!=GAL_TYPE_STRING)
3470 return -1;
3471
3472 /* Get the maximum length. */
3473 for(i=0;i<data->size;++i)
3474 maxlen = strlen(strarr[i])>maxlen ? strlen(strarr[i]) : maxlen;
3475
3476 /* For all elements, check the length and if they aren't equal to maxlen,
3477 then allocate a maxlen sized array and put the values in. */
3478 for(i=0;i<data->size;++i)
3479 {
3480 /* Allocate (and clear) the space for the new string. We want it to
3481 be cleared, so when the strings are smaller, the rest of the space
3482 is filled with '\0' (ASCII for 0) values.*/
3483 errno=0;
3484 tmp=calloc(maxlen+1, sizeof *strarr[i]);
3485 if(tmp==NULL)
3486 error(EXIT_FAILURE, 0, "%s: %zu bytes for tmp", __func__,
3487 (maxlen+1)*sizeof *strarr[i]);
3488
3489 /* Put the old array into the newly allocated space. 'tmp' was
3490 cleared (all values set to '\0', so we don't need to set the final
3491 one explicity after the copy.*/
3492 for(j=0;strarr[i][j]!='\0';++j)
3493 tmp[j]=strarr[i][j];
3494
3495 /* Free the old array and put in the new one. */
3496 free(strarr[i]);
3497 strarr[i]=tmp;
3498 }
3499
3500 /* Return the allocated space. */
3501 return maxlen+1;
3502 }
3503
3504
3505
3506
3507
3508 static void
fits_table_prepare_arrays(gal_data_t * cols,size_t numcols,int tableformat,char *** outtform,char *** outttype,char *** outtunit)3509 fits_table_prepare_arrays(gal_data_t *cols, size_t numcols, int tableformat,
3510 char ***outtform, char ***outttype,
3511 char ***outtunit)
3512 {
3513 size_t i=0;
3514 gal_data_t *col;
3515 char fmt[2], lng[3];
3516 char *blank, **tform, **ttype, **tunit;
3517
3518
3519 /* Allocate the arrays to keep the 'tform' values */
3520 errno=0;
3521 tform=*outtform=malloc(numcols*sizeof *tform);
3522 if(tform==NULL)
3523 error(EXIT_FAILURE, 0, "%s: %zu bytes for tform", __func__,
3524 numcols*sizeof *tform);
3525 errno=0;
3526 ttype=*outttype=malloc(numcols*sizeof *ttype);
3527 if(ttype==NULL)
3528 error(EXIT_FAILURE, 0, "%s: %zu bytes for ttype", __func__,
3529 numcols*sizeof *ttype);
3530 errno=0;
3531 tunit=*outtunit=malloc(numcols*sizeof *tunit);
3532 if(tunit==NULL)
3533 error(EXIT_FAILURE, 0, "%s: %zu bytes for tunit", __func__,
3534 numcols*sizeof *tunit);
3535
3536
3537 /* Go over each column and fill in these arrays. */
3538 for(col=cols; col!=NULL; col=col->next)
3539 {
3540 /* Set the 'ttype' and 'tunit' values: */
3541 if( asprintf(&ttype[i], "%s", col->name ? col->name : "")<0 )
3542 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3543 if( asprintf(&tunit[i], "%s", col->unit ? col->unit : "")<0 )
3544 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3545
3546
3547 /* FITS's TFORM depends on the type of FITS table, so work
3548 differently. */
3549 switch(tableformat)
3550 {
3551 /* FITS ASCII table. */
3552 case GAL_TABLE_FORMAT_AFITS:
3553
3554 /* Fill the printing format. */
3555 gal_tableintern_col_print_info(col, GAL_TABLE_FORMAT_AFITS,
3556 fmt, lng);
3557
3558 /* We need to check if the blank value needs is larger than the
3559 expected width or not. Its initial width is set the output
3560 of the function above, but if the value is larger,
3561 'asprintf' (which is used) will make it wider. */
3562 blank = ( gal_blank_present(col, 0)
3563 ? gal_blank_as_string(col->type, col->disp_width)
3564 : NULL );
3565
3566 /* Adjust the width. */
3567 if(blank)
3568 {
3569 col->disp_width = ( strlen(blank) > col->disp_width
3570 ? strlen(blank) : col->disp_width );
3571 free(blank);
3572 }
3573
3574 /* Print the value to be used as TFORMn: */
3575 switch(col->type)
3576 {
3577 case GAL_TYPE_STRING:
3578 case GAL_TYPE_UINT8:
3579 case GAL_TYPE_INT8:
3580 case GAL_TYPE_UINT16:
3581 case GAL_TYPE_INT16:
3582 case GAL_TYPE_UINT32:
3583 case GAL_TYPE_INT32:
3584 case GAL_TYPE_UINT64:
3585 case GAL_TYPE_INT64:
3586 if( asprintf(&tform[i], "%c%d", fmt[0], col->disp_width)<0 )
3587 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3588 break;
3589
3590 case GAL_TYPE_FLOAT32:
3591 case GAL_TYPE_FLOAT64:
3592 if( asprintf(&tform[i], "%c%d.%d", fmt[0], col->disp_width,
3593 col->disp_precision)<0 )
3594 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3595 break;
3596
3597 default:
3598 error(EXIT_FAILURE, 0, "%s: col->type code %d not recognized",
3599 __func__, col->type);
3600 }
3601 break;
3602
3603
3604 /* FITS binary table. */
3605 case GAL_TABLE_FORMAT_BFITS:
3606
3607 /* If this is a string column, set all the strings to same size,
3608 then write the value of tform depending on the type. */
3609 col->disp_width=fits_string_fixed_alloc_size(col);
3610 fmt[0]=gal_fits_type_to_bin_tform(col->type);
3611 if( col->type==GAL_TYPE_STRING )
3612 {
3613 if( asprintf(&tform[i], "%d%c", col->disp_width, fmt[0])<0 )
3614 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3615 }
3616 else
3617 {
3618 if( asprintf(&tform[i], "%c", fmt[0])<0 )
3619 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3620 }
3621 break;
3622
3623 default:
3624 error(EXIT_FAILURE, 0, "%s: tableformat code %d not recognized",
3625 __func__, tableformat);
3626 }
3627
3628
3629 /* Increment the column index. */
3630 ++i;
3631 }
3632 }
3633
3634
3635
3636
3637
3638 /* Set the blank value to use for TNULL keyword (necessary in reading and
3639 writing).
3640
3641 The blank value must be the raw value within the FITS file (before
3642 applying 'TZERO' OR 'TSCAL'). Therefore, because the following integer
3643 types aren't native to the FITS standard, we need to correct TNULL for
3644 them after applying TZERO. For example for uin16_t, TZERO is 32768, so
3645 TNULL has to be 32767 (the maximum value of the signed integer with the
3646 same width). In this way, adding TZERO to the TNULL, will make it the
3647 actual NULL value we assume in Gnuastro for uint16_t (the largest
3648 possible number). */
3649 static void *
fits_blank_for_tnull(uint8_t type)3650 fits_blank_for_tnull(uint8_t type)
3651 {
3652 /* Allocate the default blank value. */
3653 void *blank=gal_blank_alloc_write(type);
3654
3655 /* For the non-native FITS type, correct the value. */
3656 switch(type)
3657 {
3658 case GAL_TYPE_INT8: gal_type_min(GAL_TYPE_UINT8, blank); break;
3659 case GAL_TYPE_UINT16: gal_type_max(GAL_TYPE_INT16, blank); break;
3660 case GAL_TYPE_UINT32: gal_type_max(GAL_TYPE_INT32, blank); break;
3661 case GAL_TYPE_UINT64: gal_type_max(GAL_TYPE_INT64, blank); break;
3662 }
3663
3664 /* Return the final allocated pointer. */
3665 return blank;
3666 }
3667
3668
3669
3670
3671
3672 /* Write the TNULLn keywords into the FITS file. Note that this depends on
3673 the type of the table: for an ASCII table, all the columns need it. For
3674 a binary table, only the non-floating point ones (even if they don't
3675 have NULL values) must have it. */
3676 static void
fits_write_tnull_tcomm(fitsfile * fptr,gal_data_t * col,int tableformat,size_t colnum,char * tform)3677 fits_write_tnull_tcomm(fitsfile *fptr, gal_data_t *col, int tableformat,
3678 size_t colnum, char *tform)
3679 {
3680 void *blank;
3681 int status=0;
3682 char *c, *keyname, *bcomment;
3683
3684 /* Write the NULL value */
3685 switch(tableformat)
3686 {
3687 case GAL_TABLE_FORMAT_AFITS:
3688
3689 /* Print the keyword and value. */
3690 if( asprintf(&keyname, "TNULL%zu", colnum)<0 )
3691 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3692 blank=gal_blank_as_string(col->type, col->disp_width);
3693
3694 /* When in exponential form ('tform' starting with 'E'), CFITSIO
3695 writes a NaN value as 'NAN', but when in floating point form
3696 ('tform' starting with 'F'), it writes it as 'nan'. So in the
3697 former case, we need to convert the string to upper case. */
3698 if(tform[0]=='E' || tform[0]=='e')
3699 for(c=blank; *c!='\0'; ++c) *c=toupper(*c);
3700
3701 /* Write in the header. */
3702 fits_write_key(fptr, TSTRING, keyname, blank,
3703 "blank value for this column", &status);
3704
3705 /* Clean up. */
3706 free(keyname);
3707 free(blank);
3708 break;
3709
3710 case GAL_TABLE_FORMAT_BFITS:
3711
3712 /* FITS binary tables don't accept NULL values for floating point or
3713 string columns. For floating point it must be NaN, and for strings
3714 it is a blank string. */
3715 if( col->type!=GAL_TYPE_FLOAT32
3716 && col->type!=GAL_TYPE_FLOAT64
3717 && col->type!=GAL_TYPE_STRING )
3718 {
3719 /* Allocate the blank value to write into the TNULL keyword. */
3720 blank=fits_blank_for_tnull(col->type);
3721
3722 /* Prepare the name and write the keyword. */
3723 if( asprintf(&keyname, "TNULL%zu", colnum)<0 )
3724 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3725 fits_write_key(fptr, gal_fits_type_to_datatype(col->type),
3726 keyname, blank, "blank value for this column",
3727 &status);
3728 gal_fits_io_error(status, NULL);
3729 free(keyname);
3730 free(blank);
3731 }
3732 break;
3733
3734 default:
3735 error(EXIT_FAILURE, 0, "%s: tableformat code %d not recognized",
3736 __func__, tableformat);
3737 }
3738
3739 /* Write the comments if there is any. */
3740 if(col->comment)
3741 {
3742 if( asprintf(&keyname, "TCOMM%zu", colnum)<0 )
3743 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3744 if( asprintf(&bcomment, "comment for field %zu", colnum)<0 )
3745 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
3746 fits_write_key(fptr, TSTRING, keyname, col->comment,
3747 bcomment, &status);
3748 gal_fits_io_error(status, NULL);
3749 free(keyname);
3750 free(bcomment);
3751 }
3752 }
3753
3754
3755
3756
3757
3758 /* Write the given columns (a linked list of 'gal_data_t') into a FITS
3759 table.*/
3760 void
gal_fits_tab_write(gal_data_t * cols,gal_list_str_t * comments,int tableformat,char * filename,char * extname,struct gal_fits_list_key_t ** keylist)3761 gal_fits_tab_write(gal_data_t *cols, gal_list_str_t *comments,
3762 int tableformat, char *filename, char *extname,
3763 struct gal_fits_list_key_t **keylist)
3764 {
3765 void *blank;
3766 fitsfile *fptr;
3767 gal_data_t *col;
3768 size_t i, numrows=-1;
3769 gal_list_str_t *strt;
3770 char **ttype, **tform, **tunit;
3771 int tbltype, numcols=0, status=0;
3772
3773
3774 /* Make sure all the input columns have the same number of elements */
3775 for(col=cols; col!=NULL; col=col->next)
3776 {
3777 if(numrows==-1) numrows=col->size;
3778 else if(col->size!=numrows)
3779 error(EXIT_FAILURE, 0, "%s: the number of records/rows in the input "
3780 "columns are not equal", __func__);
3781 ++numcols;
3782 }
3783
3784
3785 /* Open the FITS file for writing. */
3786 fptr=gal_fits_open_to_write(filename);
3787
3788
3789 /* Prepare necessary arrays and if integer type columns have blank
3790 values, write the TNULLn keywords into the FITS file. */
3791 fits_table_prepare_arrays(cols, numcols, tableformat,
3792 &tform, &ttype, &tunit);
3793
3794
3795 /* Make the FITS file pointer. Note that tableformat was checked in
3796 'fits_table_prepare_arrays'. */
3797 tbltype = tableformat==GAL_TABLE_FORMAT_AFITS ? ASCII_TBL : BINARY_TBL;
3798 fits_create_tbl(fptr, tbltype, numrows, numcols, ttype, tform, tunit,
3799 extname, &status);
3800 gal_fits_io_error(status, NULL);
3801
3802
3803 /* Write the columns into the file and also write the blank values into
3804 the header when necessary. */
3805 i=0;
3806 for(col=cols; col!=NULL; col=col->next)
3807 {
3808 /* Write the blank value into the header and return a pointer to
3809 it. Otherwise, */
3810 fits_write_tnull_tcomm(fptr, col, tableformat, i+1, tform[i]);
3811
3812 /* Set the blank pointer if its necessary, note that strings don't
3813 need a blank pointer in a FITS ASCII table. */
3814 blank = ( gal_blank_present(col, 0)
3815 ? fits_blank_for_tnull(col->type) : NULL );
3816 if(tableformat==GAL_TABLE_FORMAT_AFITS && col->type==GAL_TYPE_STRING)
3817 { if(blank) free(blank); blank=NULL; }
3818
3819 /* Manually remove the 'blank' pointer for standard FITS table
3820 numeric types (types below). We are doing this because as of
3821 CFITSIO 3.48, CFITSIO crashes for these types when we define our
3822 own blank values within this pointer, and such values actually
3823 exist in the column. This is the error message: "Null value for
3824 integer table column is not defined (FTPCLU)". Generally, for
3825 these native FITS table types 'blank' is redundant because our
3826 blank values are actually within their numerical data range. */
3827 switch(col->type)
3828 {
3829 case GAL_TYPE_UINT8:
3830 case GAL_TYPE_INT16:
3831 case GAL_TYPE_INT32:
3832 case GAL_TYPE_INT64:
3833 free(blank); blank=NULL;
3834 break;
3835 }
3836
3837 /* Write the full column into the table. */
3838 fits_write_colnull(fptr, gal_fits_type_to_datatype(col->type),
3839 i+1, 1, 1, col->size, col->array, blank, &status);
3840 gal_fits_io_error(status, NULL);
3841
3842 /* Clean up and Increment the column counter. */
3843 if(blank) free(blank);
3844 ++i;
3845 }
3846
3847 /* Write the requested keywords. */
3848 if(keylist)
3849 gal_fits_key_write_in_ptr(keylist, fptr);
3850
3851 /* Write the comments if there were any. */
3852 for(strt=comments; strt!=NULL; strt=strt->next)
3853 fits_write_comment(fptr, strt->v, &status);
3854
3855
3856 /* Write all the headers and the version information. */
3857 gal_fits_key_write_version_in_ptr(NULL, NULL, fptr);
3858
3859
3860 /* Clean up and close the FITS file. Note that each element in the
3861 'ttype' and 'tunit' arrays just points to the respective string in the
3862 column data structure, the space for each element of the array wasn't
3863 allocated.*/
3864 for(i=0;i<numcols;++i)
3865 {
3866 free(tform[i]);
3867 free(ttype[i]);
3868 free(tunit[i]);
3869 }
3870 free(tform);
3871 free(ttype);
3872 free(tunit);
3873 fits_close_file(fptr, &status);
3874 gal_fits_io_error(status, NULL);
3875 }
3876