1 /*********************************************************************
2 Fits - View and manipulate FITS extensions and/or headers.
3 Fits 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 <errno.h>
26 #include <error.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdlib.h>
30 
31 #include <gnuastro/wcs.h>
32 #include <gnuastro/fits.h>
33 #include <gnuastro/pointer.h>
34 #include <gnuastro-internal/timing.h>
35 
36 #include <gnuastro-internal/checkset.h>
37 
38 #include "main.h"
39 
40 #include "fits.h"
41 
42 
43 
44 
45 
46 
47 
48 
49 /***********************************************************************/
50 /******************           Preparations          ********************/
51 /***********************************************************************/
52 static void
keywords_open(struct fitsparams * p,fitsfile ** fptr,int iomode)53 keywords_open(struct fitsparams *p, fitsfile **fptr, int iomode)
54 {
55   if(*fptr==NULL)
56     *fptr=gal_fits_hdu_open(p->input->v, p->cp.hdu, iomode);
57 }
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 /***********************************************************************/
79 /******************        File manipulation        ********************/
80 /***********************************************************************/
81 static void
keywords_rename_keys(struct fitsparams * p,fitsfile ** fptr,int * r)82 keywords_rename_keys(struct fitsparams *p, fitsfile **fptr, int *r)
83 {
84   int status=0;
85   char *copy, *str, *from, *to;
86 
87   /* Set the FITS file pointer. */
88   keywords_open(p, fptr, READWRITE);
89 
90   /* Tokenize the */
91   while(p->rename!=NULL)
92     {
93       /* Pop out the top element. */
94       str=gal_list_str_pop(&p->rename);
95 
96       /* Take a copy of the input string for error reporting, because
97          'strtok' will write into the array. */
98       gal_checkset_allocate_copy(str, &copy);
99 
100       /* Tokenize the input. */
101       from = strtok(str,  ", ");
102       to   = strtok(NULL, ", ");
103 
104       /* Make sure both elements were read. */
105       if(from==NULL || to==NULL)
106         error(EXIT_FAILURE, 0, "'%s' could not be tokenized in order to "
107               "complete rename. There should be a space character "
108               "or a comma (,) between the two keyword names. If you have "
109               "used the space character, be sure to enclose the value to "
110               "the '--rename' option in double quotation marks", copy);
111 
112       /* Rename the keyword */
113       fits_modify_name(*fptr, from, to, &status);
114       if(status) *r=fits_has_error(p, FITS_ACTION_RENAME, from, status);
115       status=0;
116 
117       /* Clean up the user's input string. Note that 'strtok' just changes
118          characters within the allocated string, no extra allocation is
119          done. */
120       free(str);
121       free(copy);
122     }
123 }
124 
125 
126 
127 
128 
129 /* Special write options don't have any value and the value has to be found
130    within the script. */
131 static int
keywords_write_set_value(struct fitsparams * p,fitsfile ** fptr,gal_fits_list_key_t * keyll)132 keywords_write_set_value(struct fitsparams *p, fitsfile **fptr,
133                          gal_fits_list_key_t *keyll)
134 {
135   int status=0;
136 
137   if( !strcasecmp(keyll->keyname,"checksum")
138       || !strcasecmp(keyll->keyname,"datasum") )
139     {
140       /* If a value is given, then just write what the user gave. */
141       if( keyll->value )
142         return 1;
143       else
144         {
145           /* Calculate and write the 'CHECKSUM' and 'DATASUM' keywords. */
146           if( fits_write_chksum(*fptr, &status) )
147             gal_fits_io_error(status, NULL);
148 
149           /* If the user just wanted datasum, remove the checksum
150              keyword. */
151           if( !strcasecmp(keyll->keyname,"datasum") )
152             if( fits_delete_key(*fptr, "CHECKSUM", &status) )
153               gal_fits_io_error(status, NULL);
154 
155           /* Inform the caller that everything is done. */
156           return 0;
157         }
158     }
159   else if( keyll->keyname[0]=='/' )
160     {
161       gal_fits_key_write_title_in_ptr(keyll->value, *fptr);
162       return 0;
163     }
164   else
165     error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to "
166           "fix the problem. The 'keyname' value '%s' is not "
167           "recognized as one with no value", __func__,
168           PACKAGE_BUGREPORT, keyll->keyname);
169 
170   /* Function should not reach here. */
171   error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to fix this "
172         "problem. Control should not reach the end of this function",
173         __func__, PACKAGE_BUGREPORT);
174   return 0;
175 }
176 
177 
178 
179 
180 
181 static void
keywords_write_update(struct fitsparams * p,fitsfile ** fptr,gal_fits_list_key_t * keyll,int u1w2)182 keywords_write_update(struct fitsparams *p, fitsfile **fptr,
183                       gal_fits_list_key_t *keyll, int u1w2)
184 {
185   int status=0, continuewriting=0;
186   gal_fits_list_key_t *tmp;
187 
188   /* Open the FITS file if it hasn't been opened yet. */
189   keywords_open(p, fptr, READWRITE);
190 
191   /* Go through each key and write it in the FITS file. */
192   while(keyll!=NULL)
193     {
194       /* Deal with special keywords. */
195       continuewriting=1;
196       if( keyll->value==NULL || keyll->keyname[0]=='/' )
197         continuewriting=keywords_write_set_value(p, fptr, keyll);
198 
199       /* Write the information: */
200       if(continuewriting)
201         {
202           if(u1w2==1)
203             {
204               if(keyll->value)
205                 {
206                   if( fits_update_key(*fptr,
207                                       gal_fits_type_to_datatype(keyll->type),
208                                       keyll->keyname, keyll->value,
209                                       keyll->comment, &status) )
210                     gal_fits_io_error(status, NULL);
211                 }
212               else
213                 {
214                   if(fits_write_key_null(*fptr, keyll->keyname,
215                                          keyll->comment, &status))
216                     gal_fits_io_error(status, NULL);
217                 }
218             }
219           else if (u1w2==2)
220             {
221               if(keyll->value)
222                 {
223                   if( fits_write_key(*fptr,
224                                      gal_fits_type_to_datatype(keyll->type),
225                                      keyll->keyname, keyll->value,
226                                      keyll->comment, &status) )
227                     gal_fits_io_error(status, NULL);
228                 }
229               else
230                 {
231                   if(fits_write_key_null(*fptr, keyll->keyname,
232                                          keyll->comment, &status))
233                     gal_fits_io_error(status, NULL);
234                 }
235               if(keyll->unit
236                  && fits_write_key_unit(*fptr, keyll->keyname, keyll->unit,
237                                         &status) )
238                 gal_fits_io_error(status, NULL);
239             }
240           else
241             error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at '%s' so "
242                   "we can fix this problem. The value %d is not valid for "
243                   "'u1w2'", __func__, PACKAGE_BUGREPORT, u1w2);
244 
245           /* Add the unit (if one was given). */
246           if(keyll->unit
247              && fits_write_key_unit(*fptr, keyll->keyname, keyll->unit,
248                                     &status) )
249             gal_fits_io_error(status, NULL);
250         }
251 
252       /* Free the allocated spaces if necessary: */
253       if(keyll->vfree) free(keyll->value);
254       if(keyll->kfree) free(keyll->keyname);
255       if(keyll->cfree) free(keyll->comment);
256 
257       /* Keep the pointer to the next keyword and free the allocated
258          space for this keyword.*/
259       tmp=keyll->next;
260       free(keyll);
261       keyll=tmp;
262     }
263 }
264 
265 
266 
267 
268 
269 static void
keywords_print_all_keys(struct fitsparams * p,fitsfile ** fptr)270 keywords_print_all_keys(struct fitsparams *p, fitsfile **fptr)
271 {
272   size_t i=0;
273   int nkeys, status=0;
274   char *fullheader, *c, *cf;
275 
276   /* Convert the header into a contiguous string. */
277   if( fits_hdr2str(*fptr, 0, NULL, 0, &fullheader, &nkeys, &status) )
278     gal_fits_io_error(status, NULL);
279 
280   /* FLEN_CARD supposes that the NULL string character is in the
281      end of each keyword header card. In fits_hdr2str, the NULL
282      characters are removed and so the maximum length is one
283      less. */
284   cf=(c=fullheader)+nkeys*(FLEN_CARD-1);
285   do
286     {
287       if(i && i%(FLEN_CARD-1)==0)
288         putc('\n', stdout);
289       putc(*c++, stdout);
290       ++i;
291     }
292   while(c<cf);
293   printf("\n");
294 
295   if (fits_free_memory(fullheader, &status) )
296     gal_fits_io_error(status, "problem in header.c for freeing "
297                       "the memory used to keep all the headers");
298 }
299 
300 
301 
302 
303 
304 static void
keywords_list_key_names(struct fitsparams * p,fitsfile * fptr)305 keywords_list_key_names(struct fitsparams *p, fitsfile *fptr)
306 {
307   size_t i=0;
308   int status=0;
309   char keyname[FLEN_CARD], value[FLEN_CARD], *comment=NULL;
310 
311   /* Initialize 'keyname' so the first one (that is blank) isn't
312      printed. */
313   keyname[0]='\0';
314 
315   /* Go through all the keywords until you reach 'END'. */
316   while( strcmp(keyname, "END") )
317       {
318         /* Print the most recent keyword: this is placed before reading the
319            keyword because we want to stop upon reading 'END'. */
320         if( strlen(keyname) ) printf("%s\n", keyname);
321 
322         /* Read the next keyword. */
323         fits_read_keyn(fptr, i++, keyname, value, comment, &status);
324       }
325 }
326 
327 
328 
329 
330 
331 static int
keywords_verify(struct fitsparams * p,fitsfile ** fptr)332 keywords_verify(struct fitsparams *p, fitsfile **fptr)
333 {
334   int dataok, hduok, status=0;
335 
336   /* Ask CFITSIO to verify the two keywords. */
337   if( fits_verify_chksum(*fptr, &dataok, &hduok, &status) )
338     gal_fits_io_error(status, NULL);
339 
340   /* Print some introduction: */
341   if(!p->cp.quiet)
342     printf("%s\n"
343            "Checking integrity of %s (hdu %s)\n"
344            "%s"
345            "--------\n"
346            "Basic info (remove all extra info with '--quiet'):\n"
347            "    - DATASUM: verifies only the data (not keywords).\n"
348            "    - CHECKSUM: verifies data and keywords.\n"
349            "They can be added-to/updated-in an extension/HDU with:\n"
350            "    $ astfits %s -h%s --write=checksum\n"
351            "--------\n", PROGRAM_STRING, p->input->v, p->cp.hdu,
352            ctime(&p->rawtime), p->input->v, p->cp.hdu);
353 
354   /* Print the verification result. */
355   printf("DATASUM:  %s\n",
356          dataok==1 ? "Verified" : (dataok==0 ? "NOT-PRESENT" : "INCORRECT"));
357   printf("CHECKSUM: %s\n",
358          hduok==1  ? "Verified" : (hduok==0  ? "NOT-PRESENT" : "INCORRECT"));
359 
360   /* Return failure if any of the keywords are not verified. */
361   return (dataok==-1 || hduok==-1) ? EXIT_FAILURE : EXIT_SUCCESS;
362 }
363 
364 
365 
366 
367 
368 
369 static void
keywords_copykeys(struct fitsparams * p,char * inkeys,size_t numinkeys)370 keywords_copykeys(struct fitsparams *p, char *inkeys, size_t numinkeys)
371 {
372   size_t i;
373   int status=0;
374   long initial;
375   fitsfile *fptr;
376 
377   /* Initial sanity check. Since 'numinkeys' includes 'END' (counting from
378      1, as we do here), the first keyword must not be larger OR EQUAL to
379      'numinkeys'. */
380   if(p->copykeysrange[0]>=numinkeys)
381     error(EXIT_FAILURE, 0, "%s (hdu %s): first keyword number give to "
382           "'--copykeys' (%ld) is larger than the number of keywords in this "
383           "header (%zu, including the 'END' keyword)", p->input->v, p->cp.hdu,
384           p->copykeysrange[0], numinkeys);
385 
386   /* If the user wanted to count from the end (by giving a negative value),
387      then do that. */
388   if(p->copykeysrange[1]<0)
389     {
390       /* Set the last keyword requested. */
391       initial=p->copykeysrange[1];
392       p->copykeysrange[1] += numinkeys;
393 
394       /* Sanity check. */
395       if(p->copykeysrange[0]>=p->copykeysrange[1])
396         error(EXIT_FAILURE, 0, "%s (hdu %s): the last keyword given to "
397               "'--copykeys' (%ld, or %ld after counting from the bottom) "
398               "is earlier than the first (%ld)", p->input->v, p->cp.hdu,
399               initial, p->copykeysrange[1], p->copykeysrange[0]);
400     }
401 
402   /* Final sanity check (on range limit). */
403   if(p->copykeysrange[1]>=numinkeys)
404     error(EXIT_FAILURE, 0, "%s (hdu %s): second keyword number give to "
405           "'--copykeys' (%ld) is larger than the number of keywords in this "
406           "header (%zu, including the 'END' keyword)", p->input->v, p->cp.hdu,
407           p->copykeysrange[1], numinkeys);
408 
409 
410   /* Open the output HDU. */
411   fptr=gal_fits_hdu_open(p->cp.output, p->outhdu, READWRITE);
412 
413 
414   /* Copy the requested headers into the output. */
415   for(i=p->copykeysrange[0]-1; i<=p->copykeysrange[1]-1; ++i)
416     if( fits_write_record(fptr, &inkeys[i*80], &status ) )
417       gal_fits_io_error(status, NULL);
418 
419   /* Close the output FITS file. */
420   status=0;
421   if(fits_close_file(fptr, &status))
422     gal_fits_io_error(status, NULL);
423 }
424 
425 
426 
427 
428 
429 static void
keywords_date_to_seconds(struct fitsparams * p,fitsfile * fptr)430 keywords_date_to_seconds(struct fitsparams *p, fitsfile *fptr)
431 {
432   int status=0;
433   double subsec;
434   size_t seconds;
435   char *subsecstr=NULL;
436   char fitsdate[FLEN_KEYWORD];
437 
438   /* Read the requested FITS keyword. */
439   if( fits_read_key(fptr, TSTRING, p->datetosec, &fitsdate, NULL, &status) )
440     gal_fits_io_error(status, NULL);
441 
442   /* Return the number of seconds (and subseconds).*/
443   seconds=gal_fits_key_date_to_seconds(fitsdate, &subsecstr, &subsec);
444   if(seconds==GAL_BLANK_SIZE_T)
445     error(EXIT_FAILURE, 0, "the time string couldn't be interpretted");
446 
447   /* Print the result (for the sub-seconds, print everything after the */
448   if( !p->cp.quiet )
449     {
450       printf("%s (hdu %s), key '%s': %s\n", p->input->v, p->cp.hdu,
451              p->datetosec, fitsdate);
452       printf("Seconds since 1970/01/01 (00:00:00): %zu%s\n\n", seconds,
453              subsecstr?subsecstr:"");
454       printf("(To suppress verbose output, run with '-q')\n");
455     }
456   else
457     printf("%zu%s\n", seconds, subsecstr?subsecstr:"");
458 
459   /* Clean up. */
460   if(subsecstr) free(subsecstr);
461 }
462 
463 
464 
465 
466 
467 static void
keywords_wcs_convert(struct fitsparams * p)468 keywords_wcs_convert(struct fitsparams *p)
469 {
470   int nwcs;
471   size_t ndim, *insize;
472   char *suffix, *output;
473   gal_data_t *data=NULL;
474   struct wcsprm *inwcs, *outwcs=NULL;
475   size_t *dsize, defaultsize[2]={2000,2000};
476 
477   /* If the extension has any data, read it, otherwise just make an empty
478      array. */
479   if(gal_fits_hdu_format(p->input->v, p->cp.hdu)==IMAGE_HDU)
480     {
481       /* Read the size of the dataset (we don't need the actual size!). */
482       insize=gal_fits_img_info_dim(p->input->v, p->cp.hdu, &ndim);
483       free(insize);
484 
485       /* If the number of dimensions is two, then read the dataset,
486          otherwise, ignore it. */
487       if(ndim==2)
488         data=gal_fits_img_read(p->input->v, p->cp.hdu, p->cp.minmapsize,
489                                p->cp.quietmmap);
490     }
491 
492   /* Read the input's WCS and make sure one exists. */
493   inwcs=gal_wcs_read(p->input->v, p->cp.hdu, p->cp.wcslinearmatrix,
494                      0, 0, &nwcs);
495   if(inwcs==NULL)
496     error(EXIT_FAILURE, 0, "%s (hdu %s): doesn't have any WCS structure "
497           "for converting its coordinate system or distortion",
498           p->input->v, p->cp.hdu);
499 
500   /* In case there is no dataset and the conversion is between TPV to SIP,
501      we need to set a default size and use that for the conversion, but we
502      also need to warn the user. */
503   if(p->wcsdistortion && data==NULL)
504     {
505       if( !p->cp.quiet
506           && gal_wcs_distortion_identify(inwcs)==GAL_WCS_DISTORTION_TPV
507           && p->distortionid==GAL_WCS_DISTORTION_SIP )
508         error(0, 0, "no data associated with WCS for distortion "
509               "conversion.\n\n"
510               "The requested conversion can't be done analytically, so a "
511               "solution has to be found by fitting the parameters over a "
512               "grid of pixels. We will use a default grid of %zux%zu pixels "
513               "and will proceed with the conversion. But it would be more "
514               "accurate if it is the size of the image that this WCS is "
515               "associated with",
516               defaultsize[1], defaultsize[0]);
517       dsize=defaultsize;
518     }
519   else dsize=data->dsize;
520 
521   /* Do the conversion. */
522   if(p->wcscoordsys)
523     outwcs=gal_wcs_coordsys_convert(inwcs, p->coordsysid);
524   else if(p->wcsdistortion)
525     outwcs=gal_wcs_distortion_convert(inwcs, p->distortionid, dsize);
526   else
527     error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to fix "
528           "the problem. The requested mode for this function is not "
529           "recognized", __func__, PACKAGE_BUGREPORT);
530 
531   /* Set the output filename. */
532   if(p->cp.output)
533     output=p->cp.output;
534   else
535     {
536       if( asprintf(&suffix, "-%s.fits",
537                    p->wcsdistortion ? p->wcsdistortion : p->wcscoordsys)<0 )
538         error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
539       output=gal_checkset_automatic_output(&p->cp, p->input->v, suffix);
540     }
541   gal_checkset_writable_remove(output, 0, p->cp.dontdelete);
542 
543   /* Write the output file. */
544   if(data)
545     {
546       /* Add the output WCS to the dataset and write it. */
547       data->wcs=outwcs;
548       gal_fits_img_write(data, output, NULL, PROGRAM_NAME);
549 
550       /* Clean up, but remove the pointer first (so it doesn't free it
551          here). */
552       data->wcs=NULL;
553       gal_data_free(data);
554     }
555   else
556     gal_wcs_write(outwcs, output, p->wcsdistortion, NULL, PROGRAM_NAME);
557 
558   /* Clean up. */
559   wcsfree(inwcs);
560   wcsfree(outwcs);
561   if(output!=p->cp.output) free(output);
562 }
563 
564 
565 
566 
567 
568 static void
keywords_value_in_output_copy(gal_data_t * write,gal_data_t * key,size_t in_counter)569 keywords_value_in_output_copy(gal_data_t *write, gal_data_t *key,
570                               size_t in_counter)
571 {
572   char **strarrk, **strarrw;
573 
574   /* Small sanity check. */
575   if(write->type != key->type)
576     error(EXIT_FAILURE, 0, "%s: the input datasets must have "
577           "the same data type. The 'write' and 'key' arguments "
578           "are respectively '%s' and '%s'", __func__,
579           gal_type_name(write->type, 1),
580           gal_type_name(key->type, 1));
581 
582   /* Copy the value. */
583   if(key->type==GAL_TYPE_STRING)
584     {
585       strarrk=key->array;
586       strarrw=write->array;
587       strarrw[ in_counter ] = strarrk[0];
588       strarrk[0]=NULL;
589     }
590   else
591     memcpy(gal_pointer_increment(write->array, in_counter,
592                                  write->type),
593            key->array, gal_type_sizeof(write->type));
594 }
595 
596 
597 
598 
599 
600 /* Write the value in the first row. The first row is unique here: if there
601    is only one input dataset, the dataset name will not be in the
602    output. But when there is more than one dataset, we include a column for
603    the name of the dataset. */
604 static gal_data_t *
keywords_value_in_output_first(struct fitsparams * p,gal_data_t * topout,char * filename,gal_data_t * keysll,size_t ninput)605 keywords_value_in_output_first(struct fitsparams *p, gal_data_t *topout,
606                                char *filename, gal_data_t *keysll,
607                                size_t ninput)
608 {
609   char **strarr;
610   gal_data_t *out=NULL;
611   gal_data_t *write, *key;
612   size_t in_counter=0; /* This function is only for the first row. */
613 
614   /* If a name column is necessary. */
615   if(topout)
616     {
617       /* Small sanity check. */
618       if(topout->next)
619         error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to "
620               "fix the problem. The 'next' pointer of 'topout' should "
621               "be NULL", __func__, PACKAGE_BUGREPORT);
622 
623       /* The size of the output should be the same as 'ninput'. */
624       if(topout->size!=ninput)
625         error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to "
626               "fix the problem. The number of elements in 'topout' "
627               "(%zu) is different from 'ninput' (%zu)", __func__,
628               PACKAGE_BUGREPORT, out->size, ninput);
629 
630       /* Write the filename. */
631       strarr=topout->array;
632       gal_checkset_allocate_copy(filename, &strarr[in_counter]);
633     }
634 
635   /* Add the new columns into the raw output (only keyword values). */
636   for(key=keysll; key!=NULL; key=key->next)
637     {
638       /* If the keyword couldn't be read for any reason then 'key->status'
639          will be non-zero. In this case, return a string type and put a
640          blank string value. */
641       if( key->status )
642         {
643           key->type=GAL_TYPE_STRING;
644           if(p->cp.quiet==0)
645             error(EXIT_SUCCESS, 0, "%s (hdu %s): does not contain a "
646                   "keyword '%s'", filename, p->cp.hdu, key->name);
647         }
648 
649       /* Allocate the full column for this key and add it to the end of
650          the existing output list of columns: IMPORTANT NOTE: it is
651          necessary to initialize the values because we may need to
652          change the types before fully writing values within it. */
653       write=gal_data_alloc(NULL, key->type, 1, &ninput, NULL,
654                            1, p->cp.minmapsize, p->cp.quietmmap,
655                            key->name, key->unit, key->comment);
656 
657       /* Copy the value of this key into the output. Note that for strings,
658          the arrays are initialized to NULL. */
659       if( key->status )
660         {
661           strarr=write->array;
662           gal_checkset_allocate_copy(GAL_BLANK_STRING,
663                                      &strarr[in_counter]);
664         }
665       else
666         keywords_value_in_output_copy(write, key, in_counter);
667 
668       /* Put the allocated column into the output list. */
669       gal_list_data_add(&out, write);
670     }
671 
672   /* Reverse the list (to be the same order as the user's request). */
673   gal_list_data_reverse(&out);
674 
675   /* If a first row (containing the filename) is given, then add the
676      allocated datasets to its end */
677   if(topout) { topout->next=out; out=topout; }
678 
679   /* Return the output. */
680   return out;
681 }
682 
683 
684 
685 
686 
687 static void
keywords_value_in_output_rest_replace(gal_data_t * list,gal_data_t * old,gal_data_t * new)688 keywords_value_in_output_rest_replace(gal_data_t *list, gal_data_t *old,
689                                       gal_data_t *new)
690 {
691   gal_data_t *parse;
692   new->next=old->next;
693   for(parse=list; parse!=NULL; parse=parse->next)
694     if(parse->next==old)
695       {
696         gal_data_free(old);
697         parse->next=new;
698         break;
699       }
700 }
701 
702 
703 
704 
705 
706 /* This function is for the case that we have more than one row. In this
707    case, we always want the input file's name to be printed. */
708 static void
keywords_value_in_output_rest(struct fitsparams * p,gal_data_t * out,char * filename,gal_data_t * keysll,size_t in_counter)709 keywords_value_in_output_rest(struct fitsparams *p, gal_data_t *out,
710                               char *filename, gal_data_t *keysll,
711                               size_t in_counter)
712 {
713   int goodtype;
714   char **strarr;
715   gal_data_t *write, *key;
716   gal_data_t *goodkey, *goodwrite;
717 
718   /* Write the file name in the first column. */
719   strarr=out->array;
720   gal_checkset_allocate_copy(filename, &strarr[in_counter]);
721 
722   /* Go over all the keys are write them in. */
723   write=out;
724   for(key=keysll; key!=NULL; key=key->next)
725     {
726       /* Increment the write column also. */
727       write=write->next;
728 
729       /* If the status is non-zero then the keyword couldn't be read. In
730          this case, put a blank value in this row. */
731       if(key->status)
732         {
733           gal_blank_write(gal_pointer_increment(write->array, in_counter,
734                                                 write->type),
735                           write->type);
736           if(p->cp.quiet==0)
737             error(EXIT_SUCCESS, 0, "%s (hdu %s): does not contain a "
738                   "keyword '%s'", filename, p->cp.hdu, key->name);
739           continue;
740         }
741 
742       /* This key is good and the type is string (which is the type for a
743          key that doesn't exist in the previous file(s)). In this case,
744          check if all the previous rows are blank. If they are all blank
745          then this keyword didn't exist in any of the previous files and
746          this is the first one that has the keyword. So change the type of
747          the column to the final type. */
748       else
749         {
750           if( write->type==GAL_TYPE_STRING
751               && write->type!=key->type
752               && gal_blank_number(write, 1)==write->size )
753             {
754               goodwrite=gal_data_alloc(NULL, key->type, 1, out->dsize,
755                                        NULL, 0, p->cp.minmapsize,
756                                        p->cp.quietmmap, key->name,
757                                        key->unit, key->comment);
758               gal_blank_initialize(goodwrite);
759               keywords_value_in_output_rest_replace(out, write,
760                                                     goodwrite);
761               write=goodwrite;
762             }
763         }
764 
765       /* If the previous files didn't have metadata for this keyword but
766          this file does, use the metadata here. */
767       if(write->unit==NULL && key->unit)
768         { write->unit=key->unit; key->unit=NULL; }
769       if(write->comment==NULL && key->comment)
770         { write->comment=key->comment; key->comment=NULL; }
771 
772       /* If the column types are the same, then put them in. */
773       if(key->type==write->type)
774         keywords_value_in_output_copy(write, key, in_counter);
775       else
776         {
777           /* Find the most inclusive type. */
778           goodtype=gal_type_out(key->type, write->type);
779 
780           /* Convert each of the two into the same type. */
781           goodkey = ( key->type==goodtype
782                       ? key
783                       : gal_data_copy_to_new_type(key, goodtype) );
784           goodwrite = ( write->type==goodtype
785                         ? write
786                         : gal_data_copy_to_new_type(write, goodtype) );
787 
788           /* Copy the row into the output. */
789           keywords_value_in_output_copy(goodwrite, goodkey, in_counter);
790 
791           /* If the "good" writing dataset has been changed, then
792              replace it in the output (correct its 'next' pointer, and
793              set the previous column to point to it. */
794           if(goodwrite!=write)
795             {
796               keywords_value_in_output_rest_replace(out, write,
797                                                     goodwrite);
798               write=goodwrite;
799             }
800 
801           /* If a different key has been used, clean it. */
802           if(goodkey!=key) gal_data_free(goodkey);
803         }
804     }
805 }
806 
807 
808 
809 
810 
811 static void
keywords_value(struct fitsparams * p)812 keywords_value(struct fitsparams *p)
813 {
814   int status;
815   fitsfile *fptr=NULL;
816   gal_list_str_t *input, *tmp;
817   size_t i, ii=0, ninput, nkeys;
818   gal_data_t *out=NULL, *keysll=NULL;
819 
820   /* Count how many inputs there are, and allocate the first column with
821      the name. */
822   ninput=gal_list_str_number(p->input);
823   if(ninput>1 || p->cp.quiet==0)
824     out=gal_data_alloc(NULL, GAL_TYPE_STRING, 1, &ninput, NULL, 0,
825                        p->cp.minmapsize, p->cp.quietmmap, "FILENAME",
826                        "name", "Name of input file.");
827 
828   /* Allocate the structure to host the desired keywords read from each
829      FITS file and their values. But first convert the list of strings (for
830      keyword names), (where each string can be a comma-separated list) into
831      a list with a single value per string. */
832   gal_options_merge_list_of_csv(&p->keyvalue);
833   nkeys=gal_list_str_number(p->keyvalue);
834 
835   /* Parse each input file, read the keywords and put them in the output
836      list. */
837   for(input=p->input; input!=NULL; input=input->next)
838     {
839       /* Open the input FITS file. */
840       fptr=gal_fits_hdu_open(input->v, p->cp.hdu, READONLY);
841 
842       /* Allocate the array to keep the keys. */
843       i=0;
844       keysll=gal_data_array_calloc(nkeys);
845       for(tmp=p->keyvalue; tmp!=NULL; tmp=tmp->next)
846         {
847           if(tmp->next) keysll[i].next=&keysll[i+1];
848           keysll[i].name=tmp->v;
849           ++i;
850         }
851 
852       /* Read the keys. Note that we only need the comments and units if
853          '--colinfoinstdout' is called. */
854       gal_fits_key_read_from_ptr(fptr, keysll, p->colinfoinstdout,
855                                  p->colinfoinstdout);
856 
857       /* Close the input FITS file. */
858       status=0;
859       if(fits_close_file(fptr, &status))
860         gal_fits_io_error(status, NULL);
861 
862       /* Write the values of this column into the final output. */
863       if(ii==0)
864         {
865           ++ii;
866           out=keywords_value_in_output_first(p, out, input->v,
867                                              keysll, ninput);
868         }
869       else
870         keywords_value_in_output_rest(p, out, input->v, keysll,
871                                       ii++);
872 
873       /* Clean up. */
874       for(i=0;i<nkeys;++i) keysll[i].name=NULL;
875       gal_data_array_free(keysll, nkeys, 1);
876     }
877 
878   /* Write the values. */
879   gal_checkset_writable_remove(p->cp.output, 0, p->cp.dontdelete);
880   gal_table_write(out, NULL, NULL, p->cp.tableformat,
881                   p->cp.output, "KEY-VALUES", p->colinfoinstdout);
882 
883   /* Clean up. */
884   gal_list_str_free(p->keyvalue, 0);
885 }
886 
887 
888 
889 
890 
891 
892 
893 
894 
895 
896 
897 
898 
899 
900 
901 
902 
903 
904 
905 
906 /***********************************************************************/
907 /******************           Main function         ********************/
908 /***********************************************************************/
909 /* NOTE ON CALLING keywords_open FOR EACH OPERATION:
910 
911    'keywords_open' is being called individually for each separate operation
912    because the necessary permissions differ: when the user only wants to
913    read keywords, they don't necessarily need write permissions. So if they
914    haven't asked for any writing/editing operation, we shouldn't open in
915    write-mode. Because the user might not have the permissions to write and
916    they might not want to write. 'keywords_open' will only open the file
917    once (if the pointer is already allocated, it won't do anything). */
918 int
keywords(struct fitsparams * p)919 keywords(struct fitsparams *p)
920 {
921   char *inkeys=NULL;
922   int r=EXIT_SUCCESS;
923   fitsfile *fptr=NULL;
924   gal_list_str_t *tstll;
925   int status=0, numinkeys;
926 
927   /* Print the requested keywords. Note that this option isn't called with
928      the rest. It is independent of them. */
929   if(p->keyvalue)
930     keywords_value(p);
931 
932   /* Delete the requested keywords. */
933   if(p->delete)
934     {
935       /* Open the FITS file. */
936       keywords_open(p, &fptr, READWRITE);
937 
938       /* Go over all the keywords to delete. */
939       for(tstll=p->delete; tstll!=NULL; tstll=tstll->next)
940         {
941           fits_delete_key(fptr, tstll->v, &status);
942           if(status)
943             r=fits_has_error(p, FITS_ACTION_DELETE, tstll->v, status);
944           status=0;
945         }
946     }
947 
948 
949   /* Rename the requested keywords. */
950   if(p->rename)
951     {
952       keywords_open(p, &fptr, READWRITE);
953       keywords_rename_keys(p, &fptr, &r);
954     }
955 
956 
957   /* Update the requested keywords. */
958   if(p->update)
959     {
960       keywords_open(p, &fptr, READWRITE);
961       keywords_write_update(p, &fptr, p->update_keys, 1);
962     }
963 
964 
965   /* Write the requested keywords. */
966   if(p->write)
967     {
968       keywords_open(p, &fptr, READWRITE);
969       keywords_write_update(p, &fptr, p->write_keys, 2);
970     }
971 
972 
973   /* Put in any full line of keywords as-is. */
974   if(p->asis)
975     {
976       keywords_open(p, &fptr, READWRITE);
977       for(tstll=p->asis; tstll!=NULL; tstll=tstll->next)
978         {
979           fits_write_record(fptr, tstll->v, &status);
980           if(status) r=fits_has_error(p, FITS_ACTION_WRITE, tstll->v, status);
981           status=0;
982         }
983     }
984 
985 
986   /* Add the history keyword(s). */
987   if(p->history)
988     {
989       keywords_open(p, &fptr, READWRITE);
990       for(tstll=p->history; tstll!=NULL; tstll=tstll->next)
991         {
992           fits_write_history(fptr, tstll->v, &status);
993           if(status)
994             r=fits_has_error(p, FITS_ACTION_WRITE, "HISTORY", status);
995           status=0;
996         }
997     }
998 
999 
1000   /* Add comment(s). */
1001   if(p->comment)
1002     {
1003       keywords_open(p, &fptr, READWRITE);
1004       for(tstll=p->comment; tstll!=NULL; tstll=tstll->next)
1005         {
1006           fits_write_comment(fptr, tstll->v, &status);
1007           if(status)
1008             r=fits_has_error(p, FITS_ACTION_WRITE, "COMMENT", status);
1009           status=0;
1010         }
1011     }
1012 
1013 
1014   /* Update/add the date. */
1015   if(p->date)
1016     {
1017       keywords_open(p, &fptr, READWRITE);
1018       fits_write_date(fptr, &status);
1019       if(status) r=fits_has_error(p, FITS_ACTION_WRITE, "DATE", status);
1020       status=0;
1021     }
1022 
1023 
1024   /* Print all the keywords in the extension. */
1025   if(p->printallkeys)
1026     {
1027       keywords_open(p, &fptr, READONLY);
1028       keywords_print_all_keys(p, &fptr);
1029     }
1030 
1031 
1032   /* Verify the CHECKSUM and DATASUM keys. */
1033   if(p->verify)
1034     {
1035       keywords_open(p, &fptr, READONLY);
1036       r=keywords_verify(p, &fptr);
1037     }
1038 
1039 
1040   /* If a range of keywords must be copied, get all the keywords as a
1041      single string. */
1042   if(p->copykeys)
1043     {
1044       keywords_open(p, &fptr, READONLY);
1045       if( fits_convert_hdr2str(fptr, 0, NULL, 0, &inkeys, &numinkeys,
1046                                &status) )
1047         gal_fits_io_error(status, NULL);
1048       status=0;
1049     }
1050 
1051 
1052   /* Convert the FITS date string into seconds. */
1053   if(p->datetosec)
1054     {
1055       keywords_open(p, &fptr, READONLY);
1056       keywords_date_to_seconds(p, fptr);
1057     }
1058 
1059   /* List all keyword names. */
1060   if(p->printkeynames)
1061     {
1062       keywords_open(p, &fptr, READONLY);
1063       keywords_list_key_names(p, fptr);
1064     }
1065 
1066   /* Close the FITS file */
1067   if(fptr && fits_close_file(fptr, &status))
1068     gal_fits_io_error(status, NULL);
1069 
1070 
1071   /* Write desired keywords into output. */
1072   if(p->copykeys)
1073     {
1074       keywords_copykeys(p, inkeys, numinkeys);
1075       free(inkeys);
1076     }
1077 
1078   /* Convert the input's distortion to the desired output distortion. */
1079   if(p->wcsdistortion || p->wcscoordsys)
1080     keywords_wcs_convert(p);
1081 
1082   /* Return. */
1083   return r;
1084 }
1085