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