gnuastro-commits
[Top][All Lists]
Advanced

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

[gnuastro-commits] master 05514830 2/2: Library (txt.c): Vector columns


From: Mohammad Akhlaghi
Subject: [gnuastro-commits] master 05514830 2/2: Library (txt.c): Vector columns are now allowed
Date: Sat, 31 Dec 2022 22:31:15 -0500 (EST)

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

    Library (txt.c): Vector columns are now allowed
    
    Until now, there was no way to keep multi-valued vector columns in
    Gnuastro's plain-text table format. Therefore, while reading/writing vector
    columns was not too hard with CFITSIO in FITS tables, vector columns hadn't
    been implemented yet in Gnuastro!
    
    With this commit, an extra '(N)' component has been added after the type
    component of the plain-text format metadata, which instructs the text
    library to read multiple tokens into one vector column. To be able to work
    with vector columns, three options have been added to the Table program:
    '--tovector' (which will merge multiple single-element columns into a
    vector column), '--fromvector' (which will extract a single-valued column
    from a vector column) and '--keepvectfin (which will avoid the deletion of
    the previous two options).
    
    In the process, the following things have also been done:
    
     - Some lines in the source that had more than 75 characters were broken
       into multiple lines to follow Gnuastro's coding convention.
    
     - The 'gal_fits_tab_write' function was broken into multiple smaller
       functions to help in the modularity and adding the new vector column
       features.
    
     - The 'make check' test for table's conversion now has a vector column
       also.
---
 NEWS                             |  39 +-
 bin/convertt/convertt.c          |   2 +-
 bin/cosmiccal/ui.c               |  10 +-
 bin/match/match.c                |  29 +-
 bin/match/ui.c                   |  41 +-
 bin/mkcatalog/ui.c               |  23 +-
 bin/mkprof/ui.c                  |   5 +-
 bin/statistics/ui.c              |   8 +-
 bin/table/args.h                 | 105 +++--
 bin/table/arithmetic.c           |   9 +-
 bin/table/main.h                 |   7 +-
 bin/table/table.c                | 379 ++++++++++++---
 bin/table/ui.c                   |  13 +-
 bin/table/ui.h                   |   8 +-
 bin/warp/ui.c                    |  12 +-
 doc/gnuastro.texi                | 586 +++++++++++++++--------
 lib/blank.c                      | 119 ++++-
 lib/fits.c                       | 387 ++++++++++-----
 lib/gnuastro-internal/options.h  |  16 +-
 lib/gnuastro/blank.h             |   3 +-
 lib/gnuastro/list.h              |  10 +
 lib/gnuastro/permutation.h       |   3 +
 lib/gnuastro/table.h             |  11 +-
 lib/gnuastro/txt.h               |   4 +-
 lib/list.c                       | 119 ++++-
 lib/options.c                    | 240 ++++++----
 lib/permutation.c                |  36 +-
 lib/table.c                      | 194 ++++++--
 lib/tableintern.c                |   4 +-
 lib/txt.c                        | 981 ++++++++++++++++++++++++++-------------
 tests/during-dev.sh              |  10 +-
 tests/match/sort-based.sh        |   2 +-
 tests/script/psf-select-stars.sh |   2 +-
 tests/table/table.txt            |  29 +-
 34 files changed, 2476 insertions(+), 970 deletions(-)

diff --git a/NEWS b/NEWS
index 467bfba6..bbb9e384 100644
--- a/NEWS
+++ b/NEWS
@@ -90,9 +90,20 @@ See the end of the file for license conditions.
    --outliernumngb: see description of same option in NoiseChisel.
 
    Table:
-   --information: also identifies vector columns (a column with more than
-     one value), by placing a '[N]' after the type. Where 'N' is the number
-     of elements within the vector.
+   - Vector columns with multiple values per column are now supported. The
+     following features have been added to help working on vector columns:
+     --Book: a new "Vector columns" section has been added under the Table
+       program which descibes the core concepts and usage examples of the
+       options below.
+     --information: also identifies vector columns (a column with more than
+       one value), by placing an '(N)' after the type. Where 'N' is the
+       number of elements within the vector.
+     --tovector: merge multiple single-valued columns into a new vector
+       column.
+     --fromvector: extract identified elements/tokens of a vector column to
+       separate single-valued columns.
+     --keepvectfin: do not delete the input columns to '--tovector' and
+       '--fromvector'.
    --txteasy: (or '-Y') when output is a plain-text file or just gets
      printed on standard output (terminal), all floating point columns are
      printed in fixed point notation (as in '123.456') instead of the
@@ -129,10 +140,20 @@ See the end of the file for license conditions.
    - GAL_ARITHMETIC_OP_NANOMAGGY_TO_COUNTS: convert nanomaggy to counts.
    - GAL_ARITHMETIC_OP_BOX_VERTICES_ON_SPHERE: calculate the coordinates of
      vertices of a rectable on a sphere from its center and width/height.
-   - gal_units_counts_to_nanomaggy: Convert counts to nanomaggy.
-   - gal_units_nanomaggy_to_counts: Convert nanomaggy to counts.
    - gal_data_alloc_empty: Allocate an empty dataset with a given number of
      dimensions.
+   - gal_list_f64_to_data: convert list of float64s to a 'gal_data_t'
+     dataset with the requested type.
+   - gal_list_data_remove: Remove the given dataset from the given list.
+   - gal_list_data_select_by_id: find/select a dataset from a list of
+     datasets using an identification string (either counter or name).
+   - gal_permutation_apply_onlydim0: When we have a 2D input, apply
+     permutation for all the elements of each row (along dimension-0 in C).
+   - gal_table_col_vector_extract: extract the given elements of a vector
+     column into separate columns.
+   - gal_table_cols_to_vector: merge multiple columns into a vector column.
+   - gal_units_counts_to_nanomaggy: Convert counts to nanomaggy.
+   - gal_units_nanomaggy_to_counts: Convert nanomaggy to counts.
    - gal_wcs_box_vertices_from_center: calculate the coordinates of
      vertices of a rectable on a sphere from its center and width/height.
 
@@ -187,6 +208,14 @@ See the end of the file for license conditions.
   astscript-psf-select-stars:
   - Now uses the Gaia DR3 dataset by default (until now it was using eDR3).
 
+  Library:
+  - gal_blank_remove_rows: new 'onlydim0' argument to ignore vector columns
+    when checking for blanks.
+  - gal_txt_write: new 'tab0_img1' argument. Until now, this function would
+    distinguish between images and tables using the dimensions of the
+    input. But with the addition of vector columns in tables (that have 2
+    dimensions) this argument becomes necessary.
+
 ** Bugs fixed
   bug #63266: Table ignores a value of 0 given to '--txtf32precision' or
               '--txtf32precision=0' (happens when floating point columns
diff --git a/bin/convertt/convertt.c b/bin/convertt/convertt.c
index e2e23d14..05492857 100644
--- a/bin/convertt/convertt.c
+++ b/bin/convertt/convertt.c
@@ -338,7 +338,7 @@ convertt(struct converttparams *p)
     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);
+      gal_txt_write(p->chll, NULL, NULL, p->cp.output, 0, 1);
       break;
 
     /* JPEG: */
diff --git a/bin/cosmiccal/ui.c b/bin/cosmiccal/ui.c
index 5d59ecf5..4ce9766c 100644
--- a/bin/cosmiccal/ui.c
+++ b/bin/cosmiccal/ui.c
@@ -310,7 +310,8 @@ ui_parse_obsline(struct argp_option *option, char *arg,
       *c='\0';
 
       /* Read the parameters. */
-      obsline=gal_options_parse_list_of_numbers(arg, filename, lineno);
+      obsline=gal_options_parse_list_of_numbers(arg, filename, lineno,
+                                                GAL_TYPE_FLOAT64);
 
       /* Only one number must be given as second argument. */
       if(obsline==NULL || obsline->size!=1)
@@ -321,12 +322,13 @@ ui_parse_obsline(struct argp_option *option, char *arg,
       /* If a wavelength is given directly as a number (not a name), then
          put that number in a second element of the array. */
       dptr=&manualwl;
-      if( gal_type_from_string((void **)(&dptr), linename, GAL_TYPE_FLOAT64) )
+      if( gal_type_from_string((void **)(&dptr), linename,
+                               GAL_TYPE_FLOAT64) )
         { /* 'linename' isn't a number. */
           obsline->status=gal_speclines_line_code(linename);
           if(obsline->status==GAL_SPECLINES_INVALID)
-            error(EXIT_FAILURE, 0, "'%s' not recognized as a standard spectral 
"
-                  "line name", linename);
+            error(EXIT_FAILURE, 0, "'%s' not recognized as a standard "
+                  "spectral line name", linename);
         }
       else
         { /* 'linename' is a number. */
diff --git a/bin/match/match.c b/bin/match/match.c
index 15511197..ae62e51d 100644
--- a/bin/match/match.c
+++ b/bin/match/match.c
@@ -224,23 +224,25 @@ static void
 match_arrange_in_new_col(struct matchparams *p, gal_data_t *in,
                          size_t *permutation, size_t nummatched)
 {
-  size_t c=0, i;
+  size_t c=0, i, n;
   size_t istart=p->notmatched ? nummatched : 0;
-  size_t iend=p->notmatched ? in->size : nummatched;
-  size_t outsize=p->notmatched ? in->size - nummatched : nummatched;
+  size_t iend=p->notmatched ? in->dsize[0] : nummatched;
+  size_t outrows=p->notmatched ? in->dsize[0] - nummatched : nummatched;
+
+  /* Set the number of values in this column (for vectors). */
+  n = in->ndim==1 ? 1 : in->dsize[1];
 
   /* Allocate the array. */
-  void *out=gal_pointer_allocate_ram_or_mmap(in->type, outsize, 0,
+  void *out=gal_pointer_allocate_ram_or_mmap(in->type, outrows*n, 0,
                                              p->cp.minmapsize,
                                              &in->mmapname, p->cp.quietmmap,
                                              __func__, "out");
 
   /* Copy the matched rows into the output array. */
   for(i=istart;i<iend;++i)
-    memcpy(gal_pointer_increment(out, c++, in->type),
-           gal_pointer_increment(in->array, permutation[i],
-                                 in->type),
-           gal_type_sizeof(in->type));
+    memcpy(gal_pointer_increment(out,       n*c++,            in->type),
+           gal_pointer_increment(in->array, n*permutation[i], in->type),
+           gal_type_sizeof(in->type) * n);
 
   /**********************************/
   /* Add a check so if the column is a string, we free the strings that
@@ -248,8 +250,9 @@ match_arrange_in_new_col(struct matchparams *p, gal_data_t 
*in,
   /**********************************/
 
   /* Free the existing array, and correct the sizes. */
-  in->size = in->dsize[0] = outsize;
   free(in->array);
+  in->dsize[0] = outrows;
+  in->size = in->dsize[0] * (in->ndim==1 ? 1 : in->dsize[1]);
   in->array=out;
 }
 
@@ -780,8 +783,8 @@ match_catalog(struct matchparams *p)
          tables. So if the log file is a FITS table, covert the two
          index columns to uint32. */
       tmp=gal_data_copy_to_new_type(mcols, GAL_TYPE_UINT32);
+      tmp->size=tmp->dsize[0]=nummatched;
       tmp->next=mcols->next;
-      tmp->size=nummatched;
       gal_data_free(mcols);
       mcols=tmp;
 
@@ -795,9 +798,9 @@ match_catalog(struct matchparams *p)
       /* Same for the second set of indexs. */
       tmp=gal_data_copy_to_new_type(mcols->next, GAL_TYPE_UINT32);
       uf = (u=tmp->array) + tmp->size; do (*u)++; while(++u<uf);
+      tmp->size=tmp->dsize[0]=nummatched;
       tmp->next=mcols->next->next;
       gal_data_free(mcols->next);
-      tmp->size=nummatched;
       mcols->next=tmp;
 
       /* Correct the comments. */
@@ -814,6 +817,10 @@ match_catalog(struct matchparams *p)
       /* Set the comment pointer to NULL: they weren't allocated. */
       mcols->comment=NULL;
       mcols->next->comment=NULL;
+
+      /* Inform the user that a log-file has been created. */
+      if(!p->cp.quiet)
+        fprintf(stdout, "  - Output (log): %s\n", p->logname);
     }
 
   /* Clean up. */
diff --git a/bin/match/ui.c b/bin/match/ui.c
index 7e0ce856..143e9ae5 100644
--- a/bin/match/ui.c
+++ b/bin/match/ui.c
@@ -752,6 +752,12 @@ ui_read_columns_to_double(struct matchparams *p, char 
*filename, char *hdu,
   tmp=tout;
   while(tmp!=NULL)
     {
+      /* If the coordinate column has more than one dimension (a vector
+         column), the abort with an error: */
+      if(tmp->ndim!=1)
+        error(EXIT_FAILURE, 0, "only single-valued columns can be given "
+              "to '--ccol1' or '--ccol2'");
+
       /* We need ot set the 'next' pointer  */
       ttmp=tmp->next;
       tmp->next=NULL;
@@ -993,6 +999,7 @@ ui_preparations_out_cols(struct matchparams *p)
 
 
 
+#define UI_OUT_SUFFNAME "-matched"
 static void
 ui_preparations_out_name(struct matchparams *p)
 {
@@ -1010,10 +1017,10 @@ ui_preparations_out_name(struct matchparams *p)
         {
           if(p->cp.tableformat==GAL_TABLE_FORMAT_TXT)
             p->logname=gal_checkset_automatic_output(&p->cp, refname,
-                                                     "_matched.txt");
+                                                     UI_OUT_SUFFNAME".txt");
           else
             p->logname=gal_checkset_automatic_output(&p->cp, refname,
-                                                     "_matched.fits");
+                                                     UI_OUT_SUFFNAME".fits");
         }
 
       /* Make sure a file with this name doesn't exist. */
@@ -1033,9 +1040,10 @@ ui_preparations_out_name(struct matchparams *p)
           else
             {
               suffix = ( p->kdtreemode==MATCH_KDTREE_BUILD
-                         ? "_kdtree.fits"
+                         ? "-kdtree.fits"
                          : ( p->cp.tableformat==GAL_TABLE_FORMAT_TXT
-                             ? "_matched.txt" : "_matched.fits") );
+                             ? UI_OUT_SUFFNAME".txt"
+                             : UI_OUT_SUFFNAME".fits") );
               p->out1name = gal_checkset_automatic_output(&p->cp,
                                                           refname, suffix);
             }
@@ -1064,10 +1072,12 @@ ui_preparations_out_name(struct matchparams *p)
                   p->cp.keepinputdir=1;
                   p->out1name=gal_checkset_automatic_output(&p->cp,
                                                             p->cp.output,
-                                                            "_matched_1.txt");
+                                                            UI_OUT_SUFFNAME
+                                                            "-1.txt");
                   p->out2name=gal_checkset_automatic_output(&p->cp,
                                                             p->cp.output,
-                                                            "_matched_2.txt");
+                                                            UI_OUT_SUFFNAME
+                                                            "-2.txt");
                   p->cp.keepinputdir=keepinputdir_orig;
                 }
             }
@@ -1076,15 +1086,18 @@ ui_preparations_out_name(struct matchparams *p)
               if(p->cp.tableformat==GAL_TABLE_FORMAT_TXT)
                 {
                   p->out1name=gal_checkset_automatic_output(&p->cp, refname,
-                                                            "_matched_1.txt");
+                                                            UI_OUT_SUFFNAME
+                                                            "-1.txt");
                   p->out2name=gal_checkset_automatic_output(&p->cp,
                                                             p->input2name,
-                                                            "_matched_2.txt");
+                                                            UI_OUT_SUFFNAME
+                                                            "-2.txt");
                 }
               else
                 {
                   p->out1name=gal_checkset_automatic_output(&p->cp, refname,
-                                                            "_matched.fits");
+                                                            UI_OUT_SUFFNAME
+                                                            ".fits");
                   gal_checkset_allocate_copy(p->out1name, &p->out2name);
                 }
             }
@@ -1098,13 +1111,9 @@ ui_preparations_out_name(struct matchparams *p)
 
       /* If a log file is necessary, set its name here. */
       if(p->cp.log)
-        {
-          p->logname = ( p->cp.tableformat==GAL_TABLE_FORMAT_TXT
-                         ? PROGRAM_EXEC".txt"
-                         : PROGRAM_EXEC".fits" );
-          gal_checkset_writable_remove(p->logname, refname, 0,
-                                       p->cp.dontdelete);
-        }
+        p->logname=gal_checkset_automatic_output(&p->cp, p->out1name,
+                                                 UI_OUT_SUFFNAME
+                                                 "-log.fits");
     }
 }
 
diff --git a/bin/mkcatalog/ui.c b/bin/mkcatalog/ui.c
index aaa31fb9..16d12a14 100644
--- a/bin/mkcatalog/ui.c
+++ b/bin/mkcatalog/ui.c
@@ -287,7 +287,8 @@ ui_check_upperlimit(struct argp_option *option, char *arg,
       if(option->set) return NULL;
 
       /* Read the list of numbers as an array. */
-      raw=gal_options_parse_list_of_numbers(arg, filename, lineno);
+      raw=gal_options_parse_list_of_numbers(arg, filename, lineno,
+                                            GAL_TYPE_FLOAT64);
 
       /* Make sure there is at most only two numbers given. */
       if(raw->size>2)
@@ -295,16 +296,18 @@ ui_check_upperlimit(struct argp_option *option, char *arg,
                       "'--%s') contains %zu numbers, but only one or two "
                       "are acceptable.\n\n"
                       "With this option MakeCatalog will write all the "
-                      "positions and values of the random distribution for "
-                      "one particular labeled region into a table. The "
-                      "given value(s) is(are) the label identifier.\n\n"
+                      "positions and values of the random distribution "
+                      "for one particular labeled region into a table. "
+                      "The given value(s) is(are) the label "
+                      "identifier.\n\n"
                       "With one value the distribution for an object will "
-                      "be printed: the givne number will be interpretted as "
-                      "the requested object's label. With two values, the "
-                      "distribution for a specific clump will be written. "
-                      "The first will be interpretted as the clump's host "
-                      "object label and the second as the clump's label "
-                      "within the object", arg, option->name, raw->size);
+                      "be printed: the givne number will be interpretted "
+                      "as the requested object's label. With two values, "
+                      "the distribution for a specific clump will be "
+                      "written. The first will be interpretted as the "
+                      "clump's host object label and the second as the "
+                      "clump's label within the object", arg,
+                      option->name, raw->size);
 
       /* Make sure the given values are integers and that they are larger
          than zero. */
diff --git a/bin/mkprof/ui.c b/bin/mkprof/ui.c
index e001785b..c327a0e4 100644
--- a/bin/mkprof/ui.c
+++ b/bin/mkprof/ui.c
@@ -365,7 +365,8 @@ ui_parse_kernel(struct argp_option *option, char *arg,
               "astmkprof' command) for the meaning of the numbers");
 
       /* Read the parameters. */
-      kernel=gal_options_parse_list_of_numbers(arg, filename, lineno);
+      kernel=gal_options_parse_list_of_numbers(arg, filename, lineno,
+                                               GAL_TYPE_FLOAT64);
 
       /* Put the kernel dataset into the main program structure. */
       *(gal_data_t **)(option->value) = kernel;
@@ -1733,7 +1734,7 @@ ui_finalize_coordinates(struct mkprofparams *p)
          conversion) and print a warning for those rows. IMPORTANT: we
          don't want to update 'p->num' just yet since 'flag' has the size
          of the pre-blank-removal rows. */
-      flag=gal_blank_remove_rows(coords, NULL);
+      flag=gal_blank_remove_rows(coords, NULL, 0);
       if(p->cp.quiet==0)
         {
           fl=flag->array;
diff --git a/bin/statistics/ui.c b/bin/statistics/ui.c
index 6445f0dc..743bf896 100644
--- a/bin/statistics/ui.c
+++ b/bin/statistics/ui.c
@@ -257,7 +257,8 @@ ui_add_to_single_value(struct argp_option *option, char 
*arg,
   else
     {
       /* Read the string of numbers. */
-      inputs=gal_options_parse_list_of_numbers(arg, filename, lineno);
+      inputs=gal_options_parse_list_of_numbers(arg, filename, lineno,
+                                               GAL_TYPE_FLOAT64);
       if(inputs->size==0)
         error(EXIT_FAILURE, 0, "'--%s' needs a value", option->name);
 
@@ -323,7 +324,8 @@ ui_read_quantile_range(struct argp_option *option, char 
*arg,
     }
 
   /* Parse the inputs. */
-  in=gal_options_parse_list_of_numbers(arg, filename, lineno);
+  in=gal_options_parse_list_of_numbers(arg, filename, lineno,
+                                       GAL_TYPE_FLOAT64);
 
   /* Check if there was only two numbers. */
   if(in->size!=1 && in->size!=2)
@@ -1206,7 +1208,7 @@ ui_preparations(struct statisticsparams *p)
       /* Only keep the elements we want. Note that if we have more than one
          column, we need to move the same rows in both (otherwise their
          widths won't be equal). */
-      if(p->input->next) gal_blank_remove_rows(p->input, NULL);
+      if(p->input->next) gal_blank_remove_rows(p->input, NULL, 0);
       else               gal_blank_remove(p->input);
 
       /* Make sure there actually are any (non-blank) elements left. */
diff --git a/bin/table/args.h b/bin/table/args.h
index 796220b3..3c48317d 100644
--- a/bin/table/args.h
+++ b/bin/table/args.h
@@ -167,33 +167,6 @@ struct argp_option program_options[] =
       GAL_OPTIONS_NOT_MANDATORY,
       GAL_OPTIONS_NOT_SET
     },
-    {
-      "catcolumnrawname",
-      UI_KEY_CATCOLUMNRAWNAME,
-      0,
-      0,
-      "Don't touch column names of --catcolumnfile.",
-      GAL_OPTIONS_GROUP_OUTPUT,
-      &p->catcolumnrawname,
-      GAL_OPTIONS_NO_ARG_TYPE,
-      GAL_OPTIONS_RANGE_0_OR_1,
-      GAL_OPTIONS_NOT_MANDATORY,
-      GAL_OPTIONS_NOT_SET
-    },
-    {
-      "colmetadata",
-      UI_KEY_COLMETADATA,
-      "STR,STR[,STR,STR]",
-      0,
-      "Update output metadata (name, unit, comments).",
-      GAL_OPTIONS_GROUP_OUTPUT,
-      &p->colmetadata,
-      GAL_TYPE_STRING,
-      GAL_OPTIONS_RANGE_ANY,
-      GAL_OPTIONS_NOT_MANDATORY,
-      GAL_OPTIONS_NOT_SET,
-      gal_options_parse_name_and_strings
-    },
     {
       "txteasy",
       UI_KEY_TXTEASY,
@@ -264,6 +237,84 @@ struct argp_option program_options[] =
 
 
 
+    /* Output columns. */
+    {
+      0, 0, 0, 0,
+      "Columns in output:",
+      UI_GROUP_OUTCOLS
+    },
+    {
+      "catcolumnrawname",
+      UI_KEY_CATCOLUMNRAWNAME,
+      0,
+      0,
+      "Don't touch column names of --catcolumnfile.",
+      UI_GROUP_OUTCOLS,
+      &p->catcolumnrawname,
+      GAL_OPTIONS_NO_ARG_TYPE,
+      GAL_OPTIONS_RANGE_0_OR_1,
+      GAL_OPTIONS_NOT_MANDATORY,
+      GAL_OPTIONS_NOT_SET
+    },
+    {
+      "colmetadata",
+      UI_KEY_COLMETADATA,
+      "STR,STR[,STR,STR]",
+      0,
+      "Column metadata (name, unit, comments).",
+      UI_GROUP_OUTCOLS,
+      &p->colmetadata,
+      GAL_TYPE_STRING,
+      GAL_OPTIONS_RANGE_ANY,
+      GAL_OPTIONS_NOT_MANDATORY,
+      GAL_OPTIONS_NOT_SET,
+      gal_options_parse_name_and_strings
+    },
+    {
+      "tovector",
+      UI_KEY_TOVECTOR,
+      "STR,STR[,STR]",
+      0,
+      "Merge column(s) into a vector column.",
+      UI_GROUP_OUTCOLS,
+      &p->tovector,
+      GAL_TYPE_STRLL,
+      GAL_OPTIONS_RANGE_ANY,
+      GAL_OPTIONS_NOT_MANDATORY,
+      GAL_OPTIONS_NOT_SET
+    },
+    {
+      "fromvector",
+      UI_KEY_FROMVECTOR,
+      "STR,INT[,INT]",
+      0,
+      "Extract column(s) from a vector column.",
+      UI_GROUP_OUTCOLS,
+      &p->fromvector,
+      GAL_TYPE_STRING,
+      GAL_OPTIONS_RANGE_ANY,
+      GAL_OPTIONS_NOT_MANDATORY,
+      GAL_OPTIONS_NOT_SET,
+      gal_options_parse_name_and_sizets
+    },
+    {
+      "keepvectfin",
+      UI_KEY_KEEPVECTFIN,
+      0,
+      0,
+      "Keep inputs of '--tovector' & '--fromvector'.",
+      UI_GROUP_OUTCOLS,
+      &p->keepvectfin,
+      GAL_OPTIONS_NO_ARG_TYPE,
+      GAL_OPTIONS_RANGE_0_OR_1,
+      GAL_OPTIONS_NOT_MANDATORY,
+      GAL_OPTIONS_NOT_SET
+    },
+
+
+
+
+
     /* Output Rows */
     {
       0, 0, 0, 0,
diff --git a/bin/table/arithmetic.c b/bin/table/arithmetic.c
index 3246e64e..a92224ec 100644
--- a/bin/table/arithmetic.c
+++ b/bin/table/arithmetic.c
@@ -1137,7 +1137,7 @@ arithmetic_reverse_polish(struct tableparams *p,
           token->loadcol=NULL;
         }
 
-      /* Constant number: just put it ontop of the stack. */
+      /* Constant number: just put it on top of the stack. */
       else if(token->constant)
         {
           gal_list_data_add(&stack, token->constant);
@@ -1155,7 +1155,12 @@ arithmetic_reverse_polish(struct tableparams *p,
 
       /* A column from the table. */
       else if(token->index!=GAL_BLANK_SIZE_T)
-        gal_list_data_add(&stack, p->colarray[token->index]);
+        {
+          if(p->colarray[token->index]->ndim!=1)
+            error(EXIT_FAILURE, 0, "column arithmetic currently only works "
+                  "on single-valued columns, not vector columns");
+          gal_list_data_add(&stack, p->colarray[token->index]);
+        }
 
       /* Un-recognized situation. */
       else
diff --git a/bin/table/main.h b/bin/table/main.h
index cdd504fe..15ed66ad 100644
--- a/bin/table/main.h
+++ b/bin/table/main.h
@@ -115,10 +115,13 @@ struct tableparams
   gal_data_t        *rowrange;  /* Output rows in row-counter range.    */
   size_t            rowrandom;  /* Number of rows to show randomly.     */
   uint8_t             envseed;  /* Use the environment for random seed. */
-  gal_list_str_t *catcolumnfile; /* Filename to concat column wise.     */
-  gal_list_str_t *catcolumnhdu;  /* HDU/extension for the catcolumn.    */
+  gal_list_str_t *catcolumnfile;/* Filename to concat column wise.      */
+  gal_list_str_t *catcolumnhdu; /* HDU/extension for the catcolumn.     */
   gal_list_str_t  *catcolumns;  /* List of columns to concatenate.      */
   uint8_t    catcolumnrawname;  /* Don't modify name of appended col.   */
+  gal_data_t      *fromvector;  /* Extract columns from a vector column.*/
+  uint8_t         keepvectfin;  /* Keep in.s --tovector & --fromvector. */
+  gal_list_str_t    *tovector;  /* Merge columns into a vector column.  */
   gal_list_str_t  *catrowfile;  /* Filename to concat column wise.      */
   gal_list_str_t   *catrowhdu;  /* HDU/extension for the catcolumn.     */
   gal_data_t     *colmetadata;  /* Set column metadata.                 */
diff --git a/bin/table/table.c b/bin/table/table.c
index 684a7520..0d49802a 100644
--- a/bin/table/table.c
+++ b/bin/table/table.c
@@ -35,6 +35,7 @@ along with Gnuastro. If not, see 
<http://www.gnu.org/licenses/>.
 #include <gnuastro/txt.h>
 #include <gnuastro/wcs.h>
 #include <gnuastro/fits.h>
+#include <gnuastro/list.h>
 #include <gnuastro/table.h>
 #include <gnuastro/qsort.h>
 #include <gnuastro/pointer.h>
@@ -52,12 +53,30 @@ along with Gnuastro. If not, see 
<http://www.gnu.org/licenses/>.
 
 
 
+
+
 /**************************************************************/
 /********     Selecting and ordering of columns      **********/
 /**************************************************************/
+static void
+table_error_no_column(char *optionname, char *id)
+{
+  error(EXIT_FAILURE, 0, "no column could be found with the '%s' "
+        "identifier (given to '%s'). The value to this option can "
+        "either be a column name or counter (counting from 1). For "
+        "more on how to select columns in Gnuastro, please run the "
+        "command below (press 'q' to come back to the command-line):\n\n"
+        "    info gnuastro \"selecting table columns\"\n",
+        id, optionname);
+}
+
+
+
+
+
 static void
 table_apply_permutation(gal_data_t *table, size_t *permutation,
-                        size_t newsize, int inverse)
+                        size_t permsize, int inverse)
 {
   gal_data_t *tmp;
 
@@ -65,12 +84,24 @@ table_apply_permutation(gal_data_t *table, size_t 
*permutation,
     {
       /* Apply the permutation. */
       if(inverse)
-        gal_permutation_apply_inverse(tmp, permutation);
+        {
+          if(tmp->ndim==1)
+            gal_permutation_apply_inverse(tmp, permutation);
+          else
+            error(EXIT_FAILURE, 0, "%s: inverse permutation on "
+                  "vector columns is not yet supported. Please "
+                  "get in touch with us at '%s' to add this "
+                  "feature", __func__, PACKAGE_BUGREPORT);
+        }
       else
-        gal_permutation_apply(tmp, permutation);
+        {
+          if(tmp->ndim==1) gal_permutation_apply(tmp, permutation);
+          else    gal_permutation_apply_onlydim0(tmp, permutation);
+        }
 
       /* Correct the size. */
-      tmp->size=tmp->dsize[0]=newsize;
+      tmp->dsize[0]=permsize;
+      tmp->size = tmp->dsize[0] * (tmp->ndim==1 ? 1 : tmp->dsize[1]);
     }
 }
 
@@ -83,16 +114,18 @@ table_bring_to_top(gal_data_t *table, gal_data_t *rowids)
 {
   char **strarr;
   gal_data_t *col;
-  size_t i, *ids=rowids->array;
+  size_t i, n, *ids=rowids->array;
 
-  /* Make sure the rowids are sorted by increasing index. */
+  /* Make sure the rowids are sorted by increasing index.
   gal_statistics_sort_increasing(rowids);
+  */
 
   /* Go over each column and move the desired rows to the top. */
   for(col=table;col!=NULL;col=col->next)
     {
-      /* For easy operation if the column is a string. */
+      /* For easy operation if the column is a string or vector. */
       strarr = col->type==GAL_TYPE_STRING ? col->array : NULL;
+      n = col->ndim==1 ? 1 : col->dsize[1];
 
       /* Move the desired rows up to the top. */
       for(i=0;i<rowids->size;++i)
@@ -107,9 +140,11 @@ table_bring_to_top(gal_data_t *table, gal_data_t *rowids)
                 strarr[ ids[i] ]=NULL;
               }
             else
-              memcpy(gal_pointer_increment(col->array, i,      col->type),
-                     gal_pointer_increment(col->array, ids[i], col->type),
-                     gal_type_sizeof(col->type));
+              {
+                memcpy(gal_pointer_increment(col->array, i*n,     col->type),
+                       gal_pointer_increment(col->array, ids[i]*n,col->type),
+                       n * gal_type_sizeof(col->type));
+              }
           }
 
       /* For string arrays, free the pointers of the remaining rows. */
@@ -119,7 +154,8 @@ table_bring_to_top(gal_data_t *table, gal_data_t *rowids)
 
       /* Correct the size (this should be after freeing of the string
          pointers. */
-      col->size = col->dsize[0] = rowids->size;
+      col->dsize[0] = rowids->size;
+      col->size = col->dsize[0] * n;
     }
 
 }
@@ -337,8 +373,8 @@ table_selection_equal_or_notequal(struct tableparams *p, 
gal_data_t *col,
       else
         {
           /* Allocate the value dataset. */
-          value=gal_data_alloc(NULL, GAL_TYPE_FLOAT64, 1, &one, NULL, 0, -1, 1,
-                               NULL, NULL, NULL);
+          value=gal_data_alloc(NULL, GAL_TYPE_FLOAT64, 1, &one, NULL, 0,
+                               -1, 1, NULL, NULL, NULL);
           varr=value->array;
 
           /* Read the stored string as a float64. */
@@ -410,6 +446,15 @@ table_select_by_value(struct tableparams *p)
   /* Go over each selection criteria and remove the necessary elements. */
   for(tmp=p->selectcol;tmp!=NULL;tmp=tmp->next)
     {
+      /* Make sure the input isn't a vector column. */
+      if(tmp->col->ndim!=1)
+        error(EXIT_FAILURE, 0, "row selection by value (for example with "
+              "'--range', '--inpolygon', '--equal' or '--noblank') is "
+              "currently not available for vector columns. If you need "
+              "this feature, please get in touch with us at '%s' to add "
+              "it", PACKAGE_BUGREPORT);
+
+      /* Do the specific type of selection. */
       switch(tmp->type)
         {
         case SELECT_TYPE_RANGE:
@@ -433,7 +478,8 @@ table_select_by_value(struct tableparams *p)
           break;
 
         case SELECT_TYPE_NOBLANK:
-          addmask = gal_arithmetic(GAL_ARITHMETIC_OP_ISBLANK, 1, 0, tmp->col);
+          addmask = gal_arithmetic(GAL_ARITHMETIC_OP_ISBLANK, 1, 0,
+                                   tmp->col);
           break;
 
         default:
@@ -446,7 +492,8 @@ table_select_by_value(struct tableparams *p)
       /* Remove any blank elements (incase we are on a noblank column. */
       if(tmp->type!=SELECT_TYPE_NOBLANK && gal_blank_present(tmp->col, 1))
         {
-          blmask = gal_arithmetic(GAL_ARITHMETIC_OP_ISBLANK, 1, 0, tmp->col);
+          blmask = gal_arithmetic(GAL_ARITHMETIC_OP_ISBLANK, 1, 0,
+                                  tmp->col);
           addmask=gal_arithmetic(GAL_ARITHMETIC_OP_OR, 1, inplace,
                                  addmask, blmask);
           gal_data_free(blmask);
@@ -461,7 +508,7 @@ table_select_by_value(struct tableparams *p)
            float *f=ref->array;
            uint8_t *m=mask->array;
            uint8_t *u=addmask->array, *uf=u+addmask->size;
-           printf("\n\nInput column: %s\n", ref->name ? ref->name : "No Name");
+           printf("\n\nInput column: %s\n", ref->name?ref->name:"No Name");
            printf("Range: %g, %g\n", rarr[0], rarr[1]);
            printf("%-20s%-20s%-20s\n", "Value", "This mask",
            "Including previous");
@@ -515,14 +562,15 @@ static void
 table_sort(struct tableparams *p)
 {
   gal_data_t *perm;
-  size_t c=0, *s, *sf;
+  size_t c=0, *s, *sf, dsize0=p->table->dsize[0];
   int (*qsortfn)(const void *, const void *)=NULL;
 
   /* In case there are no columns to sort, skip this function. */
   if(p->table->size==0) return;
 
-  /* Allocate the permutation array and fill it. */
-  perm=gal_data_alloc(NULL, GAL_TYPE_SIZE_T, 1, p->table->dsize, NULL, 0,
+  /* Allocate the permutation array and fill it. Note that we need 'dsize0'
+     because the first column may be a vector column (which is 2D). */
+  perm=gal_data_alloc(NULL, GAL_TYPE_SIZE_T, 1, &dsize0, NULL, 0,
                       p->cp.minmapsize, p->cp.quietmmap, NULL, NULL, NULL);
   sf=(s=perm->array)+perm->size; do *s=c++; while(++s<sf);
 
@@ -617,8 +665,7 @@ table_random_rows(gal_data_t *table, gsl_rng *rng, size_t 
numrandom,
   size_t i, j, *ids, ind;
 
   /* Sanity check. */
-  if(numrandom>table->size)
-    return EXIT_FAILURE;
+  if(numrandom>table->size) return EXIT_FAILURE;
 
   /* Allocate space for the list of rows to use. */
   rowids=gal_data_alloc(NULL, GAL_TYPE_SIZE_T, 1, &numrandom, NULL, 0,
@@ -656,18 +703,25 @@ table_select_by_position(struct tableparams *p)
 {
   char **strarr;
   gal_data_t *col;
-  size_t i, start, end;
+  size_t i, start, end, nelem;
   double *darr = p->rowrange ? p->rowrange->array : NULL;
 
+  /* If the table is already empty, then don't bother continuing. */
+  if(p->table->array==NULL) return;
+
   /* If the head or tail values are given and are larger than the number of
      rows, just set them to the number of rows (print the all the final
      rows). This is how the 'head' and 'tail' programs of GNU Coreutils
-     operate. */
-  p->head = ( ((p->head!=GAL_BLANK_SIZE_T) && (p->head > p->table->size))
-              ? p->table->size
+     operate. Note that some columns may be vector (multi-value per
+     column), in this case, they will be 2D. So we should use 'dsize[0]'
+     for the generic way to find the number of rows. */
+  p->head = ( ( (p->head!=GAL_BLANK_SIZE_T)
+                && (p->head > p->table->dsize[0]) )
+              ? p->table->dsize[0]
               : p->head );
-  p->tail = ( ((p->tail!=GAL_BLANK_SIZE_T) && (p->tail > p->table->size))
-              ? p->table->size
+  p->tail = ( ( (p->tail!=GAL_BLANK_SIZE_T)
+                && (p->tail > p->table->dsize[0]) )
+              ? p->table->dsize[0]
               : p->tail );
 
   /* Random row selection (by position, not value). This step is
@@ -690,19 +744,24 @@ table_select_by_position(struct tableparams *p)
      rows until this point. */
   if(p->rowrange)
     {
-      if(darr[0]>=p->table->size)
+      if(darr[0]>=p->table->dsize[0])
         error(EXIT_FAILURE, 0, "the first value to '--rowrange' (%g) "
               "is larger than the number of rows (%zu)",
-              darr[0]+1, p->table->size);
-      else if( darr[1]>=p->table->size )
+              darr[0]+1, p->table->dsize[0]);
+      else if( darr[1]>=p->table->dsize[0] )
         error(EXIT_FAILURE, 0, "the second value to '--rowrange' (%g) "
               "is larger than the number of rows (%zu)",
-              darr[1]+1, p->table->size);
+              darr[1]+1, p->table->dsize[0]);
     }
 
   /* Go over all the columns and make the necessary corrections. */
-  for(col=p->table;col!=NULL;col=col->next)
+  for(col=p->table; col!=NULL; col=col->next)
     {
+      /* Set the increment (number of elements in this column). For vector
+         columns, this is the number of elements in each row, and for
+         normal columns, this is 1. */
+      nelem=col->size/col->dsize[0];
+
       /* FOR STRING: we'll need to free the individual strings that will
          not be used (outside the allocated array directly
          'gal_data_t'). We don't have to worry about the space for the
@@ -719,7 +778,7 @@ table_select_by_position(struct tableparams *p)
                  it starts from 0). */
               start = darr[0];
               end   = darr[1];
-              for(i=0;i<p->table->size;++i)
+              for(i=0;i<p->table->dsize[0];++i)
                 if(i<start || i>end) { free(strarr[i]); strarr[i]=NULL; }
             }
           else
@@ -728,8 +787,8 @@ table_select_by_position(struct tableparams *p)
                  space of each string. */
               start = p->head!=GAL_BLANK_SIZE_T ? p->head : 0;
               end   = ( p->head!=GAL_BLANK_SIZE_T
-                        ? p->table->size
-                        : p->table->size - p->tail );
+                        ? p->table->dsize[0]
+                        : p->table->dsize[0] - p->tail );
               for(i=start; i<end; ++i) { free(strarr[i]); strarr[i]=NULL; }
             }
         }
@@ -738,10 +797,11 @@ table_select_by_position(struct tableparams *p)
       if(p->rowrange)
         {
           /* Move the values up to the top and correct the size. */
-          col->size=darr[1]-darr[0]+1;
+          col->dsize[0]=darr[1]-darr[0]+1;
           memmove(col->array,
-                  gal_pointer_increment(col->array, darr[0], col->type),
-                  (darr[1]-darr[0]+1)*gal_type_sizeof(col->type));
+                  gal_pointer_increment(col->array, darr[0]*nelem,
+                                        col->type),
+                  (darr[1]-darr[0]+1)*nelem*gal_type_sizeof(col->type));
         }
       else
         {
@@ -750,16 +810,19 @@ table_select_by_position(struct tableparams *p)
              safe with overlap. */
           if(p->tail!=GAL_BLANK_SIZE_T)
             memmove(col->array,
-                    gal_pointer_increment(col->array, col->size - p->tail,
+                    gal_pointer_increment(col->array,
+                                          (col->dsize[0]-p->tail)*nelem,
                                           col->type),
-                    p->tail*gal_type_sizeof(col->type));
+                    p->tail*nelem*gal_type_sizeof(col->type));
 
-          /* In any case (head or tail), the new number of column elements
-             is the given value. */
-          col->size = col->dsize[0] = ( p->head!=GAL_BLANK_SIZE_T
-                                        ? p->head
-                                        : p->tail );
+          /* In any case (head or tail), the new number of rows, then
+             update the total number of elements (may be vector). */
+          col->dsize[0] = p->head!=GAL_BLANK_SIZE_T ? p->head : p->tail;
         }
+
+      /* The 'dsize[0]' component was set above, we should not update the
+         total size. */
+      col->size = col->dsize[0] * (col->ndim==1 ? 1 : col->dsize[1]);
     }
 }
 
@@ -844,6 +907,136 @@ table_catcolumn(struct tableparams *p)
 
 
 
+static void
+table_fromvector(struct tableparams *p)
+{
+  size_t i, *iarr;
+  gal_list_sizet_t *indexs=NULL;
+  gal_data_t *tmp, *vector=NULL, *ext;
+
+  /* Parse the values given to this option. */
+  for(tmp=p->fromvector;tmp!=NULL;tmp=tmp->next)
+    {
+      /* Extract the name and element counters. */
+      vector=gal_list_data_select_by_id(p->table, tmp->name, NULL);
+      if(vector==NULL) table_error_no_column("--fromvector", tmp->name);
+
+      /* Make sure the selected column is actually a vector. */
+      if(vector->ndim!=2)
+        error(EXIT_FAILURE, 0, "column '%s' (given to '--fromvector') "
+              "is not a vector", tmp->name);
+
+      /* Loop over the values and make sure they are within the range. */
+      iarr=tmp->array;
+      for(i=0;i<tmp->size;++i)
+        {
+          /* Check if it is reasonable. */
+          if(iarr[i]>vector->dsize[1])
+            error(EXIT_FAILURE, 0, "column '%s' (given to "
+                  "'--fromvector') only has a length of %zu, but you "
+                  "have asked for element %zu", tmp->name,
+                  vector->dsize[1], iarr[i]);
+
+          /* Make sure the user didn't give a value of 0. */
+          if(iarr[i]==0)
+            error(EXIT_FAILURE, 0, "integers given to '--fromvector' "
+                  "must be larger than 1, but you have given '0'");
+
+          /* Add it to the list of indexs. Note that the user has given a
+             "counter" (starting from 1), while we want an "index"
+             (counting from 0). */
+          gal_list_sizet_add(&indexs, iarr[i]-1);
+        }
+
+      /* Reverse the list of indexes to be the same as the requested. */
+      gal_list_sizet_reverse(&indexs);
+
+      /* Extract the columns and append them to the end of the table. */
+      ext=gal_table_col_vector_extract(vector, indexs);
+      gal_list_data_last(p->table)->next=ext;
+
+      /* Remove the vector column (if requested). */
+      if(p->keepvectfin==0)
+        {
+          gal_list_data_remove(&p->table, vector);
+          gal_data_free(vector);
+        }
+    }
+
+  /* Clean up. */
+  gal_list_sizet_free(indexs);
+}
+
+
+
+
+
+static void
+table_tovector(struct tableparams *p)
+{
+  size_t i;
+  char **strarr;
+  gal_list_str_t *tstr;
+  gal_data_t *ids, *col, *tcol, *list, *vector, **torm=NULL;
+
+  /* Loop over all the calls to this option. */
+  for(tstr=p->tovector;tstr!=NULL;tstr=tstr->next)
+    {
+      /* Extract the separate csv. */
+      ids=gal_options_parse_csv_strings_raw(tstr->v, NULL, 0);
+
+      /* Allocate an array of dataset pointers to keep the columns that
+         should be removed. */
+      if(p->keepvectfin==0)
+        {
+          errno=0;
+          torm=malloc(ids->size*sizeof *torm);
+          if(torm==NULL)
+            error(EXIT_FAILURE, errno, "%s: allocating %zu bytes",
+                  __func__, ids->size*sizeof *torm);
+        }
+
+      /* Parse the given values, and extract them from the table. */
+      list=NULL;
+      strarr=ids->array;
+      for(i=0;i<ids->size;++i)
+        {
+          /* Extract this column. */
+          tcol=gal_list_data_select_by_id(p->table, strarr[i], NULL);
+          if(tcol==NULL) table_error_no_column("--tovector", strarr[i]);
+
+          /* Keep a copy of the column and put it in the list of columns to
+             add. */
+          col=gal_data_copy(tcol); col->next=NULL;
+          gal_list_data_add(&list, col);
+
+          /* Keep the pointer to this column for removal (if necessary). */
+          if(p->keepvectfin==0) torm[i]=tcol;
+        }
+
+      /* Reverse the list to be in the same order as the input, and convert
+         it to a vector.*/
+      gal_list_data_reverse(&list);
+      vector=gal_table_cols_to_vector(list);
+      gal_list_data_free(list);
+
+      /* Add this vector column to the output. */
+      gal_list_data_last(p->table)->next=vector;
+
+      /* Free the input columns if the user wanted to. */
+      if(p->keepvectfin==0)
+        for(i=0;i<ids->size;++i)
+          gal_list_data_remove(&p->table, torm[i]);
+
+      /* Clean up. */
+      gal_data_free(ids);
+      if(torm) free(torm);
+    }
+}
+
+
+
+
 /* Find the HDU of the table to read. */
 static char *
 table_catrows_findhdu(char *filename, gal_list_str_t **hdull)
@@ -879,8 +1072,8 @@ table_catrows_prepare(struct tableparams *p)
   char *hdu=NULL;
   int tableformat;
   gal_data_t *ocol, *tmp;
-  size_t i, nrows=p->table->size;
   gal_list_str_t *filell, *hdull;
+  size_t i, dsize[2], nrows=p->table->size;
   size_t numcols, numrows, filledrows=p->table->size;
 
   /* Go over all the given tables and find the final number of rows. */
@@ -901,9 +1094,13 @@ table_catrows_prepare(struct tableparams *p)
      larger array, and free the temporary 'gal_data_t'. */
   for(tmp=p->table; tmp!=NULL; tmp=tmp->next)
     {
+      /* Set the final amount of allocated space for this column. */
+      dsize[0]=nrows;
+      if(tmp->ndim==2) dsize[1]=tmp->dsize[1];
+
       /* Allocate a temporary dataset (we just want its allocated space,
          not the actual 'gal_data_t' pointer)! */
-      ocol=gal_data_alloc(NULL, tmp->type, 1, &nrows, NULL,
+      ocol=gal_data_alloc(NULL, tmp->type, tmp->ndim, dsize, NULL,
                           0, p->cp.minmapsize, p->cp.quietmmap,
                           tmp->name, tmp->unit, tmp->comment);
 
@@ -945,6 +1142,7 @@ table_catrows_prepare(struct tableparams *p)
 static void
 table_catrows(struct tableparams *p)
 {
+  size_t increment;
   char *hdu=NULL, **strarr;
   gal_data_t *new, *ttmp, *tmp;
   gal_list_str_t *filell, *hdull;
@@ -1000,8 +1198,31 @@ table_catrows(struct tableparams *p)
                   gal_fits_name_save_as_string(filell->v, hdu), colcount,
                   gal_type_name(tmp->type, 1), gal_type_name(ttmp->type, 1));
 
+          /* Make sure the two columns have the same dimensions (vector or
+             single element). */
+          if(tmp->ndim!=ttmp->ndim)
+            error(EXIT_FAILURE, 0, "%s: column %zu is a %s column. "
+                  "However, in the final table (before adding rows) this "
+                  "column is a %s column",
+                  gal_fits_name_save_as_string(filell->v, hdu), colcount,
+                  tmp->ndim==1?"single-valued":"vector",
+                  ttmp->ndim==1?"single-valued":"vector");
+
+          /* If the column is vector, make sure it has the same number of
+             elements.*/
+          if(tmp->ndim==2 && tmp->dsize[1]!=ttmp->dsize[1])
+            error(EXIT_FAILURE, 0, "%s: vector column %zu has %zu elements "
+                  "However, in the final table (before adding rows) this "
+                  "vector column has %zu elements",
+                  gal_fits_name_save_as_string(filell->v, hdu), colcount,
+                  tmp->dsize[1], ttmp->dsize[1]);
+
+          /* Set the increment on the existing table (column may be
+             vector). */
+          increment = filledrows * ( tmp->ndim==1 ? 1 : tmp->dsize[1] );
+
           /* Add the new rows and incremenet the counter. */
-          memcpy(gal_pointer_increment(ttmp->array, filledrows, ttmp->type),
+          memcpy(gal_pointer_increment(ttmp->array, increment, ttmp->type),
                  tmp->array, tmp->size*gal_type_sizeof(tmp->type));
 
           /* If the column type is a string, we should set the input
@@ -1018,7 +1239,7 @@ table_catrows(struct tableparams *p)
         }
 
       /* Clean up the columns of the table and increment 'filledrows'. */
-      filledrows += new->size;
+      filledrows += new->dsize[0];
       gal_list_data_free(new);
     }
 }
@@ -1046,7 +1267,7 @@ table_colmetadata(struct tableparams *p)
           /* We have been given a string, so find the first column that has
              the same name. */
           for(col=p->table; col!=NULL; col=col->next)
-            if(!strcmp(col->name, meta->name)) break;
+            if(!strcasecmp(col->name, meta->name)) break;
         }
       /* The column specifier is a number. */
       else
@@ -1098,6 +1319,33 @@ table_colmetadata(struct tableparams *p)
 
 
 
+void
+table_noblankend_check_add(struct tableparams *p,
+                           gal_list_sizet_t **column_indexs, size_t colind)
+{
+  size_t i=0;
+  gal_data_t *tmp;
+  static int warningprinted=0;
+
+  /* Before adding, be sure that the column is not a vector column. */
+  for(tmp=p->table;tmp!=NULL;tmp=tmp->next)
+    if(i++==colind)
+      {
+        if( tmp->ndim==1 ) gal_list_sizet_add(column_indexs, colind);
+        else if(p->cp.quiet==0 && warningprinted==0)
+          {
+            warningprinted=1;
+            error(EXIT_SUCCESS, 0, "WARNING: vector columns will be "
+                  "ignored for the '--noblankend' option. To remove "
+                  "this warning, run with '--quiet' (or '-q')");
+          }
+      }
+}
+
+
+
+
+
 void
 table_noblankend(struct tableparams *p)
 {
@@ -1115,7 +1363,7 @@ table_noblankend(struct tableparams *p)
       && !strcmp(p->noblankend->v,"_all") )
     {
       for(i=0;i<gal_list_data_number(p->table);++i)
-        gal_list_sizet_add(&column_indexs, i);
+        table_noblankend_check_add(p, &column_indexs, i);
     }
 
   /* Only certain columns should be checked, so find/add their index. */
@@ -1130,10 +1378,10 @@ table_noblankend(struct tableparams *p)
         found=0;
         for(tcol=p->table; tcol!=NULL; tcol=tcol->next)
           {
-            if( tcol->name && !strcmp(tcol->name, tmp->v) )
+            if( tcol->name && !strcasecmp(tcol->name, tmp->v) )
               {
                 found=1;
-                gal_list_sizet_add(&column_indexs, j);
+                table_noblankend_check_add(p, &column_indexs, j);
               }
             ++j;
           }
@@ -1153,8 +1401,8 @@ table_noblankend(struct tableparams *p)
             /* Make sure its not zero (the user counts from 1). */
             if(*index==0)
               error(EXIT_FAILURE, 0, "the column number (given to the "
-                    "'--noblankend' option) should start from 1, but you have "
-                    "given 0.");
+                    "'--noblankend' option) should start from 1, but you "
+                    "have given 0");
 
             /* Make sure that the index falls within the number (note that
                it still counts from 1).  */
@@ -1170,7 +1418,7 @@ table_noblankend(struct tableparams *p)
 
             /* Everything is fine, add the index to the list of columns to
                check. */
-            gal_list_sizet_add(&column_indexs, *index-1);
+            table_noblankend_check_add(p, &column_indexs, *index-1);
 
             /* Clean up. */
             free(index);
@@ -1182,9 +1430,16 @@ table_noblankend(struct tableparams *p)
       }
 
   /* Remove all blank rows from the output table, note that we don't need
-     the flags of the removed columns here. So we can just free it up. */
-  flag=gal_blank_remove_rows(p->table, column_indexs);
-  gal_data_free(flag);
+     the flags of the removed columns here. So we can just free it up.
+
+     Vector columns are currently ignored in '--noblankend', so if the user
+     only asks for no blanks in a vector column, in effect, no rows should
+     be removed. */
+  if(column_indexs)
+    {
+      flag=gal_blank_remove_rows(p->table, column_indexs, 1);
+      gal_data_free(flag);
+    }
 }
 
 
@@ -1239,6 +1494,9 @@ table(struct tableparams *p)
   /* Concatenate the columns of tables (if required). */
   if(p->catcolumnfile) table_catcolumn(p);
 
+  /* Extract columns from vector. */
+  if(p->fromvector) table_fromvector(p);
+
   /* Concatenate the rows of multiple tables (if required). */
   if(p->catrowfile) table_catrows(p);
 
@@ -1259,6 +1517,9 @@ table(struct tableparams *p)
   if(p->outcols)
     arithmetic_operate(p);
 
+  /* Merge columns into a vector column. */
+  if(p->tovector) table_tovector(p);
+
   /* When column metadata should be updated. */
   if(p->colmetadata) table_colmetadata(p);
 
diff --git a/bin/table/ui.c b/bin/table/ui.c
index 25e29ad6..c8b13f80 100644
--- a/bin/table/ui.c
+++ b/bin/table/ui.c
@@ -321,9 +321,9 @@ ui_read_check_only_options(struct tableparams *p)
         error(EXIT_FAILURE, 0, "the first value to '--rowrange' (%g) is "
               "larger than the second (%g). This option's values defines "
               "a row-counter interval, assuming the first value is the top "
-              "of the desired interval (smaller row counter) and the second "
-              "value is the bottom of the desired interval (larger row "
-              "counter)", darr[0], darr[1]);
+              "of the desired interval (smaller row counter) and the "
+              "second value is the bottom of the desired interval (larger "
+              "row counter)", darr[0], darr[1]);
     }
 
   /* If '--colmetadata' is given, make sure none of the given options have
@@ -1175,6 +1175,13 @@ ui_check_select_sort_after(struct tableparams *p, size_t 
nselect,
     }
 
 
+  /* The column to sort by should not be a vector column. */
+  if(p->sortcol && p->sortcol->ndim!=1)
+    error(EXIT_FAILURE, 0, "the column given to '--sort' cannot be a "
+          "vector column. If you need this feature, please get in "
+          "touch with us at '%s' to add it", PACKAGE_BUGREPORT);
+
+
   /* Since we can have several selection columns, we'll treat them
      differently. */
   for(i=0;i<nselect;++i)
diff --git a/bin/table/ui.h b/bin/table/ui.h
index b2555d9b..7bc0bbe7 100644
--- a/bin/table/ui.h
+++ b/bin/table/ui.h
@@ -33,7 +33,8 @@ along with Gnuastro. If not, see 
<http://www.gnu.org/licenses/>.
 /* Option groups particular to this program. */
 enum program_args_groups
 {
-  UI_GROUP_OUTROWS = GAL_OPTIONS_GROUP_AFTER_COMMON,
+  UI_GROUP_OUTCOLS = GAL_OPTIONS_GROUP_AFTER_COMMON,
+  UI_GROUP_OUTROWS,
 };
 
 
@@ -41,7 +42,7 @@ enum program_args_groups
 
 /* Available letters for short options:
 
-   a g j k l t v x y z
+   a g j l t v x y z
    G J Q
 */
 enum option_keys_enum
@@ -72,15 +73,18 @@ enum option_keys_enum
   UI_KEY_TXTF64FORMAT    = 'A',
   UI_KEY_TXTF32PRECISION = 'p',
   UI_KEY_TXTF64PRECISION = 'B',
+  UI_KEY_KEEPVECTFIN     = 'k',
 
   /* Only with long version (start with a value 1000, the rest will be set
      automatically). */
   UI_KEY_POLYGON         = 1000,
   UI_KEY_ENVSEED,
   UI_KEY_ROWRANGE,
+  UI_KEY_TOVECTOR,
   UI_KEY_ROWRANDOM,
   UI_KEY_INPOLYGON,
   UI_KEY_OUTPOLYGON,
+  UI_KEY_FROMVECTOR,
   UI_KEY_CATCOLUMNRAWNAME,
 };
 
diff --git a/bin/warp/ui.c b/bin/warp/ui.c
index 25093daa..73a0f4c0 100644
--- a/bin/warp/ui.c
+++ b/bin/warp/ui.c
@@ -240,7 +240,8 @@ ui_add_to_modular_warps_ll(struct argp_option *option, char 
*arg,
     error(EXIT_FAILURE, 0, "empty string given to '--%s'", option->name);
 
   /* Parse the (possible) arguments. */
-  new=gal_options_parse_list_of_numbers(arg, filename, lineno);
+  new=gal_options_parse_list_of_numbers(arg, filename, lineno,
+                                        GAL_TYPE_FLOAT64);
 
 
   /* If this was a matrix, then put it in the matrix element of the main
@@ -251,7 +252,8 @@ ui_add_to_modular_warps_ll(struct argp_option *option, char 
*arg,
       /* Some sanity checks. */
       if(p->matrix)
         error_at_line(EXIT_FAILURE, 0, filename, lineno, "only one matrix "
-                      "may be given, you can use multiple modular warpings");
+                      "may be given, you can use multiple modular "
+                      "warpings");
       if(new->size!=4 && new->size!=9)
         error_at_line(EXIT_FAILURE, 0, filename, lineno, "only a 4 or 9 "
                       "element 'matrix' is currently acceptable. '%s' has "
@@ -275,9 +277,9 @@ ui_add_to_modular_warps_ll(struct argp_option *option, char 
*arg,
       if(option->key==UI_KEY_ROTATE)
         {
           if(new->size!=1)
-            error_at_line(EXIT_FAILURE, 0, filename, lineno, "the 'rotate' "
-                      "option only takes one value (the angle of rotation). "
-                      "You have given: '%s'", arg);
+            error_at_line(EXIT_FAILURE, 0, filename, lineno, "the "
+                          "'rotate' option only takes one value (the "
+                          "angle of rotation). You have given: '%s'", arg);
         }
       else if (option->key==UI_KEY_FLIP)
         {
diff --git a/doc/gnuastro.texi b/doc/gnuastro.texi
index 8ee376a8..fa7d64d6 100644
--- a/doc/gnuastro.texi
+++ b/doc/gnuastro.texi
@@ -469,6 +469,7 @@ Invoking ConvertType
 Table
 
 * Printing floating point numbers::  Optimal storage of floating point types.
+* Vector columns::              How to keep more than one value in each column.
 * Column arithmetic::           How to do operations on table columns.
 * Operation precedence in Table::  Order of running options in Table.
 * Invoking asttable::           Options and arguments to Table.
@@ -11399,7 +11400,7 @@ The delimiters (or characters separating the columns) 
are white space characters
 The only further requirement is that all rows/lines must have the same number 
of columns.
 
 The columns do not have to be exactly under each other and the rows can be 
arbitrarily long with different lengths.
-For example, the following contents in a file would be interpreted as a table 
with 4 columns and 2 rows, with each element interpreted as a @code{double} 
type (see @ref{Numeric data types}).
+For example, the following contents in a file would be interpreted as a table 
with 4 columns and 2 rows, with each element interpreted as a 64-bit floating 
point type (see @ref{Numeric data types}).
 
 @example
 1     2.234948   128   39.8923e8
@@ -11409,14 +11410,14 @@ For example, the following contents in a file would 
be interpreted as a table wi
 However, the example above has no other information about the columns (it is 
just raw data, with no meta-data).
 To use this table, you have to remember what the numbers in each column 
represent.
 Also, when you want to select columns, you have to count their position within 
the table.
-This can become frustrating and prone to bad errors (getting the columns 
wrong) especially as the number of columns increase.
+This can become frustrating and prone to bad errors (getting the columns wrong 
in your scientific project!) especially as the number of columns increase.
 It is also bad for sending to a colleague, because they will find it hard to 
remember/use the columns properly.
 
 To solve these problems in Gnuastro's programs/libraries you are not limited 
to using the column's number, see @ref{Selecting table columns}.
 If the columns have names, units, or comments you can also select your columns 
based on searches/matches in these fields, for example, see @ref{Table}.
 Also, in this manner, you cannot guide the program reading the table on how to 
read the numbers.
 As an example, the first and third columns above can be read as integer types: 
the first column might be an ID and the third can be the number of pixels an 
object occupies in an image.
-So there is no need to read these to columns as a @code{double} type (which 
takes more memory, and is slower).
+So there is no need to read these to columns as a 64-bit floating point type 
(which takes more memory, and is slower).
 
 In the bare-minimum example above, you also cannot use strings of characters, 
for example, the names of filters, or some other identifier that includes 
non-numerical characters.
 In the absence of any information, only numbers can be read robustly.
@@ -11427,13 +11428,14 @@ To correct for these limitations, Gnuastro defines 
the following convention for
 The format is primarily designed for ease of reading/writing by eye/fingers, 
but is also structured enough to be read by a program.
 
 When the first non-white character in a line is @key{#}, or there are no 
non-white characters in it, then the line will not be considered as a row of 
data in the table (this is a pretty standard convention in many programs, and 
higher level languages).
-In the former case, the line is interpreted as a @emph{comment}.
+In the first case (when the first character of the line is @key{#}), the line 
is interpreted as a @emph{comment}.
+
 If the comment line starts with `@code{# Column N:}', then it is assumed to 
contain information about column @code{N} (a number, counting from 1).
 Comment lines that do not start with this pattern are ignored and you can use 
them to include any further information you want to store with the table in the 
text file.
-A column information comment is assumed to have the following format:
+The most generic column information comment line has the following format:
 
 @example
-# Column N: NAME [UNIT, TYPE, BLANK] COMMENT
+# Column N: NAME [UNIT, TYPE(NUM), BLANK] COMMENT
 @end example
 
 @cindex NaN
@@ -11441,7 +11443,10 @@ A column information comment is assumed to have the 
following format:
 Any sequence of characters between `@key{:}' and `@key{[}' will be interpreted 
as the column name (so it can contain anything except the `@key{[}' character).
 Anything between the `@key{]}' and the end of the line is defined as a comment.
 Within the brackets, anything before the first `@key{,}' is the units 
(physical units, for example, km/s, or erg/s), anything before the second 
`@key{,}' is the short type identifier (see below, and @ref{Numeric data 
types}).
+
 If the type identifier is not recognized, the default 64-bit floating point 
type will be used.
+The type identifier can optionally be followed by an integer within 
parenthesis.
+If the parenthesis is present and the integer is larger than 1, the column is 
assumed to be a ``vector column'' (which can have multiple values, for more see 
@ref{Vector columns}).
 
 Finally (still within the brackets), any non-white characters after the second 
`@key{,}' are interpreted as the blank value for that column (see @ref{Blank 
pixels}).
 The blank value can either be in the same type as the column (for example, 
@code{-99} for a signed integer column), or any string (for example, @code{NaN} 
in that same column).
@@ -14159,12 +14164,13 @@ Finally, in @ref{Invoking asttable}, we give some 
examples and describe each opt
 
 @menu
 * Printing floating point numbers::  Optimal storage of floating point types.
+* Vector columns::              How to keep more than one value in each column.
 * Column arithmetic::           How to do operations on table columns.
 * Operation precedence in Table::  Order of running options in Table.
 * Invoking asttable::           Options and arguments to Table.
 @end menu
 
-@node Printing floating point numbers, Column arithmetic, Table, Table
+@node Printing floating point numbers, Vector columns, Table, Table
 @subsection Printing floating point numbers
 
 @cindex Floating point numbers
@@ -14229,7 +14235,183 @@ They are fully described in @ref{Invoking asttable}.
 To view the contents of the table on the command-line or to feed it to a 
program that doesn't recognize FITS tables, you can use the four options above 
for a custom base-10 conversion that will not cause any loss of data.
 @end cartouche
 
-@node Column arithmetic, Operation precedence in Table, Printing floating 
point numbers, Table
+@node Vector columns, Column arithmetic, Printing floating point numbers, Table
+@subsection Vector columns
+
+@cindex Vector columns
+@cindex Columns (Vector)
+@cindex Multi-value columns (vector)
+In its most common format, each column of a table only has a single value in 
each row.
+For example, we usually have one column for the magnitude, another column for 
the RA and yet another column for the Declination of a set of galaxies/stars 
(where each galaxy is represented by one row in the table).
+This common single-valued column format is sufficient in many scenarios.
+However, in some situations (like those below) it would help to have mutilple 
values for each row in each column, not just one.
+
+@itemize
+@item
+@cindex MUSE
+@cindex Spectrum
+@cindex Radial profile
+Conceptually: the various numbers are ``connected'' to each other.
+In other words, their order and position in relation to each other matters.
+Common examples in astronomy are the radial profiles of each galaxy in your 
catalog, or their spectrum.
+For example, each 
MUSE@footnote{@url{https://www.eso.org/sci/facilities/develop/instruments/muse.html}}
 spectra has 3681 points (with a sampling of of 1.25 Angstroms).
+
+Dealing with this many separate measurements as separate columns in your table 
is very annoying and prone to error: you don't want to forget moving some of 
them in an output table for further analysis, mistakenly change their order, or 
do some operation only on a sub-set of them.
+
+@item
+Technically: in the FITS standard, you can only store a maximum of 999 columns 
in a FITS table.
+Therfore, if you have more than 999 data points for each galaxy (like the MUSE 
spectra example above), it is impossible to store each point in one table as 
separate columns.
+@end itemize
+
+To address these problems, the FITS standard has defined the concept of 
``vector'' columns in its Binary table format (ASCII FITS tables don't support 
vector columns, but Gnuastro's plain-text format does, as described here).
+Within each row of a single vector column, we can store any number of 
datapoints (like the MUSE spectra above or the full radial profile of each 
galaxy).
+All the values in a vector column have to have the same @ref{Numeric data 
types}, and the number of elements within each vector column is the same for 
all rows.
+
+By grouping conceptually similar data points (like a spectra) in one vector 
column, we can significantly reduce the number of columns and make it much more 
managable, without loosing any information!
+To demonstrate the vector column features of Gnuastro's Table program, let's 
start with a randomly generated small (5 rows and 3 columns) catalog.
+This will allows us to show the outputs of each step here, but you can apply 
the same concept to vectors with any number of colums.
+
+With the command below, we use @code{seq} to generate a single-column table 
that is piped to Gnuastro's Table program.
+Table then uses column arithmetic to generate three columns with random values 
from that base row (for more, see @ref{Column arithmetic}).
+Each column has with a larger noise sigma.
+Finally, we will add metadata to each column, giving each a different name 
(using names is always the best way to work with columns):
+
+@example
+$ seq 1 5 \
+      | asttable -c'arith $1 2  mknoise-sigma f32' \
+                 -c'arith $1 5  mknoise-sigma f32' \
+                 -c'arith $1 10 mknoise-sigma f32' \
+                 --colmetadata=1,abc,none,"First column." \
+                 --colmetadata=2,def,none,"Second column." \
+                 --colmetadata=3,ghi,none,"Third column." \
+                 --output=table.fits
+@end example
+
+With the command below, let's have a look at the table.
+When you run it, you will have a different random number generator seed, so 
the numbers will be slightly different.
+For making reproducible random numbers, see @ref{Generating random numbers}.
+The @option{-Y} option is used for more easily readable numbers (without it, 
floating point numbers are written in scientific notation, for more see 
@ref{Printing floating point numbers}) and with the @option{-O} we are asking 
Table to also print the metadata.
+For more on Table's options, see @ref{Invoking asttable} and for seeing how 
the short options can be merged, see @ref{Options}.
+
+@example
+$ asttable table.fits -YO
+# Column 1: abc [none,f32,] First column.
+# Column 2: def [none,f32,] Second column.
+# Column 3: ghi [none,f32,] Third column.
+-2.694         -9.130         +6.865
++3.166         +4.239         +18.386
++4.709         +0.561         +1.817
+-0.338         +4.927         -5.010
++7.291         +5.541         +3.311
+@end example
+
+We see that indeed, it has three columns, with our given names.
+Now, let's assume that you want to make a two-element vector column from the 
values in the @code{def} and @code{ghi} columns.
+To do that, you can use the @option{--tovector} option like below.
+As the name suggests, @option{--tovector} will merge the rows of the two 
columns into one vector column with multiple values in each row.
+
+@example
+$ asttable table.fits -YO --tovector=def,ghi
+# Column 1: abc        [none,f32   ,] First column.
+# Column 2: def-VECTOR [none,f32(2),] Vector by merging multiple cols.
+-2.694         -9.130         +6.865
++3.166         +4.239         +18.386
++4.709         +0.561         +1.817
+-0.338         +4.927         -5.010
++7.291         +5.541         +3.311
+@end example
+
+@cindex Tokens
+If you ignore the metadata, this doesn't seem to have changed anything!
+You see that each line of numbers still has three ``tokens'' (to distinguish 
them from ``columns'').
+But once you look at the metadata, you only see metadata for two columns, not 
three.
+If you look closely, the numeric data type of the newly added fourth column is 
`@code{f32(2)}' (look above, previously it was @code{f32}).
+The @code{(2)} shows that the second column contains two numbers/tokens not 
one.
+If your vector column consisted of 3681 numbers, this would be 
@code{f32(3681)}.
+Looking again at the metadata, we see that @option{--tovector} has also 
created a new name and comments for the new column.
+This is done all the time to avoid confusion with the old columns.
+
+Let's confirm that the newly added column is indeed a single column but with 
two values.
+To do this, with the command below, we'll write the output into a FITS table.
+In the same command, let's also give a more suitable name for the new 
merged/vector column).
+We can get a first confirmation by looking at the table's metadata in the 
second command below:
+
+@example
+$ asttable table.fits -YO --tovector=def,ghi --output=vec.fits \
+           --colmetadata=2,defghi,nounits,"New vector column"
+
+$ asttable vec.fits -i
+--------
+vec.fits (hdu: 1)
+-------    -----    ----        -------
+No.Name    Units    Type        Comment
+-------    -----    ----        -------
+1  abc     none     float32     First column.
+2  defghi  nounits  float32(2)  New vector column
+--------
+Number of rows: 5
+@end example
+
+@noindent
+A more robust confirmation would be to print the values in the newly added 
@code{defghi} column.
+As expected, asking for a single column with @option{--column} (or 
@option{-c}) will given us two numbers per row/line.
+
+@example
+$ asttable vec.fits -c defghi -YO
+# Column 1: defghi [nounits,f32(2),] New vector column
+-9.130         +6.865
++4.239         +18.386
++0.561         +1.817
++4.927         -5.010
++5.541         +3.311
+@end example
+
+If you want to keep the original single-valued columns that went into the 
vector column, you can use the @code{--keepvectfin} option (read it as ``KEEP 
VECtor To/From Inputs''):
+
+@example
+$ asttable table.fits -YO --tovector=def,ghi --keepvectfin \
+           --colmetadata=4,defghi,nounits,"New vector column"
+# Column 1: abc    [none   ,f32   ,] First column.
+# Column 2: def    [none   ,f32   ,] Second column.
+# Column 3: ghi    [none   ,f32   ,] Third column.
+# Column 4: defghi [nounits,f32(2),] New vector column
+-2.694         -9.130         +6.865         -9.130         +6.865
++3.166         +4.239         +18.386        +4.239         +18.386
++4.709         +0.561         +1.817         +0.561         +1.817
+-0.338         +4.927         -5.010         +4.927         -5.010
++7.291         +5.541         +3.311         +5.541         +3.311
+@end example
+
+Now that you know how to create vector columns, let's assume you have the 
inverse scenario: you want to extract one of the values of a vector column into 
a separate single-valued column.
+To do this, you can use the @option{--fromvector} option.
+The @option{--fromvector} option takes the name (or counter) of a vector 
column, followed by any number of integer counters.
+It will extract those elements into separate single-valued columns.
+For example, let's assume you want to extract the second element of the 
@code{defghi} column in the file you made before:
+
+@example
+$ asttable vec.fits --fromvector=defghi,2 -YO
+# Column 1: abc      [none   ,f32,] First column.
+# Column 2: defghi-2 [nounits,f32,] New vector column
+-2.694         +6.865
++3.166         +18.386
++4.709         +1.817
+-0.338         -5.010
++7.291         +3.311
+@end example
+
+@noindent
+Just like the case with @option{--tovector} above, if you want to keep the 
input vector column, use @option{--keepvectfin}.
+This feature is useful in scenarios where you want to select some rows based 
on a single element (or muliple) of the vector column.
+
+@cartouche
+@noindent
+@strong{Vector columns and FITS ASCII tables:} As mentioned above, the FITS 
standard only recognizes vector columns in its Binary table format (the default 
FITS table format in Gnuastro).
+You can use the @option{--tableformat=fits-ascii} option to write your tables 
in the FITS ASCII format (see @ref{Input output options}).
+In this case, if a vector column is present, it will be written as separate 
single-element columns to avoid loosing information (as if you run called 
@option{--fromvector} on all the elements of the vector column).
+A warning is printed if this occurs.
+@end cartouche
+
+@node Column arithmetic, Operation precedence in Table, Vector columns, Table
 @subsection Column arithmetic
 
 In many scenarios, you want to apply some kind of operation on the columns and 
save them in another table or feed them into another program.
@@ -14505,6 +14687,10 @@ The rest of the operations below are done on the rows, 
therefore you can merge t
 If any of the row-based operations below are requested in the same 
@code{asttable} command, they will also be applied to the rows of the added 
columns.
 However, the conditions to keep/reject rows can only be applied to the rows of 
the columns in main input table (not the columns that are added with these 
options).
 
+@item Extracting single-valued columns from vectors (@option{--fromvector})
+Once all the input columns are read into memory, if any of them are vectors, 
you can extract a single-valued column from the vector columns at this stage.
+For more on vector columns, see @ref{Vector columns}.
+
 @item Rows from other file(s) (@option{--catrowfile} and @option{--catrowhdu})
 With this feature, you can import rows from other tables (in other files, or 
other HDUs of the same FITS file).
 The same column selection of @option{--column} is applied to the tables given 
here.
@@ -14569,6 +14755,11 @@ These options limit/select rows based on their 
position within the table (not th
 Once the final rows are selected in the requested order, column arithmetic is 
done (if requested).
 For more on column arithmetic, see @ref{Column arithmetic}.
 
+@item Creating vector columns (@option{--tovector})
+After column arithmetic, there is no other way to add new columns so the 
@option{--tovector} operator is applied at this stage.
+You can use it to merge multiple columns that are available in this stage to a 
single vector column.
+For more, see @ref{Vector columns}.
+
 @item Column metadata (@option{--colmetadata})
 Changing column metadata is necessary after column arithmetic or adding new 
columns from other tables (that were done above).
 
@@ -14823,6 +15014,24 @@ See @option{--catcolumnfile} for more.
 @item --catcolumnrawname
 Do Not modify the names of the concatenated (appended) columns, see 
description in @option{--catcolumnfile}.
 
+@item --fromvector=STR,INT[,INT[,INT]]
+Extract the given tokens/elements from the given vector column into separate 
single-valued columns.
+The input vector column can be identified by its name or counter, see 
@ref{Selecting table columns}.
+After the columns are extracted, the input vector is deleted by default.
+To preserve the input vector column, you can use @option{--keepvectfin} 
described below.
+For a complete usage scenario see @ref{Vector columns}.
+
+@item --tovector=STR/INT,STR/INT[,STR/INT]
+Move the given columns into a newly created vector column.
+The given columns can be identified by their name or counter, see 
@ref{Selecting table columns}.
+After the columns are copied, they are deleted by default.
+To preserve the inputs, you can use @option{--keepvectfin} described below.
+For a complete usage scenario see @ref{Vector columns}.
+
+@item -k
+@itemx --keepvectfin
+Do not delete the input column(s) when using @option{--fromvector} or 
@option{--tovector}.
+
 @item -R FITS/TXT
 @itemx --catrowfile=FITS/TXT
 Add the rows of the given file to the output table.
@@ -31846,9 +32055,11 @@ This check is highly recommended because it will avoid 
strange bugs in later ste
 Similar to @code{gal_blank_remove}, but also shrinks/re-allocates the 
dataset's allocated memory.
 @end deftypefun
 
-@deftypefun {gal_data_t *} gal_blank_remove_rows (gal_data_t @code{*columns}, 
gal_list_sizet_t @code{*column_indexs})
+@deftypefun {gal_data_t *} gal_blank_remove_rows (gal_data_t @code{*columns}, 
gal_list_sizet_t @code{*column_indexs}, int @code{onlydim0})
 Remove (in place) any row that has at least one blank value in any of the 
input columns and return a ``flag'' dataset (that should be freed later).
 The input @code{columns} is a list of @code{gal_data_t}s (see @ref{List of 
gal_data_t}).
+When @code{onlydim0!=0} the vector columns (with 2 dimensions) will not be 
checked for the presence of blank values.
+
 After this function, all the elements in @code{columns} will still have the 
same size as each other, but if any of the searched columns has blank elements, 
all their sizes will decrease together.
 
 The returned flag dataset has the same size as the original input dataset, 
with a type of @code{uint8_t}.
@@ -32919,14 +33130,9 @@ Free every node in @code{list}.
 @node List of double, List of void, List of float, Linked lists
 @subsubsection List of @code{double}
 
-Double precision floating point numbers can accurately store real number
-until 15.9 decimals and consume 8 bytes (64-bits) of memory, see
-@ref{Numeric data types}. This level of precision makes them very good for
-serious processing in the middle of a program's execution: in many cases,
-the propagation of errors will still be insignificant compared to actual
-observational errors in a data set. But since they consume 8 bytes and more
-CPU processing power, they are often not the best choice for storing and
-transferring of data.
+Double precision floating point numbers can accurately store real number until 
15.9 decimals and consume 8 bytes (64-bits) of memory, see @ref{Numeric data 
types}.
+This level of precision makes them very good for serious processing in the 
middle of a program's execution: in many cases, the propagation of errors will 
still be insignificant compared to actual observational errors in a data set.
+But since they consume 8 bytes and more CPU processing power, they are often 
not the best choice for storing and transferring of data.
 
 @deftp {Type (C @code{struct})} gal_list_f64_t
 A single node in a list containing a 64-bit double precision @code{double}
@@ -32942,9 +33148,8 @@ typedef struct gal_list_f64_t
 
 
 @deftypefun void gal_list_f64_add (gal_list_f64_t @code{**list}, double 
@code{value})
-Add a new node (containing @code{value}) to the top of the @code{list} of
-@code{double}s and update @code{list}.  Here is one short example of
-initializing and adding elements to a string list:
+Add a new node (containing @code{value}) to the top of the @code{list} of 
@code{double}s and update @code{list}.
+Here is one short example of initializing and adding elements to a string list:
 
 @example
 gal_list_f64_t *dlist=NULL;
@@ -32955,10 +33160,9 @@ gal_list_f64_add(&dlist, 1.239378923931e-20);
 @end deftypefun
 
 @deftypefun {double} gal_list_f64_pop (gal_list_f64_t @code{**list})
-Pop the top element of @code{list} and return the value. This function will
-also change @code{list} to point to the next node in the list. If
-@code{*list==NULL}, then this function will return @code{GAL_BLANK_FLOAT64}
-(NaN, see @ref{Library blank values}).
+Pop the top element of @code{list} and return the value.
+This function will also change @code{list} to point to the next node in the 
list.
+If @code{*list==NULL}, then this function will return @code{GAL_BLANK_FLOAT64} 
(NaN, see @ref{Library blank values}).
 @end deftypefun
 
 @deftypefun size_t gal_list_f64_number (gal_list_f64_t @code{*list})
@@ -32970,13 +33174,12 @@ Return a pointer to the last node in @code{list}.
 @end deftypefun
 
 @deftypefun void gal_list_f64_print (gal_list_f64_t @code{*list})
-Print the values within each node of @code{*list} on the standard output in
-the same order that they are stored. Each floating point number is printed
-on one line. This function is mainly good for checking/debugging your
-program. For program outputs, it is best to make your own implementation with
-a better, more user-friendly format. For example, in the following code
-snippet. You can also modify it to print all values in one line, etc.,
-depending on the context of your program.
+Print the values within each node of @code{*list} on the standard output in 
the same order that they are stored.
+Each floating point number is printed on one line.
+This function is mainly good for checking/debugging your program.
+For program outputs, it is best to make your own implementation with a better, 
more user-friendly format.
+For example, in the following code snippet.
+You can also modify it to print all values in one line, etc., depending on the 
context of your program.
 
 @example
 size_t i;
@@ -32987,18 +33190,19 @@ for(tmp=list; tmp!=NULL; tmp=tmp->next)
 @end deftypefun
 
 @deftypefun void gal_list_f64_reverse (gal_list_f64_t @code{**list})
-Reverse the order of the list such that the top node in the list before
-calling this function becomes the bottom node after it.
+Reverse the order of the list such that the top node in the list before 
calling this function becomes the bottom node after it.
 @end deftypefun
 
 @deftypefun {double *} gal_list_f64_to_array (gal_list_f64_t @code{*list}, int 
@code{reverse}, size_t @code{*num})
-Dynamically allocate an array and fill it with the values in
-@code{list}. The function will return a pointer to the allocated array and
-put the number of elements in the array into the @code{num} pointer. If
-@code{reverse} has a non-zero value, the array will be filled in the
-inverse of the order of elements in @code{list}. This function can be
-useful after you have finished reading an initially unknown number of
-values and want to put them in an array for easy random access.
+Dynamically allocate an array and fill it with the values in @code{list}.
+The function will return a pointer to the allocated array and put the number 
of elements in the array into the @code{num} pointer.
+If @code{reverse} has a non-zero value, the array will be filled in the 
inverse of the order of elements in @code{list}.
+This function can be useful after you have finished reading an initially 
unknown number of values and want to put them in an array for easy random 
access.
+@end deftypefun
+
+@deftypefun {gal_data_t *} gal_list_f64_to_data (gal_list_f64_t @code{*list}, 
uint8_t @code{type}, size_t @code{minmapsize}, int @code{quietmmap})
+Write the values in the given @code{list} into a @code{gal_data_t} dataset of 
the requested @code{type}.
+The order of the values in the dataset will be the same as the order from the 
top of the list.
 @end deftypefun
 
 @deftypefun void gal_list_f64_free (gal_list_f64_t @code{*list})
@@ -33011,21 +33215,16 @@ Free every node in @code{list}.
 @node List of void, Ordered list of size_t, List of double, Linked lists
 @subsubsection List of @code{void *}
 
-In C, @code{void *} is the most generic pointer. Usually pointers are
-associated with the type of content they point to. For example, @code{int *}
-means a pointer to an integer. This ancillary information about the
-contents of the memory location is very useful for the compiler, catching
-bad errors and also documentation (it helps the reader see what the address
-in memory actually contains). However, @code{void *} is just a raw address
-(pointer), it contains no information on the contents it points to.
+In C, @code{void *} is the most generic pointer.
+Usually pointers are associated with the type of content they point to.
+For example, @code{int *} means a pointer to an integer.
+This ancillary information about the contents of the memory location is very 
useful for the compiler, catching bad errors and also documentation (it helps 
the reader see what the address in memory actually contains).
+However, @code{void *} is just a raw address (pointer), it contains no 
information on the contents it points to.
 
-These properties make the @code{void *} very useful when you want to treat
-the contents of an address in different ways. You can use the @code{void *}
-list defined in this section and its function on any kind of data: for
-example you can use it to keep a list of custom data structures that you
-have built for your own separate program. Each node in the list can keep
-anything and this gives you great versatility. But in using @code{void *},
-please beware that ``with great power comes great responsibility''.
+These properties make the @code{void *} very useful when you want to treat the 
contents of an address in different ways.
+You can use the @code{void *} list defined in this section and its function on 
any kind of data: for example you can use it to keep a list of custom data 
structures that you have built for your own separate program.
+Each node in the list can keep anything and this gives you great versatility.
+But in using @code{void *}, please beware that ``with great power comes great 
responsibility''.
 
 
 @deftp {Type (C @code{struct})} gal_list_void_t
@@ -33041,9 +33240,8 @@ typedef struct gal_list_void_t
 
 
 @deftypefun void gal_list_void_add (gal_list_void_t @code{**list}, void 
@code{*value})
-Add a new node (containing @code{value}) to the top of the @code{list} of
-@code{void *}s and update @code{list}.  Here is one short example of
-initializing and adding elements to a string list:
+Add a new node (containing @code{value}) to the top of the @code{list} of 
@code{void *}s and update @code{list}.
+Here is one short example of initializing and adding elements to a string list:
 
 @example
 gal_list_void_t *vlist=NULL;
@@ -33054,9 +33252,9 @@ gal_list_f64_add(&vlist, another_pointer);
 @end deftypefun
 
 @deftypefun {void *} gal_list_void_pop (gal_list_void_t @code{**list})
-Pop the top element of @code{list} and return the value. This function will
-also change @code{list} to point to the next node in the list. If
-@code{*list==NULL}, then this function will return @code{NULL}.
+Pop the top element of @code{list} and return the value.
+This function will also change @code{list} to point to the next node in the 
list.
+If @code{*list==NULL}, then this function will return @code{NULL}.
 @end deftypefun
 
 @deftypefun size_t gal_list_void_number (gal_list_void_t @code{*list})
@@ -33068,8 +33266,7 @@ Return a pointer to the last node in @code{list}.
 @end deftypefun
 
 @deftypefun void gal_list_void_reverse (gal_list_void_t @code{**list})
-Reverse the order of the list such that the top node in the list before
-calling this function becomes the bottom node after it.
+Reverse the order of the list such that the top node in the list before 
calling this function becomes the bottom node after it.
 @end deftypefun
 
 @deftypefun void gal_list_void_free (gal_list_void_t @code{*list})
@@ -33080,22 +33277,16 @@ Free every node in @code{list}.
 @node Ordered list of size_t, Doubly linked ordered list of size_t, List of 
void, Linked lists
 @subsubsection Ordered list of @code{size_t}
 
-Positions/sizes in a dataset are conventionally in the @code{size_t} type
-(see @ref{List of size_t}) and it sometimes occurs that you want to parse
-and read the values in a specific order. For example, you want to start from
-one pixel and add pixels to the list based on their distance to that
-pixel. So that ever time you pop an element from the list, you know it is
-the nearest that has not yet been studied. The @code{gal_list_osizet_t}
-type and its functions in this section are designed to facilitate such
-operations.
+Positions/sizes in a dataset are conventionally in the @code{size_t} type (see 
@ref{List of size_t}) and it sometimes occurs that you want to parse and read 
the values in a specific order.
+For example, you want to start from one pixel and add pixels to the list based 
on their distance to that pixel.
+So that ever time you pop an element from the list, you know it is the nearest 
that has not yet been studied.
+The @code{gal_list_osizet_t} type and its functions in this section are 
designed to facilitate such operations.
 
 @deftp {Type (C @code{struct})} gal_list_osizet_t
 @cindex @code{size_t}
-Each node in this singly-linked list contains a @code{size_t} value and a
-floating point value. The floating point value is used as a reference to
-add new nodes in a sorted manner. At any moment, the first popped node in
-this list will have the smallest @code{tosort} value, and subsequent nodes
-will have larger to values.
+Each node in this singly-linked list contains a @code{size_t} value and a 
floating point value.
+The floating point value is used as a reference to add new nodes in a sorted 
manner.
+At any moment, the first popped node in this list will have the smallest 
@code{tosort} value, and subsequent nodes will have larger to values.
 @end deftp
 @example
 typedef struct gal_list_osizet_t
@@ -33107,50 +33298,39 @@ typedef struct gal_list_osizet_t
 @end example
 
 @deftypefun void gal_list_osizet_add (gal_list_osizet_t @code{**list}, size_t 
@code{value}, float @code{tosort})
-Allocate space for a new node in @code{list}, and store @code{value} and
-@code{tosort} into it. The new node will not necessarily be at the ``top''
-of the list. If @code{*list!=NULL}, then the @code{tosort} values of
-existing nodes is inspected and the given node is placed in the list such
-that the top element (which is popped with @code{gal_list_osizet_pop}) has
-the smallest @code{tosort} value.
+Allocate space for a new node in @code{list}, and store @code{value} and 
@code{tosort} into it.
+The new node will not necessarily be at the ``top'' of the list.
+If @code{*list!=NULL}, then the @code{tosort} values of existing nodes is 
inspected and the given node is placed in the list such that the top element 
(which is popped with @code{gal_list_osizet_pop}) has the smallest 
@code{tosort} value.
 @end deftypefun
 
 @deftypefun size_t gal_list_osizet_pop (gal_list_osizet_t @code{**list}, float 
@code{*sortvalue})
-Pop a node from the top of @code{list}, return the node's @code{value} and
-put its sort value in the space that @code{sortvalue} points to. This
-function will also free the allocated space for the popped node and after
-this function, @code{list} will point to the next node (which has a larger
-@code{tosort} element).
+Pop a node from the top of @code{list}, return the node's @code{value} and put 
its sort value in the space that @code{sortvalue} points to.
+This function will also free the allocated space for the popped node and after 
this function, @code{list} will point to the next node (which has a larger 
@code{tosort} element).
 @end deftypefun
 
 @deftypefun void gal_list_osizet_to_sizet_free (gal_list_osizet_t @code{*in}, 
gal_list_sizet_t @code{**out})
-Convert the ordered list of @code{size_t}s into an ordinary @code{size_t}
-linked list. This can be useful when all the elements have been added and
-you just need to pop-out elements and do not care about the sorting values
-any more. After the conversion is done, this function will free the input
-list. Note that the @code{out} list does not have to be empty. If it already
-contains some nodes, the new nodes will be added on top of them.
+Convert the ordered list of @code{size_t}s into an ordinary @code{size_t} 
linked list.
+This can be useful when all the elements have been added and you just need to 
pop-out elements and do not care about the sorting values any more.
+After the conversion is done, this function will free the input list.
+Note that the @code{out} list does not have to be empty.
+If it already contains some nodes, the new nodes will be added on top of them.
 @end deftypefun
 
 
 @node Doubly linked ordered list of size_t, List of gal_data_t, Ordered list 
of size_t, Linked lists
 @subsubsection Doubly linked ordered list of @code{size_t}
 
-An ordered list of indices is required in many contexts, one example was
-discussed at the beginning of @ref{Ordered list of size_t}. But the list
-that was introduced there only has one point of entry: you can always only
-parse the list from smallest to largest. In this section, the doubly-linked
-@code{gal_list_dosizet_t} node is defined which will allow us to parse the
-values in ascending or descending order.
+An ordered list of indices is required in many contexts, one example was 
discussed at the beginning of @ref{Ordered list of size_t}.
+But the list that was introduced there only has one point of entry: you can 
always only parse the list from smallest to largest.
+In this section, the doubly-linked @code{gal_list_dosizet_t} node is defined 
which will allow us to parse the values in ascending or descending order.
 
 @deftp {Type (C @code{struct})} gal_list_dosizet_t
 @cindex @code{size_t}
 
-Doubly-linked, ordered @code{size_t} list node structure. Each node in this
-Doubly-linked list contains a @code{size_t} value and a floating point
-value. The floating point value is used as a reference to add new nodes in
-a sorted manner. In the functions here, this linked list can be pointed to
-by two pointers (largest and smallest) with the following format:
+Doubly-linked, ordered @code{size_t} list node structure.
+Each node in this Doubly-linked list contains a @code{size_t} value and a 
floating point value.
+The floating point value is used as a reference to add new nodes in a sorted 
manner.
+In the functions here, this linked list can be pointed to by two pointers 
(largest and smallest) with the following format:
 @example
             largest pointer
             |
@@ -33158,11 +33338,8 @@ by two pointers (largest and smallest) with the 
following format:
                                           |
                            smallest pointer
 @end example
-At any moment, the two pointers will point to the nodes containing the
-``largest'' and ``smallest'' values and the rest of the nodes will be
-sorted. This is useful when an unknown number of nodes are being added
-continuously and during the operations it is important to have the nodes in
-a sorted format.
+At any moment, the two pointers will point to the nodes containing the 
``largest'' and ``smallest'' values and the rest of the nodes will be sorted.
+This is useful when an unknown number of nodes are being added continuously 
and during the operations it is important to have the nodes in a sorted format.
 
 @example
 typedef struct gal_list_dosizet_t
@@ -33176,28 +33353,22 @@ typedef struct gal_list_dosizet_t
 @end deftp
 
 @deftypefun void gal_list_dosizet_add (gal_list_dosizet_t @code{**largest}, 
gal_list_dosizet_t @code{**smallest}, size_t @code{value}, float @code{tosort})
-Allocate space for a new node in @code{list}, and store @code{value} and
-@code{tosort} into it. If the list is empty, both @code{largest} and
-@code{smallest} must be @code{NULL}.
+Allocate space for a new node in @code{list}, and store @code{value} and 
@code{tosort} into it.
+If the list is empty, both @code{largest} and @code{smallest} must be 
@code{NULL}.
 @end deftypefun
 
 @deftypefun size_t gal_list_dosizet_pop_smallest (gal_list_dosizet_t 
@code{**largest}, gal_list_dosizet_t @code{**smallest}, float @code{tosort})
-Pop the value with the smallest reference from the doubly linked list and
-store the reference into the space pointed to by @code{tosort}. Note that
-even though only the smallest pointer will be popped, when there was only
-one node in the list, the @code{largest} pointer also has to change, so we
-need both.
+Pop the value with the smallest reference from the doubly linked list and 
store the reference into the space pointed to by @code{tosort}.
+Note that even though only the smallest pointer will be popped, when there was 
only one node in the list, the @code{largest} pointer also has to change, so we 
need both.
 @end deftypefun
 
 @deftypefun void gal_list_dosizet_print (gal_list_dosizet_t @code{*largest}, 
gal_list_dosizet_t @code{*smallest})
-Print the largest and smallest values sequentially until the list is
-parsed.
+Print the largest and smallest values sequentially until the list is parsed.
 @end deftypefun
 
 
 @deftypefun void gal_list_dosizet_to_sizet (gal_list_dosizet_t @code{*in}, 
gal_list_sizet_t @code{**out})
-Convert the doubly linked, ordered @code{size_t} list into a singly-linked
-list of @code{size_t}.
+Convert the doubly linked, ordered @code{size_t} list into a singly-linked 
list of @code{size_t}.
 @end deftypefun
 
 @deftypefun void gal_list_dosizet_free (gal_list_dosizet_t @code{*largest})
@@ -33208,22 +33379,15 @@ Free the doubly linked, ordered @code{sizet_t} list.
 @node List of gal_data_t,  , Doubly linked ordered list of size_t, Linked lists
 @subsubsection List of @code{gal_data_t}
 
-Gnuastro's generic data container has a @code{next} element which enables
-it to be used as a singly-linked list (see @ref{Generic data
-container}). The ability to connect the different data containers offers
-great advantages. For example, each column in a table in an independent
-dataset: with its own name, units, numeric data type (see @ref{Numeric data
-types}). Another application is in Tessellating an input dataset into
-separate tiles or only studying particular regions, or tiles, of a larger
-dataset (see @ref{Tessellation} and @ref{Tessellation library}). Each
-independent tile over the dataset can be connected to the others as a
-linked list and thus any number of tiles can be represented with one
-variable.
+Gnuastro's generic data container has a @code{next} element which enables it 
to be used as a singly-linked list (see @ref{Generic data container}).
+The ability to connect the different data containers offers great advantages.
+For example, each column in a table in an independent dataset: with its own 
name, units, numeric data type (see @ref{Numeric data types}).
+Another application is in Tessellating an input dataset into separate tiles or 
only studying particular regions, or tiles, of a larger dataset (see 
@ref{Tessellation} and @ref{Tessellation library}).
+Each independent tile over the dataset can be connected to the others as a 
linked list and thus any number of tiles can be represented with one variable.
 
 @deftypefun void gal_list_data_add (gal_data_t @code{**list}, gal_data_t 
@code{*newnode})
-Add an already allocated dataset (@code{newnode}) to top of
-@code{list}. Note that if @code{newnode->next!=NULL} (@code{newnode} is
-itself a list), then @code{list} will be added to its end.
+Add an already allocated dataset (@code{newnode}) to top of @code{list}.
+Note that if @code{newnode->next!=NULL} (@code{newnode} is itself a list), 
then @code{list} will be added to its end.
 
 In this example multiple images are linked together as a list:
 @example
@@ -33238,38 +33402,49 @@ gal_list_data_add( &list, tmp );
 @end deftypefun
 
 @deftypefun void gal_list_data_add_alloc (gal_data_t @code{**list}, void 
@code{*array}, uint8_t @code{type}, size_t @code{ndim}, size_t @code{*dsize}, 
struct wcsprm @code{*wcs}, int @code{clear}, size_t @code{minmapsize}, int 
@code{quietmmap}, char @code{*name}, char @code{*unit}, char @code{*comment})
-Allocate a new dataset (with @code{gal_data_alloc} in @ref{Dataset
-allocation}) and put it as the first element of @code{list}. Note that if
-this is the first node to be added to the list, @code{list} must be
-@code{NULL}.
+Allocate a new dataset (with @code{gal_data_alloc} in @ref{Dataset 
allocation}) and put it as the first element of @code{list}.
+Note that if this is the first node to be added to the list, @code{list} must 
be @code{NULL}.
 @end deftypefun
 
 @deftypefun {gal_data_t *} gal_list_data_pop (gal_data_t @code{**list})
 Pop the top node from @code{list} and return it.
 @end deftypefun
 
+@deftypefun void gal_list_data_remove (gal_data_t @code{**list}, gal_data_t 
@code{*node})
+Remove @code{node} from the given @code{list}.
+After finding the given node, this function will just set 
@code{node->next=NULL} and correct the @code{next} node of its previous element 
to its next element (thus ``removing'' it from the list).
+If @code{node} doesn't exist in the list, this function won't make any change 
to list.
+@end deftypefun
+
 @deftypefun {gal_data_t *} gal_list_data_select_by_name (gal_data_t 
@code{*list}, char @code{*name})
 Select the dataset within the list, that has a @code{name} element that is 
identical (case-sensitive) to the given @code{name}.
+If not found, a @code{NULL} pointer will be returned.
+
+Note that this dataset will not be popped from the list, only a pointer to it 
will be returned and if you free it or change its @code{next} element, it may 
harm your original list.
+@end deftypefun
+
+@deftypefun {gal_data_t *} gal_list_data_select_by_id (gal_data_t 
@code{*table}, char @code{*idstr}, size_t @code{*index})
+Select the dataset within the list that can be identified with the string 
given to @code{idstr} (which can be a counter, starting from 1, or a name).
+If not found, a @code{NULL} pointer will be returned.
+
 Note that this dataset will not be popped from the list, only a pointer to it 
will be returned and if you free it or change its @code{next} element, it may 
harm your original list.
 @end deftypefun
 
 @deftypefun void gal_list_data_reverse (gal_data_t @code{**list})
-Reverse the order of the list such that the top node in the list before
-calling this function becomes the bottom node after it.
+Reverse the order of the list such that the top node in the list before 
calling this function becomes the bottom node after it.
 @end deftypefun
 
 @deftypefun {gal_data_t **} gal_list_data_to_array_ptr (gal_data_t 
@code{*list}, size_t @code{*num})
-Allocate and return an array of @code{gal_data_t *} pointers with the same
-number of elements as the nodes in @code{list}. The pointers will be put in
-the same order that the list is parsed. Hence the N-th element in the array
-will point to the same dataset that the N-th node in the list points to.
+Allocate and return an array of @code{gal_data_t *} pointers with the same 
number of elements as the nodes in @code{list}.
+The pointers will be put in the same order that the list is parsed.
+Hence the N-th element in the array will point to the same dataset that the 
N-th node in the list points to.
 @end deftypefun
 
 @deftypefun size_t gal_list_data_number (gal_data_t @code{*list})
 Return the number of nodes in @code{list}.
 @end deftypefun
 
-@deftypefun size_t gal_list_data_last (gal_data_t @code{*list})
+@deftypefun {gal_data_t *} gal_list_data_last (gal_data_t @code{*list})
 Return a pointer to the last node in @code{list}.
 @end deftypefun
 
@@ -33456,8 +33631,12 @@ Convert the input identifier (one of the 
@code{GAL_TABLE_DISPLAY_FMT_FIXED}; for
 
 @deftypefun {gal_data_t *} gal_table_info (char @code{*filename}, char 
@code{*hdu}, gal_list_str_t @code{*lines}, size_t @code{*numcols}, size_t 
@code{*numrows}, int @code{*tableformat})
 
-Store the information of each column of a table into an array of data 
structures with @code{numcols} datasets (one data structure for each column).
-The number of rows is stored in @code{numrows}.
+Store the information of each column of a table into an array of meta-data 
@code{gal_data_t}s.
+In a metadata @code{gal_data_t}, the size elements are zero 
(@code{ndim=size=0} and @code{dsize=NULL}) but other relevant elements are 
filled).
+See the end of this description for the exact components of each 
@code{gal_data_t} that are filled.
+
+The returned array of @code{gal_data_t}s has @code{numcols} datasets (one data 
structure for each column).
+The number of rows in each dataset is stored in @code{numrows} (in a table, 
all the columns have the same number of rows).
 The format of the table (e.g., ASCII text file, or FITS binary or ASCII table) 
will be put in @code{tableformat} (macros defined above).
 If the @code{filename} is not a FITS file, then @code{hdu} will not be used 
(can be @code{NULL}).
 
@@ -33466,8 +33645,31 @@ The input must be either a file (specified by 
@code{filename}) or a list of stri
 It will mostly be the output of @code{gal_txt_stdin_read}, which is used to 
read the program's input as separate lines from the standard input (see 
@ref{Text files}).
 Note that @code{filename} and @code{lines} are mutually exclusive and one of 
them must be @code{NULL}.
 
-In the output datasets, only the meta-data strings (column name, units and 
comments), will be allocated and set.
+In the output datasets, only the meta-data strings (column name, units and 
comments), will be allocated and set as shown below.
 This function is just for column information (meta-data), not column contents.
+
+@example
+*restrict array  ->  Blank value (if present, in col's own type).
+           type  ->  Type of column data.
+           ndim  ->  0
+         *dsize  ->  NULL
+           size  ->  0
+      quietmmap  ->  ------------
+      *mmapname  ->  ------------
+     minmapsize  ->  Repeat (length of vector; 1 if not vector).
+           nwcs  ->  ------------
+           *wcs  ->  ------------
+           flag  ->  'GAL_TABLEINTERN_FLAG_*' macros.
+         status  ->  ------------
+          *name  ->  Column name.
+          *unit  ->  Column unit.
+       *comment  ->  Column comments.
+       disp_fmt  ->  'GAL_TABLE_DISPLAY_FMT' macros.
+     disp_width  ->  Width of string columns.
+ disp_precision  ->  ------------
+          *next  ->  Pointer to next column's metadata
+         *block  ->  ------------
+@end example
 @end deftypefun
 
 @deftypefun void gal_table_print_info (gal_data_t @code{*allcols}, size_t 
@code{numcols}, size_t @code{numrows})
@@ -33511,75 +33713,67 @@ The number of columns that matched each input column 
will be stored in each elem
 @end deftypefun
 
 @deftypefun {gal_list_sizet_t *} gal_table_list_of_indexs (gal_list_str_t 
@code{*cols}, gal_data_t @code{*allcols}, size_t @code{numcols}, int 
@code{searchin}, int @code{ignorecase}, char @code{*filename}, char 
@code{*hdu}, size_t @code{*colmatch})
-Returns a list of indices (starting from 0) of the input columns that match
-the names/numbers given to @code{cols}. This is a low-level operation which
-is called by @code{gal_table_read} (described above), see there for more on
-each argument's description. @code{allcols} is the returned array of
-@code{gal_table_info}.
+Returns a list of indices (starting from 0) of the input columns that match 
the names/numbers given to @code{cols}.
+This is a low-level operation which is called by @code{gal_table_read} 
(described above), see there for more on each argument's description.
+@code{allcols} is the returned array of @code{gal_table_info}.
 @end deftypefun
 
 @cindex Git
 @deftypefun void gal_table_comments_add_intro (gal_list_str_t 
@code{**comments}, char @code{*program_string}, time_t @code{*rawtime})
-Add some basic information to the list of @code{comments}. This basic
-information includes the following information
+Add some basic information to the list of @code{comments}.
+This basic information includes the following information
 @itemize
 @item
-If the program is run in a Git version controlled directory, Git's
-description is printed (see description under @code{COMMIT} in @ref{Output
-FITS files}).
+If the program is run in a Git version controlled directory, Git's description 
is printed (see description under @code{COMMIT} in @ref{Output FITS files}).
 @item
-The calendar time that is stored in @code{rawtime} (@code{time_t} is C's
-calendar time format defined in @file{time.h}). You can calculate the time
-in this format with the following expressions:
+The calendar time that is stored in @code{rawtime} (@code{time_t} is C's 
calendar time format defined in @file{time.h}).
+You can calculate the time in this format with the following expressions:
 @example
 time_t rawtime;
 time(&rawtime);
 @end example
 @item
-The name of your program in @code{program_string}. If it is @code{NULL},
-this line is ignored.
+The name of your program in @code{program_string}.
+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})
 
-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. When
-@code{filename==NULL}, the column information will be printed on the
-standard output (command-line).
+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.
+When @code{filename==NULL}, the column information will be printed on the 
standard output (command-line).
 
-If @code{comments!=NULL}, the list of comments (see @ref{List of strings})
-will also be printed into the output table. When the output table is a
-plain text file, every node of @code{comments} will be printed after a
-@code{#} (so it can be considered as a comment) and in FITS table they will
-follow a @code{COMMENT} keyword.
+If @code{comments!=NULL}, the list of comments (see @ref{List of strings}) 
will also be printed into the output table.
+When the output table is a plain text file, every node of @code{comments} will 
be printed after a @code{#} (so it can be considered as a comment) and in FITS 
table they will follow a @code{COMMENT} keyword.
 
-If a file named @code{filename} already exists, the operation depends on
-the type of output. When @code{filename} is a FITS file, the table will be
-added as a new extension after all existing extensions. If @code{filename}
-is a plain text file, this function will abort with an error.
+If a file named @code{filename} already exists, the operation depends on the 
type of output.
+When @code{filename} is a FITS file, the table will be added as a new 
extension after all existing extensions.
+If @code{filename} is a plain text file, this function will abort with an 
error.
 
-If @code{filename} is a FITS file, the table extension will have the name
-@code{extname}.
+If @code{filename} is a FITS file, the table extension will have the name 
@code{extname}.
 
-When @code{colinfoinstdout!=0} and @code{filename==NULL} (columns are
-printed in the standard output), the dataset metadata will also printed in
-the standard output. When printing to the standard output, the column
-information can be piped into another program for further processing and
-thus the meta-data (lines starting with a @code{#}) must be ignored. In
-such cases, you only print the column values by passing @code{0} to
-@code{colinfoinstdout}.
+When @code{colinfoinstdout!=0} and @code{filename==NULL} (columns are printed 
in the standard output), the dataset metadata will also printed in the standard 
output.
+When printing to the standard output, the column information can be piped into 
another program for further processing and thus the meta-data (lines starting 
with a @code{#}) must be ignored.
+In such cases, you only print the column values by passing @code{0} to 
@code{colinfoinstdout}.
 @end deftypefun
 
 @deftypefun void gal_table_write_log (gal_data_t @code{*logll}, char 
@code{*program_string}, time_t @code{*rawtime}, gal_list_str_t 
@code{*comments}, char @code{*filename}, int @code{quiet})
-Write the @code{logll} list of datasets into a table in @code{filename}
-(see @ref{List of gal_data_t}). This function is just a wrapper around
-@code{gal_table_comments_add_intro} and @code{gal_table_write} (see
-above). If @code{quiet} is non-zero, this function will print a message
-saying that the @code{filename} has been created.
+Write the @code{logll} list of datasets into a table in @code{filename} (see 
@ref{List of gal_data_t}).
+This function is just a wrapper around @code{gal_table_comments_add_intro} and 
@code{gal_table_write} (see above).
+If @code{quiet} is non-zero, this function will print a message saying that 
the @code{filename} has been created.
+@end deftypefun
+
+@deftypefun {gal_data_t *} gal_table_col_vector_extract (gal_data_t 
@code{*vector}, gal_list_sizet_t @code{*indexs})
+Given the ``vector'' column @code{vector} (which is assumed to be a 2D 
dataset), extract the tokens that are identified in the @code{indexs} list into 
a list of one dimensional datasets.
+For more on vector columns in tables, see @ref{Vector columns}.
 @end deftypefun
 
+@deftypefun {gal_data_t *} gal_table_cols_to_vector (gal_data_t @code{*list})
+Merge the one-dimensional datasets in the given list into one 2-dimensional 
dataset that can be treated as a vector column.
+All the input datasets have to have the same size and type.
+For more on vector columns in tables, see @ref{Vector columns}.
+@end deftypefun
 
 
 
@@ -34435,8 +34629,8 @@ 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})
-Write @code{cols} in a plain text file @code{filename}.
+@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})
+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:
 
 @table @asis
@@ -36096,6 +36290,12 @@ inverse:    IN_ALL[ perm[i] ]   =   IN_MEMORY[ i       
]
 @end example
 @end deftypefun
 
+@deftypefun void gal_permutation_apply_onlydim0 (gal_data_t @code{*input}, 
size_t @code{*permutation})
+Similar to @code{gal_permutation_apply}, but when the dataset is 
2-dimensional, permute each row (dimension 1 in C) as one element.
+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})
 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]}.
diff --git a/lib/blank.c b/lib/blank.c
index ccbdcc06..e4aeca4a 100644
--- a/lib/blank.c
+++ b/lib/blank.c
@@ -586,8 +586,8 @@ gal_blank_flag(gal_data_t *input)
       /* Allocate a non-cleared output array, we are going to parse the
          input and fill in each element. */
       out=gal_data_alloc(NULL, GAL_TYPE_UINT8, input->ndim, input->dsize,
-                         input->wcs, 0, input->minmapsize, input->quietmmap,
-                         NULL, "bool", NULL);
+                         input->wcs, 0, input->minmapsize,
+                         input->quietmmap, NULL, "bool", NULL);
 
       /* Set the pointers for easy looping. */
       of=(o=out->array)+input->size;
@@ -762,9 +762,9 @@ gal_blank_flag_remove(gal_data_t *input, gal_data_t *flag)
       strarr=input->array;
       for(i=0;i<input->size;++i)
         {
-          if( *f && *f!=GAL_BLANK_UINT8 )        /* Flagged to be removed */
+          if( *f && *f!=GAL_BLANK_UINT8 )    /* Flagged to be removed */
             { free(strarr[i]); strarr[i]=NULL; }
-          else strarr[num++]=strarr[i];          /* Keep. */
+          else strarr[num++]=strarr[i];      /* Keep. */
           ++f;
         }
       break;
@@ -782,6 +782,90 @@ gal_blank_flag_remove(gal_data_t *input, gal_data_t *flag)
 
 
 
+/* Remove flagged elements from a dataset (which may not necessarily
+   blank), convert it to a 1D dataset and adjust the size properly. In
+   practice this function doesn't 'realloc' the input array, all it does is
+   to shift the blank eleemnts to the end and adjust the size elements of
+   the 'gal_data_t'. */
+#define BLANK_FLAG_REMOVE_D0(IT) {                                      \
+    IT *a=input->array, *af=a+input->size, *o=input->array;             \
+    do {                                                                \
+      printf("\n%s: HERE\n", __func__);                                   \
+      if(*f) a+=nelem;                                                  \
+      else {num++; for(i=0;i<nelem;++i) {*o++=*a; ++a; printf("%zu ", i);} } \
+      ++f;                                                              \
+    }                                                                   \
+    while(a<af);                                                        \
+  }
+static void
+blank_flag_remove_dim0(gal_data_t *input, gal_data_t *flag)
+{
+  size_t i, num=0;
+  uint8_t it=input->type;
+  uint8_t *f=flag->array;
+  size_t nelem = input->ndim==1 ? 1 : input->size/input->dsize[0];
+
+  /* Sanity check. */
+  if(flag->type!=GAL_TYPE_UINT8)
+    error(EXIT_FAILURE, 0, "%s: the 'flag' argument has a '%s' type, it "
+          "must have an unsigned 8-bit type", __func__,
+          gal_type_name(flag->type, 1));
+  if(input->dsize[0]!=flag->dsize[0])
+    error(EXIT_FAILURE, 0, "%s: the 'flag' argument doesn't have the same "
+          "size as the 'input' argument", __func__);
+
+  /* This is a special function! */
+  if(flag->ndim!=1)
+    error(EXIT_FAILURE, 0, "%s: this function's 'flag' should only "
+          "have a single dimension", __func__);
+  if(input->ndim==flag->ndim)
+    error(EXIT_FAILURE, 0, "%s: this function is only for scenarios "
+          "where 'flag' is single-dimensional and the input is "
+          "multi-dimensional. Please use 'gal_blank_flag_remove' when "
+          "the input and flag have the same number of dimensions",
+          __func__);
+
+  /* If there is no elements in the input or the flag (we have already
+     confirmed that they are the same size), then just return (nothing is
+     necessary to do). */
+  if(flag->size==0 || flag->array==NULL) return;
+
+  /* Move all the non-flagged elements to the start of the array. */
+  for(i=0;i<flag->size;++i)
+    if(f[i]==0)
+      memmove(gal_pointer_increment(input->array, (num++)*nelem, it),
+              gal_pointer_increment(input->array, i*nelem,       it),
+              nelem*gal_type_sizeof(it));
+
+  /* Shift all non-blank elements to the start of the array.
+  switch(input->type)
+    {
+    case GAL_TYPE_UINT8:    BLANK_FLAG_REMOVE_D0( uint8_t  );    break;
+    case GAL_TYPE_INT8:     BLANK_FLAG_REMOVE_D0( int8_t   );    break;
+    case GAL_TYPE_UINT16:   BLANK_FLAG_REMOVE_D0( uint16_t );    break;
+    case GAL_TYPE_INT16:    BLANK_FLAG_REMOVE_D0( int16_t  );    break;
+    case GAL_TYPE_UINT32:   BLANK_FLAG_REMOVE_D0( uint32_t );    break;
+    case GAL_TYPE_INT32:    BLANK_FLAG_REMOVE_D0( int32_t  );    break;
+    case GAL_TYPE_UINT64:   BLANK_FLAG_REMOVE_D0( uint64_t );    break;
+    case GAL_TYPE_INT64:    BLANK_FLAG_REMOVE_D0( int64_t  );    break;
+    case GAL_TYPE_FLOAT32:  BLANK_FLAG_REMOVE_D0( float    );    break;
+    case GAL_TYPE_FLOAT64:  BLANK_FLAG_REMOVE_D0( double   );    break;
+    default:
+      error(EXIT_FAILURE, 0, "%s: type code %d not recognized",
+            __func__, input->type);
+    }
+  */
+
+  /* Adjust the size elements of the dataset. */
+  input->size=1;
+  input->dsize[0]=num;
+  for(i=0;i<input->ndim;++i) input->size*=input->dsize[i];
+}
+
+
+
+
+
 /* Remove blank elements from a dataset, convert it to a 1D dataset and
    adjust the size properly. In practice this function doesn't 'realloc'
    the input array, all it does is to shift the blank eleemnts to the end
@@ -869,11 +953,23 @@ gal_blank_remove_realloc(gal_data_t *input)
 
 
 static gal_data_t *
-blank_remove_in_list_merge_flags(gal_data_t *thisdata, gal_data_t *flag)
+blank_remove_in_list_merge_flags(gal_data_t *thisdata, gal_data_t *flag,
+                                 int onlydim0)
 {
   size_t i;
   uint8_t *u, *tu;
   gal_data_t *flagtmp;
+  static int warningprinted=0;
+
+  /* Ignore the dataset if it has more than one dimension and 'onlydim0' is
+     called*/
+  if(onlydim0 && thisdata->ndim>1 && warningprinted==0)
+    {
+      warningprinted=1;
+      error(EXIT_SUCCESS, 0, "%s: WARNING: multi-dimensional columns "
+            "are not supported when 'onlydim0' is non-zero", __func__);
+      return NULL;
+    }
 
   /* Build the flag of blank elements for this column. */
   flagtmp=gal_blank_flag(thisdata);
@@ -906,7 +1002,8 @@ blank_remove_in_list_merge_flags(gal_data_t *thisdata, 
gal_data_t *flag)
 
 /* Remove any row that has a blank in any of the given columns. */
 gal_data_t *
-gal_blank_remove_rows(gal_data_t *columns, gal_list_sizet_t *column_indexs)
+gal_blank_remove_rows(gal_data_t *columns, gal_list_sizet_t *column_indexs,
+                      int onlydim0)
 {
   size_t i;
   gal_list_sizet_t *tcol;
@@ -930,15 +1027,19 @@ gal_blank_remove_rows(gal_data_t *columns, 
gal_list_sizet_t *column_indexs)
                 __func__, gal_list_data_number(columns), tcol->v);
 
         /* Build the flag of blank elements for this column. */
-        flag=blank_remove_in_list_merge_flags(tmp, flag);
+        flag=blank_remove_in_list_merge_flags(tmp, flag, onlydim0);
       }
   else
     for(tmp=columns; tmp!=NULL; tmp=tmp->next)
-      flag=blank_remove_in_list_merge_flags(tmp, flag);
+      flag=blank_remove_in_list_merge_flags(tmp, flag, onlydim0);
 
   /* Now that the flags have been set, remove the rows. */
   for(tmp=columns; tmp!=NULL; tmp=tmp->next)
-    gal_blank_flag_remove(tmp, flag);
+    {
+      if(tmp->ndim==1 || onlydim0==0) gal_blank_flag_remove(tmp, flag);
+      else blank_flag_remove_dim0(tmp, flag);
+    }
+
   /* For a check.
   double *d1=columns->array, *d2=columns->next->array;
   for(i=0;i<columns->size;++i)
diff --git a/lib/fits.c b/lib/fits.c
index da6be0dd..190fb324 100644
--- a/lib/fits.c
+++ b/lib/fits.c
@@ -56,6 +56,22 @@ along with Gnuastro. If not, see 
<http://www.gnu.org/licenses/>.
 
 
 
+/*************************************************************
+ **************    Internal necessary functions   ************
+ *************************************************************/
+static void
+fits_tab_write_col(fitsfile *fptr, gal_data_t *col, int tableformat,
+                   size_t *colind, char *tform, char *filename);
+
+
+
+
+
+
+
+
+
+
 /*************************************************************
  **************        Reporting errors:       ***************
  *************************************************************/
@@ -2149,8 +2165,8 @@ gal_fits_key_write_version(gal_fits_list_key_t **keylist, 
char *title,
 
 
 void
-gal_fits_key_write_version_in_ptr(gal_fits_list_key_t **keylist, char *title,
-                                  fitsfile *fptr)
+gal_fits_key_write_version_in_ptr(gal_fits_list_key_t **keylist,
+                                  char *title, fitsfile *fptr)
 {
   int status=0;
   char *gitdescribe;
@@ -2305,8 +2321,8 @@ gal_fits_with_keyvalue(gal_list_str_t *files, char *hdu, 
char *name,
               fits_read_key(fptr, TSTRING, name, &keyvalue, NULL,
                             &status);
 
-              /* If the value corresponds to any of the user's values for this
-                 keyword, add it to the list of output names. */
+              /* If the value corresponds to any of the user's values for
+                 this keyword, add it to the list of output names. */
               if(status==0)
                 for(v=values; v!=NULL; v=v->next)
                   {
@@ -2461,13 +2477,13 @@ gal_fits_img_info(fitsfile *fptr, int *type, size_t 
*ndim, size_t **dsize,
         {
         switch(i)
           {
-          case 4: if(unit) {str = key->array; *unit = *str; *str=NULL;} break;
-          case 3: if(name) {str = key->array; *name = *str; *str=NULL;} break;
-          case 2: bscale = *(double *)(key->array);                     break;
-          case 1: str = key->array; bzero_str = *str;                   break;
+          case 4: if(unit) {str=key->array; *unit=*str; *str=NULL;} break;
+          case 3: if(name) {str=key->array; *name=*str; *str=NULL;} break;
+          case 2: bscale = *(double *)(key->array);                 break;
+          case 1: str = key->array; bzero_str = *str;               break;
           default:
-            error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to "
-                  "fix the problem. For some reason, there are more "
+            error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s "
+                  "to fix the problem. For some reason, there are more "
                   "keywords requested ", __func__, PACKAGE_BUGREPORT);
           }
         }
@@ -2866,8 +2882,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,
+                           char *program_string, int type)
 {
   /* If the input dataset is not the correct type, then convert it,
      otherwise, use the input data structure. */
@@ -2999,7 +3015,8 @@ gal_fits_tab_format(fitsfile *fitsptr)
         return GAL_TABLE_FORMAT_BFITS;
       else
         error(EXIT_FAILURE, 0, "%s: the 'XTENSION' keyword of this FITS "
-              "table ('%s') doesn't have a standard value", __func__, value);
+              "table ('%s') doesn't have a standard value", __func__,
+              value);
     }
   else
     {
@@ -3010,9 +3027,9 @@ gal_fits_tab_format(fitsfile *fitsptr)
         gal_fits_io_error(status, NULL);
     }
 
-  error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s so we can fix it. 
"
-        "Control should not have reached the end of this function", __func__,
-        PACKAGE_BUGREPORT);
+  error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s so we "
+        "can fix it. Control should not have reached the end of this "
+        "function", __func__, PACKAGE_BUGREPORT);
   return -1;
 }
 
@@ -3069,8 +3086,8 @@ set_display_format(char *tdisp, gal_data_t *data, char 
*filename, char *hdu,
 
     default:
       error(EXIT_FAILURE, 0, "%s (hdu: %s): Format character '%c' in the "
-            "value (%s) of the keyword %s not recognized in %s", filename, hdu,
-            tdisp[0], tdisp, keyname, __func__);
+            "value (%s) of the keyword %s not recognized in %s", filename,
+            hdu, tdisp[0], tdisp, keyname, __func__);
     }
 
   /* Parse the rest of the string to see if a width and precision are given
@@ -3082,8 +3099,9 @@ set_display_format(char *tdisp, gal_data_t *data, char 
*filename, char *hdu,
       data->disp_precision = strtol(&tailptr[1], &tailptr, 0);
       if(*tailptr!='\0')
         error(EXIT_FAILURE, 0, "%s (hdu: %s): The value '%s' of the "
-              "'%s' keyword could not recognized (it doesn't finish after "
-              "the precision) in %s", filename, hdu, tdisp, keyname, __func__);
+              "'%s' keyword could not recognized (it doesn't finish "
+              "after the precision) in %s", filename, hdu, tdisp,
+              keyname, __func__);
       break;
 
     case '\0':     /* No precision given, use a default value.     */
@@ -3094,8 +3112,8 @@ set_display_format(char *tdisp, gal_data_t *data, char 
*filename, char *hdu,
 
     default:
       error(EXIT_FAILURE, 0, "%s (hdu: %s): The value '%s' of the "
-            "'%s' keyword could not recognized (it doesn't have a '.', or "
-            "finish, after the width) in %s", filename, hdu, tdisp,
+            "'%s' keyword could not recognized (it doesn't have a '.', "
+            "or finish, after the width) in %s", filename, hdu, tdisp,
             keyname, __func__);
     }
 
@@ -3125,7 +3143,8 @@ fits_correct_bin_table_int_types(gal_data_t *allcols, int 
tfields,
 
       /* For a check
       printf("Column %zu initial type: %s (s: %d, z: %lld)\n", i+1,
-             gal_data_type_as_string(allcols[i].type, 1), tscal[i], tzero[i]);
+             gal_data_type_as_string(allcols[i].type, 1), tscal[i],
+             tzero[i]);
       */
 
       /* Correct the type based on the initial read type and the value to
@@ -3437,9 +3456,9 @@ gal_fits_tab_info(char *filename, char *hdu, size_t 
*numcols,
                 }
             }
           else
-            error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to "
-                  "fix the problem. Post-processing of keyword '%s' failed",
-                  __func__, PACKAGE_BUGREPORT, tmp_n->v);
+            error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s "
+                  "to fix the problem. Post-processing of keyword '%s' "
+                  "failed", __func__, PACKAGE_BUGREPORT, tmp_n->v);
 
           /* Increment the other two lists too. */
           tmp_v=tmp_v->next;
@@ -3453,6 +3472,11 @@ gal_fits_tab_info(char *filename, char *hdu, size_t 
*numcols,
     }
 
 
+  /* Set the 'next' pointer of each column. */
+  for(i=0;i<tfields;++i)
+    allcols[i].next = (i==tfields-1) ? NULL : &allcols[i+1];
+
+
   /* Correct integer types, then free the allocated arrays. */
   fits_correct_bin_table_int_types(allcols, tfields, tscal, tzero);
   free(tscal);
@@ -3472,10 +3496,10 @@ gal_fits_tab_info(char *filename, char *hdu, size_t 
*numcols,
 /* Read CFITSIO un-readable (INF, -INF or NAN) floating point values in
    FITS ASCII tables. */
 static void
-fits_tab_read_ascii_float_special(char *filename, char *hdu, fitsfile *fptr,
-                                  gal_data_t *out, size_t colnum,
-                                  size_t numrows, size_t minmapsize,
-                                  int quietmmap)
+fits_tab_read_ascii_float_special(char *filename, char *hdu,
+                                  fitsfile *fptr, gal_data_t *out,
+                                  size_t colnum, size_t numrows,
+                                  size_t minmapsize, int quietmmap)
 {
   double tmp;
   char **strarr;
@@ -3500,8 +3524,8 @@ fits_tab_read_ascii_float_special(char *filename, char 
*hdu, fitsfile *fptr,
     }
 
   /* Read the column as a string. */
-  fits_read_col(fptr, TSTRING, colnum, 1, 1, out->size, NULL, strrows->array,
-                &anynul, &status);
+  fits_read_col(fptr, TSTRING, colnum, 1, 1, out->size, NULL,
+                strrows->array, &anynul, &status);
   gal_fits_io_error(status, NULL);
 
   /* Convert the strings to float. */
@@ -3549,13 +3573,15 @@ fits_tab_read_onecol(void *in_prm)
     = (struct fits_tab_read_onecol_params *)tprm->params;
 
   /* Subsequent definitions. */
+  uint8_t type;
   char **strarr;
   fitsfile *fptr;
   gal_data_t *col;
+  size_t dsize[2];
   gal_list_sizet_t *tmp;
   void *blank, *blankuse;
   int isfloat, hdutype, anynul=0, status=0;
-  size_t i, j, c, strw, indout, indin=GAL_BLANK_SIZE_T;
+  size_t i, j, c, ndim, strw, repeat, indout, indin=GAL_BLANK_SIZE_T;
 
   /* Open the FITS file */
   fptr=gal_fits_hdu_open_format(p->filename, p->hdu, 1);
@@ -3576,9 +3602,15 @@ fits_tab_read_onecol(void *in_prm)
         { if(c==indout) { indin=tmp->v; break; } ++c; }
 
       /* Allocate the necessary space for this column. */
-      col=gal_data_alloc(NULL, p->allcols[indin].type, 1,
-                         &p->numrows, NULL, 0, p->minmapsize,
-                         p->quietmmap, p->allcols[indin].name,
+      type=p->allcols[indin].type;
+      repeat=p->allcols[indin].minmapsize;
+      if(type!=GAL_TYPE_STRING && repeat>1)
+        { ndim=2; dsize[0]=p->numrows; dsize[1]=repeat; }
+      else
+        { ndim=1; dsize[0]=p->numrows; }
+      col=gal_data_alloc(NULL, type, ndim, dsize, NULL, 0,
+                         p->minmapsize, p->quietmmap,
+                         p->allcols[indin].name,
                          p->allcols[indin].unit,
                          p->allcols[indin].comment);
 
@@ -3759,7 +3791,8 @@ gal_fits_tab_read(char *filename, char *hdu, size_t 
numrows,
           /* Do the allocation. */
           gal_list_data_add_alloc(&out, NULL, allcols[ind->v].type, 1,
                                   &numrows, NULL, 0, minmapsize, quietmmap,
-                                  allcols[ind->v].name, allcols[ind->v].unit,
+                                  allcols[ind->v].name,
+                                  allcols[ind->v].unit,
                                   allcols[ind->v].comment);
 
           /* Correct the array and sizes. */
@@ -3830,17 +3863,18 @@ fits_string_fixed_alloc_size(gal_data_t *data)
 
 
 static void
-fits_table_prepare_arrays(gal_data_t *cols, size_t numcols, int tableformat,
-                          char ***outtform, char ***outttype,
-                          char ***outtunit)
+fits_table_prepare_arrays(gal_data_t *cols, size_t numcols,
+                          int tableformat, char ***outtform,
+                          char ***outttype, char ***outtunit)
 {
-  size_t i=0;
+  size_t i=0, j;
   gal_data_t *col;
   char fmt[2], lng[3];
   char *blank, **tform, **ttype, **tunit;
 
 
-  /* Allocate the arrays to keep the 'tform' values */
+  /* Allocate the arrays to keep the 'tform', 'ttype' and 'tunit'
+     keywords. */
   errno=0;
   tform=*outtform=malloc(numcols*sizeof *tform);
   if(tform==NULL)
@@ -3867,7 +3901,6 @@ fits_table_prepare_arrays(gal_data_t *cols, size_t 
numcols, int tableformat,
       if( asprintf(&tunit[i], "%s", col->unit ? col->unit : "")<0 )
         error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
 
-
       /* FITS's TFORM depends on the type of FITS table, so work
          differently. */
       switch(tableformat)
@@ -3907,24 +3940,25 @@ fits_table_prepare_arrays(gal_data_t *cols, size_t 
numcols, int tableformat,
               case GAL_TYPE_INT32:
               case GAL_TYPE_UINT64:
               case GAL_TYPE_INT64:
-                if( asprintf(&tform[i], "%c%d", fmt[0], col->disp_width)<0 )
-                  error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
+                if(asprintf(&tform[i], "%c%d", fmt[0], col->disp_width)<0)
+                  error(EXIT_FAILURE, 0, "%s: asprintf allocation",
+                        __func__);
                 break;
 
               case GAL_TYPE_FLOAT32:
               case GAL_TYPE_FLOAT64:
                 if( asprintf(&tform[i], "%c%d.%d", fmt[0], col->disp_width,
                              col->disp_precision)<0 )
-                  error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
+                  error(EXIT_FAILURE, 0, "%s: asprintf allocation",
+                        __func__);
                 break;
 
               default:
-                error(EXIT_FAILURE, 0, "%s: col->type code %d not recognized",
-                      __func__, col->type);
+                error(EXIT_FAILURE, 0, "%s: col->type code %d not "
+                      "recognized", __func__, col->type);
               }
           break;
 
-
         /* FITS binary table. */
         case GAL_TABLE_FORMAT_BFITS:
 
@@ -3939,8 +3973,24 @@ fits_table_prepare_arrays(gal_data_t *cols, size_t 
numcols, int tableformat,
             }
           else
             {
-              if( asprintf(&tform[i], "%c", fmt[0])<0 )
-                error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
+              /* For vector columns, we need to give the number of elements
+                 in the vector. */
+              switch(col->ndim)
+                {
+                case 1:
+                  if( asprintf(&tform[i], "%c", fmt[0])<0 )
+                    error(EXIT_FAILURE, 0, "%s: asprintf allocation",
+                          __func__);
+                  break;
+                case 2:
+                  if(asprintf(&tform[i], "%zu%c", col->dsize[1], fmt[0])<0)
+                    error(EXIT_FAILURE, 0, "%s: asprintf allocation",
+                          __func__);
+                  break;
+                default:
+                  error(EXIT_FAILURE, 0, "%s: only 1D or 2D data can "
+                        "be written as a binary table", __func__);
+                }
             }
           break;
 
@@ -3949,9 +3999,19 @@ fits_table_prepare_arrays(gal_data_t *cols, size_t 
numcols, int tableformat,
                 __func__, tableformat);
         }
 
-
-      /* Increment the column index. */
-      ++i;
+      /* Increment the column index and write the values into the vector
+         columns also. */
+      if(tableformat==GAL_TABLE_FORMAT_AFITS && col->ndim>1)
+        {
+          for(j=1;j<col->dsize[1];++j)
+            {
+              gal_checkset_allocate_copy(tform[i], &tform[i+j]);
+              gal_checkset_allocate_copy(tunit[i], &tunit[i+j]);
+              gal_checkset_allocate_copy(ttype[i], &ttype[i+j]);
+            }
+          i+=col->dsize[1];
+        }
+      else ++i;
     }
 }
 
@@ -4079,6 +4139,135 @@ fits_write_tnull_tcomm(fitsfile *fptr, gal_data_t *col, 
int tableformat,
 
 
 
+static size_t
+fits_tab_write_colvec_ascii(fitsfile *fptr, gal_data_t *vector,
+                            size_t colind, char *tform, char *filename)
+{
+  int status=0;
+  char *keyname;
+  static int warnasciivec=0;
+  gal_data_t *ext, *extracted;
+  gal_list_sizet_t *indexs=NULL;
+  size_t i, coli=GAL_BLANK_SIZE_T;
+
+  /* Print a warning about the modification that is necessary. */
+  if(warnasciivec==0)
+    {
+      error(EXIT_SUCCESS, 0, "WARNING: vector (multi-valued) columns "
+            "are not supported in the FITS ASCII format. Therefore, "
+            "vector column(s) will be broken into separate "
+            "single-valued columns in '%s'", filename);
+      warnasciivec=1;
+    }
+
+  /* Build the indexs list and reverse it (adding to a last-in-first-out
+     list will result in an inverse list). */
+  for(i=0;i<vector->dsize[1];++i) gal_list_sizet_add(&indexs, i);
+  gal_list_sizet_reverse(&indexs);
+
+  /* Call the table function to extract the desired components of the
+     vector column and clean up the indexs. */
+  extracted=gal_table_col_vector_extract(vector, indexs);
+  gal_list_sizet_free(indexs);
+
+  /* Write the extracted columns into the output. */
+  i=0;
+  for(ext=extracted; ext!=NULL; ext=ext->next)
+    {
+      /* Write the column. */
+      coli = colind + i++;
+      fits_tab_write_col(fptr, ext, GAL_TABLE_FORMAT_AFITS, &coli,
+                         tform, filename);
+
+      /* Set the keyword name. */
+      if( asprintf(&keyname, "TTYPE%zu", coli)<0 )
+        error(EXIT_FAILURE, 0, "%s: asprintf for 'keyname'", __func__);
+
+      /* Update the keyword with the name of the column (that was just
+         copied in 'fits_table_prepare_arrays' from the vector-column's
+         name, resulting in similar names). */
+      fits_update_key(fptr, TSTRING, keyname, ext->name, NULL, &status);
+      gal_fits_io_error(status, NULL);
+    }
+
+  /* Return the last column-index (which has been incremented in
+     'fits_tab_write_col'). */
+  return coli;
+}
+
+
+
+
+
+/* Write a single column into the FITS table. */
+static void
+fits_tab_write_col(fitsfile *fptr, gal_data_t *col, int tableformat,
+                   size_t *colind, char *tform, char *filename)
+{
+  int status=0;
+  char **strarr;
+  void *blank=NULL;
+
+  /* If this is a FITS ASCII table, and the column is vector, we need to
+     write it as separate single-value columns and write those, then we can
+     safely return (no more need to continue). */
+  if(tableformat==GAL_TABLE_FORMAT_AFITS && col->ndim>1)
+    {
+      *colind=fits_tab_write_colvec_ascii(fptr, col, *colind, tform,
+                                          filename);
+      return;
+    }
+
+  /* Write the blank value into the header and return a pointer to
+     it. Otherwise, */
+  fits_write_tnull_tcomm(fptr, col, tableformat, *colind+1, tform);
+
+  /* Set the blank pointer if its necessary. Note that strings don't need a
+     blank pointer in a FITS ASCII table. */
+  blank = ( gal_blank_present(col, 0)
+            ? fits_blank_for_tnull(col->type) : NULL );
+  if(tableformat==GAL_TABLE_FORMAT_AFITS && col->type==GAL_TYPE_STRING)
+    { if(blank) free(blank); blank=NULL; }
+
+  /* Manually remove the 'blank' pointer for standard FITS table numeric
+     types (types below). We are doing this because as of CFITSIO 3.48,
+     CFITSIO crashes for these types when we define our own blank values
+     within this pointer, and such values actually exist in the
+     column. This is the error message: "Null value for integer table
+     column is not defined (FTPCLU)". Generally, for these native FITS
+     table types 'blank' is redundant because our blank values are actually
+     within their numerical data range. */
+  switch(col->type)
+    {
+    case GAL_TYPE_UINT8:
+    case GAL_TYPE_INT16:
+    case GAL_TYPE_INT32:
+    case GAL_TYPE_INT64:
+      free(blank); blank=NULL;
+      break;
+    }
+
+  /* Write the full column into the table. */
+  fits_write_colnull(fptr, gal_fits_type_to_datatype(col->type),
+                     *colind+1, 1, 1, col->size, col->array, blank,
+                     &status);
+  gal_fits_io_error(status, NULL);
+
+  /* Clean up and Increment the column counter. */
+  if(blank)
+    {
+      if(col->type==GAL_TYPE_STRING) {strarr=blank; free(strarr[0]);}
+      free(blank);
+    }
+
+  /* Increment the 'colind' for the next column. */
+  *colind+=1;
+}
+
+
+
+
+
 /* Write the given columns (a linked list of 'gal_data_t') into a FITS
    table.*/
 void
@@ -4086,34 +4275,38 @@ 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)
 {
-  void *blank;
   fitsfile *fptr;
   gal_data_t *col;
-  size_t i, numrows=-1;
   gal_list_str_t *strt;
+  char **ttype, **tform, **tunit;
+  size_t i, numrows=-1, thisnrows;
   int tbltype, numcols=0, status=0;
-  char **ttype, **tform, **tunit, **strarr;
 
   /* Make sure all the input columns have the same number of elements */
   for(col=cols; col!=NULL; col=col->next)
     {
-      if(numrows==-1) numrows=col->size;
-      else if(col->size!=numrows)
-        error(EXIT_FAILURE, 0, "%s: the number of records/rows in the input "
-              "columns are not equal", __func__);
-      ++numcols;
+      thisnrows = col->dsize ? col->dsize[0] : 0;
+      if(numrows==-1) numrows=thisnrows;
+      else if(thisnrows!=numrows)
+        error(EXIT_FAILURE, 0, "%s: the number of records/rows in the "
+              "input columns are not equal! The first column "
+              "has %zu rows, while column %d has %zu rows",
+              __func__, numrows, numcols+1, thisnrows);
+
+      /* ASCII FITS tables don't accept vector columns, so some extra
+         columns need to be added. */
+      numcols += ( tableformat==GAL_TABLE_FORMAT_AFITS
+                   ? (col->ndim==1 ? 1 : col->dsize[1])
+                   : 1 );
     }
 
-
   /* Open the FITS file for writing. */
   fptr=gal_fits_open_to_write(filename);
 
-
   /* Prepare necessary arrays and if integer type columns have blank
      values, write the TNULLn keywords into the FITS file. */
-  fits_table_prepare_arrays(cols, numcols, tableformat,
-                            &tform, &ttype, &tunit);
-
+  fits_table_prepare_arrays(cols, numcols, tableformat, &tform, &ttype,
+                            &tunit);
 
   /* Make the FITS file pointer. Note that tableformat was checked in
      'fits_table_prepare_arrays'. */
@@ -4122,54 +4315,11 @@ gal_fits_tab_write(gal_data_t *cols, gal_list_str_t 
*comments,
                   extname, &status);
   gal_fits_io_error(status, NULL);
 
-
   /* Write the columns into the file and also write the blank values into
      the header when necessary. */
   i=0;
-  for(col=cols; col!=NULL; col=col->next)
-    {
-      /* Write the blank value into the header and return a pointer to
-         it. Otherwise, */
-      fits_write_tnull_tcomm(fptr, col, tableformat, i+1, tform[i]);
-
-      /* Set the blank pointer if its necessary, note that strings don't
-         need a blank pointer in a FITS ASCII table. */
-      blank = ( gal_blank_present(col, 0)
-                ? fits_blank_for_tnull(col->type) : NULL );
-      if(tableformat==GAL_TABLE_FORMAT_AFITS && col->type==GAL_TYPE_STRING)
-        { if(blank) free(blank); blank=NULL; }
-
-      /* Manually remove the 'blank' pointer for standard FITS table
-         numeric types (types below). We are doing this because as of
-         CFITSIO 3.48, CFITSIO crashes for these types when we define our
-         own blank values within this pointer, and such values actually
-         exist in the column. This is the error message: "Null value for
-         integer table column is not defined (FTPCLU)". Generally, for
-         these native FITS table types 'blank' is redundant because our
-         blank values are actually within their numerical data range. */
-      switch(col->type)
-        {
-        case GAL_TYPE_UINT8:
-        case GAL_TYPE_INT16:
-        case GAL_TYPE_INT32:
-        case GAL_TYPE_INT64:
-          free(blank); blank=NULL;
-          break;
-        }
-
-      /* Write the full column into the table. */
-      fits_write_colnull(fptr, gal_fits_type_to_datatype(col->type),
-                         i+1, 1, 1, col->size, col->array, blank, &status);
-      gal_fits_io_error(status, NULL);
-
-      /* Clean up and Increment the column counter. */
-      if(blank)
-        {
-          if(col->type==GAL_TYPE_STRING) {strarr=blank; free(strarr[0]);}
-          free(blank); blank=NULL;
-        }
-      ++i;
-    }
+  for(col=cols; col!=NULL; col=col->next)/*'i' is increment in the func.*/
+    fits_tab_write_col(fptr, col, tableformat, &i, tform[i], filename);
 
   /* Write the requested keywords. */
   if(keylist)
@@ -4179,24 +4329,15 @@ gal_fits_tab_write(gal_data_t *cols, gal_list_str_t 
*comments,
   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
      allocated.*/
-  for(i=0;i<numcols;++i)
-    {
-      free(tform[i]);
-      free(ttype[i]);
-      free(tunit[i]);
-    }
-  free(tform);
-  free(ttype);
-  free(tunit);
+  for(i=0;i<numcols;++i) {free(tform[i]); free(ttype[i]); free(tunit[i]);}
+  free(tform); free(ttype); free(tunit);
   fits_close_file(fptr, &status);
   gal_fits_io_error(status, NULL);
 }
diff --git a/lib/gnuastro-internal/options.h b/lib/gnuastro-internal/options.h
index 30b79d03..b59e4e20 100644
--- a/lib/gnuastro-internal/options.h
+++ b/lib/gnuastro-internal/options.h
@@ -299,7 +299,7 @@ gal_options_read_interpmetric(struct argp_option *option, 
char *arg,
 
 gal_data_t *
 gal_options_parse_list_of_numbers(char *string, char *filename,
-                                  size_t lineno);
+                                  size_t lineno, uint8_t type);
 
 gal_data_t *
 gal_options_parse_csv_strings_raw(char *string, char *filename,
@@ -314,7 +314,8 @@ gal_options_merge_list_of_csv(gal_list_str_t **list);
 
 void *
 gal_options_parse_sizes_reverse(struct argp_option *option, char *arg,
-                                char *filename, size_t lineno, void *params);
+                                char *filename, size_t lineno,
+                                void *params);
 
 void *
 gal_options_parse_csv_float64(struct argp_option *option, char *arg,
@@ -326,11 +327,18 @@ gal_options_read_sigma_clip(struct argp_option *option, 
char *arg,
 
 void *
 gal_options_parse_name_and_strings(struct argp_option *option, char *arg,
-                                   char *filename, size_t lineno, void *junk);
+                                   char *filename, size_t lineno,
+                                   void *junk);
 
 void *
 gal_options_parse_name_and_float64s(struct argp_option *option, char *arg,
-                                    char *filename, size_t lineno, void *junk);
+                                    char *filename, size_t lineno,
+                                    void *junk);
+
+void *
+gal_options_parse_name_and_sizets(struct argp_option *option, char *arg,
+                                  char *filename, size_t lineno,
+                                  void *junk);
 
 gal_data_t *
 gal_options_parse_colon_sep_csv_raw(char *instring, char *filename,
diff --git a/lib/gnuastro/blank.h b/lib/gnuastro/blank.h
index d63852ed..5e851078 100644
--- a/lib/gnuastro/blank.h
+++ b/lib/gnuastro/blank.h
@@ -138,7 +138,8 @@ void
 gal_blank_remove_realloc(gal_data_t *input);
 
 gal_data_t *
-gal_blank_remove_rows(gal_data_t *columns, gal_list_sizet_t *column_indexs);
+gal_blank_remove_rows(gal_data_t *columns, gal_list_sizet_t *column_indexs,
+                      int onlydim0);
 
 __END_C_DECLS    /* From C++ preparations */
 
diff --git a/lib/gnuastro/list.h b/lib/gnuastro/list.h
index 9172e96c..cfa2d55c 100644
--- a/lib/gnuastro/list.h
+++ b/lib/gnuastro/list.h
@@ -231,6 +231,10 @@ gal_list_f64_reverse(gal_list_f64_t **list);
 double *
 gal_list_f64_to_array(gal_list_f64_t *list, int reverse, size_t *num);
 
+gal_data_t *
+gal_list_f64_to_data(gal_list_f64_t *list, uint8_t type,
+                     size_t minmapsize, int quietmmap);
+
 void
 gal_list_f64_free(gal_list_f64_t *list);
 
@@ -343,9 +347,15 @@ gal_list_data_add_alloc(gal_data_t **list, void *array, 
uint8_t type,
 gal_data_t *
 gal_list_data_pop(gal_data_t **list);
 
+void
+gal_list_data_remove(gal_data_t **list, gal_data_t *node);
+
 gal_data_t *
 gal_list_data_select_by_name(gal_data_t *list, char *name);
 
+gal_data_t *
+gal_list_data_select_by_id(gal_data_t *table, char *idstr, size_t *index);
+
 void
 gal_list_data_reverse(gal_data_t **list);
 
diff --git a/lib/gnuastro/permutation.h b/lib/gnuastro/permutation.h
index 555ccdd0..58bad780 100644
--- a/lib/gnuastro/permutation.h
+++ b/lib/gnuastro/permutation.h
@@ -61,6 +61,9 @@ gal_permutation_check(size_t *permutation, size_t size);
 void
 gal_permutation_apply(gal_data_t *input, size_t *permutation);
 
+void
+gal_permutation_apply_onlydim0(gal_data_t *input, size_t *permutation);
+
 void
 gal_permutation_apply_inverse(gal_data_t *input, size_t *permutation);
 
diff --git a/lib/gnuastro/table.h b/lib/gnuastro/table.h
index e9fc39d4..afb77772 100644
--- a/lib/gnuastro/table.h
+++ b/lib/gnuastro/table.h
@@ -60,8 +60,8 @@ __BEGIN_C_DECLS  /* From C++ preparations */
 #define GAL_TABLE_DEF_WIDTH_STR       6
 #define GAL_TABLE_DEF_WIDTH_INT       6
 #define GAL_TABLE_DEF_WIDTH_LINT      10
-#define GAL_TABLE_DEF_WIDTH_FLT       13
-#define GAL_TABLE_DEF_WIDTH_DBL       18
+#define GAL_TABLE_DEF_WIDTH_FLT       14
+#define GAL_TABLE_DEF_WIDTH_DBL       23
 
 #define GAL_TABLE_DEF_PRECISION_INT   0
 #define GAL_TABLE_DEF_PRECISION_FLT   6
@@ -181,7 +181,14 @@ gal_table_write_log(gal_data_t *logll, char 
*program_string,
 
 
 
+/************************************************************************/
+/***************            Column operation              ***************/
+/************************************************************************/
+gal_data_t *
+gal_table_col_vector_extract(gal_data_t *vector, gal_list_sizet_t *indexs);
 
+gal_data_t *
+gal_table_cols_to_vector(gal_data_t *list);
 
 __END_C_DECLS    /* From C++ preparations */
 
diff --git a/lib/gnuastro/txt.h b/lib/gnuastro/txt.h
index 91551966..2794648b 100644
--- a/lib/gnuastro/txt.h
+++ b/lib/gnuastro/txt.h
@@ -55,7 +55,7 @@ __BEGIN_C_DECLS  /* From C++ preparations */
 
 /* Macros.*/
 #define GAL_TXT_DELIMITERS     " ,\t\f\v"
-#define GAL_TXT_MAX_FMT_LENGTH 20
+#define GAL_TXT_MAX_FMT_LENGTH 35
 
 
 
@@ -108,7 +108,7 @@ gal_txt_stdin_read(long timeout_microsec);
 void
 gal_txt_write(gal_data_t *input, struct gal_fits_list_key_t **keylist,
               gal_list_str_t *comment, char *filename,
-              uint8_t colinfoinstdout);
+              uint8_t colinfoinstdout, int tab0_img1);
 
 
 
diff --git a/lib/list.c b/lib/list.c
index f7844374..39459d3e 100644
--- a/lib/list.c
+++ b/lib/list.c
@@ -921,6 +921,43 @@ gal_list_f64_to_array(gal_list_f64_t *list, int reverse, 
size_t *num)
 
 
 
+/* Copy a list of float64 to a 1D dataset of the desired type. */
+gal_data_t *
+gal_list_f64_to_data(gal_list_f64_t *list, uint8_t type,
+                     size_t minmapsize, int quietmmap)
+{
+  double *d;
+  gal_data_t *out;
+  size_t num, one=1;
+
+  /* In if the list is empty, return a dataset with no elements. */
+  if(list==NULL)
+    {
+      /* It is not possible to allocate a dataset with a size of 0 along
+         any dimension (in C it's possible, but conceptually it isn't). So,
+         we'll allocate space for one element, then free it. */
+      out=gal_data_alloc(NULL, type, 1, &one, NULL, 0,
+                         minmapsize, quietmmap, NULL, NULL, NULL);
+      out->size=out->dsize[0]=0;
+      free(out->array);
+      out->array=NULL;
+      return out;
+    }
+
+  /* Convert the list to an array, put it into a dataset. */
+  d=gal_list_f64_to_array(list, 0, &num);
+  out=gal_data_alloc(d, GAL_TYPE_FLOAT64, 1, &num, NULL, 0, minmapsize,
+                     quietmmap, NULL, NULL, NULL);
+
+  /* Copy to desired type and return. */
+  out=gal_data_copy_to_new_type_free(out, type);
+  return out;
+}
+
+
+
+
+
 void
 gal_list_f64_free(gal_list_f64_t *list)
 {
@@ -1384,7 +1421,6 @@ gal_list_data_add(gal_data_t **list, gal_data_t *newnode)
     /* Its not a list, so just set it to 'toadd'. */
     toadd=newnode;
 
-
   /* Set the next element of toadd and update what list points to.*/
   toadd->next=*list;
   *list=newnode;
@@ -1439,6 +1475,39 @@ gal_list_data_pop(gal_data_t **list)
 
 
 
+/* Remove one node from the list. */
+void
+gal_list_data_remove(gal_data_t **list, gal_data_t *node)
+{
+  int found=0;
+  gal_data_t *tmp, *prev=*list;
+
+  /* If this is an empty list, just ignore it. */
+  if(*list==NULL || node==NULL) return;
+
+  /* If the requested node is first. */
+  if(node==*list) { found=1; *list=(*list)->next; }
+  else
+    {
+      /* Parse the list, while keeping track of the previous. */
+      for(tmp=(*list)->next;tmp!=NULL;tmp=tmp->next)
+        {
+          /* This is the desired node, remove it from the list. */
+          if(tmp==node) { found=1; prev->next=tmp->next; break; }
+
+          /* Set the current pointer to the "prev" of the next. */
+          prev=tmp;
+        }
+    }
+
+  /* If 'node' has been identified, fully detach it from the list. */
+  if(found) node->next=NULL;
+}
+
+
+
+
+
 /* From the input list of datasets, return the first one that has a name
    equal to the input string 'name'. */
 gal_data_t *
@@ -1459,6 +1528,54 @@ gal_list_data_select_by_name(gal_data_t *list, char 
*name)
 
 
 
+/* Select a dataset from a list from its idenfier (name or counter in a
+   string). */
+gal_data_t *
+gal_list_data_select_by_id(gal_data_t *table, char *idstr, size_t *index)
+{
+  char *tailptr;
+  gal_data_t *tmp, *out=NULL;
+  size_t i, oind=GAL_BLANK_SIZE_T, colind;
+
+  /* If given string identifier ('idstr') is a number, then 'strtol' will
+     set 'tailptr' to '\0'. Otherwise, we will need to check the existing
+     column names in the table. */
+  colind=strtol(idstr, &tailptr, 10);
+  if(tailptr[0]=='\0') /* ID is a number. */
+    {
+      /* Parse the list and return the desired column. Note that the column
+         counter in the ID is assumed to be from 1, but the output "index"
+         should start from 0. If the requested counter is larger than the
+         input's number of columns, the output will automatically be NULL
+         (it has been initialized). */
+      i=0;
+      for(tmp=table;tmp!=NULL;tmp=tmp->next)
+        { ++i; if(i==colind) { oind=i-1; out=tmp; break;} }
+    }
+  else /* ID is string; parse the names in the table.*/
+    {
+      /* Parse the table and if the name exists, return it. */
+      colind=i=0;
+      for(tmp=table;tmp!=NULL;tmp=tmp->next)
+        {
+          ++i;
+          if( !strcmp(idstr, tmp->name) ) { oind=i-1; out=tmp; break; }
+        }
+    }
+
+  /* For a check.
+  printf("%s: %s is column index %zu\n", __func__, tmp->name, oind);
+  */
+
+  /* Fill the 'index' poiter (if it is not NULL!) and return 'out'. */
+  if(index) *index=oind;
+  return out;
+}
+
+
+
+
+
 void
 gal_list_data_reverse(gal_data_t **list)
 {
diff --git a/lib/options.c b/lib/options.c
index fd315c43..5ca449e6 100644
--- a/lib/options.c
+++ b/lib/options.c
@@ -232,9 +232,10 @@ gal_options_check_version(struct argp_option *option, char 
*arg,
         {
           /* Print an error message and abort.  */
           error_at_line(EXIT_FAILURE, 0, filename, lineno, "version "
-                        "mis-match: you are running GNU Astronomy Utilities "
-                        "(Gnuastro) version '%s'. However, the 'onlyversion' "
-                        "option is set to version '%s'.\n\n"
+                        "mis-match: you are running GNU Astronomy "
+                        "Utilities (Gnuastro) version '%s'. However, "
+                        "the 'onlyversion' option is set to version "
+                        "'%s'.\n\n"
                         "This was probably done for reproducibility. "
                         "Therefore, manually removing, or changing, the "
                         "option value might produce errors or unexpected "
@@ -246,12 +247,13 @@ gal_options_check_version(struct argp_option *option, 
char *arg,
                         "http://ftpmirror.gnu.org/gnuastro\n";
                         "    Alpha  (version format: X.Y.A-B):  "
                         "http://alpha.gnu.org/gnu/gnuastro\n\n";
-                        "Alternatively, you can clone Gnuastro, checkout the "
-                        "respective commit (from the version number), then "
-                        "bootstrap and build it. Please run the following "
-                        "command for more information:\n\n"
-                        "    $ info gnuastro \"Version controlled source\"\n",
-                        PACKAGE_VERSION, arg, arg);
+                        "Alternatively, you can clone Gnuastro, checkout "
+                        "the respective commit (from the version "
+                        "number), then bootstrap and build it. Please "
+                        "run the following command for more "
+                        "information:\n\n"
+                        "    $ info gnuastro \"Version controlled "
+                        "source\"\n", PACKAGE_VERSION, arg, arg);
 
           /* Just to avoid compiler warnings for unused variables. The program
              will never reach this point! */
@@ -518,8 +520,9 @@ gal_options_read_searchin(struct argp_option *option, char 
*arg,
       if((*(uint8_t *)(option->value)=gal_tableintern_string_to_searchin(arg))
          == GAL_TABLE_SEARCH_INVALID )
         error_at_line(EXIT_FAILURE, 0, filename, lineno, "'%s' (value to "
-                      "'%s' option) couldn't be recognized as a known table "
-                      "search-in field ('name', 'unit', or 'comment').\n\n"
+                      "'%s' option) couldn't be recognized as a known "
+                      "table search-in field ('name', 'unit', or "
+                      "'comment').\n\n"
                       "For more explanation, please run the following "
                       "command (press SPACE key to go down, and 'q' to "
                       "return to the command-line):\n\n"
@@ -548,9 +551,11 @@ gal_options_read_wcslinearmatrix(struct argp_option 
*option, char *arg,
       value=*(uint8_t *)(option->value);
       switch(value)
         {
-        case GAL_WCS_LINEAR_MATRIX_PC: gal_checkset_allocate_copy("pc", &str);
+        case GAL_WCS_LINEAR_MATRIX_PC: gal_checkset_allocate_copy("pc",
+                                                                  &str);
           break;
-        case GAL_WCS_LINEAR_MATRIX_CD: gal_checkset_allocate_copy("cd", &str);
+        case GAL_WCS_LINEAR_MATRIX_CD: gal_checkset_allocate_copy("cd",
+                                                                  &str);
           break;
         default:
           error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at '%s' "
@@ -594,7 +599,8 @@ gal_options_read_tableformat(struct argp_option *option, 
char *arg,
       /* Note that 'gal_data_type_as_string' returns a static string. But
          the output must be an allocated string so we can free it. */
       gal_checkset_allocate_copy(
-        gal_tableintern_format_as_string( *(uint8_t *)(option->value)), &str);
+        gal_tableintern_format_as_string( *(uint8_t *)(option->value)),
+        &str);
       return str;
     }
   else
@@ -606,8 +612,8 @@ gal_options_read_tableformat(struct argp_option *option, 
char *arg,
       if( (*(uint8_t *)(option->value)=gal_tableintern_string_to_format(arg) )
           ==GAL_TABLE_FORMAT_INVALID )
         error_at_line(EXIT_FAILURE, 0, filename, lineno, "'%s' (value to "
-                      "'%s' option) couldn't be recognized as a known table "
-                      "format field ('txt', 'fits-ascii', or "
+                      "'%s' option) couldn't be recognized as a known "
+                      "table format field ('txt', 'fits-ascii', or "
                       "'fits-binary').\n\n", arg, option->name);
 
       /* For no un-used variable warning. This function doesn't need the
@@ -698,7 +704,8 @@ gal_options_read_interpmetric(struct argp_option *option, 
char *arg,
 
       ':0.123,4.567'    which is part of    '1.2345,6.789:0.123,4.567' */
 static double
-gal_options_read_sexagesimal(size_t dim, char *str, char **tailptr)
+gal_options_read_sexagesimal(size_t dim, char *str, char **tailptr,
+                             int abort)
 {
   double out;
   char *c, *cc, *copy;
@@ -707,13 +714,16 @@ gal_options_read_sexagesimal(size_t dim, char *str, char 
**tailptr)
 
   /* A small sanity check. */
   if(dim>1)
-    error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at '%s', "
-          "to find the cause of the problem. The value of 'dim' at "
-          "this point should be 0 or 1, but it has a value of %zu",
-          __func__, PACKAGE_BUGREPORT, dim);
+    {
+      if(abort)
+        error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at '%s', "
+              "to find the cause of the problem. The value of 'dim' at "
+              "this point should be 0 or 1, but it has a value of %zu",
+              __func__, PACKAGE_BUGREPORT, dim);
+      else return NAN;
+    }
 
-  /* Parse the start of this string, until you find find exactly what it
-     is. */
+  /* Parse the start of this string, until you find exactly what it is. */
   c=str;
   while( *c!='\0' && ishd==0 && iscolon==0 )
     switch(*c++)
@@ -750,14 +760,22 @@ gal_options_read_sexagesimal(size_t dim, char *str, char 
**tailptr)
 
   /* Make sure the string could be identified properly */
   if( (isra==0 && isdec==0) || (ishd==0 && iscolon==0) )
-    error(EXIT_FAILURE, 0, "the first token in the string '%s' "
-          "couldn't be parsed as a sexagesimal string", str);
+    {
+      if(abort)
+        error(EXIT_FAILURE, 0, "the first token in the string '%s' "
+              "couldn't be parsed as a sexagesimal string", str);
+      else return NAN;
+    }
 
   /* Check if the hd-type is given for the proper dimension. */
   if( (isra && dim!=0) || (isdec && dim!=1) )
-    error(EXIT_FAILURE, 0, "the order of sexagesimal coordinates "
-          "is wrong! The first should be RA (with a format like "
-          "'__h__m__'), and the second should be Dec ('__d__m__')");
+    {
+      if(abort)
+        error(EXIT_FAILURE, 0, "the order of sexagesimal coordinates "
+              "is wrong! The first should be RA (with a format like "
+              "'__h__m__'), and the second should be Dec ('__d__m__')");
+      else return NAN;
+    }
 
   /* Depending on the mode, find the length of the full string into a new
      string to give to the unit conversion functions. Note that the
@@ -797,7 +815,7 @@ gal_options_read_sexagesimal(size_t dim, char *str, char 
**tailptr)
   *tailptr=str+stlen-1;
 
   /* Sanity check: */
-  if(isnan(out))
+  if(abort && isnan(out))
     error(EXIT_FAILURE, 0, "%s: '%s' couldn't be parsed as a "
           "sexagesimal representation of %s", __func__, copy,
           isra?"RA (right ascension)":"DEC (Declination)");
@@ -817,12 +835,13 @@ gal_options_read_sexagesimal(size_t dim, char *str, char 
**tailptr)
    given values in 'double' type. You can read the number from its 'size'
    element. */
 gal_data_t *
-gal_options_parse_list_of_numbers(char *string, char *filename, size_t lineno)
+gal_options_parse_list_of_numbers(char *string, char *filename,
+                                  size_t lineno, uint8_t type)
 {
-  size_t i, num=0;
+  size_t num=0;
   gal_data_t *out;
   char *c=string, *tailptr;
-  gal_list_f64_t *list=NULL, *tdll;
+  gal_list_f64_t *list=NULL;
   double numerator=NAN, denominator=NAN, tmp, ttmp;
 
   /* The nature of the arrays/numbers read here is very small, so since
@@ -886,7 +905,7 @@ gal_options_parse_list_of_numbers(char *string, char 
*filename, size_t lineno)
             {
               /* See if the user has given a sexagesimal value (that can't
                  be easily read with 'strtod'). */
-              ttmp=gal_options_read_sexagesimal(num%2, c, &tailptr);
+              ttmp=gal_options_read_sexagesimal(num%2, c, &tailptr, 0);
               if(isnan(ttmp))
                 {
                   /* This happens in cases like the values to Table's
@@ -894,9 +913,10 @@ gal_options_parse_list_of_numbers(char *string, char 
*filename, size_t lineno)
                      colon, but don't have a sexagesimal number. So we
                      shouldn't abort the program! */
                   if(tailptr[0]!=':')
-                    error_at_line(EXIT_FAILURE, 0, filename, lineno, "the "
-                                  "'%s' component of '%s' couldn't be parsed "
-                                  "as a usable number", c, string);
+                    error_at_line(EXIT_FAILURE, 0, filename, lineno,
+                                  "the '%s' component of '%s' couldn't "
+                                  "be parsed as a usable number", c,
+                                  string);
                 }
               else tmp=ttmp;
             }
@@ -907,8 +927,9 @@ gal_options_parse_list_of_numbers(char *string, char 
*filename, size_t lineno)
           else
             {
               if(isnan(denominator)) denominator=tmp;
-              else error_at_line(EXIT_FAILURE, 0, filename, lineno, "more "
-                                 "than two numbers in each element.");
+              else error_at_line(EXIT_FAILURE, 0, filename, lineno,
+                                 "more than two numbers in each "
+                                 "element.");
             }
 
           /* Set 'c' to tailptr. */
@@ -926,28 +947,9 @@ gal_options_parse_list_of_numbers(char *string, char 
*filename, size_t lineno)
                        ? numerator : numerator/denominator);
     }
 
-  /* Allocate the output data structure and fill it up. */
-  if(num)
-    {
-      i=num;
-      out=gal_data_alloc(NULL, GAL_TYPE_FLOAT64, 1, &num, NULL, 0,
-                         minmapsize, quietmmap, NULL, NULL, NULL);
-      for(tdll=list;tdll!=NULL;tdll=tdll->next)
-        ((double *)(out->array))[--i]=tdll->v;
-    }
-  else
-    {
-      /* It is not possible to allocate a dataset with a size of 0 along
-         any dimension (in C it's possible, but conceptually it isn't). So,
-         we'll allocate space for one element, then free it. */
-      i=1;
-      out=gal_data_alloc(NULL, GAL_TYPE_FLOAT64, 1, &i, NULL, 0,
-                         minmapsize, quietmmap, NULL, NULL, NULL);
-      out->size=out->dsize[0]=0;
-      free(out->array);
-      out->array=NULL;
-    }
-
+  /* Convert the list into the desired type. */
+  gal_list_f64_reverse(&list);
+  out=gal_list_f64_to_data(list, type, minmapsize, quietmmap);
 
   /* Clean up and return. */
   gal_list_f64_free(list);
@@ -1056,7 +1058,7 @@ gal_options_parse_csv_strings_raw(char *string, char 
*filename,
 {
   size_t i, num;
   gal_data_t *out;
-  char *c=string, *str=NULL;
+  char *c=string, *cc, *str=NULL;
   gal_list_str_t *list=NULL, *tstrll=NULL;
 
 
@@ -1074,13 +1076,30 @@ gal_options_parse_csv_strings_raw(char *string, char 
*filename,
         {
         /* Comma marks the transition to the next string. */
         case ',':
+
+          /* The whole can't start with a comma. */
           if(str==NULL)
-            error_at_line(EXIT_FAILURE, 0, filename, lineno, "a string "
-                          "must exist before the first ','. You have "
-                          "given: '%s'", string);
-          *c='\0';
-          gal_list_str_add(&list, str, 1);
-          str=NULL;  /* Mark that the next character is the start */
+            {
+              if(filename)
+                error_at_line(EXIT_FAILURE, 0, filename, lineno, "a "
+                              "string must exist before the first ','. "
+                              "You have given: '%s'", string);
+              else
+                error(EXIT_FAILURE, 0, "a string must exist before the "
+                      "first ','. You have given: '%s'", string);
+            }
+
+          /* If the previous character was a '\', this coma isn't a
+             delimiter, but is actually within the string. So shift all the
+             characters in the string one backward to remove the
+             backslah and ignore the comma as a delimiter. */
+          if(*(c-1)=='\\') for(cc=c-1;*cc!='\0';++cc) *cc=*(cc+1);
+          else
+            {
+              *c='\0';
+              gal_list_str_add(&list, str, 1);
+              str=NULL;  /* Mark that the next character is the start */
+            }
           break;
 
         /* If the character isn't a coma, it is either in the middle of a
@@ -1330,7 +1349,8 @@ gal_options_parse_sizes_reverse(struct argp_option 
*option, char *arg,
                       "given to '--%s'", option->name);
 
       /* Read the values. */
-      values=gal_options_parse_list_of_numbers(arg, filename, lineno);
+      values=gal_options_parse_list_of_numbers(arg, filename, lineno,
+                                               GAL_TYPE_FLOAT64);
 
       /* Check if the values are an integer. */
       v=values->array;
@@ -1417,7 +1437,8 @@ gal_options_parse_csv_float64(struct argp_option *option, 
char *arg,
                       "given to '--%s'", option->name);
 
       /* Read the values. */
-      values=gal_options_parse_list_of_numbers(arg, filename, lineno);
+      values=gal_options_parse_list_of_numbers(arg, filename, lineno,
+                                               GAL_TYPE_FLOAT64);
 
       /* Put the values into the option. */
       *(gal_data_t **)(option->value) = values;
@@ -1453,7 +1474,8 @@ gal_options_read_sigma_clip(struct argp_option *option, 
char *arg,
     }
 
   /* Caller wants to read the values into memory, so parse the inputs. */
-  in=gal_options_parse_list_of_numbers(arg, filename, lineno);
+  in=gal_options_parse_list_of_numbers(arg, filename, lineno,
+                                       GAL_TYPE_FLOAT64);
 
   /* Check if there was only two numbers. */
   if(in->size!=2)
@@ -1514,10 +1536,10 @@ gal_options_read_sigma_clip(struct argp_option *option, 
char *arg,
 static void *
 gal_options_parse_name_and_values(struct argp_option *option, char *arg,
                                   char *filename, size_t lineno, void *junk,
-                                  int str0_f641)
+                                  int str0_f641_sz2)
 {
-  size_t i, nc;
   double *darray=NULL;
+  size_t i, nc, *sizarr=NULL;
   gal_data_t *tmp, *existing, *dataset;
   char *c, *name, *values, **strarr=NULL;
   char *str, sstr[GAL_OPTIONS_STATIC_MEM_FOR_VALUES];
@@ -1527,8 +1549,17 @@ gal_options_parse_name_and_values(struct argp_option 
*option, char *arg,
     {
       /* Set the value pointer to 'existing'. */
       existing=*(gal_data_t **)(option->value);
-      if(str0_f641) darray = existing->array;
-      else          strarr = existing->array;
+      switch(str0_f641_sz2)
+        {
+        case 0: strarr = existing->array; break;
+        case 1: darray = existing->array; break;
+        case 2: sizarr = existing->array; break;
+        default:
+          error(EXIT_FAILURE, 0, "%s: a bug! please contact us at '%s' "
+                "to fix the problem. The code '%d' isn't acceptable for "
+                "'str0_f641_si2'", __func__, PACKAGE_BUGREPORT,
+                str0_f641_sz2);
+        }
 
       /* First write the name. */
       nc=0;
@@ -1543,8 +1574,12 @@ gal_options_parse_name_and_values(struct argp_option 
*option, char *arg,
                   "necessary characters in the statically allocated "
                   "string has become too close to %d", __func__,
                   PACKAGE_BUGREPORT, GAL_OPTIONS_STATIC_MEM_FOR_VALUES);
-          if(str0_f641) nc += sprintf(sstr+nc, "%g,", darray[i]);
-          else          nc += sprintf(sstr+nc, "%s,", strarr[i]);
+          switch(str0_f641_sz2)
+            {
+            case 0: nc += sprintf(sstr+nc, "%s,",  strarr[i]);
+            case 1: nc += sprintf(sstr+nc, "%g,",  darray[i]);
+            case 2: nc += sprintf(sstr+nc, "%zu,", sizarr[i]);
+            } /* No default necessary: valid value confirmed above. */
         }
 
       /* Finish the string. */
@@ -1573,11 +1608,28 @@ gal_options_parse_name_and_values(struct argp_option 
*option, char *arg,
       gal_checkset_allocate_copy(arg, &name);
 
       /* Read the values. */
-      dataset=( str0_f641
-                ? gal_options_parse_list_of_numbers(values, filename,
-                                                    lineno)
-                : gal_options_parse_list_of_strings(values, filename,
-                                                    lineno));
+      switch(str0_f641_sz2)
+        {
+        case 0:
+          dataset=gal_options_parse_list_of_strings(values, filename,
+                                                    lineno);
+          break;
+        case 1:
+          dataset=gal_options_parse_list_of_numbers(values, filename,
+                                                    lineno,
+                                                    GAL_TYPE_FLOAT64);
+          break;
+        case 2:
+          dataset=gal_options_parse_list_of_numbers(values, filename,
+                                                    lineno,
+                                                    GAL_TYPE_SIZE_T);
+          break;
+        default:
+          error(EXIT_FAILURE, 0, "%s: a bug! please contact us at '%s' "
+                "to fix the problem. The code '%d' isn't acceptable for "
+                "'str0_f641_si2'", __func__, PACKAGE_BUGREPORT,
+                str0_f641_sz2);
+        }
 
       /* If there actually was a string of numbers, add the dataset to the
          rest. */
@@ -1606,7 +1658,10 @@ gal_options_parse_name_and_values(struct argp_option 
*option, char *arg,
         error(EXIT_FAILURE, 0, "'--%s' requires a series of %s "
               "(separated by ',' or ':') following its first argument, "
               "please run with '--help' for more information",
-              option->name, str0_f641?"numbers":"strings");
+              option->name,
+              ( str0_f641_sz2
+                ? (str0_f641_sz2==1?"numbers":"integers")
+                : "strings" ));
 
       /* Our job is done, return NULL. */
       return NULL;
@@ -1619,7 +1674,8 @@ gal_options_parse_name_and_values(struct argp_option 
*option, char *arg,
 
 void *
 gal_options_parse_name_and_strings(struct argp_option *option, char *arg,
-                                   char *filename, size_t lineno, void *junk)
+                                   char *filename, size_t lineno,
+                                   void *junk)
 {
   return gal_options_parse_name_and_values(option, arg, filename, lineno,
                                            junk, 0);
@@ -1631,7 +1687,8 @@ gal_options_parse_name_and_strings(struct argp_option 
*option, char *arg,
 
 void *
 gal_options_parse_name_and_float64s(struct argp_option *option, char *arg,
-                                    char *filename, size_t lineno, void *junk)
+                                    char *filename, size_t lineno,
+                                    void *junk)
 {
   return gal_options_parse_name_and_values(option, arg, filename, lineno,
                                            junk, 1);
@@ -1641,6 +1698,19 @@ gal_options_parse_name_and_float64s(struct argp_option 
*option, char *arg,
 
 
 
+void *
+gal_options_parse_name_and_sizets(struct argp_option *option, char *arg,
+                                  char *filename, size_t lineno,
+                                  void *junk)
+{
+  return gal_options_parse_name_and_values(option, arg, filename, lineno,
+                                           junk, 2);
+}
+
+
+
+
+
 /* Parse strings like this: 'num1,num2:num3,n4:num5,num6' and return it as
    a data container array: all elements are simply placed after each other
    in the array. */
@@ -1720,7 +1790,7 @@ gal_options_parse_colon_sep_csv_raw(char *instring, char 
*filename,
              'tailptr' is either ',' or '\0'. */
           sread=NAN;
           if(*tailptr!=',' && *tailptr!='\0')
-            sread=gal_options_read_sexagesimal(dim, pt, &tailptr);
+            sread=gal_options_read_sexagesimal(dim, pt, &tailptr, 0);
           if(!isnan(sread)) read=sread;
 
           /* Add the read coordinate to the list of coordinates. */
diff --git a/lib/permutation.c b/lib/permutation.c
index 61278a83..ed74c077 100644
--- a/lib/permutation.c
+++ b/lib/permutation.c
@@ -88,12 +88,19 @@ gal_permutation_check(size_t *permutation, size_t size)
       permute:    OUT[ i       ]   =   IN[ perm[i] ]     i = 0 .. N-1
       inverse:    OUT[ perm[i] ]   =   IN[ i       ]     i = 0 .. N-1
 */
-void
-gal_permutation_apply(gal_data_t *input, size_t *permutation)
+static void
+permutation_apply_raw(gal_data_t *input, size_t *permutation,
+                      int onlydim0)
 {
   void *tmp;
-  size_t i, k, pk, width;
   uint8_t *array=input->array;
+  size_t i, k, pk, winc, width, size, increment;
+
+  /* If 'onlydim0' is given and the input has more than one dimension, we
+     need to permute less (only along the 0th dimension). */
+  if(onlydim0 && input->ndim>1)
+    { size=input->dsize[0]; increment=input->size/size; }
+  else { size=input->size; increment=1; }
 
   /* If permutation is NULL, then it is assumed that the data doesn't need
      to be re-ordered. */
@@ -101,10 +108,11 @@ gal_permutation_apply(gal_data_t *input, size_t 
*permutation)
     {
       /* Necessary initializations. */
       width=gal_type_sizeof(input->type);
-      tmp=gal_pointer_allocate(input->type, 1, 0, __func__, "tmp");
+      tmp=gal_pointer_allocate(input->type, increment, 0, __func__, "tmp");
 
       /* Do the permutation. */
-      for(i=0;i<input->size;++i)
+      winc=width*increment;
+      for(i=0;i<size;++i)
         {
           k=permutation[i];
 
@@ -115,16 +123,16 @@ gal_permutation_apply(gal_data_t *input, size_t 
*permutation)
               pk = permutation[k];
               if( pk != i )
                 {
-                  memcpy(tmp, &array[i*width], width);
+                  memcpy(tmp, &array[i*winc], winc);
 
                   while(pk!=i)
                     {
-                      memcpy(&array[k*width], &array[pk*width], width);
+                      memcpy(&array[k*winc], &array[pk*winc], winc);
                       k  = pk;
                       pk = permutation[k];
                     }
 
-                  memcpy(&array[k*width], tmp, width);
+                  memcpy(&array[k*winc], tmp, winc);
                 }
             }
         }
@@ -188,3 +196,15 @@ gal_permutation_apply_inverse(gal_data_t *input, size_t 
*permutation)
       free(ttmp);
     }
 }
+
+
+
+
+
+void
+gal_permutation_apply(gal_data_t *input, size_t *permutation)
+{ permutation_apply_raw(input, permutation, 0); }
+
+void
+gal_permutation_apply_onlydim0(gal_data_t *input, size_t *permutation)
+{ permutation_apply_raw(input, permutation, 1); }
diff --git a/lib/table.c b/lib/table.c
index ae5e035a..ba5c00e0 100644
--- a/lib/table.c
+++ b/lib/table.c
@@ -33,6 +33,7 @@ along with Gnuastro. If not, see 
<http://www.gnu.org/licenses/>.
 #include <gnuastro/txt.h>
 #include <gnuastro/blank.h>
 #include <gnuastro/table.h>
+#include <gnuastro/pointer.h>
 
 #include <gnuastro-internal/timing.h>
 #include <gnuastro-internal/checkset.h>
@@ -95,38 +96,10 @@ gal_table_displayflt_to_str(uint8_t fmt)
 /* Store the information of each column in a table (either as a text file
    or as a FITS table) into an array of data structures with 'numcols'
    structures (one data structure for each column). The number of rows is
-   stored in 'numrows'. The type of the table (e.g., ascii text file, or
-   FITS binary or ASCII table) will be put in 'tableformat' (macros defined
-   in 'gnuastro/table.h'.
-
-   Note that other than the character strings (column name, units and
-   comments), nothing in the data structure(s) will be allocated by this
-   function for the actual data (e.g., the 'array' or 'dsize' elements).
-
-   Here are the gal_data_t structure elements that are used in 'allcols':
-
-            *restrict array -> Blank value (if present).
-                       type -> Type of column data.
-                       ndim -> Blank number of dimensions (1)
-                     *dsize -> Blank dimension lengths (1)
-                       size -> Blank total size (1)
-                  quietmmap -> ------------
-                  *mmapname -> ------------
-                 minmapsize -> Repeat (FITS Binary 'TFORM')
-                       nwcs -> ------------
-                       *wcs -> ------------
-                       flag -> 'GAL_TABLEINTERN_FLAG_*' macros.
-                     status -> ------------
-                      *name -> Column name.
-                      *unit -> Column unit.
-                   *comment -> Column comments.
-                   disp_fmt -> 'GAL_TABLE_DISPLAY_FMT' macros.
-                 disp_width -> To keep width of string columns.
-             disp_precision -> ------------
-                      *next -> ------------
-                     *block -> ------------
-
-*/
+   stored in 'numrows'.\
+
+   See the DESCRIPTION OF THIS FUNCTION IN THE BOOK FOR a detailed listing
+   of the output's elements. */
 gal_data_t *
 gal_table_info(char *filename, char *hdu, gal_list_str_t *lines,
                size_t *numcols, size_t *numrows, int *tableformat)
@@ -141,9 +114,9 @@ gal_table_info(char *filename, char *hdu, gal_list_str_t 
*lines,
     }
 
   /* Abort with an error if we get to this point. */
-  error(EXIT_FAILURE, 0, "%s: a bug! please contact us at %s so we can fix "
-        "the problem. Control must not have reached the end of this function",
-        __func__, PACKAGE_BUGREPORT);
+  error(EXIT_FAILURE, 0, "%s: a bug! please contact us at %s so we can "
+        "fix the problem. Control must not have reached the end of this "
+        "function", __func__, PACKAGE_BUGREPORT);
   return NULL;
 }
 
@@ -182,7 +155,7 @@ gal_table_print_info(gal_data_t *allcols, size_t numcols, 
size_t numrows)
       mms=allcols[i].minmapsize;
       twt=strlen(gal_type_name(allcols[i].type, 1));
       if(allcols[i].type!=GAL_TYPE_STRING && mms>1)
-        twt += (int)(log10(mms))+1+2; /* 1 for the log, 2 for '[]'. */
+        twt += (int)(log10(mms))+1+2; /* 1 for the log, 2 for '()'. */
       if(allcols[i].type && twt>tw) tw=twt;
     }
 
@@ -210,7 +183,7 @@ gal_table_print_info(gal_data_t *allcols, size_t numcols, 
size_t numrows)
       mms=allcols[i].minmapsize;
       if(allcols[i].type!=GAL_TYPE_STRING && mms>1)
         {
-          if( asprintf(&typestr, "%s[%zu]",
+          if( asprintf(&typestr, "%s(%zu)",
                        gal_type_name(allcols[i].type, 1), mms)<0 )
             error(EXIT_FAILURE, 0, "%s: 'astprintf' allocation", __func__);
         }
@@ -344,8 +317,8 @@ gal_table_list_of_indexs(gal_list_str_t *cols, gal_data_t 
*allcols,
             errno=0;
             regex=malloc(sizeof *regex);
             if(regex==NULL)
-              error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for regex",
-                    __func__, sizeof *regex);
+              error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for "
+                    "regex", __func__, sizeof *regex);
 
             /* First we have to "compile" the string into the regular
                expression, see the "POSIX Regular Expression Compilation"
@@ -585,8 +558,8 @@ gal_table_read(char *filename, char *hdu, gal_list_str_t 
*lines,
    comments field. Note that the 'comments' has to be already sorted in the
    proper order. */
 void
-gal_table_comments_add_intro(gal_list_str_t **comments, char *program_string,
-                             time_t *rawtime)
+gal_table_comments_add_intro(gal_list_str_t **comments,
+                             char *program_string, time_t *rawtime)
 {
   char gitdescribe[100], *tmp;
 
@@ -632,11 +605,12 @@ gal_table_write(gal_data_t *cols, struct 
gal_fits_list_key_t **keylist,
         gal_fits_tab_write(cols, comments, tableformat, filename, extname,
                            keylist);
       else
-        gal_txt_write(cols, keylist, comments, filename, colinfoinstdout);
+        gal_txt_write(cols, keylist, comments, filename,
+                      colinfoinstdout, 0);
     }
   else
     /* Write to standard output. */
-    gal_txt_write(cols, keylist, comments, filename, colinfoinstdout);
+    gal_txt_write(cols, keylist, comments, filename, colinfoinstdout, 0);
 }
 
 
@@ -666,3 +640,137 @@ gal_table_write_log(gal_data_t *logll, char 
*program_string,
       free(msg);
     }
 }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/************************************************************************/
+/***************            Column operation              ***************/
+/************************************************************************/
+gal_data_t *
+gal_table_col_vector_extract(gal_data_t *vector, gal_list_sizet_t *indexs)
+{
+  uint8_t type;
+  gal_data_t *out=NULL;
+  size_t i, vw, vh, ind;
+  char *name, *basename;
+  gal_list_sizet_t *tind;
+
+  /* Basic sanity checks. */
+  if(vector==NULL) return NULL;
+  if(indexs==NULL) return NULL;
+  if(vector->ndim!=2)
+    error(EXIT_FAILURE, 0, "%s: the input 'vector' must have 2 "
+          "dimensions but has %zu dimensions", __func__, vector->ndim);
+  for(tind=indexs;tind!=NULL;tind=tind->next)
+    if(tind->v > vector->dsize[1])
+      error(EXIT_FAILURE, 0, "%s: the input vector has %zu elements but "
+            "you have asked for index %zu (counting from zero)", __func__,
+            vector->dsize[1], tind->v);
+
+  /* Allocate the output columns. */
+  type=vector->type;
+  vh=vector->dsize[0];
+  vw=vector->dsize[1];
+  for(tind=indexs;tind!=NULL;tind=tind->next)
+    {
+      /* If the vector has a name, add a counter after it. If it doesn't
+         have a name, just use 'VECTOR'. Because index couting starts from
+         0, but column counters in tables start with 1, we'll add one to
+         the index. */
+      ind=tind->v;
+      basename = vector->name ? vector->name : "VECTOR";
+      if( asprintf(&name, "%s-%zu", basename, ind+1)<0 )
+        error(EXIT_FAILURE, 0, "%s: asprintf alloc of 'name'",  __func__);
+
+      /* Allocate the output and fill it. */
+      gal_list_data_add_alloc(&out, NULL, type, 1, &vh, NULL, 1,
+                              vector->minmapsize, vector->quietmmap,
+                              name, vector->unit, vector->comment);
+      for(i=0;i<vh;++i)
+        memcpy(gal_pointer_increment(out->array,    i,        type),
+               gal_pointer_increment(vector->array, i*vw+ind, type),
+               gal_type_sizeof(type));
+
+      /* Clean up. */
+      free(name);
+    }
+
+  /* Reverse the list to be in the same order as the indexs, and return. */
+  gal_list_data_reverse(&out);
+  return out;
+}
+
+
+
+
+
+gal_data_t *
+gal_table_cols_to_vector(gal_data_t *list)
+{
+  gal_data_t *tmp, *out;
+  char *name, *unit=NULL, *inname=NULL;
+  size_t i, j, dsize[2], num=gal_list_data_number(list);
+
+  /* If the list is empty or just has a single column, return itself.*/
+  if(num<2) return list;
+
+  /* Go over the inputs na make sure they are all single dimensional and
+     have the same size. */
+  for(tmp=list;tmp!=NULL;tmp=tmp->next)
+    {
+      /* First, a sanity check. */
+      if(tmp->ndim!=1
+         || tmp->type!=list->type
+         || tmp->dsize[0]!=list->dsize[0])
+        error(EXIT_FAILURE, 0, "%s: inputs should all be single-valued "
+              "columns (one dimension) and have the same size and type",
+              __func__);
+
+      /* Find the first one with a name. */
+      if(tmp->unit && unit==NULL)   unit=tmp->unit;
+      if(tmp->name && inname==NULL) inname=tmp->name;
+    }
+
+  /* Set the name based on the input. */
+  if( asprintf(&name, "%s-VECTOR", inname?inname:"TO")<0 )
+    error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
+
+  /* Allocate the output dataset. */
+  dsize[1]=num;
+  dsize[0]=list->size;
+  out=gal_data_alloc(NULL, list->type, 2, dsize, NULL, 0, list->minmapsize,
+                     list->quietmmap, name, unit,
+                     "Vector by merging multiple columns.");
+
+  /* Fill the output dataset. */
+  j=0;
+  for(tmp=list;tmp!=NULL;tmp=tmp->next)
+    {
+      for(i=0;i<tmp->dsize[0];++i)
+        memcpy(gal_pointer_increment(out->array, i*num+j, out->type),
+               gal_pointer_increment(tmp->array, i,       out->type),
+               gal_type_sizeof(out->type));
+      ++j;
+    }
+
+  /* Clean up and return. */
+  free(name);
+  return out;
+}
diff --git a/lib/tableintern.c b/lib/tableintern.c
index 05632ad0..c5d67383 100644
--- a/lib/tableintern.c
+++ b/lib/tableintern.c
@@ -373,8 +373,8 @@ gal_tableintern_col_print_info(gal_data_t *col, int 
tableformat,
           case GAL_TABLE_DISPLAY_FMT_FIXED:   fmt[0]='f'; break;
           case GAL_TABLE_DISPLAY_FMT_EXP:     fmt[0]='e'; break;
           case GAL_TABLE_DISPLAY_FMT_GENERAL: fmt[0]='g'; break;
-          default: /* '%f' is the most conservative in plain-text tables. */
-            fmt[0] = 'f'; break;
+          default:  /* '%e' is the most conservative in plain-text:  */
+            fmt[0] = 'e'; break;  /* it is independent of the power. */
           }
 
       /* Set the width and precision */
diff --git a/lib/txt.c b/lib/txt.c
index b6accb2d..a1bf2c6e 100644
--- a/lib/txt.c
+++ b/lib/txt.c
@@ -145,17 +145,56 @@ gal_txt_contains_string(char *full, char *match)
 
 
 
+/* Read the vector type and number of elements. */
+static int
+txt_info_vector_type(char *string, size_t *repeat)
+{
+  int type, irepeat, *irptr=&irepeat;
+  void **iptr=(void **)(&irptr);
+  char *c, *rstr=NULL;
+
+  /* See if there is a '(' in the string: we already know that there was a
+     ')' at the end of the string before entring this function. After this
+     step:
+        string --> name of type
+        rstr   --> string of number of repeats. */
+  for(c=string; *c!='\0'; ++c)
+    switch(*c)
+      {
+      case '(': *c='\0'; rstr=c+1; break;
+      case ')': *c='\0';           break;
+      }
+
+  /* Read the "repeat" element, if it wasn't set (there was no opening
+     parenthesis), or the value in the parenthesis can't be read as an
+     integer number, or the given integer was negative, just ignore the
+     repeat (by setting it to 1). */
+  *repeat = ( ( rstr
+                && gal_type_from_string(iptr, rstr, GAL_TYPE_INT)==0
+                && irepeat>1 )
+              ? irepeat
+              : 1 );
+
+  /* Read the type. */
+  type=gal_type_from_name(string);
+  if(type==GAL_TYPE_INVALID) type=GAL_TYPE_FLOAT64;
+  return type;
+}
+
+
+
+
 
 /* Each information comment should have a format like this (replace
    'Column' with 'Image' for 2D arrays):
 
-      # Column N: NAME [UNITS, TYPE, BLANK] COMMENT
+      # Column N: NAME [UNITS, TYPE(REPEAT), BLANK] COMMENT
 
-  TYPE has pre-defined values, and N must be an integer, but the rest can
-  contain any characters (including whitespace characters). The UNITS,
-  TYPE, BLANK tokens are optional, if not given, default values will be
-  set. But if there are comments, then the brackets themselves are required
-  to separate the name from the comments.
+  'TYPE' has pre-defined values, and 'N' and 'REPEAT' must be an integer,
+  but the rest can contain any characters (including whitespace
+  characters). The UNITS, TYPE, BLANK tokens are optional, if not given,
+  default values will be set. But if there are comments, then the brackets
+  themselves are required to separate the name from the comments.
 
   Any white space characters before or after the delimiters (':', '[', ']',
   ',') is ignored, but spaces within the values are kept. For example, in
@@ -183,9 +222,9 @@ txt_info_from_comment(char *in_line, gal_data_t **datall, 
char *comm_start,
 {
   gal_data_t *tmp;
   int index, strw=0;
-  char *line, *aline, *tailptr;
-  size_t len=strlen(comm_start);
   int type=GAL_TYPE_FLOAT64;                     /* Default type. */
+  char *line, *aline, *tailptr;
+  size_t len=strlen(comm_start), repeat;
   char *number=NULL, *name=NULL, *comment=NULL;
   char *inbrackets=NULL, *unit=NULL, *typestr=NULL, *blank=NULL;
 
@@ -216,11 +255,13 @@ txt_info_from_comment(char *in_line, gal_data_t **datall, 
char *comm_start,
               break;
 
             case '[':
-              if(name && inbrackets==NULL) { *line='\0'; inbrackets=line+1; }
+              if(name && inbrackets==NULL)
+                { *line='\0'; inbrackets=line+1; }
               break;
 
             case ']':
-              if(inbrackets && comment==NULL) { *line='\0'; comment=line+1; }
+              if(inbrackets && comment==NULL)
+                { *line='\0'; comment=line+1; }
               break;
 
             case '\n':
@@ -275,6 +316,7 @@ txt_info_from_comment(char *in_line, gal_data_t **datall, 
char *comm_start,
          definitions above). Just note that if we are dealing with the
          string type, we have to pull out the number part first. If there
          is no number for a string type, then ignore the line. */
+      repeat=1; /* Initialize for each column. */
       if(typestr && *typestr!='\0')
         {
           typestr=gal_txt_trim_space(typestr);
@@ -288,19 +330,29 @@ txt_info_from_comment(char *in_line, gal_data_t **datall, 
char *comm_start,
             {
               type=gal_type_from_name(typestr);
               if(type==GAL_TYPE_INVALID)
-                type=GAL_TYPE_FLOAT64;
+                {
+                  /* See if this is a vector column (that has the format of
+                     'f32(N)' for example), by seeing if the last character
+                     is a parenthesis or not, we'll do the other checks in
+                     a dedicated function. */
+                  if(typestr[ strlen(typestr)-1 ] == ')')
+                    type=txt_info_vector_type(typestr, &repeat);
+
+                  /* No readable type, just set 64-bit float. */
+                  else type=GAL_TYPE_FLOAT64;
+                }
             }
         }
 
 
       /* Add this column's information into the columns linked list. We
-         will define the data structur's array to have zero dimensions (no
+         will define the data structure's array to have zero dimensions (no
          array) by default. If there is a blank value its value will be put
          into the array by 'gal_table_read_blank'. To keep the name, unit,
          and comment strings, trim the white space before and after each
          before using them here.  */
-      gal_list_data_add_alloc(datall, NULL, type, 0, NULL, NULL, 0, -1, 1,
-                              name, gal_txt_trim_space(unit),
+      gal_list_data_add_alloc(datall, NULL, type, 0, NULL, NULL, 0,
+                              repeat, 1, name, gal_txt_trim_space(unit),
                               gal_txt_trim_space(comment) );
 
 
@@ -335,7 +387,10 @@ txt_info_from_comment(char *in_line, gal_data_t **datall, 
char *comm_start,
 
    This function will return the number of tokens in the first row of the
    given text file. If the file is a text table with string columns, the
-   contents of the string column will be counted as one token.*/
+   contents of the string column will be counted as one token.
+
+   When there is no metadata, each token on the line is a separate column,
+   so the "repeat" is always one within this funciton. */
 static size_t
 txt_info_from_first_row(char *in_line, gal_data_t **datall, int format,
                         int inplace)
@@ -343,8 +398,8 @@ txt_info_from_first_row(char *in_line, gal_data_t **datall, 
int format,
   double tmpd;
   void *tmpdptr=&tmpd;
   gal_data_t *col, *prev, *tmp;
-  size_t ncol=0, maxcnum=0, numtokens;
   char *line, *token, *end, *aline=NULL;
+  size_t i, ncol, repeat=1, maxcnum=0, numchecked;
 
   /* Make a copy of the input line if necessary. */
   if(inplace) line=in_line;
@@ -375,6 +430,7 @@ txt_info_from_first_row(char *in_line, gal_data_t **datall, 
int format,
     maxcnum = maxcnum>col->status ? maxcnum : col->status;
 
   /* Go over the line check/fill the column information. */
+  ncol=0;
   while(++ncol)
     {
       /* If 'line' has already passed the end of the actual string (for
@@ -424,8 +480,14 @@ txt_info_from_first_row(char *in_line, gal_data_t 
**datall, int format,
             }
           else
             {
-              token=strtok_r(ncol==1?line:NULL, GAL_TXT_DELIMITERS, &line);
-              if(token==NULL) break;
+              /* Repeat is put in minmapsize (when we were reading the
+                 column info from comments) */
+              for(i=0;i<col->minmapsize;++i)
+                {
+                  token=strtok_r(ncol==1?line:NULL, GAL_TXT_DELIMITERS,
+                                 &line);
+                  if(token==NULL) break;
+                }
             }
         }
       else
@@ -458,38 +520,38 @@ txt_info_from_first_row(char *in_line, gal_data_t 
**datall, int format,
               /* Allocate this column's dataset and set it's 'status' to
                  the column number that it corresponds to. */
               gal_list_data_add_alloc(datall, NULL, GAL_TYPE_FLOAT64, 0,
-                                      NULL, NULL, 0, -1, 1, NULL, NULL,
+                                      NULL, NULL, 0, repeat, 1, NULL, NULL,
                                       NULL);
               (*datall)->status=ncol;
             }
         }
     }
 
-  /* When looking at a text table, 'n' is the number of columns (elements
-     in the linked list). But when looking at an image, it is the size of
-     the second dimension. To unify things from this step forwards, we will
-     thus keep the value of 'n' until this point in another variable (that
-     will be returned finally), and for an image, change 'n' to 1. This is
-     necsesary in case the user has for example given two column
-     information comments on an image plain text file.
-
-     Note that 'n' counts from 1, so the total number of tokens is one less
-     than 'n'.*/
-  numtokens=ncol-1;
+  /* When looking at a text table, 'ncol' is the number of columns
+     (elements in the linked list). But when looking at an image, it is the
+     size of the second dimension. To unify things from this step forwards,
+     we will thus keep the value of 'ncol' until this point in another
+     variable (that will be returned finally), and for an image, change
+     'ncol' to 1. This is necsesary in case the user has for example given
+     two column information comments on an image plain text file.
+
+     Note that 'ncol' counts from 1, so the total number of tokens is one
+     less than 'ncol'.*/
+  numchecked=ncol-1;
   if(format==TXT_FORMAT_IMAGE) ncol=1;
 
   /* If the number of columns/images given by the comments is larger than
      the actual number of lines, remove those that have larger numbers from
      the linked list before things get complicated outside of this
      function. */
-  if(maxcnum>numtokens)
+  if(maxcnum>numchecked)
     {
       prev=NULL;
       col=*datall;
       while(col!=NULL)
         {
           /* This column has no data (was only in comments) */
-          if(col->status > numtokens)
+          if(col->status > numchecked)
             {
               /* This column has to be removed/freed. But we have to make
                  some corrections before freeing it:
@@ -519,7 +581,7 @@ txt_info_from_first_row(char *in_line, gal_data_t **datall, 
int format,
 
   /* Return the total number of columns/second-img-dimension. */
   if(inplace==0) free(aline);
-  return numtokens;
+  return numchecked;
 }
 
 
@@ -543,17 +605,17 @@ txt_info_from_first_row(char *in_line, gal_data_t 
**datall, int format,
 static gal_data_t *
 txt_infoll_to_array(gal_data_t *datall, size_t *numdata)
 {
-  size_t numc=0, ind;
+  size_t i, numc=0, ind;
   gal_data_t *data, *dataarr;
 
   /* First find the total number of columns. */
-  for(data=datall; data!=NULL; data=data->next) ++numc;
+  numc=gal_list_data_number(datall);
 
   /* Conversion to an array is only necessary when there is more than one
      element in the list. */
   if(numc>1)
     {
-      /* Now, allocate the array and put in the values. */
+      /* Allocate the array. */
       dataarr=gal_data_array_calloc(numc);
 
       /* Put each dataset/column into its proper place in the array.  */
@@ -574,13 +636,14 @@ txt_infoll_to_array(gal_data_t *datall, size_t *numdata)
           dataarr[ind].name       = data->name;    data->name=NULL;
           dataarr[ind].unit       = data->unit;    data->unit=NULL;
           dataarr[ind].array      = data->array;   data->array=NULL;
-          dataarr[ind].dsize      = data->dsize;   data->dsize=NULL;
           dataarr[ind].comment    = data->comment; data->comment=NULL;
 
+          dataarr[ind].ndim       = 0;
+          dataarr[ind].size       = 0;
+          dataarr[ind].dsize      = NULL;
           dataarr[ind].type       = data->type;
-          dataarr[ind].ndim       = data->ndim;
-          dataarr[ind].size       = data->size;
           dataarr[ind].disp_width = data->disp_width;
+          dataarr[ind].minmapsize = data->minmapsize; /* "repeat" */
 
           /* Clean up. */
           gal_data_free(data);
@@ -589,6 +652,10 @@ txt_infoll_to_array(gal_data_t *datall, size_t *numdata)
   else
     dataarr=datall;
 
+  /* Set the 'next' pointer of each column. */
+  for(i=0;i<numc;++i)
+    dataarr[i].next = (i==numc-1) ? NULL : &dataarr[i+1];
+
   /* Return the array of all column information and put the number of
      columns into the given pointer. */
   *numdata=numc;
@@ -656,8 +723,8 @@ txt_get_info(char *filename, gal_list_str_t *lines, int 
format,
   /* Set the constant strings */
   switch(format)
     {
-    case TXT_FORMAT_TABLE: format_err="table"; comm_start="# Column "; break;
-    case TXT_FORMAT_IMAGE: format_err="image"; comm_start="# Image ";  break;
+    case TXT_FORMAT_TABLE: format_err="table";comm_start="# Column ";break;
+    case TXT_FORMAT_IMAGE: format_err="image";comm_start="# Image "; break;
     default:
       error(EXIT_FAILURE, 0, "%s: code %d not recognized",
             __func__, format);
@@ -765,9 +832,30 @@ gal_txt_image_info(char *filename, gal_list_str_t *lines, 
size_t *numimg,
 /************************************************************************/
 /***************             Read a txt table             ***************/
 /************************************************************************/
+static gal_data_t *
+txt_blocklist_add(gal_data_t *list, gal_data_t *newnode)
+{
+  newnode->block=list;
+  return newnode;
+}
+
+
+
+
+#if 0
+static size_t
+txt_blocklist_number(gal_data_t *list)
+{
+  size_t num=0;  while(list!=NULL) { ++num; list=list->block; }
+  return num;
+}
+#endif
+
+
+
 static void
 txt_read_token(gal_data_t *data, gal_data_t *info, char *token,
-               size_t i, char *filename, size_t lineno, size_t colnum)
+               size_t i, char *filename, size_t lineno, size_t toknum)
 {
   char   *tailptr, emptystr[1]="\0";
   char     **str = data->array, **strb;
@@ -922,31 +1010,31 @@ txt_read_token(gal_data_t *data, gal_data_t *info, char 
*token,
               && isdigit(*(tailptr-1))
               && *tailptr==':'
               && isdigit(*(tailptr+1)) )
-            error_at_line(EXIT_FAILURE, 0, filename, lineno, "column %zu "
+            error_at_line(EXIT_FAILURE, 0, filename, lineno, "token %zu "
                           "('%s') couldn't be read as a '%s' number.\n\n"
-                          "If it was meant to be celestial coordinates (RA "
-                          "or Dec), please use the '_h_m_' format for RA "
-                          "or '_d_m_' for Dec. The '_:_:_' format is "
-                          "ambiguous (can be used for both RA and Dec). "
-                          "Alternatively, you can use the column arithmetic "
-                          "operators 'ra-to-degree' or 'dec-to-degree' of "
-                          "'asttable' which also accept the '_:_:_' "
-                          "format. However, the 'ra-to-degree' or "
-                          "'dec-to-degree' operators require the column "
-                          "to be identified as a string with metadata. "
-                          "Please run the command below to learn more "
-                          "about column metadata and columns with string "
-                          "contents (it is easier to just use the '_h_m_' "
-                          "or '_d_m_' formats which will be automatically "
-                          "converted to degrees without any operators or "
-                          "metadata):\n\n"
-                          "   $ info gnuastro \"Gnuastro text table format\"",
-                          colnum, token,
+                          "If it was meant to be celestial coordinates "
+                          "(RA or Dec), please use the '_h_m_' format "
+                          "for RA or '_d_m_' for Dec. The '_:_:_' format "
+                          "is ambiguous (can be used for both RA and "
+                          "Dec). Alternatively, you can use the column "
+                          "arithmetic operators 'ra-to-degree' or "
+                          "'dec-to-degree' of 'asttable' which also "
+                          "accept the '_:_:_' format. However, the "
+                          "'ra-to-degree' or 'dec-to-degree' operators "
+                          "require the column to be identified as a "
+                          "string with metadata. Please run the command "
+                          "below to learn more about column metadata and "
+                          "columns with string contents (it is easier to "
+                          "just use the '_h_m_' or '_d_m_' formats which "
+                          "will be automatically converted to degrees "
+                          "without any operators or metadata):\n\n"
+                          "   $ info gnuastro \"Gnuastro text table\"",
+                          toknum, token,
                           gal_type_name(data->type, 1) );
           else
             error_at_line(EXIT_FAILURE, 0, filename, lineno, "column %zu "
                           "('%s') couldn't be read as a '%s' number",
-                          colnum, token, gal_type_name(data->type, 1) );
+                          toknum, token, gal_type_name(data->type, 1) );
         }
     }
 }
@@ -956,23 +1044,21 @@ txt_read_token(gal_data_t *data, gal_data_t *info, char 
*token,
 
 
 static void
-txt_fill(char *in_line, char **tokens, size_t maxcolnum,
-         gal_data_t *colinfo, gal_data_t *out, size_t rowind,
-         char *filename, size_t lineno, int inplace, int format)
+txt_fill(char *in_line, gal_data_t **tokeninout, size_t ntokforout,
+         gal_data_t **tokenininfo, size_t *tokenvecind,
+         size_t rowind, char *filename, size_t lineno, int inplace,
+         int format)
 {
-  gal_data_t *data;
+  gal_data_t *otmp;
   int notenoughcols=0;
-  size_t i, len, n=0, strwidth;
-  char *end, *line, *tmpstr, *aline=NULL;
+  size_t len, n=0, ind, strwidth;
+  char *end, *line, *aline, *tmpstr;
 
   /* Make a copy of the input line if necessary. */
   if(inplace) line=in_line;
-  else
-    {
-      gal_checkset_allocate_copy(in_line, &line);
-      aline=line; /* We are going to change 'line' during this function. */
-    }
+  else gal_checkset_allocate_copy(in_line, &line);
   end=line+strlen(line);
+  aline=line; /* The 'line' pointer will be shifted. */
 
   /* Remove the new-line character from the line. For more, see the top the
      explanations in 'txt_info_from_first_row': 13 is the ASCII code for
@@ -980,24 +1066,25 @@ txt_fill(char *in_line, char **tokens, size_t maxcolnum,
   if( end>line+2 && *(end-2)==13 ) *(end-2)='\0';
   else if( *(end-1)=='\n' )        *(end-1)='\0';
 
-  /* Start parsing the line. Note that 'n' and 'maxcolnum' start from one
-     when entering this loop on the first time. */
-  while(++n)
-    {
-      /* Break out of the parsing if we don't need the columns any
-         more. The table might contain many more columns, but when they
-         aren't needed, there is no point in tokenizing them. */
-      if(n>maxcolnum) break;
 
+  /* Start parsing the line, token by token. Break out of the parsing if we
+     don't need the columns any more. The table might contain many more
+     columns, but when they aren't needed, there is no point in tokenizing
+     them. Note that 'ntokforout' is the number of the last input token
+     that is used in the output, so it is inclusive. */
+  while(n<=ntokforout)
+    {
       /* Set the pointer to the start of this token/column. See
          explanations in 'txt_info_from_first_row'. Note that an image has
          a single 'info' element for the whole array, while a table has one
          for each column. */
-      if( colinfo[format==TXT_FORMAT_TABLE ? n-1 : 0].type == GAL_TYPE_STRING )
+      if( format==TXT_FORMAT_TABLE
+          && tokenininfo[n]->type == GAL_TYPE_STRING )
         {
           /* Remove any delimiters and stop at the first non-delimiter. If
              we have reached the end of the line then its an error, because
-             we were expecting a column here. */
+             we were expecting a column here (recall that empty lines are
+             skipped before reaching this point). */
           while(isspace(*line) || *line==',') ++line;
           if(*line=='\0') {notenoughcols=1; break;}
 
@@ -1013,72 +1100,311 @@ txt_fill(char *in_line, char **tokens, size_t 
maxcolnum,
              last column becomes larger than the actual length of the
              line. We should therefore first check how many characters we
              should actually copy (may be less than 'disp_width'). See
-             https://savannah.gnu.org/bugs/index.php?62720 */
-          strwidth=colinfo[n-1].disp_width;
-          len = (line+strwidth)<end ? strwidth : end-line;
-          tmpstr=gal_pointer_allocate(GAL_TYPE_UINT8, len+1, 0,
-                                      __func__, "tmpstr");
-          strncpy(tmpstr, line, len);
-          tmpstr[len]='\0';
-          tokens[n]=tmpstr;
-
-          /* Increment the line pointer beyond to the next token.*/
+             https://savannah.gnu.org/bugs/index.php?62720
+
+             If this token should be used, then its 'tokeninout' will be
+             non-NULL. */
+          strwidth=tokenininfo[n]->disp_width;
+          if(tokeninout[n])
+            {
+              /* Copy the full string column into a "standard" string
+                 (which terminates with a '\0'). */
+              len = (line+strwidth)<end ? strwidth : end-line;
+              tmpstr=gal_pointer_allocate(GAL_TYPE_UINT8, len+1, 0,
+                                          __func__, "tmpstr");
+              strncpy(tmpstr, line, len);
+              tmpstr[len]='\0';
+
+              /* Write it into all the output columns that need it (recall
+                 that if more than one output column needs a token, it is
+                 placed in the 'block' elements, 'next' is already
+                 assocated to the next column's pointer). */
+              for(otmp=tokeninout[n]; otmp!=NULL; otmp=otmp->block)
+                txt_read_token(otmp, tokenininfo[n], tmpstr, rowind,
+                               filename, lineno, n);
+
+              /* For a check.
+              printf("%s: Wrote '%s' into memory\n", __func__, tmpstr);
+              */
+
+              /* Clean up. */
+              free(tmpstr);
+            }
+
+          /* Increment the line pointer to the end of this string. */
           line += strwidth;
         }
       else
         {
           /* If we have reached the end of the line, then 'strtok_r' will
              return a NULL pointer. */
-          tmpstr=strtok_r(n==1?line:NULL, GAL_TXT_DELIMITERS, &line);
-          gal_checkset_allocate_copy(tmpstr, &tokens[n]);
-          if(tokens[n]==NULL) {notenoughcols=1; break;}
+          tmpstr=strtok_r(n==0?line:NULL, GAL_TXT_DELIMITERS, &line);
+          if(tmpstr==NULL) {notenoughcols=1; break;}
+
+          /* Convert and write the string to the desired output. */
+          if(format==TXT_FORMAT_TABLE)
+            {
+              ind = rowind * tokenininfo[n]->minmapsize + tokenvecind[n];
+              for(otmp=tokeninout[n]; otmp!=NULL; otmp=otmp->block)
+                txt_read_token(otmp, tokenininfo[n], tmpstr, ind,
+                               filename, lineno, n);
+            }
+          else /* An image */
+            txt_read_token(tokeninout[0], tokenininfo[0], tmpstr,
+                           rowind*tokeninout[0]->dsize[1]+n,
+                           filename, lineno, n);
+
+          /* For a check.
+          printf("%s: Wrote '%s' into memory\n", __func__, tmpstr);
+          */
         }
+
+      /* Increment the token counter. */
+      ++n;
     }
 
   /* Report an error if there weren't enough columns. */
   if(notenoughcols)
-    error_at_line(EXIT_FAILURE, 0, filename, lineno, "not enough columns in "
-                  "this line. Previous (uncommented) lines in this file had "
-                  "%zu columns, but this line has %zu columns", maxcolnum,
-                  n-1); /* This must be 'n-1' (since n starts from 1). */
-
-  /* For a sanity check:
-  printf("row: %zu: ", rowind+1);
-  for(n=1;n<=maxcolnum;++n) printf("-%s-, ", tokens[n]);
-  printf("\n");
+    error_at_line(EXIT_FAILURE, 0, filename, lineno, "not enough columns "
+                  "in this line");
+
+  /* Clean up. */
+  if(aline!=in_line) free(aline);
+}
+
+
+
+
+
+/* Allocate the datasets to help parse each token. */
+static void
+txt_read_prepare_alloc(gal_data_t ***tokeninout_out,
+                       gal_data_t ***tokenininfo_out,
+                       size_t **tokenvecind_out,
+                       size_t number)
+{
+  size_t *tokenvecind;
+  gal_data_t **tokeninout, **tokenininfo;
+
+  errno=0;
+  *tokeninout_out=tokeninout=calloc(number, sizeof *tokeninout);
+  if(tokeninout==NULL)
+    error(EXIT_FAILURE, errno, "%s: couldn't allocate %zu bytes for "
+          "'tokeninout'", __func__, number * sizeof *tokeninout);
+
+  errno=0;
+  *tokenininfo_out=tokenininfo=calloc(number, sizeof *tokenininfo);
+  if(tokenininfo==NULL)
+    error(EXIT_FAILURE, errno, "%s: couldn't allocate %zu bytes for "
+          "'tokenininfo'", __func__, number * sizeof *tokenininfo);
+
+  if(tokenvecind_out)
+    {
+      errno=0;
+      *tokenvecind_out=tokenvecind=calloc(number, sizeof *tokenvecind);
+      if(tokenvecind==NULL)
+        error(EXIT_FAILURE, errno, "%s: couldn't allocate %zu bytes for "
+              "'tokenvecind'", __func__, number * sizeof *tokenvecind);
+    }
+}
+
+
+
+
+
+static gal_data_t *
+txt_read_prepare_table(gal_data_t *info, size_t *indsize,
+                       gal_list_sizet_t *indexll, size_t minmapsize,
+                       int quietmmap, gal_data_t ***tokeninout_out,
+                       size_t *ntokforout, gal_data_t ***tokenininfo_out,
+                       size_t **tokenvecind_out)
+{
+  size_t *tokenvecind;
+  gal_list_sizet_t *ind;
+  size_t i, r, ndim, colc, tokc, repeat, ntokens=0, colendtok;
+  size_t dsize[2]={indsize[0]?indsize[0]:1,GAL_BLANK_SIZE_T};
+  gal_data_t *tmp, *idata, **tokeninout, **tokenininfo, *out=NULL;
+
+  /* Find how many tokens (columns, but before accounting for vectors)
+     there are in the input. Then allocate an array of 'gal_data_t *'
+     so we can keep track of which pre-vector-column should be put into
+     which output dataset. */
+  for(tmp=info; tmp!=NULL; tmp=tmp->next) ntokens+=tmp->minmapsize;
+  txt_read_prepare_alloc(tokeninout_out, tokenininfo_out, tokenvecind_out,
+                         ntokens);
+  tokenininfo=*tokenininfo_out;
+  tokenvecind=*tokenvecind_out;
+  tokeninout=*tokeninout_out;
+
+  /* Go over the requested columns from their index. */
+  for(ind=indexll; ind!=NULL; ind=ind->next)
+    {
+      /* To help in reading. */
+      idata=&info[ind->v];
+
+      /* Allocate the necessary space. If there are no rows, we are setting
+         a 1-element array to avoid any allocation errors (minmapsize,
+         which holds the "repeat", will be 1 for non-vector column). Then
+         we are freeing the allocated spaces and correcting the sizes.*/
+      ndim = (repeat=dsize[1]=idata->minmapsize)==1 ? 1 : 2;
+      gal_list_data_add_alloc(&out, NULL, idata->type, ndim, dsize,
+                              NULL, 0, minmapsize, quietmmap,
+                              idata->name, idata->unit, idata->comment);
+      out->disp_width=idata->disp_width;
+
+      /* If there were no actual rows ('numrows'==0), free the
+         allocated spaces and correct the size. */
+      if(indsize[0]==0)
+        {
+          out->size=0;
+          free(out->array);
+          free(out->dsize);
+          out->dsize=out->array=NULL;
+        }
+
+      /* Find the input token (of each line) that each input column starts
+         at. This needs special attention because vector columns can have
+         multiple tokens in one column. */
+      colc=tokc=0;
+      for(tmp=info; colc<ind->v; tmp=tmp->next)
+        { tokc+=tmp->minmapsize; ++colc; }
+
+      /* For a check:
+      printf("%s: input col %zu starts at token %-2zu and is %zu token(s) "
+             "wide [counts from 1]\n", __func__, ind->v+1, tokc+1,
+             repeat);
+      */
+
+      /* Set the pointer of this output dataset in the 'tokeninout'
+         array. If this token should be used in multiple output columns,
+         then add them to the 'block' pointer (which is not relevant here,
+         while 'next' is used to link the various columns). Note that all
+         elements of 'tokeninout' have been initialized to NULL with the
+         'calloc' function above, so we can safely use it as a list.*/
+      for(i=0;i<repeat;++i)
+        tokeninout[tokc+i]=txt_blocklist_add(tokeninout[tokc+i], out);
+    }
+
+  /* Reverse the list to be in the same order as the output. */
+  gal_list_data_reverse(&out);
+
+  /* Find the last input token that is useful for the output (to avoid
+     unnecessarily tokenizing and parsing the rest each line). If this
+     column shouldn't be read, then just put a pointer to its 'info'
+     structure, so we still know its metadata (to skip in the case of
+     strings or vectors). */
+  colc=r=0;
+  colendtok=info[colc].minmapsize;
+  for(tokc=0;tokc<ntokens;++tokc)
+    {
+      /* If we have reached the last token of this column, then increment
+         the column counter and its last token. */
+      if(tokc>=colendtok) {r=0; ++colc; colendtok+=info[colc].minmapsize;}
+
+      /* For a check:
+      printf("Token %-3zu belongs to column %zu\n", tokc+1, colc+1);
+      */
+
+      /* If this token should be read, everything has already been
+         allocated above, so just keep its counter to find the last
+         necessary token. */
+      if(tokeninout[tokc]) {*ntokforout=tokc; tokenvecind[tokc]=r;}
+      else tokenvecind[tokc]=GAL_BLANK_SIZE_T;
+
+      /* Set the pointer to the information list (necessary for all
+         columns, whether they are to be used or not). */
+      tokenininfo[tokc]=&info[colc];
+
+      /* Increment the repeat counter. */
+      ++r;
+    }
+
+  /* For a check (also un-comment the 'txt_blocklist_number' function).
+  printf("Input token --> number of output columns it is written to "
+         "[counts from 1]\n");
+  for(i=0;i<ntokens;++i)
+    printf("%-12zu --> %-5zu (vector: %zu)\n", i+1,
+           txt_blocklist_number(tokeninout[i]), tokenvecind[i]);
+  printf("Last usable token ('ntokforout'): %zu\n", *ntokforout+1);
   */
+  return out;
+}
+
+
+
+
+
+static gal_data_t *
+txt_read_prepare_img(gal_data_t *info, size_t *indsize,
+                     size_t minmapsize, int quietmmap,
+                     gal_data_t ***tokeninout_out, size_t *ntokforout,
+                     gal_data_t ***tokenininfo_out)
+{
+  gal_data_t *out;
+
+  /* Make sure that the input isn't a list. */
+  if(info->next)
+    error(EXIT_FAILURE, 0, "%s: currently reading only one image (2d "
+          "array) from a text file is possible, the 'info' input has "
+          "more than one element", __func__);
+
+  /* Allocate the output. */
+  out=gal_data_alloc(NULL, info->type, 2, indsize, NULL, 0,
+                     minmapsize, quietmmap, info->name, info->unit,
+                     info->comment);
+
+  /* Allocate the token reading pointers, set them and return. */
+  txt_read_prepare_alloc(tokeninout_out, tokenininfo_out, NULL, 1);
+  *ntokforout=out->dsize[1]-1;  /* Token counting begins from 0. */
+  (*tokenininfo_out)[0]=info;
+  (*tokeninout_out)[0]=out;
+  return out;
+}
+
+
+
 
-  /* Read the desired tokens into the columns that need them. Note that
-     when a blank value is defined for the column, the column's array
-     pointer ('colinfo[col->status-1]') is not NULL and points to the blank
-     value. For strings (or when the blank value is actually a string),
-     this will actually be a string. */
-  switch(out->ndim)
+
+static gal_data_t *
+txt_read_prepare(gal_data_t *info, size_t *indsize,
+                 gal_list_sizet_t *indexll, size_t minmapsize,
+                 int quietmmap, int format, char **line,
+                 size_t linelen, gal_data_t ***tokeninout,
+                 size_t *ntokforout, gal_data_t ***tokenininfo,
+                 size_t **tokenvecind)
+{
+  gal_data_t *out;
+
+  /* Allocate the output. */
+  switch(format)
     {
-    case 1:
-      for(data=out; data!=NULL; data=data->next)
-        txt_read_token(data, &colinfo[data->status-1], tokens[data->status],
-                       rowind, filename, lineno, data->status);
+    case TXT_FORMAT_TABLE:
+      out=txt_read_prepare_table(info, indsize, indexll, minmapsize,
+                                 quietmmap, tokeninout, ntokforout,
+                                 tokenininfo, tokenvecind);
       break;
-
-    case 2:
-      for(i=0;i<out->dsize[1];++i)
-        txt_read_token(out, colinfo, tokens[i+1], rowind * out->dsize[1] + i,
-                       filename, lineno, i+1);
+    case TXT_FORMAT_IMAGE:
+      *tokenvecind=NULL;     /* Not necessary in an image.         */
+      out=txt_read_prepare_img(info, indsize, minmapsize, quietmmap,
+                               tokeninout, ntokforout, tokenininfo);
       break;
-
-    default:
-      error(EXIT_FAILURE, 0, "%s: currently only 1 and 2 dimensional "
-            "datasets acceptable", __func__);
+    default: /* Format not recognized. */
+      error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at '%s' to "
+            "fix the problem. The format code %d is not recognized",
+            __func__, PACKAGE_BUGREPORT, format);
     }
 
-  /* Clean up the strings of each token within the tokens array, and set
-     the freed pointers to NULL. */
-  for(i=0;i<maxcolnum+1;++i)
-    if(tokens[i]) {free(tokens[i]); tokens[i]=NULL;}
+  /* Allocate the space necessary to keep a copy of each line as we parse
+     it. Note that 'getline' is going to later 'realloc' this space to fit
+     the line length. */
+  errno=0;
+  *line=malloc(linelen*sizeof *line);
+  if(*line==NULL)
+    error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for 'line'",
+          __func__, linelen*sizeof **line);
 
-  /* Clean up. */
-  if(inplace==0) free(aline);
+  /* Return the output dataset. */
+  return out;
 }
 
 
@@ -1086,18 +1412,16 @@ txt_fill(char *in_line, char **tokens, size_t maxcolnum,
 
 
 static gal_data_t *
-txt_read(char *filename, gal_list_str_t *lines, size_t *dsize,
+txt_read(char *filename, gal_list_str_t *lines, size_t *indsize,
          gal_data_t *info, gal_list_sizet_t *indexll, size_t minmapsize,
          int quietmmap, int format)
 {
   FILE *fp;
   int test;
   char *line;
-  char **tokens;
   gal_list_str_t *tmp;
-  gal_data_t *out=NULL;
-  gal_list_sizet_t *ind;
-  size_t one=1, maxcolnum=0, rowind=0, lineno=0, ndim;
+  size_t ntokforout=0, rowind=0, lineno=0, *tokenvecind;
+  gal_data_t *out=NULL, *ocol, **tokeninout, **tokenininfo;
   size_t linelen=10;        /* 'linelen' will be increased by 'getline'. */
 
   /* 'filename' and 'lines' cannot both be non-NULL. */
@@ -1107,82 +1431,13 @@ txt_read(char *filename, gal_list_str_t *lines, size_t 
*dsize,
           "arguments must be NULL, but they are both %s", __func__,
           test==2 ? "non-NULL" : "NULL");
 
-  /* Allocate the space necessary to keep a copy of each line as we parse
-     it. Note that 'getline' is going to later 'realloc' this space to fit
-     the line length. */
-  errno=0;
-  line=malloc(linelen*sizeof *line);
-  if(line==NULL)
-    error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for 'line'",
-          __func__, linelen*sizeof *line);
-
-  /* Allocate all the desired columns for output. We will be reading the
-     text file line by line, and writing in the necessary values of each
-     row individually. */
-  switch(format)
-    {
-
-    /* This is a table. */
-    case TXT_FORMAT_TABLE:
-      for(ind=indexll; ind!=NULL; ind=ind->next)
-        {
-          /* Allocate the necessary space. We are setting a 1-element array
-             to avoid any allocation errors. Then we are freeing the
-             allocated spaces and correcting the sizes.*/
-          ndim=1;
-          maxcolnum = maxcolnum>ind->v+1 ? maxcolnum : ind->v+1;
-          gal_list_data_add_alloc(&out, NULL, info[ind->v].type, ndim,
-                                  dsize[0]?dsize:&one, NULL, 0, minmapsize,
-                                  quietmmap, info[ind->v].name,
-                                  info[ind->v].unit, info[ind->v].comment);
-          out->disp_width=info[ind->v].disp_width;
-          out->status=ind->v+1;
-
-          /* If there were no actual rows (dsize[0]==0), free the allocated
-             spaces and correct the size. */
-          if(dsize[0]==0)
-            {
-              out->size=0;
-              free(out->array);
-              free(out->dsize);
-              out->dsize=out->array=NULL;
-            }
-        }
-      gal_list_data_reverse(&out);
-      break;
-
-
-    /* This is an image. */
-    case TXT_FORMAT_IMAGE:
-      if(info->next)
-        error(EXIT_FAILURE, 0, "%s: currently reading only one image (2d "
-              "array) from a text file is possible, the 'info' input has "
-              "more than one element", __func__);
-      ndim=2;
-      maxcolnum=dsize[1];
-      out=gal_data_alloc(NULL, info->type, ndim, dsize, NULL, 0, minmapsize,
-                         quietmmap, info->name, info->unit, info->comment);
-      break;
-
-
-    /* Not recognized. */
-    default:
-      error(EXIT_FAILURE, 0, "%s: format code %d not recognized",
-            __func__, format);
-    }
-
-  /* Allocate the space to keep the pointers to each token in the
-     line. This is done here to avoid having to allocate/free this array
-     for each line in 'txt_fill_columns'. Note that the column numbers are
-     counted from one (unlike indexes that are counted from zero), so we
-     need 'maxcolnum+1' elements in the array of tokens.*/
-  errno=0;
-  tokens=calloc(maxcolnum+1, sizeof *tokens);
-  if(tokens==NULL)
-    error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for 'tokens'",
-          __func__, (maxcolnum+1)*sizeof *tokens);
+  /* Necessary preparations/allocations */
+  out=txt_read_prepare(info, indsize, indexll, minmapsize, quietmmap,
+                       format, &line, linelen, &tokeninout, &ntokforout,
+                       &tokenininfo, &tokenvecind);
 
-  if(filename)
+  /* Read the input line by line. */
+  if(filename) /* Input from a file. */
     {
       /* Open the file. */
       errno=0;
@@ -1191,13 +1446,13 @@ txt_read(char *filename, gal_list_str_t *lines, size_t 
*dsize,
         error(EXIT_FAILURE, errno, "%s: couldn't open to read as a text "
               "table in %s", filename, __func__);
 
-      /* Read the data columns. */
+      /* Read the file, line by line. */
       while( getline(&line, &linelen, fp) != -1 )
         {
           ++lineno;
           if( gal_txt_line_stat(line) == GAL_TXT_LINESTAT_DATAROW )
-            txt_fill(line, tokens, maxcolnum, info, out, rowind++,
-                     filename, lineno, 1, format);
+            txt_fill(line, tokeninout, ntokforout, tokenininfo,
+                     tokenvecind, rowind++, filename, lineno, 1, format);
         }
 
       /* Clean up and close the file. */
@@ -1205,19 +1460,31 @@ txt_read(char *filename, gal_list_str_t *lines, size_t 
*dsize,
       if(fclose(fp))
         error(EXIT_FAILURE, errno, "%s: couldn't close file after reading "
               "ASCII table information in %s", filename, __func__);
-      free(line);
     }
-  else
+
+  else /* Input from standard input */
     for(tmp=lines; tmp!=NULL; tmp=tmp->next)
       {
+        /* To read for standard output, we are setting 'inplace' to zero
+           because there may only be a single copy of the input. */
         ++lineno;
         if( gal_txt_line_stat(tmp->v) == GAL_TXT_LINESTAT_DATAROW )
-          txt_fill(tmp->v, tokens, maxcolnum, info, out, rowind++,
-                   filename, lineno, 0, format);
+          txt_fill(tmp->v, tokeninout, ntokforout, tokenininfo,
+                   tokenvecind, rowind++, filename, lineno, 0, format);
       }
 
-  /* Clean up and return. */
-  free(tokens);
+  /* The 'block' pointer of the output datasets has been been used above if
+     an input column was used more than once in the output. It is no longer
+     necessary and being non-NULL can cause problems for the users of the
+     columns (because it has a special meaning in Gnuastro, outside of
+     tables, see 'lib/data.h'), so we should set them all to NULL.*/
+  for(ocol=out;ocol!=NULL;ocol=ocol->next) ocol->block=NULL;
+
+  /* Clean up the allocations of 'txt_read_prepare' and return. */
+  if(format==TXT_FORMAT_TABLE) free(tokeninout);
+  free(tokenininfo);
+  free(tokenvecind);
+  free(line);
   return out;
 }
 
@@ -1239,8 +1506,8 @@ gal_txt_table_read(char *filename, gal_list_str_t *lines, 
size_t numrows,
 
 
 gal_data_t *
-gal_txt_image_read(char *filename, gal_list_str_t *lines, size_t minmapsize,
-                   int quietmmap)
+gal_txt_image_read(char *filename, gal_list_str_t *lines,
+                   size_t minmapsize, int quietmmap)
 {
   size_t numimg, dsize[2];
   gal_data_t *img, *imginfo;
@@ -1319,7 +1586,7 @@ gal_txt_stdin_read(long timeout_microsec)
 {
   char *line;
   gal_list_str_t *out=NULL;
-  size_t lineno=0, linelen=10;/* 'linelen' will be increased by 'getline'. */
+  size_t lineno=0, linelen=10;/* 'getline' will increase 'linelen'. */
 
   /* Only continue if standard input has any contents. */
   if( txt_stdin_has_contents(timeout_microsec) )
@@ -1378,6 +1645,39 @@ gal_txt_stdin_read(long timeout_microsec)
 /************************************************************************/
 /***************              Write to txt                ***************/
 /************************************************************************/
+static void
+txt_fmts_for_printf_norm(gal_data_t *data, char *fmta, char *lng,
+                         char *fmt, int leftadjust)
+{
+  /* The space in the end of 'fmts[i*FMTS_COLS]' is to ensure that the
+     columns don't merge, even if the printed string is larger than the
+     expected width. */
+  if(data->disp_precision == GAL_BLANK_INT)
+    sprintf(fmta, "%%+%s%d%s%s ", leftadjust ? "-" : "",
+            data->disp_width, lng, fmt);
+  else
+    sprintf(fmta, "%%+%s%d.%d%s%s ", leftadjust ? "-" : "",
+            data->disp_width, data->disp_precision, lng, fmt);
+}
+
+
+
+
+
+static void
+txt_fmts_for_printf_last(int disp_precision, char *fmta, char *lng,
+                         char *fmt)
+{
+  if(disp_precision == GAL_BLANK_INT)
+    sprintf(fmta, "%%+%s%s", lng, fmt);
+  else
+    sprintf(fmta, "%%+.%d%s%s", disp_precision, lng, fmt);
+}
+
+
+
+
+
 /* Make an array of 3 strings for each column (in practice a two
    dimensional array with 3 columns in a row for each input column). The
    columns are:
@@ -1385,10 +1685,10 @@ gal_txt_stdin_read(long timeout_microsec)
      Column 0: Printf format string.
      Column 1: Gnuastro type string (in plain text format).
      Column 2: Blank value string.
-*/
-#define FMTS_COLS 3
+     Column 3: Format for last vector column. */
+#define FMTS_COLS 4
 static char **
-make_fmts_for_printf(gal_data_t *datall, int leftadjust, size_t *len)
+txt_fmts_for_printf(gal_data_t *datall, int leftadjust, int tab0_img1)
 {
   char **fmts;
   gal_data_t *data;
@@ -1403,9 +1703,6 @@ make_fmts_for_printf(gal_data_t *datall, int leftadjust, 
size_t *len)
     error(EXIT_FAILURE, errno, "%s: %zu bytes for fmts",
           __func__, FMTS_COLS*num*sizeof *fmts);
 
-  /* Initialize the length to 0. */
-  *len=0;
-
   /* Go over all the columns and make their formats. */
   for(data=datall;data!=NULL;data=data->next)
     {
@@ -1413,11 +1710,14 @@ make_fmts_for_printf(gal_data_t *datall, int 
leftadjust, size_t *len)
       errno=0;
       fmts[ i*FMTS_COLS   ] = malloc(GAL_TXT_MAX_FMT_LENGTH*sizeof **fmts);
       fmts[ i*FMTS_COLS+1 ] = malloc(GAL_TXT_MAX_FMT_LENGTH*sizeof **fmts);
-      if(fmts[i*FMTS_COLS]==NULL || fmts[i*FMTS_COLS+1]==NULL)
-        error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for fmts[%zu] "
-              "or fmts[%zu]", __func__, GAL_TXT_MAX_FMT_LENGTH*sizeof **fmts,
-              i*FMTS_COLS, i*FMTS_COLS+1);
-
+      fmts[ i*FMTS_COLS+3 ] = malloc(GAL_TXT_MAX_FMT_LENGTH*sizeof **fmts);
+      if( fmts[i*FMTS_COLS]==NULL
+          || fmts[i*FMTS_COLS+1]==NULL
+          || fmts[i*FMTS_COLS+3]==NULL )
+        error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for "
+              "fmts[%zu] or fmts[%zu]", __func__,
+              GAL_TXT_MAX_FMT_LENGTH*sizeof **fmts, i*FMTS_COLS,
+              i*FMTS_COLS+1);
 
       /* If we have a blank value, get the blank value as a string and
          adjust the width */
@@ -1425,41 +1725,15 @@ make_fmts_for_printf(gal_data_t *datall, int 
leftadjust, size_t *len)
                                 ? gal_blank_as_string(data->type, 0)
                                 : NULL );
 
-
       /* Fill in the printing paramters. */
       gal_tableintern_col_print_info(data, GAL_TABLE_FORMAT_TXT, fmt, lng);
 
-
       /* Adjust the width if a blank string was defined. */
       if(fmts[i*FMTS_COLS+2])
         data->disp_width = ( strlen(fmts[i*FMTS_COLS+2]) > data->disp_width
                              ? strlen(fmts[i*FMTS_COLS+2])
                              : data->disp_width );
 
-      /* Print the result into the allocated string and add its length to
-         the final length of the overall format statement. The space in the
-         end of 'fmts[i*2]' is to ensure that the columns don't merge, even
-         if the printed string is larger than the expected width. */
-      if(data->next)
-        {
-          if(data->disp_precision == GAL_BLANK_INT)
-            *len += 1 + sprintf(fmts[i*FMTS_COLS], "%%%s%d%s%s ",
-                                leftadjust ? "-" : "", data->disp_width,
-                                lng, fmt);
-          else
-            *len += 1 + sprintf(fmts[i*FMTS_COLS], "%%%s%d.%d%s%s ",
-                                leftadjust ? "-" : "", data->disp_width,
-                                data->disp_precision, lng, fmt);
-        }
-      else /* Last column: no empty characters (no width or adjustment). */
-        {
-          if(data->disp_precision == GAL_BLANK_INT)
-            *len += 1 + sprintf(fmts[i*FMTS_COLS], "%%%s%s", lng, fmt);
-          else
-            *len += 1 + sprintf(fmts[i*FMTS_COLS], "%%.%d%s%s",
-                                data->disp_precision, lng, fmt);
-        }
-
       /* Set the string for the Gnuastro type. For strings, we also need to
          write the maximum number of characters.*/
       if(data->type==GAL_TYPE_STRING)
@@ -1468,6 +1742,28 @@ make_fmts_for_printf(gal_data_t *datall, int leftadjust, 
size_t *len)
       else
         strcpy(fmts[i*FMTS_COLS+1], gal_type_name(data->type, 0));
 
+      /* Print the result into the allocated string. */
+      if(data->next) /* Not last column. */
+        txt_fmts_for_printf_norm(data, fmts[i*FMTS_COLS], lng, fmt,
+                                 leftadjust);
+      else /* Last column. */
+        {
+          /* For vector columns in a table that are also the last column,
+             we need both the normal format and the last column format.*/
+          if(data->ndim==2)
+            {
+              txt_fmts_for_printf_norm(data, fmts[i*FMTS_COLS], lng, fmt,
+                                       leftadjust);
+              txt_fmts_for_printf_last(data->disp_precision,
+                                       fmts[i*FMTS_COLS+3], lng, fmt);
+            }
+          else /* Last column is not a vector. */
+            {
+              txt_fmts_for_printf_last(data->disp_precision, fmts[i*FMTS_COLS],
+                                       lng, fmt);
+              fmts[i*FMTS_COLS+3][0]='\0';
+            }
+        }
 
       /* Increment the column counter. */
       ++i;
@@ -1482,33 +1778,35 @@ make_fmts_for_printf(gal_data_t *datall, int 
leftadjust, size_t *len)
 
 
 static void
-txt_print_value(FILE *fp, void *array, int type, size_t ind, char *fmt)
+txt_print_value(FILE *fp, gal_data_t *data, size_t ind, char *fmt)
 {
-  switch(type)
+  void *a=data->array;
+
+  switch(data->type)
     {
       /* Numerical types. */
-    case GAL_TYPE_UINT8:   fprintf(fp, fmt, ((uint8_t *) array)[ind]); break;
-    case GAL_TYPE_INT8:    fprintf(fp, fmt, ((int8_t *)  array)[ind]); break;
-    case GAL_TYPE_UINT16:  fprintf(fp, fmt, ((uint16_t *)array)[ind]); break;
-    case GAL_TYPE_INT16:   fprintf(fp, fmt, ((int16_t *) array)[ind]); break;
-    case GAL_TYPE_UINT32:  fprintf(fp, fmt, ((uint32_t *)array)[ind]); break;
-    case GAL_TYPE_INT32:   fprintf(fp, fmt, ((int32_t *) array)[ind]); break;
-    case GAL_TYPE_UINT64:  fprintf(fp, fmt, ((uint64_t *)array)[ind]); break;
-    case GAL_TYPE_INT64:   fprintf(fp, fmt, ((int64_t *) array)[ind]); break;
-    case GAL_TYPE_FLOAT32: fprintf(fp, fmt, ((float *)   array)[ind]); break;
-    case GAL_TYPE_FLOAT64: fprintf(fp, fmt, ((double *)  array)[ind]); break;
+    case GAL_TYPE_UINT8:   fprintf(fp, fmt, ((uint8_t *) a)[ind]); break;
+    case GAL_TYPE_INT8:    fprintf(fp, fmt, ((int8_t *)  a)[ind]); break;
+    case GAL_TYPE_UINT16:  fprintf(fp, fmt, ((uint16_t *)a)[ind]); break;
+    case GAL_TYPE_INT16:   fprintf(fp, fmt, ((int16_t *) a)[ind]); break;
+    case GAL_TYPE_UINT32:  fprintf(fp, fmt, ((uint32_t *)a)[ind]); break;
+    case GAL_TYPE_INT32:   fprintf(fp, fmt, ((int32_t *) a)[ind]); break;
+    case GAL_TYPE_UINT64:  fprintf(fp, fmt, ((uint64_t *)a)[ind]); break;
+    case GAL_TYPE_INT64:   fprintf(fp, fmt, ((int64_t *) a)[ind]); break;
+    case GAL_TYPE_FLOAT32: fprintf(fp, fmt, ((float *)   a)[ind]); break;
+    case GAL_TYPE_FLOAT64: fprintf(fp, fmt, ((double *)  a)[ind]); break;
 
       /* Special consideration for strings. */
     case GAL_TYPE_STRING:
-      if( !strcmp( ((char **)array)[ind], GAL_BLANK_STRING ) )
+      if( !strcmp( ((char **)a)[ind], GAL_BLANK_STRING ) )
         fprintf(fp, fmt, GAL_BLANK_STRING);
       else
-        fprintf(fp, fmt, ((char **)array)[ind]);
+        fprintf(fp, fmt, ((char **)a)[ind]);
       break;
 
     default:
       error(EXIT_FAILURE, 0, "%s: type code %d not recognized",
-            __func__, type);
+            __func__, data->type);
     }
 }
 
@@ -1517,12 +1815,13 @@ txt_print_value(FILE *fp, void *array, int type, size_t 
ind, char *fmt)
 
 
 static void
-txt_write_metadata(FILE *fp, gal_data_t *datall, char **fmts)
+txt_write_metadata(FILE *fp, gal_data_t *datall, char **fmts,
+                   int tab0_img1)
 {
   gal_data_t *data;
-  char *tmp, *nstr;
   size_t i, j, num=0;
-  int nlen, nw=0, uw=0, tw=0, bw=0;
+  char *tmp, *nstr, *tstr;
+  int nlen, twt, nw=0, uw=0, tw=0, bw=0;
 
   /* Get the maximum width for each information field. */
   for(data=datall;data!=NULL;data=data->next)
@@ -1531,12 +1830,24 @@ txt_write_metadata(FILE *fp, gal_data_t *datall, char 
**fmts)
       if( data->name && strlen(data->name)>nw ) nw=strlen(data->name);
       if( data->unit && strlen(data->unit)>uw ) uw=strlen(data->unit);
     }
+  data=datall;
   for(i=0;i<num;++i)
     {
-      if( (tmp=fmts[ i*FMTS_COLS+1 ]) )            /* If it isn't NULL. */
-        tw = strlen(tmp) > tw ? strlen(tmp) : tw;
-      if( (tmp=fmts[ i*FMTS_COLS+2 ]) )            /* If it isn't NULL. */
+      /* Width of blank element. */
+      if( (tmp=fmts[ i*FMTS_COLS+2 ]) )      /* If it isn't NULL. */
         bw = strlen(tmp) > bw ? strlen(tmp) : bw;
+
+      /* Width of type element. */
+      if( (tmp=fmts[ i*FMTS_COLS+1 ]) )      /* If it isn't NULL. */
+        {
+          twt=strlen(tmp);
+          if(tab0_img1==0 && data->ndim==2)        /* +1 for 0 to 10. */
+            twt+=(int)(log10(data->dsize[1]))+1+2; /* +2 for the '()'.*/
+          tw = twt > tw ? twt : tw;
+        }
+
+      /* Go onto the next data element. */
+      data=data->next;
     }
 
 
@@ -1563,14 +1874,24 @@ txt_write_metadata(FILE *fp, gal_data_t *datall, char 
**fmts)
       for(j=1;j<nlen;++j)
         if(!isdigit(nstr[j])) nstr[j] = isdigit(nstr[j-1]) ? ':' : ' ';
 
+      /* For the type, we need to account for vector clumns. */
+      if(tab0_img1==0 && data->ndim==2)
+        { if( asprintf(&tstr, "%s(%zu)", fmts[i*FMTS_COLS+1],
+                       data->dsize[1])<0 )
+            error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__); }
+      else
+        { if( asprintf(&tstr, "%s", fmts[i*FMTS_COLS+1])<0 )
+            error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__); }
+
       /* Now print the full information. */
       fprintf(fp, "# %s %s %-*s [%-*s,%-*s,%-*s] %s\n",
-              datall->ndim==1 ? "Column" : "Image", nstr,
+              tab0_img1 ? "Image" : "Column", nstr,
               nw, data->name ? data->name    : "",
               uw, data->unit ? data->unit    : "",
-              tw, fmts[i*FMTS_COLS+1] ? fmts[i*FMTS_COLS+1] : "",
+              tw, fmts[i*FMTS_COLS+1] ? tstr : "",
               bw, fmts[i*FMTS_COLS+2] ? fmts[i*FMTS_COLS+2] : "",
               data->comment ? data->comment : "");
+      free(tstr);
       ++i;
     }
 
@@ -1659,13 +1980,13 @@ 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_list_str_t *comment, char *filename,
-              uint8_t colinfoinstdout)
+              uint8_t colinfoinstdout, int tab0_img1)
 {
   FILE *fp;
   char **fmts;
   gal_list_str_t *strt;
-  size_t i, j, num=0, fmtlen;
-  gal_data_t *data, *next2d=NULL;
+  size_t i, j, k, num=0, d1;
+  gal_data_t *data, *nextimg=NULL;
 
   /* Make sure input is valid. */
   if(input==NULL) error(EXIT_FAILURE, 0, "%s: input is NULL", __func__);
@@ -1678,12 +1999,12 @@ gal_txt_write(gal_data_t *input, struct 
gal_fits_list_key_t **keylist,
           __func__, input->ndim);
 
 
-  /* For a 2D dataset, we currently don't accept a list, we can only print
-     one column. So keep the next pointer separately and restore it after
-     the job of this function is finished. */
-  if(input->ndim==2)
+  /* For an image, we currently don't accept a list, we can only print one
+     column. So keep the next pointer separately and restore it after the
+     job of this function is finished. */
+  if(tab0_img1)
     {
-      next2d=input->next;
+      nextimg=input->next;
       input->next=NULL;
     }
 
@@ -1696,8 +2017,12 @@ gal_txt_write(gal_data_t *input, struct 
gal_fits_list_key_t **keylist,
       ++num;
 
       /* Check if the dimensionality and size is the same for all the
-         elements. */
-      if( input!=data && gal_dimension_is_different(input, data) )
+         elements. The 'input->dsize && data->dsize' conditions are because
+         we may have fully empty tables (where 'dsize==NULL'). In this
+         case, we want to continue with printing, and there is no
+         problem.*/
+      if( input!=data && input->dsize && data->dsize
+          && input->dsize[0]!=data->dsize[0] )
         error(EXIT_FAILURE, 0, "%s: the input list of datasets must "
               "have the same sizes (dimensions and length along each "
               "dimension)", __func__);
@@ -1706,7 +2031,7 @@ gal_txt_write(gal_data_t *input, struct 
gal_fits_list_key_t **keylist,
 
   /* Prepare the necessary formats for each column, then allocate the space
      for the full list and concatenate all the separate inputs into it. */
-  fmts=make_fmts_for_printf(input, 1, &fmtlen);
+  fmts=txt_fmts_for_printf(input, 1, tab0_img1);
 
 
   /* Set the output FILE pointer: if it isn't NULL, its an actual file,
@@ -1737,39 +2062,46 @@ gal_txt_write(gal_data_t *input, struct 
gal_fits_list_key_t **keylist,
   else
     fp=stdout;
 
+
   /* Write the meta-data if necessary. */
   if(filename ? 1 : colinfoinstdout)
-    txt_write_metadata(fp, input, fmts);
-
-  /* Print the dataset */
-  switch(input->ndim)
-    {
-    case 1:
-      for(i=0;i<input->size;++i)                        /* Row.    */
-        {
-          j=0;
-          for(data=input;data!=NULL;data=data->next)    /* Column. */
-            txt_print_value(fp, data->array, data->type, i,
-                            fmts[j++ * FMTS_COLS]);
-          fprintf(fp, "\n");
-        }
-      break;
+    txt_write_metadata(fp, input, fmts, tab0_img1);
 
 
-    case 2:
-      for(i=0;i<input->dsize[0];++i)
+  /* Print row-by-row (if we actually have data to print! */
+  if(input->array)
+    {
+      if(tab0_img1) /* Image. */
+        for(i=0;i<input->dsize[0];++i)
+          {
+            d1=input->dsize[1];
+            for(j=0;j<d1;++j)
+              txt_print_value(fp, input, i*d1+j, fmts[j==d1-1 ? 3 : 0]);
+            fprintf(fp, "\n");
+          }
+      else /* Table. */
         {
-          for(j=0;j<input->dsize[1];++j)
-            txt_print_value(fp, input->array, input->type,
-                            i*input->dsize[1]+j, fmts[0]);
-          fprintf(fp, "\n");
+          for(i=0;i<input->dsize[0];++i)                  /* Row.    */
+            {
+              k=0; /* Column counter. */
+              for(data=input;data!=NULL;data=data->next)  /* Column. */
+                {
+                  if(data->ndim>1)  /* Vector column. */
+                    {
+                      d1=data->dsize[1];
+                      for(j=0;j<d1;++j)
+                        txt_print_value(fp, data, i*d1+j,
+                          fmts[ k * FMTS_COLS
+                      /* Last of vector column has a different format. */
+                                + (j==d1-1 && data->next==NULL ? 3 : 0) ]);
+                    }
+                  else /* Non-vector column: simple! */
+                    txt_print_value(fp, data, i, fmts[k * FMTS_COLS]);
+                  ++k;
+                }
+              fprintf(fp, "\n");
+            }
         }
-      break;
-
-
-    default:
-      error(EXIT_FAILURE, 0, "%s: a bug! input->ndim=%zu is not recognized",
-            __func__, input->ndim);
     }
 
 
@@ -1779,6 +2111,7 @@ gal_txt_write(gal_data_t *input, struct 
gal_fits_list_key_t **keylist,
       free(fmts[i*FMTS_COLS]);
       free(fmts[i*FMTS_COLS+1]);
       free(fmts[i*FMTS_COLS+2]);
+      free(fmts[i*FMTS_COLS+3]);
     }
   free(fmts);
 
@@ -1788,10 +2121,10 @@ gal_txt_write(gal_data_t *input, struct 
gal_fits_list_key_t **keylist,
     {
       errno=0;
       if(fclose(fp))
-        error(EXIT_FAILURE, errno, "%s: couldn't close file after writing "
-              "of text table in %s", filename, __func__);
+        error(EXIT_FAILURE, errno, "%s: couldn't close file after "
+              "writing of text table in %s", filename, __func__);
     }
 
-  /* Restore the next pointer for a 2D dataset. */
-  if(input->ndim==2) input->next=next2d;
+  /* Restore the next pointer for an image. */
+  if(nextimg) input->next=nextimg;
 }
diff --git a/tests/during-dev.sh b/tests/during-dev.sh
index 7b80f889..8ff0969e 100755
--- a/tests/during-dev.sh
+++ b/tests/during-dev.sh
@@ -71,7 +71,7 @@
 # space characters in them, quote the full value
 numjobs=8
 builddir=build
-outdir=
+outdir=~/tmp
 
 
 
@@ -82,10 +82,9 @@ outdir=
 # script, and once for the utility. In such cases it might be easier to
 # just add the argument/option to the final script that runs the utility
 # rather than these variables.
-utilname=
-arguments=
-options=
-
+utilname=table
+arguments=vec.fits
+options="-YO --transpose"
 
 
 # RUN THE PROCEDURES
@@ -196,6 +195,7 @@ if make -j$numjobs -C "$builddir"; then
 
     # Run the built utility with the given arguments and options.
     "$utility" $arguments $options $extraopts
+    #"$utility" table.fits
 
     # Clean up.
     rm -rf .gnuastro
diff --git a/tests/match/sort-based.sh b/tests/match/sort-based.sh
index 8b0adea5..5fd29d41 100755
--- a/tests/match/sort-based.sh
+++ b/tests/match/sort-based.sh
@@ -54,6 +54,6 @@ if [ ! -f $execname ]; then echo "$execname not created."; 
exit 77; fi
 # 'check_with_program' can be something like Valgrind or an empty
 # string. Such programs will execute the command if present and help in
 # debugging when the developer doesn't have access to the user's system.
-$check_with_program $execname $cat1 $cat2 --aperture=0.5 --log \
+$check_with_program $execname $cat1 $cat2 --aperture=0.5  \
                               --ccol1=2,3 --ccol2=2,3 --kdtree=disable \
                               --output=match-sort-based.fits
diff --git a/tests/script/psf-select-stars.sh b/tests/script/psf-select-stars.sh
index ceebba72..cb3723d7 100755
--- a/tests/script/psf-select-stars.sh
+++ b/tests/script/psf-select-stars.sh
@@ -82,4 +82,4 @@ $check_with_program $execname $fits1name --hdu=1 \
                               --mindistdeg=0.05 \
                               --matchaperturedeg=0.5 \
                               --output=$prog.fits \
-                              --tmpdir=tmpdir-$prog
+                              --tmpdir=tmpdir-$prog --keeptmp
diff --git a/tests/table/table.txt b/tests/table/table.txt
index 9712b6d7..18ee9bec 100644
--- a/tests/table/table.txt
+++ b/tests/table/table.txt
@@ -5,8 +5,8 @@
 #  - Blank values in all the columns.
 #  - Empty lines (not comment or data.
 #  - Column information comments are not in the same order as data.
-#  - A column (11, i.e., the last) with no information.
-#  - Columns with missing information
+#  - A column (12, i.e., the last) with no information.
+#  - A vector column.
 #  - Some blank values different from the internal blank values.
 
 # Column 10: DOUBLE      [no units, f64, 255]  Column with double values
@@ -22,6 +22,7 @@
 
 # Column 6:  UINT32 [,u32]
 
+# Column 11: VECTOR [no-units, f32(3)] Each row has three values
 
 # IMPORTANT NOTE FOR FITS ASCII tables: CFITSIO will give its error
 # 412 (data type conversion overflow) when the number cannot be
@@ -37,15 +38,15 @@
 # this notice are preserved.  This file is offered as-is, without any
 # warranty.
 
-1    -1    The vertical lines     3455  -3455  1  -1   9     1874.103872  
8.391334343995    1
-2    -2    are only for easy      3466      -3466  2  -2   10    123.93373    
893.3497e5        2
-3    -3    visual identification  3467 -3467  3  -3   12    -0.737648    nan   
            3
-4    -4    of the limits of this  3468  -3468  4  -4   800   78.983       
8.2328438e8       4
-5    -5    |string column.     |  3469       -3469  5  -5   8923  -99       
-7.32384e4        5
-6    -6    |characters beyond  |  20821  -20821  6  -6   9823  -99     nan     
          6
-7    -7    the last one will be   20822  -20822  7  -7   7232  9999   
8.3343e-5         7
-8    -8    read as a number.     20823  -20823  8  -8   921   2.3     
934892734.82      8
-9    -9    With@Some#!STRANGE@    60983  -25722  9  -9   8127  -99          
3474924.489252    9
-10  -10    Characters%^&*()~           62182  -30100 10  -10  8287  7.3e-4     
  -3467.3432e5      10
-11  -12 no data                65500  -32700 11  -11  999    8.73E2       nan  
             11
-255 -12             -- Last Line :-) --    65501  -32701 12  -12  8282  892.23 
      8975.3653      12
+1    -1    The vertical lines     3455  -3455  1  -1   9     1874.103872  
8.391334343995         +0.672147   +1.806462  -1.191592    1
+2    -2    are only for easy      3466      -3466  2  -2   10    123.93373    
893.3497e5         +5.600531   +3.342579  +4.003947    2
+3    -3    visual identification  3467 -3467  3  -3   12    -0.737648    nan   
                  +8.286964   +1.593513  -11.908021   3
+4    -4    of the limits of this  3468  -3468  4  -4   800   78.983       
8.2328438e8            +1.831690   -0.370989  +10.096006   4
+5    -5    |string column.     |  3469       -3469  5  -5   8923  -99       
-7.32384e4           -16.008546  +7.949368  +2.401607    5
+6    -6    |characters beyond  |  20821  -20821  6  -6   9823  -99     nan     
                  +1.724322   +1.178122  -1.299836    6
+7    -7    the last one will be   20822  -20822  7  -7   7232  9999   
8.3343e-5                  +2.044397   +2.859421  -2.17172     7
+8    -8    read as a number.     20823  -20823  8  -8   921   2.3     
934892734.82               +9.722353   -5.720703  +4.191695    8
+9    -9    With@Some#!STRANGE@    60983  -25722  9  -9   8127  -99          
3474924.489252       +2.304411   +6.798331  -1.780500    9
+10  -10    Characters%^&*()~           62182  -30100 10  -10  8287  7.3e-4     
  -3467.3432e5    +1.537032   -5.253743  +3.832875    10
+11  -12 no data                65500  -32700 11  -11  999    8.73E2       nan  
                  +9.388170   -2.710523  +2.262376    11
+255 -12             -- Last Line :-) --    65501  -32701 12  -12  8282  892.23 
      8975.3653   -10.782658  +2.881371  +0.951901    12



reply via email to

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