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, ©);
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