gnuastro-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnuastro-commits] master a78974a0: All programs: date and version metad


From: Mohammad Akhlaghi
Subject: [gnuastro-commits] master a78974a0: All programs: date and version metadata in 0-th HDU
Date: Sun, 31 Dec 2023 19:34:45 -0500 (EST)

branch: master
commit a78974a040b9524e2d8aacbdf1144df1318af67f
Author: Mohammad Akhlaghi <mohammad@akhlaghi.org>
Commit: Mohammad Akhlaghi <mohammad@akhlaghi.org>

    All programs: date and version metadata in 0-th HDU
    
    Until now, the date, software versions and Git commit keywords were written
    in every FITS HDU that was created by Gnuastro programs. This created
    validation problems (in a fully controlled environment, we may get
    perfectly reproducible data at a different date and with different software
    versions or on a different Git commit!).
    
    With this commit, these metadata have been moved to the 0-th HDU of all
    Gnuastro's outputs (along with the run-time options). Doing this in a
    robust way resulted in many low-level changes because until now the
    metadata above were treated differently from other keywords!
---
 NEWS                               |  52 +++
 bin/arithmetic/args.h              |   2 +-
 bin/arithmetic/arithmetic.c        |  26 +-
 bin/arithmetic/ui.c                |   4 +
 bin/convertt/convertt.c            |   7 +-
 bin/convolve/convolve.c            |  38 ++-
 bin/crop/crop.c                    |   6 +-
 bin/crop/onecrop.c                 |  14 +-
 bin/crop/ui.c                      |   4 +
 bin/fits/fits.c                    |   7 +-
 bin/fits/keywords.c                |  10 +-
 bin/fits/meta.c                    |  11 +-
 bin/match/match.c                  |  26 +-
 bin/mkcatalog/mkcatalog.c          |  13 +-
 bin/mkcatalog/parse.c              |   2 +-
 bin/mkcatalog/upperlimit.c         |  13 +-
 bin/mkprof/mkprof.c                |  32 +-
 bin/noisechisel/detection.c        |  35 +-
 bin/noisechisel/noisechisel.c      |  45 ++-
 bin/noisechisel/sky.c              |   6 +-
 bin/noisechisel/threshold.c        |  28 +-
 bin/noisechisel/ui.c               |   4 +-
 bin/query/query.c                  |  10 +-
 bin/segment/clumps.c               |  12 +-
 bin/segment/segment.c              |  40 ++-
 bin/segment/ui.c                   |   4 +-
 bin/statistics/sky.c               |  27 +-
 bin/statistics/statistics.c        |  50 +--
 bin/statistics/ui.c                |   5 +-
 bin/table/table.c                  |   4 +-
 bin/warp/warp.c                    |  12 +-
 doc/gnuastro.texi                  | 152 +++++----
 lib/fits.c                         | 633 ++++++++++++++++++++-----------------
 lib/gnuastro-internal/commonopts.h |  52 +++
 lib/gnuastro-internal/options.h    |  38 ++-
 lib/gnuastro/fits.h                |  53 ++--
 lib/gnuastro/table.h               |   4 +-
 lib/gnuastro/tile.h                |   2 +-
 lib/gnuastro/txt.h                 |   4 +-
 lib/gnuastro/wcs.h                 |   2 +-
 lib/options.c                      |  42 ++-
 lib/table.c                        |  13 +-
 lib/tile-internal.c                |  21 +-
 lib/tile.c                         |   8 +-
 lib/txt.c                          |  14 +-
 lib/wcs.c                          |  10 +-
 tests/buildprog/simpleio.c         |   2 +-
 47 files changed, 912 insertions(+), 687 deletions(-)

diff --git a/NEWS b/NEWS
index b338e39b..4aecc254 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,20 @@ See the end of the file for license conditions.
     this by showing the brighter parts of the image as color, intermediate
     regions as black and the noisy/faint regions as gray/white.
 
+*** All programs
+  - The following options are now available in all programs to allow
+    customization of which metadata is printed in the 0-th HDU of the FITS
+    output of all Gnuastro programs. Also see the "Changed features" list
+    for an important change in this regard to all Gnuastro output FITS
+    files.
+    --outfitsnoconfig: do not print any metadata in the 0-th HDU (including
+      the used options and their values, the date, the versions and git
+      commit of running directory).
+    --outfitsnodate: do not write the 'DATE' keyword.
+    --outfitsnoversions: do not write the versions of Gnuastro and
+      mandatory dependencies.
+    --outfitsnocommit: do not write the Git commit.
+
 *** Arithmetic
   - New operators:
     - rotate-coord: given a 2D point's coordinates, return the coordinates
@@ -94,6 +108,11 @@ See the end of the file for license conditions.
   - gal_dimension_collapse_sclip_fill_mean: filled sigma-clipped mean
   - gal_dimension_collapse_sclip_fill_median: filled sigma-clipped median.
   - gal_dimension_collapse_sclip_fill_number: filled sigma-clipped number.
+  - gal_fits_key_list_add_date: add 'DATE' to current list of keywords.
+  - gal_fits_key_list_add_git_commit: add the Git commit information of the
+    running directory to the input list of keywords (if built with libgit2).
+  - gal_fits_key_list_add_software_versions: add versions of Gnustro and
+    its mandatory dependencies to the input list of keywords.
   - gal_statistics_clip_mad: MAD clipping of given input.
   - gal_statistics_mad: return median absolute deviation (MAD).
   - gal_statistics_median_mad: return median and MAD.
@@ -129,7 +148,27 @@ See the end of the file for license conditions.
     - GAL_STATISTICS_CLIP_OUTCOL_OPTIONAL_MAD: bit flag for measuring MAD.
 
 ** Removed features
+*** Library
+  - gal_fits_key_write_version: redundant with the new
+    'gal_fits_key_list_add_software_versions'.
+  - gal_fits_key_write_version_in_ptr: as in gal_fits_key_write_version.
+  - gal_fits_key_write_config: redundant since all configuration (options)
+    are stored as generic keys.
+
 ** Changed features
+*** All programs
+  - Date and versions FITS keywords are only written in the 0-th HDU of
+    output FITS files (along with the options names and values), not the
+    HDU(s) containing data. Until now, the data and versions metadata were
+    written in the data HDU. However, ultimately, these are also metadata
+    and are better suited for the 0-th HDU. For example when everything
+    else is identical, you expect an exactly reproducible HDU (to verify
+    with the FITS 'CHECKSUM' for example), but every time you run, 'DATE'
+    will be different! Similarly for the versions of the software or Git
+    commit (they may change, while your dataset doesn't). To disable these
+    in the 0-th HDU also, see the newly added '--outfitsno*' options in the
+    new features.
+
 *** Arithmetic
   - Binary operators (like '+' or 'x') that are given two integers will
     crash with an error if the input operands have the same width but
@@ -141,10 +180,23 @@ See the end of the file for license conditions.
     on Gnuastro's Matrix chat channel (#gnuastro:openastronomy.org).
 
 *** Library
+  - gal_fits_key_write: to generalize, the "title" argument has been
+    removed (because it is a keyword). It can also create the file if it
+    doesn't exist and can optionally free the list of keywords (until now,
+    it would always free them).
+  - gal_fits_key_write_in_ptr: similar to 'gal_fits_key_write'.
+  - gal_fits_img_write: no more 'program_name'; it is a keyword title;
+    added 'freekeys' argument to optionally free the input keyword list.
+  - gal_fits_img_write_to_type: as in 'gal_fits_img_write'.
+  - gal_fits_img_write_corr_wcs_str: as in 'gal_fits_img_write'.
+  - gal_fits_tab_write: as in 'gal_fits_img_write'.
   - GAL_STATISTICS_CLIP_MAX_CONVERGE: new name for the old
     'GAL_STATISTICS_SIG_CLIP_MAX_CONVERGE'. Since we now also have
     MAD-clipping.
   - gal_statistics_clip_sigma: new name for 'gal_statistics_sigma_clip'.
+  - gal_table_write: as in 'gal_fits_img_write'.
+  - gal_tile_full_values_write: as in 'gal_fits_img_write'.
+  - gal_wcs_write: as in 'gal_fits_img_write'.
 
 ** Bugs fixed
   - bug #64852: astscript-zeropoint: crash when input is in 0-th HDU;
diff --git a/bin/arithmetic/args.h b/bin/arithmetic/args.h
index 1b1edcdc..d2b06cbe 100644
--- a/bin/arithmetic/args.h
+++ b/bin/arithmetic/args.h
@@ -159,7 +159,7 @@ struct argp_option program_options[] =
       UI_KEY_WRITEALL,
       0,
       0,
-      "Write all images in stack (not just a single one).",
+      "Write all remaining data in the output.",
       GAL_OPTIONS_GROUP_OUTPUT,
       &p->writeall,
       GAL_OPTIONS_NO_ARG_TYPE,
diff --git a/bin/arithmetic/arithmetic.c b/bin/arithmetic/arithmetic.c
index 89c45c5c..1b54bfdf 100644
--- a/bin/arithmetic/arithmetic.c
+++ b/bin/arithmetic/arithmetic.c
@@ -1257,9 +1257,8 @@ arithmetic_tofile(struct arithmeticparams *p, char 
*token, int freeflag)
   popped->wcs=p->refdata.wcs;
   if(popped->ndim==1 && p->onedasimage==0)
     gal_table_write(popped, NULL, NULL, p->cp.tableformat, filename,
-                    "ARITHMETIC", 0);
-  else
-    gal_fits_img_write(popped, filename, NULL, PROGRAM_NAME);
+                    "ARITHMETIC", 0, 0);
+  else gal_fits_img_write(popped, filename, NULL, 0);
   if(!p->cp.quiet)
     printf(" - Write: %s\n", filename);
 
@@ -1922,7 +1921,7 @@ arithmetic_final_data(struct arithmeticparams *p)
 void
 reversepolish(struct arithmeticparams *p)
 {
-  char *printnum;
+  char *printnum=NULL;
   struct operand *otmp;
   size_t num_operands=0;
   gal_list_str_t *token;
@@ -1979,8 +1978,10 @@ reversepolish(struct arithmeticparams *p)
          isn't an operator. */
       else
         {
-          operator=arithmetic_set_operator(token->v, &num_operands, &inlib);
-          arithmetic_operator_run(p, operator, token->v, num_operands, inlib);
+          operator=arithmetic_set_operator(token->v, &num_operands,
+                                           &inlib);
+          arithmetic_operator_run(p, operator, token->v, num_operands,
+                                  inlib);
         }
 
       /* Increment the token counter. */
@@ -2022,7 +2023,8 @@ reversepolish(struct arithmeticparams *p)
       printnum=gal_type_to_string(data->array, data->type, 0);
       printf("%s\n", printnum);
 
-      /* Clean up. */
+      /* Clean up (don't set it to 'NULL', this is used to specify if a
+         file was created in the end or not). */
       free(printnum);
     }
   else
@@ -2039,15 +2041,17 @@ reversepolish(struct arithmeticparams *p)
         { if(data->comment) free(data->comment);
           gal_checkset_allocate_copy(p->metacomment, &data->comment); }
 
-      /* Put a copy of the WCS structure from the reference image, it
-         will be freed while freeing 'data'. */
+      /* Put a copy of the WCS structure from the reference image, it will
+         be freed while freeing 'data'. But start the file with the 0-th
+         HDU keywords.*/
+      gal_fits_key_write(p->cp.ckeys, p->cp.output, "0", "NONE", 1, 1);
       if(data->ndim==1 && p->onedasimage==0)
         gal_table_write(data, NULL, NULL, p->cp.tableformat,
                         p->onedonstdout ? NULL : p->cp.output,
-                        "ARITHMETIC", 0);
+                        "ARITHMETIC", 0, 0);
       else
         for(tmp=data; tmp!=NULL; tmp=tmp->next)
-          gal_fits_img_write(tmp, p->cp.output, NULL, PROGRAM_NAME);
+          gal_fits_img_write(tmp, p->cp.output, NULL, 0);
 
       /* Let the user know that the job is done. */
       if(!p->cp.quiet)
diff --git a/bin/arithmetic/ui.c b/bin/arithmetic/ui.c
index 13ea0aa1..55694ea7 100644
--- a/bin/arithmetic/ui.c
+++ b/bin/arithmetic/ui.c
@@ -484,6 +484,10 @@ ui_read_check_inputs_setup(int argc, char *argv[], struct 
arithmeticparams *p)
   gal_options_print_state(cp);
 
 
+  /* Prepare all the options as FITS keywords to write in output later. */
+  gal_options_as_fits_keywords(&p->cp);
+
+
   /* Sanity check only on options. */
   ui_check_only_options(p);
 
diff --git a/bin/convertt/convertt.c b/bin/convertt/convertt.c
index 540b4682..4331e99b 100644
--- a/bin/convertt/convertt.c
+++ b/bin/convertt/convertt.c
@@ -332,19 +332,20 @@ convertt(struct converttparams *p)
       if(p->numch==3 && p->rgbtohsv)
         color_rgb_to_hsv(p);
       for(channel=p->chll; channel!=NULL; channel=channel->next)
-        gal_fits_img_write(channel, p->cp.output, NULL, PROGRAM_NAME);
+        gal_fits_img_write(channel, p->cp.output, NULL, 0);
       break;
 
     /* Plain text: only one channel is acceptable. */
     case OUT_FORMAT_TXT:
       gal_checkset_writable_remove(p->cp.output, p->inputnames->v, 0,
                                    p->cp.dontdelete);
-      gal_txt_write(p->chll, NULL, NULL, p->cp.output, 0, 1);
+      gal_txt_write(p->chll, NULL, NULL, p->cp.output, 0, 1, 0);
       break;
 
     /* JPEG: */
     case OUT_FORMAT_JPEG:
-      if(p->colormap) color_map_prepare(p); else convertt_scale_to_uchar(p);
+      if(p->colormap) color_map_prepare(p);
+      else convertt_scale_to_uchar(p);
       gal_jpeg_write(p->chll, p->cp.output, p->quality, p->widthincm);
       break;
 
diff --git a/bin/convolve/convolve.c b/bin/convolve/convolve.c
index 8cd44ef4..3dbc396b 100644
--- a/bin/convolve/convolve.c
+++ b/bin/convolve/convolve.c
@@ -662,13 +662,13 @@ convolve_frequency(struct convolveparams *p)
       /* Save the padded input image. */
       complextoreal(p->pimg, p->ps0*p->ps1, COMPLEX_TO_REAL_REAL, &tmp);
       data->array=tmp; data->name="input padded";
-      gal_fits_img_write(data, p->freqstepsname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(data, p->freqstepsname, NULL, 0);
       free(tmp); data->name=NULL;
 
       /* Save the padded kernel image. */
       complextoreal(p->pker, p->ps0*p->ps1, COMPLEX_TO_REAL_REAL, &tmp);
       data->array=tmp; data->name="kernel padded";
-      gal_fits_img_write(data, p->freqstepsname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(data, p->freqstepsname, NULL, 0);
       free(tmp); data->name=NULL;
     }
 
@@ -686,12 +686,12 @@ convolve_frequency(struct convolveparams *p)
     {
       complextoreal(p->pimg, p->ps0*p->ps1, COMPLEX_TO_REAL_SPEC, &tmp);
       data->array=tmp; data->name="input transformed";
-      gal_fits_img_write(data, p->freqstepsname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(data, p->freqstepsname, NULL, 0);
       free(tmp); data->name=NULL;
 
       complextoreal(p->pker, p->ps0*p->ps1, COMPLEX_TO_REAL_SPEC, &tmp);
       data->array=tmp; data->name="kernel transformed";
-      gal_fits_img_write(data, p->freqstepsname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(data, p->freqstepsname, NULL, 0);
       free(tmp); data->name=NULL;
     }
 
@@ -713,7 +713,7 @@ convolve_frequency(struct convolveparams *p)
     {
       complextoreal(p->pimg, p->ps0*p->ps1, COMPLEX_TO_REAL_SPEC, &tmp);
       data->array=tmp; data->name=p->makekernel ? "Divided" : "Multiplied";
-      gal_fits_img_write(data, p->freqstepsname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(data, p->freqstepsname, NULL, 0);
       free(tmp); data->name=NULL;
     }
 
@@ -729,7 +729,7 @@ convolve_frequency(struct convolveparams *p)
   if(p->checkfreqsteps)
     {
       data->array=p->rpad; data->name="padded output";
-      gal_fits_img_write(data, p->freqstepsname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(data, p->freqstepsname, NULL, 0);
       data->name=NULL; data->array=NULL;
     }
 
@@ -789,7 +789,7 @@ convolve(struct convolveparams *p)
       if(multidim && cp->tl.tilecheckname)
         {
           check=gal_tile_block_check_tiles(cp->tl.tiles);
-          gal_fits_img_write(check, cp->tl.tilecheckname, NULL, PROGRAM_NAME);
+          gal_fits_img_write(check, cp->tl.tilecheckname, NULL, 0);
           gal_data_free(check);
         }
 
@@ -797,7 +797,8 @@ convolve(struct convolveparams *p)
          want to do spatial domain convolution with this Convolve program
          is edge correction. So by default we assume it and will only
          ignore it if the user asks.*/
-      out=gal_convolve_spatial(multidim ? cp->tl.tiles : p->input, p->kernel,
+      out=gal_convolve_spatial(multidim ? cp->tl.tiles : p->input,
+                               p->kernel,
                                cp->numthreads,
                                multidim ? !p->noedgecorrection : 1,
                                multidim ? cp->tl.workoverch : 1,
@@ -812,25 +813,22 @@ convolve(struct convolveparams *p)
   else
     convolve_frequency(p);
 
-  /* Save the output (which is in p->input) array. */
-  if(p->input->ndim==1)
-    gal_table_write(p->input, NULL, NULL, p->cp.tableformat, p->cp.output,
-                    "CONVOLVED", 0);
-  else
-    gal_fits_img_write_to_type(p->input, cp->output, NULL, PROGRAM_NAME,
-                               cp->type);
-
   /* Write Convolve's parameters as keywords into the first extension of
      the output. */
   if( gal_fits_name_is_fits(p->cp.output) )
     {
-      gal_fits_key_write_filename("input", p->filename, &cp->okeys, 1,
+      gal_fits_key_write_filename("input", p->filename, &cp->ckeys, 1,
                                   cp->quiet);
-      gal_fits_key_write_config(&cp->okeys, "Convolve configuration",
-                                "CONVOLVE-CONFIG", cp->output, "0",
-                                "NONE");
+      gal_fits_key_write(cp->ckeys, cp->output, "0", "NONE", 1, 1);
     }
 
+  /* Save the output (which is in p->input) array. */
+  if(p->input->ndim==1)
+    gal_table_write(p->input, NULL, NULL, p->cp.tableformat, p->cp.output,
+                    "CONVOLVED", 0, 0);
+  else
+    gal_fits_img_write_to_type(p->input, cp->output, NULL, cp->type, 0);
+
   /* Inform the user that the job is done. */
   if(!p->cp.quiet)
     printf("  - Output: %s\n", p->cp.output);
diff --git a/bin/crop/crop.c b/bin/crop/crop.c
index 2f7b5a24..2719c1a0 100644
--- a/bin/crop/crop.c
+++ b/bin/crop/crop.c
@@ -236,8 +236,7 @@ crop_mode_img(void *inparam)
           /* Check if the center of the crop is filled or not. */
           crp->centerfilled=onecrop_center_filled(crp);
 
-          /* Add the final headers and close output FITS image: */
-          gal_fits_key_write_version_in_ptr(NULL, NULL, crp->outfits);
+          /* Close output FITS image. */
           status=0;
           if( fits_close_file(crp->outfits, &status) )
             gal_fits_io_error(status, "CFITSIO could not close "
@@ -341,8 +340,7 @@ crop_mode_wcs(void *inparam)
           /* See if the center is filled. */
           crp->centerfilled=onecrop_center_filled(crp);
 
-          /* Write all the dependency versions and close the file. */
-          gal_fits_key_write_version_in_ptr(NULL, NULL, crp->outfits);
+          /* Close the file. */
           status=0;
           if( fits_close_file(crp->outfits, &status) )
             gal_fits_io_error(status, "CFITSIO could not close the "
diff --git a/bin/crop/onecrop.c b/bin/crop/onecrop.c
index 708f1b1b..78c375d9 100644
--- a/bin/crop/onecrop.c
+++ b/bin/crop/onecrop.c
@@ -541,7 +541,6 @@ onecrop_make_array(struct onecropparams *crp, long 
*fpixel_i,
     for(i=0;i<ndim;++i)
       naxes[i] = crp->lpixel[i]-crp->fpixel[i]+1;
 
-
   /* Create the FITS file with a blank first extension, then close it, so
      with the next 'fits_open_file', we build the image in the second
      extension. But only if the user didn't want the append the crop to an
@@ -555,8 +554,9 @@ onecrop_make_array(struct onecropparams *crp, long 
*fpixel_i,
       if(p->primaryimghdu==0)
         {
           fits_create_img(ofp, SHORT_IMG, 0, naxes, &status);
-          fits_close_file(ofp, &status);
+          gal_fits_key_write(p->cp.ckeys, outname, "0", "NONE", 0, 0);
         }
+      fits_close_file(ofp, &status);
     }
 
 
@@ -750,8 +750,8 @@ onecrop(struct onecropparams *crp)
             gal_fits_io_error(status, NULL);
 
 
-          /* Write the selected region of this image as a string to include as
-             a FITS keyword. Then we want to delete the last coma ','.*/
+          /* Write the selected region of this image as a string to include
+             as a FITS keyword. Then we want to delete the last coma ','.*/
           j=0;
           for(i=0;i<ndim;++i)
             j += sprintf(&region[j], "%ld:%ld,", fpixel_i[i], lpixel_i[i]);
@@ -765,9 +765,9 @@ onecrop(struct onecropparams *crp)
                                       p->cp.quiet);
           sprintf(regionkey, "%sPIX", basekeyname);
           gal_fits_key_list_add_end(&headers, GAL_TYPE_STRING, regionkey,
-                                    0, region, 0, "Range of pixels used for "
-                                    "this output.", 0, NULL, 0);
-          gal_fits_key_write_in_ptr(&headers, ofp);
+                                    0, region, 0, "Range of pixels used "
+                                    "for this output.", 0, NULL, 0);
+          gal_fits_key_write_in_ptr(headers, ofp, 1);
         }
 
 
diff --git a/bin/crop/ui.c b/bin/crop/ui.c
index 6bd55bd5..92304b66 100644
--- a/bin/crop/ui.c
+++ b/bin/crop/ui.c
@@ -1220,6 +1220,10 @@ ui_read_check_inputs_setup(int argc, char *argv[], 
struct cropparams *p)
   gal_options_print_state(&p->cp);
 
 
+  /* Prepare all the options as FITS keywords to write in output later. */
+  gal_options_as_fits_keywords(&p->cp);
+
+
   /* Check that the options and arguments fit well with each other. Note
      that arguments don't go in a configuration file. So this test should
      be done after (possibly) printing the option values. */
diff --git a/bin/fits/fits.c b/bin/fits/fits.c
index 76447aaa..508b9667 100644
--- a/bin/fits/fits.c
+++ b/bin/fits/fits.c
@@ -308,11 +308,12 @@ fits_print_extension_info(struct fitsparams *p)
         printf("           ('%s': no unit in HDU metadata, or "
                "HDU is a table)\n", GAL_BLANK_STRING);
       if(hascomments)
-        printf(" Column 6: Comments about the HDU (e.g., if its HEALpix, or "
-               "etc).\n");
+        printf(" Column 6: Comments about the HDU (e.g., if its HEALpix, "
+               "or etc).\n");
       printf("-----\n");
     }
-  gal_table_write(cols, NULL, NULL, GAL_TABLE_FORMAT_TXT, NULL, NULL, 0);
+  gal_table_write(cols, NULL, NULL, GAL_TABLE_FORMAT_TXT, NULL, NULL,
+                  0, 0);
   gal_list_data_free(cols);
 }
 
diff --git a/bin/fits/keywords.c b/bin/fits/keywords.c
index 87f39fc5..25dd4eb6 100644
--- a/bin/fits/keywords.c
+++ b/bin/fits/keywords.c
@@ -647,8 +647,8 @@ keywords_wcs_convert(struct fitsparams *p)
     output=p->cp.output;
   else
     {
-      if( asprintf(&suffix, "-%s.fits",
-                   p->wcsdistortion ? p->wcsdistortion : p->wcscoordsys)<0 )
+      if(asprintf(&suffix, "-%s.fits",
+                  p->wcsdistortion ? p->wcsdistortion : p->wcscoordsys)<0)
         error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
       output=gal_checkset_automatic_output(&p->cp, p->input->v, suffix);
     }
@@ -659,7 +659,7 @@ keywords_wcs_convert(struct fitsparams *p)
     {
       /* Add the output WCS to the dataset and write it. */
       data->wcs=outwcs;
-      gal_fits_img_write(data, output, NULL, PROGRAM_NAME);
+      gal_fits_img_write(data, output, NULL, 0);
 
       /* Clean up, but remove the pointer first (so it doesn't free it
          here). */
@@ -667,7 +667,7 @@ keywords_wcs_convert(struct fitsparams *p)
       gal_data_free(data);
     }
   else
-    gal_wcs_write(outwcs, output, p->wcsdistortion, NULL, PROGRAM_NAME);
+    gal_wcs_write(outwcs, output, p->wcsdistortion, NULL, PROGRAM_NAME, 0);
 
   /* Clean up. */
   wcsfree(inwcs);
@@ -993,7 +993,7 @@ keywords_value(struct fitsparams *p)
   gal_checkset_writable_remove(p->cp.output, p->input->v, 0,
                                p->cp.dontdelete);
   gal_table_write(out, NULL, NULL, p->cp.tableformat,
-                  p->cp.output, "KEY-VALUES", p->colinfoinstdout);
+                  p->cp.output, "KEY-VALUES", p->colinfoinstdout, 0);
 
   /* Clean up. */
   gal_list_str_free(p->keyvalue, 0);
diff --git a/bin/fits/meta.c b/bin/fits/meta.c
index d1848657..513db127 100644
--- a/bin/fits/meta.c
+++ b/bin/fits/meta.c
@@ -108,12 +108,13 @@ meta_write_to_file(struct fitsparams *p, 
gal_warp_wcsalign_t *wa)
   gal_fits_list_key_t *headers=NULL;
 
   /* Add configuration headers. */
-  gal_fits_key_list_add_end(&headers, GAL_TYPE_STRING,
-                            "input", 0, p->input->v, 0,
-                            "File given to astfits", 0, NULL, 0);
+  gal_fits_key_list_add_end(&headers, GAL_TYPE_STRING, "input", 0,
+                            p->input->v, 0, "File given to astfits",
+                            0, NULL, 0);
   gal_fits_key_list_add_end(&headers, GAL_TYPE_SIZE_T,
                             "edgesampling", 0, &p->edgesampling, 0,
-                            "Extra sampling along pixel edges.", 0, NULL, 0);
+                            "Extra sampling along pixel edges.",
+                            0, NULL, 0);
   gal_fits_key_list_add_end(&headers, GAL_TYPE_FLOAT64,
                             "Coveredfrac", 0, &wa->coveredfrac, 0,
                             "Fraction of pixel that is covered by input",
@@ -122,7 +123,7 @@ meta_write_to_file(struct fitsparams *p, 
gal_warp_wcsalign_t *wa)
   /* Convert to type and write to file. */
   if(p->cp.type!=output->type)
     output=gal_data_copy_to_new_type_free(output, p->cp.type);
-  gal_fits_img_write(output, p->cp.output, headers, PROGRAM_NAME);
+  gal_fits_img_write(output, p->cp.output, headers, 1);
 
   /* Clean up. */
   wa->output=NULL; /* Must be here to prevent double freeing. */
diff --git a/bin/match/match.c b/bin/match/match.c
index 06524670..9941f436 100644
--- a/bin/match/match.c
+++ b/bin/match/match.c
@@ -409,7 +409,7 @@ match_catalog_read_write_all(struct matchparams *p, size_t 
*permutation,
     {
       /* Write the catalog to a file. */
       gal_table_write(cat, NULL, NULL, p->cp.tableformat, outname,
-                      extname, 0);
+                      extname, 0, 0);
 
       /* Clean up. */
       gal_list_data_free(cat);
@@ -485,7 +485,7 @@ match_catalog_write_one_row(struct matchparams *p, 
gal_data_t *a,
       /* Reverse the table and write it out. */
       gal_list_data_reverse(&cat);
       gal_table_write(cat, NULL, NULL, p->cp.tableformat,
-                      p->out1name, "MATCHED", 0);
+                      p->out1name, "MATCHED", 0, 0);
       gal_list_data_free(cat);
     }
 
@@ -493,7 +493,7 @@ match_catalog_write_one_row(struct matchparams *p, 
gal_data_t *a,
      it ('a' will be freed in the higher-level function). */
   else
     gal_table_write(a, NULL, NULL, p->cp.tableformat, p->out1name,
-                    "MATCHED", 0);
+                    "MATCHED", 0, 0);
 }
 
 
@@ -551,7 +551,7 @@ match_catalog_write_one_col(struct matchparams *p, 
gal_data_t *a,
   /* Reverse the table and write it out. */
   gal_list_data_reverse(&cat);
   gal_table_write(cat, NULL, NULL, p->cp.tableformat, p->out1name,
-                  "MATCHED", 0);
+                  "MATCHED", 0, 0);
   gal_list_data_free(cat);
 }
 
@@ -591,10 +591,10 @@ match_catalog_kdtree_build(struct matchparams *p)
   gal_fits_key_write_filename("KDTIN", p->input1name, &keylist, 0,
                               p->cp.quiet);
   gal_fits_key_list_add_end(&keylist, GAL_TYPE_SIZE_T,
-                            MATCH_KDTREE_ROOT_KEY, 0,
-                            &root, 0, comment, 0, unit, 0);
-  gal_table_write(kdtree, &keylist, NULL, GAL_TABLE_FORMAT_BFITS,
-                  p->out1name, "kdtree", 0);
+                            MATCH_KDTREE_ROOT_KEY, 0, &root, 0,
+                            comment, 0, unit, 0);
+  gal_table_write(kdtree, keylist, NULL, GAL_TABLE_FORMAT_BFITS,
+                  p->out1name, "kdtree", 0, 1);
 
   /* Let the user know that the k-d tree has been built. */
   if(!p->cp.quiet)
@@ -816,7 +816,7 @@ match_catalog(struct matchparams *p)
 
       /* Write them into the table. */
       gal_table_write(mcols, NULL, NULL, p->cp.tableformat, p->logname,
-                      "LOG_INFO", 0);
+                      "LOG_INFO", 0, 0);
 
       /* Set the comment pointer to NULL: they weren't allocated. */
       mcols->comment=NULL;
@@ -885,12 +885,10 @@ match(struct matchparams *p)
       gal_fits_key_write_filename("input1", ( p->input1name
                                               ? p->input1name
                                               : "Standard input" ),
-                                  &p->cp.okeys, 1, p->cp.quiet);
+                                  &p->cp.ckeys, 1, p->cp.quiet);
       gal_fits_key_write_filename("input2",
                                   p->input2name?p->input2name:"--coord",
-                                  &p->cp.okeys, 1, p->cp.quiet);
-      gal_fits_key_write_config(&p->cp.okeys, "Match configuration",
-                                "MATCH-CONFIG", p->out1name, "0",
-                                "NONE");
+                                  &p->cp.ckeys, 1, p->cp.quiet);
+      gal_fits_key_write(p->cp.ckeys, p->out1name, "0", "NONE", 1, 0);
     }
 }
diff --git a/bin/mkcatalog/mkcatalog.c b/bin/mkcatalog/mkcatalog.c
index a0dfeca5..9dee44bf 100644
--- a/bin/mkcatalog/mkcatalog.c
+++ b/bin/mkcatalog/mkcatalog.c
@@ -722,11 +722,10 @@ mkcatalog_write_outputs(struct mkcatalogparams *p)
       /* Reverse the comments list (so it is printed in the same order
          here), write the objects catalog and free the comments. */
       gal_list_str_reverse(&comments);
-      gal_table_write(p->objectcols, &keylist, NULL, p->cp.tableformat,
-                      p->objectsout, "OBJECTS", 0);
+      gal_table_write(p->objectcols, keylist, NULL, p->cp.tableformat,
+                      p->objectsout, "OBJECTS", 0, 1);
       gal_list_str_free(comments, 1);
 
-
       /* CLUMPS catalog */
       if(p->clumps)
         {
@@ -740,7 +739,7 @@ mkcatalog_write_outputs(struct mkcatalogparams *p)
              here), write the objects catalog and free the comments. */
           gal_list_str_reverse(&comments);
           gal_table_write(p->clumpcols, NULL, comments, p->cp.tableformat,
-                          p->clumpsout, "CLUMPS", 0);
+                          p->clumpsout, "CLUMPS", 0, 1);
           gal_list_str_free(comments, 1);
         }
     }
@@ -748,11 +747,9 @@ mkcatalog_write_outputs(struct mkcatalogparams *p)
   /* Configuration information. */
   if(outisfits)
     {
-      gal_fits_key_write_filename("input", p->objectsfile, &p->cp.okeys,
+      gal_fits_key_write_filename("input", p->objectsfile, &p->cp.ckeys,
                                   1, p->cp.quiet);
-      gal_fits_key_write_config(&p->cp.okeys, "MakeCatalog configuration",
-                                "MKCATALOG-CONFIG", p->objectsout, "0",
-                                "NONE");
+      gal_fits_key_write(p->cp.ckeys, p->objectsout, "0", "NONE", 1, 0);
     }
 
 
diff --git a/bin/mkcatalog/parse.c b/bin/mkcatalog/parse.c
index eb53564c..543e5456 100644
--- a/bin/mkcatalog/parse.c
+++ b/bin/mkcatalog/parse.c
@@ -1127,7 +1127,7 @@ parse_clumps(struct mkcatalog_passparams *pp)
 
           /* For a check on the projected 2D areas. */
           if(xybin && pp->object==2)
-            gal_fits_img_write(&xybin[i], "xybin.fits", NULL, NULL);
+            gal_fits_img_write(&xybin[i], "xybin.fits", NULL, 0);
 
         }
     }
diff --git a/bin/mkcatalog/upperlimit.c b/bin/mkcatalog/upperlimit.c
index ec7ddd16..cdc8370f 100644
--- a/bin/mkcatalog/upperlimit.c
+++ b/bin/mkcatalog/upperlimit.c
@@ -296,7 +296,8 @@ upperlimit_write_keys(struct mkcatalogparams *p,
                             "counter", 0);
   gal_fits_key_list_add_end(keylist, GAL_TYPE_STRING, "UPRNGNAM", 0,
                             (void *)(p->rng_name), 0,
-                            "Random number generator name.", 0, NULL, 0);
+                            "Random number generator name.",
+                            0, NULL, 0);
   mkcatalog_outputs_keys_numeric(keylist, &p->rng_seed,
                                  GAL_TYPE_ULONG, "UPRNGSEE",
                                  "Random number generator seed.", NULL);
@@ -346,8 +347,10 @@ upperlimit_write_keys(struct mkcatalogparams *p,
 
 /* Write the values into a table for the user */
 static void
-upperlimit_write_check(struct mkcatalogparams *p, gal_list_sizet_t *check_x,
-                       gal_list_sizet_t *check_y, gal_list_sizet_t *check_z,
+upperlimit_write_check(struct mkcatalogparams *p,
+                       gal_list_sizet_t *check_x,
+                       gal_list_sizet_t *check_y,
+                       gal_list_sizet_t *check_z,
                        gal_list_f32_t *check_s)
 {
   float *sarr;
@@ -425,8 +428,8 @@ upperlimit_write_check(struct mkcatalogparams *p, 
gal_list_sizet_t *check_x,
   x->next=y;
   if(check_z) { y->next=z; z->next=s; }
   else        { y->next=s;            }
-  gal_table_write(x, &keylist, NULL, p->cp.tableformat, p->upcheckout,
-                  "UPPERLIMIT_CHECK", 0);
+  gal_table_write(x, keylist, NULL, p->cp.tableformat, p->upcheckout,
+                  "UPPERLIMIT_CHECK", 0, 1);
 
   /* Inform the user. */
   if(!p->cp.quiet)
diff --git a/bin/mkprof/mkprof.c b/bin/mkprof/mkprof.c
index 815b1f11..31376be0 100644
--- a/bin/mkprof/mkprof.c
+++ b/bin/mkprof/mkprof.c
@@ -151,7 +151,7 @@ saveindividual(struct mkonthread *mkp)
   /* Write the array to the file (a separately built PSF doesn't need WCS
      coordinates). */
   if(ibq->ispsf && p->psfinimg==0)
-    gal_fits_img_write(ibq->image, filename, NULL, PROGRAM_NAME);
+    gal_fits_img_write(ibq->image, filename, NULL, 0);
   else
     {
       /* Allocate space for the corrected crpix and fill it in. Both
@@ -159,17 +159,20 @@ saveindividual(struct mkonthread *mkp)
       crpix=gal_pointer_allocate(GAL_TYPE_FLOAT64, ndim, 0, __func__,
                                  "crpix");
       for(i=0;i<ndim;++i)
-        crpix[i] = ((double *)(p->crpix->array))[i] - os*(mkp->fpixel_i[i]-1);
+        crpix[i] = ( ((double *)(p->crpix->array))[i]
+                     - os*(mkp->fpixel_i[i]-1) );
 
       /* Write the image. */
       gal_fits_img_write_corr_wcs_str(ibq->image, filename, p->wcsstr,
-                                      p->wcsnkeyrec, crpix, NULL,
-                                      PROGRAM_NAME);
+                                      p->wcsnkeyrec, crpix, NULL, 0);
     }
   ibq->indivcreated=1;
 
 
-  /* Write profile settings into the FITS file. */
+  /* Write profile settings into a keyword list. */
+  gal_fits_key_list_title_add(&keys, "Profile configuration", 0);
+  gal_fits_key_list_add(&keys, GAL_TYPE_STRING, "EXTNAME", 0,
+                        "PROFILE-CONFIG", 0, "HDU name", 0, NULL, 0);
   gal_fits_key_list_add(&keys, GAL_TYPE_STRING, "PROFILE", 0,
                         ui_profile_name_write(mkp->func), 0,
                         "Radial function", 0, NULL, 0);
@@ -265,9 +268,10 @@ saveindividual(struct mkonthread *mkp)
                         &p->magatpeak, 0, "Magnitude is for peak pixel, "
                         "not full profile", 0, NULL, 0);
 
+
+  /* Write the keyword list into the output file. */
   gal_fits_key_list_reverse(&keys);
-  gal_fits_key_write_config(&keys, "Profile configuration",
-                            "PROFILE-CONFIG", filename, "0", "NONE");
+  gal_fits_key_write(keys, filename, "0", "NONE", 1, 0);
 
 
   /* Report if in verbose mode. */
@@ -708,24 +712,22 @@ mkprof_write(struct mkprofparams *p)
       /* Get the current time for verbose output. */
       if(!p->cp.quiet) gettimeofday(&t1, NULL);
 
+      /* Write the configuration keywords. */
+      gal_fits_key_write_filename("input", p->catname, &p->cp.ckeys, 1,
+                                  p->cp.quiet);
+      gal_fits_key_write(p->cp.ckeys, p->mergedimgname, "0", "NONE", 1, 1);
+
       /* Write the final image into a FITS file with the requested
          type. Until now, we were using 'p->wcs' for the WCS, but from now
          on, will put it in 'out' to also free it while freeing 'out'. */
       out->wcs=p->wcs;
       gal_fits_img_write_to_type(out, p->mergedimgname, NULL,
-                                 PROGRAM_NAME, p->cp.type);
+                                 p->cp.type, 0);
       p->wcs=NULL;
 
       /* Clean up */
       gal_data_free(out);
 
-      /* Write the configuration keywords. */
-      gal_fits_key_write_filename("input", p->catname, &p->cp.okeys, 1,
-                                  p->cp.quiet);
-      gal_fits_key_write_config(&p->cp.okeys, "MakeProfiles configuration",
-                                "MKPROF-CONFIG", p->mergedimgname, "0",
-                                "NONE");
-
       /* In verbose mode, print the information. */
       if(!p->cp.quiet)
         {
diff --git a/bin/noisechisel/detection.c b/bin/noisechisel/detection.c
index 338f6ea2..b925fd7c 100644
--- a/bin/noisechisel/detection.c
+++ b/bin/noisechisel/detection.c
@@ -103,7 +103,7 @@ detection_initial(struct noisechiselparams *p)
   if(p->detectionname)
     {
       p->binary->name="THRESHOLDED";
-      gal_fits_img_write(p->binary, p->detectionname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(p->binary, p->detectionname, NULL, 0);
       p->binary->name=NULL;
     }
 
@@ -133,7 +133,7 @@ detection_initial(struct noisechiselparams *p)
   if(p->detectionname)
     {
       p->binary->name="ERODED";
-      gal_fits_img_write(p->binary, p->detectionname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(p->binary, p->detectionname, NULL, 0);
       p->binary->name=NULL;
     }
 
@@ -173,7 +173,7 @@ detection_initial(struct noisechiselparams *p)
   if(p->detectionname)
     {
       p->olabel->name="OPENED-AND-LABELED";
-      gal_fits_img_write(p->olabel, p->detectionname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(p->olabel, p->detectionname, NULL, 0);
       p->olabel->name=NULL;
     }
 
@@ -371,7 +371,7 @@ detection_pseudo_find(struct noisechiselparams *p, 
gal_data_t *workbin,
   if(p->detectionname)
     {
       workbin->name = s0d1 ? "DTHRESH-ON-DET" : "DTHRESH-ON-SKY";
-      gal_fits_img_write(workbin, p->detectionname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(workbin, p->detectionname, NULL, 0);
       workbin->name=NULL;
     }
 
@@ -382,8 +382,8 @@ detection_pseudo_find(struct noisechiselparams *p, 
gal_data_t *workbin,
      to store all tiles. Finally, since we are working on a 'uint8_t' type,
      the size of each element is only 1 byte. */
   fho_prm.copyspace=gal_pointer_allocate(GAL_TYPE_UINT8,
-                                         p->cp.numthreads*p->maxltcontig, 0,
-                                         __func__, "fho_prm.copyspace");
+                                         p->cp.numthreads*p->maxltcontig,
+                                         0, __func__, "fho_prm.copyspace");
 
 
   /* Fill the holes and open on each large tile. When no check image is
@@ -429,13 +429,14 @@ detection_pseudo_find(struct noisechiselparams *p, 
gal_data_t *workbin,
               bin->name="OPENED";
               break;
             default:
-              error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s so "
-                    "we can address the issue. the value %d is not "
-                    "recognized.", __func__, PACKAGE_BUGREPORT, fho_prm.step);
+              error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s "
+                    "so we can address the issue. the value %d is not "
+                    "recognized.", __func__, PACKAGE_BUGREPORT,
+                    fho_prm.step);
             }
 
           /* Write the temporary array into the check image. */
-          gal_fits_img_write(bin, p->detectionname, NULL, PROGRAM_NAME);
+          gal_fits_img_write(bin, p->detectionname, NULL, 0);
 
           /* Increment the step counter. */
           ++fho_prm.step;
@@ -652,7 +653,7 @@ detection_sn(struct noisechiselparams *p, gal_data_t 
*worklab, size_t num,
           while(++plab<plabend);
         }
       worklab->name=extname;
-      gal_fits_img_write(worklab, p->detectionname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(worklab, p->detectionname, NULL, 0);
       worklab->name=NULL;
     }
 
@@ -668,7 +669,8 @@ detection_sn(struct noisechiselparams *p, gal_data_t 
*worklab, size_t num,
         {
           /* Get the flux weighted center coordinates. */
           for(j=0;j<ndim;++j)
-            coord[j]=GAL_DIMENSION_FLT_TO_INT(pos[i*pcols+j+1]/pos[i*pcols]);
+            coord[j]=GAL_DIMENSION_FLT_TO_INT(pos[i*pcols+j+1]
+                                              /pos[i*pcols]);
 
           /* Get the Sky standard deviation on this tile. */
           err  = ((float *)(p->std->array))[
@@ -764,8 +766,7 @@ detection_pseudo_remove_low_sn(struct noisechiselparams *p,
   if(p->detectionname)
     {
       workbin->name="TRUE-PSEUDOS";
-      gal_fits_img_write(workbin, p->detectionname, NULL,
-                         PROGRAM_NAME);
+      gal_fits_img_write(workbin, p->detectionname, NULL, 0);
       workbin->name=NULL;
     }
 
@@ -1170,8 +1171,7 @@ detection(struct noisechiselparams *p)
   if(p->detectionname)
     {
       workbin->name="DETECTIONS-INIT-TRUE";
-      gal_fits_img_write(workbin, p->detectionname, NULL,
-                         PROGRAM_NAME);
+      gal_fits_img_write(workbin, p->detectionname, NULL, 0);
       workbin->name=NULL;
     }
   if(!p->cp.quiet)
@@ -1254,8 +1254,7 @@ detection(struct noisechiselparams *p)
   if(p->detectionname)
     {
       p->olabel->name="DETECTION-FINAL";
-      gal_fits_img_write(p->olabel, p->detectionname, NULL,
-                         PROGRAM_NAME);
+      gal_fits_img_write(p->olabel, p->detectionname, NULL, 0);
       p->olabel->name=NULL;
     }
 
diff --git a/bin/noisechisel/noisechisel.c b/bin/noisechisel/noisechisel.c
index 5fe4d759..0bc2eb60 100644
--- a/bin/noisechisel/noisechisel.c
+++ b/bin/noisechisel/noisechisel.c
@@ -97,9 +97,9 @@ noisechisel_convolve(struct noisechiselparams *p)
   /* Save the convolution step if necessary. */
   if(p->detectionname)
     {
-      gal_fits_img_write(p->input, p->detectionname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(p->input, p->detectionname, NULL, 0);
       if(p->input!=p->conv)
-        gal_fits_img_write(p->conv, p->detectionname, NULL, PROGRAM_NAME);
+        gal_fits_img_write(p->conv, p->detectionname, NULL, 0);
     }
 
   /* Convolve with wider kernel (if requested). */
@@ -144,6 +144,12 @@ noisechisel_output(struct noisechiselparams *p)
 {
   gal_fits_list_key_t *keys=NULL;
 
+  /* Write the configuration keywords. */
+  gal_fits_key_list_title_add_end(&p->cp.ckeys, "Input file", 0);
+  gal_fits_key_write_filename("input", p->inputname, &p->cp.ckeys, 0,
+                              p->cp.quiet);
+  gal_fits_key_write(p->cp.ckeys, p->cp.output, "0", "NONE", 1, 1);
+
 
   /* Put a copy of the input into the output (when necessary). */
   if(p->rawoutput==0)
@@ -154,14 +160,15 @@ noisechisel_output(struct noisechiselparams *p)
       /* Correct the name of the input and write it out. */
       if(p->input->name) free(p->input->name);
       p->input->name="INPUT-NO-SKY";
-      gal_fits_img_write(p->input, p->cp.output, NULL, PROGRAM_NAME);
+      gal_fits_img_write(p->input, p->cp.output, NULL, 0);
       p->input->name=NULL;
     }
 
 
   /* Write the detected pixels and useful information into it's header. */
-  gal_fits_key_list_add(&keys, GAL_TYPE_FLOAT32, "DETSN", 0, &p->detsnthresh,
-                        0, "Minimum S/N of true pseudo-detections", 0,
+  gal_fits_key_list_add(&keys, GAL_TYPE_FLOAT32, "DETSN", 0,
+                        &p->detsnthresh, 0,
+                        "Minimum S/N of true pseudo-detections", 0,
                         "ratio", 0);
   if(p->label)
     gal_fits_key_list_add(&keys, GAL_TYPE_SIZE_T, "NUMLABS", 0,
@@ -171,13 +178,13 @@ noisechisel_output(struct noisechiselparams *p)
   if(p->label)
     {
       p->olabel->name = "DETECTIONS";
-      gal_fits_img_write(p->olabel, p->cp.output, keys, PROGRAM_NAME);
+      gal_fits_img_write(p->olabel, p->cp.output, keys, 1);
       p->olabel->name=NULL;
     }
   else
     {
       p->binary->name = "DETECTIONS";
-      gal_fits_img_write(p->binary, p->cp.output, keys, PROGRAM_NAME);
+      gal_fits_img_write(p->binary, p->cp.output, keys, 1);
       p->binary->name=NULL;
     }
   keys=NULL;
@@ -187,34 +194,26 @@ noisechisel_output(struct noisechiselparams *p)
   if(p->sky->name) free(p->sky->name);
   p->sky->name="SKY";
   gal_tile_full_values_write(p->sky, &p->cp.tl, !p->ignoreblankintiles,
-                             p->cp.output, NULL, PROGRAM_NAME);
+                             p->cp.output, NULL, 0);
   p->sky->name=NULL;
 
 
   /* Write the Sky standard deviation into the output. */
   p->std->name="SKY_STD";
-  gal_fits_key_list_add(&keys, GAL_TYPE_FLOAT32, "MAXSTD", 0, &p->maxstd, 0,
-                        "Maximum raw tile standard deviation", 0,
+  gal_fits_key_list_add(&keys, GAL_TYPE_FLOAT32, "MAXSTD", 0, &p->maxstd,
+                        0, "Maximum raw tile standard deviation", 0,
                         p->input->unit, 0);
-  gal_fits_key_list_add(&keys, GAL_TYPE_FLOAT32, "MINSTD", 0, &p->minstd, 0,
-                        "Minimum raw tile standard deviation", 0,
+  gal_fits_key_list_add(&keys, GAL_TYPE_FLOAT32, "MINSTD", 0, &p->minstd,
+                        0, "Minimum raw tile standard deviation", 0,
                         p->input->unit, 0);
-  gal_fits_key_list_add(&keys, GAL_TYPE_FLOAT32, "MEDSTD", 0, &p->medstd, 0,
-                        "Median raw tile standard deviation", 0,
+  gal_fits_key_list_add(&keys, GAL_TYPE_FLOAT32, "MEDSTD", 0, &p->medstd,
+                        0, "Median raw tile standard deviation", 0,
                         p->input->unit, 0);
   gal_tile_full_values_write(p->std, &p->cp.tl, !p->ignoreblankintiles,
-                             p->cp.output, keys, PROGRAM_NAME);
+                             p->cp.output, keys, 1);
   p->std->name=NULL;
 
 
-  /* Write the configuration keywords. */
-  gal_fits_key_write_filename("input", p->inputname, &p->cp.okeys, 1,
-                              p->cp.quiet);
-  gal_fits_key_write_config(&p->cp.okeys, "NoiseChisel configuration",
-                            "NOISECHISEL-CONFIG", p->cp.output, "0",
-                            "NONE");
-
-
   /* Let the user know that the output is written. */
   if(!p->cp.quiet)
     printf("  - Output written to '%s'.\n", p->cp.output);
diff --git a/bin/noisechisel/sky.c b/bin/noisechisel/sky.c
index 631f8c72..290619b7 100644
--- a/bin/noisechisel/sky.c
+++ b/bin/noisechisel/sky.c
@@ -207,7 +207,7 @@ sky_and_std(struct noisechiselparams *p, char *checkname)
   if(checkname && !tl->oneelempertile)
     {
       p->binary->name="DETECTED";
-      gal_fits_img_write(p->binary, checkname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(p->binary, checkname, NULL, 0);
       p->binary->name=NULL;
     }
 
@@ -230,9 +230,9 @@ sky_and_std(struct noisechiselparams *p, char *checkname)
       p->sky->name="SKY";
       p->std->name="STD";
       gal_tile_full_values_write(p->sky, tl, !p->ignoreblankintiles,
-                                 checkname, NULL, PROGRAM_NAME);
+                                 checkname, NULL, 0);
       gal_tile_full_values_write(p->std, tl, !p->ignoreblankintiles,
-                                 checkname, NULL, PROGRAM_NAME);
+                                 checkname, NULL, 0);
       p->sky->name=p->std->name=NULL;
     }
 
diff --git a/bin/noisechisel/threshold.c b/bin/noisechisel/threshold.c
index 094ad03b..b492d0b1 100644
--- a/bin/noisechisel/threshold.c
+++ b/bin/noisechisel/threshold.c
@@ -226,7 +226,7 @@ threshold_write_sn_table(struct noisechiselparams *p, 
gal_data_t *insn,
      FITS file. We have already deleted any existing file with the same
      name in 'ui_set_output_names'.*/
   gal_table_write(cols, NULL, comments, p->cp.tableformat, filename,
-                  extname, 0);
+                  extname, 0, 0);
 
 
   /* Clean up (if necessary). */
@@ -300,12 +300,12 @@ threshold_interp_smooth(struct noisechiselparams *p, 
gal_data_t **first,
       (*second)->name="THRESH2_INTERP";
       if(third) (*third)->name="THRESH3_INTERP";
       gal_tile_full_values_write(*first, tl, !p->ignoreblankintiles,
-                                 filename, NULL, PROGRAM_NAME);
+                                 filename, NULL, 0);
       gal_tile_full_values_write(*second, tl, !p->ignoreblankintiles,
-                                 filename, NULL, PROGRAM_NAME);
+                                 filename, NULL, 0);
       if(third)
         gal_tile_full_values_write(*third, tl, !p->ignoreblankintiles,
-                                   filename, NULL, PROGRAM_NAME);
+                                   filename, NULL, 0);
       (*first)->name = (*second)->name = NULL;
       if(third) (*third)->name=NULL;
     }
@@ -341,12 +341,12 @@ threshold_interp_smooth(struct noisechiselparams *p, 
gal_data_t **first,
           (*second)->name="THRESH2_SMOOTH";
           if(third) (*third)->name="THRESH3_SMOOTH";
           gal_tile_full_values_write(*first, tl, !p->ignoreblankintiles,
-                                     filename, NULL, PROGRAM_NAME);
+                                     filename, NULL, 0);
           gal_tile_full_values_write(*second, tl, !p->ignoreblankintiles,
-                                     filename, NULL, PROGRAM_NAME);
+                                     filename, NULL, 0);
           if(third)
             gal_tile_full_values_write(*third, tl, !p->ignoreblankintiles,
-                                       filename, NULL, PROGRAM_NAME);
+                                       filename, NULL, 0);
           (*first)->name = (*second)->name = NULL;
           if(third) (*third)->name=NULL;
         }
@@ -601,11 +601,11 @@ threshold_quantile_find_apply(struct noisechiselparams *p)
      the full input when 'oneelempertile' isn't requested. */
   if(p->qthreshname && !tl->oneelempertile)
     {
-      gal_fits_img_write(p->conv ? p->conv : p->input, p->qthreshname, NULL,
-                         PROGRAM_NAME);
+      gal_fits_img_write(p->conv ? p->conv : p->input, p->qthreshname,
+                         NULL, 0);
       if(p->wconv)
         gal_fits_img_write(p->wconv ? p->wconv : p->input, p->qthreshname,
-                           NULL, PROGRAM_NAME);
+                           NULL, 0);
     }
 
 
@@ -654,10 +654,10 @@ threshold_quantile_find_apply(struct noisechiselparams *p)
       qprm.noerode_th->name="QTHRESH_NOERODE";
       gal_tile_full_values_write(qprm.erode_th, tl,
                                  !p->ignoreblankintiles,
-                                 p->qthreshname, NULL, PROGRAM_NAME);
+                                 p->qthreshname, NULL, 0);
       gal_tile_full_values_write(qprm.noerode_th, tl,
                                  !p->ignoreblankintiles,
-                                 p->qthreshname, NULL, PROGRAM_NAME);
+                                 p->qthreshname, NULL, 0);
       qprm.erode_th->name=qprm.noerode_th->name=NULL;
 
       if(qprm.expand_th)
@@ -665,7 +665,7 @@ threshold_quantile_find_apply(struct noisechiselparams *p)
           qprm.expand_th->name="QTHRESH_EXPAND";
           gal_tile_full_values_write(qprm.expand_th, tl,
                                      !p->ignoreblankintiles,
-                                     p->qthreshname, NULL, PROGRAM_NAME);
+                                     p->qthreshname, NULL, 0);
           qprm.expand_th->name=NULL;
         }
     }
@@ -716,7 +716,7 @@ threshold_quantile_find_apply(struct noisechiselparams *p)
   if(p->qthreshname && !tl->oneelempertile)
     {
       p->binary->name="QTHRESH-APPLIED";
-      gal_fits_img_write(p->binary, p->qthreshname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(p->binary, p->qthreshname, NULL, 0);
       p->binary->name=NULL;
     }
 
diff --git a/bin/noisechisel/ui.c b/bin/noisechisel/ui.c
index 01da7438..f3857897 100644
--- a/bin/noisechisel/ui.c
+++ b/bin/noisechisel/ui.c
@@ -537,12 +537,12 @@ ui_prepare_tiles(struct noisechiselparams *p)
     {
       /* Large tiles. */
       check=gal_tile_block_check_tiles(ltl->tiles);
-      gal_fits_img_write(check, tl->tilecheckname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(check, tl->tilecheckname, NULL, 0);
       gal_data_free(check);
 
       /* Small tiles. */
       check=gal_tile_block_check_tiles(tl->tiles);
-      gal_fits_img_write(check, tl->tilecheckname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(check, tl->tilecheckname, NULL, 0);
       gal_data_free(check);
 
       /* If 'continueaftercheck' hasn't been called, abort NoiseChisel. */
diff --git a/bin/query/query.c b/bin/query/query.c
index a6280f47..3c8e4315 100644
--- a/bin/query/query.c
+++ b/bin/query/query.c
@@ -282,7 +282,7 @@ query_output_data(struct queryparams *p)
                        "NONE");
   gal_table_write(table, NULL, NULL, p->cp.tableformat,
                   p->cp.output ? p->cp.output : p->cp.output,
-                  "QUERY", 0);
+                  "QUERY", 0, 0);
 
   /* Get basic information about the table and free it. */
   p->outtableinfo[0]=table->size;
@@ -354,13 +354,11 @@ query_output_finalize(struct queryparams *p)
      output was a FITS file). */
   if( p->information==0 && gal_fits_name_is_fits(p->cp.output) )
     {
-      gal_fits_key_list_title_add_end(&p->cp.okeys,
+      gal_fits_key_list_title_add_end(&p->cp.ckeys,
                                       "Constructed query command", 0);
-      gal_fits_key_list_fullcomment_add_end(&p->cp.okeys,
+      gal_fits_key_list_fullcomment_add_end(&p->cp.ckeys,
                                             p->finalcommand, 1);
-      gal_fits_key_write_config(&p->cp.okeys, "Query settings",
-                                "QUERY-CONFIG", p->cp.output, "0",
-                                "NONE");
+      gal_fits_key_write(p->cp.ckeys, p->cp.output, "0", "NONE", 1, 0);
     }
 }
 
diff --git a/bin/segment/clumps.c b/bin/segment/clumps.c
index d20b21e6..b6b59661 100644
--- a/bin/segment/clumps.c
+++ b/bin/segment/clumps.c
@@ -556,7 +556,7 @@ clumps_write_sn_table(struct segmentparams *p, gal_data_t 
*insn,
 
   /* write the table. */
   gal_table_write(cols, NULL, comments, p->cp.tableformat, filename,
-                  "SKY_CLUMP_SN", 0);
+                  "SKY_CLUMP_SN", 0, 0);
 
   /* Clean up (if necessary). */
   if(sn!=insn) gal_data_free(sn);
@@ -651,17 +651,17 @@ clumps_true_find_sn_thresh(struct segmentparams *p)
             case 1: p->clabel->name = "SKY_CLUMPS_ALL";    break;
             case 2: p->clabel->name = "SKY_CLUMPS_FOR_SN"; break;
             default:
-              error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s so "
-                    "we can address the issue. The value %d is not valid for "
-                    "clprm.step", __func__, PACKAGE_BUGREPORT, clprm.step);
+              error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s "
+                    "so we can address the issue. The value %d is not "
+                    "valid for clprm.step", __func__, PACKAGE_BUGREPORT,
+                    clprm.step);
             }
 
           /* Write the demonstration array into the check image. The
              default values are hard to view, so we'll make a copy of the
              demo, set all Sky regions to blank and all clump macro values
              to zero. */
-          gal_fits_img_write(p->clabel, p->segmentationname, NULL,
-                             PROGRAM_NAME);
+          gal_fits_img_write(p->clabel, p->segmentationname, NULL, 0);
 
           /* Increment the step counter. */
           ++clprm.step;
diff --git a/bin/segment/segment.c b/bin/segment/segment.c
index 31a5d714..f6e24b05 100644
--- a/bin/segment/segment.c
+++ b/bin/segment/segment.c
@@ -1065,8 +1065,8 @@ segment_save_sn_table(struct clumps_params *clprm)
   /* Set the column pointers and write them into a table.. */
   clumpinobj->next=sn;
   objind->next=clumpinobj;
-  gal_table_write(objind, NULL, comments, p->cp.tableformat, p->clumpsn_d_name,
-                  "DET_CLUMP_SN", 0);
+  gal_table_write(objind, NULL, comments, p->cp.tableformat,
+                  p->clumpsn_d_name, "DET_CLUMP_SN", 0, 0);
 
 
   /* Clean up. */
@@ -1312,7 +1312,7 @@ segment_detections(struct segmentparams *p)
             }
 
           /* Write the demonstration array into the check image.  */
-          gal_fits_img_write(demo, p->segmentationname, NULL, PROGRAM_NAME);
+          gal_fits_img_write(demo, p->segmentationname, NULL, 0);
 
           /* Increment the step counter. */
           ++clprm.step;
@@ -1383,9 +1383,14 @@ segment_output(struct segmentparams *p)
   float *f, *ff;
   gal_fits_list_key_t *keys=NULL;
 
+  /* Write the configuration keywords. */
+  gal_fits_key_write_filename("input", p->inputname, &p->cp.ckeys, 1,
+                              p->cp.quiet);
+  gal_fits_key_write(p->cp.ckeys, p->cp.output, "0", "NONE", 1, 1);
+
   /* The Sky-subtracted input (if requested). */
   if(!p->rawoutput)
-    gal_fits_img_write(p->input, p->cp.output, NULL, PROGRAM_NAME);
+    gal_fits_img_write(p->input, p->cp.output, NULL, 0);
 
   /* The clump labels. */
   gal_fits_key_list_add(&keys, GAL_TYPE_FLOAT32, "CLUMPSN", 0,
@@ -1395,7 +1400,7 @@ segment_output(struct segmentparams *p)
                         &p->numclumps, 0, "Total number of clumps", 0,
                         "counter", 0);
   p->clabel->name="CLUMPS";
-  gal_fits_img_write(p->clabel, p->cp.output, keys, PROGRAM_NAME);
+  gal_fits_img_write(p->clabel, p->cp.output, keys, 1);
   p->clabel->name=NULL;
   keys=NULL;
 
@@ -1403,10 +1408,10 @@ segment_output(struct segmentparams *p)
   if(!p->noobjects)
     {
       gal_fits_key_list_add(&keys, GAL_TYPE_SIZE_T, "NUMLABS", 0,
-                            &p->numobjects, 0, "Total number of objects", 0,
-                            "counter", 0);
+                            &p->numobjects, 0, "Total number of objects",
+                            0, "counter", 0);
       p->olabel->name="OBJECTS";
-      gal_fits_img_write(p->olabel, p->cp.output, keys, PROGRAM_NAME);
+      gal_fits_img_write(p->olabel, p->cp.output, keys, 1);
       p->olabel->name=NULL;
       keys=NULL;
     }
@@ -1443,21 +1448,15 @@ segment_output(struct segmentparams *p)
       if(p->std->size == p->input->size)
         {
           p->std->wcs=p->input->wcs;
-          gal_fits_img_write(p->std, p->cp.output, keys, PROGRAM_NAME);
+          gal_fits_img_write(p->std, p->cp.output, keys, 1);
           p->std->wcs=NULL;
         }
       else
-        gal_tile_full_values_write(p->std, &p->cp.tl, 1, p->cp.output, keys,
-                                   PROGRAM_NAME);
+        gal_tile_full_values_write(p->std, &p->cp.tl, 1, p->cp.output,
+                                   keys, 1);
       p->std->name=NULL;
     }
 
-  /* Write the configuration keywords. */
-  gal_fits_key_write_filename("input", p->inputname, &p->cp.okeys, 1,
-                              p->cp.quiet);
-  gal_fits_key_write_config(&p->cp.okeys, "Segment configuration",
-                            "SEGMENT-CONFIG", p->cp.output, "0", "NONE");
-
   /* Let the user know that the output is written. */
   if(!p->cp.quiet)
     printf("  - Output written to '%s'.\n", p->cp.output);
@@ -1505,12 +1504,11 @@ segment(struct segmentparams *p)
      in. */
   if(p->segmentationname)
     {
-      gal_fits_img_write(p->input, p->segmentationname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(p->input, p->segmentationname, NULL, 0);
       if(p->input!=p->conv)
-        gal_fits_img_write(p->conv, p->segmentationname, NULL, PROGRAM_NAME);
+        gal_fits_img_write(p->conv, p->segmentationname, NULL, 0);
       p->olabel->name="DETECTION_LABELS";
-      gal_fits_img_write(p->olabel, p->segmentationname, NULL,
-                         PROGRAM_NAME);
+      gal_fits_img_write(p->olabel, p->segmentationname, NULL, 0);
       p->olabel->name=NULL;
     }
   if(!p->cp.quiet)
diff --git a/bin/segment/ui.c b/bin/segment/ui.c
index c5f1b556..cc9edbf4 100644
--- a/bin/segment/ui.c
+++ b/bin/segment/ui.c
@@ -643,12 +643,12 @@ ui_prepare_tiles(struct segmentparams *p)
     {
       /* Large tiles. */
       check=gal_tile_block_check_tiles(ltl->tiles);
-      gal_fits_img_write(check, tl->tilecheckname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(check, tl->tilecheckname, NULL, 0);
       gal_data_free(check);
 
       /* Small tiles. */
       check=gal_tile_block_check_tiles(tl->tiles);
-      gal_fits_img_write(check, tl->tilecheckname, NULL, PROGRAM_NAME);
+      gal_fits_img_write(check, tl->tilecheckname, NULL, 0);
       gal_data_free(check);
 
       /* If 'continueaftercheck' hasn't been called, abort NoiseChisel. */
diff --git a/bin/statistics/sky.c b/bin/statistics/sky.c
index af83f47a..273b2b3c 100644
--- a/bin/statistics/sky.c
+++ b/bin/statistics/sky.c
@@ -166,9 +166,10 @@ sky(struct statisticsparams *p)
         printf("  - Kernel: %s (hdu: %s)\n", p->kernelname, p->khdu);
     }
 
+
   /* When checking steps, the input image is the first extension. */
   if(p->checksky)
-    gal_fits_img_write(p->input, p->checkskyname, NULL, PROGRAM_NAME);
+    gal_fits_img_write(p->input, p->checkskyname, NULL,  0);
 
 
   /* Convolve the image (if desired). */
@@ -179,8 +180,7 @@ sky(struct statisticsparams *p)
                                         cp->numthreads, 1,
                                         tl->workoverch, 0);
       if(p->checksky)
-        gal_fits_img_write(p->convolved, p->checkskyname, NULL,
-                           PROGRAM_NAME);
+        gal_fits_img_write(p->convolved, p->checkskyname, NULL, 0);
       if(!cp->quiet)
         gal_timing_report(&t1, "Input convolved with kernel.", 1);
     }
@@ -212,9 +212,9 @@ sky(struct statisticsparams *p)
   if(p->checksky)
     {
       gal_tile_full_values_write(p->sky_t, tl, !p->ignoreblankintiles,
-                                 p->checkskyname, NULL, PROGRAM_NAME);
+                                 p->checkskyname, NULL, 0);
       gal_tile_full_values_write(p->std_t, tl, !p->ignoreblankintiles,
-                                 p->checkskyname, NULL, PROGRAM_NAME);
+                                 p->checkskyname, NULL, 0);
     }
 
 
@@ -244,9 +244,9 @@ sky(struct statisticsparams *p)
   if(p->checksky)
     {
       gal_tile_full_values_write(p->sky_t, tl, !p->ignoreblankintiles,
-                                 p->checkskyname, NULL, PROGRAM_NAME);
+                                 p->checkskyname, NULL, 0);
       gal_tile_full_values_write(p->std_t, tl, !p->ignoreblankintiles,
-                                 p->checkskyname, NULL, PROGRAM_NAME);
+                                 p->checkskyname, NULL, 0);
     }
 
 
@@ -268,9 +268,9 @@ sky(struct statisticsparams *p)
       if(p->checksky)
         {
           gal_tile_full_values_write(p->sky_t, tl, !p->ignoreblankintiles,
-                                     p->checkskyname, NULL, PROGRAM_NAME);
+                                     p->checkskyname, NULL, 0);
           gal_tile_full_values_write(p->std_t, tl, !p->ignoreblankintiles,
-                                     p->checkskyname, NULL, PROGRAM_NAME);
+                                     p->checkskyname, NULL, 0);
           if(!cp->quiet)
             printf("  - Check image written to '%s'.\n", p->checkskyname);
         }
@@ -290,14 +290,13 @@ sky(struct statisticsparams *p)
   p->std_t->name="SKY_STD";
   p->cp.keepinputdir=keepinputdir;
   gal_tile_full_values_write(p->sky_t, tl, !p->ignoreblankintiles, outname,
-                             NULL, PROGRAM_NAME);
+                             NULL, 0);
   gal_tile_full_values_write(p->std_t, tl, !p->ignoreblankintiles, outname,
-                             NULL, PROGRAM_NAME);
+                             NULL, 0);
   p->sky_t->name = p->std_t->name = NULL;
-  gal_fits_key_write_filename("input", p->inputname, &p->cp.okeys, 1,
+  gal_fits_key_write_filename("input", p->inputname, &p->cp.ckeys, 1,
                               p->cp.quiet);
-  gal_fits_key_write_config(&p->cp.okeys, "Statistics configuration",
-                            "STATISTICS-CONFIG", outname, "0", "NONE");
+  gal_fits_key_write(p->cp.ckeys, outname, "0", "NONE", 1, 0);
   if(!cp->quiet)
     printf("  - Sky and its STD written to '%s'.\n", outname);
 
diff --git a/bin/statistics/statistics.c b/bin/statistics/statistics.c
index 809e369f..372d0ad3 100644
--- a/bin/statistics/statistics.c
+++ b/bin/statistics/statistics.c
@@ -378,22 +378,21 @@ statistics_interpolate_and_write(struct statisticsparams 
*p,
       && !(p->cp.interponlyblank && gal_blank_present(values, 1)==0) )
     {
       interpd=gal_interpolate_neighbors(values, &cp->tl,
-                                        cp->interpmetric,
-                                        cp->interpnumngb,
-                                        cp->numthreads,
-                                        cp->interponlyblank, 0,
-                                        GAL_INTERPOLATE_NEIGHBORS_FUNC_MEDIAN);
+                              cp->interpmetric,
+                              cp->interpnumngb,
+                              cp->numthreads,
+                              cp->interponlyblank, 0,
+                              GAL_INTERPOLATE_NEIGHBORS_FUNC_MEDIAN);
       gal_data_free(values);
       values=interpd;
     }
 
   /* Write the values. */
   gal_tile_full_values_write(values, &cp->tl, !p->ignoreblankintiles,
-                             output, NULL, PROGRAM_NAME);
-  gal_fits_key_write_filename("input", p->inputname, &p->cp.okeys, 1,
+                             output, NULL, 0);
+  gal_fits_key_write_filename("input", p->inputname, &p->cp.ckeys, 1,
                               p->cp.quiet);
-  gal_fits_key_write_config(&p->cp.okeys, "Statistics configuration",
-                            "STATISTICS-CONFIG", output, "0", "NONE");
+  gal_fits_key_write(p->cp.ckeys, output, "0", "NONE", 1, 0);
 }
 
 
@@ -764,16 +763,15 @@ write_output_table(struct statisticsparams *p, gal_data_t 
*table,
   /* Write the table. */
   gal_checkset_writable_remove(output, p->inputname, 0, p->cp.dontdelete);
   gal_table_write(table, NULL, comments, p->cp.tableformat, output,
-                  "TABLE", 0);
+                  "TABLE", 0, 0);
 
 
   /* Write the configuration information if we have a FITS output. */
   if(isfits)
     {
-      gal_fits_key_write_filename("input", p->inputname, &p->cp.okeys, 1,
+      gal_fits_key_write_filename("input", p->inputname, &p->cp.ckeys, 1,
                                   p->cp.quiet);
-      gal_fits_key_write_config(&p->cp.okeys, "Statistics configuration",
-                                "STATISTICS-CONFIG", output, "0", "NONE");
+      gal_fits_key_write(p->cp.ckeys, output, "0", "NONE", 1, 0);
     }
 
 
@@ -944,11 +942,10 @@ histogram_2d(struct statisticsparams *p)
 
       /* Write the output. */
       output=statistics_output_name(p, suf, &isfits);
-      gal_fits_img_write(img, output, NULL, PROGRAM_STRING);
-      gal_fits_key_write_filename("input", p->inputname, &p->cp.okeys, 1,
+      gal_fits_img_write(img, output, NULL, 0);
+      gal_fits_key_write_filename("input", p->inputname, &p->cp.ckeys, 1,
                                   p->cp.quiet);
-      gal_fits_key_write_config(&p->cp.okeys, "Statistics configuration",
-                                "STATISTICS-CONFIG", output, "0", "NONE");
+      gal_fits_key_write(p->cp.ckeys, output, "0", "NONE", 1, 0);
 
       /* Clean up and let the user know that the histogram is built. */
       free(ctype[0]);
@@ -1303,17 +1300,20 @@ statistics_fit_params_to_keys(struct statisticsparams 
*p, gal_data_t *fit,
   gal_fits_key_list_title_add(&out, "Fit results", 0);
   gal_fits_key_list_add(&out, GAL_TYPE_STRING, "FITTYPE", 0,
                         gal_fit_name_from_id(p->fitid), 0,
-                        "Functional form of the fitting.", 0, NULL, 0);
+                        "Functional form of the fitting.",
+                        0, NULL, 0);
   if( p->fitid==GAL_FIT_POLYNOMIAL
       || p->fitid==GAL_FIT_POLYNOMIAL_ROBUST
       || p->fitid==GAL_FIT_POLYNOMIAL_WEIGHTED )
     gal_fits_key_list_add(&out, GAL_TYPE_SIZE_T, "FITMAXP", 0,
                           &p->fitmaxpower, 0,
-                          "Maximum power of polynomial.", 0, NULL, 0);
+                          "Maximum power of polynomial.",
+                          0, NULL, 0);
   if( p->fitid==GAL_FIT_POLYNOMIAL_ROBUST )
     gal_fits_key_list_add(&out, GAL_TYPE_STRING, "FITRTYP", 0,
                           p->fitrobustname, 0,
-                          "Function for removing outliers", 0, NULL, 0);
+                          "Function for removing outliers",
+                          0, NULL, 0);
   gal_fits_key_list_add(&out, GAL_TYPE_STRING, "FITIN", 0,
                         p->inputname, 0,"Name of file with input columns.",
                         0, NULL, 0);
@@ -1331,7 +1331,8 @@ statistics_fit_params_to_keys(struct statisticsparams *p, 
gal_data_t *fit,
     {
       gal_fits_key_list_add(&out, GAL_TYPE_STRING, "FITWCOL", 0,
                             p->columns->next->next->v, 0,
-                            "Name or Number of weight column.", 0, NULL, 0);
+                            "Name or Number of weight column.",
+                            0, NULL, 0);
       gal_fits_key_list_add(&out, GAL_TYPE_STRING, "FITWNAT", 0,
                             whtnat, 0, "Nature of weight column.",
                             0, NULL, 0);
@@ -1342,7 +1343,8 @@ statistics_fit_params_to_keys(struct statisticsparams *p, 
gal_data_t *fit,
                           "Robust fitting (rejecting outliers) function.",
                           0, NULL, 0);
   gal_fits_key_list_add(&out, GAL_TYPE_FLOAT64, "FRDCHISQ", 0,
-                        redchisq, 0, "Reduced chi^2 of fit.", 0, NULL, 0);
+                        redchisq, 0, "Reduced chi^2 of fit.",
+                        0, NULL, 0);
 
   /* Add the Fitting results. */
   switch(p->fitid)
@@ -1483,8 +1485,8 @@ statistics_fit_estimate(struct statisticsparams *p, 
gal_data_t *fit,
             printf("  Written to: %s\n", p->cp.output);
         }
       keys=statistics_fit_params_to_keys(p, fit, whtnat, redchisq);
-      gal_table_write(p->fitestval, &keys, NULL, p->cp.tableformat,
-                      p->cp.output, "FIT_ESTIMATE", 0);
+      gal_table_write(p->fitestval, keys, NULL, p->cp.tableformat,
+                      p->cp.output, "FIT_ESTIMATE", 0, 1);
     }
 
   /* Print estimated value on the commandline. */
diff --git a/bin/statistics/ui.c b/bin/statistics/ui.c
index 7dce8312..8de836f5 100644
--- a/bin/statistics/ui.c
+++ b/bin/statistics/ui.c
@@ -1199,14 +1199,13 @@ ui_preparations(struct statisticsparams *p)
                                                           "_tiled.fits");
           check=gal_tile_block_check_tiles(tl->tiles);
           if(p->inputformat==INPUT_FORMAT_IMAGE)
-            gal_fits_img_write(check, tl->tilecheckname, NULL,
-                               PROGRAM_NAME);
+            gal_fits_img_write(check, tl->tilecheckname, NULL, 0);
           else
             {
               gal_checkset_writable_remove(tl->tilecheckname, p->inputname,
                                            0, cp->dontdelete);
               gal_table_write(check, NULL, NULL, cp->tableformat,
-                              tl->tilecheckname, "TABLE", 0);
+                              tl->tilecheckname, "TABLE", 0, 0);
             }
           gal_data_free(check);
         }
diff --git a/bin/table/table.c b/bin/table/table.c
index 2ce80e49..607259f4 100644
--- a/bin/table/table.c
+++ b/bin/table/table.c
@@ -1603,8 +1603,8 @@ table(struct tableparams *p)
   if(p->table)
     {
       table_txt_formats(p);
-      gal_table_write(p->table, NULL, NULL, p->cp.tableformat, p->cp.output,
-                      "TABLE", p->colinfoinstdout);
+      gal_table_write(p->table, NULL, NULL, p->cp.tableformat,
+                      p->cp.output, "TABLE", p->colinfoinstdout, 0);
     }
   else
     error(EXIT_FAILURE, 0, "no output columns");
diff --git a/bin/warp/warp.c b/bin/warp/warp.c
index 4d025e5e..8daccd1a 100644
--- a/bin/warp/warp.c
+++ b/bin/warp/warp.c
@@ -416,7 +416,8 @@ warp_write_to_file(struct warpparams *p, int hasmatrix)
         sprintf(&keyword[i*FLEN_KEYWORD], "WMTX%zu_%zu", i/3+1, i%3+1);
         gal_fits_key_list_add_end(&headers, GAL_TYPE_FLOAT64,
                                   &keyword[i*FLEN_KEYWORD], 0, &m[i], 0,
-                                  "Warp matrix element value", 0, NULL, 0);
+                                  "Warp matrix element value",
+                                  0, NULL, 0);
       }
 
   /* Convert the output image if needed. */
@@ -425,16 +426,15 @@ warp_write_to_file(struct warpparams *p, int hasmatrix)
 
   /* Save the output and 'MAX-FRAC' if available. */
   for(tmp=p->output;tmp!=NULL;tmp=tmp->next)
-    gal_fits_img_write(tmp, p->cp.output, NULL, PROGRAM_NAME);
+    gal_fits_img_write(tmp, p->cp.output, NULL, 0);
 
   /* Write the configuration keywords on HDU/extension '0'. */
-  gal_fits_key_write_filename("input", p->inputname, &p->cp.okeys,
+  gal_fits_key_write_filename("input", p->inputname, &p->cp.ckeys,
                               1, p->cp.quiet);
-  gal_fits_key_write_config(&p->cp.okeys, "Warp configuration",
-                            "WARP-CONFIG", p->cp.output, "0", "NONE");
+  gal_fits_key_write(p->cp.ckeys, p->cp.output, "0", "NONE", 1, 0);
 
   /* Write headers on HDU/extension '1'. */
-  gal_fits_key_write(&headers, NULL, p->cp.output, "1", "NONE");
+  gal_fits_key_write(headers, p->cp.output, "1", "NONE", 1, 0);
 }
 
 
diff --git a/doc/gnuastro.texi b/doc/gnuastro.texi
index 99733163..fc4aceab 100644
--- a/doc/gnuastro.texi
+++ b/doc/gnuastro.texi
@@ -13822,6 +13822,18 @@ A FITS ASCII table (see @ref{Recognized table 
formats}).
 A FITS binary table (see @ref{Recognized table formats}).
 @end table
 
+@item --outfitsnoconfig
+Do not write any of the program's metadata (option values or versions and 
dates) into the 0-th HDU of the output FITS file, see @ref{Output FITS files}.
+
+@item --outfitsnodate
+Do not write the @code{DATE} or @code{DATEUTC} keywords into the 0-th HDU of 
the output FITS file, see @ref{Output FITS files}.
+
+@item --outfitsnocommit
+Do not write the @code{COMMIT} keyword into the 0-th HDU of the output FITS 
file, see @ref{Output FITS files}.
+
+@item --outfitsnoversions
+Do not write the versions of any dependency software into the 0-th HDU of the 
output FITS file, see @ref{Output FITS files}.
+
 @end vtable
 
 
@@ -15608,8 +15620,9 @@ For more on this standard, please see @ref{Fits}.
 
 As a community convention described in @ref{Fits}, the first extension of all 
FITS files produced by Gnuastro's programs only contains the meta-data that is 
intended for the file's extension(s).
 For a Gnuastro program, this generic meta-data (that is stored as FITS keyword 
records) is its configuration when it produced this dataset: file name(s) of 
input(s) and option names, values and comments.
-Note that when the configuration is too trivial (only input filename, for 
example, the program @ref{Table}) no meta-data is written in this extension.
+You can use the @option{--outfitsnoconfig} option to stop the programs from 
writing these keywords into the first extension of their output.
 
+When the configuration is too trivial (only input filename, for example, the 
program @ref{Table}) no meta-data is written in this extension.
 FITS keywords have the following limitations in regards to generic option 
names and values which are described below:
 
 @itemize
@@ -15630,7 +15643,7 @@ $ astfits image_detected.fits -h0 | grep -i snquant
 @end itemize
 
 The keywords above are classified (separated by an empty line and title) as a 
group titled ``ProgramName configuration''.
-This meta-data extension, as well as all the other extensions (which contain 
data), also contain have final group of keywords to keep the basic date and 
version information of Gnuastro, its dependencies and the pipeline that is 
using Gnuastro (if it is under version control).
+This meta-data extension also contains a final group of keywords to keep the 
basic date and version information of Gnuastro, its dependencies and the 
pipeline that is using Gnuastro (if it is under version control); they are 
listed below.
 
 @table @command
 
@@ -15638,6 +15651,14 @@ This meta-data extension, as well as all the other 
extensions (which contain dat
 The creation time of the FITS file.
 This date is written directly by CFITSIO and is in UT format.
 
+While the date can be a good metadata in most scenarios, it does have a 
caveat: when everything else in your output is the same between multiple runs, 
the date will be different!
+If exact reproducibility is important for you, this can be annoying!
+To stop any Gnuastro program from writing the @code{DATE} keyword, you can use 
the @option{--outfitsnodate} (see @ref{Input output options}).
+
+@item DATEUTC
+If the date in the @code{DATE} keyword is in 
@url{https://en.wikipedia.org/wiki/Coordinated_Universal_Time, UTC}, this 
keyword will have a value of 1; otherwise, it will have a value of 0.
+If @code{DATE} is not written, this is also ignored.
+
 @item COMMIT
 Git's commit description from the running directory of Gnuastro's programs.
 If the running directory is not version controlled or @file{libgit2} is not 
installed (see @ref{Optional dependencies}) then this keyword will not be 
present.
@@ -15667,34 +15688,27 @@ One important feature of version control is that the 
research result (FITS image
 This information will enable you to exactly reproduce that same result later, 
even if you have made changes/progress.
 For one example of a research paper's reproduction pipeline, please see the 
@url{https://gitlab.com/makhlaghi/NoiseChisel-paper, reproduction pipeline} of 
the @url{https://arxiv.org/abs/1505.01664, paper} describing @ref{NoiseChisel}.
 
+In case you don't want the @code{COMMIT} keyword in the first extension of 
your output FITS file, you can use the @option{--outfitsnocommit} option (see 
@ref{Input output options}).
+
 @item CFITSIO
 The version of CFITSIO used (see @ref{CFITSIO}).
+This can be disabled with @option{--outfitsnoversions} (see @ref{Input output 
options}).
 
 @item WCSLIB
 The version of WCSLIB used (see @ref{WCSLIB}).
 Note that older versions of WCSLIB do not report the version internally.
 So this is only available if you are using more recent WCSLIB versions.
+This can be disabled with @option{--outfitsnoversions} (see @ref{Input output 
options}).
 
 @item GSL
 The version of GNU Scientific Library that was used, see @ref{GNU Scientific 
Library}.
+This can be disabled with @option{--outfitsnoversions} (see @ref{Input output 
options}).
 
 @item GNUASTRO
 The version of Gnuastro used (see @ref{Version numbering}).
+This can be disabled with @option{--outfitsnoversions} (see @ref{Input output 
options}).
 @end table
 
-Here is one example of the last few lines of an example output.
-
-@example
-              / Versions and date
-DATE    = '...'                / file creation date
-COMMIT  = 'v0-8-g547f6eb'      / Commit description in running dir.
-CFITSIO = '3.45    '           / CFITSIO version.
-WCSLIB  = '5.19    '           / WCSLIB version.
-GSL     = '2.5     '           / GNU Scientific Library version.
-GNUASTRO= '0.7     '           / GNU Astronomy Utilities version.
-END
-@end example
-
 @node Numeric locale,  , Output FITS files, Common program behavior
 @section Numeric locale
 
@@ -38983,7 +38997,7 @@ If it is @code{NULL}, this line is ignored.
 @end itemize
 @end deftypefun
 
-@deftypefun void gal_table_write (gal_data_t @code{*cols}, struct 
gal_fits_list_key_t @code{**keywords}, gal_list_str_t @code{*comments}, int 
@code{tableformat}, char @code{*filename}, char @code{*extname}, uint8_t 
@code{colinfoinstdout})
+@deftypefun void gal_table_write (gal_data_t @code{*cols}, struct 
gal_fits_list_key_t @code{**keylist}, gal_list_str_t @code{*comments}, int 
@code{tableformat}, char @code{*filename}, char @code{*extname}, uint8_t 
@code{colinfoinstdout}, int @code{freekeys})
 
 Write @code{cols} (a list of datasets, see @ref{List of gal_data_t}) into a 
table stored in @code{filename}.
 The format of the table can be determined with @code{tableformat} that accepts 
the macros defined above.
@@ -39404,16 +39418,36 @@ If @code{cfree} is non-zero, the space allocated for 
@code{comment} will be free
 Similar to @code{gal_fits_key_list_title_add}, but put the comments at the end 
of the list.
 @end deftypefun
 
-@deftypefun void gal_fits_key_list_comment_add (gal_fits_list_key_t 
@code{**list}, char @code{*comment}, int @code{fcfree})
+@deftypefun void gal_fits_key_list_fullcomment_add (gal_fits_list_key_t 
@code{**list}, char @code{*comment}, int @code{fcfree})
 Add a @code{COMMENT} keyword to the top of the keywords list.
 If the comment is longer than 70 characters, CFITSIO will automatically break 
it into multiple @code{COMMENT} keywords.
 If @code{fcfree} is non-zero, the space allocated for @code{comment} will be 
freed immediately after writing the keyword (in another function).
 @end deftypefun
 
-@deftypefun void gal_fits_key_list_comment_add_end (gal_fits_list_key_t 
@code{**list}, char @code{*comment}, int @code{fcfree})
+@deftypefun void gal_fits_key_list_fullcomment_add_end (gal_fits_list_key_t 
@code{**list}, char @code{*comment}, int @code{fcfree})
 Similar to @code{gal_fits_key_list_comment_add}, but put the comments at the 
end of the list.
 @end deftypefun
 
+@deftypefun void gal_fits_key_list_add_date (gal_fits_list_key_t 
@code{**keylist}, char @code{*comment})
+Add a @code{DATE} keyword to the input list of keywords containing the date 
this function was activated in the format of @code{YYYY-MM-DDThh:mm:ss}.
+This function will also add a @code{DATEUTC} keyword that specifies if the 
date is in UTC or local time (this depends on CFITSIO being able to detect UTC 
in the running operating system or not).
+
+The comment of the keyword should also be specified as the second argument.
+The comment is useful to inform users what this date refers to; for example 
the program starting time, its ending time, or etc.
+For more, see the description under @code{DATE} in @ref{Output FITS files}.
+@end deftypefun
+
+@deftypefun void gal_fits_key_list_add_software_versions (gal_fits_list_key_t 
@code{**keylist})
+Add the version of Gnuastro @ref{Mandatory dependencies} to the list of 
keywords.
+Each software's keyword has the same name as the software itself (for example 
@code{GNUASTRO} or @code{GSL}.
+For the full list of software, see @ref{Output FITS files}.
+@end deftypefun
+
+@deftypefun void gal_fits_key_list_add_git_commit (gal_fits_list_key_t 
@code{**keylist})
+If the optional libgit2 dependency is installed and your program is being run 
in a directory that is under version control, a @code{COMMIT} keyword will be 
added on the top of the list of keywords.
+For more, see the description of @code{COMMIT} in @ref{Output FITS files}.
+@end deftypefun
+
 @deftypefun void gal_fits_key_list_reverse (gal_fits_list_key_t @code{**list})
 Reverse the input list of keywords.
 @end deftypefun
@@ -39447,8 +39481,14 @@ Write the WCS header string (produced with WCSLIB's 
@code{wcshdo} function) into
 This function will put a few blank keyword lines along with a comment 
@code{WCS information} before writing each keyword record.
 @end deftypefun
 
-@deftypefun void gal_fits_key_write (gal_fits_list_key_t @code{**keylist}, 
char @code{*title}, char @code{*filename}, char @code{*hdu})
-Write the list of keywords in @code{keylist} into the @code{hdu} extension of 
the file called @code{filename} (the file must already exist) and free the list.
+@deftypefun void gal_fits_key_write (gal_fits_list_key_t @code{**keylist}, 
char @code{*filename}, char @code{*hdu}, char @code{*hdu_option_name}, int 
@code{freekeys}, int @code{create_fits_not_exists})
+Write the list of keywords in @code{keylist} into the @code{hdu} extension of 
the file called @code{filename}.
+If the file may not exist before this function is activated, set 
@code{create_fits_not_exists} to non-zero and set the HDU to @code{"0"}.
+If the keywords should be freed after they are written, set the 
@code{freekeys} value to non-zero.
+
+The @code{hdu_option_name} will be used in error messages if the HDU in the 
file cannot be opened for writing.
+This is useful for the users of your programs to inform them what option they 
should give a HDU to.
+If you don't have an option that is configured by the users of your program, 
you can set this to @code{NONE}.
 
 The list nodes are meant to be dynamically allocated (because they will be 
freed after being written).
 We thus recommend using the @code{gal_fits_key_list_add} or 
@code{gal_fits_key_list_add_end} to create and fill the list.
@@ -39470,43 +39510,18 @@ int main()
   char *comment="A good description of the key";
   gal_fits_key_list_add_end(&keylist, GAL_TYPE_FLOAT32, keyname, 0,
                             &value, 0, comment, 0, unit, 0);
-  gal_fits_key_write(&keylist, "Matching metadata", filename, "1");
+  gal_fits_key_list_title_add(&keylist, "Matching metadata", 0);
+  gal_fits_key_write(&keylist, filename, "1", "NONE", 1, 0);
   return EXIT_SUCCESS;
 @}
 @end example
 @end deftypefun
 
-@deftypefun void gal_fits_key_write_in_ptr (gal_fits_list_key_t 
@code{**keylist}, fitsfile @code{*fptr})
+@deftypefun void gal_fits_key_write_in_ptr (gal_fits_list_key_t 
@code{**keylist}, fitsfile @code{*fptr}, int @code{freekeys})
 Write the list of keywords in @code{keylist} into the given CFITSIO 
@code{fitsfile} pointer and free keylist.
 For more on the input @code{keylist}, see the description and example for 
@code{gal_fits_key_write}, above.
 @end deftypefun
 
-@deftypefun void gal_fits_key_write_version (gal_fits_list_key_t 
@code{**keylist}, char @code{*title}, char @code{*filename}, char @code{*hdu})
-Write the (optional, when @code{keylist!=NULL}) given list of keywords under 
the optional FITS keyword @code{title}, then print all the important version 
and date information.
-This is basically, just a wrapper over 
@code{gal_fits_key_write_version_in_ptr}.
-@end deftypefun
-
-@deftypefun void gal_fits_key_write_version_in_ptr (gal_fits_list_key_t 
@code{**keylist}, char @code{*title}, fitsfile @code{*fptr})
-Write or update (all the) keyword(s) in @code{headers} into the FITS
-pointer, but also the date, name of your program (@code{program_name}),
-along with the versions of CFITSIO, WCSLIB (when available), GSL, Gnuastro,
-and (the possible) commit information into the header as described in
-@ref{Output FITS files}.
-
-Since the data processing depends on the versions of the libraries you have 
used, it is strongly recommended to include this information in every FITS 
output.
-@code{gal_fits_img_write} and @code{gal_fits_tab_write} will automatically use 
this function.
-@end deftypefun
-
-@deftypefun void gal_fits_key_write_config (gal_fits_list_key_t 
@code{**keylist}, char @code{*title}, char @code{*extname}, char 
@code{*filename}, char @code{*hdu}, char @code{*hdu_option_name})
-Write the given keyword list (@code{keylist}) into the @code{hdu} extension of 
@code{filename}, ending it with version information.
-This function will write @code{extname} as the name of the extension (value to 
the standard @code{EXTNAME} FITS keyword).
-The list of keywords will then be printed under a title called @code{title}.
-
-This function is used by many Gnuastro programs and is primarily intended for 
writing configuration settings of a program into the zero-th extension of their 
FITS outputs (which is empty when the FITS file is created by Gnuastro's 
program and this library).
-
-For more on @code{hdu_option_name} see the description of 
@code{gal_fits_hdu_open} in @ref{FITS HDUs}.
-@end deftypefun
-
 @deftypefun {gal_list_str_t *} gal_fits_with_keyvalue (gal_list_str_t *files, 
char *hdu, char *name, gal_list_str_t *values)
 Given a list of FITS file names (@code{files}), a certain HDU (@code{hdu}), a 
certain keyword name (@code{name}), and a list of acceptable values 
(@code{values}), return the subset of file names where the requested keyword 
name has one of the acceptable values.
 @end deftypefun
@@ -39574,29 +39589,36 @@ The finally returned dataset will have a 
@code{float32} type.
 For more on @code{hdu_option_name} see the description of 
@code{gal_fits_hdu_open} in @ref{FITS HDUs}.
 @end deftypefun
 
-@deftypefun {fitsfile *} gal_fits_img_write_to_ptr (gal_data_t @code{*input}, 
char @code{*filename})
+@deftypefun {fitsfile *} gal_fits_img_write_to_ptr (gal_data_t @code{*input}, 
char @code{*filename}, gal_fits_list_key_t @code{*keylist}, int @code{freekeys})
 Write the @code{input} dataset into a FITS file named @file{filename} and 
return the corresponding CFITSIO @code{fitsfile} pointer.
 This function will not close @code{fitsfile}, so you can still add other 
extensions to it after this function or make other modifications.
+
+In case you want to add keywords into the HDU that containst he data, you can 
use the second two arguments (see the description of @code{gal_fits_key_write}).
+These keywords will be written into the HDU before writing the data: when 
there are more than roughly 5 keywords (assuming your dataset has WCS) and your 
dataset is large, this can result in significant optimization of the running 
time (because adding a keyword beyond the 36 key slots will cause the whole 
data to shift for another block of 36 keywords).
 @end deftypefun
 
-@deftypefun void gal_fits_img_write (gal_data_t @code{*data}, char 
@code{*filename}, gal_fits_list_key_t @code{*headers}, char 
@code{*program_string})
+@deftypefun void gal_fits_img_write (gal_data_t @code{*data}, char 
@code{*filename}, gal_fits_list_key_t @code{*keylist}, int @code{freekeys})
 Write the @code{input} dataset into the FITS file named @file{filename}.
-Also add the @code{headers} keywords to the newly created HDU/extension
-along with your program's name (@code{program_string}).
+Also add the list of header keywords (@code{keylist}) to the newly created 
HDU/extension
+The list of keywords will be freed after writing into the HDU, if you need 
them later, keep a separate copy of the list before calling this function.
+
+For the importance of why it is better to add your keywords in this function 
(before writing the data) or after it, see the description of 
@code{gal_fits_img_write_to_ptr}.
 @end deftypefun
 
-@deftypefun void gal_fits_img_write_to_type (gal_data_t @code{*data}, char 
@code{*filename}, gal_fits_list_key_t @code{*headers}, char 
@code{*program_string}, int @code{type})
+@deftypefun void gal_fits_img_write_to_type (gal_data_t @code{*data}, char 
@code{*filename}, gal_fits_list_key_t @code{*keylist}, int @code{type}, int 
@code{freekeys})
 Convert the @code{input} dataset into @code{type}, then write it into the FITS 
file named @file{filename}.
-Also add the @code{headers} keywords to the newly created HDU/extension along 
with your program's name (@code{program_string}).
+Also add the @code{keylist} keywords to the newly created HDU/extension along 
with your program's name (@code{program_string}).
 After the FITS file is written, this function will free the copied dataset 
(with type @code{type}) from memory.
 
+For the importance of why it is better to add your keywords in this function 
(before writing the data) or after it, see the description of 
@code{gal_fits_img_write_to_ptr}.
 This is just a wrapper for the @code{gal_data_copy_to_new_type} and 
@code{gal_fits_img_write} functions.
 @end deftypefun
 
-@deftypefun void gal_fits_img_write_corr_wcs_str (gal_data_t @code{*data}, 
char @code{*filename}, char @code{*wcsstr}, int @code{nkeyrec}, double 
@code{*crpix}, gal_fits_list_key_t @code{*headers}, char @code{*program_string})
+@deftypefun void gal_fits_img_write_corr_wcs_str (gal_data_t @code{*data}, 
char @code{*filename}, char @code{*wcsstr}, int @code{nkeyrec}, double 
@code{*crpix}, gal_fits_list_key_t @code{*keylist}, int @code{freekeys})
 Write the @code{input} dataset into @file{filename} using the @code{wcsstr} 
while correcting the @code{CRPIX} values.
+For the importance of why it is better to add your keywords in this function 
(before writing the data) or after it, see the description of 
@code{gal_fits_img_write_to_ptr}.
 
-This function is mainly useful when you want to make FITS files in parallel 
(from one main WCS structure, with just differing CRPIX).
+This function is mainly useful when you want to make FITS files in parallel 
(from one main WCS structure, with just a differing CRPIX), for more on the 
arguments, see the description of @code{gal_fits_img_write}.
 This can happen in the following cases for example:
 
 @itemize
@@ -39659,12 +39681,15 @@ Note that this is a low-level function, so the output 
data linked list is the in
 It is recommended to use @code{gal_table_read} for generic reading of tables, 
see @ref{Table input output}.
 @end deftypefun
 
-@deftypefun void gal_fits_tab_write (gal_data_t @code{*cols}, gal_list_str_t 
@code{*comments}, int @code{tableformat}, char @code{*filename}, char 
@code{*extname})
+@deftypefun void gal_fits_tab_write (gal_data_t @code{*cols}, gal_list_str_t 
@code{*comments}, int @code{tableformat}, char @code{*filename}, char 
@code{*extname}, gal_fits_list_key_t @code{*keywords}, int @code{freekeys})
 Write the list of datasets in @code{cols} (see @ref{List of gal_data_t}) as 
separate columns in a FITS table in @code{filename}.
 If @code{filename} already exists then this function will write the table as a 
new extension called @code{extname}, after all existing ones.
 The format of the table (ASCII or binary) may be specified with the 
@code{tableformat} (see @ref{Table input output}).
 If @code{comments!=NULL}, each node of the list of strings will be written as 
a @code{COMMENT} keywords in the output FITS file (see @ref{List of strings}.
 
+In case your table needs metadata keywords, you can use the @code{listkeys} 
and @code{freekeys}.
+For more on these, see the description of @code{gal_fits_key_write_in_ptr}.
+
 This is a low-level function for tables.
 It is recommended to use @code{gal_table_write} for generic writing of tables 
in a variety of formats, see @ref{Table input output}.
 @end deftypefun
@@ -39786,7 +39811,7 @@ We often need to read a text file several times: once 
to count how many columns
 So it easier to keep it all in allocated memory and pass it on from the start 
for each round.
 @end deftypefun
 
-@deftypefun void gal_txt_write (gal_data_t @code{*cols}, struct 
gal_fits_list_key_t @code{**keylist}, gal_list_str_t @code{*comment}, char 
@code{*filename}, uint8_t @code{colinfoinstdout}, int @code{tab0_img1})
+@deftypefun void gal_txt_write (gal_data_t @code{*cols}, struct 
gal_fits_list_key_t @code{**keylist}, gal_list_str_t @code{*comment}, char 
@code{*filename}, uint8_t @code{colinfoinstdout}, int @code{tab0_img1}, int 
@code{freekeys})
 Write @code{cols} in a plain text file @code{filename} (table when 
@code{tab0_img1==0} and image when @code{tab0_img1==1}).
 @code{cols} may have one or two dimensions which determines the output:
 
@@ -40297,11 +40322,12 @@ Each FITS keyword is 80 characters wide (according to 
the FITS standard), and th
 The output of this function can later be written into an opened FITS file 
using @code{gal_fits_key_write_wcsstr} (see @ref{FITS header keywords}).
 @end deftypefun
 
-@deftypefun void gal_wcs_write (struct wcsprm @code{*wcs}, char 
@code{*filename}, char @code{*extname}, gal_fits_list_key_t @code{*headers}, 
char @code{*program_string})
+@deftypefun void gal_wcs_write (struct wcsprm @code{*wcs}, char 
@code{*filename}, char @code{*extname}, gal_fits_list_key_t @code{*keylist}, 
int @code{freekeys})
 Write the given WCS structure into the second extension of an empty FITS 
header.
 The first/primary extension will be empty like the default format of all 
Gnuastro outputs.
 When @code{extname!=NULL} it will be used as the FITS extension name.
-Any set of extra headers can also be written through the @code{headers} list 
and if @code{program_string!=NULL} it will be used in a commented keyword title 
just above the written version information.
+Any set of extra headers can also be written through the @code{keylist} list.
+If @code{freekeys!=0} then the list of keywords will be freed after they are 
written.
 @end deftypefun
 
 @deftypefun void gal_wcs_write_in_fitsptr (fitsfile @code{*fptr}, struct 
wcsprm @code{*wcs})
@@ -41462,7 +41488,7 @@ In other words, only permute along dimension 0.
 The @code{permutation} array should therefore only have @code{input->dsize[0]} 
elements.
 @end deftypefun
 
-@deftypefun void gal_tile_full_values_write (gal_data_t @code{*tilevalues}, 
struct gal_tile_two_layer_params @code{*tl}, int @code{withblank}, char 
@code{*filename}, gal_fits_list_key_t @code{*keys}, char @code{*program_string})
+@deftypefun void gal_tile_full_values_write (gal_data_t @code{*tilevalues}, 
struct gal_tile_two_layer_params @code{*tl}, int @code{withblank}, char 
@code{*filename}, gal_fits_list_key_t @code{*keys}, int @code{freekeys})
 Write one value for each tile into a file.
 It is important to note that the values in @code{tilevalues} must be ordered 
in the same manner as the tiles, so @code{tilevalues->array[i]} is the value 
that should be given to @code{tl->tiles[i]}.
 The @code{tl->permutation} array must have been initialized before calling 
this function with @code{gal_tile_full_permutation}.
@@ -41959,7 +41985,7 @@ main (void)
   gal_fits_key_list_add_end(&keylist, GAL_TYPE_SIZE_T, keyname, 0,
                             &root, 0, comment, 0, unit, 0);
   gal_table_write(kdtree, &keylist, NULL, GAL_TABLE_FORMAT_BFITS,
-                  kdtreefile, "kdtree", 0);
+                  kdtreefile, "kdtree", 0, 1);
 
   /* Clean up and return. */
   gal_list_data_free(input);
@@ -44747,7 +44773,7 @@ main(void)
   c1->name = "COUNTER";
   c2->name = "VALUE";
   gal_table_write(c1, NULL, NULL, GAL_TABLE_FORMAT_BFITS, outname,
-                  "MY-COLUMNS", 0);
+                  "MY-COLUMNS", 0, 0);
 
   /* The names were not allocated, so to avoid cleaning-up problems,
    * we will set them to NULL. */
diff --git a/lib/fits.c b/lib/fits.c
index 87ea4e8e..4918d198 100644
--- a/lib/fits.c
+++ b/lib/fits.c
@@ -607,14 +607,15 @@ fits_type_correct(int *type, double bscale, char 
*bzero_str)
           else
             if( bzero == 9223372036854775808LLU )
               {
-                fprintf(stderr, "\nWARNING in %s: the BZERO header keyword "
-                        "value ('%s') is very close (but not exactly equal) "
-                        "to '%s'. The latter value in the FITS standard is "
-                        "used to identify that the dataset should be read as "
-                        "unsigned 64-bit integers instead of signed 64-bit "
-                        "integers. Depending on your version of CFITSIO, "
-                        "it may be read as a signed or unsigned 64-bit "
-                        "integer array\n\n", __func__, bzero_str, bzero_u64);
+                fprintf(stderr, "\nWARNING in %s: the BZERO header "
+                        "keyword value ('%s') is very close (but not "
+                        "exactly equal) to '%s'. The latter value in the "
+                        "FITS standard is used to identify that the "
+                        "dataset should be read as unsigned 64-bit "
+                        "integers instead of signed 64-bit integers. "
+                        "Depending on your version of CFITSIO, it may be "
+                        "read as a signed or unsigned 64-bit integer "
+                        "array\n\n", __func__, bzero_str, bzero_u64);
                 tofloat=0;
               }
           break;
@@ -674,7 +675,7 @@ gal_fits_open_to_write(char *filename)
       if( fits_create_img(fptr, BYTE_IMG, 0, &naxes, &status) )
         gal_fits_io_error(status, NULL);
 
-      /* Close the blank extension. */
+      /* Close the empty extension. */
       if( fits_close_file(fptr, &status) )
         gal_fits_io_error(status, NULL);
     }
@@ -965,14 +966,14 @@ gal_fits_hdu_open_format(char *filename, char *hdu, int 
img0_tab1,
         {
           /* Let the user know. */
           if( gal_fits_hdu_is_healpix(fptr) )
-            error(EXIT_FAILURE, 0, "%s (hdu: %s): appears to be a HEALPix 
table "
-                  "(which is a 2D dataset on a spherical surface: stored as "
-                  "a 1D table). You can use the 'HPXcvt' command-line utility "
-                  "to convert it to a 2D image that can easily be used by "
-                  "other programs. 'HPXcvt' is built and installed as part of "
-                  "WCSLIB (which is a mandatory dependency of Gnuastro, so "
-                  "you should already have it), run 'man HPXcvt' for more",
-                  filename, hdu);
+            error(EXIT_FAILURE, 0, "%s (hdu: %s): appears to be a HEALPix "
+                  "table (which is a 2D dataset on a spherical surface: "
+                  "stored as a 1D table). You can use the 'HPXcvt' "
+                  "command-line utility to convert it to a 2D image that "
+                  "can easily be used by other programs. 'HPXcvt' is built "
+                  "and installed as part of WCSLIB (which is a mandatory "
+                  "dependency of Gnuastro, so you should already have it), "
+                  "run 'man HPXcvt' for more", filename, hdu);
           else
             error(EXIT_FAILURE, 0, "%s (hdu: %s): not an image",
                   filename, hdu);
@@ -1166,9 +1167,11 @@ gal_fits_key_date_to_struct_tm(char *fitsdate, struct tm 
*tp)
                 : strptime(nosubsec, hassq?"'%F'"   :"%F"   , tp))
             : ( hasT
                 ? ( hasZ
-                    ? strptime(nosubsec, 
hassq?"'%d/%m/%yT%TZ'":"%d/%m/%yT%TZ", tp)
-                    : strptime(nosubsec, hassq?"'%d/%m/%yT%T'":"%d/%m/%yT%T", 
tp))
-                : strptime(nosubsec, hassq?"'%d/%m/%y'"   :"%d/%m/%y"   , tp)
+                    ? strptime(nosubsec,
+                               hassq?"'%d/%m/%yT%TZ'":"%d/%m/%yT%TZ", tp)
+                    : strptime(nosubsec,
+                               hassq?"'%d/%m/%yT%T'":"%d/%m/%yT%T", tp))
+                : strptime(nosubsec, hassq?"'%d/%m/%y'"   :"%d/%m/%y", tp)
                 )
             )
         );
@@ -1246,9 +1249,9 @@ gal_fits_key_date_to_seconds(char *fitsdate, char 
**subsecstr,
         {
           if(subsec)
             if( gal_type_from_string(&subsecptr, tmp, GAL_TYPE_FLOAT64) )
-              error(EXIT_FAILURE, 0, "%s: the sub-second portion of '%s' (or "
-                    "'%s') couldn't be read as a number", __func__, fitsdate,
-                    tmp);
+              error(EXIT_FAILURE, 0, "%s: the sub-second portion of '%s' "
+                    "(or '%s') couldn't be read as a number", __func__,
+                    fitsdate, tmp);
         }
       else { if(subsec) *subsec=NAN; }
     }
@@ -1565,7 +1568,8 @@ gal_fits_key_list_add_end(gal_fits_list_key_t **list, 
uint8_t type,
 
 /* Only set this key to be a title. */
 void
-gal_fits_key_list_title_add(gal_fits_list_key_t **list, char *title, int tfree)
+gal_fits_key_list_title_add(gal_fits_list_key_t **list, char *title,
+                            int tfree)
 {
   gal_fits_list_key_t *newnode;
 
@@ -1690,6 +1694,129 @@ 
gal_fits_key_list_fullcomment_add_end(gal_fits_list_key_t **list,
 
 
 
+/* Add 'DATE' on top of the list of keywords. */
+void
+gal_fits_key_list_add_date(gal_fits_list_key_t **keylist,
+                           char *incomment)
+{
+  int status=0;
+  uint8_t *isutc;
+  int timeref=0; /* ==1: returned string is not UTC */
+  char *datestr, *kname, *comment, *unit;
+
+  /* The FITS date format has 19 characters: 'YYYY-MM-DDTHH:MM:SS'. As a
+     string, we also need a byte for '\0'. */
+  datestr=gal_pointer_allocate(GAL_TYPE_UINT8, 20, 0, __func__,
+                               "datestr");
+  fits_get_system_time(datestr, &timeref, &status);
+
+  /* Allocate the necessary components. */
+  gal_checkset_allocate_copy("DATE", &kname);
+  gal_checkset_allocate_copy(incomment, &comment);
+  gal_checkset_allocate_copy("YYYY-MM-DDThh:mm:ss", &unit);
+  gal_fits_key_list_add(keylist, GAL_TYPE_STRING, kname, 1, datestr, 1,
+                        comment, 1, unit, 1);
+
+  /* Add keyword to note if date is in UTC. */
+  isutc=gal_pointer_allocate(GAL_TYPE_UINT8, 1, 0, __func__, "isutc");
+  *isutc=!timeref;
+  gal_checkset_allocate_copy("DATEUTC", &kname);
+  gal_checkset_allocate_copy("If 'DATE' is in UTC, value is '1'.",
+                             &comment);
+  gal_checkset_allocate_copy("bool", &unit);
+  gal_fits_key_list_add(keylist, GAL_TYPE_UINT8, kname, 1, isutc, 1,
+                        comment, 1, unit, 1);
+}
+
+
+
+
+
+void
+gal_fits_key_list_add_software_versions(gal_fits_list_key_t **keylist)
+{
+  char *gnuastroversion;
+  char *kname, *comment, *gslversion, *cfitsioversion;
+
+  /* Variables that are only necessary for WCSLIB's version. */
+#if GAL_CONFIG_HAVE_WCSLIB_VERSION == 1
+  int wcslibvers[3];
+  char *wcslibversion;
+  const char *wcslibversion_const;
+#endif
+
+  /* Set the version of CFITSIO as a string: before version 4.0.0 of
+     CFITSIO, there were only two numbers in the version (for example
+     '3.49' and '3.48'), but from the 4th major release, there are three
+     numbers in the version string. The third number corresponds to a new
+     'CFITSIO_MICRO' macro. So if it doesn't exist, we'll just print two
+     numbers, otherwise, we'll print the three. */
+#ifdef CFITSIO_MICRO
+  if( asprintf(&cfitsioversion, "%d.%d.%d", CFITSIO_MAJOR,
+               CFITSIO_MINOR, CFITSIO_MICRO)<0 )
+    error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
+#else
+  if( asprintf(cfitsioversion, "%d.%d", CFITSIO_MAJOR,
+               CFITSIO_MINOR)<0 )
+    error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
+#endif
+  gal_checkset_allocate_copy("CFITSIO", &kname);
+  gal_checkset_allocate_copy("Version of used CFITSIO.", &comment);
+  gal_fits_key_list_add(keylist, GAL_TYPE_STRING, kname, 1,
+                        cfitsioversion, 1, comment, 1, NULL, 0);
+
+  /* Write the WCSLIB version. Before WCSLIB 5.0, the wcslib_version
+     function was not defined. Sometime in the future were everyone has
+     moved to more recent versions of WCSLIB, we can remove this macro and
+     its check in configure.ac.*/
+#if GAL_CONFIG_HAVE_WCSLIB_VERSION == 1
+  wcslibversion_const=wcslib_version(wcslibvers);
+  gal_checkset_allocate_copy(wcslibversion_const, &wcslibversion);
+  gal_checkset_allocate_copy("WCSLIB", &kname);
+  gal_checkset_allocate_copy("Version of used WCSLIB.", &comment);
+  gal_fits_key_list_add(keylist, GAL_TYPE_STRING, kname, 1,
+                        wcslibversion, 1, comment, 1, NULL, 0);
+#endif
+
+  /* Write the GSL version. */
+  gal_checkset_allocate_copy("GSL", &kname);
+  gal_checkset_allocate_copy(GSL_VERSION, &gslversion);
+  gal_checkset_allocate_copy("Version of used GNU Scientific Library.",
+                             &comment);
+  gal_fits_key_list_add(keylist, GAL_TYPE_STRING, kname, 1,
+                        gslversion, 1, comment, 1, NULL, 0);
+
+  /* Write the Gnuastro's version. */
+  gal_checkset_allocate_copy("GNUASTRO", &kname);
+  gal_checkset_allocate_copy(PACKAGE_VERSION, &gnuastroversion);
+  gal_checkset_allocate_copy("Version of used GNU Astronomy Utilities.",
+                             &comment);
+  gal_fits_key_list_add(keylist, GAL_TYPE_STRING, kname, 1,
+                        gnuastroversion, 1, comment, 1, NULL, 0);
+}
+
+
+
+
+
+void
+gal_fits_key_list_add_git_commit(gal_fits_list_key_t **keylist)
+{
+  char *kname, *comment, *gitdescribe=gal_git_describe();
+  if(gitdescribe)
+    {
+      gal_checkset_allocate_copy("COMMIT", &kname);
+      gal_checkset_allocate_copy("Git commit in running directory.",
+                                 &comment);
+      gal_fits_key_list_add(keylist, GAL_TYPE_STRING, kname, 1,
+                            gitdescribe, 1, comment, 1, NULL, 0);
+    }
+}
+
+
+
+
+
 void
 gal_fits_key_list_reverse(gal_fits_list_key_t **list)
 {
@@ -1730,9 +1857,9 @@ gal_fits_key_write_title_in_ptr(char *title, fitsfile 
*fptr)
     {
       /* A small sanity check. */
       if( strlen(title) + strlen(GAL_FITS_KEY_TITLE_START) > 78 )
-        fprintf(stderr, "%s: FITS keyword title '%s' is too long to be fully "
-                "included in the keyword record (80 characters, where the "
-                "title is prefixed with %zu space characters)",
+        fprintf(stderr, "%s: FITS keyword title '%s' is too long to be "
+                "fully included in the keyword record (80 characters, "
+                "where the title is prefixed with %zu space characters)",
                 __func__, title, strlen(GAL_FITS_KEY_TITLE_START));
 
       /* Set the last element of the blank array. */
@@ -1898,7 +2025,8 @@ gal_fits_key_write_filename(char *keynamebase, char 
*filename,
    attempt the check in 3D datasets. We'll read each written CDELT value
    with CFITSIO and if its zero, we'll correct it. */
 static void
-fits_bug_wrapper_cdelt_zero(fitsfile *fptr, struct wcsprm *wcs, char *keystr)
+fits_bug_wrapper_cdelt_zero(fitsfile *fptr, struct wcsprm *wcs,
+                            char *keystr)
 {
   char *keyname;
   double keyvalue;
@@ -2021,18 +2149,39 @@ gal_fits_key_write_wcsstr(fitsfile *fptr, struct wcsprm 
*wcs,
 /* Write the given list of header keywords into the specified HDU of the
    specified FITS file. */
 void
-gal_fits_key_write(gal_fits_list_key_t **keylist, char *title,
-                   char *filename, char *hdu, char *hdu_option_name)
+gal_fits_key_write(gal_fits_list_key_t *keylist, char *filename,
+                   char *hdu, char *hdu_option_name, int freekeys,
+                   int create_fits_not_exists)
 {
   int status=0;
-  fitsfile *fptr=gal_fits_hdu_open(filename, hdu, READWRITE, 1,
-                                   hdu_option_name);
+  fitsfile *fptr;
 
-  /* Write the title. */
-  gal_fits_key_write_title_in_ptr(title, fptr);
+  /* If the file already exist or the user didn't want to create it, then
+     just (try) open(ing) it. We are still trying to open the file when the
+     user doesn't want to create the file because of the good error
+     messages in 'gal_fits_hdu_open'. */
+  if( gal_checkset_check_file_return(filename)
+      || create_fits_not_exists==0)
+    fptr=gal_fits_hdu_open(filename, hdu, READWRITE, 1,
+                           hdu_option_name);
+  else
+    {
+      if( hdu[0]=='0' && hdu[1]=='\0' ) /* only valid for hdu=="0". */
+        {
+          fptr=gal_fits_open_to_write(filename);
+          if( fits_close_file(fptr, &status) )
+            gal_fits_io_error(status, NULL);
+          fptr=gal_fits_hdu_open(filename, hdu, READWRITE, 1,
+                                 hdu_option_name);
+        }
+      else
+        error(EXIT_FAILURE, 0, "%s: when 'create_if_not_exists!=0', "
+              "the 'hdu' argument should be '0', but it is '%s'",
+              __func__, hdu);
+    }
 
   /* Write the keywords into the FITS file pointer ('fptr'). */
-  gal_fits_key_write_in_ptr(keylist, fptr);
+  gal_fits_key_write_in_ptr(keylist, fptr, freekeys);
 
   /* Close the input FITS file. */
   fits_close_file(fptr, &status);
@@ -2090,221 +2239,74 @@ 
gal_fits_key_write_in_ptr_nan_check(gal_fits_list_key_t *tmp)
    file. Every keyword that is written is freed, that is why we need the
    pointer to the linked list (to correct it after we finish). */
 void
-gal_fits_key_write_in_ptr(gal_fits_list_key_t **keylist, fitsfile *fptr)
+gal_fits_key_write_in_ptr(gal_fits_list_key_t *keylist,
+                          fitsfile *fptr, int freekeys)
 {
   int status=0;
   void *ifnan=NULL;
-  gal_fits_list_key_t *tmp, *ttmp;
+  gal_fits_list_key_t *key, *tmp;
 
-  tmp=*keylist;
-  while(tmp!=NULL)
+  /* Go over the list (in case it is not NULL). */
+  key=keylist;
+  while(key!=NULL)
     {
       /* If a title is requested, only put a title. */
-      if(tmp->title)
+      if(key->title)
         {
-          gal_fits_key_write_title_in_ptr(tmp->title, fptr);
-          if(tmp->tfree) free(tmp->title);
+          gal_fits_key_write_title_in_ptr(key->title, fptr);
+          if(freekeys && key->tfree) free(key->title);
         }
-      else if (tmp->fullcomment)
+      else if (key->fullcomment)
         {
-          if( fits_write_comment(fptr, tmp->fullcomment, &status) )
+          if( fits_write_comment(fptr, key->fullcomment, &status) )
             gal_fits_io_error(status, NULL);
-          if(tmp->fcfree) free(tmp->fullcomment);
+          if(freekeys && key->fcfree) free(key->fullcomment);
         }
       else
         {
           /* Write the basic key value and comments. */
-          if(tmp->value)
+          if(key->value)
             {
               /* Print a warning if the value is NaN. */
-              ifnan=gal_fits_key_write_in_ptr_nan_check(tmp);
+              ifnan=gal_fits_key_write_in_ptr_nan_check(key);
 
               /* Write/Update the keyword value. */
               if( fits_update_key(fptr,
-                                  gal_fits_type_to_datatype(tmp->type),
-                                  tmp->keyname, ifnan?ifnan:tmp->value,
-                                  tmp->comment, &status) )
+                                  gal_fits_type_to_datatype(key->type),
+                                  key->keyname, ifnan?ifnan:key->value,
+                                  key->comment, &status) )
                 gal_fits_io_error(status, NULL);
               if(ifnan) free(ifnan);
             }
           else
             {
-              if(fits_update_key_null(fptr, tmp->keyname, tmp->comment,
+              if(fits_update_key_null(fptr, key->keyname, key->comment,
                                       &status))
                 gal_fits_io_error(status, NULL);
             }
 
           /* Write the units if it was given. */
-          if( tmp->unit
-              && fits_write_key_unit(fptr, tmp->keyname, tmp->unit,
+          if( key->unit
+              && fits_write_key_unit(fptr, key->keyname, key->unit,
                                      &status) )
             gal_fits_io_error(status, NULL);
 
           /* Free the value pointer if desired: */
-          if(tmp->ufree) free(tmp->unit);
-          if(tmp->vfree) free(tmp->value);
-          if(tmp->kfree) free(tmp->keyname);
-          if(tmp->cfree) free(tmp->comment);
+          if(freekeys)
+            {
+              if(key->ufree) free(key->unit);
+              if(key->vfree) free(key->value);
+              if(key->kfree) free(key->keyname);
+              if(key->cfree) free(key->comment);
+            }
         }
 
       /* Keep the pointer to the next keyword and free the allocated
          space for this keyword. */
-      ttmp=tmp->next;
-      free(tmp);
-      tmp=ttmp;
-    }
-
-  /* Set it to NULL so it isn't mistakenly used later. */
-  *keylist=NULL;
-}
-
-
-
-
-
-/* Write the given list of header keywords and version info into the give
-   file. */
-void
-gal_fits_key_write_version(gal_fits_list_key_t **keylist, char *title,
-                           char *filename, char *hdu,
-                           char *hdu_option_name)
-{
-  int status=0;
-  fitsfile *fptr=gal_fits_hdu_open(filename, hdu, READWRITE, 1,
-                                   hdu_option_name);
-
-  /* Write the given keys followed by the versions. */
-  gal_fits_key_write_version_in_ptr(keylist, title, fptr);
-
-  /* Close the input FITS file. */
-  fits_close_file(fptr, &status);
-  gal_fits_io_error(status, NULL);
-}
-
-
-
-
-
-void
-gal_fits_key_write_version_in_ptr(gal_fits_list_key_t **keylist,
-                                  char *title, fitsfile *fptr)
-{
-  int status=0;
-  char *gitdescribe;
-  char cfitsioversion[20];
-
-  /* Before WCSLIB 5.0, the wcslib_version function was not
-     defined. Sometime in the future were everyone has moved to more
-     recent versions of WCSLIB, we can remove this macro and its check
-     in configure.ac. */
-#if GAL_CONFIG_HAVE_WCSLIB_VERSION == 1
-  int wcslibvers[3];
-  char wcslibversion[20];
-  const char *wcslibversion_const;
-#endif
-
-  /* Small sanity check. */
-  if(fptr==NULL)
-    error(EXIT_FAILURE, 0, "%s: input FITS pointer is NULL", __func__);
-
-  /* If any header keywords are specified, add them: */
-  if(keylist && *keylist)
-    {
-      gal_fits_key_write_title_in_ptr(title?title:"Specific keys", fptr);
-      gal_fits_key_write_in_ptr(keylist, fptr);
+      tmp=key->next;
+      if(freekeys) free(key);
+      key=tmp;
     }
-
-  /* Print 'Versions and date' title. */
-  gal_fits_key_write_title_in_ptr("Versions and date", fptr);
-
-  /* Set the version of CFITSIO as a string: before version 4.0.0 of
-     CFITSIO, there were only two numbers in the version (for example
-     '3.49' and '3.48'), but from the 4th major release, there are three
-     numbers in the version string. The third number corresponds to a new
-     'CFITSIO_MICRO' macro. So if it doesn't exist, we'll just print two
-     numbers, otherwise, we'll print the three. */
-#ifdef CFITSIO_MICRO
-  sprintf(cfitsioversion, "%d.%d.%d", CFITSIO_MAJOR,
-          CFITSIO_MINOR, CFITSIO_MICRO);
-#else
-  sprintf(cfitsioversion, "%d.%d", CFITSIO_MAJOR,
-          CFITSIO_MINOR);
-#endif
-
-  /* Write all the information: */
-  fits_write_date(fptr, &status);
-
-  /* Write the version of CFITSIO. */
-  fits_update_key(fptr, TSTRING, "CFITSIO", cfitsioversion,
-                  "CFITSIO version.", &status);
-
-  /* Write the WCSLIB version. */
-#if GAL_CONFIG_HAVE_WCSLIB_VERSION == 1
-  wcslibversion_const=wcslib_version(wcslibvers);
-  strcpy(wcslibversion, wcslibversion_const);
-  fits_update_key(fptr, TSTRING, "WCSLIB", wcslibversion,
-                  "WCSLIB version.", &status);
-#endif
-
-  /* Write the GSL version. */
-  fits_update_key(fptr, TSTRING, "GSL", GSL_VERSION,
-                  "GNU Scientific Library version.", &status);
-
-  /* Write the Gnuastro version. */
-  fits_update_key(fptr, TSTRING, "GNUASTRO", PACKAGE_VERSION,
-                  "GNU Astronomy Utilities version.", &status);
-
-  /* If we are in a version controlled directory and have libgit2
-     installed, write the commit description into the FITS file. */
-  gitdescribe=gal_git_describe();
-  if(gitdescribe)
-    {
-      fits_update_key(fptr, TSTRING, "COMMIT", gitdescribe,
-                      "Git commit in running directory.", &status);
-      free(gitdescribe);
-    }
-
-  /* Report any error if a problem came up. */
-  gal_fits_io_error(status, NULL);
-}
-
-
-
-
-
-
-/* Write the given keywords into the given extension of the given file,
-   ending it with version information. This is primarily intended for
-   writing configuration settings of a program into the zero-th extension
-   of the FITS file (which is empty when the FITS file is created by
-   Gnuastro's program and this library). */
-void
-gal_fits_key_write_config(gal_fits_list_key_t **keylist, char *title,
-                          char *extname, char *filename, char *hdu,
-                          char *hdu_option_name)
-{
-  int status=0;
-  fitsfile *fptr=gal_fits_hdu_open(filename, hdu, READWRITE, 1,
-                                   hdu_option_name);
-
-  /* Delete the two extra comment lines describing the FITS standard that
-     CFITSIO puts in when it creates a new extension. We'll set status to 0
-     afterwards so even if they don't exist, the program continues
-     normally. */
-  fits_delete_key(fptr, "COMMENT", &status);
-  fits_delete_key(fptr, "COMMENT", &status);
-  status=0;
-
-  /* Put a name for the zero-th extension. */
-  if(fits_write_key(fptr, TSTRING, "EXTNAME", extname, "", &status))
-    gal_fits_io_error(status, NULL);
-
-  /* Write all the given keywords. */
-  gal_fits_key_write_version_in_ptr(keylist, title, fptr);
-
-  /* Close the FITS file. */
-  if( fits_close_file(fptr, &status) )
-    gal_fits_io_error(status, NULL);
 }
 
 
@@ -2720,21 +2722,84 @@ gal_fits_img_read_kernel(char *filename, char *hdu, 
size_t minmapsize,
 
 
 
+/* Write the requested header keywords first (if we add them after writing
+   the image, and there is many keywords (more than 2880/80=36), the whole
+   image needs to be shifted to accommodate a new 2880 byte block for new
+   keywords (which wastes time). */
+static void
+gal_fits_img_write_to_ptr_keys(fitsfile *fptr, gal_data_t *towrite,
+                               int datatype, int hasblank,
+                               gal_fits_list_key_t *keylist, int freekeys)
+{
+  void *blank;
+  int status=0;
+
+  /* Remove the two comment lines put by CFITSIO. Note that in some cases,
+     it might not exist. When this happens, the status value will be
+     non-zero. We don't care about this error, so to be safe, we will just
+     reset the status variable after these calls. */
+  fits_delete_key(fptr, "COMMENT", &status);
+  fits_delete_key(fptr, "COMMENT", &status);
+  status=0;
+
+  /* If we have blank pixels, we need to define a BLANK keyword when we are
+     dealing with integer types. */
+  if(hasblank)
+    switch(towrite->type)
+      {
+      case GAL_TYPE_FLOAT32:
+      case GAL_TYPE_FLOAT64:
+        /* Do nothing! Since there are much fewer floating point types
+           (that don't need any BLANK keyword), we are checking them. */
+        break;
+
+      default:
+        blank=gal_fits_key_img_blank(towrite->type);
+        if(fits_write_key(fptr, datatype, "BLANK", blank,
+                          "Pixels with no data.", &status) )
+          gal_fits_io_error(status, "adding the BLANK keyword");
+        free(blank);
+      }
+
+  /* Write the extension name to the header. */
+  if(towrite->name)
+    fits_write_key(fptr, TSTRING, "EXTNAME", towrite->name, "", &status);
+
+  /* Write the units to the header. */
+  if(towrite->unit)
+    fits_write_key(fptr, TSTRING, "BUNIT", towrite->unit, "", &status);
+
+  /* Write comments if they exist. */
+  if(towrite->comment)
+    fits_write_comment(fptr, towrite->comment, &status);
+
+  /* If a WCS structure is present, write it in the FITS file pointer
+     ('fptr'). */
+  if(towrite->wcs)
+    gal_wcs_write_in_fitsptr(fptr, towrite->wcs);
+
+  /* Write any requested keywords. */
+  gal_fits_key_write_in_ptr(keylist, fptr, freekeys);
+}
+
+
+
+
 
 /* This function will write all the data array information (including its
    WCS information) into a FITS file, but will not close it. Instead it
    will pass along the FITS pointer for further modification. */
 fitsfile *
-gal_fits_img_write_to_ptr(gal_data_t *input, char *filename)
+gal_fits_img_write_to_ptr(gal_data_t *input, char *filename,
+                          gal_fits_list_key_t *keylist, int freekeys)
 {
-  void *blank;
   int64_t *i64;
   char *u64key;
   fitsfile *fptr;
   uint64_t *u64, *u64f;
-  long fpixel=1, *naxes;
   size_t i, ndim=input->ndim;
-  int hasblank, status=0, datatype=0;
+  long fpixel=1, *naxes, *naxesone=NULL;
+  int bitpix, hasblank, status=0, datatype=0;
   gal_data_t *i64data, *towrite, *block=gal_tile_block(input);
 
   /* Small sanity check. */
@@ -2748,11 +2813,18 @@ gal_fits_img_write_to_ptr(gal_data_t *input, char 
*filename)
   hasblank=gal_blank_present(towrite, 0);
 
 
-  /* Allocate the naxis area. */
+  /* Allocate the naxes space(s): we need 'naxesone' only when there are
+     keywords to be written. */
   naxes=gal_pointer_allocate( ( sizeof(long)==8
                                 ? GAL_TYPE_INT64
                                 : GAL_TYPE_INT32 ), ndim, 0, __func__,
                               "naxes");
+  naxesone = ( keylist
+               ? gal_pointer_allocate( ( sizeof(long)==8
+                                         ? GAL_TYPE_INT64
+                                         : GAL_TYPE_INT32 ), ndim, 0, __func__,
+                                       "naxes")
+               : NULL );
 
 
   /* Open the file for writing. */
@@ -2760,7 +2832,11 @@ gal_fits_img_write_to_ptr(gal_data_t *input, char 
*filename)
 
 
   /* Fill the 'naxes' array (in opposite order, and 'long' type): */
-  for(i=0;i<ndim;++i) naxes[ndim-1-i]=towrite->dsize[i];
+  for(i=0;i<ndim;++i)
+    {
+      naxes[ndim-1-i]=towrite->dsize[i];
+      if(naxesone) naxesone[ndim-1-i]=0;
+    }
 
 
   /* Create the FITS file. Unfortunately CFITSIO doesn't have a macro for
@@ -2770,6 +2846,16 @@ gal_fits_img_write_to_ptr(gal_data_t *input, char 
*filename)
      keywords accordingly. */
   if(block->type==GAL_TYPE_UINT64)
     {
+      /* Print a warning to let the user know that some extra operation is
+         necessary. */
+      error(EXIT_SUCCESS, 0, "%s: WARNING: Unfortunately CFITSIO does "
+            "not support unsigned 64-bit integers (TLONGLONG is only "
+            "for signed INT64). Therefore some manual operation is "
+            "necessary to create the output and this can slow down your "
+            "program. If unsigned 64-bit integers are not absolutely "
+            "vital for your output's format, it helps to choose another "
+            "type", __func__);
+
       /* Allocate the necessary space. */
       i64data=gal_data_alloc(NULL, GAL_TYPE_INT64, ndim, towrite->dsize,
                              NULL, 0, block->minmapsize, block->quietmmap,
@@ -2788,17 +2874,28 @@ gal_fits_img_write_to_ptr(gal_data_t *input, char 
*filename)
       else
         do *i64++ = (*u64 + INT64_MIN); while(++u64<u64f);
 
-      /* We can now use CFITSIO's signed-int64 type macros. */
+      /* We can now use CFITSIO's signed-int64 type macros and create the
+         image HDU. However, if we have keywords, first, we will create a 1
+         element image of the same dimensions (this is easy to move in case
+         there are many keywords). */
       datatype=TLONGLONG;
-      fits_create_img(fptr, LONGLONG_IMG, ndim, naxes, &status);
+      bitpix=LONGLONG_IMG;
+      fits_create_img(fptr, bitpix, ndim, keylist?naxesone:naxes,
+                      &status);
       gal_fits_io_error(status, NULL);
 
-      /* Write the image into the file. */
+      /* Write the keywords first (to avoid having to shift the image if
+         there are many keywords). */
+      gal_fits_img_write_to_ptr_keys(fptr, towrite, datatype, hasblank,
+                                     keylist, freekeys);
+
+      /* If we had keywords, we should resize the output array and write
+         the image. */
+      if(keylist) fits_resize_img(fptr, bitpix, ndim, naxes, &status);
       fits_write_img(fptr, datatype, fpixel, i64data->size, i64data->array,
                      &status);
       gal_fits_io_error(status, NULL);
 
-
       /* We need to write the BZERO and BSCALE keywords manually. VERY
          IMPORTANT: this has to be done after writing the array. We cannot
          write this huge integer as a variable, so we'll simply write the
@@ -2816,67 +2913,22 @@ gal_fits_img_write_to_ptr(gal_data_t *input, char 
*filename)
       datatype=gal_fits_type_to_datatype(block->type);
 
       /* Create the FITS file. */
-      fits_create_img(fptr, gal_fits_type_to_bitpix(towrite->type),
-                      ndim, naxes, &status);
+      bitpix=gal_fits_type_to_bitpix(towrite->type);
+      fits_create_img(fptr, bitpix, ndim, keylist?naxesone:naxes, &status);
       gal_fits_io_error(status, NULL);
 
+      /* Write the keywords first (to avoid having to shift the image if
+         there are many keywords). */
+      gal_fits_img_write_to_ptr_keys(fptr, towrite, datatype, hasblank,
+                                     keylist, freekeys);
+
       /* Write the image into the file. */
+      if(keylist) fits_resize_img(fptr, bitpix, ndim, naxes, &status);
       fits_write_img(fptr, datatype, fpixel, towrite->size, towrite->array,
                      &status);
       gal_fits_io_error(status, NULL);
     }
 
-
-  /* Remove the two comment lines put by CFITSIO. Note that in some cases,
-     it might not exist. When this happens, the status value will be
-     non-zero. We don't care about this error, so to be safe, we will just
-     reset the status variable after these calls. */
-  fits_delete_key(fptr, "COMMENT", &status);
-  fits_delete_key(fptr, "COMMENT", &status);
-  status=0;
-
-
-  /* If we have blank pixels, we need to define a BLANK keyword when we are
-     dealing with integer types. */
-  if(hasblank)
-    switch(towrite->type)
-      {
-      case GAL_TYPE_FLOAT32:
-      case GAL_TYPE_FLOAT64:
-        /* Do nothing! Since there are much fewer floating point types
-           (that don't need any BLANK keyword), we are checking them. */
-        break;
-
-      default:
-        blank=gal_fits_key_img_blank(towrite->type);
-        if(fits_write_key(fptr, datatype, "BLANK", blank,
-                          "Pixels with no data.", &status) )
-          gal_fits_io_error(status, "adding the BLANK keyword");
-        free(blank);
-      }
-
-
-  /* Write the extension name to the header. */
-  if(towrite->name)
-    fits_write_key(fptr, TSTRING, "EXTNAME", towrite->name, "", &status);
-
-
-  /* Write the units to the header. */
-  if(towrite->unit)
-    fits_write_key(fptr, TSTRING, "BUNIT", towrite->unit, "", &status);
-
-
-  /* Write comments if they exist. */
-  if(towrite->comment)
-    fits_write_comment(fptr, towrite->comment, &status);
-
-
-  /* If a WCS structure is present, write it in the FITS file pointer
-     ('fptr'). */
-  if(towrite->wcs)
-    gal_wcs_write_in_fitsptr(fptr, towrite->wcs);
-
-
   /* If there were any errors, report them and return.*/
   free(naxes);
   gal_fits_io_error(status, NULL);
@@ -2890,16 +2942,13 @@ gal_fits_img_write_to_ptr(gal_data_t *input, char 
*filename)
 
 void
 gal_fits_img_write(gal_data_t *data, char *filename,
-                   gal_fits_list_key_t *headers, char *program_string)
+                   gal_fits_list_key_t *keylist, int freekeys)
 {
   int status=0;
-  fitsfile *fptr;
+  fitsfile *fptr=NULL;
 
   /* Write the data array into a FITS file and keep it open: */
-  fptr=gal_fits_img_write_to_ptr(data, filename);
-
-  /* Write all the headers and the version information. */
-  gal_fits_key_write_version_in_ptr(&headers, program_string, fptr);
+  fptr=gal_fits_img_write_to_ptr(data, filename, keylist, freekeys);
 
   /* Close the FITS file. */
   fits_close_file(fptr, &status);
@@ -2912,8 +2961,8 @@ gal_fits_img_write(gal_data_t *data, char *filename,
 
 void
 gal_fits_img_write_to_type(gal_data_t *data, char *filename,
-                           gal_fits_list_key_t *headers,
-                           char *program_string, int type)
+                           gal_fits_list_key_t *headers, int type,
+                           int freekeys)
 {
   /* If the input dataset is not the correct type, then convert it,
      otherwise, use the input data structure. */
@@ -2922,7 +2971,7 @@ gal_fits_img_write_to_type(gal_data_t *data, char 
*filename,
                          : gal_data_copy_to_new_type(data, type));
 
   /* Write the converted dataset into an image. */
-  gal_fits_img_write(towrite, filename, headers, program_string);
+  gal_fits_img_write(towrite, filename, headers, freekeys);
 
   /* Free the dataset if it was allocated. */
   if(towrite!=data) gal_data_free(towrite);
@@ -2947,8 +2996,7 @@ gal_fits_img_write_to_type(gal_data_t *data, char 
*filename,
 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)
+                                gal_fits_list_key_t *keylist, int freekeys)
 {
   int status=0;
   fitsfile *fptr;
@@ -2959,7 +3007,7 @@ gal_fits_img_write_corr_wcs_str(gal_data_t *input, char 
*filename,
           __func__);
 
   /* Write the data array into a FITS file and keep it open. */
-  fptr=gal_fits_img_write_to_ptr(input, filename);
+  fptr=gal_fits_img_write_to_ptr(input, filename, keylist, freekeys);
 
   /* Write the WCS headers into the FITS file. */
   gal_fits_key_write_wcsstr(fptr, NULL, wcsstr, nkeyrec);
@@ -2977,9 +3025,6 @@ gal_fits_img_write_corr_wcs_str(gal_data_t *input, char 
*filename,
       gal_fits_io_error(status, NULL);
     }
 
-  /* Write all the headers and the version information. */
-  gal_fits_key_write_version_in_ptr(&headers, program_string, fptr);
-
   /* Close the file and return. */
   fits_close_file(fptr, &status);
   gal_fits_io_error(status, NULL);
@@ -4320,7 +4365,7 @@ fits_tab_write_col(fitsfile *fptr, gal_data_t *col, int 
tableformat,
 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)
+                   struct gal_fits_list_key_t *keylist, int freekeys)
 {
   fitsfile *fptr;
   gal_data_t *col;
@@ -4369,16 +4414,12 @@ gal_fits_tab_write(gal_data_t *cols, gal_list_str_t 
*comments,
     fits_tab_write_col(fptr, col, tableformat, &i, tform[i], filename);
 
   /* Write the requested keywords. */
-  if(keylist)
-    gal_fits_key_write_in_ptr(keylist, fptr);
+  if(keylist) gal_fits_key_write_in_ptr(keylist, fptr, freekeys);
 
   /* Write the comments if there were any. */
   for(strt=comments; strt!=NULL; strt=strt->next)
     fits_write_comment(fptr, strt->v, &status);
 
-  /* Write all the headers and the version information. */
-  gal_fits_key_write_version_in_ptr(NULL, NULL, fptr);
-
   /* Clean up and close the FITS file. Note that each element in the
      'ttype' and 'tunit' arrays just points to the respective string in the
      column data structure, the space for each element of the array wasn't
diff --git a/lib/gnuastro-internal/commonopts.h 
b/lib/gnuastro-internal/commonopts.h
index 78e6d624..0eb89f44 100644
--- a/lib/gnuastro-internal/commonopts.h
+++ b/lib/gnuastro-internal/commonopts.h
@@ -315,6 +315,58 @@ struct argp_option gal_commonopts_options[] =
       GAL_OPTIONS_NOT_MANDATORY,
       GAL_OPTIONS_NOT_SET
     },
+    {
+      "outfitsnodate",
+      GAL_OPTIONS_KEY_OUTFITSNODATE,
+      0,
+      0,
+      "No 'DATE' in 0-th HDU of output FITS.",
+      GAL_OPTIONS_GROUP_OUTPUT,
+      &cp->outfitsnodate,
+      GAL_OPTIONS_NO_ARG_TYPE,
+      GAL_OPTIONS_RANGE_0_OR_1,
+      GAL_OPTIONS_NOT_MANDATORY,
+      GAL_OPTIONS_NOT_SET
+    },
+    {
+      "outfitsnoversions",
+      GAL_OPTIONS_KEY_OUTFITSNOVERSIONS,
+      0,
+      0,
+      "No versions in 0-th HDU of output FITS.",
+      GAL_OPTIONS_GROUP_OUTPUT,
+      &cp->outfitsnoversions,
+      GAL_OPTIONS_NO_ARG_TYPE,
+      GAL_OPTIONS_RANGE_0_OR_1,
+      GAL_OPTIONS_NOT_MANDATORY,
+      GAL_OPTIONS_NOT_SET
+    },
+    {
+      "outfitsnocommit",
+      GAL_OPTIONS_KEY_OUTFITSNOCOMMIT,
+      0,
+      0,
+      "No Git commit in 0-th HDU of output FITS.",
+      GAL_OPTIONS_GROUP_OUTPUT,
+      &cp->outfitsnocommit,
+      GAL_OPTIONS_NO_ARG_TYPE,
+      GAL_OPTIONS_RANGE_0_OR_1,
+      GAL_OPTIONS_NOT_MANDATORY,
+      GAL_OPTIONS_NOT_SET
+    },
+    {
+      "outfitsnoconfig",
+      GAL_OPTIONS_KEY_OUTFITSNOCONFIG,
+      0,
+      0,
+      "No metadata in 0-th HDU of output FITS.",
+      GAL_OPTIONS_GROUP_OUTPUT,
+      &cp->outfitsnoconfig,
+      GAL_OPTIONS_NO_ARG_TYPE,
+      GAL_OPTIONS_RANGE_0_OR_1,
+      GAL_OPTIONS_NOT_MANDATORY,
+      GAL_OPTIONS_NOT_SET
+    },
 
 
 
diff --git a/lib/gnuastro-internal/options.h b/lib/gnuastro-internal/options.h
index 1d4b117e..508142ee 100644
--- a/lib/gnuastro-internal/options.h
+++ b/lib/gnuastro-internal/options.h
@@ -109,25 +109,29 @@ enum options_common_keys
   GAL_OPTIONS_KEY_REMAINDERFRAC = 'F',
 
   /* Only long option (integers for keywords). */
-  GAL_OPTIONS_KEY_STDINTIMEOUT = 500,
-  GAL_OPTIONS_KEY_MINMAPSIZE,
-  GAL_OPTIONS_KEY_QUIETMMAP,
-  GAL_OPTIONS_KEY_LOG,
+  GAL_OPTIONS_KEY_LOG           = 500,
   GAL_OPTIONS_KEY_CITE,
   GAL_OPTIONS_KEY_CONFIG,
   GAL_OPTIONS_KEY_SEARCHIN,
+  GAL_OPTIONS_KEY_QUIETMMAP,
+  GAL_OPTIONS_KEY_WORKOVERCH,
+  GAL_OPTIONS_KEY_MINMAPSIZE,
   GAL_OPTIONS_KEY_LASTCONFIG,
-  GAL_OPTIONS_KEY_CHECKCONFIG,
-  GAL_OPTIONS_KEY_CONFIGPREFIX,
+  GAL_OPTIONS_KEY_CHECKTILES,
   GAL_OPTIONS_KEY_TABLEFORMAT,
+  GAL_OPTIONS_KEY_CHECKCONFIG,
   GAL_OPTIONS_KEY_ONLYVERSION,
-  GAL_OPTIONS_KEY_WORKOVERCH,
-  GAL_OPTIONS_KEY_CHECKTILES,
-  GAL_OPTIONS_KEY_ONEELEMPERTILE,
-  GAL_OPTIONS_KEY_INTERPONLYBLANK,
+  GAL_OPTIONS_KEY_CONFIGPREFIX,
   GAL_OPTIONS_KEY_INTERPMETRIC,
+  GAL_OPTIONS_KEY_STDINTIMEOUT,
   GAL_OPTIONS_KEY_INTERPNUMNGB,
+  GAL_OPTIONS_KEY_OUTFITSNODATE,
+  GAL_OPTIONS_KEY_ONEELEMPERTILE,
+  GAL_OPTIONS_KEY_OUTFITSNOCONFIG,
+  GAL_OPTIONS_KEY_OUTFITSNOCOMMIT,
+  GAL_OPTIONS_KEY_INTERPONLYBLANK,
   GAL_OPTIONS_KEY_WCSLINEARMATRIX,
+  GAL_OPTIONS_KEY_OUTFITSNOVERSIONS,
 };
 
 
@@ -201,6 +205,11 @@ struct gal_options_common_params
   uint8_t      wcslinearmatrix; /* WCS matrix to use (PC or CD).          */
   uint8_t           dontdelete; /* ==1: Don't delete existing file.       */
   uint8_t         keepinputdir; /* Keep input directory for auto output.  */
+  uint8_t        outfitsnodate; /* No 'DATE' in 0th FITS HDU.             */
+  uint8_t      outfitsnocommit; /* No Git commit in 0th FITS HDU.         */
+  uint8_t    outfitsnoversions; /* No software versions in FITS.          */
+  uint8_t      outfitsnoconfig; /* No metadata in 0th FITS HDU.           */
+  gal_fits_list_key_t   *ckeys; /* Conf. as FITS keys in 0th output HDU.  */
 
   /* Operating modes. */
   uint8_t                quiet; /* Only print errors.                     */
@@ -218,9 +227,6 @@ struct gal_options_common_params
   uint8_t          checkconfig; /* Check config files and values.         */
   char           *configprefix; /* Custom prefix in --config files.       */
 
-  /* Output files. */
-  gal_fits_list_key_t  *okeys;  /* Configuration as FITS keys in output.  */
-
   /* For internal (to option processing) purposes. */
   uint8_t                 keep; /* Output file can exist.                 */
   void         *program_struct; /* Host program's main variable struct.   */
@@ -230,9 +236,9 @@ struct gal_options_common_params
   char        *program_authors; /* List of the program authors.           */
   struct argp_option *coptions; /* Common options to all programs.        */
   struct argp_option *poptions; /* Program specific options.              */
-  gal_list_i32_t  *mand_common; /* Common mandatory options.  */
-  gal_list_str_t  *novalue_doc; /* Mandatory opts, no value   */
-  gal_list_str_t *novalue_name; /* Mandatory opts, no value   */
+  gal_list_i32_t  *mand_common; /* Common mandatory options.              */
+  gal_list_str_t  *novalue_doc; /* Mandatory opts, no value               */
+  gal_list_str_t *novalue_name; /* Mandatory opts, no value               */
 };
 
 
diff --git a/lib/gnuastro/fits.h b/lib/gnuastro/fits.h
index 41f80e20..d46c08de 100644
--- a/lib/gnuastro/fits.h
+++ b/lib/gnuastro/fits.h
@@ -74,6 +74,8 @@ __BEGIN_C_DECLS  /* From C++ preparations */
 
 
 
+
+
 /* To create a linked list of headers. */
 typedef struct gal_fits_list_key_t
 {
@@ -95,7 +97,10 @@ typedef struct gal_fits_list_key_t
 
 
 
-/* table.h needs 'gal_fits_list_key_t'. */
+
+
+/* table.h needs 'gal_fits_list_key_t', so it should be included here, not
+   with the other headers above. */
 #include <gnuastro/table.h>
 
 
@@ -235,6 +240,16 @@ void
 gal_fits_key_list_fullcomment_add_end(gal_fits_list_key_t **list,
                                       char *fullcomment, int fcfree);
 
+void
+gal_fits_key_list_add_date(gal_fits_list_key_t **keylist,
+                           char *comment);
+
+void
+gal_fits_key_list_add_software_versions(gal_fits_list_key_t **keylist);
+
+void
+gal_fits_key_list_add_git_commit(gal_fits_list_key_t **keylist);
+
 void
 gal_fits_key_list_reverse(gal_fits_list_key_t **list);
 
@@ -251,25 +266,13 @@ gal_fits_key_write_wcsstr(fitsfile *fptr, struct wcsprm 
*wcs,
                           char *wcsstr, int nkeyrec);
 
 void
-gal_fits_key_write(gal_fits_list_key_t **keylist, char *title,
-                   char *filename, char *hdu, char *hdu_option_name);
-
-void
-gal_fits_key_write_in_ptr(gal_fits_list_key_t **keylist, fitsfile *fptr);
-
-void
-gal_fits_key_write_version(gal_fits_list_key_t **keylist, char *title,
-                           char *filename, char *hdu,
-                           char *hdu_option_name);
-
-void
-gal_fits_key_write_version_in_ptr(gal_fits_list_key_t **keylist, char *title,
-                                  fitsfile *fptr);
+gal_fits_key_write(gal_fits_list_key_t *keylist, char *filename,
+                   char *hdu, char *hdu_option_name, int freekeys,
+                   int create_fits_not_exists);
 
 void
-gal_fits_key_write_config(gal_fits_list_key_t **keylist, char *title,
-                          char *extname, char *filename, char *hdu,
-                          char *hdu_option_name);
+gal_fits_key_write_in_ptr(gal_fits_list_key_t *keylist,
+                          fitsfile *fptr, int freekeys);
 
 gal_list_str_t *
 gal_fits_with_keyvalue(gal_list_str_t *files, char *hdu, char *name,
@@ -308,22 +311,22 @@ gal_fits_img_read_kernel(char *filename, char *hdu, 
size_t minmapsize,
                          int quietmmap, char *hdu_option_name);
 
 fitsfile *
-gal_fits_img_write_to_ptr(gal_data_t *data, char *filename);
+gal_fits_img_write_to_ptr(gal_data_t *data, char *filename,
+                          gal_fits_list_key_t *keylist, int freekeys);
 
 void
 gal_fits_img_write(gal_data_t *data, char *filename,
-                   gal_fits_list_key_t *headers, char *program_string);
+                   gal_fits_list_key_t *keylist, int freekeys);
 
 void
 gal_fits_img_write_to_type(gal_data_t *data, char *filename,
-                           gal_fits_list_key_t *headers,
-                           char *program_string, int type);
+                           gal_fits_list_key_t *keylist, int type,
+                           int freekeys);
 
 void
 gal_fits_img_write_corr_wcs_str(gal_data_t *input, char *filename,
                                 char *wcsheader, int nkeyrec, double *crpix,
-                                gal_fits_list_key_t *headers,
-                                char *program_string);
+                                gal_fits_list_key_t *keylist, int freekeys);
 
 
 
@@ -352,7 +355,7 @@ gal_fits_tab_read(char *filename, char *hdu, size_t numrows,
 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 **keywords);
+                   struct gal_fits_list_key_t *keywords, int freekeys);
 
 
 
diff --git a/lib/gnuastro/table.h b/lib/gnuastro/table.h
index d0700e1d..2feb6379 100644
--- a/lib/gnuastro/table.h
+++ b/lib/gnuastro/table.h
@@ -171,9 +171,9 @@ gal_table_comments_add_intro(gal_list_str_t **comments,
                              char *program_string, time_t *rawtime);
 
 void
-gal_table_write(gal_data_t *cols, struct gal_fits_list_key_t **keylist,
+gal_table_write(gal_data_t *cols, struct gal_fits_list_key_t *keylist,
                 gal_list_str_t *comments, int tableformat, char *filename,
-                char *extname, uint8_t colinfoinstdout);
+                char *extname, uint8_t colinfoinstdout, int freekeys);
 
 void
 gal_table_write_log(gal_data_t *logll, char *program_string,
diff --git a/lib/gnuastro/tile.h b/lib/gnuastro/tile.h
index ee0f7e29..fb9a417a 100644
--- a/lib/gnuastro/tile.h
+++ b/lib/gnuastro/tile.h
@@ -153,7 +153,7 @@ void
 gal_tile_full_values_write(gal_data_t *tilevalues,
                            struct gal_tile_two_layer_params *tl,
                            int withblank, char *filename,
-                           gal_fits_list_key_t *keys, char *program_string);
+                           gal_fits_list_key_t *keys, int freekeys);
 
 gal_data_t *
 gal_tile_full_values_smooth(gal_data_t *tilevalues,
diff --git a/lib/gnuastro/txt.h b/lib/gnuastro/txt.h
index 9ca95687..ad48d8a9 100644
--- a/lib/gnuastro/txt.h
+++ b/lib/gnuastro/txt.h
@@ -106,9 +106,9 @@ gal_list_str_t *
 gal_txt_stdin_read(long timeout_microsec);
 
 void
-gal_txt_write(gal_data_t *input, struct gal_fits_list_key_t **keylist,
+gal_txt_write(gal_data_t *input, struct gal_fits_list_key_t *keylist,
               gal_list_str_t *comment, char *filename,
-              uint8_t colinfoinstdout, int tab0_img1);
+              uint8_t colinfoinstdout, int tab0_img1, int freekeys);
 
 
 
diff --git a/lib/gnuastro/wcs.h b/lib/gnuastro/wcs.h
index 35be2a5b..5c02be4a 100644
--- a/lib/gnuastro/wcs.h
+++ b/lib/gnuastro/wcs.h
@@ -184,7 +184,7 @@ gal_wcs_dimension_name(struct wcsprm *wcs, size_t 
dimension);
 void
 gal_wcs_write(struct wcsprm *wcs, char *filename,
               char *extname, gal_fits_list_key_t *headers,
-              char *program_string);
+              char *program_string, int freekeys);
 
 char *
 gal_wcs_write_wcsstr(struct wcsprm *wcs, int *nkeyrec);
diff --git a/lib/options.c b/lib/options.c
index b3f6e714..1e7bfb79 100644
--- a/lib/options.c
+++ b/lib/options.c
@@ -3450,10 +3450,46 @@ options_as_fits_keywords_write(gal_fits_list_key_t 
**keys,
 void
 gal_options_as_fits_keywords(struct gal_options_common_params *cp)
 {
+  char *pname_upper, *extname;
+
+  /* If the user doesn't want any configuration, return (so the pointer
+     remains NULL and nothing is written). */
+  if( cp->outfitsnoconfig ) return;
+
+  /* Add the extension name (to be all-caps). Note that 'toupper' will make
+     it upper-case inplace (which is not intended here). */
+  gal_checkset_allocate_copy(cp->program_name, &pname_upper);
+  gal_checkset_string_case_change(pname_upper, 1);
+  extname=gal_checkset_malloc_cat(pname_upper, "-CONFIG");
+  gal_fits_key_list_add(&cp->ckeys, GAL_TYPE_STRING, "EXTNAME", 0,
+                        extname, 1, "HDU name", 0, NULL, 0);
+  free(pname_upper);
+
+  /* Title for general configuration keywords. */
+  gal_fits_key_list_title_add(&cp->ckeys, "Option values", 0);
+
   /* Write all the command and program-specific options. */
-  options_as_fits_keywords_write(&cp->okeys, cp->coptions, cp);
-  options_as_fits_keywords_write(&cp->okeys, cp->poptions, cp);
+  options_as_fits_keywords_write(&cp->ckeys, cp->coptions, cp);
+  options_as_fits_keywords_write(&cp->ckeys, cp->poptions, cp);
+
+  /* Write the library version information into the configuration
+     keywords. */
+  if(    cp->outfitsnodate==0
+      || cp->outfitsnoversions==0
+      || cp->outfitsnocommit==0 )
+    {
+      if(cp->outfitsnodate==0)
+        gal_fits_key_list_title_add(&cp->ckeys, "Versions and date", 0);
+      else
+        gal_fits_key_list_title_add(&cp->ckeys, "Versions", 0);
+      if(cp->outfitsnodate==0)
+        gal_fits_key_list_add_date(&cp->ckeys, "Date processing started.");
+      if(cp->outfitsnoversions==0)
+        gal_fits_key_list_add_software_versions(&cp->ckeys);
+      if(cp->outfitsnocommit==0)
+        gal_fits_key_list_add_git_commit(&cp->ckeys);
+    }
 
   /* Reverse the list (its a first-in-first-out list). */
-  gal_fits_key_list_reverse(&cp->okeys);
+  gal_fits_key_list_reverse(&cp->ckeys);
 }
diff --git a/lib/table.c b/lib/table.c
index 6a831f63..fa9dae5a 100644
--- a/lib/table.c
+++ b/lib/table.c
@@ -595,9 +595,9 @@ gal_table_comments_add_intro(gal_list_str_t **comments,
    table will then be written into 'filename' with a format that is
    specified by 'tableformat'. */
 void
-gal_table_write(gal_data_t *cols, struct gal_fits_list_key_t **keylist,
+gal_table_write(gal_data_t *cols, struct gal_fits_list_key_t *keylist,
                 gal_list_str_t *comments, int tableformat, char *filename,
-                char *extname, uint8_t colinfoinstdout)
+                char *extname, uint8_t colinfoinstdout, int freekeys)
 {
   /* If a filename was given, then the tableformat is relevant and must be
      used. When the filename is empty, a text table must be printed on the
@@ -606,14 +606,15 @@ gal_table_write(gal_data_t *cols, struct 
gal_fits_list_key_t **keylist,
     {
       if(gal_fits_name_is_fits(filename))
         gal_fits_tab_write(cols, comments, tableformat, filename, extname,
-                           keylist);
+                           keylist, freekeys);
       else
         gal_txt_write(cols, keylist, comments, filename,
-                      colinfoinstdout, 0);
+                      colinfoinstdout, 0, freekeys);
     }
   else
     /* Write to standard output. */
-    gal_txt_write(cols, keylist, comments, filename, colinfoinstdout, 0);
+    gal_txt_write(cols, keylist, comments, filename, colinfoinstdout, 0,
+                  freekeys);
 }
 
 
@@ -632,7 +633,7 @@ gal_table_write_log(gal_data_t *logll, char *program_string,
 
   /* Write the log file to disk. */
   gal_table_write(logll, NULL, comments, GAL_TABLE_FORMAT_TXT,
-                  filename, "LOG", 0);
+                  filename, "LOG", 0, 0);
 
   /* In verbose mode, print the information. */
   if(!quiet)
diff --git a/lib/tile-internal.c b/lib/tile-internal.c
index b9f8f259..9dce8904 100644
--- a/lib/tile-internal.c
+++ b/lib/tile-internal.c
@@ -252,9 +252,9 @@ gal_tileinternal_no_outlier(gal_data_t *first, gal_data_t 
*second,
 
   /* A small sanity check. */
   if(first->size!=tl->tottiles)
-    error(EXIT_FAILURE, 0, "%s: 'first->size' and 'tl->tottiles' must have "
-          "the same value, but they don't: %zu, %zu", __func__, first->size,
-          tl->tottiles);
+    error(EXIT_FAILURE, 0, "%s: 'first->size' and 'tl->tottiles' must "
+          "have the same value, but they don't: %zu, %zu", __func__,
+          first->size, tl->tottiles);
 
   /* Do the work. */
   for(i=0;i<tl->totchannels;++i)
@@ -266,13 +266,14 @@ gal_tileinternal_no_outlier(gal_data_t *first, gal_data_t 
*second,
     {
       first->name="VALUE1_NO_OUTLIER";
       second->name="VALUE2_NO_OUTLIER";
-      gal_tile_full_values_write(first, tl, 1, filename, NULL, NULL);
-      gal_tile_full_values_write(second, tl, 1, filename, NULL, NULL);
+      gal_tile_full_values_write(first, tl, 1, filename, NULL, 0);
+      gal_tile_full_values_write(second, tl, 1, filename, NULL, 0);
       first->name=second->name=NULL;
       if(third)
         {
           third->name="VALUE3_NO_OUTLIER";
-          gal_tile_full_values_write(third, tl, 1, filename, NULL, NULL);
+          gal_tile_full_values_write(third, tl, 1, filename,
+                                     NULL, 0);
           third->name=NULL;
         }
     }
@@ -706,18 +707,20 @@ gal_tileinternal_no_outlier_local(gal_data_t *input, 
gal_data_t *second,
   if(filename)
     {
       input->name="VALUE1_NO_OUTLIER";
-      gal_tile_full_values_write(input, tl, 1, filename, NULL, NULL);
+      gal_tile_full_values_write(input, tl, 1, filename, NULL, 0);
       input->name=NULL;
       if(second)
         {
           second->name="VALUE2_NO_OUTLIER";
-          gal_tile_full_values_write(second, tl, 1, filename, NULL, NULL);
+          gal_tile_full_values_write(second, tl, 1, filename,
+                                     NULL, 0);
           second->name=NULL;
         }
       if(third)
         {
           third->name="VALUE3_NO_OUTLIER";
-          gal_tile_full_values_write(third, tl, 1, filename, NULL, NULL);
+          gal_tile_full_values_write(third, tl, 1, filename,
+                                     NULL, 0);
           third->name=NULL;
         }
     }
diff --git a/lib/tile.c b/lib/tile.c
index 973dec84..fbd04dbb 100644
--- a/lib/tile.c
+++ b/lib/tile.c
@@ -1110,7 +1110,7 @@ void
 gal_tile_full_values_write(gal_data_t *tilevalues,
                            struct gal_tile_two_layer_params *tl,
                            int withblank, char *filename,
-                           gal_fits_list_key_t *keys, char *program_string)
+                           gal_fits_list_key_t *keys, int freekeys)
 {
   gal_data_t *disp;
 
@@ -1121,8 +1121,8 @@ gal_tile_full_values_write(gal_data_t *tilevalues,
         {
           /* A small sanity check. */
           if(tl->permutation==NULL)
-            error(EXIT_FAILURE, 0, "%s: no permutation defined for the input "
-                  "tessellation", __func__);
+            error(EXIT_FAILURE, 0, "%s: no permutation defined for the "
+                  "input tessellation", __func__);
 
           /* Writing tile values to disk is not done for checking, not for
              efficiency. So to be safe (allow the caller to work on
@@ -1138,7 +1138,7 @@ gal_tile_full_values_write(gal_data_t *tilevalues,
                                           withblank, 0);
 
   /* Write the array as a file and then clean up (if necessary). */
-  gal_fits_img_write(disp, filename, keys, program_string);
+  gal_fits_img_write(disp, filename, keys, freekeys);
   if(disp!=tilevalues) gal_data_free(disp);
 }
 
diff --git a/lib/txt.c b/lib/txt.c
index 8cb7ecd2..3d994444 100644
--- a/lib/txt.c
+++ b/lib/txt.c
@@ -1918,13 +1918,14 @@ txt_write_metadata(FILE *fp, gal_data_t *datall, char 
**fmts,
 
 
 static void
-txt_write_keys(FILE *fp, struct gal_fits_list_key_t **keylist)
+txt_write_keys(FILE *fp, struct gal_fits_list_key_t *keylist,
+               int freekeys)
 {
   char *ending;
   char *valuestr;
   gal_fits_list_key_t *tmp, *ttmp;
 
-  tmp=*keylist;
+  tmp=keylist;
   while(tmp!=NULL)
     {
       /* If a title is requested, only put a title. */
@@ -1981,9 +1982,6 @@ txt_write_keys(FILE *fp, struct gal_fits_list_key_t 
**keylist)
       free(tmp);
       tmp=ttmp;
     }
-
-  /* Set it to NULL so it isn't mistakenly used later. */
-  *keylist=NULL;
 }
 
 
@@ -1991,9 +1989,9 @@ txt_write_keys(FILE *fp, struct gal_fits_list_key_t 
**keylist)
 
 
 void
-gal_txt_write(gal_data_t *input, struct gal_fits_list_key_t **keylist,
+gal_txt_write(gal_data_t *input, struct gal_fits_list_key_t *keylist,
               gal_list_str_t *comment, char *filename,
-              uint8_t colinfoinstdout, int tab0_img1)
+              uint8_t colinfoinstdout, int tab0_img1, int freekeys)
 {
   FILE *fp;
   char **fmts;
@@ -2070,7 +2068,7 @@ gal_txt_write(gal_data_t *input, struct 
gal_fits_list_key_t **keylist,
         fprintf(fp, "# %s\n", strt->v);
 
       /* Write the keywords. */
-      if(keylist) txt_write_keys(fp, keylist);
+      if(keylist) txt_write_keys(fp, keylist, freekeys);
     }
   else
     fp=stdout;
diff --git a/lib/wcs.c b/lib/wcs.c
index 630704a7..0299d69a 100644
--- a/lib/wcs.c
+++ b/lib/wcs.c
@@ -791,8 +791,8 @@ gal_wcs_write_in_fitsptr(fitsfile *fptr, struct wcsprm *wcs)
 
 void
 gal_wcs_write(struct wcsprm *wcs, char *filename,
-              char *extname, gal_fits_list_key_t *headers,
-              char *program_string)
+              char *extname, gal_fits_list_key_t *keylist,
+              char *program_string, int freekeys)
 {
   int status=0;
   size_t ndim=0;
@@ -828,8 +828,10 @@ gal_wcs_write(struct wcsprm *wcs, char *filename,
   /* Write the WCS structure. */
   gal_wcs_write_in_fitsptr(fptr, wcs);
 
-  /* Write all the headers and the version information. */
-  gal_fits_key_write_version_in_ptr(&headers, program_string, fptr);
+  /* Write all the headers and the version information (note that after
+     writing the keywords they are automatically freed). */
+  gal_fits_key_list_title_add(&keylist, program_string, 0);
+  gal_fits_key_write_in_ptr(keylist, fptr, freekeys);
 
   /* Close the FITS file. */
   fits_close_file(fptr, &status);
diff --git a/tests/buildprog/simpleio.c b/tests/buildprog/simpleio.c
index a66a96cf..38c33568 100644
--- a/tests/buildprog/simpleio.c
+++ b/tests/buildprog/simpleio.c
@@ -45,7 +45,7 @@ main(int argc, char *argv[])
   printf("%s (hdu %s) is read into memory.\n", argv[1], argv[2]);
 
   /* Save the image in memory into another file. */
-  gal_fits_img_write(image, outname, NULL, "BuildProgram's Simpleio");
+  gal_fits_img_write(image, outname, NULL, 0);
 
   /* Let the user know. */
   printf("%s created.\n", outname);



reply via email to

[Prev in Thread] Current Thread [Next in Thread]